Skip to content

Commit

Permalink
Move DeviceStatus and its helpers to separate class
Browse files Browse the repository at this point in the history
  • Loading branch information
rytilahti committed Aug 13, 2022
1 parent 4f712f1 commit 7b645f7
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 102 deletions.
103 changes: 2 additions & 101 deletions miio/device.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,7 @@
import inspect
import logging
import warnings
from enum import Enum
from pprint import pformat as pf
from typing import ( # noqa: F401
Any,
Dict,
List,
Optional,
Union,
get_args,
get_origin,
get_type_hints,
)
from typing import Any, Dict, List, Optional, Union # noqa: F401

import click

Expand All @@ -24,6 +13,7 @@
SwitchDescriptor,
)
from .deviceinfo import DeviceInfo
from .devicestatus import DeviceStatus
from .exceptions import DeviceInfoUnavailableException, PayloadDecodeException
from .miioprotocol import MiIOProtocol

Expand All @@ -37,95 +27,6 @@ class UpdateState(Enum):
Idle = "idle"


def sensor(*, name: str, icon: str = "mdi:sensor", unit: str = "", **kwargs):
"""Decorator helper to create sensordescriptors for status classes.
The information can be used by users of the library to programatically find out what
types of sensors are available for the device.
"""

def decorator_sensor(func):
property_name = func.__name__

def _sensor_type_for_return_type(func):
rtype = get_type_hints(func).get("return")
if get_origin(rtype) is Union: # Unwrap Optional[]
rtype, _ = get_args(rtype)

if rtype == bool:
return "binary"
else:
return "sensor"

sensor_type = _sensor_type_for_return_type(func)
descriptor = SensorDescriptor(
id=str(property_name),
property=str(property_name),
name=name,
icon=icon,
unit=unit,
type=sensor_type,
**kwargs,
)
func._sensor = descriptor

return func

return decorator_sensor


class _StatusMeta(type):
"""Meta class to provide introspectable properties."""

def __new__(metacls, name, bases, namespace, **kwargs):
cls = super().__new__(metacls, name, bases, namespace)
cls._sensors = []
for n in namespace:
prop = getattr(namespace[n], "fget", None)
if prop:
sensor = getattr(prop, "_sensor", None)
if sensor:
_LOGGER.debug(f"Found sensor: {sensor} for {name}")
cls._sensors.append(sensor)

return cls


class DeviceStatus(metaclass=_StatusMeta):
"""Base class for status containers.
All status container classes should inherit from this class:
* This class allows downstream users to access the available information in an
introspectable way.
* The __repr__ implementation returns all defined properties and their values.
"""

def __repr__(self):
props = inspect.getmembers(self.__class__, lambda o: isinstance(o, property))

s = f"<{self.__class__.__name__}"
for prop_tuple in props:
name, prop = prop_tuple
try:
# ignore deprecation warnings
with warnings.catch_warnings(record=True):
prop_value = prop.fget(self)
except Exception as ex:
prop_value = ex.__class__.__name__

s += f" {name}={prop_value}"
s += ">"
return s

def sensors(self):
"""Return the list of sensors exposed by the status container.
You can use @sensor decorator to define sensors inside your status class.
"""
return self._sensors


class Device(metaclass=DeviceGroupMeta):
"""Base class for all device implementations.
Expand Down
97 changes: 97 additions & 0 deletions miio/devicestatus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import inspect
import logging
import warnings
from typing import Union, get_args, get_origin, get_type_hints

from .descriptors import SensorDescriptor

_LOGGER = logging.getLogger(__name__)


class _StatusMeta(type):
"""Meta class to provide introspectable properties."""

def __new__(metacls, name, bases, namespace, **kwargs):
cls = super().__new__(metacls, name, bases, namespace)
cls._sensors = []
for n in namespace:
prop = getattr(namespace[n], "fget", None)
if prop:
sensor = getattr(prop, "_sensor", None)
if sensor:
_LOGGER.debug(f"Found sensor: {sensor} for {name}")
cls._sensors.append(sensor)

return cls


class DeviceStatus(metaclass=_StatusMeta):
"""Base class for status containers.
All status container classes should inherit from this class:
* This class allows downstream users to access the available information in an
introspectable way.
* The __repr__ implementation returns all defined properties and their values.
"""

def __repr__(self):
props = inspect.getmembers(self.__class__, lambda o: isinstance(o, property))

s = f"<{self.__class__.__name__}"
for prop_tuple in props:
name, prop = prop_tuple
try:
# ignore deprecation warnings
with warnings.catch_warnings(record=True):
prop_value = prop.fget(self)
except Exception as ex:
prop_value = ex.__class__.__name__

s += f" {name}={prop_value}"
s += ">"
return s

def sensors(self):
"""Return the list of sensors exposed by the status container.
You can use @sensor decorator to define sensors inside your status class.
"""
return self._sensors


def sensor(*, name: str, icon: str = "mdi:sensor", unit: str = "", **kwargs):
"""Decorator helper to create sensordescriptors for status classes.
The information can be used by users of the library to programatically find out what
types of sensors are available for the device.
"""

def decorator_sensor(func):
property_name = func.__name__

def _sensor_type_for_return_type(func):
rtype = get_type_hints(func).get("return")
if get_origin(rtype) is Union: # Unwrap Optional[]
rtype, _ = get_args(rtype)

if rtype == bool:
return "binary"
else:
return "sensor"

sensor_type = _sensor_type_for_return_type(func)
descriptor = SensorDescriptor(
id=str(property_name),
property=str(property_name),
name=name,
icon=icon,
unit=unit,
type=sensor_type,
**kwargs,
)
func._sensor = descriptor

return func

return decorator_sensor
3 changes: 2 additions & 1 deletion miio/powerstrip.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
import click

from .click_common import EnumType, command, format_output
from .device import Device, DeviceStatus, sensor
from .device import Device
from .devicestatus import DeviceStatus, sensor
from .exceptions import DeviceException
from .utils import deprecated

Expand Down

0 comments on commit 7b645f7

Please sign in to comment.