Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sync utils with core #89

Merged
merged 3 commits into from
Dec 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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