- Fixed `switchbot.status` to handle virtual devices

- Fixed StrippedString schema field serialize handler

- Fixed rendering of lists in documentation schemas
This commit is contained in:
Fabio Manganiello 2021-10-26 00:48:05 +02:00
parent 952a2a9379
commit 6db070db1c
Signed by: blacklight
GPG Key ID: D90FBA7F76362774
5 changed files with 66 additions and 15 deletions

View File

@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
Given the high speed of development in the first phase, changes are being reported only starting from v0.20.2. Given the high speed of development in the first phase, changes are being reported only starting from v0.20.2.
## [Unreleased]
### Fixed
- Fixed `switchbot.status` method in case of virtual devices.
## [0.22.4] - 2021-10-19 ## [0.22.4] - 2021-10-19
### Added ### Added

View File

@ -38,6 +38,13 @@ class SchemaDirective(Directive):
return bool(randint(0, 1)) return bool(randint(0, 1))
if isinstance(field, fields.URL): if isinstance(field, fields.URL):
return 'https://example.org' return 'https://example.org'
if isinstance(field, fields.List):
return [cls._get_field_value(field.inner)]
if isinstance(field, fields.Dict):
return {
cls._get_field_value(field.key_field) if field.key_field else 'key':
cls._get_field_value(field.value_field) if field.value_field else 'value'
}
if isinstance(field, fields.Nested): if isinstance(field, fields.Nested):
ret = { ret = {
name: cls._get_field_value(f) name: cls._get_field_value(f)

View File

@ -96,10 +96,15 @@ class SwitchbotPlugin(SwitchPlugin):
return devices return devices
def _worker(self, q: queue.Queue, method: str = 'get', *args, device=None, **kwargs): def _worker(self, q: queue.Queue, method: str = 'get', *args, device: Optional[dict] = None, **kwargs):
schema = DeviceStatusSchema()
try: try:
res = self._run(method, *args, device=device, **kwargs) if method == 'get' and args and args[0] == 'status' and device and device.get('is_virtual'):
q.put(DeviceStatusSchema().dump(res)) res = schema.load(device)
else:
res = self._run(method, *args, device=device, **kwargs)
q.put(schema.dump(res))
except Exception as e: except Exception as e:
self.logger.exception(e) self.logger.exception(e)
q.put(e) q.put(e)
@ -115,15 +120,21 @@ class SwitchbotPlugin(SwitchPlugin):
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
devices = self.devices().output devices = self.devices().output
if device: if device:
device_info = self._get_device(device)
status = {} if device_info['is_virtual'] else self._run('get', 'status', device=device_info)
return { return {
**device, **device_info,
**self._run('get', 'status', device=self._get_device(device)), **status,
} }
devices_by_id = {dev['id']: dev for dev in devices} devices_by_id = {dev['id']: dev for dev in devices}
queues = [queue.Queue()] * len(devices) queues = [queue.Queue()] * len(devices)
workers = [ workers = [
threading.Thread(target=self._worker, args=(queues[i], 'get', 'status'), kwargs={'device': dev}) threading.Thread(
target=self._worker,
args=(queues[i], 'get', 'status'),
kwargs={'device': dev}
)
for i, dev in enumerate(devices) for i, dev in enumerate(devices)
] ]

View File

@ -1,4 +1,4 @@
from datetime import datetime from datetime import datetime, date
from typing import Optional, Union from typing import Optional, Union
from dateutil.parser import isoparse from dateutil.parser import isoparse
@ -12,6 +12,10 @@ class StrippedString(fields.Function): # lgtm [py/missing-call-to-init]
kwargs['deserialize'] = self._strip kwargs['deserialize'] = self._strip
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def _serialize(self, value, attr, obj, **kwargs) -> Optional[str]:
if obj.get(attr) is not None:
return self._strip(obj.get(attr))
@staticmethod @staticmethod
def _strip(value: str): def _strip(value: str):
return value.strip() return value.strip()
@ -34,9 +38,32 @@ class DateTime(fields.Function):
return normalize_datetime(value) return normalize_datetime(value)
def normalize_datetime(dt: Union[str, datetime]) -> Optional[datetime]: class Date(fields.Function):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.metadata = {
'example': date.today().isoformat(),
**(self.metadata or {}),
}
def _serialize(self, value, attr, obj, **kwargs) -> Optional[str]:
value = normalize_datetime(obj.get(attr))
if value:
return date(value.year, value.month, value.day).isoformat()
def _deserialize(self, value, attr, data, **kwargs) -> Optional[date]:
dt = normalize_datetime(value)
return date.fromtimestamp(dt.timestamp())
def normalize_datetime(dt: Union[str, date, datetime]) -> Optional[Union[date, datetime]]:
if not dt: if not dt:
return return
if isinstance(dt, datetime): if isinstance(dt, datetime) or isinstance(dt, date):
return dt return dt
return isoparse(dt)
try:
dt = float(dt)
return datetime.fromtimestamp(dt)
except (TypeError, ValueError):
return isoparse(dt)

View File

@ -1,6 +1,5 @@
from marshmallow import fields from marshmallow import fields
from marshmallow.schema import Schema from marshmallow.schema import Schema
from marshmallow.validate import OneOf
device_types = [ device_types = [
@ -26,6 +25,7 @@ device_types = [
'Speaker', 'Speaker',
'Water Heater', 'Water Heater',
'Vacuum Cleaner', 'Vacuum Cleaner',
'Remote',
'Others', 'Others',
] ]
@ -51,12 +51,12 @@ class DeviceSchema(Schema):
id = fields.String(attribute='deviceId', required=True, metadata=dict(description='Device unique ID')) id = fields.String(attribute='deviceId', required=True, metadata=dict(description='Device unique ID'))
name = fields.String(attribute='deviceName', metadata=dict(description='Device name')) name = fields.String(attribute='deviceName', metadata=dict(description='Device name'))
device_type = fields.String( device_type = fields.String(
attribute='deviceType', validate=OneOf(device_types), attribute='deviceType',
metadata=dict(description=f'Supported types: [{", ".join(device_types)}]') metadata=dict(description=f'Default types: [{", ".join(device_types)}]')
) )
remote_type = fields.String( remote_type = fields.String(
attribute='remoteType', validate=OneOf(remote_types), attribute='remoteType',
metadata=dict(description=f'Supported types: [{", ".join(remote_types)}]') metadata=dict(description=f'Default types: [{", ".join(remote_types)}]')
) )
hub_id = fields.String(attribute='hubDeviceId', metadata=dict(description='Parent hub device unique ID')) hub_id = fields.String(attribute='hubDeviceId', metadata=dict(description='Parent hub device unique ID'))
cloud_service_enabled = fields.Boolean( cloud_service_enabled = fields.Boolean(