tplink: forward compatible typing and test changes for kasa 0.8 (#131623)

pull/131249/head^2
Steven B. 2024-11-26 19:50:26 +00:00 committed by GitHub
parent 2edcda47b0
commit f3964596de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 41 additions and 27 deletions

View File

@ -148,7 +148,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: TPLinkConfigEntry) -> bo
if conn_params_dict := entry.data.get(CONF_CONNECTION_PARAMETERS): if conn_params_dict := entry.data.get(CONF_CONNECTION_PARAMETERS):
try: try:
conn_params = Device.ConnectionParameters.from_dict(conn_params_dict) conn_params = Device.ConnectionParameters.from_dict(conn_params_dict)
except KasaException: except (KasaException, TypeError, ValueError, LookupError):
_LOGGER.warning( _LOGGER.warning(
"Invalid connection parameters dict for %s: %s", host, conn_params_dict "Invalid connection parameters dict for %s: %s", host, conn_params_dict
) )

View File

@ -3,7 +3,7 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Final from typing import Final, cast
from kasa import Feature from kasa import Feature
@ -98,4 +98,4 @@ class TPLinkBinarySensorEntity(CoordinatedTPLinkFeatureEntity, BinarySensorEntit
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> None:
"""Update the entity's attributes.""" """Update the entity's attributes."""
self._attr_is_on = self._feature.value self._attr_is_on = cast(bool | None, self._feature.value)

View File

@ -116,8 +116,8 @@ class TPLinkClimateEntity(CoordinatedTPLinkEntity, ClimateEntity):
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> None:
"""Update the entity's attributes.""" """Update the entity's attributes."""
self._attr_current_temperature = self._temp_feature.value self._attr_current_temperature = cast(float | None, self._temp_feature.value)
self._attr_target_temperature = self._target_feature.value self._attr_target_temperature = cast(float | None, self._target_feature.value)
self._attr_hvac_mode = ( self._attr_hvac_mode = (
HVACMode.HEAT if self._state_feature.value else HVACMode.OFF HVACMode.HEAT if self._state_feature.value else HVACMode.OFF
@ -134,7 +134,9 @@ class TPLinkClimateEntity(CoordinatedTPLinkEntity, ClimateEntity):
self._attr_hvac_action = HVACAction.OFF self._attr_hvac_action = HVACAction.OFF
return return
self._attr_hvac_action = STATE_TO_ACTION[self._mode_feature.value] self._attr_hvac_action = STATE_TO_ACTION[
cast(ThermostatState, self._mode_feature.value)
]
def _get_unique_id(self) -> str: def _get_unique_id(self) -> str:
"""Return unique id.""" """Return unique id."""

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
import logging import logging
from typing import Final from typing import Final, cast
from kasa import Device, Feature from kasa import Device, Feature
@ -108,4 +108,4 @@ class TPLinkNumberEntity(CoordinatedTPLinkFeatureEntity, NumberEntity):
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> None:
"""Update the entity's attributes.""" """Update the entity's attributes."""
self._attr_native_value = self._feature.value self._attr_native_value = cast(float | None, self._feature.value)

View File

@ -93,4 +93,4 @@ class TPLinkSelectEntity(CoordinatedTPLinkFeatureEntity, SelectEntity):
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> None:
"""Update the entity's attributes.""" """Update the entity's attributes."""
self._attr_current_option = self._feature.value self._attr_current_option = cast(str | None, self._feature.value)

View File

@ -3,7 +3,7 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import cast from typing import TYPE_CHECKING, cast
from kasa import Feature from kasa import Feature
@ -161,6 +161,12 @@ class TPLinkSensorEntity(CoordinatedTPLinkFeatureEntity, SensorEntity):
# We probably do not need this, when we are rounding already? # We probably do not need this, when we are rounding already?
self._attr_suggested_display_precision = self._feature.precision_hint self._attr_suggested_display_precision = self._feature.precision_hint
if TYPE_CHECKING:
# pylint: disable-next=import-outside-toplevel
from datetime import date, datetime
assert isinstance(value, str | int | float | date | datetime | None)
self._attr_native_value = value self._attr_native_value = value
# Map to homeassistant units and fallback to upstream one if none found # Map to homeassistant units and fallback to upstream one if none found
if (unit := self._feature.unit) is not None: if (unit := self._feature.unit) is not None:

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
import logging import logging
from typing import Any from typing import Any, cast
from kasa import Feature from kasa import Feature
@ -99,4 +99,4 @@ class TPLinkSwitch(CoordinatedTPLinkFeatureEntity, SwitchEntity):
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> None:
"""Update the entity's attributes.""" """Update the entity's attributes."""
self._attr_is_on = self._feature.value self._attr_is_on = cast(bool | None, self._feature.value)

View File

@ -6,6 +6,7 @@ from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import AsyncMock, MagicMock, patch
from kasa import ( from kasa import (
BaseProtocol,
Device, Device,
DeviceConfig, DeviceConfig,
DeviceConnectionParameters, DeviceConnectionParameters,
@ -17,7 +18,6 @@ from kasa import (
Module, Module,
) )
from kasa.interfaces import Fan, Light, LightEffect, LightState from kasa.interfaces import Fan, Light, LightEffect, LightState
from kasa.protocol import BaseProtocol
from kasa.smart.modules.alarm import Alarm from kasa.smart.modules.alarm import Alarm
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
@ -62,7 +62,9 @@ CONN_PARAMS_LEGACY = DeviceConnectionParameters(
DeviceFamily.IotSmartPlugSwitch, DeviceEncryptionType.Xor DeviceFamily.IotSmartPlugSwitch, DeviceEncryptionType.Xor
) )
DEVICE_CONFIG_LEGACY = DeviceConfig(IP_ADDRESS) DEVICE_CONFIG_LEGACY = DeviceConfig(IP_ADDRESS)
DEVICE_CONFIG_DICT_LEGACY = DEVICE_CONFIG_LEGACY.to_dict(exclude_credentials=True) DEVICE_CONFIG_DICT_LEGACY = {
k: v for k, v in DEVICE_CONFIG_LEGACY.to_dict().items() if k != "credentials"
}
CREDENTIALS = Credentials("foo", "bar") CREDENTIALS = Credentials("foo", "bar")
CREDENTIALS_HASH_AES = "AES/abcdefghijklmnopqrstuvabcdefghijklmnopqrstuv==" CREDENTIALS_HASH_AES = "AES/abcdefghijklmnopqrstuvabcdefghijklmnopqrstuv=="
CREDENTIALS_HASH_KLAP = "KLAP/abcdefghijklmnopqrstuv==" CREDENTIALS_HASH_KLAP = "KLAP/abcdefghijklmnopqrstuv=="
@ -86,8 +88,12 @@ DEVICE_CONFIG_AES = DeviceConfig(
uses_http=True, uses_http=True,
aes_keys=AES_KEYS, aes_keys=AES_KEYS,
) )
DEVICE_CONFIG_DICT_KLAP = DEVICE_CONFIG_KLAP.to_dict(exclude_credentials=True) DEVICE_CONFIG_DICT_KLAP = {
DEVICE_CONFIG_DICT_AES = DEVICE_CONFIG_AES.to_dict(exclude_credentials=True) k: v for k, v in DEVICE_CONFIG_KLAP.to_dict().items() if k != "credentials"
}
DEVICE_CONFIG_DICT_AES = {
k: v for k, v in DEVICE_CONFIG_AES.to_dict().items() if k != "credentials"
}
CREATE_ENTRY_DATA_LEGACY = { CREATE_ENTRY_DATA_LEGACY = {
CONF_HOST: IP_ADDRESS, CONF_HOST: IP_ADDRESS,
CONF_ALIAS: ALIAS, CONF_ALIAS: ALIAS,

View File

@ -45,6 +45,7 @@ from . import (
CREDENTIALS_HASH_AES, CREDENTIALS_HASH_AES,
CREDENTIALS_HASH_KLAP, CREDENTIALS_HASH_KLAP,
DEVICE_CONFIG_AES, DEVICE_CONFIG_AES,
DEVICE_CONFIG_DICT_KLAP,
DEVICE_CONFIG_KLAP, DEVICE_CONFIG_KLAP,
DEVICE_CONFIG_LEGACY, DEVICE_CONFIG_LEGACY,
DEVICE_ID, DEVICE_ID,
@ -538,9 +539,8 @@ async def test_move_credentials_hash(
from the device. from the device.
""" """
device_config = { device_config = {
**DEVICE_CONFIG_KLAP.to_dict( **DEVICE_CONFIG_DICT_KLAP,
exclude_credentials=True, credentials_hash="theHash" "credentials_hash": "theHash",
)
} }
entry_data = {**CREATE_ENTRY_DATA_KLAP, CONF_DEVICE_CONFIG: device_config} entry_data = {**CREATE_ENTRY_DATA_KLAP, CONF_DEVICE_CONFIG: device_config}
@ -586,9 +586,8 @@ async def test_move_credentials_hash_auth_error(
in async_setup_entry. in async_setup_entry.
""" """
device_config = { device_config = {
**DEVICE_CONFIG_KLAP.to_dict( **DEVICE_CONFIG_DICT_KLAP,
exclude_credentials=True, credentials_hash="theHash" "credentials_hash": "theHash",
)
} }
entry_data = {**CREATE_ENTRY_DATA_KLAP, CONF_DEVICE_CONFIG: device_config} entry_data = {**CREATE_ENTRY_DATA_KLAP, CONF_DEVICE_CONFIG: device_config}
@ -630,9 +629,8 @@ async def test_move_credentials_hash_other_error(
at the end of the test. at the end of the test.
""" """
device_config = { device_config = {
**DEVICE_CONFIG_KLAP.to_dict( **DEVICE_CONFIG_DICT_KLAP,
exclude_credentials=True, credentials_hash="theHash" "credentials_hash": "theHash",
)
} }
entry_data = {**CREATE_ENTRY_DATA_KLAP, CONF_DEVICE_CONFIG: device_config} entry_data = {**CREATE_ENTRY_DATA_KLAP, CONF_DEVICE_CONFIG: device_config}
@ -729,7 +727,7 @@ async def test_credentials_hash_auth_error(
await hass.async_block_till_done() await hass.async_block_till_done()
expected_config = DeviceConfig.from_dict( expected_config = DeviceConfig.from_dict(
DEVICE_CONFIG_KLAP.to_dict(exclude_credentials=True, credentials_hash="theHash") {**DEVICE_CONFIG_DICT_KLAP, "credentials_hash": "theHash"}
) )
expected_config.uses_http = False expected_config.uses_http = False
expected_config.http_client = "Foo" expected_config.http_client = "Foo"
@ -767,7 +765,9 @@ async def test_migrate_remove_device_config(
CONF_HOST: expected_entry_data[CONF_HOST], CONF_HOST: expected_entry_data[CONF_HOST],
CONF_ALIAS: ALIAS, CONF_ALIAS: ALIAS,
CONF_MODEL: MODEL, CONF_MODEL: MODEL,
CONF_DEVICE_CONFIG: device_config.to_dict(exclude_credentials=True), CONF_DEVICE_CONFIG: {
k: v for k, v in device_config.to_dict().items() if k != "credentials"
},
} }
entry = MockConfigEntry( entry = MockConfigEntry(