mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-22 19:23:34 +00:00
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:
parent
4beff228c9
commit
8c39e81a40
@ -22,12 +22,12 @@ import asyncclick as click
|
|||||||
|
|
||||||
from devtools.helpers.smartrequests import SmartRequest, get_component_requests
|
from devtools.helpers.smartrequests import SmartRequest, get_component_requests
|
||||||
from kasa import (
|
from kasa import (
|
||||||
AuthenticationException,
|
AuthenticationError,
|
||||||
Credentials,
|
Credentials,
|
||||||
Device,
|
Device,
|
||||||
Discover,
|
Discover,
|
||||||
SmartDeviceException,
|
KasaException,
|
||||||
TimeoutException,
|
TimeoutError,
|
||||||
)
|
)
|
||||||
from kasa.discover import DiscoveryResult
|
from kasa.discover import DiscoveryResult
|
||||||
from kasa.exceptions import SmartErrorCode
|
from kasa.exceptions import SmartErrorCode
|
||||||
@ -303,19 +303,16 @@ async def _make_requests_or_exit(
|
|||||||
for method, result in responses.items():
|
for method, result in responses.items():
|
||||||
final[method] = result
|
final[method] = result
|
||||||
return final
|
return final
|
||||||
except AuthenticationException as ex:
|
except AuthenticationError as ex:
|
||||||
_echo_error(
|
_echo_error(
|
||||||
f"Unable to query the device due to an authentication error: {ex}",
|
f"Unable to query the device due to an authentication error: {ex}",
|
||||||
)
|
)
|
||||||
exit(1)
|
exit(1)
|
||||||
except SmartDeviceException as ex:
|
except KasaException as ex:
|
||||||
_echo_error(
|
_echo_error(
|
||||||
f"Unable to query {name} at once: {ex}",
|
f"Unable to query {name} at once: {ex}",
|
||||||
)
|
)
|
||||||
if (
|
if isinstance(ex, TimeoutError):
|
||||||
isinstance(ex, TimeoutException)
|
|
||||||
or ex.error_code == SmartErrorCode.SESSION_TIMEOUT_ERROR
|
|
||||||
):
|
|
||||||
_echo_error(
|
_echo_error(
|
||||||
"Timeout, try reducing the batch size via --batch-size option.",
|
"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(
|
response = await device.protocol.query(
|
||||||
SmartRequest._create_request_dict(test_call.request)
|
SmartRequest._create_request_dict(test_call.request)
|
||||||
)
|
)
|
||||||
except AuthenticationException as ex:
|
except AuthenticationError as ex:
|
||||||
_echo_error(
|
_echo_error(
|
||||||
f"Unable to query the device due to an authentication error: {ex}",
|
f"Unable to query the device due to an authentication error: {ex}",
|
||||||
)
|
)
|
||||||
|
@ -100,6 +100,17 @@ The classes providing this functionality are:
|
|||||||
- :class:`KlapTransport <kasa.klaptransport.KlapTransport>`
|
- :class:`KlapTransport <kasa.klaptransport.KlapTransport>`
|
||||||
- :class:`KlapTransportV2 <kasa.klaptransport.KlapTransportV2>`
|
- :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
|
API documentation for modules
|
||||||
*****************************
|
*****************************
|
||||||
@ -154,3 +165,26 @@ API documentation for protocols and transports
|
|||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
:undoc-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:
|
||||||
|
@ -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.
|
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.
|
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:
|
Simple example script showing some functionality for legacy devices:
|
||||||
|
|
||||||
@ -154,15 +154,3 @@ API documentation
|
|||||||
.. autoclass:: Credentials
|
.. autoclass:: Credentials
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
||||||
.. autoclass:: SmartDeviceException
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
|
|
||||||
.. autoclass:: AuthenticationException
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
|
|
||||||
.. autoclass:: UnsupportedDeviceException
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
|
@ -8,7 +8,7 @@ All common, shared functionalities are available through `SmartDevice` class::
|
|||||||
For device type specific actions `SmartBulb`, `SmartPlug`, or `SmartStrip`
|
For device type specific actions `SmartBulb`, `SmartPlug`, or `SmartStrip`
|
||||||
should be used instead.
|
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.
|
to be handled by the user of the library.
|
||||||
"""
|
"""
|
||||||
from importlib.metadata import version
|
from importlib.metadata import version
|
||||||
@ -28,10 +28,11 @@ from kasa.deviceconfig import (
|
|||||||
from kasa.discover import Discover
|
from kasa.discover import Discover
|
||||||
from kasa.emeterstatus import EmeterStatus
|
from kasa.emeterstatus import EmeterStatus
|
||||||
from kasa.exceptions import (
|
from kasa.exceptions import (
|
||||||
AuthenticationException,
|
AuthenticationError,
|
||||||
SmartDeviceException,
|
DeviceError,
|
||||||
TimeoutException,
|
KasaException,
|
||||||
UnsupportedDeviceException,
|
TimeoutError,
|
||||||
|
UnsupportedDeviceError,
|
||||||
)
|
)
|
||||||
from kasa.feature import Feature, FeatureType
|
from kasa.feature import Feature, FeatureType
|
||||||
from kasa.iot.iotbulb import BulbPreset, TurnOnBehavior, TurnOnBehaviors
|
from kasa.iot.iotbulb import BulbPreset, TurnOnBehavior, TurnOnBehaviors
|
||||||
@ -61,10 +62,11 @@ __all__ = [
|
|||||||
"Device",
|
"Device",
|
||||||
"Bulb",
|
"Bulb",
|
||||||
"Plug",
|
"Plug",
|
||||||
"SmartDeviceException",
|
"KasaException",
|
||||||
"AuthenticationException",
|
"AuthenticationError",
|
||||||
"UnsupportedDeviceException",
|
"DeviceError",
|
||||||
"TimeoutException",
|
"UnsupportedDeviceError",
|
||||||
|
"TimeoutError",
|
||||||
"Credentials",
|
"Credentials",
|
||||||
"DeviceConfig",
|
"DeviceConfig",
|
||||||
"ConnectionType",
|
"ConnectionType",
|
||||||
@ -84,6 +86,12 @@ deprecated_smart_devices = {
|
|||||||
"SmartDimmer": iot.IotDimmer,
|
"SmartDimmer": iot.IotDimmer,
|
||||||
"SmartBulbPreset": BulbPreset,
|
"SmartBulbPreset": BulbPreset,
|
||||||
}
|
}
|
||||||
|
deprecated_exceptions = {
|
||||||
|
"SmartDeviceException": KasaException,
|
||||||
|
"UnsupportedDeviceException": UnsupportedDeviceError,
|
||||||
|
"AuthenticationException": AuthenticationError,
|
||||||
|
"TimeoutException": TimeoutError,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(name):
|
def __getattr__(name):
|
||||||
@ -101,6 +109,11 @@ def __getattr__(name):
|
|||||||
stacklevel=1,
|
stacklevel=1,
|
||||||
)
|
)
|
||||||
return new_class
|
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}")
|
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||||
|
|
||||||
|
|
||||||
@ -112,6 +125,11 @@ if TYPE_CHECKING:
|
|||||||
SmartStrip = iot.IotStrip
|
SmartStrip = iot.IotStrip
|
||||||
SmartDimmer = iot.IotDimmer
|
SmartDimmer = iot.IotDimmer
|
||||||
SmartBulbPreset = BulbPreset
|
SmartBulbPreset = BulbPreset
|
||||||
|
|
||||||
|
SmartDeviceException = KasaException
|
||||||
|
UnsupportedDeviceException = UnsupportedDeviceError
|
||||||
|
AuthenticationException = AuthenticationError
|
||||||
|
TimeoutException = TimeoutError
|
||||||
# Instanstiate all classes so the type checkers catch abstract issues
|
# Instanstiate all classes so the type checkers catch abstract issues
|
||||||
from . import smart
|
from . import smart
|
||||||
|
|
||||||
|
@ -22,12 +22,11 @@ from .deviceconfig import DeviceConfig
|
|||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
SMART_AUTHENTICATION_ERRORS,
|
SMART_AUTHENTICATION_ERRORS,
|
||||||
SMART_RETRYABLE_ERRORS,
|
SMART_RETRYABLE_ERRORS,
|
||||||
SMART_TIMEOUT_ERRORS,
|
AuthenticationError,
|
||||||
AuthenticationException,
|
DeviceError,
|
||||||
RetryableException,
|
KasaException,
|
||||||
SmartDeviceException,
|
|
||||||
SmartErrorCode,
|
SmartErrorCode,
|
||||||
TimeoutException,
|
_RetryableError,
|
||||||
)
|
)
|
||||||
from .httpclient import HttpClient
|
from .httpclient import HttpClient
|
||||||
from .json import dumps as json_dumps
|
from .json import dumps as json_dumps
|
||||||
@ -141,14 +140,12 @@ class AesTransport(BaseTransport):
|
|||||||
if error_code == SmartErrorCode.SUCCESS:
|
if error_code == SmartErrorCode.SUCCESS:
|
||||||
return
|
return
|
||||||
msg = f"{msg}: {self._host}: {error_code.name}({error_code.value})"
|
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:
|
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:
|
if error_code in SMART_AUTHENTICATION_ERRORS:
|
||||||
self._state = TransportState.HANDSHAKE_REQUIRED
|
self._state = TransportState.HANDSHAKE_REQUIRED
|
||||||
raise AuthenticationException(msg, error_code=error_code)
|
raise AuthenticationError(msg, error_code=error_code)
|
||||||
raise SmartDeviceException(msg, error_code=error_code)
|
raise DeviceError(msg, error_code=error_code)
|
||||||
|
|
||||||
async def send_secure_passthrough(self, request: str) -> Dict[str, Any]:
|
async def send_secure_passthrough(self, request: str) -> Dict[str, Any]:
|
||||||
"""Send encrypted message as passthrough."""
|
"""Send encrypted message as passthrough."""
|
||||||
@ -171,7 +168,7 @@ class AesTransport(BaseTransport):
|
|||||||
# _LOGGER.debug(f"secure_passthrough response is {status_code}: {resp_dict}")
|
# _LOGGER.debug(f"secure_passthrough response is {status_code}: {resp_dict}")
|
||||||
|
|
||||||
if status_code != 200:
|
if status_code != 200:
|
||||||
raise SmartDeviceException(
|
raise KasaException(
|
||||||
f"{self._host} responded with an unexpected "
|
f"{self._host} responded with an unexpected "
|
||||||
+ f"status code {status_code} to passthrough"
|
+ f"status code {status_code} to passthrough"
|
||||||
)
|
)
|
||||||
@ -197,7 +194,7 @@ class AesTransport(BaseTransport):
|
|||||||
self._host,
|
self._host,
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise SmartDeviceException(
|
raise KasaException(
|
||||||
f"Unable to decrypt response from {self._host}, "
|
f"Unable to decrypt response from {self._host}, "
|
||||||
+ f"error: {ex}, response: {raw_response}",
|
+ f"error: {ex}, response: {raw_response}",
|
||||||
ex,
|
ex,
|
||||||
@ -208,7 +205,7 @@ class AesTransport(BaseTransport):
|
|||||||
"""Login to the device."""
|
"""Login to the device."""
|
||||||
try:
|
try:
|
||||||
await self.try_login(self._login_params)
|
await self.try_login(self._login_params)
|
||||||
except AuthenticationException as aex:
|
except AuthenticationError as aex:
|
||||||
try:
|
try:
|
||||||
if aex.error_code is not SmartErrorCode.LOGIN_ERROR:
|
if aex.error_code is not SmartErrorCode.LOGIN_ERROR:
|
||||||
raise aex
|
raise aex
|
||||||
@ -223,10 +220,10 @@ class AesTransport(BaseTransport):
|
|||||||
"%s: logged in with default credentials",
|
"%s: logged in with default credentials",
|
||||||
self._host,
|
self._host,
|
||||||
)
|
)
|
||||||
except AuthenticationException:
|
except AuthenticationError:
|
||||||
raise
|
raise
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise SmartDeviceException(
|
raise KasaException(
|
||||||
"Unable to login and trying default "
|
"Unable to login and trying default "
|
||||||
+ f"login raised another exception: {ex}",
|
+ f"login raised another exception: {ex}",
|
||||||
ex,
|
ex,
|
||||||
@ -292,7 +289,7 @@ class AesTransport(BaseTransport):
|
|||||||
_LOGGER.debug("Device responded with: %s", resp_dict)
|
_LOGGER.debug("Device responded with: %s", resp_dict)
|
||||||
|
|
||||||
if status_code != 200:
|
if status_code != 200:
|
||||||
raise SmartDeviceException(
|
raise KasaException(
|
||||||
f"{self._host} responded with an unexpected "
|
f"{self._host} responded with an unexpected "
|
||||||
+ f"status code {status_code} to handshake"
|
+ f"status code {status_code} to handshake"
|
||||||
)
|
)
|
||||||
@ -347,7 +344,7 @@ class AesTransport(BaseTransport):
|
|||||||
await self.perform_login()
|
await self.perform_login()
|
||||||
# After a login failure handshake needs to
|
# After a login failure handshake needs to
|
||||||
# be redone or a 9999 error is received.
|
# be redone or a 9999 error is received.
|
||||||
except AuthenticationException as ex:
|
except AuthenticationError as ex:
|
||||||
self._state = TransportState.HANDSHAKE_REQUIRED
|
self._state = TransportState.HANDSHAKE_REQUIRED
|
||||||
raise ex
|
raise ex
|
||||||
|
|
||||||
|
12
kasa/cli.py
12
kasa/cli.py
@ -13,7 +13,7 @@ from typing import Any, Dict, cast
|
|||||||
import asyncclick as click
|
import asyncclick as click
|
||||||
|
|
||||||
from kasa import (
|
from kasa import (
|
||||||
AuthenticationException,
|
AuthenticationError,
|
||||||
Bulb,
|
Bulb,
|
||||||
ConnectionType,
|
ConnectionType,
|
||||||
Credentials,
|
Credentials,
|
||||||
@ -22,8 +22,8 @@ from kasa import (
|
|||||||
DeviceFamilyType,
|
DeviceFamilyType,
|
||||||
Discover,
|
Discover,
|
||||||
EncryptType,
|
EncryptType,
|
||||||
SmartDeviceException,
|
KasaException,
|
||||||
UnsupportedDeviceException,
|
UnsupportedDeviceError,
|
||||||
)
|
)
|
||||||
from kasa.discover import DiscoveryResult
|
from kasa.discover import DiscoveryResult
|
||||||
from kasa.iot import IotBulb, IotDevice, IotDimmer, IotLightStrip, IotPlug, IotStrip
|
from kasa.iot import IotBulb, IotDevice, IotDimmer, IotLightStrip, IotPlug, IotStrip
|
||||||
@ -458,7 +458,7 @@ async def discover(ctx):
|
|||||||
unsupported = []
|
unsupported = []
|
||||||
auth_failed = []
|
auth_failed = []
|
||||||
|
|
||||||
async def print_unsupported(unsupported_exception: UnsupportedDeviceException):
|
async def print_unsupported(unsupported_exception: UnsupportedDeviceError):
|
||||||
unsupported.append(unsupported_exception)
|
unsupported.append(unsupported_exception)
|
||||||
async with sem:
|
async with sem:
|
||||||
if unsupported_exception.discovery_result:
|
if unsupported_exception.discovery_result:
|
||||||
@ -476,7 +476,7 @@ async def discover(ctx):
|
|||||||
async with sem:
|
async with sem:
|
||||||
try:
|
try:
|
||||||
await dev.update()
|
await dev.update()
|
||||||
except AuthenticationException:
|
except AuthenticationError:
|
||||||
auth_failed.append(dev._discovery_info)
|
auth_failed.append(dev._discovery_info)
|
||||||
echo("== Authentication failed for device ==")
|
echo("== Authentication failed for device ==")
|
||||||
_echo_discovery_info(dev._discovery_info)
|
_echo_discovery_info(dev._discovery_info)
|
||||||
@ -677,7 +677,7 @@ async def cmd_command(dev: Device, module, command, parameters):
|
|||||||
elif isinstance(dev, SmartDevice):
|
elif isinstance(dev, SmartDevice):
|
||||||
res = await dev._query_helper(command, parameters)
|
res = await dev._query_helper(command, parameters)
|
||||||
else:
|
else:
|
||||||
raise SmartDeviceException("Unexpected device type %s.", dev)
|
raise KasaException("Unexpected device type %s.", dev)
|
||||||
echo(json.dumps(res))
|
echo(json.dumps(res))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ from .credentials import Credentials
|
|||||||
from .device_type import DeviceType
|
from .device_type import DeviceType
|
||||||
from .deviceconfig import DeviceConfig
|
from .deviceconfig import DeviceConfig
|
||||||
from .emeterstatus import EmeterStatus
|
from .emeterstatus import EmeterStatus
|
||||||
from .exceptions import SmartDeviceException
|
from .exceptions import KasaException
|
||||||
from .feature import Feature
|
from .feature import Feature
|
||||||
from .iotprotocol import IotProtocol
|
from .iotprotocol import IotProtocol
|
||||||
from .protocol import BaseProtocol
|
from .protocol import BaseProtocol
|
||||||
@ -242,12 +242,12 @@ class Device(ABC):
|
|||||||
if p.alias == name:
|
if p.alias == name:
|
||||||
return p
|
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":
|
def get_plug_by_index(self, index: int) -> "Device":
|
||||||
"""Return child device for the given index."""
|
"""Return child device for the given index."""
|
||||||
if index + 1 > len(self.children) or index < 0:
|
if index + 1 > len(self.children) or index < 0:
|
||||||
raise SmartDeviceException(
|
raise KasaException(
|
||||||
f"Invalid index {index}, device has {len(self.children)} plugs"
|
f"Invalid index {index}, device has {len(self.children)} plugs"
|
||||||
)
|
)
|
||||||
return self.children[index]
|
return self.children[index]
|
||||||
@ -306,7 +306,7 @@ class Device(ABC):
|
|||||||
"""Add a new feature to the device."""
|
"""Add a new feature to the device."""
|
||||||
desc_name = feature.name.lower().replace(" ", "_")
|
desc_name = feature.name.lower().replace(" ", "_")
|
||||||
if desc_name in self._features:
|
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
|
self._features[desc_name] = feature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -6,7 +6,7 @@ from typing import Any, Dict, Optional, Tuple, Type
|
|||||||
from .aestransport import AesTransport
|
from .aestransport import AesTransport
|
||||||
from .device import Device
|
from .device import Device
|
||||||
from .deviceconfig import DeviceConfig
|
from .deviceconfig import DeviceConfig
|
||||||
from .exceptions import SmartDeviceException, UnsupportedDeviceException
|
from .exceptions import KasaException, UnsupportedDeviceError
|
||||||
from .iot import IotBulb, IotDevice, IotDimmer, IotLightStrip, IotPlug, IotStrip
|
from .iot import IotBulb, IotDevice, IotDimmer, IotLightStrip, IotPlug, IotStrip
|
||||||
from .iotprotocol import IotProtocol
|
from .iotprotocol import IotProtocol
|
||||||
from .klaptransport import KlapTransport, KlapTransportV2
|
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.
|
:return: Object for querying/controlling found device.
|
||||||
"""
|
"""
|
||||||
if host and config or (not host and not config):
|
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:
|
if host:
|
||||||
config = DeviceConfig(host=host)
|
config = DeviceConfig(host=host)
|
||||||
|
|
||||||
if (protocol := get_protocol(config=config)) is None:
|
if (protocol := get_protocol(config=config)) is None:
|
||||||
raise UnsupportedDeviceException(
|
raise UnsupportedDeviceError(
|
||||||
f"Unsupported device for {config.host}: "
|
f"Unsupported device for {config.host}: "
|
||||||
+ f"{config.connection_type.device_family.value}"
|
+ f"{config.connection_type.device_family.value}"
|
||||||
)
|
)
|
||||||
@ -99,7 +99,7 @@ async def _connect(config: DeviceConfig, protocol: BaseProtocol) -> "Device":
|
|||||||
_perf_log(True, "update")
|
_perf_log(True, "update")
|
||||||
return device
|
return device
|
||||||
else:
|
else:
|
||||||
raise UnsupportedDeviceException(
|
raise UnsupportedDeviceError(
|
||||||
f"Unsupported device for {config.host}: "
|
f"Unsupported device for {config.host}: "
|
||||||
+ f"{config.connection_type.device_family.value}"
|
+ 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]:
|
def get_device_class_from_sys_info(info: Dict[str, Any]) -> Type[IotDevice]:
|
||||||
"""Find SmartDevice subclass for device described by passed data."""
|
"""Find SmartDevice subclass for device described by passed data."""
|
||||||
if "system" not in info or "get_sysinfo" not in info["system"]:
|
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"]
|
sysinfo: Dict[str, Any] = info["system"]["get_sysinfo"]
|
||||||
type_: Optional[str] = sysinfo.get("type", sysinfo.get("mic_type"))
|
type_: Optional[str] = sysinfo.get("type", sysinfo.get("mic_type"))
|
||||||
if type_ is None:
|
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"]:
|
if "dev_name" in sysinfo and "Dimmer" in sysinfo["dev_name"]:
|
||||||
return IotDimmer
|
return IotDimmer
|
||||||
@ -129,7 +129,7 @@ def get_device_class_from_sys_info(info: Dict[str, Any]) -> Type[IotDevice]:
|
|||||||
return IotLightStrip
|
return IotLightStrip
|
||||||
|
|
||||||
return IotBulb
|
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]]:
|
def get_device_class_from_family(device_type: str) -> Optional[Type[Device]]:
|
||||||
|
@ -5,7 +5,7 @@ from enum import Enum
|
|||||||
from typing import TYPE_CHECKING, Dict, Optional, Union
|
from typing import TYPE_CHECKING, Dict, Optional, Union
|
||||||
|
|
||||||
from .credentials import Credentials
|
from .credentials import Credentials
|
||||||
from .exceptions import SmartDeviceException
|
from .exceptions import KasaException
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from aiohttp import ClientSession
|
from aiohttp import ClientSession
|
||||||
@ -46,7 +46,7 @@ def _dataclass_from_dict(klass, in_val):
|
|||||||
fieldtypes[dict_key], in_val[dict_key]
|
fieldtypes[dict_key], in_val[dict_key]
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise SmartDeviceException(
|
raise KasaException(
|
||||||
f"Cannot create dataclass from dict, unknown key: {dict_key}"
|
f"Cannot create dataclass from dict, unknown key: {dict_key}"
|
||||||
)
|
)
|
||||||
return klass(**val)
|
return klass(**val)
|
||||||
@ -92,7 +92,7 @@ class ConnectionType:
|
|||||||
login_version,
|
login_version,
|
||||||
)
|
)
|
||||||
except (ValueError, TypeError) as ex:
|
except (ValueError, TypeError) as ex:
|
||||||
raise SmartDeviceException(
|
raise KasaException(
|
||||||
f"Invalid connection parameters for {device_family}."
|
f"Invalid connection parameters for {device_family}."
|
||||||
+ f"{encryption_type}.{login_version}"
|
+ f"{encryption_type}.{login_version}"
|
||||||
) from ex
|
) from ex
|
||||||
@ -113,9 +113,7 @@ class ConnectionType:
|
|||||||
login_version, # type: ignore[arg-type]
|
login_version, # type: ignore[arg-type]
|
||||||
)
|
)
|
||||||
|
|
||||||
raise SmartDeviceException(
|
raise KasaException(f"Invalid connection type data for {connection_type_dict}")
|
||||||
f"Invalid connection type data for {connection_type_dict}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Union[str, int]]:
|
def to_dict(self) -> Dict[str, Union[str, int]]:
|
||||||
"""Convert connection params to dict."""
|
"""Convert connection params to dict."""
|
||||||
@ -185,4 +183,4 @@ class DeviceConfig:
|
|||||||
"""Return device config from dict."""
|
"""Return device config from dict."""
|
||||||
if isinstance(config_dict, dict):
|
if isinstance(config_dict, dict):
|
||||||
return _dataclass_from_dict(DeviceConfig, config_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}")
|
||||||
|
@ -24,9 +24,9 @@ from kasa.device_factory import (
|
|||||||
)
|
)
|
||||||
from kasa.deviceconfig import ConnectionType, DeviceConfig, EncryptType
|
from kasa.deviceconfig import ConnectionType, DeviceConfig, EncryptType
|
||||||
from kasa.exceptions import (
|
from kasa.exceptions import (
|
||||||
SmartDeviceException,
|
KasaException,
|
||||||
TimeoutException,
|
TimeoutError,
|
||||||
UnsupportedDeviceException,
|
UnsupportedDeviceError,
|
||||||
)
|
)
|
||||||
from kasa.iot.iotdevice import IotDevice
|
from kasa.iot.iotdevice import IotDevice
|
||||||
from kasa.json import dumps as json_dumps
|
from kasa.json import dumps as json_dumps
|
||||||
@ -59,7 +59,7 @@ class _DiscoverProtocol(asyncio.DatagramProtocol):
|
|||||||
discovery_timeout: int = 5,
|
discovery_timeout: int = 5,
|
||||||
interface: Optional[str] = None,
|
interface: Optional[str] = None,
|
||||||
on_unsupported: Optional[
|
on_unsupported: Optional[
|
||||||
Callable[[UnsupportedDeviceException], Awaitable[None]]
|
Callable[[UnsupportedDeviceError], Awaitable[None]]
|
||||||
] = None,
|
] = None,
|
||||||
port: Optional[int] = None,
|
port: Optional[int] = None,
|
||||||
credentials: Optional[Credentials] = None,
|
credentials: Optional[Credentials] = None,
|
||||||
@ -162,14 +162,14 @@ class _DiscoverProtocol(asyncio.DatagramProtocol):
|
|||||||
device = Discover._get_device_instance(data, config)
|
device = Discover._get_device_instance(data, config)
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
except UnsupportedDeviceException as udex:
|
except UnsupportedDeviceError as udex:
|
||||||
_LOGGER.debug("Unsupported device found at %s << %s", ip, udex)
|
_LOGGER.debug("Unsupported device found at %s << %s", ip, udex)
|
||||||
self.unsupported_device_exceptions[ip] = udex
|
self.unsupported_device_exceptions[ip] = udex
|
||||||
if self.on_unsupported is not None:
|
if self.on_unsupported is not None:
|
||||||
self._run_callback_task(self.on_unsupported(udex))
|
self._run_callback_task(self.on_unsupported(udex))
|
||||||
self._handle_discovered_event()
|
self._handle_discovered_event()
|
||||||
return
|
return
|
||||||
except SmartDeviceException as ex:
|
except KasaException as ex:
|
||||||
_LOGGER.debug(f"[DISCOVERY] Unable to find device type for {ip}: {ex}")
|
_LOGGER.debug(f"[DISCOVERY] Unable to find device type for {ip}: {ex}")
|
||||||
self.invalid_device_exceptions[ip] = ex
|
self.invalid_device_exceptions[ip] = ex
|
||||||
self._handle_discovered_event()
|
self._handle_discovered_event()
|
||||||
@ -311,7 +311,7 @@ class Discover:
|
|||||||
try:
|
try:
|
||||||
_LOGGER.debug("Waiting %s seconds for responses...", discovery_timeout)
|
_LOGGER.debug("Waiting %s seconds for responses...", discovery_timeout)
|
||||||
await protocol.wait_for_discovery_to_complete()
|
await protocol.wait_for_discovery_to_complete()
|
||||||
except SmartDeviceException as ex:
|
except KasaException as ex:
|
||||||
for device in protocol.discovered_devices.values():
|
for device in protocol.discovered_devices.values():
|
||||||
await device.protocol.close()
|
await device.protocol.close()
|
||||||
raise ex
|
raise ex
|
||||||
@ -368,9 +368,7 @@ class Discover:
|
|||||||
# https://docs.python.org/3/library/socket.html#socket.getaddrinfo
|
# https://docs.python.org/3/library/socket.html#socket.getaddrinfo
|
||||||
ip = adrrinfo[0][4][0]
|
ip = adrrinfo[0][4][0]
|
||||||
except socket.gaierror as gex:
|
except socket.gaierror as gex:
|
||||||
raise SmartDeviceException(
|
raise KasaException(f"Could not resolve hostname {host}") from gex
|
||||||
f"Could not resolve hostname {host}"
|
|
||||||
) from gex
|
|
||||||
|
|
||||||
transport, protocol = await loop.create_datagram_endpoint(
|
transport, protocol = await loop.create_datagram_endpoint(
|
||||||
lambda: _DiscoverProtocol(
|
lambda: _DiscoverProtocol(
|
||||||
@ -401,7 +399,7 @@ class Discover:
|
|||||||
elif ip in protocol.invalid_device_exceptions:
|
elif ip in protocol.invalid_device_exceptions:
|
||||||
raise protocol.invalid_device_exceptions[ip]
|
raise protocol.invalid_device_exceptions[ip]
|
||||||
else:
|
else:
|
||||||
raise TimeoutException(f"Timed out getting discovery response for {host}")
|
raise TimeoutError(f"Timed out getting discovery response for {host}")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_device_class(info: dict) -> Type[Device]:
|
def _get_device_class(info: dict) -> Type[Device]:
|
||||||
@ -410,7 +408,7 @@ class Discover:
|
|||||||
discovery_result = DiscoveryResult(**info["result"])
|
discovery_result = DiscoveryResult(**info["result"])
|
||||||
dev_class = get_device_class_from_family(discovery_result.device_type)
|
dev_class = get_device_class_from_family(discovery_result.device_type)
|
||||||
if not dev_class:
|
if not dev_class:
|
||||||
raise UnsupportedDeviceException(
|
raise UnsupportedDeviceError(
|
||||||
"Unknown device type: %s" % discovery_result.device_type,
|
"Unknown device type: %s" % discovery_result.device_type,
|
||||||
discovery_result=info,
|
discovery_result=info,
|
||||||
)
|
)
|
||||||
@ -424,7 +422,7 @@ class Discover:
|
|||||||
try:
|
try:
|
||||||
info = json_loads(XorEncryption.decrypt(data))
|
info = json_loads(XorEncryption.decrypt(data))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise SmartDeviceException(
|
raise KasaException(
|
||||||
f"Unable to read response from device: {config.host}: {ex}"
|
f"Unable to read response from device: {config.host}: {ex}"
|
||||||
) from ex
|
) from ex
|
||||||
|
|
||||||
@ -451,7 +449,7 @@ class Discover:
|
|||||||
info = json_loads(data[16:])
|
info = json_loads(data[16:])
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
_LOGGER.debug("Got invalid response from device %s: %s", config.host, data)
|
_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}"
|
f"Unable to read response from device: {config.host}: {ex}"
|
||||||
) from ex
|
) from ex
|
||||||
try:
|
try:
|
||||||
@ -460,7 +458,7 @@ class Discover:
|
|||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Unable to parse discovery from device %s: %s", config.host, info
|
"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}"
|
f"Unable to parse discovery from device: {config.host}: {ex}"
|
||||||
) from ex
|
) from ex
|
||||||
|
|
||||||
@ -472,15 +470,15 @@ class Discover:
|
|||||||
discovery_result.mgt_encrypt_schm.encrypt_type,
|
discovery_result.mgt_encrypt_schm.encrypt_type,
|
||||||
discovery_result.mgt_encrypt_schm.lv,
|
discovery_result.mgt_encrypt_schm.lv,
|
||||||
)
|
)
|
||||||
except SmartDeviceException as ex:
|
except KasaException as ex:
|
||||||
raise UnsupportedDeviceException(
|
raise UnsupportedDeviceError(
|
||||||
f"Unsupported device {config.host} of type {type_} "
|
f"Unsupported device {config.host} of type {type_} "
|
||||||
+ f"with encrypt_type {discovery_result.mgt_encrypt_schm.encrypt_type}",
|
+ f"with encrypt_type {discovery_result.mgt_encrypt_schm.encrypt_type}",
|
||||||
discovery_result=discovery_result.get_dict(),
|
discovery_result=discovery_result.get_dict(),
|
||||||
) from ex
|
) from ex
|
||||||
if (device_class := get_device_class_from_family(type_)) is None:
|
if (device_class := get_device_class_from_family(type_)) is None:
|
||||||
_LOGGER.warning("Got unsupported device type: %s", type_)
|
_LOGGER.warning("Got unsupported device type: %s", type_)
|
||||||
raise UnsupportedDeviceException(
|
raise UnsupportedDeviceError(
|
||||||
f"Unsupported device {config.host} of type {type_}: {info}",
|
f"Unsupported device {config.host} of type {type_}: {info}",
|
||||||
discovery_result=discovery_result.get_dict(),
|
discovery_result=discovery_result.get_dict(),
|
||||||
)
|
)
|
||||||
@ -488,7 +486,7 @@ class Discover:
|
|||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Got unsupported connection type: %s", config.connection_type.to_dict()
|
"Got unsupported connection type: %s", config.connection_type.to_dict()
|
||||||
)
|
)
|
||||||
raise UnsupportedDeviceException(
|
raise UnsupportedDeviceError(
|
||||||
f"Unsupported encryption scheme {config.host} of "
|
f"Unsupported encryption scheme {config.host} of "
|
||||||
+ f"type {config.connection_type.to_dict()}: {info}",
|
+ f"type {config.connection_type.to_dict()}: {info}",
|
||||||
discovery_result=discovery_result.get_dict(),
|
discovery_result=discovery_result.get_dict(),
|
||||||
|
@ -1,47 +1,59 @@
|
|||||||
"""python-kasa exceptions."""
|
"""python-kasa exceptions."""
|
||||||
from asyncio import TimeoutError
|
from asyncio import TimeoutError as _asyncioTimeoutError
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from typing import Any, Optional
|
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."""
|
"""Base exception for device errors."""
|
||||||
|
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
self.error_code: Optional["SmartErrorCode"] = kwargs.get("error_code", None)
|
self.error_code: Optional["SmartErrorCode"] = kwargs.get("error_code", None)
|
||||||
super().__init__(*args)
|
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):
|
def __str__(self):
|
||||||
"""Exception for trying to connect to unsupported devices."""
|
err_code = f" (error_code={self.error_code.name})" if self.error_code else ""
|
||||||
|
return super().__str__() + err_code
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
||||||
self.discovery_result = kwargs.get("discovery_result")
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class AuthenticationException(SmartDeviceException):
|
class AuthenticationError(DeviceError):
|
||||||
"""Base exception for device authentication errors."""
|
"""Base exception for device authentication errors."""
|
||||||
|
|
||||||
|
|
||||||
class RetryableException(SmartDeviceException):
|
class _RetryableError(DeviceError):
|
||||||
"""Retryable exception for device errors."""
|
"""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):
|
class SmartErrorCode(IntEnum):
|
||||||
"""Enum for SMART Error Codes."""
|
"""Enum for SMART Error Codes."""
|
||||||
|
|
||||||
@ -109,6 +121,7 @@ SMART_RETRYABLE_ERRORS = [
|
|||||||
SmartErrorCode.TRANSPORT_NOT_AVAILABLE_ERROR,
|
SmartErrorCode.TRANSPORT_NOT_AVAILABLE_ERROR,
|
||||||
SmartErrorCode.HTTP_TRANSPORT_FAILED_ERROR,
|
SmartErrorCode.HTTP_TRANSPORT_FAILED_ERROR,
|
||||||
SmartErrorCode.UNSPECIFIC_ERROR,
|
SmartErrorCode.UNSPECIFIC_ERROR,
|
||||||
|
SmartErrorCode.SESSION_TIMEOUT_ERROR,
|
||||||
]
|
]
|
||||||
|
|
||||||
SMART_AUTHENTICATION_ERRORS = [
|
SMART_AUTHENTICATION_ERRORS = [
|
||||||
@ -118,7 +131,3 @@ SMART_AUTHENTICATION_ERRORS = [
|
|||||||
SmartErrorCode.HAND_SHAKE_FAILED_ERROR,
|
SmartErrorCode.HAND_SHAKE_FAILED_ERROR,
|
||||||
SmartErrorCode.TRANSPORT_UNKNOWN_CREDENTIALS_ERROR,
|
SmartErrorCode.TRANSPORT_UNKNOWN_CREDENTIALS_ERROR,
|
||||||
]
|
]
|
||||||
|
|
||||||
SMART_TIMEOUT_ERRORS = [
|
|
||||||
SmartErrorCode.SESSION_TIMEOUT_ERROR,
|
|
||||||
]
|
|
||||||
|
@ -8,9 +8,9 @@ from yarl import URL
|
|||||||
|
|
||||||
from .deviceconfig import DeviceConfig
|
from .deviceconfig import DeviceConfig
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
ConnectionException,
|
KasaException,
|
||||||
SmartDeviceException,
|
TimeoutError,
|
||||||
TimeoutException,
|
_ConnectionError,
|
||||||
)
|
)
|
||||||
from .json import loads as json_loads
|
from .json import loads as json_loads
|
||||||
|
|
||||||
@ -86,17 +86,17 @@ class HttpClient:
|
|||||||
response_data = json_loads(response_data.decode())
|
response_data = json_loads(response_data.decode())
|
||||||
|
|
||||||
except (aiohttp.ServerDisconnectedError, aiohttp.ClientOSError) as ex:
|
except (aiohttp.ServerDisconnectedError, aiohttp.ClientOSError) as ex:
|
||||||
raise ConnectionException(
|
raise _ConnectionError(
|
||||||
f"Device connection error: {self._config.host}: {ex}", ex
|
f"Device connection error: {self._config.host}: {ex}", ex
|
||||||
) from ex
|
) from ex
|
||||||
except (aiohttp.ServerTimeoutError, asyncio.TimeoutError) as ex:
|
except (aiohttp.ServerTimeoutError, asyncio.TimeoutError) as ex:
|
||||||
raise TimeoutException(
|
raise TimeoutError(
|
||||||
"Unable to query the device, "
|
"Unable to query the device, "
|
||||||
+ f"timed out: {self._config.host}: {ex}",
|
+ f"timed out: {self._config.host}: {ex}",
|
||||||
ex,
|
ex,
|
||||||
) from ex
|
) from ex
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise SmartDeviceException(
|
raise KasaException(
|
||||||
f"Unable to query the device: {self._config.host}: {ex}", ex
|
f"Unable to query the device: {self._config.host}: {ex}", ex
|
||||||
) from ex
|
) from ex
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ from ..bulb import HSV, Bulb, BulbPreset, ColorTempRange
|
|||||||
from ..device_type import DeviceType
|
from ..device_type import DeviceType
|
||||||
from ..deviceconfig import DeviceConfig
|
from ..deviceconfig import DeviceConfig
|
||||||
from ..protocol import BaseProtocol
|
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
|
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.
|
so you must await :func:`update()` to fetch updates values from the device.
|
||||||
|
|
||||||
Errors reported by the device are raised as
|
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.
|
and should be handled by the user of the library.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@ -233,7 +233,7 @@ class IotBulb(IotDevice, Bulb):
|
|||||||
:return: White temperature range in Kelvin (minimum, maximum)
|
:return: White temperature range in Kelvin (minimum, maximum)
|
||||||
"""
|
"""
|
||||||
if not self.is_variable_color_temp:
|
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():
|
for model, temp_range in TPLINK_KELVIN.items():
|
||||||
sys_info = self.sys_info
|
sys_info = self.sys_info
|
||||||
@ -249,7 +249,7 @@ class IotBulb(IotDevice, Bulb):
|
|||||||
"""Query the light state."""
|
"""Query the light state."""
|
||||||
light_state = self.sys_info["light_state"]
|
light_state = self.sys_info["light_state"]
|
||||||
if light_state is None:
|
if light_state is None:
|
||||||
raise SmartDeviceException(
|
raise KasaException(
|
||||||
"The device has no light_state or you have not called update()"
|
"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, %, %)
|
:return: hue, saturation and value (degrees, %, %)
|
||||||
"""
|
"""
|
||||||
if not self.is_color:
|
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)
|
light_state = cast(dict, self.light_state)
|
||||||
|
|
||||||
@ -360,7 +360,7 @@ class IotBulb(IotDevice, Bulb):
|
|||||||
:param int transition: transition in milliseconds.
|
:param int transition: transition in milliseconds.
|
||||||
"""
|
"""
|
||||||
if not self.is_color:
|
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):
|
if not isinstance(hue, int) or not (0 <= hue <= 360):
|
||||||
raise ValueError(f"Invalid hue value: {hue} (valid range: 0-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:
|
def color_temp(self) -> int:
|
||||||
"""Return color temperature of the device in kelvin."""
|
"""Return color temperature of the device in kelvin."""
|
||||||
if not self.is_variable_color_temp:
|
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
|
light_state = self.light_state
|
||||||
return int(light_state["color_temp"])
|
return int(light_state["color_temp"])
|
||||||
@ -402,7 +402,7 @@ class IotBulb(IotDevice, Bulb):
|
|||||||
:param int transition: transition in milliseconds.
|
:param int transition: transition in milliseconds.
|
||||||
"""
|
"""
|
||||||
if not self.is_variable_color_temp:
|
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
|
valid_temperature_range = self.valid_temperature_range
|
||||||
if temp < valid_temperature_range[0] or temp > valid_temperature_range[1]:
|
if temp < valid_temperature_range[0] or temp > valid_temperature_range[1]:
|
||||||
@ -423,7 +423,7 @@ class IotBulb(IotDevice, Bulb):
|
|||||||
def brightness(self) -> int:
|
def brightness(self) -> int:
|
||||||
"""Return the current brightness in percentage."""
|
"""Return the current brightness in percentage."""
|
||||||
if not self.is_dimmable: # pragma: no cover
|
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
|
light_state = self.light_state
|
||||||
return int(light_state["brightness"])
|
return int(light_state["brightness"])
|
||||||
@ -438,7 +438,7 @@ class IotBulb(IotDevice, Bulb):
|
|||||||
:param int transition: transition in milliseconds.
|
:param int transition: transition in milliseconds.
|
||||||
"""
|
"""
|
||||||
if not self.is_dimmable: # pragma: no cover
|
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)
|
self._raise_for_invalid_brightness(brightness)
|
||||||
|
|
||||||
@ -511,10 +511,10 @@ class IotBulb(IotDevice, Bulb):
|
|||||||
obtained using :func:`presets`.
|
obtained using :func:`presets`.
|
||||||
"""
|
"""
|
||||||
if len(self.presets) == 0:
|
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):
|
if preset.index >= len(self.presets):
|
||||||
raise SmartDeviceException("Invalid preset index")
|
raise KasaException("Invalid preset index")
|
||||||
|
|
||||||
return await self._query_helper(
|
return await self._query_helper(
|
||||||
self.LIGHT_SERVICE, "set_preferred_state", preset.dict(exclude_none=True)
|
self.LIGHT_SERVICE, "set_preferred_state", preset.dict(exclude_none=True)
|
||||||
|
@ -21,7 +21,7 @@ from typing import Any, Dict, List, Optional, Sequence, Set
|
|||||||
from ..device import Device, WifiNetwork
|
from ..device import Device, WifiNetwork
|
||||||
from ..deviceconfig import DeviceConfig
|
from ..deviceconfig import DeviceConfig
|
||||||
from ..emeterstatus import EmeterStatus
|
from ..emeterstatus import EmeterStatus
|
||||||
from ..exceptions import SmartDeviceException
|
from ..exceptions import KasaException
|
||||||
from ..feature import Feature
|
from ..feature import Feature
|
||||||
from ..protocol import BaseProtocol
|
from ..protocol import BaseProtocol
|
||||||
from .iotmodule import IotModule
|
from .iotmodule import IotModule
|
||||||
@ -48,9 +48,7 @@ def requires_update(f):
|
|||||||
async def wrapped(*args, **kwargs):
|
async def wrapped(*args, **kwargs):
|
||||||
self = args[0]
|
self = args[0]
|
||||||
if self._last_update is None and f.__name__ not in self._sys_info:
|
if self._last_update is None and f.__name__ not in self._sys_info:
|
||||||
raise SmartDeviceException(
|
raise KasaException("You need to await update() to access the data")
|
||||||
"You need to await update() to access the data"
|
|
||||||
)
|
|
||||||
return await f(*args, **kwargs)
|
return await f(*args, **kwargs)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@ -59,9 +57,7 @@ def requires_update(f):
|
|||||||
def wrapped(*args, **kwargs):
|
def wrapped(*args, **kwargs):
|
||||||
self = args[0]
|
self = args[0]
|
||||||
if self._last_update is None and f.__name__ not in self._sys_info:
|
if self._last_update is None and f.__name__ not in self._sys_info:
|
||||||
raise SmartDeviceException(
|
raise KasaException("You need to await update() to access the data")
|
||||||
"You need to await update() to access the data"
|
|
||||||
)
|
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
f.requires_update = True
|
f.requires_update = True
|
||||||
@ -92,7 +88,8 @@ class IotDevice(Device):
|
|||||||
All changes to the device are done using awaitable methods,
|
All changes to the device are done using awaitable methods,
|
||||||
which will not change the cached values, but you must await update() separately.
|
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.
|
and should be handled by the user of the library.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@ -221,9 +218,9 @@ class IotDevice(Device):
|
|||||||
def _verify_emeter(self) -> None:
|
def _verify_emeter(self) -> None:
|
||||||
"""Raise an exception if there is no emeter."""
|
"""Raise an exception if there is no emeter."""
|
||||||
if not self.has_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:
|
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(
|
async def _query_helper(
|
||||||
self, target: str, cmd: str, arg: Optional[Dict] = None, child_ids=None
|
self, target: str, cmd: str, arg: Optional[Dict] = None, child_ids=None
|
||||||
@ -241,20 +238,20 @@ class IotDevice(Device):
|
|||||||
try:
|
try:
|
||||||
response = await self._raw_query(request=request)
|
response = await self._raw_query(request=request)
|
||||||
except Exception as ex:
|
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:
|
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]
|
result = response[target]
|
||||||
if "err_code" in result and result["err_code"] != 0:
|
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:
|
if cmd not in result:
|
||||||
raise SmartDeviceException(f"No command in response: {response}")
|
raise KasaException(f"No command in response: {response}")
|
||||||
result = result[cmd]
|
result = result[cmd]
|
||||||
if "err_code" in result and result["err_code"] != 0:
|
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:
|
if "err_code" in result:
|
||||||
del result["err_code"]
|
del result["err_code"]
|
||||||
@ -513,7 +510,7 @@ class IotDevice(Device):
|
|||||||
sys_info = self._sys_info
|
sys_info = self._sys_info
|
||||||
mac = sys_info.get("mac", sys_info.get("mic_mac"))
|
mac = sys_info.get("mac", sys_info.get("mic_mac"))
|
||||||
if not mac:
|
if not mac:
|
||||||
raise SmartDeviceException(
|
raise KasaException(
|
||||||
"Unknown mac, please submit a bug report with sys_info output."
|
"Unknown mac, please submit a bug report with sys_info output."
|
||||||
)
|
)
|
||||||
mac = mac.replace("-", ":")
|
mac = mac.replace("-", ":")
|
||||||
@ -656,14 +653,14 @@ class IotDevice(Device):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
info = await _scan("netif")
|
info = await _scan("netif")
|
||||||
except SmartDeviceException as ex:
|
except KasaException as ex:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Unable to scan using 'netif', retrying with 'softaponboarding': %s", ex
|
"Unable to scan using 'netif', retrying with 'softaponboarding': %s", ex
|
||||||
)
|
)
|
||||||
info = await _scan("smartlife.iot.common.softaponboarding")
|
info = await _scan("smartlife.iot.common.softaponboarding")
|
||||||
|
|
||||||
if "ap_list" not in info:
|
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"]]
|
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)}
|
payload = {"ssid": ssid, "password": password, "key_type": int(keytype)}
|
||||||
try:
|
try:
|
||||||
return await _join("netif", payload)
|
return await _join("netif", payload)
|
||||||
except SmartDeviceException as ex:
|
except KasaException as ex:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Unable to join using 'netif', retrying with 'softaponboarding': %s", ex
|
"Unable to join using 'netif', retrying with 'softaponboarding': %s", ex
|
||||||
)
|
)
|
||||||
|
@ -5,7 +5,7 @@ from typing import Any, Dict, Optional
|
|||||||
from ..device_type import DeviceType
|
from ..device_type import DeviceType
|
||||||
from ..deviceconfig import DeviceConfig
|
from ..deviceconfig import DeviceConfig
|
||||||
from ..protocol import BaseProtocol
|
from ..protocol import BaseProtocol
|
||||||
from .iotdevice import SmartDeviceException, requires_update
|
from .iotdevice import KasaException, requires_update
|
||||||
from .iotplug import IotPlug
|
from .iotplug import IotPlug
|
||||||
from .modules import AmbientLight, Motion
|
from .modules import AmbientLight, Motion
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ class IotDimmer(IotPlug):
|
|||||||
which will not change the cached values,
|
which will not change the cached values,
|
||||||
but you must await :func:`update()` separately.
|
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.
|
and should be handled by the user of the library.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@ -88,7 +88,7 @@ class IotDimmer(IotPlug):
|
|||||||
Will return a range between 0 - 100.
|
Will return a range between 0 - 100.
|
||||||
"""
|
"""
|
||||||
if not self.is_dimmable:
|
if not self.is_dimmable:
|
||||||
raise SmartDeviceException("Device is not dimmable.")
|
raise KasaException("Device is not dimmable.")
|
||||||
|
|
||||||
sys_info = self.sys_info
|
sys_info = self.sys_info
|
||||||
return int(sys_info["brightness"])
|
return int(sys_info["brightness"])
|
||||||
@ -103,7 +103,7 @@ class IotDimmer(IotPlug):
|
|||||||
Using a transition will cause the dimmer to turn on.
|
Using a transition will cause the dimmer to turn on.
|
||||||
"""
|
"""
|
||||||
if not self.is_dimmable:
|
if not self.is_dimmable:
|
||||||
raise SmartDeviceException("Device is not dimmable.")
|
raise KasaException("Device is not dimmable.")
|
||||||
|
|
||||||
if not isinstance(brightness, int):
|
if not isinstance(brightness, int):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
@ -6,7 +6,7 @@ from ..deviceconfig import DeviceConfig
|
|||||||
from ..effects import EFFECT_MAPPING_V1, EFFECT_NAMES_V1
|
from ..effects import EFFECT_MAPPING_V1, EFFECT_NAMES_V1
|
||||||
from ..protocol import BaseProtocol
|
from ..protocol import BaseProtocol
|
||||||
from .iotbulb import IotBulb
|
from .iotbulb import IotBulb
|
||||||
from .iotdevice import SmartDeviceException, requires_update
|
from .iotdevice import KasaException, requires_update
|
||||||
|
|
||||||
|
|
||||||
class IotLightStrip(IotBulb):
|
class IotLightStrip(IotBulb):
|
||||||
@ -117,7 +117,7 @@ class IotLightStrip(IotBulb):
|
|||||||
:param int transition: The wanted transition time
|
:param int transition: The wanted transition time
|
||||||
"""
|
"""
|
||||||
if effect not in EFFECT_MAPPING_V1:
|
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]
|
effect_dict = EFFECT_MAPPING_V1[effect]
|
||||||
if brightness is not None:
|
if brightness is not None:
|
||||||
effect_dict["brightness"] = brightness
|
effect_dict["brightness"] = brightness
|
||||||
@ -136,7 +136,7 @@ class IotLightStrip(IotBulb):
|
|||||||
:param str effect_dict: The custom effect dict to set
|
:param str effect_dict: The custom effect dict to set
|
||||||
"""
|
"""
|
||||||
if not self.has_effects:
|
if not self.has_effects:
|
||||||
raise SmartDeviceException("Bulb does not support effects.")
|
raise KasaException("Bulb does not support effects.")
|
||||||
await self._query_helper(
|
await self._query_helper(
|
||||||
"smartlife.iot.lighting_effect",
|
"smartlife.iot.lighting_effect",
|
||||||
"set_lighting_effect",
|
"set_lighting_effect",
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import collections
|
import collections
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from ..exceptions import SmartDeviceException
|
from ..exceptions import KasaException
|
||||||
from ..module import Module
|
from ..module import Module
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -43,7 +43,7 @@ class IotModule(Module):
|
|||||||
def data(self):
|
def data(self):
|
||||||
"""Return the module specific raw data from the last update."""
|
"""Return the module specific raw data from the last update."""
|
||||||
if self._module not in self._device._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"You need to call update() prior accessing module data"
|
||||||
f" for '{self._module}'"
|
f" for '{self._module}'"
|
||||||
)
|
)
|
||||||
|
@ -22,7 +22,7 @@ class IotPlug(IotDevice):
|
|||||||
which will not change the cached values,
|
which will not change the cached values,
|
||||||
but you must await :func:`update()` separately.
|
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.
|
and should be handled by the user of the library.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
@ -6,7 +6,7 @@ from typing import Any, DefaultDict, Dict, Optional
|
|||||||
|
|
||||||
from ..device_type import DeviceType
|
from ..device_type import DeviceType
|
||||||
from ..deviceconfig import DeviceConfig
|
from ..deviceconfig import DeviceConfig
|
||||||
from ..exceptions import SmartDeviceException
|
from ..exceptions import KasaException
|
||||||
from ..protocol import BaseProtocol
|
from ..protocol import BaseProtocol
|
||||||
from .iotdevice import (
|
from .iotdevice import (
|
||||||
EmeterStatus,
|
EmeterStatus,
|
||||||
@ -43,7 +43,7 @@ class IotStrip(IotDevice):
|
|||||||
which will not change the cached values,
|
which will not change the cached values,
|
||||||
but you must await :func:`update()` separately.
|
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.
|
and should be handled by the user of the library.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@ -375,4 +375,4 @@ class IotStripPlug(IotPlug):
|
|||||||
if plug["id"] == self.child_id:
|
if plug["id"] == self.child_id:
|
||||||
return plug
|
return plug
|
||||||
|
|
||||||
raise SmartDeviceException(f"Unable to find children {self.child_id}")
|
raise KasaException(f"Unable to find children {self.child_id}")
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from ...exceptions import SmartDeviceException
|
from ...exceptions import KasaException
|
||||||
from ..iotmodule import IotModule
|
from ..iotmodule import IotModule
|
||||||
|
|
||||||
|
|
||||||
@ -54,9 +54,7 @@ class Motion(IotModule):
|
|||||||
elif range is not None:
|
elif range is not None:
|
||||||
payload = {"index": range.value}
|
payload = {"index": range.value}
|
||||||
else:
|
else:
|
||||||
raise SmartDeviceException(
|
raise KasaException("Either range or custom_range need to be defined")
|
||||||
"Either range or custom_range need to be defined"
|
|
||||||
)
|
|
||||||
|
|
||||||
return await self.call("set_trigger_sens", payload)
|
return await self.call("set_trigger_sens", payload)
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Provides the current time and timezone information."""
|
"""Provides the current time and timezone information."""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from ...exceptions import SmartDeviceException
|
from ...exceptions import KasaException
|
||||||
from ..iotmodule import IotModule, merge
|
from ..iotmodule import IotModule, merge
|
||||||
|
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ class Time(IotModule):
|
|||||||
res["min"],
|
res["min"],
|
||||||
res["sec"],
|
res["sec"],
|
||||||
)
|
)
|
||||||
except SmartDeviceException:
|
except KasaException:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_timezone(self):
|
async def get_timezone(self):
|
||||||
|
@ -5,11 +5,11 @@ from typing import Dict, Optional, Union
|
|||||||
|
|
||||||
from .deviceconfig import DeviceConfig
|
from .deviceconfig import DeviceConfig
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
AuthenticationException,
|
AuthenticationError,
|
||||||
ConnectionException,
|
KasaException,
|
||||||
RetryableException,
|
TimeoutError,
|
||||||
SmartDeviceException,
|
_ConnectionError,
|
||||||
TimeoutException,
|
_RetryableError,
|
||||||
)
|
)
|
||||||
from .json import dumps as json_dumps
|
from .json import dumps as json_dumps
|
||||||
from .protocol import BaseProtocol, BaseTransport
|
from .protocol import BaseProtocol, BaseTransport
|
||||||
@ -46,31 +46,31 @@ class IotProtocol(BaseProtocol):
|
|||||||
for retry in range(retry_count + 1):
|
for retry in range(retry_count + 1):
|
||||||
try:
|
try:
|
||||||
return await self._execute_query(request, retry)
|
return await self._execute_query(request, retry)
|
||||||
except ConnectionException as sdex:
|
except _ConnectionError as sdex:
|
||||||
if retry >= retry_count:
|
if retry >= retry_count:
|
||||||
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
|
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
|
||||||
raise sdex
|
raise sdex
|
||||||
continue
|
continue
|
||||||
except AuthenticationException as auex:
|
except AuthenticationError as auex:
|
||||||
await self._transport.reset()
|
await self._transport.reset()
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Unable to authenticate with %s, not retrying", self._host
|
"Unable to authenticate with %s, not retrying", self._host
|
||||||
)
|
)
|
||||||
raise auex
|
raise auex
|
||||||
except RetryableException as ex:
|
except _RetryableError as ex:
|
||||||
await self._transport.reset()
|
await self._transport.reset()
|
||||||
if retry >= retry_count:
|
if retry >= retry_count:
|
||||||
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
|
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
|
||||||
raise ex
|
raise ex
|
||||||
continue
|
continue
|
||||||
except TimeoutException as ex:
|
except TimeoutError as ex:
|
||||||
await self._transport.reset()
|
await self._transport.reset()
|
||||||
if retry >= retry_count:
|
if retry >= retry_count:
|
||||||
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
|
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
|
||||||
raise ex
|
raise ex
|
||||||
await asyncio.sleep(self.BACKOFF_SECONDS_AFTER_TIMEOUT)
|
await asyncio.sleep(self.BACKOFF_SECONDS_AFTER_TIMEOUT)
|
||||||
continue
|
continue
|
||||||
except SmartDeviceException as ex:
|
except KasaException as ex:
|
||||||
await self._transport.reset()
|
await self._transport.reset()
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Unable to query the device: %s, not retrying: %s",
|
"Unable to query the device: %s, not retrying: %s",
|
||||||
@ -80,7 +80,7 @@ class IotProtocol(BaseProtocol):
|
|||||||
raise ex
|
raise ex
|
||||||
|
|
||||||
# make mypy happy, this should never be reached..
|
# 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:
|
async def _execute_query(self, request: str, retry_count: int) -> Dict:
|
||||||
return await self._transport.send(request)
|
return await self._transport.send(request)
|
||||||
@ -101,7 +101,7 @@ class _deprecated_TPLinkSmartHomeProtocol(IotProtocol):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Create a protocol object."""
|
"""Create a protocol object."""
|
||||||
if not host and not transport:
|
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:
|
if not transport:
|
||||||
config = DeviceConfig(
|
config = DeviceConfig(
|
||||||
host=host, # type: ignore[arg-type]
|
host=host, # type: ignore[arg-type]
|
||||||
|
@ -57,7 +57,7 @@ from yarl import URL
|
|||||||
|
|
||||||
from .credentials import Credentials
|
from .credentials import Credentials
|
||||||
from .deviceconfig import DeviceConfig
|
from .deviceconfig import DeviceConfig
|
||||||
from .exceptions import AuthenticationException, SmartDeviceException
|
from .exceptions import AuthenticationError, KasaException
|
||||||
from .httpclient import HttpClient
|
from .httpclient import HttpClient
|
||||||
from .json import loads as json_loads
|
from .json import loads as json_loads
|
||||||
from .protocol import DEFAULT_CREDENTIALS, BaseTransport, get_default_credentials, md5
|
from .protocol import DEFAULT_CREDENTIALS, BaseTransport, get_default_credentials, md5
|
||||||
@ -159,7 +159,7 @@ class KlapTransport(BaseTransport):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if response_status != 200:
|
if response_status != 200:
|
||||||
raise SmartDeviceException(
|
raise KasaException(
|
||||||
f"Device {self._host} responded with {response_status} to handshake1"
|
f"Device {self._host} responded with {response_status} to handshake1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ class KlapTransport(BaseTransport):
|
|||||||
server_hash = response_data[16:]
|
server_hash = response_data[16:]
|
||||||
|
|
||||||
if len(server_hash) != 32:
|
if len(server_hash) != 32:
|
||||||
raise SmartDeviceException(
|
raise KasaException(
|
||||||
f"Device {self._host} responded with unexpected klap response "
|
f"Device {self._host} responded with unexpected klap response "
|
||||||
+ f"{response_data!r} to handshake1"
|
+ 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}"
|
msg = f"Server response doesn't match our challenge on ip {self._host}"
|
||||||
_LOGGER.debug(msg)
|
_LOGGER.debug(msg)
|
||||||
raise AuthenticationException(msg)
|
raise AuthenticationError(msg)
|
||||||
|
|
||||||
async def perform_handshake2(
|
async def perform_handshake2(
|
||||||
self, local_seed, remote_seed, auth_hash
|
self, local_seed, remote_seed, auth_hash
|
||||||
@ -267,8 +267,8 @@ class KlapTransport(BaseTransport):
|
|||||||
|
|
||||||
if response_status != 200:
|
if response_status != 200:
|
||||||
# This shouldn't be caused by incorrect
|
# This shouldn't be caused by incorrect
|
||||||
# credentials so don't raise AuthenticationException
|
# credentials so don't raise AuthenticationError
|
||||||
raise SmartDeviceException(
|
raise KasaException(
|
||||||
f"Device {self._host} responded with {response_status} to handshake2"
|
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 we failed with a security error, force a new handshake next time.
|
||||||
if response_status == 403:
|
if response_status == 403:
|
||||||
self._handshake_done = False
|
self._handshake_done = False
|
||||||
raise AuthenticationException(
|
raise AuthenticationError(
|
||||||
f"Got a security error from {self._host} after handshake "
|
f"Got a security error from {self._host} after handshake "
|
||||||
+ "completed"
|
+ "completed"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise SmartDeviceException(
|
raise KasaException(
|
||||||
f"Device {self._host} responded with {response_status} to"
|
f"Device {self._host} responded with {response_status} to"
|
||||||
+ f"request with seq {seq}"
|
+ f"request with seq {seq}"
|
||||||
)
|
)
|
||||||
|
@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
|
|||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from .device import Device
|
from .device import Device
|
||||||
from .exceptions import SmartDeviceException
|
from .exceptions import KasaException
|
||||||
from .feature import Feature
|
from .feature import Feature
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -39,7 +39,7 @@ class Module(ABC):
|
|||||||
"""Add module feature."""
|
"""Add module feature."""
|
||||||
feat_name = f"{self._module}_{feature.name}"
|
feat_name = f"{self._module}_{feature.name}"
|
||||||
if feat_name in self._module_features:
|
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
|
self._module_features[feat_name] = feature
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
|
@ -4,7 +4,7 @@ from typing import Any, Dict, List, Optional
|
|||||||
from ..bulb import Bulb
|
from ..bulb import Bulb
|
||||||
from ..device_type import DeviceType
|
from ..device_type import DeviceType
|
||||||
from ..deviceconfig import DeviceConfig
|
from ..deviceconfig import DeviceConfig
|
||||||
from ..exceptions import SmartDeviceException
|
from ..exceptions import KasaException
|
||||||
from ..iot.iotbulb import HSV, BulbPreset, ColorTempRange
|
from ..iot.iotbulb import HSV, BulbPreset, ColorTempRange
|
||||||
from ..smartprotocol import SmartProtocol
|
from ..smartprotocol import SmartProtocol
|
||||||
from .smartdevice import SmartDevice
|
from .smartdevice import SmartDevice
|
||||||
@ -57,7 +57,7 @@ class SmartBulb(SmartDevice, Bulb):
|
|||||||
:return: White temperature range in Kelvin (minimum, maximum)
|
:return: White temperature range in Kelvin (minimum, maximum)
|
||||||
"""
|
"""
|
||||||
if not self.is_variable_color_temp:
|
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])
|
ct_range = self._info.get("color_temp_range", [0, 0])
|
||||||
return ColorTempRange(min=ct_range[0], max=ct_range[1])
|
return ColorTempRange(min=ct_range[0], max=ct_range[1])
|
||||||
@ -107,7 +107,7 @@ class SmartBulb(SmartDevice, Bulb):
|
|||||||
:return: hue, saturation and value (degrees, %, %)
|
:return: hue, saturation and value (degrees, %, %)
|
||||||
"""
|
"""
|
||||||
if not self.is_color:
|
if not self.is_color:
|
||||||
raise SmartDeviceException("Bulb does not support color.")
|
raise KasaException("Bulb does not support color.")
|
||||||
|
|
||||||
h, s, v = (
|
h, s, v = (
|
||||||
self._info.get("hue", 0),
|
self._info.get("hue", 0),
|
||||||
@ -121,7 +121,7 @@ class SmartBulb(SmartDevice, Bulb):
|
|||||||
def color_temp(self) -> int:
|
def color_temp(self) -> int:
|
||||||
"""Whether the bulb supports color temperature changes."""
|
"""Whether the bulb supports color temperature changes."""
|
||||||
if not self.is_variable_color_temp:
|
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)
|
return self._info.get("color_temp", -1)
|
||||||
|
|
||||||
@ -129,7 +129,7 @@ class SmartBulb(SmartDevice, Bulb):
|
|||||||
def brightness(self) -> int:
|
def brightness(self) -> int:
|
||||||
"""Return the current brightness in percentage."""
|
"""Return the current brightness in percentage."""
|
||||||
if not self.is_dimmable: # pragma: no cover
|
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)
|
return self._info.get("brightness", -1)
|
||||||
|
|
||||||
@ -151,7 +151,7 @@ class SmartBulb(SmartDevice, Bulb):
|
|||||||
:param int transition: transition in milliseconds.
|
:param int transition: transition in milliseconds.
|
||||||
"""
|
"""
|
||||||
if not self.is_color:
|
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):
|
if not isinstance(hue, int) or not (0 <= hue <= 360):
|
||||||
raise ValueError(f"Invalid hue value: {hue} (valid range: 0-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
|
# TODO: Note, trying to set brightness at the same time
|
||||||
# with color_temp causes error -1008
|
# with color_temp causes error -1008
|
||||||
if not self.is_variable_color_temp:
|
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
|
valid_temperature_range = self.valid_temperature_range
|
||||||
if temp < valid_temperature_range[0] or temp > valid_temperature_range[1]:
|
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.
|
:param int transition: transition in milliseconds.
|
||||||
"""
|
"""
|
||||||
if not self.is_dimmable: # pragma: no cover
|
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(
|
return await self.protocol.query(
|
||||||
{"set_device_info": {"brightness": brightness}}
|
{"set_device_info": {"brightness": brightness}}
|
||||||
|
@ -9,7 +9,7 @@ from ..device import Device, WifiNetwork
|
|||||||
from ..device_type import DeviceType
|
from ..device_type import DeviceType
|
||||||
from ..deviceconfig import DeviceConfig
|
from ..deviceconfig import DeviceConfig
|
||||||
from ..emeterstatus import EmeterStatus
|
from ..emeterstatus import EmeterStatus
|
||||||
from ..exceptions import AuthenticationException, SmartDeviceException, SmartErrorCode
|
from ..exceptions import AuthenticationError, DeviceError, KasaException, SmartErrorCode
|
||||||
from ..feature import Feature, FeatureType
|
from ..feature import Feature, FeatureType
|
||||||
from ..smartprotocol import SmartProtocol
|
from ..smartprotocol import SmartProtocol
|
||||||
from .modules import ( # noqa: F401
|
from .modules import ( # noqa: F401
|
||||||
@ -85,7 +85,7 @@ class SmartDevice(Device):
|
|||||||
return response
|
return response
|
||||||
if default is not None:
|
if default is not None:
|
||||||
return default
|
return default
|
||||||
raise SmartDeviceException(
|
raise KasaException(
|
||||||
f"{request} not found in {responses} for device {self.host}"
|
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):
|
async def update(self, update_children: bool = True):
|
||||||
"""Update the device."""
|
"""Update the device."""
|
||||||
if self.credentials is None and self.credentials_hash is None:
|
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:
|
if self._components_raw is None:
|
||||||
await self._negotiate()
|
await self._negotiate()
|
||||||
@ -341,7 +341,7 @@ class SmartDevice(Device):
|
|||||||
"""Retrieve current energy readings."""
|
"""Retrieve current energy readings."""
|
||||||
_LOGGER.warning("Deprecated, use `emeter_realtime`.")
|
_LOGGER.warning("Deprecated, use `emeter_realtime`.")
|
||||||
if not self.has_emeter:
|
if not self.has_emeter:
|
||||||
raise SmartDeviceException("Device has no emeter")
|
raise KasaException("Device has no emeter")
|
||||||
return self.emeter_realtime
|
return self.emeter_realtime
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -421,7 +421,7 @@ class SmartDevice(Device):
|
|||||||
after some delay.
|
after some delay.
|
||||||
"""
|
"""
|
||||||
if not self.credentials:
|
if not self.credentials:
|
||||||
raise AuthenticationException("Device requires authentication.")
|
raise AuthenticationError("Device requires authentication.")
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"account": {
|
"account": {
|
||||||
@ -445,10 +445,9 @@ class SmartDevice(Device):
|
|||||||
# Thus, We limit retries and suppress the raised exception as useless.
|
# Thus, We limit retries and suppress the raised exception as useless.
|
||||||
try:
|
try:
|
||||||
return await self.protocol.query({"set_qs_info": payload}, retry_count=0)
|
return await self.protocol.query({"set_qs_info": payload}, retry_count=0)
|
||||||
except SmartDeviceException as ex:
|
except DeviceError:
|
||||||
if ex.error_code: # Re-raise on device-reported errors
|
raise # Re-raise on device-reported errors
|
||||||
raise
|
except KasaException:
|
||||||
|
|
||||||
_LOGGER.debug("Received an expected for wifi join, but this is expected")
|
_LOGGER.debug("Received an expected for wifi join, but this is expected")
|
||||||
|
|
||||||
async def update_credentials(self, username: str, password: str):
|
async def update_credentials(self, username: str, password: str):
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Dict, Type
|
from typing import TYPE_CHECKING, Dict, Type
|
||||||
|
|
||||||
from ..exceptions import SmartDeviceException
|
from ..exceptions import KasaException
|
||||||
from ..module import Module
|
from ..module import Module
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -59,7 +59,7 @@ class SmartModule(Module):
|
|||||||
q_keys = list(q.keys())
|
q_keys = list(q.keys())
|
||||||
# TODO: hacky way to check if update has been called.
|
# TODO: hacky way to check if update has been called.
|
||||||
if q_keys[0] not in self._device._last_update:
|
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"You need to call update() prior accessing module data"
|
||||||
f" for '{self._module}'"
|
f" for '{self._module}'"
|
||||||
)
|
)
|
||||||
|
@ -15,13 +15,13 @@ from typing import Any, Dict, Union
|
|||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
SMART_AUTHENTICATION_ERRORS,
|
SMART_AUTHENTICATION_ERRORS,
|
||||||
SMART_RETRYABLE_ERRORS,
|
SMART_RETRYABLE_ERRORS,
|
||||||
SMART_TIMEOUT_ERRORS,
|
AuthenticationError,
|
||||||
AuthenticationException,
|
DeviceError,
|
||||||
ConnectionException,
|
KasaException,
|
||||||
RetryableException,
|
|
||||||
SmartDeviceException,
|
|
||||||
SmartErrorCode,
|
SmartErrorCode,
|
||||||
TimeoutException,
|
TimeoutError,
|
||||||
|
_ConnectionError,
|
||||||
|
_RetryableError,
|
||||||
)
|
)
|
||||||
from .json import dumps as json_dumps
|
from .json import dumps as json_dumps
|
||||||
from .protocol import BaseProtocol, BaseTransport, md5
|
from .protocol import BaseProtocol, BaseTransport, md5
|
||||||
@ -66,32 +66,32 @@ class SmartProtocol(BaseProtocol):
|
|||||||
for retry in range(retry_count + 1):
|
for retry in range(retry_count + 1):
|
||||||
try:
|
try:
|
||||||
return await self._execute_query(request, retry)
|
return await self._execute_query(request, retry)
|
||||||
except ConnectionException as sdex:
|
except _ConnectionError as sdex:
|
||||||
if retry >= retry_count:
|
if retry >= retry_count:
|
||||||
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
|
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
|
||||||
raise sdex
|
raise sdex
|
||||||
continue
|
continue
|
||||||
except AuthenticationException as auex:
|
except AuthenticationError as auex:
|
||||||
await self._transport.reset()
|
await self._transport.reset()
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Unable to authenticate with %s, not retrying", self._host
|
"Unable to authenticate with %s, not retrying", self._host
|
||||||
)
|
)
|
||||||
raise auex
|
raise auex
|
||||||
except RetryableException as ex:
|
except _RetryableError as ex:
|
||||||
await self._transport.reset()
|
await self._transport.reset()
|
||||||
if retry >= retry_count:
|
if retry >= retry_count:
|
||||||
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
|
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
|
||||||
raise ex
|
raise ex
|
||||||
await asyncio.sleep(self.BACKOFF_SECONDS_AFTER_TIMEOUT)
|
await asyncio.sleep(self.BACKOFF_SECONDS_AFTER_TIMEOUT)
|
||||||
continue
|
continue
|
||||||
except TimeoutException as ex:
|
except TimeoutError as ex:
|
||||||
await self._transport.reset()
|
await self._transport.reset()
|
||||||
if retry >= retry_count:
|
if retry >= retry_count:
|
||||||
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
|
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
|
||||||
raise ex
|
raise ex
|
||||||
await asyncio.sleep(self.BACKOFF_SECONDS_AFTER_TIMEOUT)
|
await asyncio.sleep(self.BACKOFF_SECONDS_AFTER_TIMEOUT)
|
||||||
continue
|
continue
|
||||||
except SmartDeviceException as ex:
|
except KasaException as ex:
|
||||||
await self._transport.reset()
|
await self._transport.reset()
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Unable to query the device: %s, not retrying: %s",
|
"Unable to query the device: %s, not retrying: %s",
|
||||||
@ -101,7 +101,7 @@ class SmartProtocol(BaseProtocol):
|
|||||||
raise ex
|
raise ex
|
||||||
|
|
||||||
# make mypy happy, this should never be reached..
|
# 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:
|
async def _execute_multiple_query(self, request: Dict, retry_count: int) -> Dict:
|
||||||
debug_enabled = _LOGGER.isEnabledFor(logging.DEBUG)
|
debug_enabled = _LOGGER.isEnabledFor(logging.DEBUG)
|
||||||
@ -193,13 +193,11 @@ class SmartProtocol(BaseProtocol):
|
|||||||
+ f"{error_code.name}({error_code.value})"
|
+ f"{error_code.name}({error_code.value})"
|
||||||
+ f" for method: {method}"
|
+ 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:
|
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:
|
if error_code in SMART_AUTHENTICATION_ERRORS:
|
||||||
raise AuthenticationException(msg, error_code=error_code)
|
raise AuthenticationError(msg, error_code=error_code)
|
||||||
raise SmartDeviceException(msg, error_code=error_code)
|
raise DeviceError(msg, error_code=error_code)
|
||||||
|
|
||||||
async def close(self) -> None:
|
async def close(self) -> None:
|
||||||
"""Close the underlying transport."""
|
"""Close the underlying transport."""
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import warnings
|
import warnings
|
||||||
from json import loads as json_loads
|
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
|
from kasa.protocol import BaseTransport
|
||||||
|
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ class FakeSmartTransport(BaseTransport):
|
|||||||
)
|
)
|
||||||
return {"result": missing_result[1], "error_code": 0}
|
return {"result": missing_result[1], "error_code": 0}
|
||||||
else:
|
else:
|
||||||
raise SmartDeviceException(f"Fixture doesn't support {method}")
|
raise KasaException(f"Fixture doesn't support {method}")
|
||||||
elif method == "set_qs_info":
|
elif method == "set_qs_info":
|
||||||
return {"error_code": 0}
|
return {"error_code": 0}
|
||||||
elif method[:4] == "set_":
|
elif method[:4] == "set_":
|
||||||
|
@ -19,8 +19,8 @@ from ..aestransport import AesEncyptionSession, AesTransport, TransportState
|
|||||||
from ..credentials import Credentials
|
from ..credentials import Credentials
|
||||||
from ..deviceconfig import DeviceConfig
|
from ..deviceconfig import DeviceConfig
|
||||||
from ..exceptions import (
|
from ..exceptions import (
|
||||||
AuthenticationException,
|
AuthenticationError,
|
||||||
SmartDeviceException,
|
KasaException,
|
||||||
SmartErrorCode,
|
SmartErrorCode,
|
||||||
)
|
)
|
||||||
from ..httpclient import HttpClient
|
from ..httpclient import HttpClient
|
||||||
@ -49,8 +49,8 @@ status_parameters = pytest.mark.parametrize(
|
|||||||
"status_code, error_code, inner_error_code, expectation",
|
"status_code, error_code, inner_error_code, expectation",
|
||||||
[
|
[
|
||||||
(200, 0, 0, does_not_raise()),
|
(200, 0, 0, does_not_raise()),
|
||||||
(400, 0, 0, pytest.raises(SmartDeviceException)),
|
(400, 0, 0, pytest.raises(KasaException)),
|
||||||
(200, -1, 0, pytest.raises(SmartDeviceException)),
|
(200, -1, 0, pytest.raises(KasaException)),
|
||||||
],
|
],
|
||||||
ids=("success", "status_code", "error_code"),
|
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, 0, 0, 0], does_not_raise(), 4),
|
||||||
(
|
(
|
||||||
[SmartErrorCode.LOGIN_ERROR, SmartErrorCode.LOGIN_ERROR],
|
[SmartErrorCode.LOGIN_ERROR, SmartErrorCode.LOGIN_ERROR],
|
||||||
pytest.raises(AuthenticationException),
|
pytest.raises(AuthenticationError),
|
||||||
3,
|
3,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
[SmartErrorCode.LOGIN_FAILED_ERROR],
|
[SmartErrorCode.LOGIN_FAILED_ERROR],
|
||||||
pytest.raises(AuthenticationException),
|
pytest.raises(AuthenticationError),
|
||||||
1,
|
1,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
[SmartErrorCode.LOGIN_ERROR, SmartErrorCode.SESSION_TIMEOUT_ERROR],
|
[SmartErrorCode.LOGIN_ERROR, SmartErrorCode.SESSION_TIMEOUT_ERROR],
|
||||||
pytest.raises(SmartDeviceException),
|
pytest.raises(KasaException),
|
||||||
3,
|
3,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -238,7 +238,7 @@ async def test_unencrypted_response_invalid_json(mocker, caplog):
|
|||||||
}
|
}
|
||||||
caplog.set_level(logging.DEBUG)
|
caplog.set_level(logging.DEBUG)
|
||||||
msg = f"Unable to decrypt response from {host}, error: Incorrect padding, response: Foobar"
|
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))
|
await transport.send(json_dumps(request))
|
||||||
|
|
||||||
|
|
||||||
@ -267,7 +267,7 @@ async def test_passthrough_errors(mocker, error_code):
|
|||||||
"requestID": 1,
|
"requestID": 1,
|
||||||
"terminal_uuid": "foobar",
|
"terminal_uuid": "foobar",
|
||||||
}
|
}
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await transport.send(json_dumps(request))
|
await transport.send(json_dumps(request))
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ from voluptuous import (
|
|||||||
Schema,
|
Schema,
|
||||||
)
|
)
|
||||||
|
|
||||||
from kasa import Bulb, BulbPreset, DeviceType, SmartDeviceException
|
from kasa import Bulb, BulbPreset, DeviceType, KasaException
|
||||||
from kasa.iot import IotBulb
|
from kasa.iot import IotBulb
|
||||||
|
|
||||||
from .conftest import (
|
from .conftest import (
|
||||||
@ -51,7 +51,7 @@ async def test_state_attributes(dev: Bulb):
|
|||||||
|
|
||||||
@bulb_iot
|
@bulb_iot
|
||||||
async def test_light_state_without_update(dev: IotBulb, monkeypatch):
|
async def test_light_state_without_update(dev: IotBulb, monkeypatch):
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
monkeypatch.setitem(
|
monkeypatch.setitem(
|
||||||
dev._last_update["system"]["get_sysinfo"], "light_state", None
|
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):
|
async def test_hsv_on_non_color(dev: Bulb):
|
||||||
assert not dev.is_color
|
assert not dev.is_color
|
||||||
|
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await dev.set_hsv(0, 0, 0)
|
await dev.set_hsv(0, 0, 0)
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
print(dev.hsv)
|
print(dev.hsv)
|
||||||
|
|
||||||
|
|
||||||
@ -175,13 +175,13 @@ async def test_out_of_range_temperature(dev: Bulb):
|
|||||||
|
|
||||||
@non_variable_temp
|
@non_variable_temp
|
||||||
async def test_non_variable_temp(dev: Bulb):
|
async def test_non_variable_temp(dev: Bulb):
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await dev.set_color_temp(2700)
|
await dev.set_color_temp(2700)
|
||||||
|
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
print(dev.valid_temperature_range)
|
print(dev.valid_temperature_range)
|
||||||
|
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
print(dev.color_temp)
|
print(dev.color_temp)
|
||||||
|
|
||||||
|
|
||||||
@ -238,9 +238,9 @@ async def test_invalid_brightness(dev: Bulb):
|
|||||||
async def test_non_dimmable(dev: Bulb):
|
async def test_non_dimmable(dev: Bulb):
|
||||||
assert not dev.is_dimmable
|
assert not dev.is_dimmable
|
||||||
|
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
assert dev.brightness == 0
|
assert dev.brightness == 0
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await dev.set_brightness(100)
|
await dev.set_brightness(100)
|
||||||
|
|
||||||
|
|
||||||
@ -296,7 +296,7 @@ async def test_modify_preset(dev: IotBulb, mocker):
|
|||||||
await dev.save_preset(preset)
|
await dev.save_preset(preset)
|
||||||
assert dev.presets[0].brightness == 10
|
assert dev.presets[0].brightness == 10
|
||||||
|
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await dev.save_preset(
|
await dev.save_preset(
|
||||||
BulbPreset(index=5, hue=0, brightness=0, saturation=0, color_temp=0)
|
BulbPreset(index=5, hue=0, brightness=0, saturation=0, color_temp=0)
|
||||||
)
|
)
|
||||||
|
@ -6,12 +6,13 @@ import pytest
|
|||||||
from asyncclick.testing import CliRunner
|
from asyncclick.testing import CliRunner
|
||||||
|
|
||||||
from kasa import (
|
from kasa import (
|
||||||
AuthenticationException,
|
AuthenticationError,
|
||||||
Credentials,
|
Credentials,
|
||||||
Device,
|
Device,
|
||||||
|
DeviceError,
|
||||||
EmeterStatus,
|
EmeterStatus,
|
||||||
SmartDeviceException,
|
KasaException,
|
||||||
UnsupportedDeviceException,
|
UnsupportedDeviceError,
|
||||||
)
|
)
|
||||||
from kasa.cli import (
|
from kasa.cli import (
|
||||||
TYPE_TO_CLASS,
|
TYPE_TO_CLASS,
|
||||||
@ -188,15 +189,13 @@ async def test_wifi_join_no_creds(dev):
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert res.exit_code != 0
|
assert res.exit_code != 0
|
||||||
assert isinstance(res.exception, AuthenticationException)
|
assert isinstance(res.exception, AuthenticationError)
|
||||||
|
|
||||||
|
|
||||||
@device_smart
|
@device_smart
|
||||||
async def test_wifi_join_exception(dev, mocker):
|
async def test_wifi_join_exception(dev, mocker):
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
mocker.patch.object(
|
mocker.patch.object(dev.protocol, "query", side_effect=DeviceError(error_code=9999))
|
||||||
dev.protocol, "query", side_effect=SmartDeviceException(error_code=9999)
|
|
||||||
)
|
|
||||||
res = await runner.invoke(
|
res = await runner.invoke(
|
||||||
wifi,
|
wifi,
|
||||||
["join", "FOOBAR", "--keytype", "wpa_psk", "--password", "foobar"],
|
["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 res.exit_code != 0
|
||||||
assert isinstance(res.exception, SmartDeviceException)
|
assert isinstance(res.exception, KasaException)
|
||||||
|
|
||||||
|
|
||||||
@device_smart
|
@device_smart
|
||||||
@ -509,7 +508,7 @@ async def test_host_unsupported(unsupported_device_info):
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert res.exit_code != 0
|
assert res.exit_code != 0
|
||||||
assert isinstance(res.exception, UnsupportedDeviceException)
|
assert isinstance(res.exception, UnsupportedDeviceError)
|
||||||
|
|
||||||
|
|
||||||
@new_discovery
|
@new_discovery
|
||||||
@ -522,7 +521,7 @@ async def test_discover_auth_failed(discovery_mock, mocker):
|
|||||||
mocker.patch.object(
|
mocker.patch.object(
|
||||||
device_class,
|
device_class,
|
||||||
"update",
|
"update",
|
||||||
side_effect=AuthenticationException("Failed to authenticate"),
|
side_effect=AuthenticationError("Failed to authenticate"),
|
||||||
)
|
)
|
||||||
res = await runner.invoke(
|
res = await runner.invoke(
|
||||||
cli,
|
cli,
|
||||||
@ -553,7 +552,7 @@ async def test_host_auth_failed(discovery_mock, mocker):
|
|||||||
mocker.patch.object(
|
mocker.patch.object(
|
||||||
device_class,
|
device_class,
|
||||||
"update",
|
"update",
|
||||||
side_effect=AuthenticationException("Failed to authenticate"),
|
side_effect=AuthenticationError("Failed to authenticate"),
|
||||||
)
|
)
|
||||||
res = await runner.invoke(
|
res = await runner.invoke(
|
||||||
cli,
|
cli,
|
||||||
@ -569,7 +568,7 @@ async def test_host_auth_failed(discovery_mock, mocker):
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert res.exit_code != 0
|
assert res.exit_code != 0
|
||||||
assert isinstance(res.exception, AuthenticationException)
|
assert isinstance(res.exception, AuthenticationError)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("device_type", list(TYPE_TO_CLASS))
|
@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):
|
async def test_errors(mocker):
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
err = SmartDeviceException("Foobar")
|
err = KasaException("Foobar")
|
||||||
|
|
||||||
# Test masking
|
# Test masking
|
||||||
mocker.patch("kasa.Discover.discover", side_effect=err)
|
mocker.patch("kasa.Discover.discover", side_effect=err)
|
||||||
|
@ -8,7 +8,7 @@ from kasa import (
|
|||||||
Credentials,
|
Credentials,
|
||||||
Device,
|
Device,
|
||||||
Discover,
|
Discover,
|
||||||
SmartDeviceException,
|
KasaException,
|
||||||
)
|
)
|
||||||
from kasa.device_factory import connect, get_protocol
|
from kasa.device_factory import connect, get_protocol
|
||||||
from kasa.deviceconfig import (
|
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):
|
async def test_connect_query_fails(all_fixture_data: dict, mocker):
|
||||||
"""Make sure that connect fails when query fails."""
|
"""Make sure that connect fails when query fails."""
|
||||||
host = "127.0.0.1"
|
host = "127.0.0.1"
|
||||||
mocker.patch("kasa.IotProtocol.query", side_effect=SmartDeviceException)
|
mocker.patch("kasa.IotProtocol.query", side_effect=KasaException)
|
||||||
mocker.patch("kasa.SmartProtocol.query", side_effect=SmartDeviceException)
|
mocker.patch("kasa.SmartProtocol.query", side_effect=KasaException)
|
||||||
|
|
||||||
ctype, _ = _get_connection_type_device_class(all_fixture_data)
|
ctype, _ = _get_connection_type_device_class(all_fixture_data)
|
||||||
config = DeviceConfig(
|
config = DeviceConfig(
|
||||||
@ -120,7 +120,7 @@ async def test_connect_query_fails(all_fixture_data: dict, mocker):
|
|||||||
protocol_class = get_protocol(config).__class__
|
protocol_class = get_protocol(config).__class__
|
||||||
close_mock = mocker.patch.object(protocol_class, "close")
|
close_mock = mocker.patch.object(protocol_class, "close")
|
||||||
assert close_mock.call_count == 0
|
assert close_mock.call_count == 0
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await connect(config=config)
|
await connect(config=config)
|
||||||
assert close_mock.call_count == 1
|
assert close_mock.call_count == 1
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ from kasa.credentials import Credentials
|
|||||||
from kasa.deviceconfig import (
|
from kasa.deviceconfig import (
|
||||||
DeviceConfig,
|
DeviceConfig,
|
||||||
)
|
)
|
||||||
from kasa.exceptions import SmartDeviceException
|
from kasa.exceptions import KasaException
|
||||||
|
|
||||||
|
|
||||||
async def test_serialization():
|
async def test_serialization():
|
||||||
@ -29,7 +29,7 @@ async def test_serialization():
|
|||||||
ids=["invalid-dict", "not-dict"],
|
ids=["invalid-dict", "not-dict"],
|
||||||
)
|
)
|
||||||
def test_deserialization_errors(input_value, expected_msg):
|
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)
|
DeviceConfig.from_dict(input_value)
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,14 +13,14 @@ from kasa import (
|
|||||||
Device,
|
Device,
|
||||||
DeviceType,
|
DeviceType,
|
||||||
Discover,
|
Discover,
|
||||||
SmartDeviceException,
|
KasaException,
|
||||||
)
|
)
|
||||||
from kasa.deviceconfig import (
|
from kasa.deviceconfig import (
|
||||||
ConnectionType,
|
ConnectionType,
|
||||||
DeviceConfig,
|
DeviceConfig,
|
||||||
)
|
)
|
||||||
from kasa.discover import DiscoveryResult, _DiscoverProtocol, json_dumps
|
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.iot import IotDevice
|
||||||
from kasa.xortransport import XorEncryption
|
from kasa.xortransport import XorEncryption
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ async def test_type_detection_lightstrip(dev: Device):
|
|||||||
|
|
||||||
async def test_type_unknown():
|
async def test_type_unknown():
|
||||||
invalid_info = {"system": {"get_sysinfo": {"type": "nosuchtype"}}}
|
invalid_info = {"system": {"get_sysinfo": {"type": "nosuchtype"}}}
|
||||||
with pytest.raises(UnsupportedDeviceException):
|
with pytest.raises(UnsupportedDeviceError):
|
||||||
Discover._get_device_class(invalid_info)
|
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
|
assert update_mock.call_count == 0
|
||||||
|
|
||||||
mocker.patch("socket.getaddrinfo", side_effect=socket.gaierror())
|
mocker.patch("socket.getaddrinfo", side_effect=socket.gaierror())
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
x = await Discover.discover_single(host, credentials=Credentials())
|
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
|
# Test with a valid unsupported response
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
UnsupportedDeviceException,
|
UnsupportedDeviceError,
|
||||||
):
|
):
|
||||||
await Discover.discover_single(host)
|
await Discover.discover_single(host)
|
||||||
|
|
||||||
@ -171,7 +171,7 @@ async def test_discover_single_no_response(mocker):
|
|||||||
host = "127.0.0.1"
|
host = "127.0.0.1"
|
||||||
mocker.patch.object(_DiscoverProtocol, "do_discover")
|
mocker.patch.object(_DiscoverProtocol, "do_discover")
|
||||||
with pytest.raises(
|
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)
|
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)
|
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)
|
await Discover.discover_single(host)
|
||||||
|
|
||||||
|
|
||||||
@ -280,11 +280,11 @@ async def test_discover_single_authentication(discovery_mock, mocker):
|
|||||||
mocker.patch.object(
|
mocker.patch.object(
|
||||||
device_class,
|
device_class,
|
||||||
"update",
|
"update",
|
||||||
side_effect=AuthenticationException("Failed to authenticate"),
|
side_effect=AuthenticationError("Failed to authenticate"),
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
AuthenticationException,
|
AuthenticationError,
|
||||||
match="Failed to authenticate",
|
match="Failed to authenticate",
|
||||||
):
|
):
|
||||||
device = await Discover.discover_single(
|
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
|
# TODO implement requires_update for SmartDevice
|
||||||
if isinstance(device, IotDevice):
|
if isinstance(device, IotDevice):
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
SmartDeviceException,
|
KasaException,
|
||||||
match=re.escape("You need to await update() to access the data"),
|
match=re.escape("You need to await update() to access the data"),
|
||||||
):
|
):
|
||||||
assert device.supported_modules
|
assert device.supported_modules
|
||||||
@ -456,9 +456,9 @@ async def test_discover_propogates_task_exceptions(discovery_mock):
|
|||||||
discovery_timeout = 0
|
discovery_timeout = 0
|
||||||
|
|
||||||
async def on_discovered(dev):
|
async def on_discovered(dev):
|
||||||
raise SmartDeviceException("Dummy exception")
|
raise KasaException("Dummy exception")
|
||||||
|
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await Discover.discover(
|
await Discover.discover(
|
||||||
discovery_timeout=discovery_timeout, on_discovered=on_discovered
|
discovery_timeout=discovery_timeout, on_discovered=on_discovered
|
||||||
)
|
)
|
||||||
|
@ -10,7 +10,7 @@ from voluptuous import (
|
|||||||
Schema,
|
Schema,
|
||||||
)
|
)
|
||||||
|
|
||||||
from kasa import EmeterStatus, SmartDeviceException
|
from kasa import EmeterStatus, KasaException
|
||||||
from kasa.iot import IotDevice
|
from kasa.iot import IotDevice
|
||||||
from kasa.iot.modules.emeter import Emeter
|
from kasa.iot.modules.emeter import Emeter
|
||||||
|
|
||||||
@ -38,16 +38,16 @@ CURRENT_CONSUMPTION_SCHEMA = Schema(
|
|||||||
async def test_no_emeter(dev):
|
async def test_no_emeter(dev):
|
||||||
assert not dev.has_emeter
|
assert not dev.has_emeter
|
||||||
|
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await dev.get_emeter_realtime()
|
await dev.get_emeter_realtime()
|
||||||
# Only iot devices support the historical stats so other
|
# Only iot devices support the historical stats so other
|
||||||
# devices will not implement the methods below
|
# devices will not implement the methods below
|
||||||
if isinstance(dev, IotDevice):
|
if isinstance(dev, IotDevice):
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await dev.get_emeter_daily()
|
await dev.get_emeter_daily()
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await dev.get_emeter_monthly()
|
await dev.get_emeter_monthly()
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await dev.erase_emeter_stats()
|
await dev.erase_emeter_stats()
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,9 +6,9 @@ import pytest
|
|||||||
|
|
||||||
from ..deviceconfig import DeviceConfig
|
from ..deviceconfig import DeviceConfig
|
||||||
from ..exceptions import (
|
from ..exceptions import (
|
||||||
ConnectionException,
|
KasaException,
|
||||||
SmartDeviceException,
|
TimeoutError,
|
||||||
TimeoutException,
|
_ConnectionError,
|
||||||
)
|
)
|
||||||
from ..httpclient import HttpClient
|
from ..httpclient import HttpClient
|
||||||
|
|
||||||
@ -18,28 +18,28 @@ from ..httpclient import HttpClient
|
|||||||
[
|
[
|
||||||
(
|
(
|
||||||
aiohttp.ServerDisconnectedError(),
|
aiohttp.ServerDisconnectedError(),
|
||||||
ConnectionException,
|
_ConnectionError,
|
||||||
"Device connection error: ",
|
"Device connection error: ",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
aiohttp.ClientOSError(),
|
aiohttp.ClientOSError(),
|
||||||
ConnectionException,
|
_ConnectionError,
|
||||||
"Device connection error: ",
|
"Device connection error: ",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
aiohttp.ServerTimeoutError(),
|
aiohttp.ServerTimeoutError(),
|
||||||
TimeoutException,
|
TimeoutError,
|
||||||
"Unable to query the device, timed out: ",
|
"Unable to query the device, timed out: ",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
asyncio.TimeoutError(),
|
asyncio.TimeoutError(),
|
||||||
TimeoutException,
|
TimeoutError,
|
||||||
"Unable to query the device, timed out: ",
|
"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),
|
aiohttp.ServerFingerprintMismatch("exp", "got", "host", 1),
|
||||||
SmartDeviceException,
|
KasaException,
|
||||||
"Unable to query the device: ",
|
"Unable to query the device: ",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -12,11 +12,11 @@ from ..aestransport import AesTransport
|
|||||||
from ..credentials import Credentials
|
from ..credentials import Credentials
|
||||||
from ..deviceconfig import DeviceConfig
|
from ..deviceconfig import DeviceConfig
|
||||||
from ..exceptions import (
|
from ..exceptions import (
|
||||||
AuthenticationException,
|
AuthenticationError,
|
||||||
ConnectionException,
|
KasaException,
|
||||||
RetryableException,
|
TimeoutError,
|
||||||
SmartDeviceException,
|
_ConnectionError,
|
||||||
TimeoutException,
|
_RetryableError,
|
||||||
)
|
)
|
||||||
from ..httpclient import HttpClient
|
from ..httpclient import HttpClient
|
||||||
from ..iotprotocol import IotProtocol
|
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)
|
mocker.patch.object(protocol_class, "BACKOFF_SECONDS_AFTER_TIMEOUT", 0)
|
||||||
|
|
||||||
config = DeviceConfig(host)
|
config = DeviceConfig(host)
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await protocol_class(transport=transport_class(config=config)).query(
|
await protocol_class(transport=transport_class(config=config)).query(
|
||||||
DUMMY_QUERY, retry_count=retry_count
|
DUMMY_QUERY, retry_count=retry_count
|
||||||
)
|
)
|
||||||
@ -80,11 +80,11 @@ async def test_protocol_retries_via_client_session(
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"error, retry_expectation",
|
"error, retry_expectation",
|
||||||
[
|
[
|
||||||
(SmartDeviceException("dummy exception"), False),
|
(KasaException("dummy exception"), False),
|
||||||
(RetryableException("dummy exception"), True),
|
(_RetryableError("dummy exception"), True),
|
||||||
(TimeoutException("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("transport_class", [AesTransport, KlapTransport])
|
||||||
@pytest.mark.parametrize("protocol_class", [IotProtocol, SmartProtocol])
|
@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)
|
mocker.patch.object(protocol_class, "BACKOFF_SECONDS_AFTER_TIMEOUT", 0)
|
||||||
|
|
||||||
config = DeviceConfig(host)
|
config = DeviceConfig(host)
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await protocol_class(transport=transport_class(config=config)).query(
|
await protocol_class(transport=transport_class(config=config)).query(
|
||||||
DUMMY_QUERY, retry_count=retry_count
|
DUMMY_QUERY, retry_count=retry_count
|
||||||
)
|
)
|
||||||
@ -115,11 +115,11 @@ async def test_protocol_no_retry_on_connection_error(
|
|||||||
conn = mocker.patch.object(
|
conn = mocker.patch.object(
|
||||||
aiohttp.ClientSession,
|
aiohttp.ClientSession,
|
||||||
"post",
|
"post",
|
||||||
side_effect=AuthenticationException("foo"),
|
side_effect=AuthenticationError("foo"),
|
||||||
)
|
)
|
||||||
mocker.patch.object(protocol_class, "BACKOFF_SECONDS_AFTER_TIMEOUT", 0)
|
mocker.patch.object(protocol_class, "BACKOFF_SECONDS_AFTER_TIMEOUT", 0)
|
||||||
config = DeviceConfig(host)
|
config = DeviceConfig(host)
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await protocol_class(transport=transport_class(config=config)).query(
|
await protocol_class(transport=transport_class(config=config)).query(
|
||||||
DUMMY_QUERY, retry_count=5
|
DUMMY_QUERY, retry_count=5
|
||||||
)
|
)
|
||||||
@ -139,7 +139,7 @@ async def test_protocol_retry_recoverable_error(
|
|||||||
side_effect=aiohttp.ClientOSError("foo"),
|
side_effect=aiohttp.ClientOSError("foo"),
|
||||||
)
|
)
|
||||||
config = DeviceConfig(host)
|
config = DeviceConfig(host)
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await protocol_class(transport=transport_class(config=config)).query(
|
await protocol_class(transport=transport_class(config=config)).query(
|
||||||
DUMMY_QUERY, retry_count=5
|
DUMMY_QUERY, retry_count=5
|
||||||
)
|
)
|
||||||
@ -159,7 +159,7 @@ async def test_protocol_reconnect(mocker, retry_count, protocol_class, transport
|
|||||||
nonlocal remaining
|
nonlocal remaining
|
||||||
remaining -= 1
|
remaining -= 1
|
||||||
if remaining:
|
if remaining:
|
||||||
raise ConnectionException("Simulated connection failure")
|
raise _ConnectionError("Simulated connection failure")
|
||||||
|
|
||||||
return mock_response
|
return mock_response
|
||||||
|
|
||||||
@ -249,7 +249,7 @@ def test_encrypt_unicode():
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
Credentials("shouldfail", "shouldfail"),
|
Credentials("shouldfail", "shouldfail"),
|
||||||
pytest.raises(AuthenticationException),
|
pytest.raises(AuthenticationError),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
ids=("client", "blank", "kasa_setup", "shouldfail"),
|
ids=("client", "blank", "kasa_setup", "shouldfail"),
|
||||||
@ -350,7 +350,7 @@ async def test_handshake(
|
|||||||
assert protocol._transport._handshake_done is True
|
assert protocol._transport._handshake_done is True
|
||||||
|
|
||||||
response_status = 403
|
response_status = 403
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await protocol._transport.perform_handshake()
|
await protocol._transport.perform_handshake()
|
||||||
assert protocol._transport._handshake_done is False
|
assert protocol._transport._handshake_done is False
|
||||||
await protocol.close()
|
await protocol.close()
|
||||||
@ -405,37 +405,37 @@ async def test_query(mocker):
|
|||||||
pytest.param(
|
pytest.param(
|
||||||
(403, 403, 403),
|
(403, 403, 403),
|
||||||
True,
|
True,
|
||||||
pytest.raises(SmartDeviceException),
|
pytest.raises(KasaException),
|
||||||
id="handshake1-403-status",
|
id="handshake1-403-status",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
(200, 403, 403),
|
(200, 403, 403),
|
||||||
True,
|
True,
|
||||||
pytest.raises(SmartDeviceException),
|
pytest.raises(KasaException),
|
||||||
id="handshake2-403-status",
|
id="handshake2-403-status",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
(200, 200, 403),
|
(200, 200, 403),
|
||||||
True,
|
True,
|
||||||
pytest.raises(AuthenticationException),
|
pytest.raises(AuthenticationError),
|
||||||
id="request-403-status",
|
id="request-403-status",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
(200, 200, 400),
|
(200, 200, 400),
|
||||||
True,
|
True,
|
||||||
pytest.raises(SmartDeviceException),
|
pytest.raises(KasaException),
|
||||||
id="request-400-status",
|
id="request-400-status",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
(200, 200, 200),
|
(200, 200, 200),
|
||||||
False,
|
False,
|
||||||
pytest.raises(AuthenticationException),
|
pytest.raises(AuthenticationError),
|
||||||
id="handshake1-wrong-auth",
|
id="handshake1-wrong-auth",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
(200, 200, 200),
|
(200, 200, 200),
|
||||||
secrets.token_bytes(16),
|
secrets.token_bytes(16),
|
||||||
pytest.raises(SmartDeviceException),
|
pytest.raises(KasaException),
|
||||||
id="handshake1-bad-auth-length",
|
id="handshake1-bad-auth-length",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from kasa import DeviceType
|
from kasa import DeviceType
|
||||||
from kasa.exceptions import SmartDeviceException
|
from kasa.exceptions import KasaException
|
||||||
from kasa.iot import IotLightStrip
|
from kasa.iot import IotLightStrip
|
||||||
|
|
||||||
from .conftest import lightstrip
|
from .conftest import lightstrip
|
||||||
@ -23,7 +23,7 @@ async def test_lightstrip_effect(dev: IotLightStrip):
|
|||||||
|
|
||||||
@lightstrip
|
@lightstrip
|
||||||
async def test_effects_lightstrip_set_effect(dev: IotLightStrip):
|
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("Not real")
|
||||||
|
|
||||||
await dev.set_effect("Candy Cane")
|
await dev.set_effect("Candy Cane")
|
||||||
|
@ -14,7 +14,7 @@ import pytest
|
|||||||
from ..aestransport import AesTransport
|
from ..aestransport import AesTransport
|
||||||
from ..credentials import Credentials
|
from ..credentials import Credentials
|
||||||
from ..deviceconfig import DeviceConfig
|
from ..deviceconfig import DeviceConfig
|
||||||
from ..exceptions import SmartDeviceException
|
from ..exceptions import KasaException
|
||||||
from ..iotprotocol import IotProtocol, _deprecated_TPLinkSmartHomeProtocol
|
from ..iotprotocol import IotProtocol, _deprecated_TPLinkSmartHomeProtocol
|
||||||
from ..klaptransport import KlapTransport, KlapTransportV2
|
from ..klaptransport import KlapTransport, KlapTransportV2
|
||||||
from ..protocol import (
|
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)
|
conn = mocker.patch("asyncio.open_connection", side_effect=aio_mock_writer)
|
||||||
config = DeviceConfig("127.0.0.1")
|
config = DeviceConfig("127.0.0.1")
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await protocol_class(transport=transport_class(config=config)).query(
|
await protocol_class(transport=transport_class(config=config)).query(
|
||||||
{}, retry_count=retry_count
|
{}, 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"),
|
side_effect=OSError(errno.EHOSTUNREACH, "No route to host"),
|
||||||
)
|
)
|
||||||
config = DeviceConfig("127.0.0.1")
|
config = DeviceConfig("127.0.0.1")
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await protocol_class(transport=transport_class(config=config)).query(
|
await protocol_class(transport=transport_class(config=config)).query(
|
||||||
{}, retry_count=5
|
{}, retry_count=5
|
||||||
)
|
)
|
||||||
@ -94,7 +94,7 @@ async def test_protocol_no_retry_connection_refused(
|
|||||||
side_effect=ConnectionRefusedError,
|
side_effect=ConnectionRefusedError,
|
||||||
)
|
)
|
||||||
config = DeviceConfig("127.0.0.1")
|
config = DeviceConfig("127.0.0.1")
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await protocol_class(transport=transport_class(config=config)).query(
|
await protocol_class(transport=transport_class(config=config)).query(
|
||||||
{}, retry_count=5
|
{}, retry_count=5
|
||||||
)
|
)
|
||||||
@ -118,7 +118,7 @@ async def test_protocol_retry_recoverable_error(
|
|||||||
side_effect=OSError(errno.ECONNRESET, "Connection reset by peer"),
|
side_effect=OSError(errno.ECONNRESET, "Connection reset by peer"),
|
||||||
)
|
)
|
||||||
config = DeviceConfig("127.0.0.1")
|
config = DeviceConfig("127.0.0.1")
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await protocol_class(transport=transport_class(config=config)).query(
|
await protocol_class(transport=transport_class(config=config)).query(
|
||||||
{}, retry_count=5
|
{}, retry_count=5
|
||||||
)
|
)
|
||||||
@ -553,7 +553,7 @@ async def test_protocol_will_retry_on_connect(
|
|||||||
retry_count = 2
|
retry_count = 2
|
||||||
conn = mocker.patch("asyncio.open_connection", side_effect=error)
|
conn = mocker.patch("asyncio.open_connection", side_effect=error)
|
||||||
config = DeviceConfig("127.0.0.1")
|
config = DeviceConfig("127.0.0.1")
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await protocol_class(transport=transport_class(config=config)).query(
|
await protocol_class(transport=transport_class(config=config)).query(
|
||||||
{}, retry_count=retry_count
|
{}, 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)
|
conn = mocker.patch("asyncio.open_connection", side_effect=aio_mock_writer)
|
||||||
write_mock = mocker.patch("asyncio.StreamWriter.write", side_effect=error)
|
write_mock = mocker.patch("asyncio.StreamWriter.write", side_effect=error)
|
||||||
config = DeviceConfig("127.0.0.1")
|
config = DeviceConfig("127.0.0.1")
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await protocol_class(transport=transport_class(config=config)).query(
|
await protocol_class(transport=transport_class(config=config)).query(
|
||||||
{}, retry_count=retry_count
|
{}, retry_count=retry_count
|
||||||
)
|
)
|
||||||
@ -609,9 +609,7 @@ def test_deprecated_protocol():
|
|||||||
with pytest.deprecated_call():
|
with pytest.deprecated_call():
|
||||||
from kasa import TPLinkSmartHomeProtocol
|
from kasa import TPLinkSmartHomeProtocol
|
||||||
|
|
||||||
with pytest.raises(
|
with pytest.raises(KasaException, match="host or transport must be supplied"):
|
||||||
SmartDeviceException, match="host or transport must be supplied"
|
|
||||||
):
|
|
||||||
proto = TPLinkSmartHomeProtocol()
|
proto = TPLinkSmartHomeProtocol()
|
||||||
host = "127.0.0.1"
|
host = "127.0.0.1"
|
||||||
proto = TPLinkSmartHomeProtocol(host=host)
|
proto = TPLinkSmartHomeProtocol(host=host)
|
||||||
|
@ -21,7 +21,7 @@ from voluptuous import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
import kasa
|
import kasa
|
||||||
from kasa import Credentials, Device, DeviceConfig, SmartDeviceException
|
from kasa import Credentials, Device, DeviceConfig, KasaException
|
||||||
from kasa.exceptions import SmartErrorCode
|
from kasa.exceptions import SmartErrorCode
|
||||||
from kasa.iot import IotDevice
|
from kasa.iot import IotDevice
|
||||||
from kasa.smart import SmartChildDevice, SmartDevice
|
from kasa.smart import SmartChildDevice, SmartDevice
|
||||||
@ -67,8 +67,8 @@ async def test_state_info(dev):
|
|||||||
@device_iot
|
@device_iot
|
||||||
async def test_invalid_connection(dev):
|
async def test_invalid_connection(dev):
|
||||||
with patch.object(
|
with patch.object(
|
||||||
FakeIotProtocol, "query", side_effect=SmartDeviceException
|
FakeIotProtocol, "query", side_effect=KasaException
|
||||||
), pytest.raises(SmartDeviceException):
|
), pytest.raises(KasaException):
|
||||||
await dev.update()
|
await dev.update()
|
||||||
|
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ async def test_initial_update_no_emeter(dev, mocker):
|
|||||||
|
|
||||||
@device_iot
|
@device_iot
|
||||||
async def test_query_helper(dev):
|
async def test_query_helper(dev):
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
await dev._query_helper("test", "testcmd", {})
|
await dev._query_helper("test", "testcmd", {})
|
||||||
# TODO check for unwrapping?
|
# 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"
|
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(
|
with patch.object(dev.protocol, "query", return_value=mock_response), pytest.raises(
|
||||||
SmartDeviceException, match=msg
|
KasaException, match=msg
|
||||||
):
|
):
|
||||||
await dev.update()
|
await dev.update()
|
||||||
|
|
||||||
@ -348,6 +348,16 @@ def test_deprecated_devices(device_class, use_class):
|
|||||||
getattr(module, use_class.__name__)
|
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):
|
def check_mac(x):
|
||||||
if re.match("[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", x.lower()):
|
if re.match("[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", x.lower()):
|
||||||
return x
|
return x
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
from itertools import chain
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ..credentials import Credentials
|
from ..credentials import Credentials
|
||||||
from ..deviceconfig import DeviceConfig
|
from ..deviceconfig import DeviceConfig
|
||||||
from ..exceptions import (
|
from ..exceptions import (
|
||||||
SMART_RETRYABLE_ERRORS,
|
SMART_RETRYABLE_ERRORS,
|
||||||
SMART_TIMEOUT_ERRORS,
|
KasaException,
|
||||||
SmartDeviceException,
|
|
||||||
SmartErrorCode,
|
SmartErrorCode,
|
||||||
)
|
)
|
||||||
from ..smartprotocol import _ChildProtocolWrapper
|
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
|
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)
|
await dummy_protocol.query(DUMMY_QUERY, retry_count=2)
|
||||||
|
|
||||||
if error_code in chain(SMART_TIMEOUT_ERRORS, SMART_RETRYABLE_ERRORS):
|
expected_calls = 3 if error_code in SMART_RETRYABLE_ERRORS else 1
|
||||||
expected_calls = 3
|
|
||||||
else:
|
|
||||||
expected_calls = 1
|
|
||||||
assert send_mock.call_count == expected_calls
|
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}}}
|
mock_response = {"error_code": 0, "result": {"responseData": {"error_code": -1001}}}
|
||||||
|
|
||||||
mocker.patch.object(wrapped_protocol._transport, "send", return_value=mock_response)
|
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)
|
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)
|
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)
|
await dummy_protocol.query(DUMMY_QUERY)
|
||||||
|
@ -2,7 +2,7 @@ from datetime import datetime
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from kasa import SmartDeviceException
|
from kasa import KasaException
|
||||||
from kasa.iot import IotStrip
|
from kasa.iot import IotStrip
|
||||||
|
|
||||||
from .conftest import handle_turn_on, strip, turn_on
|
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
|
name = dev.children[0].alias
|
||||||
assert dev.get_plug_by_name(name) == dev.children[0] # type: ignore[arg-type]
|
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")
|
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):
|
async def test_get_plug_by_index(dev: IotStrip):
|
||||||
assert dev.get_plug_by_index(0) == dev.children[0]
|
assert dev.get_plug_by_index(0) == dev.children[0]
|
||||||
|
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
dev.get_plug_by_index(-1)
|
dev.get_plug_by_index(-1)
|
||||||
|
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(KasaException):
|
||||||
dev.get_plug_by_index(len(dev.children))
|
dev.get_plug_by_index(len(dev.children))
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ from typing import Dict, Generator, Optional
|
|||||||
from async_timeout import timeout as asyncio_timeout
|
from async_timeout import timeout as asyncio_timeout
|
||||||
|
|
||||||
from .deviceconfig import DeviceConfig
|
from .deviceconfig import DeviceConfig
|
||||||
from .exceptions import RetryableException, SmartDeviceException
|
from .exceptions import KasaException, _RetryableError
|
||||||
from .json import loads as json_loads
|
from .json import loads as json_loads
|
||||||
from .protocol import BaseTransport
|
from .protocol import BaseTransport
|
||||||
|
|
||||||
@ -129,24 +129,24 @@ class XorTransport(BaseTransport):
|
|||||||
await self._connect(self._timeout)
|
await self._connect(self._timeout)
|
||||||
except ConnectionRefusedError as ex:
|
except ConnectionRefusedError as ex:
|
||||||
await self.reset()
|
await self.reset()
|
||||||
raise SmartDeviceException(
|
raise KasaException(
|
||||||
f"Unable to connect to the device: {self._host}:{self._port}: {ex}"
|
f"Unable to connect to the device: {self._host}:{self._port}: {ex}"
|
||||||
) from ex
|
) from ex
|
||||||
except OSError as ex:
|
except OSError as ex:
|
||||||
await self.reset()
|
await self.reset()
|
||||||
if ex.errno in _NO_RETRY_ERRORS:
|
if ex.errno in _NO_RETRY_ERRORS:
|
||||||
raise SmartDeviceException(
|
raise KasaException(
|
||||||
f"Unable to connect to the device:"
|
f"Unable to connect to the device:"
|
||||||
f" {self._host}:{self._port}: {ex}"
|
f" {self._host}:{self._port}: {ex}"
|
||||||
) from ex
|
) from ex
|
||||||
else:
|
else:
|
||||||
raise RetryableException(
|
raise _RetryableError(
|
||||||
f"Unable to connect to the device:"
|
f"Unable to connect to the device:"
|
||||||
f" {self._host}:{self._port}: {ex}"
|
f" {self._host}:{self._port}: {ex}"
|
||||||
) from ex
|
) from ex
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
await self.reset()
|
await self.reset()
|
||||||
raise RetryableException(
|
raise _RetryableError(
|
||||||
f"Unable to connect to the device:" f" {self._host}:{self._port}: {ex}"
|
f"Unable to connect to the device:" f" {self._host}:{self._port}: {ex}"
|
||||||
) from ex
|
) from ex
|
||||||
except BaseException:
|
except BaseException:
|
||||||
@ -162,7 +162,7 @@ class XorTransport(BaseTransport):
|
|||||||
return await self._execute_send(request)
|
return await self._execute_send(request)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
await self.reset()
|
await self.reset()
|
||||||
raise RetryableException(
|
raise _RetryableError(
|
||||||
f"Unable to query the device {self._host}:{self._port}: {ex}"
|
f"Unable to query the device {self._host}:{self._port}: {ex}"
|
||||||
) from ex
|
) from ex
|
||||||
except BaseException:
|
except BaseException:
|
||||||
|
Loading…
Reference in New Issue
Block a user