From 6db070db1c12b1378fb93b54bc0f4f6c7f5dbbb4 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Tue, 26 Oct 2021 00:48:05 +0200 Subject: [PATCH] - Fixed `switchbot.status` to handle virtual devices - Fixed StrippedString schema field serialize handler - Fixed rendering of lists in documentation schemas --- CHANGELOG.md | 6 +++++ docs/source/_ext/sphinx_marshmallow.py | 7 +++++ platypush/plugins/switchbot/__init__.py | 23 +++++++++++----- platypush/schemas/__init__.py | 35 ++++++++++++++++++++++--- platypush/schemas/switchbot.py | 10 +++---- 5 files changed, 66 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b3e30751..57b1d7bed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ 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. +## [Unreleased] + +### Fixed + +- Fixed `switchbot.status` method in case of virtual devices. + ## [0.22.4] - 2021-10-19 ### Added diff --git a/docs/source/_ext/sphinx_marshmallow.py b/docs/source/_ext/sphinx_marshmallow.py index b08e5c549..ababdd459 100644 --- a/docs/source/_ext/sphinx_marshmallow.py +++ b/docs/source/_ext/sphinx_marshmallow.py @@ -38,6 +38,13 @@ class SchemaDirective(Directive): return bool(randint(0, 1)) if isinstance(field, fields.URL): 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): ret = { name: cls._get_field_value(f) diff --git a/platypush/plugins/switchbot/__init__.py b/platypush/plugins/switchbot/__init__.py index d1279a21d..6545f8873 100644 --- a/platypush/plugins/switchbot/__init__.py +++ b/platypush/plugins/switchbot/__init__.py @@ -96,10 +96,15 @@ class SwitchbotPlugin(SwitchPlugin): 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: - res = self._run(method, *args, device=device, **kwargs) - q.put(DeviceStatusSchema().dump(res)) + if method == 'get' and args and args[0] == 'status' and device and device.get('is_virtual'): + res = schema.load(device) + else: + res = self._run(method, *args, device=device, **kwargs) + + q.put(schema.dump(res)) except Exception as e: self.logger.exception(e) q.put(e) @@ -115,15 +120,21 @@ class SwitchbotPlugin(SwitchPlugin): # noinspection PyUnresolvedReferences devices = self.devices().output if device: + device_info = self._get_device(device) + status = {} if device_info['is_virtual'] else self._run('get', 'status', device=device_info) return { - **device, - **self._run('get', 'status', device=self._get_device(device)), + **device_info, + **status, } devices_by_id = {dev['id']: dev for dev in devices} queues = [queue.Queue()] * len(devices) 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) ] diff --git a/platypush/schemas/__init__.py b/platypush/schemas/__init__.py index ce6e6d068..c0f4a71bf 100644 --- a/platypush/schemas/__init__.py +++ b/platypush/schemas/__init__.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, date from typing import Optional, Union from dateutil.parser import isoparse @@ -12,6 +12,10 @@ class StrippedString(fields.Function): # lgtm [py/missing-call-to-init] kwargs['deserialize'] = self._strip 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 def _strip(value: str): return value.strip() @@ -34,9 +38,32 @@ class DateTime(fields.Function): 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: return - if isinstance(dt, datetime): + if isinstance(dt, datetime) or isinstance(dt, date): return dt - return isoparse(dt) + + try: + dt = float(dt) + return datetime.fromtimestamp(dt) + except (TypeError, ValueError): + return isoparse(dt) diff --git a/platypush/schemas/switchbot.py b/platypush/schemas/switchbot.py index 22fe1d3f1..c4037205c 100644 --- a/platypush/schemas/switchbot.py +++ b/platypush/schemas/switchbot.py @@ -1,6 +1,5 @@ from marshmallow import fields from marshmallow.schema import Schema -from marshmallow.validate import OneOf device_types = [ @@ -26,6 +25,7 @@ device_types = [ 'Speaker', 'Water Heater', 'Vacuum Cleaner', + 'Remote', 'Others', ] @@ -51,12 +51,12 @@ class DeviceSchema(Schema): id = fields.String(attribute='deviceId', required=True, metadata=dict(description='Device unique ID')) name = fields.String(attribute='deviceName', metadata=dict(description='Device name')) device_type = fields.String( - attribute='deviceType', validate=OneOf(device_types), - metadata=dict(description=f'Supported types: [{", ".join(device_types)}]') + attribute='deviceType', + metadata=dict(description=f'Default types: [{", ".join(device_types)}]') ) remote_type = fields.String( - attribute='remoteType', validate=OneOf(remote_types), - metadata=dict(description=f'Supported types: [{", ".join(remote_types)}]') + attribute='remoteType', + metadata=dict(description=f'Default types: [{", ".join(remote_types)}]') ) hub_id = fields.String(attribute='hubDeviceId', metadata=dict(description='Parent hub device unique ID')) cloud_service_enabled = fields.Boolean(