mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 18:27:09 +00:00
Fix Nord Pool empty response (#134033)
* Fix Nord Pool empty response * Mods * reset validate prices
This commit is contained in:
parent
b3aede611a
commit
645f2e44b9
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
@ -73,7 +72,7 @@ class NordPoolDataUpdateCoordinator(DataUpdateCoordinator[DeliveryPeriodsData]):
|
|||||||
self.hass, self.fetch_data, self.get_next_interval(dt_util.utcnow())
|
self.hass, self.fetch_data, self.get_next_interval(dt_util.utcnow())
|
||||||
)
|
)
|
||||||
data = await self.api_call()
|
data = await self.api_call()
|
||||||
if data:
|
if data and data.entries:
|
||||||
self.async_set_updated_data(data)
|
self.async_set_updated_data(data)
|
||||||
|
|
||||||
async def api_call(self, retry: int = 3) -> DeliveryPeriodsData | None:
|
async def api_call(self, retry: int = 3) -> DeliveryPeriodsData | None:
|
||||||
@ -90,18 +89,20 @@ class NordPoolDataUpdateCoordinator(DataUpdateCoordinator[DeliveryPeriodsData]):
|
|||||||
self.config_entry.data[CONF_AREAS],
|
self.config_entry.data[CONF_AREAS],
|
||||||
)
|
)
|
||||||
except (
|
except (
|
||||||
NordPoolEmptyResponseError,
|
|
||||||
NordPoolResponseError,
|
NordPoolResponseError,
|
||||||
NordPoolError,
|
NordPoolError,
|
||||||
) as error:
|
) as error:
|
||||||
LOGGER.debug("Connection error: %s", error)
|
LOGGER.debug("Connection error: %s", error)
|
||||||
if retry > 0:
|
|
||||||
next_run = (4 - retry) * 15
|
|
||||||
LOGGER.debug("Wait %d seconds for next try", next_run)
|
|
||||||
await asyncio.sleep(next_run)
|
|
||||||
return await self.api_call(retry - 1)
|
|
||||||
self.async_set_update_error(error)
|
self.async_set_update_error(error)
|
||||||
|
|
||||||
|
if data:
|
||||||
|
current_day = dt_util.utcnow().strftime("%Y-%m-%d")
|
||||||
|
for entry in data.entries:
|
||||||
|
if entry.requested_date == current_day:
|
||||||
|
LOGGER.debug("Data for current day found")
|
||||||
|
return data
|
||||||
|
|
||||||
|
self.async_set_update_error(NordPoolEmptyResponseError("No current day data"))
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def merge_price_entries(self) -> list[DeliveryPeriodEntry]:
|
def merge_price_entries(self) -> list[DeliveryPeriodEntry]:
|
||||||
|
@ -8,6 +8,6 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["pynordpool"],
|
"loggers": ["pynordpool"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["pynordpool==0.2.3"],
|
"requirements": ["pynordpool==0.2.4"],
|
||||||
"single_config_entry": true
|
"single_config_entry": true
|
||||||
}
|
}
|
||||||
|
@ -2118,7 +2118,7 @@ pynetio==0.1.9.1
|
|||||||
pynobo==1.8.1
|
pynobo==1.8.1
|
||||||
|
|
||||||
# homeassistant.components.nordpool
|
# homeassistant.components.nordpool
|
||||||
pynordpool==0.2.3
|
pynordpool==0.2.4
|
||||||
|
|
||||||
# homeassistant.components.nuki
|
# homeassistant.components.nuki
|
||||||
pynuki==1.6.3
|
pynuki==1.6.3
|
||||||
|
@ -1720,7 +1720,7 @@ pynetgear==0.10.10
|
|||||||
pynobo==1.8.1
|
pynobo==1.8.1
|
||||||
|
|
||||||
# homeassistant.components.nordpool
|
# homeassistant.components.nordpool
|
||||||
pynordpool==0.2.3
|
pynordpool==0.2.4
|
||||||
|
|
||||||
# homeassistant.components.nuki
|
# homeassistant.components.nuki
|
||||||
pynuki==1.6.3
|
pynuki==1.6.3
|
||||||
|
@ -5,7 +5,6 @@ from __future__ import annotations
|
|||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
import json
|
import json
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from pynordpool import API, NordPoolClient
|
from pynordpool import API, NordPoolClient
|
||||||
import pytest
|
import pytest
|
||||||
@ -20,13 +19,6 @@ from tests.common import MockConfigEntry, load_fixture
|
|||||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
async def no_sleep() -> AsyncGenerator[None]:
|
|
||||||
"""No sleeping."""
|
|
||||||
with patch("homeassistant.components.nordpool.coordinator.asyncio.sleep"):
|
|
||||||
yield
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
async def load_int(hass: HomeAssistant, get_client: NordPoolClient) -> MockConfigEntry:
|
async def load_int(hass: HomeAssistant, get_client: NordPoolClient) -> MockConfigEntry:
|
||||||
"""Set up the Nord Pool integration in Home Assistant."""
|
"""Set up the Nord Pool integration in Home Assistant."""
|
||||||
|
@ -55,7 +55,7 @@ async def test_coordinator(
|
|||||||
freezer.tick(timedelta(hours=1))
|
freezer.tick(timedelta(hours=1))
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
assert mock_data.call_count == 4
|
assert mock_data.call_count == 1
|
||||||
state = hass.states.get("sensor.nord_pool_se3_current_price")
|
state = hass.states.get("sensor.nord_pool_se3_current_price")
|
||||||
assert state.state == STATE_UNAVAILABLE
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ async def test_coordinator(
|
|||||||
freezer.tick(timedelta(hours=1))
|
freezer.tick(timedelta(hours=1))
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
assert mock_data.call_count == 4
|
assert mock_data.call_count == 1
|
||||||
state = hass.states.get("sensor.nord_pool_se3_current_price")
|
state = hass.states.get("sensor.nord_pool_se3_current_price")
|
||||||
assert state.state == STATE_UNAVAILABLE
|
assert state.state == STATE_UNAVAILABLE
|
||||||
assert "Authentication error" in caplog.text
|
assert "Authentication error" in caplog.text
|
||||||
@ -84,7 +84,8 @@ async def test_coordinator(
|
|||||||
freezer.tick(timedelta(hours=1))
|
freezer.tick(timedelta(hours=1))
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
assert mock_data.call_count == 4
|
# Empty responses does not raise
|
||||||
|
assert mock_data.call_count == 3
|
||||||
state = hass.states.get("sensor.nord_pool_se3_current_price")
|
state = hass.states.get("sensor.nord_pool_se3_current_price")
|
||||||
assert state.state == STATE_UNAVAILABLE
|
assert state.state == STATE_UNAVAILABLE
|
||||||
assert "Empty response" in caplog.text
|
assert "Empty response" in caplog.text
|
||||||
@ -99,7 +100,7 @@ async def test_coordinator(
|
|||||||
freezer.tick(timedelta(hours=1))
|
freezer.tick(timedelta(hours=1))
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
assert mock_data.call_count == 4
|
assert mock_data.call_count == 1
|
||||||
state = hass.states.get("sensor.nord_pool_se3_current_price")
|
state = hass.states.get("sensor.nord_pool_se3_current_price")
|
||||||
assert state.state == STATE_UNAVAILABLE
|
assert state.state == STATE_UNAVAILABLE
|
||||||
assert "Response error" in caplog.text
|
assert "Response error" in caplog.text
|
||||||
|
@ -2,14 +2,22 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
from http import HTTPStatus
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
from pynordpool import API
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy.assertion import SnapshotAssertion
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import STATE_UNKNOWN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
from tests.common import snapshot_platform
|
from tests.common import async_fire_time_changed, snapshot_platform
|
||||||
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.freeze_time("2024-11-05T18:00:00+00:00")
|
@pytest.mark.freeze_time("2024-11-05T18:00:00+00:00")
|
||||||
@ -59,3 +67,132 @@ async def test_sensor_no_previous_price(
|
|||||||
assert current_price.state == "0.12666" # SE3 2024-11-05T23:00:00Z
|
assert current_price.state == "0.12666" # SE3 2024-11-05T23:00:00Z
|
||||||
assert last_price.state == "0.28914" # SE3 2024-11-05T22:00:00Z
|
assert last_price.state == "0.28914" # SE3 2024-11-05T22:00:00Z
|
||||||
assert next_price.state == "0.07406" # SE3 2024-11-06T00:00:00Z
|
assert next_price.state == "0.07406" # SE3 2024-11-06T00:00:00Z
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.freeze_time("2024-11-05T11:00:01+01:00")
|
||||||
|
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||||
|
async def test_sensor_empty_response(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
load_int: ConfigEntry,
|
||||||
|
load_json: list[dict[str, Any]],
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test the Nord Pool sensor with empty response."""
|
||||||
|
|
||||||
|
responses = list(load_json)
|
||||||
|
|
||||||
|
current_price = hass.states.get("sensor.nord_pool_se3_current_price")
|
||||||
|
last_price = hass.states.get("sensor.nord_pool_se3_previous_price")
|
||||||
|
next_price = hass.states.get("sensor.nord_pool_se3_next_price")
|
||||||
|
assert current_price is not None
|
||||||
|
assert last_price is not None
|
||||||
|
assert next_price is not None
|
||||||
|
assert current_price.state == "0.92737"
|
||||||
|
assert last_price.state == "1.03132"
|
||||||
|
assert next_price.state == "0.92505"
|
||||||
|
|
||||||
|
aioclient_mock.clear_requests()
|
||||||
|
aioclient_mock.request(
|
||||||
|
"GET",
|
||||||
|
url=API + "/DayAheadPrices",
|
||||||
|
params={
|
||||||
|
"date": "2024-11-04",
|
||||||
|
"market": "DayAhead",
|
||||||
|
"deliveryArea": "SE3,SE4",
|
||||||
|
"currency": "SEK",
|
||||||
|
},
|
||||||
|
json=responses[1],
|
||||||
|
)
|
||||||
|
aioclient_mock.request(
|
||||||
|
"GET",
|
||||||
|
url=API + "/DayAheadPrices",
|
||||||
|
params={
|
||||||
|
"date": "2024-11-05",
|
||||||
|
"market": "DayAhead",
|
||||||
|
"deliveryArea": "SE3,SE4",
|
||||||
|
"currency": "SEK",
|
||||||
|
},
|
||||||
|
json=responses[0],
|
||||||
|
)
|
||||||
|
# Future date without data should return 204
|
||||||
|
aioclient_mock.request(
|
||||||
|
"GET",
|
||||||
|
url=API + "/DayAheadPrices",
|
||||||
|
params={
|
||||||
|
"date": "2024-11-06",
|
||||||
|
"market": "DayAhead",
|
||||||
|
"deliveryArea": "SE3,SE4",
|
||||||
|
"currency": "SEK",
|
||||||
|
},
|
||||||
|
status=HTTPStatus.NO_CONTENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
freezer.tick(timedelta(hours=1))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
|
||||||
|
# All prices should be known as tomorrow is not loaded by sensors
|
||||||
|
|
||||||
|
current_price = hass.states.get("sensor.nord_pool_se3_current_price")
|
||||||
|
last_price = hass.states.get("sensor.nord_pool_se3_previous_price")
|
||||||
|
next_price = hass.states.get("sensor.nord_pool_se3_next_price")
|
||||||
|
assert current_price is not None
|
||||||
|
assert last_price is not None
|
||||||
|
assert next_price is not None
|
||||||
|
assert current_price.state == "0.92505"
|
||||||
|
assert last_price.state == "0.92737"
|
||||||
|
assert next_price.state == "0.94949"
|
||||||
|
|
||||||
|
aioclient_mock.clear_requests()
|
||||||
|
aioclient_mock.request(
|
||||||
|
"GET",
|
||||||
|
url=API + "/DayAheadPrices",
|
||||||
|
params={
|
||||||
|
"date": "2024-11-04",
|
||||||
|
"market": "DayAhead",
|
||||||
|
"deliveryArea": "SE3,SE4",
|
||||||
|
"currency": "SEK",
|
||||||
|
},
|
||||||
|
json=responses[1],
|
||||||
|
)
|
||||||
|
aioclient_mock.request(
|
||||||
|
"GET",
|
||||||
|
url=API + "/DayAheadPrices",
|
||||||
|
params={
|
||||||
|
"date": "2024-11-05",
|
||||||
|
"market": "DayAhead",
|
||||||
|
"deliveryArea": "SE3,SE4",
|
||||||
|
"currency": "SEK",
|
||||||
|
},
|
||||||
|
json=responses[0],
|
||||||
|
)
|
||||||
|
# Future date without data should return 204
|
||||||
|
aioclient_mock.request(
|
||||||
|
"GET",
|
||||||
|
url=API + "/DayAheadPrices",
|
||||||
|
params={
|
||||||
|
"date": "2024-11-06",
|
||||||
|
"market": "DayAhead",
|
||||||
|
"deliveryArea": "SE3,SE4",
|
||||||
|
"currency": "SEK",
|
||||||
|
},
|
||||||
|
status=HTTPStatus.NO_CONTENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
freezer.move_to("2024-11-05T22:00:01+00:00")
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
|
||||||
|
# Current and last price should be known, next price should be unknown
|
||||||
|
# as api responds with empty data (204)
|
||||||
|
|
||||||
|
current_price = hass.states.get("sensor.nord_pool_se3_current_price")
|
||||||
|
last_price = hass.states.get("sensor.nord_pool_se3_previous_price")
|
||||||
|
next_price = hass.states.get("sensor.nord_pool_se3_next_price")
|
||||||
|
assert current_price is not None
|
||||||
|
assert last_price is not None
|
||||||
|
assert next_price is not None
|
||||||
|
assert current_price.state == "0.28914"
|
||||||
|
assert last_price.state == "0.5223"
|
||||||
|
assert next_price.state == STATE_UNKNOWN
|
||||||
|
Loading…
x
Reference in New Issue
Block a user