platypush/platypush/event/hook.py

166 lines
5.1 KiB
Python
Raw Normal View History

import json
import logging
import re
from platypush.config import Config
from platypush.message.event import Event, EventMatchResult
from platypush.message.request import Request
from platypush.utils import get_event_class_by_type
def parse(msg):
""" Builds a dict given another dictionary or
a JSON UTF-8 encoded string/bytearray """
if isinstance(msg, bytes) or isinstance(msg, bytearray):
msg = msg.decode('utf-8')
if isinstance(msg, str):
try:
msg = json.loads(msg.strip())
except:
logging.warning('Invalid JSON message: {}'.format(msg))
return None
return msg
class EventCondition(object):
""" Event hook condition class """
def __init__(self, type=Event.__class__, **kwargs):
"""
Rule constructor.
Params:
type -- Class of the event to be built
kwargs -- Fields rules as a key-value (e.g. source_button=btn_id
or recognized_phrase='Your phrase')
"""
self.type = type
self.args = {}
self.parsed_args = {}
for (key, value) in kwargs.items():
# TODO So far we only allow simple value match. If value is a dict
# instead, we should allow more a sophisticated attribute matching,
# e.g. or conditions, in, and other operators.
self.args[key] = value
@classmethod
def build(cls, rule):
""" Builds a rule given either another EventRule, a dictionary or
a JSON UTF-8 encoded string/bytearray """
if isinstance(rule, cls): return rule
else: rule = parse(rule)
assert isinstance(rule, dict)
type = get_event_class_by_type(
rule.pop('type') if 'type' in rule else 'Event')
args = {}
for (key, value) in rule.items():
args[key] = value
return cls(type=type, **args)
class EventAction(Request):
""" Event hook action class. It is a special type of runnable request
whose fields can be configured later depending on the event context """
def __init__(self, target=Config.get('device_id'), action=None, **args):
super().__init__(target=target, action=action, **args)
def execute(self, **context):
event_args = context.pop('event').args if 'event' in context else {}
for (argname, value) in self.args.items():
if isinstance(value, str):
parsed_value = ''
while value:
m = re.match('([^\\\]*)\$([\w\d_-]+)(.*)', value)
if m:
context_argname = m.group(2)
value = m.group(3)
if context_argname in context:
parsed_value += m.group(1) + context[context_argname]
else:
parsed_value += m.group(1) + '$' + m.group(2)
else:
parsed_value += value
value = ''
value = parsed_value
self.args[argname] = value
super().execute()
@classmethod
def build(cls, action):
action = super().parse(action)
action['origin'] = Config.get('device_id')
if 'target' not in action:
action['target'] = action['origin']
return super().build(action)
class EventHook(object):
""" Event hook class. It consists of one conditionss and
one or multiple actions to be executed """
def __init__(self, name, condition=None, actions=[]):
""" Construtor. Takes a name, a EventCondition object and a list of
EventAction objects as input """
self.name = name
self.condition = EventCondition.build(condition or {})
self.actions = actions
@classmethod
def build(cls, name, hook):
""" Builds a rule given either another EventRule, a dictionary or
a JSON UTF-8 encoded string/bytearray """
if isinstance(hook, cls): return hook
else: hook = parse(hook)
assert isinstance(hook, dict)
condition = EventCondition.build(hook['if']) if 'if' in hook else None
actions = []
if 'then' in hook:
if isinstance(hook['then'], list):
actions = [EventAction.build(action) for action in hook['then']]
else:
actions = [EventAction.build(hook['then'])]
return cls(name=name, condition=condition, actions=actions)
2017-12-24 13:15:37 +01:00
def matches_event(self, event):
""" Returns an EventMatchResult object containing the information
about the match between the event and this hook """
return event.matches_condition(self.condition)
def run(self, event):
""" Checks the condition of the hook against a particular event and
runs the hook actions if the condition is met """
2017-12-24 13:15:37 +01:00
result = self.matches_event(event)
if result.is_match:
logging.info('Running hook {} triggered by an event'.format(self.name))
for action in self.actions:
action.execute(event=event, **result.parsed_args)
# vim:sw=4:ts=4:et: