Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Hydrawise initialize data immediately #101936

Merged
merged 4 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 5 additions & 21 deletions homeassistant/components/hydrawise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@


from pydrawise import legacy
from requests.exceptions import ConnectTimeout, HTTPError
import voluptuous as vol

from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
Expand All @@ -13,11 +12,10 @@
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType

from .const import DOMAIN, LOGGER, SCAN_INTERVAL
from .const import DOMAIN, SCAN_INTERVAL
from .coordinator import HydrawiseDataUpdateCoordinator

CONFIG_SCHEMA = vol.Schema(
Expand Down Expand Up @@ -53,24 +51,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up Hydrawise from a config entry."""
access_token = config_entry.data[CONF_API_KEY]
try:
hydrawise = await hass.async_add_executor_job(
legacy.LegacyHydrawise, access_token
)
except (ConnectTimeout, HTTPError) as ex:
LOGGER.error("Unable to connect to Hydrawise cloud service: %s", str(ex))
raise ConfigEntryNotReady(
f"Unable to connect to Hydrawise cloud service: {ex}"
) from ex

hass.data.setdefault(DOMAIN, {})[
config_entry.entry_id
] = HydrawiseDataUpdateCoordinator(hass, hydrawise, SCAN_INTERVAL)
if not hydrawise.controller_info or not hydrawise.controller_status:
raise ConfigEntryNotReady("Hydrawise data not loaded")

# NOTE: We don't need to call async_config_entry_first_refresh() because
# data is fetched when the Hydrawiser object is instantiated.
hydrawise = legacy.LegacyHydrawise(access_token, load_on_init=False)
coordinator = HydrawiseDataUpdateCoordinator(hass, hydrawise, SCAN_INTERVAL)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
return True

Expand Down
11 changes: 4 additions & 7 deletions homeassistant/components/hydrawise/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

from .const import DOMAIN, LOGGER
from .const import DOMAIN
from .coordinator import HydrawiseDataUpdateCoordinator
from .entity import HydrawiseEntity

Expand Down Expand Up @@ -95,13 +95,10 @@ async def async_setup_entry(
class HydrawiseBinarySensor(HydrawiseEntity, BinarySensorEntity):
"""A sensor implementation for Hydrawise device."""

@callback
def _handle_coordinator_update(self) -> None:
"""Get the latest data and updates the state."""
LOGGER.debug("Updating Hydrawise binary sensor: %s", self.name)
def _update_attrs(self) -> None:
"""Update state attributes."""
if self.entity_description.key == "status":
self._attr_is_on = self.coordinator.last_update_success
elif self.entity_description.key == "is_watering":
relay_data = self.coordinator.api.relays_by_zone_number[self.data["relay"]]
self._attr_is_on = relay_data["timestr"] == "Now"
super()._handle_coordinator_update()
12 changes: 12 additions & 0 deletions homeassistant/components/hydrawise/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from typing import Any

from homeassistant.core import callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity
Expand Down Expand Up @@ -36,3 +37,14 @@ def __init__(
name=data["name"],
manufacturer=MANUFACTURER,
)
self._update_attrs()

def _update_attrs(self) -> None:
"""Update state attributes."""
return # pragma: no cover

@callback
def _handle_coordinator_update(self) -> None:
"""Get the latest data and updates the state."""
self._update_attrs()
super()._handle_coordinator_update()
12 changes: 4 additions & 8 deletions homeassistant/components/hydrawise/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_MONITORED_CONDITIONS, UnitOfTime
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import dt as dt_util

from .const import DOMAIN, LOGGER
from .const import DOMAIN
from .coordinator import HydrawiseDataUpdateCoordinator
from .entity import HydrawiseEntity

Expand Down Expand Up @@ -82,10 +82,8 @@ async def async_setup_entry(
class HydrawiseSensor(HydrawiseEntity, SensorEntity):
"""A sensor implementation for Hydrawise device."""

@callback
def _handle_coordinator_update(self) -> None:
"""Get the latest data and updates the states."""
LOGGER.debug("Updating Hydrawise sensor: %s", self.name)
def _update_attrs(self) -> None:
"""Update state attributes."""
relay_data = self.coordinator.api.relays_by_zone_number[self.data["relay"]]
if self.entity_description.key == "watering_time":
if relay_data["timestr"] == "Now":
Expand All @@ -94,8 +92,6 @@ def _handle_coordinator_update(self) -> None:
self._attr_native_value = 0
else: # _sensor_type == 'next_cycle'
next_cycle = min(relay_data["time"], TWO_YEAR_SECONDS)
LOGGER.debug("New cycle time: %s", next_cycle)
self._attr_native_value = dt_util.utc_from_timestamp(
dt_util.as_timestamp(dt_util.now()) + next_cycle
)
super()._handle_coordinator_update()
10 changes: 3 additions & 7 deletions homeassistant/components/hydrawise/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
Expand All @@ -23,7 +23,6 @@
CONF_WATERING_TIME,
DEFAULT_WATERING_TIME,
DOMAIN,
LOGGER,
)
from .coordinator import HydrawiseDataUpdateCoordinator
from .entity import HydrawiseEntity
Expand Down Expand Up @@ -124,14 +123,11 @@ def turn_off(self, **kwargs: Any) -> None:
elif self.entity_description.key == "auto_watering":
self.coordinator.api.suspend_zone(365, zone_number)

@callback
def _handle_coordinator_update(self) -> None:
"""Update device state."""
def _update_attrs(self) -> None:
"""Update state attributes."""
zone_number = self.data["relay"]
LOGGER.debug("Updating Hydrawise switch: %s", self.name)
timestr = self.coordinator.api.relays_by_zone_number[zone_number]["timestr"]
if self.entity_description.key == "manual_watering":
self._attr_is_on = timestr == "Now"
elif self.entity_description.key == "auto_watering":
self._attr_is_on = timestr not in {"", "Now"}
super()._handle_coordinator_update()
5 changes: 0 additions & 5 deletions tests/components/hydrawise/test_binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ async def test_states(
freezer: FrozenDateTimeFactory,
) -> None:
"""Test binary_sensor states."""
# Make the coordinator refresh data.
freezer.tick(SCAN_INTERVAL + timedelta(seconds=30))
async_fire_time_changed(hass)
await hass.async_block_till_done()

connectivity = hass.states.get("binary_sensor.home_controller_connectivity")
assert connectivity is not None
assert connectivity.state == "on"
Expand Down
34 changes: 14 additions & 20 deletions tests/components/hydrawise/test_init.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Tests for the Hydrawise integration."""

from unittest.mock import Mock, patch
from unittest.mock import Mock

from requests.exceptions import HTTPError

Expand All @@ -15,6 +15,7 @@

async def test_setup_import_success(hass: HomeAssistant, mock_pydrawise: Mock) -> None:
"""Test that setup with a YAML config triggers an import and warning."""
mock_pydrawise.update_controller_info.return_value = True
mock_pydrawise.customer_id = 12345
mock_pydrawise.status = "unknown"
config = {"hydrawise": {CONF_ACCESS_TOKEN: "_access-token_"}}
Expand All @@ -29,29 +30,22 @@ async def test_setup_import_success(hass: HomeAssistant, mock_pydrawise: Mock) -


async def test_connect_retry(
dknowles2 marked this conversation as resolved.
Show resolved Hide resolved
hass: HomeAssistant, mock_config_entry: MockConfigEntry
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_pydrawise: Mock
) -> None:
"""Test that a connection error triggers a retry."""
with patch("pydrawise.legacy.LegacyHydrawise") as mock_api:
mock_api.side_effect = HTTPError
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_api.assert_called_once()
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
mock_pydrawise.update_controller_info.side_effect = HTTPError
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.SETUP_RETRY


async def test_setup_no_data(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_pydrawise: Mock
) -> None:
"""Test that no data from the API triggers a retry."""
with patch("pydrawise.legacy.LegacyHydrawise") as mock_api:
mock_api.return_value.controller_info = {}
mock_api.return_value.controller_status = None
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_api.assert_called_once()
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
mock_pydrawise.update_controller_info.return_value = False
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.SETUP_RETRY
12 changes: 2 additions & 10 deletions tests/components/hydrawise/test_sensor.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
"""Test Hydrawise sensor."""

from datetime import timedelta

from freezegun.api import FrozenDateTimeFactory
import pytest

from homeassistant.components.hydrawise.const import SCAN_INTERVAL
from homeassistant.core import HomeAssistant

from tests.common import MockConfigEntry, async_fire_time_changed
from tests.common import MockConfigEntry


@pytest.mark.freeze_time("2023-10-01 00:00:00+00:00")
Expand All @@ -18,11 +15,6 @@ async def test_states(
freezer: FrozenDateTimeFactory,
) -> None:
"""Test sensor states."""
# Make the coordinator refresh data.
freezer.tick(SCAN_INTERVAL + timedelta(seconds=30))
async_fire_time_changed(hass)
await hass.async_block_till_done()

watering_time1 = hass.states.get("sensor.zone_one_watering_time")
assert watering_time1 is not None
assert watering_time1.state == "0"
Expand All @@ -33,4 +25,4 @@ async def test_states(

next_cycle = hass.states.get("sensor.zone_one_next_cycle")
assert next_cycle is not None
assert next_cycle.state == "2023-10-04T19:52:27+00:00"
assert next_cycle.state == "2023-10-04T19:49:57+00:00"
9 changes: 1 addition & 8 deletions tests/components/hydrawise/test_switch.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
"""Test Hydrawise switch."""

from datetime import timedelta
from unittest.mock import Mock

from freezegun.api import FrozenDateTimeFactory

from homeassistant.components.hydrawise.const import SCAN_INTERVAL
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON
from homeassistant.core import HomeAssistant

from tests.common import MockConfigEntry, async_fire_time_changed
from tests.common import MockConfigEntry


async def test_states(
Expand All @@ -19,11 +17,6 @@ async def test_states(
freezer: FrozenDateTimeFactory,
) -> None:
"""Test switch states."""
# Make the coordinator refresh data.
freezer.tick(SCAN_INTERVAL + timedelta(seconds=30))
async_fire_time_changed(hass)
await hass.async_block_till_done()

watering1 = hass.states.get("switch.zone_one_manual_watering")
assert watering1 is not None
assert watering1.state == "off"
Expand Down