diff --git a/platypush/config/__init__.py b/platypush/config/__init__.py index cb559c008a..32eaa52809 100644 --- a/platypush/config/__init__.py +++ b/platypush/config/__init__.py @@ -60,6 +60,7 @@ class Config(object): self._config = self._read_config_file(self._cfgfile) if 'token' in self._config: + self._config['token'] = self._config['token'] self._config['token_hash'] = get_hash(self._config['token']) if 'workdir' not in self._config: diff --git a/platypush/event/hook.py b/platypush/event/hook.py index f728d611ba..d995c01052 100644 --- a/platypush/event/hook.py +++ b/platypush/event/hook.py @@ -86,6 +86,11 @@ class EventAction(Request): if 'target' not in action: action['target'] = action['origin'] + + token = Config.get('token') + if token: + action['token'] = token + return super().build(action) diff --git a/platypush/message/request/__init__.py b/platypush/message/request/__init__.py index 345f5e575a..3ef9f3277c 100644 --- a/platypush/message/request/__init__.py +++ b/platypush/message/request/__init__.py @@ -193,7 +193,7 @@ class Request(Message): raise RuntimeError('Response processed with errors: {}'.format(response)) logger.info('Processed response from plugin {}: {}'. - format(plugin, response)) + format(plugin, str(response))) except Exception as e: # Retry mechanism response = Response(output=None, errors=[str(e), traceback.format_exc()]) diff --git a/platypush/plugins/__init__.py b/platypush/plugins/__init__.py index 70f77f36b8..58adfebc68 100644 --- a/platypush/plugins/__init__.py +++ b/platypush/plugins/__init__.py @@ -1,13 +1,26 @@ import sys import logging +import traceback from platypush.config import Config from platypush.message.response import Response +from platypush.utils import get_decorators def action(f): def _execute_action(*args, **kwargs): - return f(*args, **kwargs) + output = None + errors = [] + + try: + output = f(*args, **kwargs) + except Exception as e: + if isinstance(args[0], Plugin): + args[0].logger.exception(e) + errors.append(str(e) + '\n' + traceback.format_exc()) + + return Response(output=output, errors=errors) + return _execute_action @@ -19,7 +32,13 @@ class Plugin(object): if 'logging' in kwargs: self.logger.setLevel(getattr(logging, kwargs['logging'].upper())) + self.registered_actions = set(get_decorators(self.__class__).get('action', [])) + def run(self, method, *args, **kwargs): + if method not in self.registered_actions: + raise RuntimeError('{} is not a registered action on {}'.format( + method, self.__class__.__name__)) + return getattr(self, method)(*args, **kwargs) diff --git a/platypush/plugins/assistant/google/__init__.py b/platypush/plugins/assistant/google/__init__.py index aa29242e34..aebf08152d 100644 --- a/platypush/plugins/assistant/google/__init__.py +++ b/platypush/plugins/assistant/google/__init__.py @@ -3,9 +3,7 @@ """ from platypush.context import get_backend -from platypush.message.response import Response - -from platypush.plugins import Plugin +from platypush.plugins import Plugin, action class AssistantGooglePlugin(Plugin): """ @@ -14,21 +12,25 @@ class AssistantGooglePlugin(Plugin): backend to programmatically control the conversation status. """ + def __init__(*args, **kwargs): + super().__init__(*args, **kwargs) + + @action def start_conversation(self): """ Programmatically start a conversation with the assistant """ assistant = get_backend('assistant.google') assistant.start_conversation() - return Response(output='', errors=[]) + @action def stop_conversation(self): """ Programmatically stop a running conversation with the assistant """ assistant = get_backend('assistant.google') assistant.stop_conversation() - return Response(output='', errors=[]) + # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/assistant/google/pushtotalk.py b/platypush/plugins/assistant/google/pushtotalk.py index cd01b06e71..ba00f26ed2 100644 --- a/platypush/plugins/assistant/google/pushtotalk.py +++ b/platypush/plugins/assistant/google/pushtotalk.py @@ -3,9 +3,7 @@ """ from platypush.context import get_backend -from platypush.message.response import Response - -from platypush.plugins import Plugin +from platypush.plugins import Plugin, action class AssistantGooglePushtotalkPlugin(Plugin): """ @@ -14,21 +12,24 @@ class AssistantGooglePushtotalkPlugin(Plugin): :mod:`platypush.backend.assistant.google.pushtotalk` backend. """ + def __init__(*args, **kwargs): + super().__init__(*args, **kwargs) + + @action def start_conversation(self): """ Programmatically start a conversation with the assistant """ assistant = get_backend('assistant.google.pushtotalk') assistant.start_conversation() - return Response(output='', errors=[]) + @action def stop_conversation(self): """ Programmatically stop a running conversation with the assistant """ assistant = get_backend('assistant.google.pushtotalk') assistant.stop_conversation() - return Response(output='', errors=[]) # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/calendar/__init__.py b/platypush/plugins/calendar/__init__.py index 760cbde291..ad226f27b6 100644 --- a/platypush/plugins/calendar/__init__.py +++ b/platypush/plugins/calendar/__init__.py @@ -7,8 +7,7 @@ import importlib from abc import ABCMeta, abstractmethod -from platypush.plugins import Plugin -from platypush.message.response import Response +from platypush.plugins import Plugin, action class CalendarInterface: @@ -64,6 +63,7 @@ class CalendarPlugin(Plugin, CalendarInterface): self.calendars.append(getattr(module, class_name)(**calendar)) + @action def get_upcoming_events(self, max_results=10): """ Get a list of upcoming events merging all the available calendars. @@ -116,7 +116,7 @@ class CalendarPlugin(Plugin, CalendarInterface): else event['start']['date'] + 'T00:00:00+00:00' ))[:max_results] - return Response(output=events) + return events # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/calendar/ical.py b/platypush/plugins/calendar/ical.py index b924e50db2..5927a53802 100644 --- a/platypush/plugins/calendar/ical.py +++ b/platypush/plugins/calendar/ical.py @@ -9,8 +9,7 @@ import pytz from icalendar import Calendar, Event -from platypush.message.response import Response -from platypush.plugins import Plugin +from platypush.plugins import Plugin, action from platypush.plugins.calendar import CalendarInterface @@ -61,6 +60,7 @@ class IcalCalendarPlugin(Plugin, CalendarInterface): } + @action def get_upcoming_events(self, max_results=10, only_participating=True): """ Get the upcoming events. See @@ -90,7 +90,7 @@ class IcalCalendarPlugin(Plugin, CalendarInterface): else: self.logger.error("HTTP error while getting {}: {}".format(self.url, response)) - return Response(output=events) + return events # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/camera/pi.py b/platypush/plugins/camera/pi.py index 09cdab45ac..dbb22df9fc 100644 --- a/platypush/plugins/camera/pi.py +++ b/platypush/plugins/camera/pi.py @@ -3,9 +3,7 @@ """ from platypush.context import get_backend -from platypush.message.response import Response - -from platypush.plugins import Plugin +from platypush.plugins import Plugin, action class CameraPiPlugin(Plugin): @@ -15,22 +13,26 @@ class CameraPiPlugin(Plugin): to programmatically control the status. """ + def __init__(*args, **kwargs): + super().__init__(*args, **kwargs) + + @action def start_recording(self): """ Start recording """ camera = get_backend('camera.pi') camera.send_camera_action(camera.CameraAction.START_RECORDING) - return Response(output={'status':'ok'}) + @action def stop_recording(self): """ Stop recording """ camera = get_backend('camera.pi') camera.send_camera_action(camera.CameraAction.STOP_RECORDING) - return Response(output={'status':'ok'}) + @action def take_picture(self, image_file): """ Take a picture. @@ -40,7 +42,7 @@ class CameraPiPlugin(Plugin): """ camera = get_backend('camera.pi') camera.send_camera_action(camera.CameraAction.TAKE_PICTURE, image_file=image_file) - return Response(output={'image_file':image_file}) + return {'image_file': image_file} # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/db/__init__.py b/platypush/plugins/db/__init__.py index b945523e2f..cc4645ece7 100644 --- a/platypush/plugins/db/__init__.py +++ b/platypush/plugins/db/__init__.py @@ -4,9 +4,7 @@ from sqlalchemy import create_engine, Table, MetaData -from platypush.message.response import Response - -from .. import Plugin +from platypush.plugins import Plugin, action class DbPlugin(Plugin): """ @@ -40,6 +38,7 @@ class DbPlugin(Plugin): else: return self.engine + @action def execute(self, statement, engine=None, *args, **kwargs): """ Executes a raw SQL statement. @@ -65,9 +64,8 @@ class DbPlugin(Plugin): result = connection.execute(statement) connection.commit() - return Response() - + @action def select(self, query, engine=None, *args, **kwargs): """ Returns rows (as a list of hashes) given a query. @@ -119,9 +117,10 @@ class DbPlugin(Plugin): for row in result.fetchall() ] - return Response(output=rows) + return rows + @action def insert(self, table, records, engine=None, *args, **kwargs): """ Inserts records (as a list of hashes) into a table. @@ -169,8 +168,6 @@ class DbPlugin(Plugin): insert = table.insert().values(**record) engine.execute(insert) - return Response() - # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/google/calendar.py b/platypush/plugins/google/calendar.py index d35c1e6001..50a6da992f 100644 --- a/platypush/plugins/google/calendar.py +++ b/platypush/plugins/google/calendar.py @@ -9,7 +9,7 @@ import os from apiclient import discovery -from platypush.message.response import Response +from platypush.plugins import action from platypush.plugins.google import GooglePlugin from platypush.plugins.calendar import CalendarInterface @@ -25,6 +25,7 @@ class GoogleCalendarPlugin(GooglePlugin, CalendarInterface): super().__init__(scopes=self.scopes, *args, **kwargs) + @action def get_upcoming_events(self, max_results=10): """ Get the upcoming events. See diff --git a/platypush/plugins/google/mail.py b/platypush/plugins/google/mail.py index 8cb45ce2c1..ec3d696a36 100644 --- a/platypush/plugins/google/mail.py +++ b/platypush/plugins/google/mail.py @@ -17,7 +17,7 @@ from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText -from platypush.message.response import Response +from platypush.plugins import action from platypush.plugins.google import GooglePlugin @@ -32,6 +32,7 @@ class GoogleMailPlugin(GooglePlugin): super().__init__(scopes=self.scopes, *args, **kwargs) + @action def compose(self, sender, to, subject, body, files=None): """ Compose a message. @@ -91,9 +92,10 @@ class GoogleMailPlugin(GooglePlugin): message = (service.users().messages().send( userId='me', body=body).execute()) - return Response(output=message) + return message + @action def get_labels(self): """ Returns the available labels on the GMail account @@ -101,7 +103,7 @@ class GoogleMailPlugin(GooglePlugin): service = self._get_service() results = service.users().labels().list(userId='me').execute() labels = results.get('labels', []) - return Response(output=labels) + return labels def _get_service(self): diff --git a/platypush/plugins/google/maps.py b/platypush/plugins/google/maps.py index 6f38676fab..c636021a98 100644 --- a/platypush/plugins/google/maps.py +++ b/platypush/plugins/google/maps.py @@ -5,7 +5,7 @@ import json import requests -from platypush.message.response import Response +from platypush.plugins import action from platypush.plugins.google import GooglePlugin @@ -26,6 +26,7 @@ class GoogleMapsPlugin(GooglePlugin): self.api_key = api_key + @action def get_address_from_latlng(self, latitude, longitude): """ Get an address information given lat/long @@ -65,7 +66,7 @@ class GoogleMapsPlugin(GooglePlugin): elif component_type == 'postal_code': address['postal_code'] = addr_component['long_name'] - return Response(output=address) + return address # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/gpio/__init__.py b/platypush/plugins/gpio/__init__.py index 99f331cbbb..c4746b28b8 100644 --- a/platypush/plugins/gpio/__init__.py +++ b/platypush/plugins/gpio/__init__.py @@ -5,8 +5,7 @@ import threading import time -from platypush.message.response import Response -from platypush.plugins import Plugin +from platypush.plugins import Plugin, action class GpioPlugin(Plugin): @@ -17,6 +16,10 @@ class GpioPlugin(Plugin): * **RPi.GPIO** (`pip install RPi.GPIO`) """ + def __init__(*args, **kwargs): + super().__init__(*args, **kwargs) + + @action def write(self, pin, val): """ Write a byte value to a pin. @@ -44,12 +47,13 @@ class GpioPlugin(Plugin): gpio.setup(pin, gpio.OUT) gpio.output(pin, val) - return Response(output={ + return { 'pin': pin, 'val': val, 'method': 'write', - }) + } + @action def read(self, pin): """ Reads a value from a PIN. @@ -74,11 +78,11 @@ class GpioPlugin(Plugin): gpio.setup(pin, gpio.IN) val = gpio.input(pin) - return Response(output={ + return { 'pin': pin, 'val': val, 'method': 'read', - }) + } # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/gpio/sensor/__init__.py b/platypush/plugins/gpio/sensor/__init__.py index 29e0af05b4..e163a33d15 100644 --- a/platypush/plugins/gpio/sensor/__init__.py +++ b/platypush/plugins/gpio/sensor/__init__.py @@ -1,4 +1,4 @@ -from platypush.plugins import Plugin +from platypush.plugins import Plugin, action class GpioSensorPlugin(Plugin): @@ -10,6 +10,7 @@ class GpioSensorPlugin(Plugin): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + @action def get_measurement(self, *args, **kwargs): """ Implemented by the subclasses. @@ -27,6 +28,7 @@ class GpioSensorPlugin(Plugin): """ raise NotImplementedError('get_measurement should be implemented in a derived class') + @action def get_data(self, *args, **kwargs): """ Alias for ``get_measurement`` diff --git a/platypush/plugins/gpio/sensor/distance/__init__.py b/platypush/plugins/gpio/sensor/distance/__init__.py index 97e6162f31..78d81a39bb 100644 --- a/platypush/plugins/gpio/sensor/distance/__init__.py +++ b/platypush/plugins/gpio/sensor/distance/__init__.py @@ -1,7 +1,7 @@ import threading import time -from platypush.message.response import Response +from platypush.plugins import action from platypush.plugins.gpio.sensor import GpioSensorPlugin @@ -39,6 +39,7 @@ class GpioSensorDistancePlugin(GpioSensorPlugin): gpio.output(self.trigger_pin, False) + @action def get_measurement(self): """ Extends :func:`.GpioSensorPlugin.get_measurement` diff --git a/platypush/plugins/gpio/sensor/mcp3008/__init__.py b/platypush/plugins/gpio/sensor/mcp3008/__init__.py index 520b7139d1..80ffc8bea5 100644 --- a/platypush/plugins/gpio/sensor/mcp3008/__init__.py +++ b/platypush/plugins/gpio/sensor/mcp3008/__init__.py @@ -1,8 +1,8 @@ import enum import time +from platypush.plugins import action from platypush.plugins.gpio.sensor import GpioSensorPlugin -from platypush.message.response import Response class MCP3008Mode(enum.Enum): @@ -125,6 +125,7 @@ class GpioSensorMcp3008Plugin(GpioSensorPlugin): return (value * self.Vdd) / 1023.0 if value is not None else None + @action def get_measurement(self): """ Returns a measurement from the sensors connected to the MCP3008 device. @@ -164,7 +165,7 @@ class GpioSensorMcp3008Plugin(GpioSensorPlugin): else: values[i] = value - return Response(output=values) + return values # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/gpio/zeroborg/__init__.py b/platypush/plugins/gpio/zeroborg/__init__.py index e725d4bbd1..ba0db0eed9 100644 --- a/platypush/plugins/gpio/zeroborg/__init__.py +++ b/platypush/plugins/gpio/zeroborg/__init__.py @@ -2,8 +2,7 @@ import enum import threading import time -from platypush.message.response import Response -from platypush.plugins import Plugin +from platypush.plugins import Plugin, action from platypush.context import get_plugin from platypush.config import Config @@ -95,7 +94,7 @@ class GpioZeroborgPlugin(Plugin): value = None while value is None: - value = plugin.get_measurement() + value = plugin.get_measurement().output if time.time() - measure_start_time > timeout: return None @@ -127,6 +126,7 @@ class GpioZeroborgPlugin(Plugin): return direction + @action def drive(self, direction): """ Drive the motors in a certain direction. @@ -184,9 +184,10 @@ class GpioZeroborgPlugin(Plugin): self._drive_thread = threading.Thread(target=_run) self._drive_thread.start() - return Response(output={'status': 'running', 'direction': direction}) + return {'status': 'running', 'direction': direction} + @action def stop(self): """ Turns off the motors @@ -199,7 +200,7 @@ class GpioZeroborgPlugin(Plugin): self.zb.MotorsOff() self.zb.ResetEpo() - return Response(output={'status':'stopped'}) + return {'status':'stopped'} # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/http/request/__init__.py b/platypush/plugins/http/request/__init__.py index 60f3cb8784..c9ef4388a6 100644 --- a/platypush/plugins/http/request/__init__.py +++ b/platypush/plugins/http/request/__init__.py @@ -1,8 +1,6 @@ import requests -from platypush.message.response import Response - -from platypush.plugins import Plugin +from platypush.plugins import Plugin, action class HttpRequestPlugin(Plugin): """ @@ -41,6 +39,9 @@ class HttpRequestPlugin(Plugin): } """ + def __init__(*args, **kwargs): + super().__init__(*args, **kwargs) + def _exec(self, method, url, output='text', **kwargs): """ Available output types: text (default), json, binary """ @@ -51,9 +52,10 @@ class HttpRequestPlugin(Plugin): if output == 'json': output = response.json() if output == 'binary': output = response.content - return Response(output=output, errors=[]) + return output + @action def get(self, url, **kwargs): """ Perform a GET request @@ -68,6 +70,7 @@ class HttpRequestPlugin(Plugin): return self._exec(method='get', url=url, **kwargs) + @action def post(self, url, **kwargs): """ Perform a POST request @@ -82,6 +85,7 @@ class HttpRequestPlugin(Plugin): return self._exec(method='post', url=url, **kwargs) + @action def head(self, url, **kwargs): """ Perform an HTTP HEAD request @@ -96,6 +100,7 @@ class HttpRequestPlugin(Plugin): return self._exec(method='head', url=url, **kwargs) + @action def put(self, url, **kwargs): """ Perform a PUT request @@ -110,6 +115,7 @@ class HttpRequestPlugin(Plugin): return self._exec(method='put', url=url, **kwargs) + @action def delete(self, url, **kwargs): """ Perform a DELETE request @@ -124,6 +130,7 @@ class HttpRequestPlugin(Plugin): return self._exec(method='delete', url=url, **kwargs) + @action def options(self, url, **kwargs): """ Perform an HTTP OPTIONS request diff --git a/platypush/plugins/http/request/ota/booking/__init__.py b/platypush/plugins/http/request/ota/booking/__init__.py index da810ee8ae..22ec50184d 100644 --- a/platypush/plugins/http/request/ota/booking/__init__.py +++ b/platypush/plugins/http/request/ota/booking/__init__.py @@ -1,6 +1,7 @@ import datetime import dateutil.parser +from platypush.plugins import action from platypush.plugins.http.request import HttpRequestPlugin class HttpRequestOtaBookingPlugin(HttpRequestPlugin): @@ -12,6 +13,7 @@ class HttpRequestOtaBookingPlugin(HttpRequestPlugin): self.timeout = timeout + @action def get_reservations(self, day='today'): url = 'https://hub-api.booking.com/v1/hotels/{}/reservations' \ .format(self.hotel_id) diff --git a/platypush/plugins/lastfm/__init__.py b/platypush/plugins/lastfm/__init__.py index 747cf39156..3ec26488a0 100644 --- a/platypush/plugins/lastfm/__init__.py +++ b/platypush/plugins/lastfm/__init__.py @@ -1,9 +1,7 @@ import pylast import time -from platypush.message.response import Response - -from .. import Plugin +from platypush.plugins import Plugin, action class LastfmPlugin(Plugin): """ @@ -42,6 +40,7 @@ class LastfmPlugin(Plugin): password_hash = pylast.md5(self.password)) + @action def scrobble(self, artist, title, album=None, **kwargs): """ Scrobble a track to Last.FM @@ -61,8 +60,6 @@ class LastfmPlugin(Plugin): timestamp = int(time.time()), ) - return Response() - def update_now_playing(self, artist, title, album=None, **kwargs): """ @@ -82,8 +79,6 @@ class LastfmPlugin(Plugin): album = album, ) - return Response() - # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/light/__init__.py b/platypush/plugins/light/__init__.py index f4fa7a34c2..7b37c0365a 100644 --- a/platypush/plugins/light/__init__.py +++ b/platypush/plugins/light/__init__.py @@ -1,22 +1,26 @@ -from .. import Plugin +from platypush.plugins import Plugin, action class LightPlugin(Plugin): """ Abstract plugin to interface your logic with lights/bulbs. """ + @action def on(self): """ Turn the light on """ raise NotImplementedError() + @action def off(self): """ Turn the light off """ raise NotImplementedError() + @action def toggle(self): """ Toggle the light status (on/off) """ raise NotImplementedError() + @action def status(self): """ Get the light status """ raise NotImplementedError() diff --git a/platypush/plugins/light/hue/__init__.py b/platypush/plugins/light/hue/__init__.py index 71677f38a7..57688048bf 100644 --- a/platypush/plugins/light/hue/__init__.py +++ b/platypush/plugins/light/hue/__init__.py @@ -8,9 +8,9 @@ from redis.exceptions import TimeoutError as QueueTimeoutError from phue import Bridge from platypush.context import get_backend -from platypush.message.response import Response +from platypush.plugins import action +from platypush.plugins.light import LightPlugin -from .. import LightPlugin class LightHuePlugin(LightPlugin): """ @@ -75,6 +75,7 @@ class LightHuePlugin(LightPlugin): for g in groups: self.lights.extend([l.name for l in g.lights]) + @action def connect(self): """ Connect to the configured Hue bridge. If the device hasn't been paired @@ -100,6 +101,7 @@ class LightHuePlugin(LightPlugin): self.logger.info('Bridge already connected') + @action def get_scenes(self): """ Get the available scenes on the devices. @@ -129,9 +131,10 @@ class LightHuePlugin(LightPlugin): } """ - return Response(output=self.bridge.get_scene()) + return self.bridge.get_scene() + @action def get_lights(self): """ Get the configured lights. @@ -170,9 +173,10 @@ class LightHuePlugin(LightPlugin): } """ - return Response(output=self.bridge.get_light()) + return self.bridge.get_light() + @action def get_groups(self): """ Get the list of configured light groups. @@ -222,7 +226,7 @@ class LightHuePlugin(LightPlugin): } """ - return Response(output=self.bridge.get_group()) + return self.bridge.get_group() def _exec(self, attr, *args, **kwargs): @@ -257,8 +261,8 @@ class LightHuePlugin(LightPlugin): self.bridge = None raise e - return Response(output='ok') + @action def set_light(self, light, **kwargs): """ Set a light (or lights) property. @@ -281,8 +285,8 @@ class LightHuePlugin(LightPlugin): self.connect() self.bridge.set_light(light, **kwargs) - return Response(output='ok') + @action def set_group(self, group, **kwargs): """ Set a group (or groups) property. @@ -305,8 +309,8 @@ class LightHuePlugin(LightPlugin): self.connect() self.bridge.set_group(group, **kwargs) - return Response(output='ok') + @action def on(self, lights=[], groups=[]): """ Turn lights/groups on. @@ -317,6 +321,7 @@ class LightHuePlugin(LightPlugin): return self._exec('on', True, lights=lights, groups=groups) + @action def off(self, lights=[], groups=[]): """ Turn lights/groups off. @@ -327,6 +332,7 @@ class LightHuePlugin(LightPlugin): return self._exec('on', False, lights=lights, groups=groups) + @action def bri(self, value, lights=[], groups=[]): """ Set lights/groups brightness. @@ -339,6 +345,7 @@ class LightHuePlugin(LightPlugin): return self._exec('bri', int(value) % (self.MAX_BRI+1), lights=lights, groups=groups) + @action def sat(self, value, lights=[], groups=[]): """ Set lights/groups saturation. @@ -351,6 +358,7 @@ class LightHuePlugin(LightPlugin): return self._exec('sat', int(value) % (self.MAX_SAT+1), lights=lights, groups=groups) + @action def hue(self, value, lights=[], groups=[]): """ Set lights/groups color hue. @@ -363,6 +371,7 @@ class LightHuePlugin(LightPlugin): return self._exec('hue', int(value) % (self.MAX_HUE+1), lights=lights, groups=groups) + @action def scene(self, name, lights=[], groups=[]): """ Set a scene by name. @@ -374,6 +383,7 @@ class LightHuePlugin(LightPlugin): return self._exec('scene', name=name, lights=lights, groups=groups) + @action def is_animation_running(self): """ :returns: True if there is an animation running, false otherwise. @@ -381,6 +391,7 @@ class LightHuePlugin(LightPlugin): return self.animation_thread is not None + @action def stop_animation(self): """ Stop a running animation if any @@ -389,6 +400,7 @@ class LightHuePlugin(LightPlugin): if self.animation_thread and self.animation_thread.is_alive(): self.redis.rpush(self.ANIMATION_CTRL_QUEUE_NAME, 'STOP') + @action def animate(self, animation, duration=None, hue_range=[0, MAX_HUE], sat_range=[0, MAX_SAT], bri_range=[MAX_BRI-1, MAX_BRI], lights=None, groups=None, @@ -529,7 +541,6 @@ class LightHuePlugin(LightPlugin): self.stop_animation() self.animation_thread = Thread(target=_animate_thread, args=(lights,)) self.animation_thread.start() - return Response(output='ok') # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/media/ctrl.py b/platypush/plugins/media/ctrl.py index c5c87f5790..03c64a7b32 100644 --- a/platypush/plugins/media/ctrl.py +++ b/platypush/plugins/media/ctrl.py @@ -2,10 +2,9 @@ import re import subprocess from platypush.context import get_plugin -from platypush.message.response import Response from platypush.plugins.media import PlayerState -from .. import Plugin +from platypush.plugins import Plugin, action class MediaCtrlPlugin(Plugin): """ @@ -76,9 +75,9 @@ class MediaCtrlPlugin(Plugin): return None + @action def play(self, url): (type, resource) = self._get_type_and_resource_by_url(url) - response = Response(output='', errors = []) plugin_name = None if type == 'mpd': @@ -99,11 +98,13 @@ class MediaCtrlPlugin(Plugin): self.url = resource return self.plugin.play(resource) + @action def pause(self): plugin = self._get_playing_plugin() if plugin: return plugin.pause() + @action def stop(self): plugin = self._get_playing_plugin() if plugin: @@ -112,36 +113,43 @@ class MediaCtrlPlugin(Plugin): return ret + @action def voldown(self): plugin = self._get_playing_plugin() if plugin: return plugin.voldown() + @action def volup(self): plugin = self._get_playing_plugin() if plugin: return plugin.volup() + @action def back(self): plugin = self._get_playing_plugin() if plugin: return plugin.back() + @action def forward(self): plugin = self._get_playing_plugin() if plugin: return plugin.forward() + @action def next(self): plugin = self._get_playing_plugin() if plugin: return plugin.next() + @action def previous(self): plugin = self._get_playing_plugin() if plugin: return plugin.previous() + @action def status(self): plugin = self._get_playing_plugin() if plugin: return plugin.status() diff --git a/platypush/plugins/midi.py b/platypush/plugins/midi.py index 501fb3d9d5..de0e35a154 100644 --- a/platypush/plugins/midi.py +++ b/platypush/plugins/midi.py @@ -1,8 +1,7 @@ import rtmidi import time -from platypush.message.response import Response -from platypush.plugins import Plugin +from platypush.plugins import Plugin, action class MidiPlugin(Plugin): @@ -39,6 +38,7 @@ class MidiPlugin(Plugin): format(self.device_name)) + @action def send_message(self, values, *args, **kwargs): """ :param values: Values is expected to be a list containing the MIDI command code and the command parameters - see reference at https://ccrma.stanford.edu/~craig/articles/linuxmidi/misc/essenmidi.html @@ -70,9 +70,9 @@ class MidiPlugin(Plugin): """ self.midiout.send_message(values, *args, **kwargs) - return Response(output={'status':'ok'}) + @action def play_note(self, note, velocity, duration=0): """ Play a note with selected velocity and duration. @@ -96,6 +96,7 @@ class MidiPlugin(Plugin): self._played_notes.remove(note) + @action def release_note(self, note): """ Release a played note. @@ -108,6 +109,7 @@ class MidiPlugin(Plugin): self._played_notes.remove(note) + @action def release_all_notes(self): """ Release all the notes being played. diff --git a/platypush/plugins/mqtt.py b/platypush/plugins/mqtt.py index 7a6615b287..cd8cd5f5b3 100644 --- a/platypush/plugins/mqtt.py +++ b/platypush/plugins/mqtt.py @@ -2,8 +2,7 @@ import json import paho.mqtt.publish as publisher from platypush.message import Message -from platypush.message.response import Response -from platypush.plugins import Plugin +from platypush.plugins import Plugin, action class MqttPlugin(Plugin): @@ -12,6 +11,7 @@ class MqttPlugin(Plugin): with the MQTT protocol, see http://mqtt.org/ """ + @action def send_message(self, topic, msg, host, port=1883, *args, **kwargs): """ Sends a message to a topic/channel. @@ -35,7 +35,6 @@ class MqttPlugin(Plugin): except: pass publisher.single(topic, str(msg), hostname=host, port=port) - return Response(output={'state': 'ok'}) # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/music/__init__.py b/platypush/plugins/music/__init__.py index 90f5f59eca..42c20681e5 100644 --- a/platypush/plugins/music/__init__.py +++ b/platypush/plugins/music/__init__.py @@ -1,33 +1,47 @@ -from .. import Plugin +from platypush.plugins import Plugin, action + class MusicPlugin(Plugin): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @action def play(self): raise NotImplementedError() + @action def pause(self): raise NotImplementedError() + @action def stop(self): raise NotImplementedError() + @action def next(self): raise NotImplementedError() + @action def previous(self): raise NotImplementedError() + @action def setvol(self, vol): raise NotImplementedError() + @action def add(self, content): raise NotImplementedError() + @action def playlistadd(self, playlist): raise NotImplementedError() + @action def clear(self): raise NotImplementedError() + @action def status(self): raise NotImplementedError() diff --git a/platypush/plugins/music/mpd/__init__.py b/platypush/plugins/music/mpd/__init__.py index 9214136478..dab128aa17 100644 --- a/platypush/plugins/music/mpd/__init__.py +++ b/platypush/plugins/music/mpd/__init__.py @@ -1,9 +1,8 @@ import mpd import re -from platypush.message.response import Response - -from .. import MusicPlugin +from platypush.plugins import action +from platypush.plugins.music import MusicPlugin class MusicMpdPlugin(MusicPlugin): """ @@ -29,6 +28,7 @@ class MusicMpdPlugin(MusicPlugin): :type port: int """ + super().__init__() self.host = host self.port = port self.client = mpd.MPDClient(use_unicode=True) @@ -36,8 +36,9 @@ class MusicMpdPlugin(MusicPlugin): def _exec(self, method, *args, **kwargs): getattr(self.client, method)(*args, **kwargs) - return self.status() + return self.status().output + @action def play(self, resource=None): """ Play a resource by path/URI @@ -51,6 +52,7 @@ class MusicMpdPlugin(MusicPlugin): self.add(resource) return self._exec('play') + @action def play_pos(self, pos): """ Play a track in the current playlist by position number @@ -61,6 +63,7 @@ class MusicMpdPlugin(MusicPlugin): return self._exec('play', pos) + @action def pause(self): """ Pause playback """ @@ -68,31 +71,38 @@ class MusicMpdPlugin(MusicPlugin): if status == 'play': return self._exec('pause') else: return self._exec('play') + @action def pause_if_playing(self): """ Pause playback only if it's playing """ status = self.status().output['state'] - if status == 'play': return self._exec('pause') - else: return Response(output={}) + if status == 'play': + return self._exec('pause') + @action def play_if_paused(self): """ Play only if it's paused (resume) """ status = self.status().output['state'] - if status == 'pause': return self._exec('play') - else: return Response(output={}) + if status == 'pause': + return self._exec('play') + @action def stop(self): """ Stop playback """ - return self._exec('stop') + + @action def play_or_stop(self): """ Play or stop (play state toggle) """ status = self.status().output['state'] - if status == 'play': return self._exec('stop') - else: return self._exec('play') + if status == 'play': + return self._exec('stop') + else: + return self._exec('play') + @action def playid(self, track_id): """ Play a track by ID @@ -103,14 +113,17 @@ class MusicMpdPlugin(MusicPlugin): return self._exec('playid', track_id) + @action def next(self): """ Play the next track """ return self._exec('next') + @action def previous(self): """ Play the previous track """ return self._exec('previous') + @action def setvol(self, vol): """ Set the volume @@ -120,6 +133,7 @@ class MusicMpdPlugin(MusicPlugin): """ return self._exec('setvol', vol) + @action def volup(self, delta=10): """ Turn up the volume @@ -134,6 +148,7 @@ class MusicMpdPlugin(MusicPlugin): self.setvol(str(new_volume)) return self.status() + @action def voldown(self, delta=10): """ Turn down the volume @@ -148,6 +163,7 @@ class MusicMpdPlugin(MusicPlugin): self.setvol(str(new_volume)) return self.status() + @action def random(self, value=None): """ Set shuffle mode @@ -161,6 +177,7 @@ class MusicMpdPlugin(MusicPlugin): value = 1 if value == 0 else 0 return self._exec('random', value) + @action def repeat(self, value=None): """ Set repeat mode @@ -174,6 +191,7 @@ class MusicMpdPlugin(MusicPlugin): value = 1 if value == 0 else 0 return self._exec('repeat', value) + @action def add(self, resource): """ Add a resource (track, album, artist, folder etc.) to the current playlist @@ -184,6 +202,7 @@ class MusicMpdPlugin(MusicPlugin): return self._exec('add', resource) + @action def load(self, playlist): """ Load and play a playlist by name @@ -195,10 +214,12 @@ class MusicMpdPlugin(MusicPlugin): self._exec('load', playlist) return self.play() + @action def clear(self): """ Clear the current playlist """ return self._exec('clear') + @action def seekcur(self, value): """ Seek to the specified position @@ -209,16 +230,19 @@ class MusicMpdPlugin(MusicPlugin): return self._exec('seekcur', value) + @action def forward(self): """ Go forward by 15 seconds """ return self._exec('seekcur', '+15') + @action def back(self): """ Go backward by 15 seconds """ return self._exec('seekcur', '-15') + @action def status(self): """ :returns: The current state. @@ -245,8 +269,9 @@ class MusicMpdPlugin(MusicPlugin): } """ - return Response(output=self.client.status()) + return self.client.status() + @action def currentsong(self): """ :returns: The currently played track. @@ -277,8 +302,9 @@ class MusicMpdPlugin(MusicPlugin): track['artist'] = m.group(1) track['title'] = m.group(2) - return Response(output=track) + return track + @action def playlistinfo(self): """ :returns: The tracks in the current playlist as a list of dicts. @@ -315,8 +341,9 @@ class MusicMpdPlugin(MusicPlugin): ] """ - return Response(output=self.client.playlistinfo()) + return self.client.playlistinfo() + @action def listplaylists(self): """ :returns: The playlists available on the server as a list of dicts. @@ -338,17 +365,18 @@ class MusicMpdPlugin(MusicPlugin): ] """ - return Response(output=sorted(self.client.listplaylists(), - key=lambda p: p['playlist'])) + return sorted(self.client.listplaylists(), key=lambda p: p['playlist']) + @action def lsinfo(self, uri=None): """ Returns the list of playlists and directories on the server """ output = self.client.lsinfo(uri) if uri else self.client.lsinfo() - return Response(output=output) + return output + @action def plchanges(self, version): """ Show what has changed on the current playlist since a specified playlist @@ -360,8 +388,9 @@ class MusicMpdPlugin(MusicPlugin): :returns: A list of dicts representing the songs being added since the specified version """ - return Response(output=self.client.plchanges(version)) + return self.client.plchanges(version) + @action def searchaddplaylist(self, name): """ Search and add a playlist by (partial or full) name @@ -379,10 +408,9 @@ class MusicMpdPlugin(MusicPlugin): self.client.clear() self.client.load(playlists[0]) self.client.play() - return Response(output={'playlist': playlists[0]}) - - return Response(output={}) + return {'playlist': playlists[0]} + @action def find(self, filter, *args, **kwargs): """ Find in the database/library by filter. @@ -392,9 +420,9 @@ class MusicMpdPlugin(MusicPlugin): :returns: list[dict] """ - return Response( - output=self.client.find(*filter, *args, **kwargs)) + return self.client.find(*filter, *args, **kwargs) + @action def findadd(self, filter, *args, **kwargs): """ Find in the database/library by filter and add to the current playlist. @@ -404,9 +432,9 @@ class MusicMpdPlugin(MusicPlugin): :returns: list[dict] """ - return Response( - output=self.client.findadd(*filter, *args, **kwargs)) + return self.client.findadd(*filter, *args, **kwargs) + @action def search(self, filter, *args, **kwargs): """ Free search by filter. @@ -422,8 +450,9 @@ class MusicMpdPlugin(MusicPlugin): items = sorted(items, key=lambda item: 0 if item['file'].startswith('spotify:') else 1) - return Response(output=items) + return items + @action def searchadd(self, filter, *args, **kwargs): """ Free search by filter and add the results to the current playlist. @@ -433,8 +462,7 @@ class MusicMpdPlugin(MusicPlugin): :returns: list[dict] """ - return Response( - output=self.client.searchadd(*filter, *args, **kwargs)) + return self.client.searchadd(*filter, *args, **kwargs) # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/pushbullet.py b/platypush/plugins/pushbullet.py index d9c02d64d2..c016ca1832 100644 --- a/platypush/plugins/pushbullet.py +++ b/platypush/plugins/pushbullet.py @@ -3,8 +3,7 @@ import os import requests from platypush.context import get_backend -from platypush.message.response import Response -from platypush.plugins import Plugin +from platypush.plugins import Plugin, action class PushbulletPlugin(Plugin): @@ -18,6 +17,7 @@ class PushbulletPlugin(Plugin): * **requests** (``pip install requests``) """ + @action def send_push(self, **kwargs): """ Send a push. @@ -40,9 +40,8 @@ class PushbulletPlugin(Plugin): raise Exception('Pushbullet push failed with status {}: {}'. format(resp.status_code, resp.json())) - return Response(output={'status':'ok'}) - + @action def send_file(self, filename): """ Send a file. @@ -83,11 +82,11 @@ class PushbulletPlugin(Plugin): raise Exception('Pushbullet file push failed with status {}'. format(resp.status_code)) - return Response(output={ + return { 'filename': r['file_name'], 'type': r['file_type'], 'url': r['file_url'] - }) + } # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/redis.py b/platypush/plugins/redis.py index 8a65258d42..0d07ccb8a6 100644 --- a/platypush/plugins/redis.py +++ b/platypush/plugins/redis.py @@ -1,7 +1,6 @@ from redis import Redis -from platypush.message.response import Response -from platypush.plugins import Plugin +from platypush.plugins import Plugin, action class RedisPlugin(Plugin): @@ -13,6 +12,7 @@ class RedisPlugin(Plugin): * **redis** (``pip install redis``) """ + @action def send_message(self, queue, msg, *args, **kwargs): """ Send a message to a Redis queu. @@ -32,7 +32,6 @@ class RedisPlugin(Plugin): redis = Redis(*args, **kwargs) redis.rpush(queue, msg) - return Response(output={'state': 'ok'}) # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/serial/__init__.py b/platypush/plugins/serial/__init__.py index 0083fde69d..a49860c73d 100644 --- a/platypush/plugins/serial/__init__.py +++ b/platypush/plugins/serial/__init__.py @@ -1,9 +1,7 @@ import json import serial -from platypush.message.response import Response - -from .. import Plugin +from platypush.plugins import Plugin, action class SerialPlugin(Plugin): @@ -60,6 +58,7 @@ class SerialPlugin(Plugin): return output.decode().strip() + @action def get_data(self): """ Reads JSON data from the serial device and returns it as a message @@ -78,7 +77,7 @@ class SerialPlugin(Plugin): self.logger.warning('Invalid JSON message from {}: {}'. format(self.device, data)) - return Response(output=data) + return data # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/shell/__init__.py b/platypush/plugins/shell/__init__.py index 41ab1a170a..02d724edd2 100644 --- a/platypush/plugins/shell/__init__.py +++ b/platypush/plugins/shell/__init__.py @@ -1,14 +1,15 @@ import subprocess from platypush.message.response import Response +from platypush.plugins import Plugin, action -from .. import Plugin class ShellPlugin(Plugin): """ Plugin to run custom shell commands. """ + @action def exec(self, cmd): """ Execute a command. @@ -19,16 +20,12 @@ class ShellPlugin(Plugin): :returns: A response object where the ``output`` field will contain the command output as a string, and the ``errors`` field will contain whatever was sent to stderr. """ - output = None - errors = [] - try: - output = subprocess.check_output( + return subprocess.check_output( cmd, stderr=subprocess.STDOUT, shell=True).decode('utf-8') except subprocess.CalledProcessError as e: - errors = [e.output.decode('utf-8')] + raise RuntimeError(e.output.decode('utf-8')) - return Response(output=output, errors=errors) # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/switch/__init__.py b/platypush/plugins/switch/__init__.py index 24316f383a..b025807ccd 100644 --- a/platypush/plugins/switch/__init__.py +++ b/platypush/plugins/switch/__init__.py @@ -1,22 +1,29 @@ -from .. import Plugin +from platypush.plugins import Plugin, action class SwitchPlugin(Plugin): """ Abstract class for interacting with switch devices """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @action def on(self, args): """ Turn the device on """ raise NotImplementedError() + @action def off(self, args): """ Turn the device off """ raise NotImplementedError() + @action def toggle(self, args): """ Toggle the device status (on/off) """ raise NotImplementedError() + @action def status(self): """ Get the device state """ raise NotImplementedError() diff --git a/platypush/plugins/switch/switchbot/__init__.py b/platypush/plugins/switch/switchbot/__init__.py index b8b00e6ef9..13185dfd65 100644 --- a/platypush/plugins/switch/switchbot/__init__.py +++ b/platypush/plugins/switch/switchbot/__init__.py @@ -4,9 +4,8 @@ import time from bluetooth.ble import DiscoveryService, GATTRequester -from platypush.message.response import Response - -from .. import SwitchPlugin +from platypush.plugins import action +from platypush.plugins.switch import SwitchPlugin class Scanner(object): service_uuid = '1bc5d5a5-0200b89f-e6114d22-000da2cb' @@ -112,6 +111,7 @@ class SwitchSwitchbotPlugin(SwitchPlugin): :type devices: dict """ + super().__init__(*args, **kwargs) self.bt_interface = bt_interface self.connect_timeout = connect_timeout if connect_timeout else 5 self.scan_timeout = scan_timeout if scan_timeout else 2 @@ -119,15 +119,12 @@ class SwitchSwitchbotPlugin(SwitchPlugin): def _run(self, device, command=None): - output = None - errors = [] - try: # XXX this requires sudo and it's executed in its own process # because the Switchbot plugin requires root privileges to send # raw bluetooth messages on the interface. Make sure that the user # that runs platypush has the right permissions to run this with sudo - output = subprocess.check_output(( + return subprocess.check_output(( 'sudo python3 -m platypush.plugins.switch.switchbot ' + '--device {} ' + ('--interface {} '.format(self.bt_interface) if self.bt_interface else '') + @@ -135,11 +132,10 @@ class SwitchSwitchbotPlugin(SwitchPlugin): ('--{} '.format(command) if command else '')).format(device), stderr=subprocess.STDOUT, shell=True).decode('utf-8') except subprocess.CalledProcessError as e: - errors = [e.output.decode('utf-8')] - - return Response(output=output, errors=errors) + raise RuntimeError(e.output.decode('utf-8')) + @action def press(self, device): """ Send a press button command to a device @@ -149,6 +145,7 @@ class SwitchSwitchbotPlugin(SwitchPlugin): """ return self._run(device) + @action def on(self, device): """ Send a press-on button command to a device @@ -158,6 +155,7 @@ class SwitchSwitchbotPlugin(SwitchPlugin): """ return self._run(device, 'on') + @action def off(self, device): """ Send a press-off button command to a device @@ -167,25 +165,17 @@ class SwitchSwitchbotPlugin(SwitchPlugin): """ return self._run(device, 'off') + @action def scan(self): """ Scan for available Switchbot devices nearby """ - output = None - errors = [] - try: - print('sudo python3 -m platypush.plugins.switch.switchbot --scan ' + - ('--interface {} '.format(self.bt_interface) if self.bt_interface else '') + - ('--scan-timeout {} '.format(self.scan_timeout) if self.scan_timeout else '')) - - output = subprocess.check_output( + return subprocess.check_output( 'sudo python3 -m platypush.plugins.switch.switchbot --scan ' + ('--interface {} '.format(self.bt_interface) if self.bt_interface else '') + ('--scan-timeout {} '.format(self.scan_timeout) if self.scan_timeout else ''), stderr=subprocess.STDOUT, shell=True).decode('utf-8') except subprocess.CalledProcessError as e: - errors = [e.output.decode('utf-8')] - - return Response(output=output, errors=errors) + raise RuntimeError(e.output.decode('utf-8')) # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/switch/tplink.py b/platypush/plugins/switch/tplink.py index c1a58196cd..6a8100dcfd 100644 --- a/platypush/plugins/switch/tplink.py +++ b/platypush/plugins/switch/tplink.py @@ -1,6 +1,6 @@ from pyHS100 import Discover -from platypush.message.response import Response +from platypush.plugins import action from platypush.plugins.switch import SwitchPlugin @@ -17,6 +17,8 @@ class SwitchTplinkPlugin(SwitchPlugin): _ip_to_dev = {} _alias_to_dev = {} + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) def _scan(self): devices = Discover.discover() @@ -46,6 +48,7 @@ class SwitchTplinkPlugin(SwitchPlugin): raise RuntimeError('Device {} not found'.format(device)) + @action def status(self): """ :returns: The available device over the network as a @@ -61,8 +64,9 @@ class SwitchTplinkPlugin(SwitchPlugin): } for (ip, dev) in self._scan().items() } } - return Response(output=devices) + return devices + @action def on(self, device): """ Turn on a device @@ -73,9 +77,10 @@ class SwitchTplinkPlugin(SwitchPlugin): device = self._get_device(device) device.turn_on() - return Response(output={'status':'on'}) + return {'status':'on'} + @action def off(self, device): """ Turn off a device @@ -86,9 +91,10 @@ class SwitchTplinkPlugin(SwitchPlugin): device = self._get_device(device) device.turn_off() - return Response(output={'status':'off'}) + return {'status':'off'} + @action def toggle(self, device): """ Toggle the state of a device (on/off) @@ -104,7 +110,7 @@ class SwitchTplinkPlugin(SwitchPlugin): else: device.turn_on() - return Response(output={'status': 'off' if device.is_off else 'on'}) + return {'status': 'off' if device.is_off else 'on'} # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/switch/wemo/__init__.py b/platypush/plugins/switch/wemo/__init__.py index 7fae85fd4d..ecbfd793e9 100644 --- a/platypush/plugins/switch/wemo/__init__.py +++ b/platypush/plugins/switch/wemo/__init__.py @@ -1,9 +1,9 @@ import json from ouimeaux.environment import Environment, UnknownDevice -from platypush.message.response import Response +from platypush.plugins import action +from platypush.plugins.switch import SwitchPlugin -from .. import SwitchPlugin class SwitchWemoPlugin(SwitchPlugin): """ @@ -20,8 +20,8 @@ class SwitchWemoPlugin(SwitchPlugin): :param discovery_seconds: Discovery time when scanning for devices (default: 3) :type discovery_seconds: int """ - super().__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.discovery_seconds=discovery_seconds self.env = Environment() self.env.start() @@ -33,6 +33,7 @@ class SwitchWemoPlugin(SwitchPlugin): self.env.discover(seconds=self.discovery_seconds) self.devices = self.env.devices + @action def get_devices(self): """ Get the list of available devices @@ -57,8 +58,8 @@ class SwitchWemoPlugin(SwitchPlugin): } """ self.refresh_devices() - return Response( - output = { 'devices': [ + return { + 'devices': [ { 'host': dev.host, 'name': dev.name, @@ -67,8 +68,8 @@ class SwitchWemoPlugin(SwitchPlugin): 'serialnumber': dev.serialnumber, } for (name, dev) in self.devices.items() - ] } - ) + ] + } def _exec(self, method, device, *args, **kwargs): if device not in self.devices: @@ -81,9 +82,9 @@ class SwitchWemoPlugin(SwitchPlugin): dev = self.devices[device] getattr(dev, method)(*args, **kwargs) - resp = {'device': device, 'state': dev.get_state()} - return Response(output=json.dumps(resp)) + return {'device': device, 'state': dev.get_state()} + @action def on(self, device): """ Turn a switch on @@ -93,6 +94,7 @@ class SwitchWemoPlugin(SwitchPlugin): """ return self._exec('on', device) + @action def off(self, device): """ Turn a switch off @@ -102,6 +104,7 @@ class SwitchWemoPlugin(SwitchPlugin): """ return self._exec('off', device) + @action def toggle(self, device): """ Toggle the state of a switch (on/off) diff --git a/platypush/plugins/tts/__init__.py b/platypush/plugins/tts/__init__.py index 0e9161276c..893dc18f58 100644 --- a/platypush/plugins/tts/__init__.py +++ b/platypush/plugins/tts/__init__.py @@ -2,8 +2,7 @@ import subprocess import urllib.parse from platypush.message.response import Response - -from .. import Plugin +from platypush.plugins import Plugin, action class TtsPlugin(Plugin): """ @@ -18,6 +17,7 @@ class TtsPlugin(Plugin): super().__init__() self.lang=lang + @action def say(self, phrase, lang=None): """ Say a phrase @@ -41,12 +41,10 @@ class TtsPlugin(Plugin): }))] try: - output = subprocess.check_output( + return subprocess.check_output( cmd, stderr=subprocess.STDOUT, shell=True).decode('utf-8') except subprocess.CalledProcessError as e: - errors = [e.output.decode('utf-8')] - - return Response(output=output, errors=errors) + raise RuntimeError(e.output.decode('utf-8')) # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/variable.py b/platypush/plugins/variable.py index 8c183514f0..efc883847a 100644 --- a/platypush/plugins/variable.py +++ b/platypush/plugins/variable.py @@ -1,5 +1,4 @@ -from platypush.message.response import Response -from platypush.plugins import Plugin +from platypush.plugins import Plugin, action class VariablePlugin(Plugin): @@ -12,6 +11,7 @@ class VariablePlugin(Plugin): super().__init__(*args, **kwargs) self._variables = {} + @action def get(self, name, default_value=None): """ Get the value of a variable by name. @@ -24,8 +24,9 @@ class VariablePlugin(Plugin): :returns: A map in the format ``{"":""}`` """ - return Response(output={name: self._variables.get(name, default_value)}) + return {name: self._variables.get(name, default_value)} + @action def set(self, **kwargs): """ Set a variable or a set of variables. @@ -35,8 +36,9 @@ class VariablePlugin(Plugin): for (name, value) in kwargs.items(): self._variables[name] = value - return Response(output=kwargs) + return kwargs + @action def unset(self, name): """ Unset a variable by name if it's set @@ -46,9 +48,6 @@ class VariablePlugin(Plugin): """ if name in self._variables: del self._variables[name] - return Response(output={'status':'ok'}) - else: - return Response(output={'status':'not_found'}) # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/video/omxplayer.py b/platypush/plugins/video/omxplayer.py index 2f60d8de58..849f3c11d0 100644 --- a/platypush/plugins/video/omxplayer.py +++ b/platypush/plugins/video/omxplayer.py @@ -12,11 +12,11 @@ from omxplayer import OMXPlayer from platypush.context import get_backend from platypush.plugins.media import PlayerState -from platypush.message.response import Response from platypush.message.event.video import VideoPlayEvent, VideoPauseEvent, \ VideoStopEvent, NewPlayingVideoEvent -from .. import Plugin +from platypush.plugins import Plugin, action + class VideoOmxplayerPlugin(Plugin): """ @@ -83,6 +83,7 @@ class VideoOmxplayerPlugin(Plugin): self.videos_queue = [] self.torrent_ports = torrent_ports if torrent_ports else self.default_torrent_ports + @action def play(self, resource): """ Play a resource. @@ -105,9 +106,7 @@ class VideoOmxplayerPlugin(Plugin): self.videos_queue = resources resource = self.videos_queue.pop(0) else: - error = 'Unable to download torrent {}'.format(resource) - self.logger.warning(error) - return Response(errors=[error]) + raise RuntimeError('Unable to download torrent {}'.format(resource)) self.logger.info('Playing {}'.format(resource)) @@ -130,10 +129,12 @@ class VideoOmxplayerPlugin(Plugin): return self.status() + @action def pause(self): """ Pause the playback """ if self.player: self.player.play_pause() + @action def stop(self): """ Stop the playback """ if self.player: @@ -143,30 +144,35 @@ class VideoOmxplayerPlugin(Plugin): return self.status() + @action def voldown(self): """ Volume down by 10% """ if self.player: self.player.set_volume(max(-6000, self.player.volume()-1000)) return self.status() + @action def volup(self): """ Volume up by 10% """ if self.player: self.player.set_volume(min(0, self.player.volume()+1000)) return self.status() + @action def back(self): """ Back by 30 seconds """ if self.player: self.player.seek(-30) return self.status() + @action def forward(self): """ Forward by 30 seconds """ if self.player: self.player.seek(+30) return self.status() + @action def next(self): """ Play the next track/video """ if self.player: @@ -176,19 +182,19 @@ class VideoOmxplayerPlugin(Plugin): video = self.videos_queue.pop(0) return self.play(video) - return Response(output={'status': 'no media'}, errors = []) - - + @action def hide_subtitles(self): """ Hide the subtitles """ if self.player: self.player.hide_subtitles() return self.status() + @action def hide_video(self): """ Hide the video """ if self.player: self.player.hide_video() return self.status() + @action def is_playing(self): """ :returns: True if it's playing, False otherwise @@ -197,6 +203,7 @@ class VideoOmxplayerPlugin(Plugin): if self.player: return self.player.is_playing() else: return False + @action def load(self, resource, pause=False): """ Load a resource/video in the player. @@ -208,21 +215,26 @@ class VideoOmxplayerPlugin(Plugin): if self.player: self.player.load(resource, pause) return self.status() + @action def metadata(self): """ Get the metadata of the current video """ - if self.player: return Response(output=self.player.metadata()) + if self.player: + return self.player.metadata() return self.status() + @action def mute(self): """ Mute the player """ if self.player: self.player.mute() return self.status() + @action def unmute(self): """ Unmute the player """ if self.player: self.player.unmute() return self.status() + @action def seek(self, relative_position): """ Seek backward/forward by the specified number of seconds @@ -234,6 +246,7 @@ class VideoOmxplayerPlugin(Plugin): if self.player: self.player.seek(relative_position) return self.status() + @action def set_position(self, position): """ Seek backward/forward to the specified absolute position @@ -245,6 +258,7 @@ class VideoOmxplayerPlugin(Plugin): if self.player: self.player.set_seek(position) return self.status() + @action def set_volume(self, volume): """ Set the volume @@ -258,6 +272,7 @@ class VideoOmxplayerPlugin(Plugin): if self.player: self.player.set_volume(volume) return self.status() + @action def status(self): """ Get the current player state. @@ -285,7 +300,7 @@ class VideoOmxplayerPlugin(Plugin): elif state == 'stopped': state = PlayerState.STOP.value elif state == 'paused': state = PlayerState.PAUSE.value - return Response(output=json.dumps({ + return { 'source': self.player.get_source(), 'state': state, 'volume': self.player.volume(), @@ -293,12 +308,13 @@ class VideoOmxplayerPlugin(Plugin): 'duration': self.player.duration(), 'width': self.player.width(), 'height': self.player.height(), - })) + } else: - return Response(output=json.dumps({ + return { 'state': PlayerState.STOP.value - })) + } + @action def send_message(self, msg): try: redis = get_backend('redis') @@ -311,16 +327,19 @@ class VideoOmxplayerPlugin(Plugin): redis.send_message(msg) + @action def on_play(self): def _f(player): self.send_message(VideoPlayEvent(video=self.player.get_source())) return _f + @action def on_pause(self): def _f(player): self.send_message(VideoPauseEvent(video=self.player.get_source())) return _f + @action def on_stop(self): def _f(player): self.send_message(VideoStopEvent()) @@ -335,6 +354,7 @@ class VideoOmxplayerPlugin(Plugin): self.player.pauseEvent += self.on_pause() self.player.stopEvent += self.on_stop() + @action def search(self, query, types=None, queue_results=False, autoplay=False): """ Perform a video search. @@ -376,7 +396,7 @@ class VideoOmxplayerPlugin(Plugin): elif autoplay: self.play(results[0]['url']) - return Response(output=results) + return results @classmethod def _is_video_file(cls, filename): @@ -388,6 +408,7 @@ class VideoOmxplayerPlugin(Plugin): return is_video + @action def file_search(self, query): results = [] query_tokens = [_.lower() for _ in re.split('\s+', query.strip())] @@ -413,8 +434,9 @@ class VideoOmxplayerPlugin(Plugin): 'title': f, }) - return Response(output=results) + return results + @action def youtube_search(self, query): self.logger.info('Searching YouTube for "{}"'.format(query)) @@ -439,7 +461,7 @@ class VideoOmxplayerPlugin(Plugin): self.logger.info('{} YouTube video results for the search query "{}"' .format(len(results), query)) - return Response(output=results) + return results @classmethod @@ -476,8 +498,9 @@ class VideoOmxplayerPlugin(Plugin): for _ in json.loads(request.read())['MovieList'] ] - return Response(output=results) + return results + @action def download_torrent(self, magnet): """ Download a torrent to ``download_dir`` by Magnet URI @@ -538,9 +561,10 @@ class VideoOmxplayerPlugin(Plugin): time.sleep(5) - return Response(output=files) + return files + @action def get_torrent_state(self): return self.torrent_state diff --git a/platypush/plugins/video/torrentcast.py b/platypush/plugins/video/torrentcast.py index 250c5c9e9a..868294e221 100644 --- a/platypush/plugins/video/torrentcast.py +++ b/platypush/plugins/video/torrentcast.py @@ -3,10 +3,8 @@ import urllib3 import urllib.request import urllib.parse +from platypush.plugins import Plugin, action from platypush.plugins.media import PlayerState -from platypush.message.response import Response - -from .. import Plugin class VideoTorrentcastPlugin(Plugin): def __init__(self, server='localhost', port=9090, *args, **kwargs): @@ -14,6 +12,7 @@ class VideoTorrentcastPlugin(Plugin): self.port = port self.state = PlayerState.STOP.value + @action def play(self, url): request = urllib.request.urlopen( 'http://{}:{}/play/'.format(self.server, self.port), @@ -23,24 +22,27 @@ class VideoTorrentcastPlugin(Plugin): ) self.state = PlayerState.PLAY.value - return Response(output=request.read()) + return request.read() + @action def pause(self): http = urllib3.PoolManager() request = http.request('POST', 'http://{}:{}/pause/'.format(self.server, self.port)) self.state = PlayerState.PAUSE.value - return Response(output=request.read()) + return request.read() + @action def stop(self): http = urllib3.PoolManager() request = http.request('POST', 'http://{}:{}/stop/'.format(self.server, self.port)) self.state = PlayerState.STOP.value - return Response(output=request.read()) + return request.read() + @action def search(self, query): request = urllib.request.urlopen(urllib.request.Request( 'https://api.apidomain.info/list?' + urllib.parse.urlencode({ @@ -56,8 +58,9 @@ class VideoTorrentcastPlugin(Plugin): ) results = json.loads(request.read()) - return Response(output=results) + return results + @action def search_and_play(self, query): response = self.search(query) if not response.output['MovieList']: @@ -71,13 +74,20 @@ class VideoTorrentcastPlugin(Plugin): return self.play(magnet) - def voldown(self): return Response(output='Unsupported method') - def volup(self): return Response(output='Unsupported method') - def back(self): return Response(output='Unsupported method') - def forward(self): return Response(output='Unsupported method') + @action + def voldown(self): raise NotImplementedError() - def status(self): - return Response(output={ 'state': self.state }) + @action + def volup(self): raise NotImplementedError() + + @action + def back(self): raise NotImplementedError() + + @action + def forward(self): raise NotImplementedError() + + @action + def status(self): return { 'state': self.state } # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/weather/forecast.py b/platypush/plugins/weather/forecast.py index 85b9e62747..df6ee53c56 100644 --- a/platypush/plugins/weather/forecast.py +++ b/platypush/plugins/weather/forecast.py @@ -1,4 +1,4 @@ -from platypush.message.response import Response +from platypush.plugins import action from platypush.plugins.http.request import HttpRequestPlugin @@ -46,6 +46,7 @@ class WeatherForecastPlugin(HttpRequestPlugin): format(self.darksky_token, (lat or self.lat), (long or self.long), self.units) + @action def get_current_weather(self, lat=None, long=None, **kwargs): """ Get the current weather. @@ -83,8 +84,9 @@ class WeatherForecastPlugin(HttpRequestPlugin): """ response = self.get(self._get_url(lat, long)) - return Response(output=response.output['currently']) + return response.output['currently'] + @action def get_hourly_forecast(self, lat=None, long=None, **kwargs): """ Get the hourly forecast. @@ -146,8 +148,9 @@ class WeatherForecastPlugin(HttpRequestPlugin): """ response = self.get(self._get_url(lat, long)) - return Response(output=response.output['hourly']) + return response.output['hourly'] + @action def get_daily_forecast(self, lat=None, long=None, **kwargs): """ Get the daily forecast. @@ -250,7 +253,7 @@ class WeatherForecastPlugin(HttpRequestPlugin): """ response = self.get(self._get_url(lat, long)) - return Response(output=response.output['daily']) + return response.output['daily'] # vim:sw=4:ts=4:et: diff --git a/platypush/utils/__init__.py b/platypush/utils/__init__.py index 62317e62ef..1015ac893e 100644 --- a/platypush/utils/__init__.py +++ b/platypush/utils/__init__.py @@ -1,6 +1,8 @@ +import ast import errno import hashlib import importlib +import inspect import logging import os import signal @@ -67,8 +69,32 @@ def clear_timeout(): def get_hash(s): + """ Get the SHA256 hash hexdigest of a string input """ return hashlib.sha256(s.encode('utf-8')).hexdigest() +def get_decorators(cls): + target = cls + decorators = {} + + def visit_FunctionDef(node): + # decorators[node.name] = [] + for n in node.decorator_list: + name = '' + if isinstance(n, ast.Call): + name = n.func.attr if isinstance(n.func, ast.Attribute) else n.func.id + else: + name = n.attr if isinstance(n, ast.Attribute) else n.id + + decorators[name] = decorators.get(name, []) + # decorators[node.name].append(name) + decorators[name].append(node.name) + + node_iter = ast.NodeVisitor() + node_iter.visit_FunctionDef = visit_FunctionDef + node_iter.visit(ast.parse(inspect.getsource(target))) + return decorators + + # vim:sw=4:ts=4:et: