Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revise error handling to be more consistent for library users #180

Merged
merged 5 commits into from
Jan 27, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 4 additions & 23 deletions miio/ceil_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@
import logging
import click
import sys
import ipaddress

if sys.version_info < (3, 4):
print("To use this script you need python 3.4 or newer, got %s" %
sys.version_info)
sys.exit(1)

from miio.click_common import (ExceptionHandlerGroup, validate_ip,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

module level import not at top of file

validate_token)
import miio # noqa: E402


_LOGGER = logging.getLogger(__name__)
pass_dev = click.make_pass_decorator(miio.Ceil)

Expand All @@ -36,22 +32,7 @@ def validate_scene(ctx, param, value):
return value


def validate_ip(ctx, param, value):
try:
ipaddress.ip_address(value)
return value
except ValueError as ex:
raise click.BadParameter("Invalid IP: %s" % ex)


def validate_token(ctx, param, value):
token_len = len(value)
if token_len != 32:
raise click.BadParameter("Token length != 32 chars: %s" % token_len)
return value


@click.group(invoke_without_command=True)
@click.group(invoke_without_command=True, cls=ExceptionHandlerGroup)
@click.option('--ip', envvar="DEVICE_IP", callback=validate_ip)
@click.option('--token', envvar="DEVICE_TOKEN", callback=validate_token)
@click.option('-d', '--debug', default=False, count=True)
Expand Down
41 changes: 41 additions & 0 deletions miio/click_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Click commons.

This file contains common functions for cli tools.
"""
import sys
if sys.version_info < (3, 4):
print("To use this script you need python 3.4 or newer, got %s" %
sys.version_info)
sys.exit(1)

import click

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

module level import not at top of file

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

module level import not at top of file

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The printout has to be before trying to import miio as otherwise it'll bail out with syntax errors.

import ipaddress

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

module level import not at top of file

import miio

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

module level import not at top of file

import logging

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

module level import not at top of file



_LOGGER = logging.getLogger(__name__)


def validate_ip(ctx, param, value):
try:
ipaddress.ip_address(value)
return value
except ValueError as ex:
raise click.BadParameter("Invalid IP: %s" % ex)


def validate_token(ctx, param, value):
token_len = len(value)
if token_len != 32:
raise click.BadParameter("Token length != 32 chars: %s" % token_len)
return value


class ExceptionHandlerGroup(click.Group):
def __call__(self, *args, **kwargs):
try:
return self.main(*args, **kwargs)
except miio.DeviceException as ex:
_LOGGER.debug("Exception: %s", ex, exc_info=True)
click.echo(click.style("Error: %s" % ex, fg='red', bold=True))
15 changes: 14 additions & 1 deletion miio/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import datetime
import socket
import logging
import construct
from typing import Any, List, Optional # noqa: F401

from .protocol import Message
Expand All @@ -14,6 +15,11 @@ class DeviceException(Exception):
pass


class DeviceError(DeviceException):
"""Exception communicating an error delivered by the target device."""
pass


class DeviceInfo:
"""Container of miIO device information.
Hardware properties such as device model, MAC address, memory information,
Expand Down Expand Up @@ -242,18 +248,25 @@ def send(self, command: str, parameters: Any=None, retry_count=3) -> Any:
m.header.value.ts,
m.data.value["id"],
m.data.value)
if "error" in m.data.value:
raise DeviceError(m.data.value["error"])

try:
return m.data.value["result"]
except KeyError:
return m.data.value
except construct.core.ChecksumError as ex:
raise DeviceException("Got checksum error which indicates use "
"of an invalid token. "
"Please check your token!") from ex
except OSError as ex:
_LOGGER.error("Got error when receiving: %s", ex)
if retry_count > 0:
_LOGGER.warning("Retrying with incremented id, "
"retries left: %s", retry_count)
self.__id += 100
return self.send(command, parameters, retry_count - 1)
raise DeviceException from ex
raise DeviceException("No response from the device") from ex

def raw_command(self, cmd, params):
"""Send a raw command to the device.
Expand Down
27 changes: 4 additions & 23 deletions miio/philips_eyecare_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@
import logging
import click
import sys
import ipaddress

if sys.version_info < (3, 4):
print("To use this script you need python 3.4 or newer, got %s" %
sys.version_info)
sys.exit(1)

from miio.click_common import (ExceptionHandlerGroup, validate_ip,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

module level import not at top of file

validate_token)
import miio # noqa: E402


_LOGGER = logging.getLogger(__name__)
pass_dev = click.make_pass_decorator(miio.PhilipsEyecare)

Expand All @@ -36,22 +32,7 @@ def validate_scene(ctx, param, value):
return value


def validate_ip(ctx, param, value):
try:
ipaddress.ip_address(value)
return value
except ValueError as ex:
raise click.BadParameter("Invalid IP: %s" % ex)


def validate_token(ctx, param, value):
token_len = len(value)
if token_len != 32:
raise click.BadParameter("Token length != 32 chars: %s" % token_len)
return value


@click.group(invoke_without_command=True)
@click.group(invoke_without_command=True, cls=ExceptionHandlerGroup)
@click.option('--ip', envvar="DEVICE_IP", callback=validate_ip)
@click.option('--token', envvar="DEVICE_TOKEN", callback=validate_token)
@click.option('-d', '--debug', default=False, count=True)
Expand Down
27 changes: 4 additions & 23 deletions miio/plug_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,17 @@
import click
import ast
import sys
import ipaddress
from typing import Any # noqa: F401

if sys.version_info < (3, 4):
print("To use this script you need python 3.4 or newer, got %s" %
sys.version_info)
sys.exit(1)

from miio.click_common import (ExceptionHandlerGroup, validate_ip,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

module level import not at top of file

validate_token)
import miio # noqa: E402


_LOGGER = logging.getLogger(__name__)
pass_dev = click.make_pass_decorator(miio.Plug)


def validate_ip(ctx, param, value):
try:
ipaddress.ip_address(value)
return value
except ValueError as ex:
raise click.BadParameter("Invalid IP: %s" % ex)


def validate_token(ctx, param, value):
token_len = len(value)
if token_len != 32:
raise click.BadParameter("Token length != 32 chars: %s" % token_len)
return value


@click.group(invoke_without_command=True)
@click.group(invoke_without_command=True, cls=ExceptionHandlerGroup)
@click.option('--ip', envvar="DEVICE_IP", callback=validate_ip)
@click.option('--token', envvar="DEVICE_TOKEN", callback=validate_token)
@click.option('-d', '--debug', default=False, count=True)
Expand Down
31 changes: 3 additions & 28 deletions miio/vacuum_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,20 @@
import ast
import sys
import json
import ipaddress
import time
import pathlib
from appdirs import user_cache_dir
from pprint import pformat as pf
from typing import Any # noqa: F401


if sys.version_info < (3, 4):
print("To use this script you need python 3.4 or newer, got %s" %
sys.version_info)
sys.exit(1)

from miio.click_common import (ExceptionHandlerGroup, validate_ip,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

module level import not at top of file

validate_token)
import miio # noqa: E402

_LOGGER = logging.getLogger(__name__)
pass_dev = click.make_pass_decorator(miio.Device, ensure=True)


def validate_ip(ctx, param, value):
if value is None:
return value
try:
ipaddress.ip_address(value)
return value
except ValueError as ex:
raise click.BadParameter("Invalid IP: %s" % ex)


def validate_token(ctx, param, value):
if value is None:
return value
token_len = len(value)
if token_len != 32:
raise click.BadParameter("Token length != 32 chars: %s" % token_len)
return value


@click.group(invoke_without_command=True)
@click.group(invoke_without_command=True, cls=ExceptionHandlerGroup)
@click.option('--ip', envvar="MIROBO_IP", callback=validate_ip)
@click.option('--token', envvar="MIROBO_TOKEN", callback=validate_token)
@click.option('-d', '--debug', default=False, count=True)
Expand Down