Bump PySuez to 1.3.1 (#129825)

This commit is contained in:
jb101010-2 2024-11-07 14:25:38 +01:00 committed by GitHub
parent a3ba7803db
commit 0e324c074a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 88 additions and 151 deletions

View File

@ -5,8 +5,7 @@ from __future__ import annotations
import logging import logging
from typing import Any from typing import Any
from pysuez import SuezClient from pysuez import PySuezError, SuezClient
from pysuez.client import PySuezError
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
@ -26,7 +25,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
) )
def validate_input(data: dict[str, Any]) -> None: async def validate_input(data: dict[str, Any]) -> None:
"""Validate the user input allows us to connect. """Validate the user input allows us to connect.
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
@ -36,9 +35,8 @@ def validate_input(data: dict[str, Any]) -> None:
data[CONF_USERNAME], data[CONF_USERNAME],
data[CONF_PASSWORD], data[CONF_PASSWORD],
data[CONF_COUNTER_ID], data[CONF_COUNTER_ID],
provider=None,
) )
if not client.check_credentials(): if not await client.check_credentials():
raise InvalidAuth raise InvalidAuth
except PySuezError as ex: except PySuezError as ex:
raise CannotConnect from ex raise CannotConnect from ex
@ -58,7 +56,7 @@ class SuezWaterConfigFlow(ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(user_input[CONF_USERNAME]) await self.async_set_unique_id(user_input[CONF_USERNAME])
self._abort_if_unique_id_configured() self._abort_if_unique_id_configured()
try: try:
await self.hass.async_add_executor_job(validate_input, user_input) await validate_input(user_input)
except CannotConnect: except CannotConnect:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
except InvalidAuth: except InvalidAuth:

View File

@ -1,39 +1,20 @@
"""Suez water update coordinator.""" """Suez water update coordinator."""
import asyncio from pysuez import AggregatedData, PySuezError, SuezClient
from dataclasses import dataclass
from datetime import date
from pysuez import SuezClient
from pysuez.client import PySuezError
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import _LOGGER, HomeAssistant from homeassistant.core import _LOGGER, HomeAssistant
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryError
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import CONF_COUNTER_ID, DATA_REFRESH_INTERVAL, DOMAIN from .const import CONF_COUNTER_ID, DATA_REFRESH_INTERVAL, DOMAIN
@dataclass class SuezWaterCoordinator(DataUpdateCoordinator[AggregatedData]):
class AggregatedSensorData:
"""Hold suez water aggregated sensor data."""
value: float
current_month: dict[date, float]
previous_month: dict[date, float]
previous_year: dict[str, float]
current_year: dict[str, float]
history: dict[date, float]
highest_monthly_consumption: float
attribution: str
class SuezWaterCoordinator(DataUpdateCoordinator[AggregatedSensorData]):
"""Suez water coordinator.""" """Suez water coordinator."""
_sync_client: SuezClient _suez_client: SuezClient
config_entry: ConfigEntry config_entry: ConfigEntry
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None: def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
@ -48,61 +29,22 @@ class SuezWaterCoordinator(DataUpdateCoordinator[AggregatedSensorData]):
) )
async def _async_setup(self) -> None: async def _async_setup(self) -> None:
self._sync_client = await self.hass.async_add_executor_job(self._get_client) self._suez_client = SuezClient(
username=self.config_entry.data[CONF_USERNAME],
password=self.config_entry.data[CONF_PASSWORD],
counter_id=self.config_entry.data[CONF_COUNTER_ID],
)
if not await self._suez_client.check_credentials():
raise ConfigEntryError("Invalid credentials for suez water")
async def _async_update_data(self) -> AggregatedSensorData: async def _async_update_data(self) -> AggregatedData:
"""Fetch data from API endpoint.""" """Fetch data from API endpoint."""
async with asyncio.timeout(30):
return await self.hass.async_add_executor_job(self._fetch_data)
def _fetch_data(self) -> AggregatedSensorData:
"""Fetch latest data from Suez."""
try: try:
self._sync_client.update() data = await self._suez_client.fetch_aggregated_data()
except PySuezError as err: except PySuezError as err:
_LOGGER.exception(err)
raise UpdateFailed( raise UpdateFailed(
f"Suez coordinator error communicating with API: {err}" f"Suez coordinator error communicating with API: {err}"
) from err ) from err
current_month = {} _LOGGER.debug("Successfully fetched suez data")
for item in self._sync_client.attributes["thisMonthConsumption"]: return data
current_month[item] = self._sync_client.attributes["thisMonthConsumption"][
item
]
previous_month = {}
for item in self._sync_client.attributes["previousMonthConsumption"]:
previous_month[item] = self._sync_client.attributes[
"previousMonthConsumption"
][item]
highest_monthly_consumption = self._sync_client.attributes[
"highestMonthlyConsumption"
]
previous_year = self._sync_client.attributes["lastYearOverAll"]
current_year = self._sync_client.attributes["thisYearOverAll"]
history = {}
for item in self._sync_client.attributes["history"]:
history[item] = self._sync_client.attributes["history"][item]
_LOGGER.debug("Retrieved consumption: " + str(self._sync_client.state))
return AggregatedSensorData(
self._sync_client.state,
current_month,
previous_month,
previous_year,
current_year,
history,
highest_monthly_consumption,
self._sync_client.attributes["attribution"],
)
def _get_client(self) -> SuezClient:
try:
client = SuezClient(
username=self.config_entry.data[CONF_USERNAME],
password=self.config_entry.data[CONF_PASSWORD],
counter_id=self.config_entry.data[CONF_COUNTER_ID],
provider=None,
)
if not client.check_credentials():
raise ConfigEntryError
except PySuezError as ex:
raise ConfigEntryNotReady from ex
return client

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/suez_water", "documentation": "https://www.home-assistant.io/integrations/suez_water",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["pysuez", "regex"], "loggers": ["pysuez", "regex"],
"requirements": ["pysuezV2==0.2.2"] "requirements": ["pysuezV2==1.3.1"]
} }

View File

@ -2284,7 +2284,7 @@ pysqueezebox==0.10.0
pystiebeleltron==0.0.1.dev2 pystiebeleltron==0.0.1.dev2
# homeassistant.components.suez_water # homeassistant.components.suez_water
pysuezV2==0.2.2 pysuezV2==1.3.1
# homeassistant.components.switchbee # homeassistant.components.switchbee
pyswitchbee==1.8.3 pyswitchbee==1.8.3

View File

@ -1841,7 +1841,7 @@ pyspeex-noise==1.0.2
pysqueezebox==0.10.0 pysqueezebox==0.10.0
# homeassistant.components.suez_water # homeassistant.components.suez_water
pysuezV2==0.2.2 pysuezV2==1.3.1
# homeassistant.components.switchbee # homeassistant.components.switchbee
pyswitchbee==1.8.3 pyswitchbee==1.8.3

View File

@ -1,11 +1,12 @@
"""Common fixtures for the Suez Water tests.""" """Common fixtures for the Suez Water tests."""
from collections.abc import Generator from collections.abc import Generator
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import AsyncMock, patch
import pytest import pytest
from homeassistant.components.suez_water.const import DOMAIN from homeassistant.components.suez_water.const import DOMAIN
from homeassistant.components.suez_water.coordinator import AggregatedData
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -37,7 +38,7 @@ def mock_setup_entry() -> Generator[AsyncMock]:
@pytest.fixture(name="suez_client") @pytest.fixture(name="suez_client")
def mock_suez_client() -> Generator[MagicMock]: def mock_suez_data() -> Generator[AsyncMock]:
"""Create mock for suez_water external api.""" """Create mock for suez_water external api."""
with ( with (
patch( patch(
@ -48,28 +49,30 @@ def mock_suez_client() -> Generator[MagicMock]:
new=mock_client, new=mock_client,
), ),
): ):
client = mock_client.return_value suez_client = mock_client.return_value
client.check_credentials.return_value = True suez_client.check_credentials.return_value = True
client.update.return_value = None
client.state = 160 result = AggregatedData(
client.attributes = { value=160,
"thisMonthConsumption": { current_month={
"2024-01-01": 130, "2024-01-01": 130,
"2024-01-02": 145, "2024-01-02": 145,
}, },
"previousMonthConsumption": { previous_month={
"2024-12-01": 154, "2024-12-01": 154,
"2024-12-02": 166, "2024-12-02": 166,
}, },
"highestMonthlyConsumption": 2558, current_year=1500,
"lastYearOverAll": 1000, previous_year=1000,
"thisYearOverAll": 1500, attribution="suez water mock test",
"history": { highest_monthly_consumption=2558,
history={
"2024-01-01": 130, "2024-01-01": 130,
"2024-01-02": 145, "2024-01-02": 145,
"2024-12-01": 154, "2024-12-01": 154,
"2024-12-02": 166, "2024-12-02": 166,
}, },
"attribution": "suez water mock test", )
}
yield client suez_client.fetch_aggregated_data.return_value = result
yield suez_client

View File

@ -1,8 +1,8 @@
"""Test the Suez Water config flow.""" """Test the Suez Water config flow."""
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock
from pysuez.client import PySuezError from pysuez.exception import PySuezError
import pytest import pytest
from homeassistant import config_entries from homeassistant import config_entries
@ -15,7 +15,9 @@ from .conftest import MOCK_DATA
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None: async def test_form(
hass: HomeAssistant, mock_setup_entry: AsyncMock, suez_client: AsyncMock
) -> None:
"""Test we get the form.""" """Test we get the form."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": config_entries.SOURCE_USER}
@ -23,12 +25,11 @@ async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["errors"] == {} assert result["errors"] == {}
with patch("homeassistant.components.suez_water.config_flow.SuezClient"): result = await hass.config_entries.flow.async_configure(
result = await hass.config_entries.flow.async_configure( result["flow_id"],
result["flow_id"], MOCK_DATA,
MOCK_DATA, )
) await hass.async_block_till_done()
await hass.async_block_till_done()
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "test-username" assert result["title"] == "test-username"
@ -38,37 +39,28 @@ async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
async def test_form_invalid_auth( async def test_form_invalid_auth(
hass: HomeAssistant, mock_setup_entry: AsyncMock hass: HomeAssistant, mock_setup_entry: AsyncMock, suez_client: AsyncMock
) -> None: ) -> None:
"""Test we handle invalid auth.""" """Test we handle invalid auth."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": config_entries.SOURCE_USER}
) )
with ( suez_client.check_credentials.return_value = False
patch( result = await hass.config_entries.flow.async_configure(
"homeassistant.components.suez_water.config_flow.SuezClient.__init__", result["flow_id"],
return_value=None, MOCK_DATA,
), )
patch(
"homeassistant.components.suez_water.config_flow.SuezClient.check_credentials",
return_value=False,
),
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
MOCK_DATA,
)
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": "invalid_auth"} assert result["errors"] == {"base": "invalid_auth"}
with patch("homeassistant.components.suez_water.config_flow.SuezClient"): suez_client.check_credentials.return_value = True
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
MOCK_DATA, MOCK_DATA,
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "test-username" assert result["title"] == "test-username"
@ -104,32 +96,32 @@ async def test_form_already_configured(hass: HomeAssistant) -> None:
("exception", "error"), [(PySuezError, "cannot_connect"), (Exception, "unknown")] ("exception", "error"), [(PySuezError, "cannot_connect"), (Exception, "unknown")]
) )
async def test_form_error( async def test_form_error(
hass: HomeAssistant, mock_setup_entry: AsyncMock, exception: Exception, error: str hass: HomeAssistant,
mock_setup_entry: AsyncMock,
exception: Exception,
suez_client: AsyncMock,
error: str,
) -> None: ) -> None:
"""Test we handle errors.""" """Test we handle errors."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": config_entries.SOURCE_USER}
) )
with patch( suez_client.check_credentials.side_effect = exception
"homeassistant.components.suez_water.config_flow.SuezClient", result = await hass.config_entries.flow.async_configure(
side_effect=exception, result["flow_id"],
): MOCK_DATA,
result = await hass.config_entries.flow.async_configure( )
result["flow_id"],
MOCK_DATA,
)
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": error} assert result["errors"] == {"base": error}
with patch( suez_client.check_credentials.return_value = True
"homeassistant.components.suez_water.config_flow.SuezClient", suez_client.check_credentials.side_effect = None
): result = await hass.config_entries.flow.async_configure(
result = await hass.config_entries.flow.async_configure( result["flow_id"],
result["flow_id"], MOCK_DATA,
MOCK_DATA, )
)
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "test-username" assert result["title"] == "test-username"

View File

@ -1,5 +1,7 @@
"""Test Suez_water integration initialization.""" """Test Suez_water integration initialization."""
from unittest.mock import AsyncMock
from homeassistant.components.suez_water.coordinator import PySuezError from homeassistant.components.suez_water.coordinator import PySuezError
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -11,7 +13,7 @@ from tests.common import MockConfigEntry
async def test_initialization_invalid_credentials( async def test_initialization_invalid_credentials(
hass: HomeAssistant, hass: HomeAssistant,
suez_client, suez_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
) -> None: ) -> None:
"""Test that suez_water can't be loaded with invalid credentials.""" """Test that suez_water can't be loaded with invalid credentials."""
@ -24,7 +26,7 @@ async def test_initialization_invalid_credentials(
async def test_initialization_setup_api_error( async def test_initialization_setup_api_error(
hass: HomeAssistant, hass: HomeAssistant,
suez_client, suez_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
) -> None: ) -> None:
"""Test that suez_water needs to retry loading if api failed to connect.""" """Test that suez_water needs to retry loading if api failed to connect."""

View File

@ -1,6 +1,6 @@
"""Test Suez_water sensor platform.""" """Test Suez_water sensor platform."""
from unittest.mock import MagicMock, patch from unittest.mock import AsyncMock, patch
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
@ -20,7 +20,7 @@ from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_plat
async def test_sensors_valid_state( async def test_sensors_valid_state(
hass: HomeAssistant, hass: HomeAssistant,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
suez_client: MagicMock, suez_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
) -> None: ) -> None:
@ -34,7 +34,7 @@ async def test_sensors_valid_state(
async def test_sensors_failed_update( async def test_sensors_failed_update(
hass: HomeAssistant, hass: HomeAssistant,
suez_client, suez_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
) -> None: ) -> None:
@ -51,7 +51,7 @@ async def test_sensors_failed_update(
assert entity_ids[0] assert entity_ids[0]
assert state.state != STATE_UNAVAILABLE assert state.state != STATE_UNAVAILABLE
suez_client.update.side_effect = PySuezError("Should fail to update") suez_client.fetch_aggregated_data.side_effect = PySuezError("Should fail to update")
freezer.tick(DATA_REFRESH_INTERVAL) freezer.tick(DATA_REFRESH_INTERVAL)
async_fire_time_changed(hass) async_fire_time_changed(hass)