Skip to content

Commit

Permalink
sync utils with core (#89)
Browse files Browse the repository at this point in the history
* sync utils with core

- make audio utils compatible and move missing helpers
- sync duplicated intent service utils

* sync EventSchedulerInterface

* audio unittests
  • Loading branch information
JarbasAl committed Dec 16, 2022
1 parent 8e0c4b7 commit 2f3a979
Show file tree
Hide file tree
Showing 4 changed files with 479 additions and 76 deletions.
92 changes: 71 additions & 21 deletions ovos_utils/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,40 +189,55 @@ def clear(self):
class EventSchedulerInterface:
"""Interface for accessing the event scheduler over the message bus."""

def __init__(self, name, sched_id=None, bus=None):
self.name = name
self.sched_id = sched_id
self.bus = bus or FakeBus()
def __init__(self, name=None, sched_id=None, bus=None, skill_id=None):
# NOTE: can not rename or move sched_id/name arguments to keep api compatibility
if name:
LOG.warning("name argument has been deprecated! use skill_id instead")
if sched_id:
LOG.warning("sched_id argument has been deprecated! use skill_id instead")

self.skill_id = skill_id or sched_id or name or self.__class__.__name__
self.bus = bus
self.events = EventContainer(bus)

self.scheduled_repeats = []

def set_bus(self, bus):
"""Attach the messagebus of the parent skill
Args:
bus (MessageBusClient): websocket connection to the messagebus
"""
self.bus = bus
self.events.set_bus(bus)

def set_id(self, sched_id):
self.sched_id = sched_id
"""Attach the skill_id of the parent skill
Args:
sched_id (str): skill_id of the parent skill
"""
# NOTE: can not rename sched_id kwarg to keep api compatibility
self.skill_id = sched_id

def _create_unique_name(self, name):
"""Return a name unique to this skill using the format
[skill_id]:[name].
Arguments:
Args:
name: Name to use internally
Returns:
str: name unique to this skill
"""
return str(self.sched_id) + ':' + (name or '')
return self.skill_id + ':' + (name or '')

def _schedule_event(self, handler, when, data, name,
repeat_interval=None, context=None):
"""Underlying method for schedule_event and schedule_repeating_event.
Takes scheduling information and sends it off on the message bus.
Arguments:
Args:
handler: method to be called
when (datetime): time (in system timezone) for first
calling the handler, or None to
Expand All @@ -237,31 +252,32 @@ def _schedule_event(self, handler, when, data, name,
if isinstance(when, (int, float)) and when >= 0:
when = datetime.now() + timedelta(seconds=when)
if not name:
name = self.name + handler.__name__
name = self.skill_id + handler.__name__
unique_name = self._create_unique_name(name)
if repeat_interval:
self.scheduled_repeats.append(name) # store "friendly name"

data = data or {}

def on_error(e):
LOG.exception('An error occured executing the scheduled event '
'{}'.format(repr(e)))
LOG.exception(f'An error occurred executing the scheduled event {e}')

wrapped = create_basic_wrapper(handler, on_error)
self.events.add(unique_name, wrapped, once=not repeat_interval)
event_data = {'time': time.mktime(when.timetuple()),
'event': unique_name,
'repeat': repeat_interval,
'data': data}
context = context or {}
context["skill_id"] = self.skill_id
self.bus.emit(Message('mycroft.scheduler.schedule_event',
data=event_data, context=context))

def schedule_event(self, handler, when, data=None, name=None,
context=None):
"""Schedule a single-shot event.
Arguments:
Args:
handler: method to be called
when (datetime/int/float): datetime (in system timezone) or
number of seconds in the future when the
Expand All @@ -280,7 +296,7 @@ def schedule_repeating_event(self, handler, when, interval,
data=None, name=None, context=None):
"""Schedule a repeating event.
Arguments:
Args:
handler: method to be called
when (datetime): time (in system timezone) for first
calling the handler, or None to
Expand All @@ -307,7 +323,7 @@ def schedule_repeating_event(self, handler, when, interval,
def update_scheduled_event(self, name, data=None):
"""Change data of event.
Arguments:
Args:
name (str): reference name of event (from original scheduling)
"""
data = data or {}
Expand All @@ -316,13 +332,13 @@ def update_scheduled_event(self, name, data=None):
'data': data
}
self.bus.emit(Message('mycroft.schedule.update_event',
data=data))
data=data, context={"skill_id": self.skill_id}))

def cancel_scheduled_event(self, name):
"""Cancel a pending event. The event will no longer be scheduled
to be executed
Arguments:
Args:
name (str): reference name of event (from original scheduling)
"""
unique_name = self._create_unique_name(name)
Expand All @@ -331,12 +347,13 @@ def cancel_scheduled_event(self, name):
self.scheduled_repeats.remove(name)
if self.events.remove(unique_name):
self.bus.emit(Message('mycroft.scheduler.remove_event',
data=data))
data=data,
context={"skill_id": self.skill_id}))

def get_scheduled_event_status(self, name):
"""Get scheduled event data and return the amount of time left
Arguments:
Args:
name (str): reference name of event (from original scheduling)
Returns:
Expand All @@ -348,8 +365,9 @@ def get_scheduled_event_status(self, name):
event_name = self._create_unique_name(name)
data = {'name': event_name}

reply_name = 'mycroft.event_status.callback.{}'.format(event_name)
msg = Message('mycroft.scheduler.get_event', data=data)
reply_name = f'mycroft.event_status.callback.{event_name}'
msg = Message('mycroft.scheduler.get_event', data=data,
context={"skill_id": self.skill_id})
status = self.bus.wait_for_response(msg, reply_type=reply_name)

if status:
Expand All @@ -372,3 +390,35 @@ def shutdown(self):
"""Shutdown the interface unregistering any event handlers."""
self.cancel_all_repeating_events()
self.events.clear()

@property
def sched_id(self):
"""DEPRECATED: do not use, method only for api backwards compatibility
Logs a warning and returns self.skill_id
"""
LOG.warning("self.sched_id has been deprecated! use self.skill_id instead")
return self.skill_id

@sched_id.setter
def sched_id(self, skill_id):
"""DEPRECATED: do not use, method only for api backwards compatibility
Logs a warning and sets self.skill_id
"""
LOG.warning("self.sched_id has been deprecated! use self.skill_id instead")
self.skill_id = skill_id

@property
def name(self):
"""DEPRECATED: do not use, method only for api backwards compatibility
Logs a warning and returns self.skill_id
"""
LOG.warning("self.name has been deprecated! use self.skill_id instead")
return self.skill_id

@name.setter
def name(self, skill_id):
"""DEPRECATED: do not use, method only for api backwards compatibility
Logs a warning and sets self.skill_id
"""
LOG.warning("self.name has been deprecated! use self.skill_id instead")
self.skill_id = skill_id
87 changes: 61 additions & 26 deletions ovos_utils/intents/intent_service_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ def __init__(self, bus=None):
self.registered_intents = []
self.detached_intents = []

@property
def intent_names(self):
return [a[0] for a in self.registered_intents + self.detached_intents]

def set_bus(self, bus):
self.bus = bus

Expand All @@ -109,22 +113,30 @@ def register_adapt_keyword(self, vocab_type, entity, aliases=None,
vocab_type(str): Keyword reference
entity (str): Primary keyword
aliases (list): List of alternative kewords
aliases (list): List of alternative keywords
"""
aliases = aliases or []
msg = dig_for_message() or Message("")
if "skill_id" not in msg.context:
msg.context["skill_id"] = self.skill_id

# TODO 22.02: Remove compatibility data
aliases = aliases or []
entity_data = {'entity_value': entity,
'entity_type': vocab_type,
'lang': lang}
compatibility_data = {'start': entity, 'end': vocab_type}

self.bus.emit(msg.forward("register_vocab",
{'start': entity,
'end': vocab_type,
'lang': lang}))
{**entity_data, **compatibility_data}))
for alias in aliases:
alias_data = {
'entity_value': alias,
'entity_type': vocab_type,
'alias_of': entity,
'lang': lang}
compatibility_data = {'start': alias, 'end': vocab_type}
self.bus.emit(msg.forward("register_vocab",
{'start': alias,
'end': vocab_type,
'alias_of': entity,
'lang': lang}))
{**alias_data, **compatibility_data}))

def register_adapt_regex(self, regex, lang=None):
"""Register a regex with the intent service.
Expand Down Expand Up @@ -156,27 +168,50 @@ def register_adapt_intent(self, name, intent_parser):
def detach_intent(self, intent_name):
"""Remove an intent from the intent service.
DEPRECATED: please use remove_intent instead, all other methods from this class expect intent_name,
this was the weird one expecting the internal munged intent_name with skill_id
The intent is saved in the list of detached intents for use when
re-enabling an intent.
Args:
intent_name(str): internal munged intent name (skill_id:name)
"""
name = intent_name.split(':')[1]
self.remove_intent(name)

def remove_intent(self, intent_name):
"""Remove an intent from the intent service.
The intent is saved in the list of detached intents for use when
re-enabling an intent.
Args:
intent_name(str): Intent reference
"""
# split skill_id if needed
if intent_name not in self and ":" in intent_name:
name = intent_name.split(':')[1]
else:
name = intent_name

if name in self:
msg = dig_for_message() or Message("")
if "skill_id" not in msg.context:
msg.context["skill_id"] = self.skill_id
self.bus.emit(msg.forward("detach_intent",
{"intent_name": intent_name}))
self.detached_intents.append((name, self.get_intent(name)))
msg = dig_for_message() or Message("")
if "skill_id" not in msg.context:
msg.context["skill_id"] = self.skill_id
if intent_name in self.intent_names:
self.detached_intents.append((intent_name, self.get_intent(intent_name)))
self.registered_intents = [pair for pair in self.registered_intents
if pair[0] != name]
if pair[0] != intent_name]
self.bus.emit(msg.forward("detach_intent",
{"intent_name": f"{self.skill_id}.{intent_name}"}))

def intent_is_detached(self, intent_name):
"""Determine if an intent is detached.
Args:
intent_name(str): Intent reference
Returns:
(bool) True if intent is found, else False.
"""
for (name, _) in self.detached_intents:
if name == intent_name:
return True
return False

def set_adapt_context(self, context, word, origin):
"""Set an Adapt context.
Expand Down Expand Up @@ -214,7 +249,7 @@ def register_padatious_intent(self, intent_name, filename, lang):
if not isinstance(filename, str):
raise ValueError('Filename path must be a string')
if not exists(filename):
raise FileNotFoundError('Unable to find "{}"'.format(filename))
raise FileNotFoundError(f'Unable to find "{filename}"')

data = {'file_name': filename,
'name': intent_name,
Expand Down Expand Up @@ -257,7 +292,7 @@ def __contains__(self, val):
return val in [i[0] for i in self.registered_intents]

def get_intent_names(self):
return [i[0] for i in self.registered_intents]
return self.intent_names

def detach_all(self):
for name in self.get_intent_names():
Expand All @@ -276,7 +311,7 @@ def get_intent(self, intent_name):
Returns:
Found intent or None if none were found.
"""
for name, intent in self:
for name, intent in self.registered_intents:
if name == intent_name:
return intent
for name, intent in self.detached_intents:
Expand Down

0 comments on commit 2f3a979

Please sign in to comment.