From 491c2cd571ec07e7ab87db4128867a7915981107 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Wed, 13 Dec 2017 03:37:28 +0100 Subject: [PATCH] * More consistent management of responses * Better per-plugin/per-backend logging management --- README.md | 13 +++--- platypush/__init__.py | 50 ++++++++++------------- platypush/backend/__init__.py | 3 +- platypush/plugins/__init__.py | 15 +++++-- platypush/plugins/light/hue/__init__.py | 33 ++++++--------- platypush/plugins/music/mpd/__init__.py | 30 +++++++------- platypush/plugins/shell/__init__.py | 9 ++-- platypush/plugins/switch/wemo/__init__.py | 41 ++++++++++++------- platypush/response/__init__.py | 16 ++++++++ 9 files changed, 118 insertions(+), 92 deletions(-) create mode 100644 platypush/response/__init__.py diff --git a/README.md b/README.md index eb38d5291..906f03296 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,8 @@ The `__init__.py` will look like this: ```python import batman +from platypush.response import Response + from .. import LightPlugin class LightBatsignalPlugin(LightPlugin): @@ -123,22 +125,21 @@ class LightBatsignalPlugin(LightPlugin): self.batsignal.notify_robin() self.batsignal.on() + return Response('ok') def off(self): self.batsignal.off() + return Response('ok') def toggle(self): self.batsignal.toggle() + return Response('ok') - def status(self): - return [self.batsignal.status().stdout, self.batsignal.status().stderr] ``` -6. It's a good practice to define a `status` method in your plugin, which returns a 2-items list like `[output, error]`. +6. Rebuild and reinstall `platypush` if required and relaunch it. -7. Rebuild and reinstall `platypush` if required and relaunch it. - -8. Test your new plugin by sending some bullets to it: +7. Test your new plugin by sending some bullets to it: ```shell pusher --target your_pc --action light.batsignal.on --urgent 1 diff --git a/platypush/__init__.py b/platypush/__init__.py index e2eb2dbc3..dde4037a1 100644 --- a/platypush/__init__.py +++ b/platypush/__init__.py @@ -66,21 +66,11 @@ def _exec_func(args, retry=True): return try: - ret = plugin.run(method=method_name, **args) - out = None - err = None - - if isinstance(ret, list): - out = ret[0] - err = ret[1] if len(ret) > 1 else None - elif ret is not None: - out = ret - - if out: - logging.info('Command output: {}'.format(out)) - - if err: - logging.warn('Command error: {}'.format(err)) + response = plugin.run(method=method_name, **args) + if response and response.is_error(): + logging.warn('Response processed with errors: {}'.format(response)) + else: + logging.info('Processed response: {}'.format(response)) except Exception as e: logging.exception(e) if retry: @@ -122,6 +112,14 @@ def parse_config_file(config_file=None): if 'disabled' in config[section] and config[section]['disabled']: del config[section] + if 'logging' not in config: + config['logging'] = logging.INFO + else: + config['logging'] = getattr(logging, config['logging'].upper()) + + if 'device_id' not in config: + config['device_id'] = socket.gethostname() + return config @@ -161,13 +159,18 @@ def get_default_pusher_backend(config): return backends[0] if backends else None +def get_logging_level(): + global config + return config['logging'] + + def get_device_id(): global config return config['device_id'] if 'device_id' in config else None def main(): - DEBUG = False + debug = False config_file = None plugins_dir = os.path.join(wrkdir, 'plugins') @@ -178,7 +181,7 @@ def main(): if opt == '-c': config_file = arg if opt == '-v': - DEBUG = True + debug = True elif opt == '-h': print(''' Usage: {} [-v] [-h] [-c ] @@ -189,19 +192,10 @@ Usage: {} [-v] [-h] [-c ] return config = parse_config_file(config_file) + if debug: config['logging'] = logging.DEBUG logging.info('Configuration dump: {}'.format(config)) - if 'device_id' not in config: - config['device_id'] = socket.gethostname() - - if 'debug' in config: - DEBUG = config['debug'] - - if DEBUG: - logging.basicConfig(level=logging.DEBUG) - websocket.enableTrace(True) - else: - logging.basicConfig(level=logging.INFO) + logging.basicConfig(level=get_logging_level()) mq = Queue() backends = get_backends(config) diff --git a/platypush/backend/__init__.py b/platypush/backend/__init__.py index bdf564e7c..6501fd649 100644 --- a/platypush/backend/__init__.py +++ b/platypush/backend/__init__.py @@ -1,5 +1,4 @@ import logging -import socket import platypush from threading import Thread @@ -30,7 +29,7 @@ class Backend(Thread): self.on_init = on_init self.on_close = on_close self.on_error = on_error - self.device_id = platypush.get_device_id() or socket.gethostname() + self.device_id = platypush.get_device_id() Thread.__init__(self) logging.basicConfig(level=logging.INFO diff --git a/platypush/plugins/__init__.py b/platypush/plugins/__init__.py index dfc1e2389..441e4ffbd 100644 --- a/platypush/plugins/__init__.py +++ b/platypush/plugins/__init__.py @@ -1,6 +1,9 @@ import os import sys import logging +import traceback + +from platypush.response import Response class Plugin(object): def __init__(self, config): @@ -16,12 +19,18 @@ class Plugin(object): def _set_logging(self): if 'logging' in self.config: - logging.basicConfig(level=getattr(logging, self.config['logging'])) + self._logging = self.config.pop('logging') else: - logging.basicConfig(level=logging.INFO) + self._logging = logging.INFO + + logging.basicConfig(level=self._logging) def run(self, method, *args, **kwargs): - res = getattr(self, method)(*args, **kwargs) + try: + res = getattr(self, method)(*args, **kwargs) + except Exception as e: + res = Response(output=None, errors=[e, traceback.format_exc()]) + return res # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/light/hue/__init__.py b/platypush/plugins/light/hue/__init__.py index 9b1da8e56..a89681ea6 100644 --- a/platypush/plugins/light/hue/__init__.py +++ b/platypush/plugins/light/hue/__init__.py @@ -2,6 +2,7 @@ import logging import time from phue import Bridge +from platypush.response import Response from .. import LightPlugin @@ -57,19 +58,13 @@ class LightHuePlugin(LightPlugin): scenes = [s.name for s in self.bridge.scenes] # TODO Expand it with custom scenes specified in config.yaml as in #14 - def _execute(self, attr, *args, **kwargs): + def _exec(self, attr, *args, **kwargs): try: self.connect() except Exception as e: - logging.exception(e) # Reset bridge connection self.bridge = None - - if 'is_retry' not in kwargs: - time.sleep(1) - self._execute(attr, is_retry=True, *args, **kwargs) - - return + raise e lights = []; groups = [] if 'lights' in kwargs and kwargs['lights']: @@ -93,37 +88,33 @@ class LightHuePlugin(LightPlugin): elif lights: self.bridge.set_light(lights, attr, *args) except Exception as e: - logging.exception(e) # Reset bridge connection self.bridge = None + raise e + + return Response(output='ok') def on(self, lights=[], groups=[]): - self._execute('on', True, lights=lights, groups=groups) + return self._exec('on', True, lights=lights, groups=groups) def off(self, lights=[], groups=[]): - self._execute('on', False, lights=lights, groups=groups) + return self._exec('on', False, lights=lights, groups=groups) def bri(self, value, lights=[], groups=[]): - self._execute('bri', int(value) % (self.MAX_BRI+1), + return self._exec('bri', int(value) % (self.MAX_BRI+1), lights=lights, groups=groups) def sat(self, value, lights=[], groups=[]): - self._execute('sat', int(value) % (self.MAX_SAT+1), + return self._exec('sat', int(value) % (self.MAX_SAT+1), lights=lights, groups=groups) def hue(self, value, lights=[], groups=[]): - self._execute('hue', int(value) % (self.MAX_HUE+1), - lights=lights, groups=groups) - - def hue(self, value, lights=[], groups=[]): - self._execute('hue', int(value) % (self.MAX_HUE+1), + return self._exec('hue', int(value) % (self.MAX_HUE+1), lights=lights, groups=groups) def scene(self, name, lights=[], groups=[]): - self._execute('scene', name=name, lights=lights, groups=groups) + return self._exec('scene', name=name, lights=lights, groups=groups) - def status(self): - return [''] # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/music/mpd/__init__.py b/platypush/plugins/music/mpd/__init__.py index ba5e32dba..c924073a6 100644 --- a/platypush/plugins/music/mpd/__init__.py +++ b/platypush/plugins/music/mpd/__init__.py @@ -1,45 +1,47 @@ import mpd +from platypush.response import Response + from .. import MusicPlugin class MusicMpdPlugin(MusicPlugin): - _requires = [ - 'mpd' - ] - def _init(self): self.client = mpd.MPDClient(use_unicode=True) self.client.connect(self.config['host'], self.config['port']) + def _exec(self, method, *args, **kwargs): + getattr(self.client, method)(*args, **kwargs) + return self.status() + def play(self): - self.client.play() + return self._exec('play') def pause(self): - self.client.pause() + return self._exec('pause') def stop(self): - self.client.stop() + return self._exec('stop') def next(self): - self.client.next() + return self._exec('next') def previous(self): - self.client.previous() + return self._exec('previous') def setvol(self, vol): - self.client.setvol(vol) + return self._exec('setvol', vol) def add(self, content): - self.client.add(content) + return self._exec('add', content) def playlistadd(self, playlist): - self.client.playlistadd(playlist) + return self._exec('playlistadd', playlist) def clear(self): - self.client.clear() + return self._exec('clear') def status(self): - return self.client.status() + return Response(output=self.client.status()) # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/shell/__init__.py b/platypush/plugins/shell/__init__.py index a95e5d68a..66ef8c37f 100644 --- a/platypush/plugins/shell/__init__.py +++ b/platypush/plugins/shell/__init__.py @@ -1,5 +1,7 @@ import subprocess +from platypush.response import Response + from .. import Plugin class ShellPlugin(Plugin): @@ -8,11 +10,12 @@ class ShellPlugin(Plugin): error = None try: - output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True) + output = subprocess.check_output( + cmd, stderr=subprocess.STDOUT, shell=True).decode('utf-8') except subprocess.CalledProcessError as e: - error = e.output + error = e.output.decode('utf-8') - return [output, error] + return Response(output=output, errors=[error]) # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/switch/wemo/__init__.py b/platypush/plugins/switch/wemo/__init__.py index c691c8408..5b5aa0a40 100644 --- a/platypush/plugins/switch/wemo/__init__.py +++ b/platypush/plugins/switch/wemo/__init__.py @@ -1,35 +1,46 @@ import logging +import json from ouimeaux.environment import Environment, UnknownDevice +from platypush.response import Response from .. import SwitchPlugin class SwitchWemoPlugin(SwitchPlugin): - def _init(self): - logging.basicConfig(level=logging.INFO) - + def _init(self, discovery_seconds=3): + self.discovery_seconds=discovery_seconds self.env = Environment() self.env.start() + self.refresh_devices() + + def refresh_devices(self): logging.info('Starting WeMo discovery') - self.env.discover(seconds=3) + self.env.discover(seconds=self.discovery_seconds) + self.devices = self.env.devices + + def _exec(self, method, device, *args, **kwargs): + if device not in self.devices: + self.refresh_devices() + + if device not in self.devices: + raise RuntimeError('Device {} not found'.format(device)) + + logging.info('{} -> {}'.format(device, method)) + dev = self.devices[device] + getattr(dev, method)(*args, **kwargs) + + resp = {'device': device, 'state': dev.get_state()} + return Response(output=json.dumps(resp)) def on(self, device): - switch = self.env.get_switch(device) - logging.info('Turning {} on'.format(device)) - switch.on() + return self._exec('on', device) def off(self, device): - switch = self.env.get_switch(device) - logging.info('Turning {} off'.format(device)) - switch.off() + return self._exec('off', device) def toggle(self, device): - switch = self.env.get_switch(device) - logging.info('Toggling {}'.format(device)) - switch.toggle() + return self._exec('toggle', device) - def status(self): - return [''] # vim:sw=4:ts=4:et: diff --git a/platypush/response/__init__.py b/platypush/response/__init__.py new file mode 100644 index 000000000..a3c0e2c37 --- /dev/null +++ b/platypush/response/__init__.py @@ -0,0 +1,16 @@ +import json + +class Response(object): + def __init__(self, output=None, errors=[]): + self.output = output + self.errors = errors + + def __str__(self): + return json.dumps({ 'output': self.output, 'error': self.errors }) + + def is_error(self): + return len(self.errors) != 0 + + +# vim:sw=4:ts=4:et: +