Exclude __getattr__ for deprecated attributes from type checkers (#1294)

This commit is contained in:
Steven B. 2024-11-21 18:40:13 +00:00 committed by GitHub
parent 652b4e0bd7
commit cae9decb02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 73 additions and 50 deletions

View File

@ -97,8 +97,9 @@ deprecated_classes = {
"DeviceFamilyType": DeviceFamily, "DeviceFamilyType": DeviceFamily,
} }
if not TYPE_CHECKING:
def __getattr__(name: str) -> Any: def __getattr__(name: str) -> Any:
if name in deprecated_names: if name in deprecated_names:
warn(f"{name} is deprecated", DeprecationWarning, stacklevel=2) warn(f"{name} is deprecated", DeprecationWarning, stacklevel=2)
return globals()[f"_deprecated_{name}"] return globals()[f"_deprecated_{name}"]
@ -106,8 +107,8 @@ def __getattr__(name: str) -> Any:
new_class = deprecated_smart_devices[name] new_class = deprecated_smart_devices[name]
package_name = ".".join(new_class.__module__.split(".")[:-1]) package_name = ".".join(new_class.__module__.split(".")[:-1])
warn( warn(
f"{name} is deprecated, use {new_class.__name__} " f"{name} is deprecated, use {new_class.__name__} from "
+ f"from package {package_name} instead or use Discover.discover_single()" + f"package {package_name} instead or use Discover.discover_single()"
+ " and Device.connect() to support new protocols", + " and Device.connect() to support new protocols",
DeprecationWarning, DeprecationWarning,
stacklevel=2, stacklevel=2,

View File

@ -190,7 +190,7 @@ async def presets_modify(dev: Device, index, brightness, hue, saturation, temper
@click.option("--preset", type=int) @click.option("--preset", type=int)
async def turn_on_behavior(dev: Device, type, last, preset): async def turn_on_behavior(dev: Device, type, last, preset):
"""Modify bulb turn-on behavior.""" """Modify bulb turn-on behavior."""
if not dev.is_bulb or not isinstance(dev, IotBulb): if dev.device_type is not Device.Type.Bulb or not isinstance(dev, IotBulb):
error("Presets only supported on iot bulbs") error("Presets only supported on iot bulbs")
return return
settings = await dev.get_turn_on_behavior() settings = await dev.get_turn_on_behavior()

View File

@ -566,9 +566,13 @@ class Device(ABC):
"supported_modules": (None, ["modules"]), "supported_modules": (None, ["modules"]),
} }
if not TYPE_CHECKING:
def __getattr__(self, name: str) -> Any: def __getattr__(self, name: str) -> Any:
# is_device_type # is_device_type
if dep_device_type_attr := self._deprecated_device_type_attributes.get(name): if dep_device_type_attr := self._deprecated_device_type_attributes.get(
name
):
msg = f"{name} is deprecated, use device_type property instead" msg = f"{name} is deprecated, use device_type property instead"
warn(msg, DeprecationWarning, stacklevel=2) warn(msg, DeprecationWarning, stacklevel=2)
return self.device_type == dep_device_type_attr[1] return self.device_type == dep_device_type_attr[1]

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from enum import IntFlag, auto from enum import IntFlag, auto
from typing import Any from typing import TYPE_CHECKING, Any
from warnings import warn from warnings import warn
from ..emeterstatus import EmeterStatus from ..emeterstatus import EmeterStatus
@ -184,6 +184,8 @@ class Energy(Module, ABC):
"get_monthstat": "get_monthly_stats", "get_monthstat": "get_monthly_stats",
} }
if not TYPE_CHECKING:
def __getattr__(self, name: str) -> Any: def __getattr__(self, name: str) -> Any:
if attr := self._deprecated_attributes.get(name): if attr := self._deprecated_attributes.get(name):
msg = f"{name} is deprecated, use {attr} instead" msg = f"{name} is deprecated, use {attr} instead"

View File

@ -154,7 +154,7 @@ class IotDimmer(IotPlug):
""" """
if transition is not None: if transition is not None:
return await self.set_dimmer_transition( return await self.set_dimmer_transition(
brightness=self.brightness, transition=transition brightness=self._brightness, transition=transition
) )
return await super().turn_on() return await super().turn_on()

View File

@ -3,13 +3,16 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from typing import Any from typing import TYPE_CHECKING, Any
from ..exceptions import KasaException from ..exceptions import KasaException
from ..module import Module from ..module import Module
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
if TYPE_CHECKING:
from .iotdevice import IotDevice
def _merge_dict(dest: dict, source: dict) -> dict: def _merge_dict(dest: dict, source: dict) -> dict:
"""Update dict recursively.""" """Update dict recursively."""
@ -27,6 +30,8 @@ merge = _merge_dict
class IotModule(Module): class IotModule(Module):
"""Base class implemention for all IOT modules.""" """Base class implemention for all IOT modules."""
_device: IotDevice
async def call(self, method: str, params: dict | None = None) -> dict: async def call(self, method: str, params: dict | None = None) -> dict:
"""Call the given method with the given parameters.""" """Call the given method with the given parameters."""
return await self._device._query_helper(self._module, method, params) return await self._device._query_helper(self._module, method, params)

View File

@ -5,7 +5,7 @@ from __future__ import annotations
import logging import logging
from collections import defaultdict from collections import defaultdict
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any from typing import TYPE_CHECKING, Any
from ..device_type import DeviceType from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig from ..deviceconfig import DeviceConfig
@ -145,6 +145,8 @@ class IotStrip(IotDevice):
if update_children: if update_children:
for plug in self.children: for plug in self.children:
if TYPE_CHECKING:
assert isinstance(plug, IotStripPlug)
await plug._update() await plug._update()
if not self.features: if not self.features:

View File

@ -207,6 +207,8 @@ class Light(IotModule, LightInterface):
# iot protocol Dimmers and smart protocol devices do not support # iot protocol Dimmers and smart protocol devices do not support
# brightness of 0 so 0 will turn off all devices for consistency # brightness of 0 so 0 will turn off all devices for consistency
if (bulb := self._get_bulb_device()) is None: # Dimmer if (bulb := self._get_bulb_device()) is None: # Dimmer
if TYPE_CHECKING:
assert isinstance(self._device, IotDimmer)
if state.brightness == 0 or state.light_on is False: if state.brightness == 0 or state.light_on is False:
return await self._device.turn_off(transition=state.transition) return await self._device.turn_off(transition=state.transition)
elif state.brightness: elif state.brightness:

View File

@ -28,6 +28,8 @@ from .modules import (
) )
from .smartmodule import SmartModule from .smartmodule import SmartModule
if TYPE_CHECKING:
from .smartchilddevice import SmartChildDevice
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -196,6 +198,8 @@ class SmartDevice(Device):
# child modules have access to their sysinfo. # child modules have access to their sysinfo.
if update_children or self.device_type != DeviceType.Hub: if update_children or self.device_type != DeviceType.Hub:
for child in self._children.values(): for child in self._children.values():
if TYPE_CHECKING:
assert isinstance(child, SmartChildDevice)
await child._update() await child._update()
# We can first initialize the features after the first update. # We can first initialize the features after the first update.

View File

@ -16,6 +16,7 @@ from kasa.iot import IotDevice, IotStrip
from kasa.iot.modules.emeter import Emeter from kasa.iot.modules.emeter import Emeter
from kasa.smart import SmartDevice from kasa.smart import SmartDevice
from kasa.smart.modules import Energy as SmartEnergyModule from kasa.smart.modules import Energy as SmartEnergyModule
from kasa.smart.smartmodule import SmartModule
from .conftest import has_emeter, has_emeter_iot, no_emeter from .conftest import has_emeter, has_emeter_iot, no_emeter
@ -192,6 +193,7 @@ async def test_supported(dev: Device):
pytest.skip(f"Energy module not supported for {dev}.") pytest.skip(f"Energy module not supported for {dev}.")
energy_module = dev.modules.get(Module.Energy) energy_module = dev.modules.get(Module.Energy)
assert energy_module assert energy_module
if isinstance(dev, IotDevice): if isinstance(dev, IotDevice):
info = ( info = (
dev._last_update dev._last_update
@ -210,6 +212,7 @@ async def test_supported(dev: Device):
) )
assert energy_module.supports(Energy.ModuleFeature.PERIODIC_STATS) is True assert energy_module.supports(Energy.ModuleFeature.PERIODIC_STATS) is True
else: else:
assert isinstance(energy_module, SmartModule)
assert energy_module.supports(Energy.ModuleFeature.CONSUMPTION_TOTAL) is False assert energy_module.supports(Energy.ModuleFeature.CONSUMPTION_TOTAL) is False
assert energy_module.supports(Energy.ModuleFeature.PERIODIC_STATS) is False assert energy_module.supports(Energy.ModuleFeature.PERIODIC_STATS) is False
if energy_module.supported_version < 2: if energy_module.supported_version < 2: