Rename and deprecate exception classes (#739)

# Public #
SmartDeviceException -> KasaException
UnsupportedDeviceException(SmartDeviceException) -> UnsupportedDeviceError(KasaException)
TimeoutException(SmartDeviceException, asyncio.TimeoutError) -> TimeoutError(KasaException, asyncio.TimeoutError)

Add new exception for error codes -> DeviceError(KasaException)
AuthenticationException(SmartDeviceException) -> AuthenticationError(DeviceError)

# Internal #
RetryableException(SmartDeviceException) -> _RetryableError(DeviceError)
ConnectionException(SmartDeviceException) -> _ConnectionError(KasaException)
This commit is contained in:
Steven B 2024-02-21 15:52:55 +00:00 committed by GitHub
parent 4beff228c9
commit 8c39e81a40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 393 additions and 361 deletions

View File

@ -22,12 +22,12 @@ import asyncclick as click
from devtools.helpers.smartrequests import SmartRequest, get_component_requests
from kasa import (
AuthenticationException,
AuthenticationError,
Credentials,
Device,
Discover,
SmartDeviceException,
TimeoutException,
KasaException,
TimeoutError,
)
from kasa.discover import DiscoveryResult
from kasa.exceptions import SmartErrorCode
@ -303,19 +303,16 @@ async def _make_requests_or_exit(
for method, result in responses.items():
final[method] = result
return final
except AuthenticationException as ex:
except AuthenticationError as ex:
_echo_error(
f"Unable to query the device due to an authentication error: {ex}",
)
exit(1)
except SmartDeviceException as ex:
except KasaException as ex:
_echo_error(
f"Unable to query {name} at once: {ex}",
)
if (
isinstance(ex, TimeoutException)
or ex.error_code == SmartErrorCode.SESSION_TIMEOUT_ERROR
):
if isinstance(ex, TimeoutError):
_echo_error(
"Timeout, try reducing the batch size via --batch-size option.",
)
@ -400,7 +397,7 @@ async def get_smart_fixture(device: SmartDevice, batch_size: int):
response = await device.protocol.query(
SmartRequest._create_request_dict(test_call.request)
)
except AuthenticationException as ex:
except AuthenticationError as ex:
_echo_error(
f"Unable to query the device due to an authentication error: {ex}",
)

View File

@ -100,6 +100,17 @@ The classes providing this functionality are:
- :class:`KlapTransport <kasa.klaptransport.KlapTransport>`
- :class:`KlapTransportV2 <kasa.klaptransport.KlapTransportV2>`
Errors and Exceptions
*********************
The base exception for all library errors is :class:`KasaException <kasa.exceptions.KasaException>`.
- If the device returns an error the library raises a :class:`DeviceError <kasa.exceptions.DeviceError>` which will usually contain an ``error_code`` with the detail.
- If the device fails to authenticate the library raises an :class:`AuthenticationError <kasa.exceptions.AuthenticationError>` which is derived
from :class:`DeviceError <kasa.exceptions.DeviceError>` and could contain an ``error_code`` depending on the type of failure.
- If the library encounters and unsupported deviceit raises an :class:`UnsupportedDeviceError <kasa.exceptions.UnsupportedDeviceError>`.
- If the device fails to respond within a timeout the library raises a :class:`TimeoutError <kasa.exceptions.TimeoutError>`.
- All other failures will raise the base :class:`KasaException <kasa.exceptions.KasaException>` class.
API documentation for modules
*****************************
@ -154,3 +165,26 @@ API documentation for protocols and transports
:members:
:inherited-members:
:undoc-members:
API documentation for errors and exceptions
*******************************************
.. autoclass:: kasa.exceptions.KasaException
:members:
:undoc-members:
.. autoclass:: kasa.exceptions.DeviceError
:members:
:undoc-members:
.. autoclass:: kasa.exceptions.AuthenticationError
:members:
:undoc-members:
.. autoclass:: kasa.exceptions.UnsupportedDeviceError
:members:
:undoc-members:
.. autoclass:: kasa.exceptions.TimeoutError
:members:
:undoc-members:

View File

@ -24,7 +24,7 @@ Methods changing the state of the device do not invalidate the cache (i.e., ther
You can assume that the operation has succeeded if no exception is raised.
These methods will return the device response, which can be useful for some use cases.
Errors are raised as :class:`SmartDeviceException` instances for the library user to handle.
Errors are raised as :class:`KasaException` instances for the library user to handle.
Simple example script showing some functionality for legacy devices:
@ -154,15 +154,3 @@ API documentation
.. autoclass:: Credentials
:members:
:undoc-members:
.. autoclass:: SmartDeviceException
:members:
:undoc-members:
.. autoclass:: AuthenticationException
:members:
:undoc-members:
.. autoclass:: UnsupportedDeviceException
:members:
:undoc-members:

View File

@ -8,7 +8,7 @@ All common, shared functionalities are available through `SmartDevice` class::
For device type specific actions `SmartBulb`, `SmartPlug`, or `SmartStrip`
should be used instead.
Module-specific errors are raised as `SmartDeviceException` and are expected
Module-specific errors are raised as `KasaException` and are expected
to be handled by the user of the library.
"""
from importlib.metadata import version
@ -28,10 +28,11 @@ from kasa.deviceconfig import (
from kasa.discover import Discover
from kasa.emeterstatus import EmeterStatus
from kasa.exceptions import (
AuthenticationException,
SmartDeviceException,
TimeoutException,
UnsupportedDeviceException,
AuthenticationError,
DeviceError,
KasaException,
TimeoutError,
UnsupportedDeviceError,
)
from kasa.feature import Feature, FeatureType
from kasa.iot.iotbulb import BulbPreset, TurnOnBehavior, TurnOnBehaviors
@ -61,10 +62,11 @@ __all__ = [
"Device",
"Bulb",
"Plug",
"SmartDeviceException",
"AuthenticationException",
"UnsupportedDeviceException",
"TimeoutException",
"KasaException",
"AuthenticationError",
"DeviceError",
"UnsupportedDeviceError",
"TimeoutError",
"Credentials",
"DeviceConfig",
"ConnectionType",
@ -84,6 +86,12 @@ deprecated_smart_devices = {
"SmartDimmer": iot.IotDimmer,
"SmartBulbPreset": BulbPreset,
}
deprecated_exceptions = {
"SmartDeviceException": KasaException,
"UnsupportedDeviceException": UnsupportedDeviceError,
"AuthenticationException": AuthenticationError,
"TimeoutException": TimeoutError,
}
def __getattr__(name):
@ -101,6 +109,11 @@ def __getattr__(name):
stacklevel=1,
)
return new_class
if name in deprecated_exceptions:
new_class = deprecated_exceptions[name]
msg = f"{name} is deprecated, use {new_class.__name__} instead"
warn(msg, DeprecationWarning, stacklevel=1)
return new_class
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@ -112,6 +125,11 @@ if TYPE_CHECKING:
SmartStrip = iot.IotStrip
SmartDimmer = iot.IotDimmer
SmartBulbPreset = BulbPreset
SmartDeviceException = KasaException
UnsupportedDeviceException = UnsupportedDeviceError
AuthenticationException = AuthenticationError
TimeoutException = TimeoutError
# Instanstiate all classes so the type checkers catch abstract issues
from . import smart

View File

@ -22,12 +22,11 @@ from .deviceconfig import DeviceConfig
from .exceptions import (
SMART_AUTHENTICATION_ERRORS,
SMART_RETRYABLE_ERRORS,
SMART_TIMEOUT_ERRORS,
AuthenticationException,
RetryableException,
SmartDeviceException,
AuthenticationError,
DeviceError,
KasaException,
SmartErrorCode,
TimeoutException,
_RetryableError,
)
from .httpclient import HttpClient
from .json import dumps as json_dumps
@ -141,14 +140,12 @@ class AesTransport(BaseTransport):
if error_code == SmartErrorCode.SUCCESS:
return
msg = f"{msg}: {self._host}: {error_code.name}({error_code.value})"
if error_code in SMART_TIMEOUT_ERRORS:
raise TimeoutException(msg, error_code=error_code)
if error_code in SMART_RETRYABLE_ERRORS:
raise RetryableException(msg, error_code=error_code)
raise _RetryableError(msg, error_code=error_code)
if error_code in SMART_AUTHENTICATION_ERRORS:
self._state = TransportState.HANDSHAKE_REQUIRED
raise AuthenticationException(msg, error_code=error_code)
raise SmartDeviceException(msg, error_code=error_code)
raise AuthenticationError(msg, error_code=error_code)
raise DeviceError(msg, error_code=error_code)
async def send_secure_passthrough(self, request: str) -> Dict[str, Any]:
"""Send encrypted message as passthrough."""
@ -171,7 +168,7 @@ class AesTransport(BaseTransport):
# _LOGGER.debug(f"secure_passthrough response is {status_code}: {resp_dict}")
if status_code != 200:
raise SmartDeviceException(
raise KasaException(
f"{self._host} responded with an unexpected "
+ f"status code {status_code} to passthrough"
)
@ -197,7 +194,7 @@ class AesTransport(BaseTransport):
self._host,
)
except Exception:
raise SmartDeviceException(
raise KasaException(
f"Unable to decrypt response from {self._host}, "
+ f"error: {ex}, response: {raw_response}",
ex,
@ -208,7 +205,7 @@ class AesTransport(BaseTransport):
"""Login to the device."""
try:
await self.try_login(self._login_params)
except AuthenticationException as aex:
except AuthenticationError as aex:
try:
if aex.error_code is not SmartErrorCode.LOGIN_ERROR:
raise aex
@ -223,10 +220,10 @@ class AesTransport(BaseTransport):
"%s: logged in with default credentials",
self._host,
)
except AuthenticationException:
except AuthenticationError:
raise
except Exception as ex:
raise SmartDeviceException(
raise KasaException(
"Unable to login and trying default "
+ f"login raised another exception: {ex}",
ex,
@ -292,7 +289,7 @@ class AesTransport(BaseTransport):
_LOGGER.debug("Device responded with: %s", resp_dict)
if status_code != 200:
raise SmartDeviceException(
raise KasaException(
f"{self._host} responded with an unexpected "
+ f"status code {status_code} to handshake"
)
@ -347,7 +344,7 @@ class AesTransport(BaseTransport):
await self.perform_login()
# After a login failure handshake needs to
# be redone or a 9999 error is received.
except AuthenticationException as ex:
except AuthenticationError as ex:
self._state = TransportState.HANDSHAKE_REQUIRED
raise ex

View File

@ -13,7 +13,7 @@ from typing import Any, Dict, cast
import asyncclick as click
from kasa import (
AuthenticationException,
AuthenticationError,
Bulb,
ConnectionType,
Credentials,
@ -22,8 +22,8 @@ from kasa import (
DeviceFamilyType,
Discover,
EncryptType,
SmartDeviceException,
UnsupportedDeviceException,
KasaException,
UnsupportedDeviceError,
)
from kasa.discover import DiscoveryResult
from kasa.iot import IotBulb, IotDevice, IotDimmer, IotLightStrip, IotPlug, IotStrip
@ -458,7 +458,7 @@ async def discover(ctx):
unsupported = []
auth_failed = []
async def print_unsupported(unsupported_exception: UnsupportedDeviceException):
async def print_unsupported(unsupported_exception: UnsupportedDeviceError):
unsupported.append(unsupported_exception)
async with sem:
if unsupported_exception.discovery_result:
@ -476,7 +476,7 @@ async def discover(ctx):
async with sem:
try:
await dev.update()
except AuthenticationException:
except AuthenticationError:
auth_failed.append(dev._discovery_info)
echo("== Authentication failed for device ==")
_echo_discovery_info(dev._discovery_info)
@ -677,7 +677,7 @@ async def cmd_command(dev: Device, module, command, parameters):
elif isinstance(dev, SmartDevice):
res = await dev._query_helper(command, parameters)
else:
raise SmartDeviceException("Unexpected device type %s.", dev)
raise KasaException("Unexpected device type %s.", dev)
echo(json.dumps(res))
return res

View File

@ -9,7 +9,7 @@ from .credentials import Credentials
from .device_type import DeviceType
from .deviceconfig import DeviceConfig
from .emeterstatus import EmeterStatus
from .exceptions import SmartDeviceException
from .exceptions import KasaException
from .feature import Feature
from .iotprotocol import IotProtocol
from .protocol import BaseProtocol
@ -242,12 +242,12 @@ class Device(ABC):
if p.alias == name:
return p
raise SmartDeviceException(f"Device has no child with {name}")
raise KasaException(f"Device has no child with {name}")
def get_plug_by_index(self, index: int) -> "Device":
"""Return child device for the given index."""
if index + 1 > len(self.children) or index < 0:
raise SmartDeviceException(
raise KasaException(
f"Invalid index {index}, device has {len(self.children)} plugs"
)
return self.children[index]
@ -306,7 +306,7 @@ class Device(ABC):
"""Add a new feature to the device."""
desc_name = feature.name.lower().replace(" ", "_")
if desc_name in self._features:
raise SmartDeviceException("Duplicate feature name %s" % desc_name)
raise KasaException("Duplicate feature name %s" % desc_name)
self._features[desc_name] = feature
@property

View File

@ -6,7 +6,7 @@ from typing import Any, Dict, Optional, Tuple, Type
from .aestransport import AesTransport
from .device import Device
from .deviceconfig import DeviceConfig
from .exceptions import SmartDeviceException, UnsupportedDeviceException
from .exceptions import KasaException, UnsupportedDeviceError
from .iot import IotBulb, IotDevice, IotDimmer, IotLightStrip, IotPlug, IotStrip
from .iotprotocol import IotProtocol
from .klaptransport import KlapTransport, KlapTransportV2
@ -45,12 +45,12 @@ async def connect(*, host: Optional[str] = None, config: DeviceConfig) -> "Devic
:return: Object for querying/controlling found device.
"""
if host and config or (not host and not config):
raise SmartDeviceException("One of host or config must be provded and not both")
raise KasaException("One of host or config must be provded and not both")
if host:
config = DeviceConfig(host=host)
if (protocol := get_protocol(config=config)) is None:
raise UnsupportedDeviceException(
raise UnsupportedDeviceError(
f"Unsupported device for {config.host}: "
+ f"{config.connection_type.device_family.value}"
)
@ -99,7 +99,7 @@ async def _connect(config: DeviceConfig, protocol: BaseProtocol) -> "Device":
_perf_log(True, "update")
return device
else:
raise UnsupportedDeviceException(
raise UnsupportedDeviceError(
f"Unsupported device for {config.host}: "
+ f"{config.connection_type.device_family.value}"
)
@ -108,12 +108,12 @@ async def _connect(config: DeviceConfig, protocol: BaseProtocol) -> "Device":
def get_device_class_from_sys_info(info: Dict[str, Any]) -> Type[IotDevice]:
"""Find SmartDevice subclass for device described by passed data."""
if "system" not in info or "get_sysinfo" not in info["system"]:
raise SmartDeviceException("No 'system' or 'get_sysinfo' in response")
raise KasaException("No 'system' or 'get_sysinfo' in response")
sysinfo: Dict[str, Any] = info["system"]["get_sysinfo"]
type_: Optional[str] = sysinfo.get("type", sysinfo.get("mic_type"))
if type_ is None:
raise SmartDeviceException("Unable to find the device type field!")
raise KasaException("Unable to find the device type field!")
if "dev_name" in sysinfo and "Dimmer" in sysinfo["dev_name"]:
return IotDimmer
@ -129,7 +129,7 @@ def get_device_class_from_sys_info(info: Dict[str, Any]) -> Type[IotDevice]:
return IotLightStrip
return IotBulb
raise UnsupportedDeviceException("Unknown device type: %s" % type_)
raise UnsupportedDeviceError("Unknown device type: %s" % type_)
def get_device_class_from_family(device_type: str) -> Optional[Type[Device]]:

View File

@ -5,7 +5,7 @@ from enum import Enum
from typing import TYPE_CHECKING, Dict, Optional, Union
from .credentials import Credentials
from .exceptions import SmartDeviceException
from .exceptions import KasaException
if TYPE_CHECKING:
from aiohttp import ClientSession
@ -46,7 +46,7 @@ def _dataclass_from_dict(klass, in_val):
fieldtypes[dict_key], in_val[dict_key]
)
else:
raise SmartDeviceException(
raise KasaException(
f"Cannot create dataclass from dict, unknown key: {dict_key}"
)
return klass(**val)
@ -92,7 +92,7 @@ class ConnectionType:
login_version,
)
except (ValueError, TypeError) as ex:
raise SmartDeviceException(
raise KasaException(
f"Invalid connection parameters for {device_family}."
+ f"{encryption_type}.{login_version}"
) from ex
@ -113,9 +113,7 @@ class ConnectionType:
login_version, # type: ignore[arg-type]
)
raise SmartDeviceException(
f"Invalid connection type data for {connection_type_dict}"
)
raise KasaException(f"Invalid connection type data for {connection_type_dict}")
def to_dict(self) -> Dict[str, Union[str, int]]:
"""Convert connection params to dict."""
@ -185,4 +183,4 @@ class DeviceConfig:
"""Return device config from dict."""
if isinstance(config_dict, dict):
return _dataclass_from_dict(DeviceConfig, config_dict)
raise SmartDeviceException(f"Invalid device config data: {config_dict}")
raise KasaException(f"Invalid device config data: {config_dict}")

View File

@ -24,9 +24,9 @@ from kasa.device_factory import (
)
from kasa.deviceconfig import ConnectionType, DeviceConfig, EncryptType
from kasa.exceptions import (
SmartDeviceException,
TimeoutException,
UnsupportedDeviceException,
KasaException,
TimeoutError,
UnsupportedDeviceError,
)
from kasa.iot.iotdevice import IotDevice
from kasa.json import dumps as json_dumps
@ -59,7 +59,7 @@ class _DiscoverProtocol(asyncio.DatagramProtocol):
discovery_timeout: int = 5,
interface: Optional[str] = None,
on_unsupported: Optional[
Callable[[UnsupportedDeviceException], Awaitable[None]]
Callable[[UnsupportedDeviceError], Awaitable[None]]
] = None,
port: Optional[int] = None,
credentials: Optional[Credentials] = None,
@ -162,14 +162,14 @@ class _DiscoverProtocol(asyncio.DatagramProtocol):
device = Discover._get_device_instance(data, config)
else:
return
except UnsupportedDeviceException as udex:
except UnsupportedDeviceError as udex:
_LOGGER.debug("Unsupported device found at %s << %s", ip, udex)
self.unsupported_device_exceptions[ip] = udex
if self.on_unsupported is not None:
self._run_callback_task(self.on_unsupported(udex))
self._handle_discovered_event()
return
except SmartDeviceException as ex:
except KasaException as ex:
_LOGGER.debug(f"[DISCOVERY] Unable to find device type for {ip}: {ex}")
self.invalid_device_exceptions[ip] = ex
self._handle_discovered_event()
@ -311,7 +311,7 @@ class Discover:
try:
_LOGGER.debug("Waiting %s seconds for responses...", discovery_timeout)
await protocol.wait_for_discovery_to_complete()
except SmartDeviceException as ex:
except KasaException as ex:
for device in protocol.discovered_devices.values():
await device.protocol.close()
raise ex
@ -368,9 +368,7 @@ class Discover:
# https://docs.python.org/3/library/socket.html#socket.getaddrinfo
ip = adrrinfo[0][4][0]
except socket.gaierror as gex:
raise SmartDeviceException(
f"Could not resolve hostname {host}"
) from gex
raise KasaException(f"Could not resolve hostname {host}") from gex
transport, protocol = await loop.create_datagram_endpoint(
lambda: _DiscoverProtocol(
@ -401,7 +399,7 @@ class Discover:
elif ip in protocol.invalid_device_exceptions:
raise protocol.invalid_device_exceptions[ip]
else:
raise TimeoutException(f"Timed out getting discovery response for {host}")
raise TimeoutError(f"Timed out getting discovery response for {host}")
@staticmethod
def _get_device_class(info: dict) -> Type[Device]:
@ -410,7 +408,7 @@ class Discover:
discovery_result = DiscoveryResult(**info["result"])
dev_class = get_device_class_from_family(discovery_result.device_type)
if not dev_class:
raise UnsupportedDeviceException(
raise UnsupportedDeviceError(
"Unknown device type: %s" % discovery_result.device_type,
discovery_result=info,
)
@ -424,7 +422,7 @@ class Discover:
try:
info = json_loads(XorEncryption.decrypt(data))
except Exception as ex:
raise SmartDeviceException(
raise KasaException(
f"Unable to read response from device: {config.host}: {ex}"
) from ex
@ -451,7 +449,7 @@ class Discover:
info = json_loads(data[16:])
except Exception as ex:
_LOGGER.debug("Got invalid response from device %s: %s", config.host, data)
raise SmartDeviceException(
raise KasaException(
f"Unable to read response from device: {config.host}: {ex}"
) from ex
try:
@ -460,7 +458,7 @@ class Discover:
_LOGGER.debug(
"Unable to parse discovery from device %s: %s", config.host, info
)
raise UnsupportedDeviceException(
raise UnsupportedDeviceError(
f"Unable to parse discovery from device: {config.host}: {ex}"
) from ex
@ -472,15 +470,15 @@ class Discover:
discovery_result.mgt_encrypt_schm.encrypt_type,
discovery_result.mgt_encrypt_schm.lv,
)
except SmartDeviceException as ex:
raise UnsupportedDeviceException(
except KasaException as ex:
raise UnsupportedDeviceError(
f"Unsupported device {config.host} of type {type_} "
+ f"with encrypt_type {discovery_result.mgt_encrypt_schm.encrypt_type}",
discovery_result=discovery_result.get_dict(),
) from ex
if (device_class := get_device_class_from_family(type_)) is None:
_LOGGER.warning("Got unsupported device type: %s", type_)
raise UnsupportedDeviceException(
raise UnsupportedDeviceError(
f"Unsupported device {config.host} of type {type_}: {info}",
discovery_result=discovery_result.get_dict(),
)
@ -488,7 +486,7 @@ class Discover:
_LOGGER.warning(
"Got unsupported connection type: %s", config.connection_type.to_dict()
)
raise UnsupportedDeviceException(
raise UnsupportedDeviceError(
f"Unsupported encryption scheme {config.host} of "
+ f"type {config.connection_type.to_dict()}: {info}",
discovery_result=discovery_result.get_dict(),

View File

@ -1,47 +1,59 @@
"""python-kasa exceptions."""
from asyncio import TimeoutError
from asyncio import TimeoutError as _asyncioTimeoutError
from enum import IntEnum
from typing import Any, Optional
class SmartDeviceException(Exception):
class KasaException(Exception):
"""Base exception for library errors."""
class TimeoutError(KasaException, _asyncioTimeoutError):
"""Timeout exception for device errors."""
def __repr__(self):
return KasaException.__repr__(self)
def __str__(self):
return KasaException.__str__(self)
class _ConnectionError(KasaException):
"""Connection exception for device errors."""
class UnsupportedDeviceError(KasaException):
"""Exception for trying to connect to unsupported devices."""
def __init__(self, *args: Any, **kwargs: Any) -> None:
self.discovery_result = kwargs.get("discovery_result")
super().__init__(*args)
class DeviceError(KasaException):
"""Base exception for device errors."""
def __init__(self, *args: Any, **kwargs: Any) -> None:
self.error_code: Optional["SmartErrorCode"] = kwargs.get("error_code", None)
super().__init__(*args)
def __repr__(self):
err_code = self.error_code.__repr__() if self.error_code else ""
return f"{self.__class__.__name__}({err_code})"
class UnsupportedDeviceException(SmartDeviceException):
"""Exception for trying to connect to unsupported devices."""
def __init__(self, *args: Any, **kwargs: Any) -> None:
self.discovery_result = kwargs.get("discovery_result")
super().__init__(*args, **kwargs)
def __str__(self):
err_code = f" (error_code={self.error_code.name})" if self.error_code else ""
return super().__str__() + err_code
class AuthenticationException(SmartDeviceException):
class AuthenticationError(DeviceError):
"""Base exception for device authentication errors."""
class RetryableException(SmartDeviceException):
class _RetryableError(DeviceError):
"""Retryable exception for device errors."""
class TimeoutException(SmartDeviceException, TimeoutError):
"""Timeout exception for device errors."""
def __repr__(self):
return SmartDeviceException.__repr__(self)
def __str__(self):
return SmartDeviceException.__str__(self)
class ConnectionException(SmartDeviceException):
"""Connection exception for device errors."""
class SmartErrorCode(IntEnum):
"""Enum for SMART Error Codes."""
@ -109,6 +121,7 @@ SMART_RETRYABLE_ERRORS = [
SmartErrorCode.TRANSPORT_NOT_AVAILABLE_ERROR,
SmartErrorCode.HTTP_TRANSPORT_FAILED_ERROR,
SmartErrorCode.UNSPECIFIC_ERROR,
SmartErrorCode.SESSION_TIMEOUT_ERROR,
]
SMART_AUTHENTICATION_ERRORS = [
@ -118,7 +131,3 @@ SMART_AUTHENTICATION_ERRORS = [
SmartErrorCode.HAND_SHAKE_FAILED_ERROR,
SmartErrorCode.TRANSPORT_UNKNOWN_CREDENTIALS_ERROR,
]
SMART_TIMEOUT_ERRORS = [
SmartErrorCode.SESSION_TIMEOUT_ERROR,
]

View File

@ -8,9 +8,9 @@ from yarl import URL
from .deviceconfig import DeviceConfig
from .exceptions import (
ConnectionException,
SmartDeviceException,
TimeoutException,
KasaException,
TimeoutError,
_ConnectionError,
)
from .json import loads as json_loads
@ -86,17 +86,17 @@ class HttpClient:
response_data = json_loads(response_data.decode())
except (aiohttp.ServerDisconnectedError, aiohttp.ClientOSError) as ex:
raise ConnectionException(
raise _ConnectionError(
f"Device connection error: {self._config.host}: {ex}", ex
) from ex
except (aiohttp.ServerTimeoutError, asyncio.TimeoutError) as ex:
raise TimeoutException(
raise TimeoutError(
"Unable to query the device, "
+ f"timed out: {self._config.host}: {ex}",
ex,
) from ex
except Exception as ex:
raise SmartDeviceException(
raise KasaException(
f"Unable to query the device: {self._config.host}: {ex}", ex
) from ex

View File

@ -13,7 +13,7 @@ from ..bulb import HSV, Bulb, BulbPreset, ColorTempRange
from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig
from ..protocol import BaseProtocol
from .iotdevice import IotDevice, SmartDeviceException, requires_update
from .iotdevice import IotDevice, KasaException, requires_update
from .modules import Antitheft, Cloud, Countdown, Emeter, Schedule, Time, Usage
@ -97,7 +97,7 @@ class IotBulb(IotDevice, Bulb):
so you must await :func:`update()` to fetch updates values from the device.
Errors reported by the device are raised as
:class:`SmartDeviceExceptions <kasa.exceptions.SmartDeviceException>`,
:class:`KasaException <kasa.exceptions.KasaException>`,
and should be handled by the user of the library.
Examples:
@ -233,7 +233,7 @@ class IotBulb(IotDevice, Bulb):
:return: White temperature range in Kelvin (minimum, maximum)
"""
if not self.is_variable_color_temp:
raise SmartDeviceException("Color temperature not supported")
raise KasaException("Color temperature not supported")
for model, temp_range in TPLINK_KELVIN.items():
sys_info = self.sys_info
@ -249,7 +249,7 @@ class IotBulb(IotDevice, Bulb):
"""Query the light state."""
light_state = self.sys_info["light_state"]
if light_state is None:
raise SmartDeviceException(
raise KasaException(
"The device has no light_state or you have not called update()"
)
@ -333,7 +333,7 @@ class IotBulb(IotDevice, Bulb):
:return: hue, saturation and value (degrees, %, %)
"""
if not self.is_color:
raise SmartDeviceException("Bulb does not support color.")
raise KasaException("Bulb does not support color.")
light_state = cast(dict, self.light_state)
@ -360,7 +360,7 @@ class IotBulb(IotDevice, Bulb):
:param int transition: transition in milliseconds.
"""
if not self.is_color:
raise SmartDeviceException("Bulb does not support color.")
raise KasaException("Bulb does not support color.")
if not isinstance(hue, int) or not (0 <= hue <= 360):
raise ValueError(f"Invalid hue value: {hue} (valid range: 0-360)")
@ -387,7 +387,7 @@ class IotBulb(IotDevice, Bulb):
def color_temp(self) -> int:
"""Return color temperature of the device in kelvin."""
if not self.is_variable_color_temp:
raise SmartDeviceException("Bulb does not support colortemp.")
raise KasaException("Bulb does not support colortemp.")
light_state = self.light_state
return int(light_state["color_temp"])
@ -402,7 +402,7 @@ class IotBulb(IotDevice, Bulb):
:param int transition: transition in milliseconds.
"""
if not self.is_variable_color_temp:
raise SmartDeviceException("Bulb does not support colortemp.")
raise KasaException("Bulb does not support colortemp.")
valid_temperature_range = self.valid_temperature_range
if temp < valid_temperature_range[0] or temp > valid_temperature_range[1]:
@ -423,7 +423,7 @@ class IotBulb(IotDevice, Bulb):
def brightness(self) -> int:
"""Return the current brightness in percentage."""
if not self.is_dimmable: # pragma: no cover
raise SmartDeviceException("Bulb is not dimmable.")
raise KasaException("Bulb is not dimmable.")
light_state = self.light_state
return int(light_state["brightness"])
@ -438,7 +438,7 @@ class IotBulb(IotDevice, Bulb):
:param int transition: transition in milliseconds.
"""
if not self.is_dimmable: # pragma: no cover
raise SmartDeviceException("Bulb is not dimmable.")
raise KasaException("Bulb is not dimmable.")
self._raise_for_invalid_brightness(brightness)
@ -511,10 +511,10 @@ class IotBulb(IotDevice, Bulb):
obtained using :func:`presets`.
"""
if len(self.presets) == 0:
raise SmartDeviceException("Device does not supported saving presets")
raise KasaException("Device does not supported saving presets")
if preset.index >= len(self.presets):
raise SmartDeviceException("Invalid preset index")
raise KasaException("Invalid preset index")
return await self._query_helper(
self.LIGHT_SERVICE, "set_preferred_state", preset.dict(exclude_none=True)

View File

@ -21,7 +21,7 @@ from typing import Any, Dict, List, Optional, Sequence, Set
from ..device import Device, WifiNetwork
from ..deviceconfig import DeviceConfig
from ..emeterstatus import EmeterStatus
from ..exceptions import SmartDeviceException
from ..exceptions import KasaException
from ..feature import Feature
from ..protocol import BaseProtocol
from .iotmodule import IotModule
@ -48,9 +48,7 @@ def requires_update(f):
async def wrapped(*args, **kwargs):
self = args[0]
if self._last_update is None and f.__name__ not in self._sys_info:
raise SmartDeviceException(
"You need to await update() to access the data"
)
raise KasaException("You need to await update() to access the data")
return await f(*args, **kwargs)
else:
@ -59,9 +57,7 @@ def requires_update(f):
def wrapped(*args, **kwargs):
self = args[0]
if self._last_update is None and f.__name__ not in self._sys_info:
raise SmartDeviceException(
"You need to await update() to access the data"
)
raise KasaException("You need to await update() to access the data")
return f(*args, **kwargs)
f.requires_update = True
@ -92,7 +88,8 @@ class IotDevice(Device):
All changes to the device are done using awaitable methods,
which will not change the cached values, but you must await update() separately.
Errors reported by the device are raised as SmartDeviceExceptions,
Errors reported by the device are raised as
:class:`KasaException <kasa.exceptions.KasaException>`,
and should be handled by the user of the library.
Examples:
@ -221,9 +218,9 @@ class IotDevice(Device):
def _verify_emeter(self) -> None:
"""Raise an exception if there is no emeter."""
if not self.has_emeter:
raise SmartDeviceException("Device has no emeter")
raise KasaException("Device has no emeter")
if self.emeter_type not in self._last_update:
raise SmartDeviceException("update() required prior accessing emeter")
raise KasaException("update() required prior accessing emeter")
async def _query_helper(
self, target: str, cmd: str, arg: Optional[Dict] = None, child_ids=None
@ -241,20 +238,20 @@ class IotDevice(Device):
try:
response = await self._raw_query(request=request)
except Exception as ex:
raise SmartDeviceException(f"Communication error on {target}:{cmd}") from ex
raise KasaException(f"Communication error on {target}:{cmd}") from ex
if target not in response:
raise SmartDeviceException(f"No required {target} in response: {response}")
raise KasaException(f"No required {target} in response: {response}")
result = response[target]
if "err_code" in result and result["err_code"] != 0:
raise SmartDeviceException(f"Error on {target}.{cmd}: {result}")
raise KasaException(f"Error on {target}.{cmd}: {result}")
if cmd not in result:
raise SmartDeviceException(f"No command in response: {response}")
raise KasaException(f"No command in response: {response}")
result = result[cmd]
if "err_code" in result and result["err_code"] != 0:
raise SmartDeviceException(f"Error on {target} {cmd}: {result}")
raise KasaException(f"Error on {target} {cmd}: {result}")
if "err_code" in result:
del result["err_code"]
@ -513,7 +510,7 @@ class IotDevice(Device):
sys_info = self._sys_info
mac = sys_info.get("mac", sys_info.get("mic_mac"))
if not mac:
raise SmartDeviceException(
raise KasaException(
"Unknown mac, please submit a bug report with sys_info output."
)
mac = mac.replace("-", ":")
@ -656,14 +653,14 @@ class IotDevice(Device):
try:
info = await _scan("netif")
except SmartDeviceException as ex:
except KasaException as ex:
_LOGGER.debug(
"Unable to scan using 'netif', retrying with 'softaponboarding': %s", ex
)
info = await _scan("smartlife.iot.common.softaponboarding")
if "ap_list" not in info:
raise SmartDeviceException("Invalid response for wifi scan: %s" % info)
raise KasaException("Invalid response for wifi scan: %s" % info)
return [WifiNetwork(**x) for x in info["ap_list"]]
@ -679,7 +676,7 @@ class IotDevice(Device):
payload = {"ssid": ssid, "password": password, "key_type": int(keytype)}
try:
return await _join("netif", payload)
except SmartDeviceException as ex:
except KasaException as ex:
_LOGGER.debug(
"Unable to join using 'netif', retrying with 'softaponboarding': %s", ex
)

View File

@ -5,7 +5,7 @@ from typing import Any, Dict, Optional
from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig
from ..protocol import BaseProtocol
from .iotdevice import SmartDeviceException, requires_update
from .iotdevice import KasaException, requires_update
from .iotplug import IotPlug
from .modules import AmbientLight, Motion
@ -46,7 +46,7 @@ class IotDimmer(IotPlug):
which will not change the cached values,
but you must await :func:`update()` separately.
Errors reported by the device are raised as :class:`SmartDeviceException`\s,
Errors reported by the device are raised as :class:`KasaException`\s,
and should be handled by the user of the library.
Examples:
@ -88,7 +88,7 @@ class IotDimmer(IotPlug):
Will return a range between 0 - 100.
"""
if not self.is_dimmable:
raise SmartDeviceException("Device is not dimmable.")
raise KasaException("Device is not dimmable.")
sys_info = self.sys_info
return int(sys_info["brightness"])
@ -103,7 +103,7 @@ class IotDimmer(IotPlug):
Using a transition will cause the dimmer to turn on.
"""
if not self.is_dimmable:
raise SmartDeviceException("Device is not dimmable.")
raise KasaException("Device is not dimmable.")
if not isinstance(brightness, int):
raise ValueError(

View File

@ -6,7 +6,7 @@ from ..deviceconfig import DeviceConfig
from ..effects import EFFECT_MAPPING_V1, EFFECT_NAMES_V1
from ..protocol import BaseProtocol
from .iotbulb import IotBulb
from .iotdevice import SmartDeviceException, requires_update
from .iotdevice import KasaException, requires_update
class IotLightStrip(IotBulb):
@ -117,7 +117,7 @@ class IotLightStrip(IotBulb):
:param int transition: The wanted transition time
"""
if effect not in EFFECT_MAPPING_V1:
raise SmartDeviceException(f"The effect {effect} is not a built in effect.")
raise KasaException(f"The effect {effect} is not a built in effect.")
effect_dict = EFFECT_MAPPING_V1[effect]
if brightness is not None:
effect_dict["brightness"] = brightness
@ -136,7 +136,7 @@ class IotLightStrip(IotBulb):
:param str effect_dict: The custom effect dict to set
"""
if not self.has_effects:
raise SmartDeviceException("Bulb does not support effects.")
raise KasaException("Bulb does not support effects.")
await self._query_helper(
"smartlife.iot.lighting_effect",
"set_lighting_effect",

View File

@ -2,7 +2,7 @@
import collections
import logging
from ..exceptions import SmartDeviceException
from ..exceptions import KasaException
from ..module import Module
_LOGGER = logging.getLogger(__name__)
@ -43,7 +43,7 @@ class IotModule(Module):
def data(self):
"""Return the module specific raw data from the last update."""
if self._module not in self._device._last_update:
raise SmartDeviceException(
raise KasaException(
f"You need to call update() prior accessing module data"
f" for '{self._module}'"
)

View File

@ -22,7 +22,7 @@ class IotPlug(IotDevice):
which will not change the cached values,
but you must await :func:`update()` separately.
Errors reported by the device are raised as :class:`SmartDeviceException`\s,
Errors reported by the device are raised as :class:`KasaException`\s,
and should be handled by the user of the library.
Examples:

View File

@ -6,7 +6,7 @@ from typing import Any, DefaultDict, Dict, Optional
from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig
from ..exceptions import SmartDeviceException
from ..exceptions import KasaException
from ..protocol import BaseProtocol
from .iotdevice import (
EmeterStatus,
@ -43,7 +43,7 @@ class IotStrip(IotDevice):
which will not change the cached values,
but you must await :func:`update()` separately.
Errors reported by the device are raised as :class:`SmartDeviceException`\s,
Errors reported by the device are raised as :class:`KasaException`\s,
and should be handled by the user of the library.
Examples:
@ -375,4 +375,4 @@ class IotStripPlug(IotPlug):
if plug["id"] == self.child_id:
return plug
raise SmartDeviceException(f"Unable to find children {self.child_id}")
raise KasaException(f"Unable to find children {self.child_id}")

View File

@ -2,7 +2,7 @@
from enum import Enum
from typing import Optional
from ...exceptions import SmartDeviceException
from ...exceptions import KasaException
from ..iotmodule import IotModule
@ -54,9 +54,7 @@ class Motion(IotModule):
elif range is not None:
payload = {"index": range.value}
else:
raise SmartDeviceException(
"Either range or custom_range need to be defined"
)
raise KasaException("Either range or custom_range need to be defined")
return await self.call("set_trigger_sens", payload)

View File

@ -1,7 +1,7 @@
"""Provides the current time and timezone information."""
from datetime import datetime
from ...exceptions import SmartDeviceException
from ...exceptions import KasaException
from ..iotmodule import IotModule, merge
@ -46,7 +46,7 @@ class Time(IotModule):
res["min"],
res["sec"],
)
except SmartDeviceException:
except KasaException:
return None
async def get_timezone(self):

View File

@ -5,11 +5,11 @@ from typing import Dict, Optional, Union
from .deviceconfig import DeviceConfig
from .exceptions import (
AuthenticationException,
ConnectionException,
RetryableException,
SmartDeviceException,
TimeoutException,
AuthenticationError,
KasaException,
TimeoutError,
_ConnectionError,
_RetryableError,
)
from .json import dumps as json_dumps
from .protocol import BaseProtocol, BaseTransport
@ -46,31 +46,31 @@ class IotProtocol(BaseProtocol):
for retry in range(retry_count + 1):
try:
return await self._execute_query(request, retry)
except ConnectionException as sdex:
except _ConnectionError as sdex:
if retry >= retry_count:
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
raise sdex
continue
except AuthenticationException as auex:
except AuthenticationError as auex:
await self._transport.reset()
_LOGGER.debug(
"Unable to authenticate with %s, not retrying", self._host
)
raise auex
except RetryableException as ex:
except _RetryableError as ex:
await self._transport.reset()
if retry >= retry_count:
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
raise ex
continue
except TimeoutException as ex:
except TimeoutError as ex:
await self._transport.reset()
if retry >= retry_count:
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
raise ex
await asyncio.sleep(self.BACKOFF_SECONDS_AFTER_TIMEOUT)
continue
except SmartDeviceException as ex:
except KasaException as ex:
await self._transport.reset()
_LOGGER.debug(
"Unable to query the device: %s, not retrying: %s",
@ -80,7 +80,7 @@ class IotProtocol(BaseProtocol):
raise ex
# make mypy happy, this should never be reached..
raise SmartDeviceException("Query reached somehow to unreachable")
raise KasaException("Query reached somehow to unreachable")
async def _execute_query(self, request: str, retry_count: int) -> Dict:
return await self._transport.send(request)
@ -101,7 +101,7 @@ class _deprecated_TPLinkSmartHomeProtocol(IotProtocol):
) -> None:
"""Create a protocol object."""
if not host and not transport:
raise SmartDeviceException("host or transport must be supplied")
raise KasaException("host or transport must be supplied")
if not transport:
config = DeviceConfig(
host=host, # type: ignore[arg-type]

View File

@ -57,7 +57,7 @@ from yarl import URL
from .credentials import Credentials
from .deviceconfig import DeviceConfig
from .exceptions import AuthenticationException, SmartDeviceException
from .exceptions import AuthenticationError, KasaException
from .httpclient import HttpClient
from .json import loads as json_loads
from .protocol import DEFAULT_CREDENTIALS, BaseTransport, get_default_credentials, md5
@ -159,7 +159,7 @@ class KlapTransport(BaseTransport):
)
if response_status != 200:
raise SmartDeviceException(
raise KasaException(
f"Device {self._host} responded with {response_status} to handshake1"
)
@ -168,7 +168,7 @@ class KlapTransport(BaseTransport):
server_hash = response_data[16:]
if len(server_hash) != 32:
raise SmartDeviceException(
raise KasaException(
f"Device {self._host} responded with unexpected klap response "
+ f"{response_data!r} to handshake1"
)
@ -236,7 +236,7 @@ class KlapTransport(BaseTransport):
msg = f"Server response doesn't match our challenge on ip {self._host}"
_LOGGER.debug(msg)
raise AuthenticationException(msg)
raise AuthenticationError(msg)
async def perform_handshake2(
self, local_seed, remote_seed, auth_hash
@ -267,8 +267,8 @@ class KlapTransport(BaseTransport):
if response_status != 200:
# This shouldn't be caused by incorrect
# credentials so don't raise AuthenticationException
raise SmartDeviceException(
# credentials so don't raise AuthenticationError
raise KasaException(
f"Device {self._host} responded with {response_status} to handshake2"
)
@ -337,12 +337,12 @@ class KlapTransport(BaseTransport):
# If we failed with a security error, force a new handshake next time.
if response_status == 403:
self._handshake_done = False
raise AuthenticationException(
raise AuthenticationError(
f"Got a security error from {self._host} after handshake "
+ "completed"
)
else:
raise SmartDeviceException(
raise KasaException(
f"Device {self._host} responded with {response_status} to"
+ f"request with seq {seq}"
)

View File

@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
from typing import Dict
from .device import Device
from .exceptions import SmartDeviceException
from .exceptions import KasaException
from .feature import Feature
_LOGGER = logging.getLogger(__name__)
@ -39,7 +39,7 @@ class Module(ABC):
"""Add module feature."""
feat_name = f"{self._module}_{feature.name}"
if feat_name in self._module_features:
raise SmartDeviceException("Duplicate name detected %s" % feat_name)
raise KasaException("Duplicate name detected %s" % feat_name)
self._module_features[feat_name] = feature
def __repr__(self) -> str:

View File

@ -4,7 +4,7 @@ from typing import Any, Dict, List, Optional
from ..bulb import Bulb
from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig
from ..exceptions import SmartDeviceException
from ..exceptions import KasaException
from ..iot.iotbulb import HSV, BulbPreset, ColorTempRange
from ..smartprotocol import SmartProtocol
from .smartdevice import SmartDevice
@ -57,7 +57,7 @@ class SmartBulb(SmartDevice, Bulb):
:return: White temperature range in Kelvin (minimum, maximum)
"""
if not self.is_variable_color_temp:
raise SmartDeviceException("Color temperature not supported")
raise KasaException("Color temperature not supported")
ct_range = self._info.get("color_temp_range", [0, 0])
return ColorTempRange(min=ct_range[0], max=ct_range[1])
@ -107,7 +107,7 @@ class SmartBulb(SmartDevice, Bulb):
:return: hue, saturation and value (degrees, %, %)
"""
if not self.is_color:
raise SmartDeviceException("Bulb does not support color.")
raise KasaException("Bulb does not support color.")
h, s, v = (
self._info.get("hue", 0),
@ -121,7 +121,7 @@ class SmartBulb(SmartDevice, Bulb):
def color_temp(self) -> int:
"""Whether the bulb supports color temperature changes."""
if not self.is_variable_color_temp:
raise SmartDeviceException("Bulb does not support colortemp.")
raise KasaException("Bulb does not support colortemp.")
return self._info.get("color_temp", -1)
@ -129,7 +129,7 @@ class SmartBulb(SmartDevice, Bulb):
def brightness(self) -> int:
"""Return the current brightness in percentage."""
if not self.is_dimmable: # pragma: no cover
raise SmartDeviceException("Bulb is not dimmable.")
raise KasaException("Bulb is not dimmable.")
return self._info.get("brightness", -1)
@ -151,7 +151,7 @@ class SmartBulb(SmartDevice, Bulb):
:param int transition: transition in milliseconds.
"""
if not self.is_color:
raise SmartDeviceException("Bulb does not support color.")
raise KasaException("Bulb does not support color.")
if not isinstance(hue, int) or not (0 <= hue <= 360):
raise ValueError(f"Invalid hue value: {hue} (valid range: 0-360)")
@ -188,7 +188,7 @@ class SmartBulb(SmartDevice, Bulb):
# TODO: Note, trying to set brightness at the same time
# with color_temp causes error -1008
if not self.is_variable_color_temp:
raise SmartDeviceException("Bulb does not support colortemp.")
raise KasaException("Bulb does not support colortemp.")
valid_temperature_range = self.valid_temperature_range
if temp < valid_temperature_range[0] or temp > valid_temperature_range[1]:
@ -211,7 +211,7 @@ class SmartBulb(SmartDevice, Bulb):
:param int transition: transition in milliseconds.
"""
if not self.is_dimmable: # pragma: no cover
raise SmartDeviceException("Bulb is not dimmable.")
raise KasaException("Bulb is not dimmable.")
return await self.protocol.query(
{"set_device_info": {"brightness": brightness}}

View File

@ -9,7 +9,7 @@ from ..device import Device, WifiNetwork
from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig
from ..emeterstatus import EmeterStatus
from ..exceptions import AuthenticationException, SmartDeviceException, SmartErrorCode
from ..exceptions import AuthenticationError, DeviceError, KasaException, SmartErrorCode
from ..feature import Feature, FeatureType
from ..smartprotocol import SmartProtocol
from .modules import ( # noqa: F401
@ -85,7 +85,7 @@ class SmartDevice(Device):
return response
if default is not None:
return default
raise SmartDeviceException(
raise KasaException(
f"{request} not found in {responses} for device {self.host}"
)
@ -100,7 +100,7 @@ class SmartDevice(Device):
async def update(self, update_children: bool = True):
"""Update the device."""
if self.credentials is None and self.credentials_hash is None:
raise AuthenticationException("Tapo plug requires authentication.")
raise AuthenticationError("Tapo plug requires authentication.")
if self._components_raw is None:
await self._negotiate()
@ -341,7 +341,7 @@ class SmartDevice(Device):
"""Retrieve current energy readings."""
_LOGGER.warning("Deprecated, use `emeter_realtime`.")
if not self.has_emeter:
raise SmartDeviceException("Device has no emeter")
raise KasaException("Device has no emeter")
return self.emeter_realtime
@property
@ -421,7 +421,7 @@ class SmartDevice(Device):
after some delay.
"""
if not self.credentials:
raise AuthenticationException("Device requires authentication.")
raise AuthenticationError("Device requires authentication.")
payload = {
"account": {
@ -445,10 +445,9 @@ class SmartDevice(Device):
# Thus, We limit retries and suppress the raised exception as useless.
try:
return await self.protocol.query({"set_qs_info": payload}, retry_count=0)
except SmartDeviceException as ex:
if ex.error_code: # Re-raise on device-reported errors
raise
except DeviceError:
raise # Re-raise on device-reported errors
except KasaException:
_LOGGER.debug("Received an expected for wifi join, but this is expected")
async def update_credentials(self, username: str, password: str):

View File

@ -2,7 +2,7 @@
import logging
from typing import TYPE_CHECKING, Dict, Type
from ..exceptions import SmartDeviceException
from ..exceptions import KasaException
from ..module import Module
if TYPE_CHECKING:
@ -59,7 +59,7 @@ class SmartModule(Module):
q_keys = list(q.keys())
# TODO: hacky way to check if update has been called.
if q_keys[0] not in self._device._last_update:
raise SmartDeviceException(
raise KasaException(
f"You need to call update() prior accessing module data"
f" for '{self._module}'"
)

View File

@ -15,13 +15,13 @@ from typing import Any, Dict, Union
from .exceptions import (
SMART_AUTHENTICATION_ERRORS,
SMART_RETRYABLE_ERRORS,
SMART_TIMEOUT_ERRORS,
AuthenticationException,
ConnectionException,
RetryableException,
SmartDeviceException,
AuthenticationError,
DeviceError,
KasaException,
SmartErrorCode,
TimeoutException,
TimeoutError,
_ConnectionError,
_RetryableError,
)
from .json import dumps as json_dumps
from .protocol import BaseProtocol, BaseTransport, md5
@ -66,32 +66,32 @@ class SmartProtocol(BaseProtocol):
for retry in range(retry_count + 1):
try:
return await self._execute_query(request, retry)
except ConnectionException as sdex:
except _ConnectionError as sdex:
if retry >= retry_count:
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
raise sdex
continue
except AuthenticationException as auex:
except AuthenticationError as auex:
await self._transport.reset()
_LOGGER.debug(
"Unable to authenticate with %s, not retrying", self._host
)
raise auex
except RetryableException as ex:
except _RetryableError as ex:
await self._transport.reset()
if retry >= retry_count:
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
raise ex
await asyncio.sleep(self.BACKOFF_SECONDS_AFTER_TIMEOUT)
continue
except TimeoutException as ex:
except TimeoutError as ex:
await self._transport.reset()
if retry >= retry_count:
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
raise ex
await asyncio.sleep(self.BACKOFF_SECONDS_AFTER_TIMEOUT)
continue
except SmartDeviceException as ex:
except KasaException as ex:
await self._transport.reset()
_LOGGER.debug(
"Unable to query the device: %s, not retrying: %s",
@ -101,7 +101,7 @@ class SmartProtocol(BaseProtocol):
raise ex
# make mypy happy, this should never be reached..
raise SmartDeviceException("Query reached somehow to unreachable")
raise KasaException("Query reached somehow to unreachable")
async def _execute_multiple_query(self, request: Dict, retry_count: int) -> Dict:
debug_enabled = _LOGGER.isEnabledFor(logging.DEBUG)
@ -193,13 +193,11 @@ class SmartProtocol(BaseProtocol):
+ f"{error_code.name}({error_code.value})"
+ f" for method: {method}"
)
if error_code in SMART_TIMEOUT_ERRORS:
raise TimeoutException(msg, error_code=error_code)
if error_code in SMART_RETRYABLE_ERRORS:
raise RetryableException(msg, error_code=error_code)
raise _RetryableError(msg, error_code=error_code)
if error_code in SMART_AUTHENTICATION_ERRORS:
raise AuthenticationException(msg, error_code=error_code)
raise SmartDeviceException(msg, error_code=error_code)
raise AuthenticationError(msg, error_code=error_code)
raise DeviceError(msg, error_code=error_code)
async def close(self) -> None:
"""Close the underlying transport."""

View File

@ -1,7 +1,7 @@
import warnings
from json import loads as json_loads
from kasa import Credentials, DeviceConfig, SmartDeviceException, SmartProtocol
from kasa import Credentials, DeviceConfig, KasaException, SmartProtocol
from kasa.protocol import BaseTransport
@ -144,7 +144,7 @@ class FakeSmartTransport(BaseTransport):
)
return {"result": missing_result[1], "error_code": 0}
else:
raise SmartDeviceException(f"Fixture doesn't support {method}")
raise KasaException(f"Fixture doesn't support {method}")
elif method == "set_qs_info":
return {"error_code": 0}
elif method[:4] == "set_":

View File

@ -19,8 +19,8 @@ from ..aestransport import AesEncyptionSession, AesTransport, TransportState
from ..credentials import Credentials
from ..deviceconfig import DeviceConfig
from ..exceptions import (
AuthenticationException,
SmartDeviceException,
AuthenticationError,
KasaException,
SmartErrorCode,
)
from ..httpclient import HttpClient
@ -49,8 +49,8 @@ status_parameters = pytest.mark.parametrize(
"status_code, error_code, inner_error_code, expectation",
[
(200, 0, 0, does_not_raise()),
(400, 0, 0, pytest.raises(SmartDeviceException)),
(200, -1, 0, pytest.raises(SmartDeviceException)),
(400, 0, 0, pytest.raises(KasaException)),
(200, -1, 0, pytest.raises(KasaException)),
],
ids=("success", "status_code", "error_code"),
)
@ -101,17 +101,17 @@ async def test_login(mocker, status_code, error_code, inner_error_code, expectat
([SmartErrorCode.LOGIN_ERROR, 0, 0, 0], does_not_raise(), 4),
(
[SmartErrorCode.LOGIN_ERROR, SmartErrorCode.LOGIN_ERROR],
pytest.raises(AuthenticationException),
pytest.raises(AuthenticationError),
3,
),
(
[SmartErrorCode.LOGIN_FAILED_ERROR],
pytest.raises(AuthenticationException),
pytest.raises(AuthenticationError),
1,
),
(
[SmartErrorCode.LOGIN_ERROR, SmartErrorCode.SESSION_TIMEOUT_ERROR],
pytest.raises(SmartDeviceException),
pytest.raises(KasaException),
3,
),
],
@ -238,7 +238,7 @@ async def test_unencrypted_response_invalid_json(mocker, caplog):
}
caplog.set_level(logging.DEBUG)
msg = f"Unable to decrypt response from {host}, error: Incorrect padding, response: Foobar"
with pytest.raises(SmartDeviceException, match=msg):
with pytest.raises(KasaException, match=msg):
await transport.send(json_dumps(request))
@ -267,7 +267,7 @@ async def test_passthrough_errors(mocker, error_code):
"requestID": 1,
"terminal_uuid": "foobar",
}
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await transport.send(json_dumps(request))

View File

@ -7,7 +7,7 @@ from voluptuous import (
Schema,
)
from kasa import Bulb, BulbPreset, DeviceType, SmartDeviceException
from kasa import Bulb, BulbPreset, DeviceType, KasaException
from kasa.iot import IotBulb
from .conftest import (
@ -51,7 +51,7 @@ async def test_state_attributes(dev: Bulb):
@bulb_iot
async def test_light_state_without_update(dev: IotBulb, monkeypatch):
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
monkeypatch.setitem(
dev._last_update["system"]["get_sysinfo"], "light_state", None
)
@ -123,9 +123,9 @@ async def test_color_state_information(dev: Bulb):
async def test_hsv_on_non_color(dev: Bulb):
assert not dev.is_color
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await dev.set_hsv(0, 0, 0)
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
print(dev.hsv)
@ -175,13 +175,13 @@ async def test_out_of_range_temperature(dev: Bulb):
@non_variable_temp
async def test_non_variable_temp(dev: Bulb):
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await dev.set_color_temp(2700)
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
print(dev.valid_temperature_range)
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
print(dev.color_temp)
@ -238,9 +238,9 @@ async def test_invalid_brightness(dev: Bulb):
async def test_non_dimmable(dev: Bulb):
assert not dev.is_dimmable
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
assert dev.brightness == 0
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await dev.set_brightness(100)
@ -296,7 +296,7 @@ async def test_modify_preset(dev: IotBulb, mocker):
await dev.save_preset(preset)
assert dev.presets[0].brightness == 10
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await dev.save_preset(
BulbPreset(index=5, hue=0, brightness=0, saturation=0, color_temp=0)
)

View File

@ -6,12 +6,13 @@ import pytest
from asyncclick.testing import CliRunner
from kasa import (
AuthenticationException,
AuthenticationError,
Credentials,
Device,
DeviceError,
EmeterStatus,
SmartDeviceException,
UnsupportedDeviceException,
KasaException,
UnsupportedDeviceError,
)
from kasa.cli import (
TYPE_TO_CLASS,
@ -188,15 +189,13 @@ async def test_wifi_join_no_creds(dev):
)
assert res.exit_code != 0
assert isinstance(res.exception, AuthenticationException)
assert isinstance(res.exception, AuthenticationError)
@device_smart
async def test_wifi_join_exception(dev, mocker):
runner = CliRunner()
mocker.patch.object(
dev.protocol, "query", side_effect=SmartDeviceException(error_code=9999)
)
mocker.patch.object(dev.protocol, "query", side_effect=DeviceError(error_code=9999))
res = await runner.invoke(
wifi,
["join", "FOOBAR", "--keytype", "wpa_psk", "--password", "foobar"],
@ -204,7 +203,7 @@ async def test_wifi_join_exception(dev, mocker):
)
assert res.exit_code != 0
assert isinstance(res.exception, SmartDeviceException)
assert isinstance(res.exception, KasaException)
@device_smart
@ -509,7 +508,7 @@ async def test_host_unsupported(unsupported_device_info):
)
assert res.exit_code != 0
assert isinstance(res.exception, UnsupportedDeviceException)
assert isinstance(res.exception, UnsupportedDeviceError)
@new_discovery
@ -522,7 +521,7 @@ async def test_discover_auth_failed(discovery_mock, mocker):
mocker.patch.object(
device_class,
"update",
side_effect=AuthenticationException("Failed to authenticate"),
side_effect=AuthenticationError("Failed to authenticate"),
)
res = await runner.invoke(
cli,
@ -553,7 +552,7 @@ async def test_host_auth_failed(discovery_mock, mocker):
mocker.patch.object(
device_class,
"update",
side_effect=AuthenticationException("Failed to authenticate"),
side_effect=AuthenticationError("Failed to authenticate"),
)
res = await runner.invoke(
cli,
@ -569,7 +568,7 @@ async def test_host_auth_failed(discovery_mock, mocker):
)
assert res.exit_code != 0
assert isinstance(res.exception, AuthenticationException)
assert isinstance(res.exception, AuthenticationError)
@pytest.mark.parametrize("device_type", list(TYPE_TO_CLASS))
@ -616,7 +615,7 @@ async def test_shell(dev: Device, mocker):
async def test_errors(mocker):
runner = CliRunner()
err = SmartDeviceException("Foobar")
err = KasaException("Foobar")
# Test masking
mocker.patch("kasa.Discover.discover", side_effect=err)

View File

@ -8,7 +8,7 @@ from kasa import (
Credentials,
Device,
Discover,
SmartDeviceException,
KasaException,
)
from kasa.device_factory import connect, get_protocol
from kasa.deviceconfig import (
@ -110,8 +110,8 @@ async def test_connect_logs_connect_time(
async def test_connect_query_fails(all_fixture_data: dict, mocker):
"""Make sure that connect fails when query fails."""
host = "127.0.0.1"
mocker.patch("kasa.IotProtocol.query", side_effect=SmartDeviceException)
mocker.patch("kasa.SmartProtocol.query", side_effect=SmartDeviceException)
mocker.patch("kasa.IotProtocol.query", side_effect=KasaException)
mocker.patch("kasa.SmartProtocol.query", side_effect=KasaException)
ctype, _ = _get_connection_type_device_class(all_fixture_data)
config = DeviceConfig(
@ -120,7 +120,7 @@ async def test_connect_query_fails(all_fixture_data: dict, mocker):
protocol_class = get_protocol(config).__class__
close_mock = mocker.patch.object(protocol_class, "close")
assert close_mock.call_count == 0
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await connect(config=config)
assert close_mock.call_count == 1

View File

@ -8,7 +8,7 @@ from kasa.credentials import Credentials
from kasa.deviceconfig import (
DeviceConfig,
)
from kasa.exceptions import SmartDeviceException
from kasa.exceptions import KasaException
async def test_serialization():
@ -29,7 +29,7 @@ async def test_serialization():
ids=["invalid-dict", "not-dict"],
)
def test_deserialization_errors(input_value, expected_msg):
with pytest.raises(SmartDeviceException, match=expected_msg):
with pytest.raises(KasaException, match=expected_msg):
DeviceConfig.from_dict(input_value)

View File

@ -13,14 +13,14 @@ from kasa import (
Device,
DeviceType,
Discover,
SmartDeviceException,
KasaException,
)
from kasa.deviceconfig import (
ConnectionType,
DeviceConfig,
)
from kasa.discover import DiscoveryResult, _DiscoverProtocol, json_dumps
from kasa.exceptions import AuthenticationException, UnsupportedDeviceException
from kasa.exceptions import AuthenticationError, UnsupportedDeviceError
from kasa.iot import IotDevice
from kasa.xortransport import XorEncryption
@ -94,7 +94,7 @@ async def test_type_detection_lightstrip(dev: Device):
async def test_type_unknown():
invalid_info = {"system": {"get_sysinfo": {"type": "nosuchtype"}}}
with pytest.raises(UnsupportedDeviceException):
with pytest.raises(UnsupportedDeviceError):
Discover._get_device_class(invalid_info)
@ -151,7 +151,7 @@ async def test_discover_single_hostname(discovery_mock, mocker):
assert update_mock.call_count == 0
mocker.patch("socket.getaddrinfo", side_effect=socket.gaierror())
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
x = await Discover.discover_single(host, credentials=Credentials())
@ -161,7 +161,7 @@ async def test_discover_single_unsupported(unsupported_device_info, mocker):
# Test with a valid unsupported response
with pytest.raises(
UnsupportedDeviceException,
UnsupportedDeviceError,
):
await Discover.discover_single(host)
@ -171,7 +171,7 @@ async def test_discover_single_no_response(mocker):
host = "127.0.0.1"
mocker.patch.object(_DiscoverProtocol, "do_discover")
with pytest.raises(
SmartDeviceException, match=f"Timed out getting discovery response for {host}"
KasaException, match=f"Timed out getting discovery response for {host}"
):
await Discover.discover_single(host, discovery_timeout=0)
@ -198,7 +198,7 @@ async def test_discover_invalid_info(msg, data, mocker):
mocker.patch.object(_DiscoverProtocol, "do_discover", mock_discover)
with pytest.raises(SmartDeviceException, match=msg):
with pytest.raises(KasaException, match=msg):
await Discover.discover_single(host)
@ -280,11 +280,11 @@ async def test_discover_single_authentication(discovery_mock, mocker):
mocker.patch.object(
device_class,
"update",
side_effect=AuthenticationException("Failed to authenticate"),
side_effect=AuthenticationError("Failed to authenticate"),
)
with pytest.raises(
AuthenticationException,
AuthenticationError,
match="Failed to authenticate",
):
device = await Discover.discover_single(
@ -315,7 +315,7 @@ async def test_device_update_from_new_discovery_info(discovery_data):
# TODO implement requires_update for SmartDevice
if isinstance(device, IotDevice):
with pytest.raises(
SmartDeviceException,
KasaException,
match=re.escape("You need to await update() to access the data"),
):
assert device.supported_modules
@ -456,9 +456,9 @@ async def test_discover_propogates_task_exceptions(discovery_mock):
discovery_timeout = 0
async def on_discovered(dev):
raise SmartDeviceException("Dummy exception")
raise KasaException("Dummy exception")
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await Discover.discover(
discovery_timeout=discovery_timeout, on_discovered=on_discovered
)

View File

@ -10,7 +10,7 @@ from voluptuous import (
Schema,
)
from kasa import EmeterStatus, SmartDeviceException
from kasa import EmeterStatus, KasaException
from kasa.iot import IotDevice
from kasa.iot.modules.emeter import Emeter
@ -38,16 +38,16 @@ CURRENT_CONSUMPTION_SCHEMA = Schema(
async def test_no_emeter(dev):
assert not dev.has_emeter
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await dev.get_emeter_realtime()
# Only iot devices support the historical stats so other
# devices will not implement the methods below
if isinstance(dev, IotDevice):
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await dev.get_emeter_daily()
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await dev.get_emeter_monthly()
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await dev.erase_emeter_stats()

View File

@ -6,9 +6,9 @@ import pytest
from ..deviceconfig import DeviceConfig
from ..exceptions import (
ConnectionException,
SmartDeviceException,
TimeoutException,
KasaException,
TimeoutError,
_ConnectionError,
)
from ..httpclient import HttpClient
@ -18,28 +18,28 @@ from ..httpclient import HttpClient
[
(
aiohttp.ServerDisconnectedError(),
ConnectionException,
_ConnectionError,
"Device connection error: ",
),
(
aiohttp.ClientOSError(),
ConnectionException,
_ConnectionError,
"Device connection error: ",
),
(
aiohttp.ServerTimeoutError(),
TimeoutException,
TimeoutError,
"Unable to query the device, timed out: ",
),
(
asyncio.TimeoutError(),
TimeoutException,
TimeoutError,
"Unable to query the device, timed out: ",
),
(Exception(), SmartDeviceException, "Unable to query the device: "),
(Exception(), KasaException, "Unable to query the device: "),
(
aiohttp.ServerFingerprintMismatch("exp", "got", "host", 1),
SmartDeviceException,
KasaException,
"Unable to query the device: ",
),
],

View File

@ -12,11 +12,11 @@ from ..aestransport import AesTransport
from ..credentials import Credentials
from ..deviceconfig import DeviceConfig
from ..exceptions import (
AuthenticationException,
ConnectionException,
RetryableException,
SmartDeviceException,
TimeoutException,
AuthenticationError,
KasaException,
TimeoutError,
_ConnectionError,
_RetryableError,
)
from ..httpclient import HttpClient
from ..iotprotocol import IotProtocol
@ -68,7 +68,7 @@ async def test_protocol_retries_via_client_session(
mocker.patch.object(protocol_class, "BACKOFF_SECONDS_AFTER_TIMEOUT", 0)
config = DeviceConfig(host)
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await protocol_class(transport=transport_class(config=config)).query(
DUMMY_QUERY, retry_count=retry_count
)
@ -80,11 +80,11 @@ async def test_protocol_retries_via_client_session(
@pytest.mark.parametrize(
"error, retry_expectation",
[
(SmartDeviceException("dummy exception"), False),
(RetryableException("dummy exception"), True),
(TimeoutException("dummy exception"), True),
(KasaException("dummy exception"), False),
(_RetryableError("dummy exception"), True),
(TimeoutError("dummy exception"), True),
],
ids=("SmartDeviceException", "RetryableException", "TimeoutException"),
ids=("KasaException", "_RetryableError", "TimeoutError"),
)
@pytest.mark.parametrize("transport_class", [AesTransport, KlapTransport])
@pytest.mark.parametrize("protocol_class", [IotProtocol, SmartProtocol])
@ -97,7 +97,7 @@ async def test_protocol_retries_via_httpclient(
mocker.patch.object(protocol_class, "BACKOFF_SECONDS_AFTER_TIMEOUT", 0)
config = DeviceConfig(host)
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await protocol_class(transport=transport_class(config=config)).query(
DUMMY_QUERY, retry_count=retry_count
)
@ -115,11 +115,11 @@ async def test_protocol_no_retry_on_connection_error(
conn = mocker.patch.object(
aiohttp.ClientSession,
"post",
side_effect=AuthenticationException("foo"),
side_effect=AuthenticationError("foo"),
)
mocker.patch.object(protocol_class, "BACKOFF_SECONDS_AFTER_TIMEOUT", 0)
config = DeviceConfig(host)
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await protocol_class(transport=transport_class(config=config)).query(
DUMMY_QUERY, retry_count=5
)
@ -139,7 +139,7 @@ async def test_protocol_retry_recoverable_error(
side_effect=aiohttp.ClientOSError("foo"),
)
config = DeviceConfig(host)
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await protocol_class(transport=transport_class(config=config)).query(
DUMMY_QUERY, retry_count=5
)
@ -159,7 +159,7 @@ async def test_protocol_reconnect(mocker, retry_count, protocol_class, transport
nonlocal remaining
remaining -= 1
if remaining:
raise ConnectionException("Simulated connection failure")
raise _ConnectionError("Simulated connection failure")
return mock_response
@ -249,7 +249,7 @@ def test_encrypt_unicode():
),
(
Credentials("shouldfail", "shouldfail"),
pytest.raises(AuthenticationException),
pytest.raises(AuthenticationError),
),
],
ids=("client", "blank", "kasa_setup", "shouldfail"),
@ -350,7 +350,7 @@ async def test_handshake(
assert protocol._transport._handshake_done is True
response_status = 403
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await protocol._transport.perform_handshake()
assert protocol._transport._handshake_done is False
await protocol.close()
@ -405,37 +405,37 @@ async def test_query(mocker):
pytest.param(
(403, 403, 403),
True,
pytest.raises(SmartDeviceException),
pytest.raises(KasaException),
id="handshake1-403-status",
),
pytest.param(
(200, 403, 403),
True,
pytest.raises(SmartDeviceException),
pytest.raises(KasaException),
id="handshake2-403-status",
),
pytest.param(
(200, 200, 403),
True,
pytest.raises(AuthenticationException),
pytest.raises(AuthenticationError),
id="request-403-status",
),
pytest.param(
(200, 200, 400),
True,
pytest.raises(SmartDeviceException),
pytest.raises(KasaException),
id="request-400-status",
),
pytest.param(
(200, 200, 200),
False,
pytest.raises(AuthenticationException),
pytest.raises(AuthenticationError),
id="handshake1-wrong-auth",
),
pytest.param(
(200, 200, 200),
secrets.token_bytes(16),
pytest.raises(SmartDeviceException),
pytest.raises(KasaException),
id="handshake1-bad-auth-length",
),
],

View File

@ -1,7 +1,7 @@
import pytest
from kasa import DeviceType
from kasa.exceptions import SmartDeviceException
from kasa.exceptions import KasaException
from kasa.iot import IotLightStrip
from .conftest import lightstrip
@ -23,7 +23,7 @@ async def test_lightstrip_effect(dev: IotLightStrip):
@lightstrip
async def test_effects_lightstrip_set_effect(dev: IotLightStrip):
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await dev.set_effect("Not real")
await dev.set_effect("Candy Cane")

View File

@ -14,7 +14,7 @@ import pytest
from ..aestransport import AesTransport
from ..credentials import Credentials
from ..deviceconfig import DeviceConfig
from ..exceptions import SmartDeviceException
from ..exceptions import KasaException
from ..iotprotocol import IotProtocol, _deprecated_TPLinkSmartHomeProtocol
from ..klaptransport import KlapTransport, KlapTransportV2
from ..protocol import (
@ -46,7 +46,7 @@ async def test_protocol_retries(mocker, retry_count, protocol_class, transport_c
conn = mocker.patch("asyncio.open_connection", side_effect=aio_mock_writer)
config = DeviceConfig("127.0.0.1")
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await protocol_class(transport=transport_class(config=config)).query(
{}, retry_count=retry_count
)
@ -70,7 +70,7 @@ async def test_protocol_no_retry_on_unreachable(
side_effect=OSError(errno.EHOSTUNREACH, "No route to host"),
)
config = DeviceConfig("127.0.0.1")
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await protocol_class(transport=transport_class(config=config)).query(
{}, retry_count=5
)
@ -94,7 +94,7 @@ async def test_protocol_no_retry_connection_refused(
side_effect=ConnectionRefusedError,
)
config = DeviceConfig("127.0.0.1")
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await protocol_class(transport=transport_class(config=config)).query(
{}, retry_count=5
)
@ -118,7 +118,7 @@ async def test_protocol_retry_recoverable_error(
side_effect=OSError(errno.ECONNRESET, "Connection reset by peer"),
)
config = DeviceConfig("127.0.0.1")
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await protocol_class(transport=transport_class(config=config)).query(
{}, retry_count=5
)
@ -553,7 +553,7 @@ async def test_protocol_will_retry_on_connect(
retry_count = 2
conn = mocker.patch("asyncio.open_connection", side_effect=error)
config = DeviceConfig("127.0.0.1")
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await protocol_class(transport=transport_class(config=config)).query(
{}, retry_count=retry_count
)
@ -595,7 +595,7 @@ async def test_protocol_will_retry_on_write(
conn = mocker.patch("asyncio.open_connection", side_effect=aio_mock_writer)
write_mock = mocker.patch("asyncio.StreamWriter.write", side_effect=error)
config = DeviceConfig("127.0.0.1")
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await protocol_class(transport=transport_class(config=config)).query(
{}, retry_count=retry_count
)
@ -609,9 +609,7 @@ def test_deprecated_protocol():
with pytest.deprecated_call():
from kasa import TPLinkSmartHomeProtocol
with pytest.raises(
SmartDeviceException, match="host or transport must be supplied"
):
with pytest.raises(KasaException, match="host or transport must be supplied"):
proto = TPLinkSmartHomeProtocol()
host = "127.0.0.1"
proto = TPLinkSmartHomeProtocol(host=host)

View File

@ -21,7 +21,7 @@ from voluptuous import (
)
import kasa
from kasa import Credentials, Device, DeviceConfig, SmartDeviceException
from kasa import Credentials, Device, DeviceConfig, KasaException
from kasa.exceptions import SmartErrorCode
from kasa.iot import IotDevice
from kasa.smart import SmartChildDevice, SmartDevice
@ -67,8 +67,8 @@ async def test_state_info(dev):
@device_iot
async def test_invalid_connection(dev):
with patch.object(
FakeIotProtocol, "query", side_effect=SmartDeviceException
), pytest.raises(SmartDeviceException):
FakeIotProtocol, "query", side_effect=KasaException
), pytest.raises(KasaException):
await dev.update()
@ -98,7 +98,7 @@ async def test_initial_update_no_emeter(dev, mocker):
@device_iot
async def test_query_helper(dev):
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await dev._query_helper("test", "testcmd", {})
# TODO check for unwrapping?
@ -328,7 +328,7 @@ async def test_update_no_device_info(dev: SmartDevice):
}
msg = f"get_device_info not found in {mock_response} for device 127.0.0.123"
with patch.object(dev.protocol, "query", return_value=mock_response), pytest.raises(
SmartDeviceException, match=msg
KasaException, match=msg
):
await dev.update()
@ -348,6 +348,16 @@ def test_deprecated_devices(device_class, use_class):
getattr(module, use_class.__name__)
@pytest.mark.parametrize(
"exceptions_class, use_class", kasa.deprecated_exceptions.items()
)
def test_deprecated_exceptions(exceptions_class, use_class):
msg = f"{exceptions_class} is deprecated, use {use_class.__name__} instead"
with pytest.deprecated_call(match=msg):
getattr(kasa, exceptions_class)
getattr(kasa, use_class.__name__)
def check_mac(x):
if re.match("[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", x.lower()):
return x

View File

@ -1,13 +1,10 @@
from itertools import chain
import pytest
from ..credentials import Credentials
from ..deviceconfig import DeviceConfig
from ..exceptions import (
SMART_RETRYABLE_ERRORS,
SMART_TIMEOUT_ERRORS,
SmartDeviceException,
KasaException,
SmartErrorCode,
)
from ..smartprotocol import _ChildProtocolWrapper
@ -28,13 +25,10 @@ async def test_smart_device_errors(dummy_protocol, mocker, error_code):
dummy_protocol._transport, "send", return_value=mock_response
)
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await dummy_protocol.query(DUMMY_QUERY, retry_count=2)
if error_code in chain(SMART_TIMEOUT_ERRORS, SMART_RETRYABLE_ERRORS):
expected_calls = 3
else:
expected_calls = 1
expected_calls = 3 if error_code in SMART_RETRYABLE_ERRORS else 1
assert send_mock.call_count == expected_calls
@ -124,7 +118,7 @@ async def test_childdevicewrapper_error(dummy_protocol, mocker):
mock_response = {"error_code": 0, "result": {"responseData": {"error_code": -1001}}}
mocker.patch.object(wrapped_protocol._transport, "send", return_value=mock_response)
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await wrapped_protocol.query(DUMMY_QUERY)
@ -180,5 +174,5 @@ async def test_childdevicewrapper_multiplerequest_error(dummy_protocol, mocker):
}
mocker.patch.object(dummy_protocol._transport, "send", return_value=mock_response)
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
await dummy_protocol.query(DUMMY_QUERY)

View File

@ -2,7 +2,7 @@ from datetime import datetime
import pytest
from kasa import SmartDeviceException
from kasa import KasaException
from kasa.iot import IotStrip
from .conftest import handle_turn_on, strip, turn_on
@ -73,7 +73,7 @@ async def test_get_plug_by_name(dev: IotStrip):
name = dev.children[0].alias
assert dev.get_plug_by_name(name) == dev.children[0] # type: ignore[arg-type]
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
dev.get_plug_by_name("NONEXISTING NAME")
@ -81,10 +81,10 @@ async def test_get_plug_by_name(dev: IotStrip):
async def test_get_plug_by_index(dev: IotStrip):
assert dev.get_plug_by_index(0) == dev.children[0]
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
dev.get_plug_by_index(-1)
with pytest.raises(SmartDeviceException):
with pytest.raises(KasaException):
dev.get_plug_by_index(len(dev.children))

View File

@ -23,7 +23,7 @@ from typing import Dict, Generator, Optional
from async_timeout import timeout as asyncio_timeout
from .deviceconfig import DeviceConfig
from .exceptions import RetryableException, SmartDeviceException
from .exceptions import KasaException, _RetryableError
from .json import loads as json_loads
from .protocol import BaseTransport
@ -129,24 +129,24 @@ class XorTransport(BaseTransport):
await self._connect(self._timeout)
except ConnectionRefusedError as ex:
await self.reset()
raise SmartDeviceException(
raise KasaException(
f"Unable to connect to the device: {self._host}:{self._port}: {ex}"
) from ex
except OSError as ex:
await self.reset()
if ex.errno in _NO_RETRY_ERRORS:
raise SmartDeviceException(
raise KasaException(
f"Unable to connect to the device:"
f" {self._host}:{self._port}: {ex}"
) from ex
else:
raise RetryableException(
raise _RetryableError(
f"Unable to connect to the device:"
f" {self._host}:{self._port}: {ex}"
) from ex
except Exception as ex:
await self.reset()
raise RetryableException(
raise _RetryableError(
f"Unable to connect to the device:" f" {self._host}:{self._port}: {ex}"
) from ex
except BaseException:
@ -162,7 +162,7 @@ class XorTransport(BaseTransport):
return await self._execute_send(request)
except Exception as ex:
await self.reset()
raise RetryableException(
raise _RetryableError(
f"Unable to query the device {self._host}:{self._port}: {ex}"
) from ex
except BaseException: