Bump python-duco-connectivity to 0.14.0 (#180964)

This commit is contained in:
Ronald van der Meer
2026-09-01 07:16:34 +02:00
committed by GitHub
parent b49acde2e0
commit 4555ef36cd
7 changed files with 39 additions and 88 deletions
+20 -35
View File
@@ -10,7 +10,6 @@ from duco_connectivity.exceptions import (
DucoConnectionError,
DucoError,
DucoResponseError,
DucoUnsupportedCapabilityError,
)
from duco_connectivity.models import (
BoardInfo,
@@ -53,8 +52,6 @@ class DucoCoordinator(DataUpdateCoordinator[DucoData]):
config_entry: DucoConfigEntry
board_info: BoardInfo
_supports_time_filter_remain: bool
_supports_ventilation_temperatures: bool
_supports_bypass_supply_temperature_targets: bool
_configured_node_names: dict[int, str]
def __init__(
@@ -74,8 +71,6 @@ class DucoCoordinator(DataUpdateCoordinator[DucoData]):
self.client = client
self._configured_node_names = {}
self._supports_time_filter_remain = True
self._supports_ventilation_temperatures = True
self._supports_bypass_supply_temperature_targets = True
async def _async_load_node_names(self) -> None:
"""Load configured Duco node names during setup."""
@@ -193,38 +188,28 @@ class DucoCoordinator(DataUpdateCoordinator[DucoData]):
ventilation_temperatures = (
self.data.ventilation_temperatures if self.data else None
)
if self._supports_ventilation_temperatures:
try:
ventilation_temperatures = (
await self.client.async_get_ventilation_temperature_info()
)
except DucoUnsupportedCapabilityError:
ventilation_temperatures = None
self._supports_ventilation_temperatures = False
except DucoError as err:
_LOGGER.debug(
"Could not fetch Duco ventilation temperatures", exc_info=err
)
try:
ventilation_temperatures = (
await self.client.async_get_ventilation_temperature_info()
)
except DucoError as err:
_LOGGER.debug("Could not fetch Duco ventilation temperatures", exc_info=err)
bypass_supply_temperature_targets: dict[int, BypassSupplyTemperatureTarget] = {}
if self._supports_bypass_supply_temperature_targets:
try:
bypass_supply_temperature_targets = (
await self.client.async_get_bypass_supply_temperature_targets()
)
except DucoUnsupportedCapabilityError:
bypass_supply_temperature_targets = {}
self._supports_bypass_supply_temperature_targets = False
except DucoConnectionError as err:
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="cannot_connect",
) from err
except DucoError as err:
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="api_error",
) from err
try:
bypass_supply_temperature_targets = (
await self.client.async_get_bypass_supply_temperature_targets()
)
except DucoConnectionError as err:
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="cannot_connect",
) from err
except DucoError as err:
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="api_error",
) from err
return DucoData(
nodes={node.node_id: node for node in nodes},
+1 -1
View File
@@ -13,7 +13,7 @@
"iot_class": "local_polling",
"loggers": ["duco_connectivity"],
"quality_scale": "platinum",
"requirements": ["python-duco-connectivity==0.13.1"],
"requirements": ["python-duco-connectivity==0.14.0"],
"zeroconf": [
{
"name": "duco [[][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][]].*",
-8
View File
@@ -56,14 +56,6 @@ async def async_setup_entry(
if (description.key, zone_id) in known_entities:
continue
# Skip incomplete metadata because guessing valid limits would expose an invalid control.
if (
target.minimum is None
or target.maximum is None
or target.increment is None
):
continue
known_entities.add((description.key, zone_id))
new_entities.append(
DucoBypassSupplyTemperatureTargetNumber(
+1 -1
View File
@@ -2702,7 +2702,7 @@ python-digitalocean==1.13.2
python-dropbox-api==0.1.4
# homeassistant.components.duco
python-duco-connectivity==0.13.1
python-duco-connectivity==0.14.0
# homeassistant.components.ecobee
python-ecobee-api==0.4.1
+10 -11
View File
@@ -13,7 +13,6 @@ from duco_connectivity import (
DucoConnectionError,
DucoError,
DucoResponseError,
DucoUnsupportedCapabilityError,
LanInfo,
Node,
NodeListActionItemList,
@@ -268,20 +267,18 @@ async def test_setup_entry_retries_on_bypass_temperature_failure(
assert mock_config_entry.error_reason_translation_placeholders is None
async def test_unsupported_bypass_temperature_capability_is_not_repolled(
async def test_empty_bypass_temperature_targets_are_retried(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
mock_bypass_supply_temperature_targets: dict[int, BypassSupplyTemperatureTarget],
mock_config_entry: MockConfigEntry,
mock_duco_client: AsyncMock,
) -> None:
"""Test an unsupported bulk bypass target endpoint is not polled again."""
mock_duco_client.async_get_bypass_supply_temperature_targets.side_effect = (
DucoUnsupportedCapabilityError(
400,
"/config",
'{"Code":3,"Result":"FAILED"}',
)
)
"""Test empty bypass targets are retried and can later create entities."""
mock_duco_client.async_get_bypass_supply_temperature_targets.side_effect = [
{},
mock_bypass_supply_temperature_targets.copy(),
]
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
@@ -296,7 +293,9 @@ async def test_unsupported_bypass_temperature_capability_is_not_repolled(
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
mock_duco_client.async_get_bypass_supply_temperature_targets.assert_awaited_once_with()
assert mock_duco_client.async_get_bypass_supply_temperature_targets.await_count == 2
assert hass.states.get("number.living_bypass_target_1") is not None
assert hass.states.get("number.living_bypass_target_2") is not None
async def test_missing_bypass_temperature_targets_are_retried(
-26
View File
@@ -92,32 +92,6 @@ async def test_bypass_supply_temperature_targets_missing_skips_number_creation(
assert hass.states.get(_ZONE_2_ENTITY_ID) is None
@pytest.mark.parametrize(
"field",
[
pytest.param("minimum", id="missing_minimum"),
pytest.param("maximum", id="missing_maximum"),
pytest.param("increment", id="missing_increment"),
],
)
@pytest.mark.usefixtures("mock_duco_client")
async def test_bypass_supply_temperature_target_incomplete_metadata_skips_number_creation(
hass: HomeAssistant,
mock_bypass_supply_temperature_targets: dict[int, BypassSupplyTemperatureTarget],
mock_config_entry: MockConfigEntry,
field: str,
) -> None:
"""Test incomplete target metadata does not expose an invalid control."""
mock_bypass_supply_temperature_targets[1] = replace(
mock_bypass_supply_temperature_targets[1], **{field: None}
)
await setup_platform_integration(hass, mock_config_entry, [Platform.NUMBER])
assert hass.states.get(_ZONE_1_ENTITY_ID) is None
assert hass.states.get(_ZONE_2_ENTITY_ID) is not None
@pytest.mark.usefixtures("init_integration")
async def test_set_bypass_supply_temperature_target(
hass: HomeAssistant,
+7 -6
View File
@@ -7,7 +7,6 @@ from unittest.mock import AsyncMock
from duco_connectivity import (
DucoConnectionError,
DucoError,
DucoUnsupportedCapabilityError,
Node,
NodeGeneralInfo,
NodeSensorInfo,
@@ -277,15 +276,15 @@ async def test_time_filter_remaining_missing_skips_sensor_creation(
assert hass.states.get(FILTER_REMAINING_ENTITY_ID) is None
async def test_ventilation_temperatures_missing_skip_sensor_creation(
async def test_empty_ventilation_temperatures_are_retried(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_duco_client: AsyncMock,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test unsupported ventilation temperatures never expose temperature states."""
"""Test empty ventilation temperatures are retried and can appear later."""
mock_duco_client.async_get_ventilation_temperature_info.side_effect = [
DucoUnsupportedCapabilityError(400, "/info", '{"Code":3,"Result":"FAILED"}'),
VentilationTemperatureInfo(),
VentilationTemperatureInfo(temp_oda=5.5),
]
@@ -298,8 +297,10 @@ async def test_ventilation_temperatures_missing_skip_sensor_creation(
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
for entity_id in VENTILATION_TEMPERATURE_ENTITY_IDS:
assert hass.states.get(entity_id) is None
assert mock_duco_client.async_get_ventilation_temperature_info.await_count == 2
state = hass.states.get("sensor.living_outdoor_air_temperature")
assert state is not None
assert state.state == "5.5"
async def test_partial_ventilation_temperatures_only_expose_available_sensor_values(