mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-22 19:23:34 +00:00
Exclude __getattr__ for deprecated attributes from type checkers (#1294)
This commit is contained in:
parent
652b4e0bd7
commit
cae9decb02
@ -97,28 +97,29 @@ 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}"]
|
||||||
if name in deprecated_smart_devices:
|
if name in deprecated_smart_devices:
|
||||||
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,
|
||||||
)
|
)
|
||||||
return new_class
|
return new_class
|
||||||
if name in deprecated_classes:
|
if name in deprecated_classes:
|
||||||
new_class = deprecated_classes[name] # type: ignore[assignment]
|
new_class = deprecated_classes[name] # type: ignore[assignment]
|
||||||
msg = f"{name} is deprecated, use {new_class.__name__} instead"
|
msg = f"{name} is deprecated, use {new_class.__name__} instead"
|
||||||
warn(msg, DeprecationWarning, stacklevel=2)
|
warn(msg, DeprecationWarning, stacklevel=2)
|
||||||
return new_class
|
return new_class
|
||||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -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()
|
||||||
|
@ -566,21 +566,25 @@ class Device(ABC):
|
|||||||
"supported_modules": (None, ["modules"]),
|
"supported_modules": (None, ["modules"]),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __getattr__(self, name: str) -> Any:
|
if not TYPE_CHECKING:
|
||||||
# is_device_type
|
|
||||||
if dep_device_type_attr := self._deprecated_device_type_attributes.get(name):
|
def __getattr__(self, name: str) -> Any:
|
||||||
msg = f"{name} is deprecated, use device_type property instead"
|
# is_device_type
|
||||||
warn(msg, DeprecationWarning, stacklevel=2)
|
if dep_device_type_attr := self._deprecated_device_type_attributes.get(
|
||||||
return self.device_type == dep_device_type_attr[1]
|
name
|
||||||
# Other deprecated attributes
|
):
|
||||||
if (dep_attr := self._deprecated_other_attributes.get(name)) and (
|
msg = f"{name} is deprecated, use device_type property instead"
|
||||||
(replacing_attr := self._get_replacing_attr(dep_attr[0], *dep_attr[1]))
|
warn(msg, DeprecationWarning, stacklevel=2)
|
||||||
is not None
|
return self.device_type == dep_device_type_attr[1]
|
||||||
):
|
# Other deprecated attributes
|
||||||
mod = dep_attr[0]
|
if (dep_attr := self._deprecated_other_attributes.get(name)) and (
|
||||||
dev_or_mod = self.modules[mod] if mod else self
|
(replacing_attr := self._get_replacing_attr(dep_attr[0], *dep_attr[1]))
|
||||||
replacing = f"Module.{mod} in device.modules" if mod else replacing_attr
|
is not None
|
||||||
msg = f"{name} is deprecated, use: {replacing} instead"
|
):
|
||||||
warn(msg, DeprecationWarning, stacklevel=2)
|
mod = dep_attr[0]
|
||||||
return getattr(dev_or_mod, replacing_attr)
|
dev_or_mod = self.modules[mod] if mod else self
|
||||||
raise AttributeError(f"Device has no attribute {name!r}")
|
replacing = f"Module.{mod} in device.modules" if mod else replacing_attr
|
||||||
|
msg = f"{name} is deprecated, use: {replacing} instead"
|
||||||
|
warn(msg, DeprecationWarning, stacklevel=2)
|
||||||
|
return getattr(dev_or_mod, replacing_attr)
|
||||||
|
raise AttributeError(f"Device has no attribute {name!r}")
|
||||||
|
@ -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,9 +184,11 @@ class Energy(Module, ABC):
|
|||||||
"get_monthstat": "get_monthly_stats",
|
"get_monthstat": "get_monthly_stats",
|
||||||
}
|
}
|
||||||
|
|
||||||
def __getattr__(self, name: str) -> Any:
|
if not TYPE_CHECKING:
|
||||||
if attr := self._deprecated_attributes.get(name):
|
|
||||||
msg = f"{name} is deprecated, use {attr} instead"
|
def __getattr__(self, name: str) -> Any:
|
||||||
warn(msg, DeprecationWarning, stacklevel=2)
|
if attr := self._deprecated_attributes.get(name):
|
||||||
return getattr(self, attr)
|
msg = f"{name} is deprecated, use {attr} instead"
|
||||||
raise AttributeError(f"Energy module has no attribute {name!r}")
|
warn(msg, DeprecationWarning, stacklevel=2)
|
||||||
|
return getattr(self, attr)
|
||||||
|
raise AttributeError(f"Energy module has no attribute {name!r}")
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
|
@ -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:
|
||||||
|
@ -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.
|
||||||
|
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user