Skip to content

Commit

Permalink
Clean up raised library exceptions
Browse files Browse the repository at this point in the history
This commit removes all unnecessary custom classes in favor of using standard python exceptions or DeviceExceptions:
* Invalid input values are now reported using TypeErrors and ValueErrors
* UnsupportedFeatureException gets raised if the device does not support the wanted feature

Most of of the downstream users are hopefully catching DeviceExceptions for error handling so this change requires no further action.
  • Loading branch information
rytilahti committed Oct 24, 2022
1 parent 8270db5 commit 81b3384
Show file tree
Hide file tree
Showing 84 changed files with 501 additions and 723 deletions.
3 changes: 1 addition & 2 deletions miio/__init__.py
Expand Up @@ -9,7 +9,7 @@

from miio.device import Device
from miio.devicestatus import DeviceStatus
from miio.exceptions import DeviceError, DeviceException
from miio.exceptions import DeviceError, DeviceException, UnsupportedFeatureException
from miio.miot_device import MiotDevice
from miio.deviceinfo import DeviceInfo

Expand Down Expand Up @@ -69,7 +69,6 @@
Pro2Vacuum,
RoborockVacuum,
RoidmiVacuumMiot,
VacuumException,
ViomiVacuum,
)
from miio.integrations.vacuum.roborock.vacuumcontainers import (
Expand Down
13 changes: 2 additions & 11 deletions miio/airconditioner_miot.py
Expand Up @@ -6,7 +6,6 @@
import click

from .click_common import EnumType, command, format_output
from .exceptions import DeviceException
from .miot_device import DeviceStatus, MiotDevice

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -59,10 +58,6 @@
]


class AirConditionerMiotException(DeviceException):
pass


class CleaningStatus(DeviceStatus):
def __init__(self, status: str):
"""Auto clean mode indicator.
Expand Down Expand Up @@ -349,9 +344,7 @@ def set_target_temperature(self, target_temperature: float):
or target_temperature > 31.0
or target_temperature % 0.5 != 0
):
raise AirConditionerMiotException(
"Invalid target temperature: %s" % target_temperature
)
raise ValueError("Invalid target temperature: %s" % target_temperature)
return self.set_property("target_temperature", target_temperature)

@command(
Expand Down Expand Up @@ -443,9 +436,7 @@ def set_buzzer(self, buzzer: bool):
def set_fan_speed_percent(self, fan_speed_percent):
"""Set fan speed in percent, should be between 1 to 100 or 101(auto)."""
if fan_speed_percent < 1 or fan_speed_percent > 101:
raise AirConditionerMiotException(
"Invalid fan percent: %s" % fan_speed_percent
)
raise ValueError("Invalid fan percent: %s" % fan_speed_percent)
return self.set_property("fan_speed_percent", fan_speed_percent)

@command(
Expand Down
15 changes: 3 additions & 12 deletions miio/airconditioningcompanion.py
Expand Up @@ -6,7 +6,6 @@

from .click_common import EnumType, command, format_output
from .device import Device, DeviceStatus
from .exceptions import DeviceException

_LOGGER = logging.getLogger(__name__)

Expand All @@ -17,10 +16,6 @@
MODELS_SUPPORTED = [MODEL_ACPARTNER_V1, MODEL_ACPARTNER_V2, MODEL_ACPARTNER_V3]


class AirConditioningCompanionException(DeviceException):
pass


class OperationMode(enum.Enum):
Heat = 0
Cool = 1
Expand Down Expand Up @@ -313,19 +308,15 @@ def send_ir_code(self, model: str, code: str, slot: int = 0):
try:
model_bytes = bytes.fromhex(model)
except ValueError:
raise AirConditioningCompanionException(
"Invalid model. A hexadecimal string must be provided"
)
raise ValueError("Invalid model. A hexadecimal string must be provided")

try:
code_bytes = bytes.fromhex(code)
except ValueError:
raise AirConditioningCompanionException(
"Invalid code. A hexadecimal string must be provided"
)
raise ValueError("Invalid code. A hexadecimal string must be provided")

if slot < 0 or slot > 134:
raise AirConditioningCompanionException("Invalid slot: %s" % slot)
raise ValueError("Invalid slot: %s" % slot)

slot_bytes = bytes([121 + slot])

Expand Down
5 changes: 0 additions & 5 deletions miio/airconditioningcompanionMCN.py
Expand Up @@ -5,17 +5,12 @@

from .click_common import command, format_output
from .device import Device, DeviceStatus
from .exceptions import DeviceException

_LOGGER = logging.getLogger(__name__)

MODEL_ACPARTNER_MCN02 = "lumi.acpartner.mcn02"


class AirConditioningCompanionException(DeviceException):
pass


class OperationMode(enum.Enum):
Cool = "cool"
Heat = "heat"
Expand Down
10 changes: 2 additions & 8 deletions miio/airdehumidifier.py
Expand Up @@ -33,10 +33,6 @@
}


class AirDehumidifierException(DeviceException):
pass


class OperationMode(enum.Enum):
On = "on"
Auto = "auto"
Expand Down Expand Up @@ -227,7 +223,7 @@ def set_fan_speed(self, fan_speed: FanSpeed):
return self.send("set_fan_level", [fan_speed.value])
except DeviceError as ex:
if ex.code == -10000:
raise AirDehumidifierException(
raise DeviceException(
"Unable to set fan speed, this can happen if device is turned off."
) from ex

Expand Down Expand Up @@ -279,8 +275,6 @@ def set_child_lock(self, lock: bool):
def set_target_humidity(self, humidity: int):
"""Set the auto target humidity."""
if humidity not in [40, 50, 60]:
raise AirDehumidifierException(
"Invalid auto target humidity: %s" % humidity
)
raise ValueError("Invalid auto target humidity: %s" % humidity)

return self.send("set_auto", [humidity])
7 changes: 1 addition & 6 deletions miio/airqualitymonitor.py
Expand Up @@ -6,7 +6,6 @@

from .click_common import command, format_output
from .device import Device, DeviceStatus
from .exceptions import DeviceException

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -37,10 +36,6 @@
}


class AirQualityMonitorException(DeviceException):
pass


class AirQualityMonitorStatus(DeviceStatus):
"""Container of air quality monitor status."""

Expand Down Expand Up @@ -275,6 +270,6 @@ def set_night_time(
end = end_hour * 3600 + end_minute * 60

if begin < 0 or begin > 86399 or end < 0 or end > 86399:
AirQualityMonitorException("Begin or/and end time invalid.")
ValueError("Begin or/and end time invalid.")

self.send("set_night_time", [begin, end])
11 changes: 3 additions & 8 deletions miio/airqualitymonitor_miot.py
Expand Up @@ -4,7 +4,6 @@
import click

from .click_common import command, format_output
from .exceptions import DeviceException
from .miot_device import DeviceStatus, MiotDevice

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -47,10 +46,6 @@
}


class AirQualityMonitorMiotException(DeviceException):
pass


class ChargingState(enum.Enum):
Unplugged = 0 # Not mentioned in the spec
Charging = 1
Expand Down Expand Up @@ -205,7 +200,7 @@ def status(self) -> AirQualityMonitorCGDN1Status:
def set_monitoring_frequency_duration(self, duration):
"""Set monitoring frequency."""
if duration < 0 or duration > 600:
raise AirQualityMonitorMiotException(
raise ValueError(
"Invalid duration: %s. Must be between 0 and 600" % duration
)
return self.set_property("monitoring_frequency", duration)
Expand All @@ -217,7 +212,7 @@ def set_monitoring_frequency_duration(self, duration):
def set_device_off_duration(self, duration):
"""Set device off duration."""
if duration < 0 or duration > 60:
raise AirQualityMonitorMiotException(
raise ValueError(
"Invalid duration: %s. Must be between 0 and 60" % duration
)
return self.set_property("device_off", duration)
Expand All @@ -229,7 +224,7 @@ def set_device_off_duration(self, duration):
def set_screen_off_duration(self, duration):
"""Set screen off duration."""
if duration < 0 or duration > 300:
raise AirQualityMonitorMiotException(
raise ValueError(
"Invalid duration: %s. Must be between 0 and 300" % duration
)
return self.set_property("screen_off", duration)
Expand Down
9 changes: 2 additions & 7 deletions miio/aqaracamera.py
Expand Up @@ -16,15 +16,10 @@

from .click_common import command, format_output
from .device import Device, DeviceStatus
from .exceptions import DeviceException

_LOGGER = logging.getLogger(__name__)


class CameraException(DeviceException):
pass


@attr.s
class CameraOffset:
"""Container for camera offset data."""
Expand Down Expand Up @@ -255,7 +250,7 @@ def fullstop_off(self):
def pair(self, timeout: int):
"""Start (or stop with "0") pairing."""
if timeout < 0:
raise CameraException("Invalid timeout: %s" % timeout)
raise ValueError("Invalid timeout: %s" % timeout)

return self.send("start_zigbee_join", [timeout])

Expand Down Expand Up @@ -292,7 +287,7 @@ def arm_status(self):
def set_alarm_volume(self, volume):
"""Set alarm volume."""
if volume < 0 or volume > 100:
raise CameraException("Volume has to be [0,100], was %s" % volume)
raise ValueError("Volume has to be [0,100], was %s" % volume)
return self.send("set_alarming_volume", [volume])[0] == "ok"

@command(click.argument("sound_id", type=str, required=False, default=None))
Expand Down
19 changes: 7 additions & 12 deletions miio/chuangmi_ir.py
Expand Up @@ -21,11 +21,6 @@

from .click_common import command, format_output
from .device import Device
from .exceptions import DeviceException


class ChuangmiIrException(DeviceException):
pass


class ChuangmiIr(Device):
Expand All @@ -50,7 +45,7 @@ def learn(self, key: int = 1):
"""

if key < 1 or key > 1000000:
raise ChuangmiIrException("Invalid storage slot.")
raise ValueError("Invalid storage slot.")
return self.send("miIO.ir_learn", {"key": str(key)})

@command(
Expand All @@ -73,7 +68,7 @@ def read(self, key: int = 1):
"""

if key < 1 or key > 1000000:
raise ChuangmiIrException("Invalid storage slot.")
raise ValueError("Invalid storage slot.")
return self.send("miIO.ir_read", {"key": str(key)})

def play_raw(self, command: str, frequency: int = 38400, length: int = -1):
Expand Down Expand Up @@ -110,12 +105,12 @@ def pronto_to_raw(cls, pronto: str, repeats: int = 1) -> Tuple[str, int]:
:param int repeats: Number of extra signal repeats.
"""
if repeats < 0:
raise ChuangmiIrException("Invalid repeats value")
raise ValueError("Invalid repeats value")

try:
pronto_data = Pronto.parse(bytearray.fromhex(pronto))
except Exception as ex:
raise ChuangmiIrException("Invalid Pronto command") from ex
raise ValueError("Invalid Pronto command") from ex

if len(pronto_data.intro) == 0:
repeats += 1
Expand Down Expand Up @@ -161,10 +156,10 @@ def play(self, command: str):

arg_types = [int, int]
if len(command_args) > len(arg_types):
raise ChuangmiIrException("Invalid command arguments count")
raise ValueError("Invalid command arguments count")

if command_type not in ["raw", "pronto"]:
raise ChuangmiIrException("Invalid command type")
raise ValueError("Invalid command type")

play_method: Callable
if command_type == "raw":
Expand All @@ -176,7 +171,7 @@ def play(self, command: str):
try:
converted_command_args = [t(v) for v, t in zip(command_args, arg_types)]
except Exception as ex:
raise ChuangmiIrException("Invalid command arguments") from ex
raise ValueError("Invalid command arguments") from ex

return play_method(command, *converted_command_args)

Expand Down
2 changes: 1 addition & 1 deletion miio/click_common.py
Expand Up @@ -49,7 +49,7 @@ class ExceptionHandlerGroup(click.Group):
def __call__(self, *args, **kwargs):
try:
return self.main(*args, **kwargs)
except miio.DeviceException as ex:
except (ValueError, miio.DeviceException) as ex:
_LOGGER.debug("Exception: %s", ex, exc_info=True)
click.echo(click.style("Error: %s" % ex, fg="red", bold=True))

Expand Down
6 changes: 2 additions & 4 deletions miio/cloud.py
Expand Up @@ -5,6 +5,8 @@
import attr
import click

from miio.exceptions import CloudException

_LOGGER = logging.getLogger(__name__)

if TYPE_CHECKING:
Expand All @@ -13,10 +15,6 @@
AVAILABLE_LOCALES = ["cn", "de", "i2", "ru", "sg", "us"]


class CloudException(Exception):
"""Exception raised for cloud connectivity issues."""


@attr.s(auto_attribs=True)
class CloudDeviceInfo:
"""Container for device data from the cloud.
Expand Down
9 changes: 2 additions & 7 deletions miio/cooker.py
Expand Up @@ -9,7 +9,6 @@

from .click_common import command, format_output
from .device import Device, DeviceStatus
from .exceptions import DeviceException

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -71,10 +70,6 @@
}


class CookerException(DeviceException):
pass


class OperationMode(enum.Enum):
# Observed
Running = "running"
Expand Down Expand Up @@ -644,7 +639,7 @@ def status(self) -> CookerStatus:
def start(self, profile: str):
"""Start cooking a profile."""
if not self._validate_profile(profile):
raise CookerException("Invalid cooking profile: %s" % profile)
raise ValueError("Invalid cooking profile: %s" % profile)

self.send("set_start", [profile])

Expand Down Expand Up @@ -691,7 +686,7 @@ def set_interaction(self, settings: CookerSettings, timeouts: InteractionTimeout
def set_menu(self, profile: str):
"""Select one of the default(?) cooking profiles."""
if not self._validate_profile(profile):
raise CookerException("Invalid cooking profile: %s" % profile)
raise ValueError("Invalid cooking profile: %s" % profile)

self.send("set_menu", [profile])

Expand Down

0 comments on commit 81b3384

Please sign in to comment.