forked from platypush/platypush
Better event hooks filters.
- Support for nested attributes on event hook conditions. Things like these are now possible: ``` from platypush.event.hook import hook from platypush.message.event.entities import EntityUpdateEvent @hook(EntityUpdateEvent, entity={"external_id": "system:cpu"}) def on_cpu_update_event(event: EntityUpdateEvent, **_): print(event.args["entity"]["percent"]) ``` - The scoring/regex extraction/partial string match logic in `_matches_argument` is actually only needed for `SpeechRecognizedEvent`. Other events don't need these features, and event hooks may be actually triggered unexpectedly in case of partial matches. Therefore, the "complex" `_matches_argument` has been moved as an override only for `SpeechRecognizedEvent`, and all the other events will perform simple key-value matching.
This commit is contained in:
parent
ee54e0edbf
commit
245472a4c5
3 changed files with 217 additions and 128 deletions
|
@ -2,8 +2,6 @@ import copy
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
@ -31,7 +29,7 @@ class Event(Message):
|
||||||
timestamp=None,
|
timestamp=None,
|
||||||
logging_level=logging.INFO,
|
logging_level=logging.INFO,
|
||||||
disable_web_clients_notification=False,
|
disable_web_clients_notification=False,
|
||||||
**kwargs
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
:param target: Target node
|
:param target: Target node
|
||||||
|
@ -53,7 +51,7 @@ class Event(Message):
|
||||||
self.id = id if id else self._generate_id()
|
self.id = id if id else self._generate_id()
|
||||||
self.target = target if target else Config.get('device_id')
|
self.target = target if target else Config.get('device_id')
|
||||||
self.origin = origin if origin else Config.get('device_id')
|
self.origin = origin if origin else Config.get('device_id')
|
||||||
self.type = '{}.{}'.format(self.__class__.__module__, self.__class__.__name__)
|
self.type = f'{self.__class__.__module__}.{self.__class__.__name__}'
|
||||||
self.args = kwargs
|
self.args = kwargs
|
||||||
self.disable_web_clients_notification = disable_web_clients_notification
|
self.disable_web_clients_notification = disable_web_clients_notification
|
||||||
|
|
||||||
|
@ -71,8 +69,10 @@ class Event(Message):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def build(cls, msg):
|
def build(cls, msg):
|
||||||
"""Builds an event message from a JSON UTF-8 string/bytearray, a
|
"""
|
||||||
dictionary, or another Event"""
|
Builds an event message from a JSON UTF-8 string/bytearray, a
|
||||||
|
dictionary, or another Event
|
||||||
|
"""
|
||||||
|
|
||||||
msg = super().parse(msg)
|
msg = super().parse(msg)
|
||||||
event_type = msg['args'].pop('type')
|
event_type = msg['args'].pop('type')
|
||||||
|
@ -88,7 +88,42 @@ class Event(Message):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _generate_id():
|
def _generate_id():
|
||||||
"""Generate a unique event ID"""
|
"""Generate a unique event ID"""
|
||||||
return ''.join(['{:02x}'.format(random.randint(0, 255)) for _ in range(16)])
|
return ''.join([f'{random.randint(0, 255):02x}' for _ in range(16)])
|
||||||
|
|
||||||
|
def _matches_condition(
|
||||||
|
self,
|
||||||
|
condition: dict,
|
||||||
|
args: dict,
|
||||||
|
result: "EventMatchResult",
|
||||||
|
match_scores: list,
|
||||||
|
) -> bool:
|
||||||
|
for attr, value in condition.items():
|
||||||
|
if attr not in args:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if isinstance(args[attr], str):
|
||||||
|
self._matches_argument(
|
||||||
|
argname=attr, condition_value=value, args=args, result=result
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.is_match:
|
||||||
|
match_scores.append(result.score)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
elif isinstance(value, dict):
|
||||||
|
if not isinstance(args[attr], dict):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self._matches_condition(
|
||||||
|
condition=value,
|
||||||
|
args=args[attr],
|
||||||
|
result=result,
|
||||||
|
match_scores=match_scores,
|
||||||
|
)
|
||||||
|
elif args[attr] != value:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def matches_condition(self, condition):
|
def matches_condition(self, condition):
|
||||||
"""
|
"""
|
||||||
|
@ -102,21 +137,12 @@ class Event(Message):
|
||||||
if not isinstance(self, condition.type):
|
if not isinstance(self, condition.type):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
for attr, value in condition.args.items():
|
if not self._matches_condition(
|
||||||
if attr not in self.args:
|
condition=condition.args,
|
||||||
return result
|
args=self.args,
|
||||||
|
result=result,
|
||||||
if isinstance(self.args[attr], str):
|
match_scores=match_scores,
|
||||||
arg_result = self._matches_argument(argname=attr, condition_value=value)
|
):
|
||||||
|
|
||||||
if arg_result.is_match:
|
|
||||||
match_scores.append(arg_result.score)
|
|
||||||
for parsed_arg, parsed_value in arg_result.parsed_args.items():
|
|
||||||
result.parsed_args[parsed_arg] = parsed_value
|
|
||||||
else:
|
|
||||||
return result
|
|
||||||
elif self.args[attr] != value:
|
|
||||||
# TODO proper support for list and dictionary matches
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
result.is_match = True
|
result.is_match = True
|
||||||
|
@ -125,75 +151,20 @@ class Event(Message):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _matches_argument(self, argname, condition_value):
|
def _matches_argument(
|
||||||
|
self, argname, condition_value, args, result: "EventMatchResult"
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Returns an EventMatchResult if the event argument [argname] matches
|
Returns an EventMatchResult if the event argument [argname] matches
|
||||||
[condition_value].
|
[condition_value].
|
||||||
|
|
||||||
- Example:
|
|
||||||
- self.args = {
|
|
||||||
'phrase': 'Hey dude turn on the living room lights'
|
|
||||||
}
|
|
||||||
|
|
||||||
- self._matches_argument(argname='phrase', condition_value='Turn on the ${lights} lights')
|
|
||||||
will return EventMatchResult(is_match=True, parsed_args={ 'lights': 'living room' })
|
|
||||||
|
|
||||||
- self._matches_argument(argname='phrase', condition_value='Turn off the ${lights} lights')
|
|
||||||
will return EventMatchResult(is_match=False, parsed_args={})
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = EventMatchResult(is_match=False)
|
# Simple equality match by default. It can be overridden by the derived classes.
|
||||||
if self.args.get(argname) == condition_value:
|
result.is_match = args.get(argname) == condition_value
|
||||||
# In case of an exact match, return immediately
|
if result.is_match:
|
||||||
result.is_match = True
|
result.score += 2
|
||||||
result.score = sys.maxsize
|
|
||||||
return result
|
|
||||||
|
|
||||||
event_tokens = re.split(r'\s+', self.args.get(argname, '').strip().lower())
|
|
||||||
condition_tokens = re.split(r'\s+', condition_value.strip().lower())
|
|
||||||
|
|
||||||
while event_tokens and condition_tokens:
|
|
||||||
event_token = event_tokens[0]
|
|
||||||
condition_token = condition_tokens[0]
|
|
||||||
|
|
||||||
if event_token == condition_token:
|
|
||||||
event_tokens.pop(0)
|
|
||||||
condition_tokens.pop(0)
|
|
||||||
result.score += 1.5
|
|
||||||
elif re.search(condition_token, event_token):
|
|
||||||
m = re.search('({})'.format(condition_token), event_token)
|
|
||||||
if m and m.group(1):
|
|
||||||
event_tokens.pop(0)
|
|
||||||
result.score += 1.25
|
|
||||||
|
|
||||||
condition_tokens.pop(0)
|
|
||||||
else:
|
else:
|
||||||
m = re.match(r'[^\\]*\${(.+?)}', condition_token)
|
result.score = 0
|
||||||
if m:
|
|
||||||
argname = m.group(1)
|
|
||||||
if argname not in result.parsed_args:
|
|
||||||
result.parsed_args[argname] = event_token
|
|
||||||
result.score += 1.0
|
|
||||||
else:
|
|
||||||
result.parsed_args[argname] += ' ' + event_token
|
|
||||||
|
|
||||||
if (len(condition_tokens) == 1 and len(event_tokens) == 1) or (
|
|
||||||
len(event_tokens) > 1
|
|
||||||
and len(condition_tokens) > 1
|
|
||||||
and event_tokens[1] == condition_tokens[1]
|
|
||||||
):
|
|
||||||
# Stop appending tokens to this argument, as the next
|
|
||||||
# condition will be satisfied as well
|
|
||||||
condition_tokens.pop(0)
|
|
||||||
|
|
||||||
event_tokens.pop(0)
|
|
||||||
else:
|
|
||||||
result.score -= 1.0
|
|
||||||
event_tokens.pop(0)
|
|
||||||
|
|
||||||
# It's a match if all the tokens in the condition string have been satisfied
|
|
||||||
result.is_match = len(condition_tokens) == 0
|
|
||||||
return result
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""
|
"""
|
||||||
|
@ -218,11 +189,13 @@ class Event(Message):
|
||||||
|
|
||||||
|
|
||||||
class EventMatchResult:
|
class EventMatchResult:
|
||||||
"""When comparing an event against an event condition, you want to
|
"""
|
||||||
|
When comparing an event against an event condition, you want to
|
||||||
return this object. It contains the match status (True or False),
|
return this object. It contains the match status (True or False),
|
||||||
any parsed arguments, and a match_score that identifies how "strong"
|
any parsed arguments, and a match_score that identifies how "strong"
|
||||||
the match is - in case of multiple event matches, the ones with the
|
the match is - in case of multiple event matches, the ones with the
|
||||||
highest score will win"""
|
highest score will win.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, is_match, score=0.0, parsed_args=None):
|
def __init__(self, is_match, score=0.0, parsed_args=None):
|
||||||
self.is_match = is_match
|
self.is_match = is_match
|
||||||
|
@ -231,6 +204,9 @@ class EventMatchResult:
|
||||||
|
|
||||||
|
|
||||||
def flatten(args):
|
def flatten(args):
|
||||||
|
"""
|
||||||
|
Flatten a nested dictionary for string serialization.
|
||||||
|
"""
|
||||||
if isinstance(args, dict):
|
if isinstance(args, dict):
|
||||||
for key, value in args.items():
|
for key, value in args.items():
|
||||||
if isinstance(value, date):
|
if isinstance(value, date):
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
from platypush.context import get_backend, get_plugin
|
from platypush.context import get_backend, get_plugin
|
||||||
from platypush.message.event import Event
|
from platypush.message.event import Event
|
||||||
|
|
||||||
|
|
||||||
class AssistantEvent(Event):
|
class AssistantEvent(Event):
|
||||||
""" Base class for assistant events """
|
"""Base class for assistant events"""
|
||||||
|
|
||||||
def __init__(self, assistant=None, *args, **kwargs):
|
def __init__(self, *args, assistant=None, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.logger = logging.getLogger('platypush:assistant')
|
self.logger = logging.getLogger('platypush:assistant')
|
||||||
|
|
||||||
|
@ -20,7 +23,9 @@ class AssistantEvent(Event):
|
||||||
self._assistant = get_plugin('assistant.google.pushtotalk')
|
self._assistant = get_plugin('assistant.google.pushtotalk')
|
||||||
|
|
||||||
if not self._assistant:
|
if not self._assistant:
|
||||||
self.logger.warning('Assistant plugin/backend not configured/initialized')
|
self.logger.warning(
|
||||||
|
'Assistant plugin/backend not configured/initialized'
|
||||||
|
)
|
||||||
self._assistant = None
|
self._assistant = None
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,7 +43,7 @@ class ConversationEndEvent(AssistantEvent):
|
||||||
Event triggered when a conversation ends
|
Event triggered when a conversation ends
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, with_follow_on_turn=False, *args, **kwargs):
|
def __init__(self, *args, with_follow_on_turn=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
:param with_follow_on_turn: Set to true if the conversation expects a user follow-up, false otherwise
|
:param with_follow_on_turn: Set to true if the conversation expects a user follow-up, false otherwise
|
||||||
:type with_follow_on_turn: str
|
:type with_follow_on_turn: str
|
||||||
|
@ -75,9 +80,6 @@ class NoResponseEvent(ConversationEndEvent):
|
||||||
Event triggered when a conversation ends with no response
|
Event triggered when a conversation ends with no response
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class SpeechRecognizedEvent(AssistantEvent):
|
class SpeechRecognizedEvent(AssistantEvent):
|
||||||
"""
|
"""
|
||||||
|
@ -105,18 +107,89 @@ class SpeechRecognizedEvent(AssistantEvent):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@override
|
||||||
|
def _matches_argument(self, argname, condition_value, args, result):
|
||||||
|
"""
|
||||||
|
Overrides the default `_matches_argument` method to allow partial
|
||||||
|
phrase matches and text extraction.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
args = {
|
||||||
|
'phrase': 'Hey dude turn on the living room lights'
|
||||||
|
}
|
||||||
|
|
||||||
|
- `self._matches_argument(argname='phrase', condition_value='Turn on the ${lights} lights')`
|
||||||
|
will return `EventMatchResult(is_match=True, parsed_args={ 'lights': 'living room' })`
|
||||||
|
|
||||||
|
- `self._matches_argument(argname='phrase', condition_value='Turn off the ${lights} lights')`
|
||||||
|
will return `EventMatchResult(is_match=False, parsed_args={})`
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if args.get(argname) == condition_value:
|
||||||
|
# In case of an exact match, return immediately
|
||||||
|
result.is_match = True
|
||||||
|
result.score = sys.maxsize
|
||||||
|
return result
|
||||||
|
|
||||||
|
event_tokens = re.split(r'\s+', args.get(argname, '').strip().lower())
|
||||||
|
condition_tokens = re.split(r'\s+', condition_value.strip().lower())
|
||||||
|
|
||||||
|
while event_tokens and condition_tokens:
|
||||||
|
event_token = event_tokens[0]
|
||||||
|
condition_token = condition_tokens[0]
|
||||||
|
|
||||||
|
if event_token == condition_token:
|
||||||
|
event_tokens.pop(0)
|
||||||
|
condition_tokens.pop(0)
|
||||||
|
result.score += 1.5
|
||||||
|
elif re.search(condition_token, event_token):
|
||||||
|
m = re.search(f'({condition_token})', event_token)
|
||||||
|
if m and m.group(1):
|
||||||
|
event_tokens.pop(0)
|
||||||
|
result.score += 1.25
|
||||||
|
|
||||||
|
condition_tokens.pop(0)
|
||||||
|
else:
|
||||||
|
m = re.match(r'[^\\]*\${(.+?)}', condition_token)
|
||||||
|
if m:
|
||||||
|
argname = m.group(1)
|
||||||
|
if argname not in result.parsed_args:
|
||||||
|
result.parsed_args[argname] = event_token
|
||||||
|
result.score += 1.0
|
||||||
|
else:
|
||||||
|
result.parsed_args[argname] += ' ' + event_token
|
||||||
|
|
||||||
|
if (len(condition_tokens) == 1 and len(event_tokens) == 1) or (
|
||||||
|
len(event_tokens) > 1
|
||||||
|
and len(condition_tokens) > 1
|
||||||
|
and event_tokens[1] == condition_tokens[1]
|
||||||
|
):
|
||||||
|
# Stop appending tokens to this argument, as the next
|
||||||
|
# condition will be satisfied as well
|
||||||
|
condition_tokens.pop(0)
|
||||||
|
|
||||||
|
event_tokens.pop(0)
|
||||||
|
else:
|
||||||
|
result.score -= 1.0
|
||||||
|
event_tokens.pop(0)
|
||||||
|
|
||||||
|
# It's a match if all the tokens in the condition string have been satisfied
|
||||||
|
result.is_match = len(condition_tokens) == 0
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class HotwordDetectedEvent(AssistantEvent):
|
class HotwordDetectedEvent(AssistantEvent):
|
||||||
"""
|
"""
|
||||||
Event triggered when a custom hotword is detected
|
Event triggered when a custom hotword is detected
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, hotword=None, *args, **kwargs):
|
def __init__(self, *args, hotword=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
:param hotword: The detected user hotword
|
:param hotword: The detected user hotword
|
||||||
:type hotword: str
|
:type hotword: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(*args, hotword=hotword, **kwargs)
|
super().__init__(*args, hotword=hotword, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@ -134,67 +207,47 @@ class AlertStartedEvent(AssistantEvent):
|
||||||
Event triggered when an alert starts on the assistant
|
Event triggered when an alert starts on the assistant
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class AlertEndEvent(AssistantEvent):
|
class AlertEndEvent(AssistantEvent):
|
||||||
"""
|
"""
|
||||||
Event triggered when an alert ends on the assistant
|
Event triggered when an alert ends on the assistant
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class AlarmStartedEvent(AlertStartedEvent):
|
class AlarmStartedEvent(AlertStartedEvent):
|
||||||
"""
|
"""
|
||||||
Event triggered when an alarm starts on the assistant
|
Event triggered when an alarm starts on the assistant
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class AlarmEndEvent(AlertEndEvent):
|
class AlarmEndEvent(AlertEndEvent):
|
||||||
"""
|
"""
|
||||||
Event triggered when an alarm ends on the assistant
|
Event triggered when an alarm ends on the assistant
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TimerStartedEvent(AlertStartedEvent):
|
class TimerStartedEvent(AlertStartedEvent):
|
||||||
"""
|
"""
|
||||||
Event triggered when a timer starts on the assistant
|
Event triggered when a timer starts on the assistant
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TimerEndEvent(AlertEndEvent):
|
class TimerEndEvent(AlertEndEvent):
|
||||||
"""
|
"""
|
||||||
Event triggered when a timer ends on the assistant
|
Event triggered when a timer ends on the assistant
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class MicMutedEvent(AssistantEvent):
|
class MicMutedEvent(AssistantEvent):
|
||||||
"""
|
"""
|
||||||
Event triggered when the microphone is muted.
|
Event triggered when the microphone is muted.
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MicUnmutedEvent(AssistantEvent):
|
class MicUnmutedEvent(AssistantEvent):
|
||||||
"""
|
"""
|
||||||
Event triggered when the microphone is muted.
|
Event triggered when the microphone is muted.
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -1,28 +1,88 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from platypush.event.hook import EventCondition
|
from platypush.event.hook import EventCondition
|
||||||
|
from platypush.message.event.assistant import SpeechRecognizedEvent
|
||||||
from platypush.message.event.ping import PingEvent
|
from platypush.message.event.ping import PingEvent
|
||||||
|
|
||||||
|
|
||||||
condition = EventCondition.build({
|
|
||||||
'type': 'platypush.message.event.ping.PingEvent',
|
|
||||||
'message': 'This is (the)? answer: ${answer}'
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def test_event_parse():
|
def test_event_parse():
|
||||||
"""
|
"""
|
||||||
Test for the events/conditions matching logic.
|
Test for the events/conditions matching logic.
|
||||||
"""
|
"""
|
||||||
message = "GARBAGE GARBAGE this is the answer: 42"
|
condition = EventCondition.build(
|
||||||
event = PingEvent(message=message)
|
{
|
||||||
|
'type': 'platypush.message.event.ping.PingEvent',
|
||||||
|
'message': 'This is a test message',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
event = PingEvent(message=condition.args['message'])
|
||||||
|
result = event.matches_condition(condition)
|
||||||
|
assert result.is_match
|
||||||
|
|
||||||
|
event = PingEvent(message="This is not a test message")
|
||||||
|
result = event.matches_condition(condition)
|
||||||
|
assert not result.is_match
|
||||||
|
|
||||||
|
|
||||||
|
def test_nested_event_condition():
|
||||||
|
"""
|
||||||
|
Verify that nested event conditions work as expected.
|
||||||
|
"""
|
||||||
|
condition = EventCondition.build(
|
||||||
|
{
|
||||||
|
'type': 'platypush.message.event.ping.PingEvent',
|
||||||
|
'message': {
|
||||||
|
'foo': 'bar',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
event = PingEvent(
|
||||||
|
message={
|
||||||
|
'foo': 'bar',
|
||||||
|
'baz': 'clang',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert event.matches_condition(condition).is_match
|
||||||
|
|
||||||
|
event = PingEvent(
|
||||||
|
message={
|
||||||
|
'something': 'else',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert not event.matches_condition(condition).is_match
|
||||||
|
|
||||||
|
event = PingEvent(
|
||||||
|
message={
|
||||||
|
'foo': 'baz',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert not event.matches_condition(condition).is_match
|
||||||
|
|
||||||
|
|
||||||
|
def test_speech_recognized_event_parse():
|
||||||
|
"""
|
||||||
|
Test the event parsing and text extraction logic for the
|
||||||
|
SpeechRecognizedEvent.
|
||||||
|
"""
|
||||||
|
condition = EventCondition.build(
|
||||||
|
{
|
||||||
|
'type': 'platypush.message.event.assistant.SpeechRecognizedEvent',
|
||||||
|
'phrase': 'This is (the)? answer: ${answer}',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
event = SpeechRecognizedEvent(phrase="GARBAGE GARBAGE this is the answer: 42")
|
||||||
result = event.matches_condition(condition)
|
result = event.matches_condition(condition)
|
||||||
assert result.is_match
|
assert result.is_match
|
||||||
assert 'answer' in result.parsed_args
|
assert 'answer' in result.parsed_args
|
||||||
assert result.parsed_args['answer'] == '42'
|
assert result.parsed_args['answer'] == '42'
|
||||||
|
|
||||||
message = "what is not the answer? 43"
|
event = PingEvent(phrase="what is not the answer? 43")
|
||||||
event = PingEvent(message=message)
|
|
||||||
result = event.matches_condition(condition)
|
result = event.matches_condition(condition)
|
||||||
assert not result.is_match
|
assert not result.is_match
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue