Skip to content

Commit

Permalink
Use the new hybrid Hydrawise client (#136522)
Browse files Browse the repository at this point in the history
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
  • Loading branch information
dknowles2 and joostlek authored Jan 29, 2025
1 parent 04d1d80 commit b73203f
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 48 deletions.
19 changes: 12 additions & 7 deletions homeassistant/components/hydrawise/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""Support for Hydrawise cloud."""

from pydrawise import auth, client
from pydrawise import auth, hybrid

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.const import CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed

Expand All @@ -21,16 +21,21 @@
Platform.VALVE,
]

_REQUIRED_AUTH_KEYS = (CONF_USERNAME, CONF_PASSWORD, CONF_API_KEY)


async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up Hydrawise from a config entry."""
if CONF_USERNAME not in config_entry.data or CONF_PASSWORD not in config_entry.data:
# The GraphQL API requires username and password to authenticate. If either is
# missing, reauth is required.
if any(k not in config_entry.data for k in _REQUIRED_AUTH_KEYS):
# If we are missing any required authentication keys, trigger a reauth flow.
raise ConfigEntryAuthFailed

hydrawise = client.Hydrawise(
auth.Auth(config_entry.data[CONF_USERNAME], config_entry.data[CONF_PASSWORD]),
hydrawise = hybrid.HybridClient(
auth.HybridAuth(
config_entry.data[CONF_USERNAME],
config_entry.data[CONF_PASSWORD],
config_entry.data[CONF_API_KEY],
),
app_id=APP_ID,
)

Expand Down
42 changes: 30 additions & 12 deletions homeassistant/components/hydrawise/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,32 @@
from typing import Any

from aiohttp import ClientError
from pydrawise import auth as pydrawise_auth, client
from pydrawise import auth as pydrawise_auth, hybrid
from pydrawise.exceptions import NotAuthorizedError
import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.const import CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME

from .const import APP_ID, DOMAIN, LOGGER

STEP_USER_DATA_SCHEMA = vol.Schema(
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
{
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
vol.Required(CONF_API_KEY): str,
}
)
STEP_REAUTH_DATA_SCHEMA = vol.Schema(
{vol.Required(CONF_PASSWORD): str, vol.Required(CONF_API_KEY): str}
)
STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str})


class HydrawiseConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Hydrawise."""

VERSION = 1
MINOR_VERSION = 2

async def async_step_user(
self, user_input: dict[str, Any] | None = None
Expand All @@ -34,14 +41,19 @@ async def async_step_user(
return self._show_user_form({})
username = user_input[CONF_USERNAME]
password = user_input[CONF_PASSWORD]
unique_id, errors = await _authenticate(username, password)
api_key = user_input[CONF_API_KEY]
unique_id, errors = await _authenticate(username, password, api_key)
if errors:
return self._show_user_form(errors)
await self.async_set_unique_id(unique_id)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=username,
data={CONF_USERNAME: username, CONF_PASSWORD: password},
data={
CONF_USERNAME: username,
CONF_PASSWORD: password,
CONF_API_KEY: api_key,
},
)

def _show_user_form(self, errors: dict[str, str]) -> ConfigFlowResult:
Expand All @@ -65,14 +77,20 @@ async def async_step_reauth_confirm(
reauth_entry = self._get_reauth_entry()
username = reauth_entry.data[CONF_USERNAME]
password = user_input[CONF_PASSWORD]
user_id, errors = await _authenticate(username, password)
api_key = user_input[CONF_API_KEY]
user_id, errors = await _authenticate(username, password, api_key)
if user_id is None:
return self._show_reauth_form(errors)

await self.async_set_unique_id(user_id)
self._abort_if_unique_id_mismatch(reason="wrong_account")
return self.async_update_reload_and_abort(
reauth_entry, data={CONF_USERNAME: username, CONF_PASSWORD: password}
reauth_entry,
data={
CONF_USERNAME: username,
CONF_PASSWORD: password,
CONF_API_KEY: api_key,
},
)

def _show_reauth_form(self, errors: dict[str, str]) -> ConfigFlowResult:
Expand All @@ -82,14 +100,14 @@ def _show_reauth_form(self, errors: dict[str, str]) -> ConfigFlowResult:


async def _authenticate(
username: str, password: str
username: str, password: str, api_key: str
) -> tuple[str | None, dict[str, str]]:
"""Authenticate with the Hydrawise API."""
unique_id = None
errors: dict[str, str] = {}
auth = pydrawise_auth.Auth(username, password)
auth = pydrawise_auth.HybridAuth(username, password, api_key)
try:
await auth.token()
await auth.check()
except NotAuthorizedError:
errors["base"] = "invalid_auth"
except TimeoutError:
Expand All @@ -99,7 +117,7 @@ async def _authenticate(
return unique_id, errors

try:
api = client.Hydrawise(auth, app_id=APP_ID)
api = hybrid.HybridClient(auth, app_id=APP_ID)
# Don't fetch zones because we don't need them yet.
user = await api.get_user(fetch_zones=False)
except TimeoutError:
Expand Down
8 changes: 4 additions & 4 deletions homeassistant/components/hydrawise/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from dataclasses import dataclass, field

from pydrawise import Hydrawise
from pydrawise import HydrawiseBase
from pydrawise.schema import Controller, ControllerWaterUseSummary, Sensor, User, Zone

from homeassistant.core import HomeAssistant
Expand Down Expand Up @@ -38,7 +38,7 @@ class HydrawiseUpdateCoordinators:
class HydrawiseDataUpdateCoordinator(DataUpdateCoordinator[HydrawiseData]):
"""Base class for Hydrawise Data Update Coordinators."""

api: Hydrawise
api: HydrawiseBase


class HydrawiseMainDataUpdateCoordinator(HydrawiseDataUpdateCoordinator):
Expand All @@ -49,7 +49,7 @@ class HydrawiseMainDataUpdateCoordinator(HydrawiseDataUpdateCoordinator):
integration are updated in a timely manner.
"""

def __init__(self, hass: HomeAssistant, api: Hydrawise) -> None:
def __init__(self, hass: HomeAssistant, api: HydrawiseBase) -> None:
"""Initialize HydrawiseDataUpdateCoordinator."""
super().__init__(hass, LOGGER, name=DOMAIN, update_interval=MAIN_SCAN_INTERVAL)
self.api = api
Expand Down Expand Up @@ -82,7 +82,7 @@ class HydrawiseWaterUseDataUpdateCoordinator(HydrawiseDataUpdateCoordinator):
def __init__(
self,
hass: HomeAssistant,
api: Hydrawise,
api: HydrawiseBase,
main_coordinator: HydrawiseMainDataUpdateCoordinator,
) -> None:
"""Initialize HydrawiseWaterUseDataUpdateCoordinator."""
Expand Down
12 changes: 10 additions & 2 deletions homeassistant/components/hydrawise/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,22 @@
"description": "Please provide the username and password for your Hydrawise cloud account:",
"data": {
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
"password": "[%key:common::config_flow::data::password%]",
"api_key": "[%key:common::config_flow::data::api_key%]"
},
"data_description": {
"api_key": "You can generate an API Key in the 'Account Details' section of the Hydrawise app"
}
},
"reauth_confirm": {
"title": "[%key:common::config_flow::title::reauth%]",
"description": "The Hydrawise integration needs to re-authenticate your account",
"data": {
"password": "[%key:common::config_flow::data::password%]"
"password": "[%key:common::config_flow::data::password%]",
"api_key": "[%key:common::config_flow::data::api_key%]"
},
"data_description": {
"api_key": "[%key:component::hydrawise::config::step::user::data_description::api_key%]"
}
}
},
Expand Down
6 changes: 3 additions & 3 deletions homeassistant/components/hydrawise/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from datetime import timedelta
from typing import Any

from pydrawise import Hydrawise, Zone
from pydrawise import HydrawiseBase, Zone

from homeassistant.components.switch import (
SwitchDeviceClass,
Expand All @@ -28,8 +28,8 @@
class HydrawiseSwitchEntityDescription(SwitchEntityDescription):
"""Describes Hydrawise binary sensor."""

turn_on_fn: Callable[[Hydrawise, Zone], Coroutine[Any, Any, None]]
turn_off_fn: Callable[[Hydrawise, Zone], Coroutine[Any, Any, None]]
turn_on_fn: Callable[[HydrawiseBase, Zone], Coroutine[Any, Any, None]]
turn_off_fn: Callable[[HydrawiseBase, Zone], Coroutine[Any, Any, None]]
value_fn: Callable[[Zone], bool]


Expand Down
7 changes: 4 additions & 3 deletions tests/components/hydrawise/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def mock_pydrawise(
controller_water_use_summary: ControllerWaterUseSummary,
) -> Generator[AsyncMock]:
"""Mock Hydrawise."""
with patch("pydrawise.client.Hydrawise", autospec=True) as mock_pydrawise:
with patch("pydrawise.hybrid.HybridClient", autospec=True) as mock_pydrawise:
user.controllers = [controller]
controller.sensors = sensors
mock_pydrawise.return_value.get_user.return_value = user
Expand All @@ -76,8 +76,8 @@ def mock_pydrawise(

@pytest.fixture
def mock_auth() -> Generator[AsyncMock]:
"""Mock pydrawise Auth."""
with patch("pydrawise.auth.Auth", autospec=True) as mock_auth:
"""Mock pydrawise HybridAuth."""
with patch("pydrawise.auth.HybridAuth", autospec=True) as mock_auth:
yield mock_auth.return_value


Expand Down Expand Up @@ -215,6 +215,7 @@ def mock_config_entry() -> MockConfigEntry:
data={
CONF_USERNAME: "asfd@asdf.com",
CONF_PASSWORD: "__password__",
CONF_API_KEY: "abc123",
},
unique_id="hydrawise-customerid",
version=1,
Expand Down
Loading

0 comments on commit b73203f

Please sign in to comment.