tplink: forward compatible typing and test changes for kasa 0.8 (#131623)
parent
2edcda47b0
commit
f3964596de
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Loading…
Reference in New Issue