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

Fix/decouple from mycroft #21

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix/decouple_decorators_from_skills
being in the skills module was causing cyclic imports due to helper skill classes importing mycroft
  • Loading branch information
JarbasAl committed Jun 2, 2022
commit e02252e17293e0a170ee7bcdb87c2f47cd412a8a
4 changes: 2 additions & 2 deletions ovos_workshop/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from ovos_workshop.app import OVOSAbstractApplication
from ovos_workshop.skills.decorators import *
from ovos_workshop.skills.decorators.killable import killable_event, \
from ovos_workshop.decorators import *
from ovos_workshop.decorators.killable import killable_event, \
AbortEvent, AbortQuestion
from ovos_workshop.skills.layers import IntentLayers
4 changes: 2 additions & 2 deletions ovos_workshop/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@

from ovos_utils.skills import get_non_properties
from ovos_utils.intents import IntentBuilder, Intent, AdaptIntent
from ovos_workshop.skills.decorators import *
from ovos_workshop.skills.decorators.killable import killable_event, \
from ovos_workshop.decorators import *
from ovos_workshop.decorators.killable import killable_event, \
AbortEvent, AbortQuestion
from ovos_workshop.skills.layers import IntentLayers

Expand Down
22 changes: 22 additions & 0 deletions ovos_workshop/decorators/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from ovos_workshop.decorators.killable import \
killable_intent, killable_event
from ovos_workshop.decorators.layers import enables_layer, \
disables_layer, layer_intent, removes_layer, resets_layers, replaces_layer
from ovos_workshop.decorators.converse import converse_handler
from ovos_workshop.decorators.fallback_handler import fallback_handler


def resting_screen_handler(name):
"""Decorator for adding a method as an resting screen handler.

If selected will be shown on screen when device enters idle mode.
"""

def real_decorator(func):
# Store the resting information inside the function
# This will be used later in register_resting_screen
if not hasattr(func, 'resting_handler'):
func.resting_handler = name
return func

return real_decorator
11 changes: 11 additions & 0 deletions ovos_workshop/decorators/converse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@


def converse_handler():
"""Decorator for aliasing a method as the converse method"""

def real_decorator(func):
if not hasattr(func, 'converse'):
func.converse = True
return func

return real_decorator
8 changes: 8 additions & 0 deletions ovos_workshop/decorators/fallback_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

def fallback_handler(priority=50):
def real_decorator(func):
if not hasattr(func, 'fallback_priority'):
func.fallback_priority = priority
return func

return real_decorator
80 changes: 80 additions & 0 deletions ovos_workshop/decorators/killable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import time
from ovos_utils import create_killable_daemon
from ovos_utils.messagebus import Message
import threading
from inspect import signature
from functools import wraps


class AbortEvent(StopIteration):
""" abort bus event handler """


class AbortIntent(AbortEvent):
""" abort intent parsing """


class AbortQuestion(AbortEvent):
""" gracefully abort get_response queries """


def killable_intent(msg="mycroft.skills.abort_execution",
callback=None, react_to_stop=True, call_stop=True,
stop_tts=True):
return killable_event(msg, AbortIntent, callback, react_to_stop,
call_stop, stop_tts)


def killable_event(msg="mycroft.skills.abort_execution", exc=AbortEvent,
callback=None, react_to_stop=False, call_stop=False,
stop_tts=False):
# Begin wrapper
def create_killable(func):

@wraps(func)
def call_function(*args, **kwargs):
skill = args[0]
t = create_killable_daemon(func, args, kwargs, autostart=False)

def abort(_):
if not t.is_alive():
return
if stop_tts:
skill.bus.emit(Message("mycroft.audio.speech.stop"))
if call_stop:
# call stop on parent skill
skill.stop()

# ensure no orphan get_response daemons
# this is the only killable daemon that core itself will
# create, users should also account for this condition with
# callbacks if using the decorator for other purposes
skill._handle_killed_wait_response()

try:
while t.is_alive():
t.raise_exc(exc)
time.sleep(0.1)
except threading.ThreadError:
pass # already killed
except AssertionError:
pass # could not determine thread id ?
if callback is not None:
if len(signature(callback).parameters) == 1:
# class method, needs self
callback(args[0])
else:
callback()

# save reference to threads so they can be killed later
skill._threads.append(t)
skill.bus.once(msg, abort)
if react_to_stop:
skill.bus.once(args[0].skill_id + ".stop", abort)
t.start()
return t

return call_function

return create_killable

130 changes: 130 additions & 0 deletions ovos_workshop/decorators/layers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import inspect
from functools import wraps

from ovos_workshop.skills.layers import IntentLayers


def dig_for_skill(max_records: int = 10):
from ovos_workshop.app import OVOSAbstractApplication
from ovos_workshop.skills import MycroftSkill
stack = inspect.stack()[1:] # First frame will be this function call
stack = stack if len(stack) <= max_records else stack[:max_records]
for record in stack:
args = inspect.getargvalues(record.frame)
if args.locals.get("self"):
obj = args.locals["self"]
if isinstance(obj, MycroftSkill) or \
isinstance(obj, OVOSAbstractApplication):
return obj
elif args.locals.get("args"):
for obj in args.locals["args"]:
if isinstance(obj, MycroftSkill) or \
isinstance(obj, OVOSAbstractApplication):
return obj
return None


def enables_layer(layer_name):
def layer_handler(func):
@wraps(func)
def call_function(*args, **kwargs):
skill = dig_for_skill()
skill.intent_layers = skill.intent_layers or \
IntentLayers().bind(skill)
func(*args, **kwargs)
skill.intent_layers.activate_layer(layer_name)

return call_function

return layer_handler


def disables_layer(layer_name):
def layer_handler(func):
@wraps(func)
def call_function(*args, **kwargs):
skill = dig_for_skill()
skill.intent_layers = skill.intent_layers or \
IntentLayers().bind(skill)
func(*args, **kwargs)
skill.intent_layers.deactivate_layer(layer_name)

return call_function

return layer_handler


def replaces_layer(layer_name, intent_list):
def layer_handler(func):
@wraps(func)
def call_function(*args, **kwargs):
skill = dig_for_skill()
skill.intent_layers = skill.intent_layers or \
IntentLayers().bind(skill)
func(*args, **kwargs)
skill.intent_layers.replace_layer(layer_name, intent_list)

return call_function

return layer_handler


def removes_layer(layer_name, intent_list):
def layer_handler(func):
@wraps(func)
def call_function(*args, **kwargs):
skill = dig_for_skill()
skill.intent_layers = skill.intent_layers or \
IntentLayers().bind(skill)
func(*args, **kwargs)
skill.intent_layers.replace_layer(layer_name, intent_list)

return call_function

return layer_handler


def resets_layers():
def layer_handler(func):
@wraps(func)
def call_function(*args, **kwargs):
skill = dig_for_skill()
skill.intent_layers = skill.intent_layers or \
IntentLayers().bind(skill)
func(*args, **kwargs)
skill.intent_layers.disable()

return call_function

return layer_handler


def layer_intent(intent_parser, layer_name):
"""Decorator for adding a method as an intent handler belonging to an
intent layer."""

def real_decorator(func):
# Store the intent_parser inside the function
# This will be used later to call register_intent
if not hasattr(func, 'intents'):
func.intents = []
if not hasattr(func, 'intent_layers'):
func.intent_layers = {}

func.intents.append(intent_parser)
if layer_name not in func.intent_layers:
func.intent_layers[layer_name] = []

# get intent_name
if hasattr(intent_parser, "build"):
intent = intent_parser.build()
intent_name = intent.name or func.__name__
elif hasattr(intent_parser, "name"):
intent_name = intent_parser.name
else:
intent_name = intent_parser

func.intent_layers[layer_name].append(intent_name)
return func

return real_decorator
102 changes: 102 additions & 0 deletions ovos_workshop/decorators/ocp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from functools import wraps
from ovos_workshop.skills.layers import IntentLayers
from ovos_plugin_common_play.ocp import *
from ovos_plugin_common_play.ocp.status import *


def ocp_search():
"""Decorator for adding a method as an common play search handler."""

def real_decorator(func):
# Store the flag inside the function
# This will be used later to identify the method
if not hasattr(func, 'is_ocp_search_handler'):
func.is_ocp_search_handler = True

return func

return real_decorator


def ocp_play():
"""Decorator for adding a method as an common play search handler."""

def real_decorator(func):
# Store the flag inside the function
# This will be used later to identify the method
if not hasattr(func, 'is_ocp_playback_handler'):
func.is_ocp_playback_handler = True

return func

return real_decorator


def ocp_previous():
"""Decorator for adding a method as an common play prev handler."""

def real_decorator(func):
# Store the flag inside the function
# This will be used later to identify the method
if not hasattr(func, 'is_ocp_prev_handler'):
func.is_ocp_prev_handler = True

return func

return real_decorator


def ocp_next():
"""Decorator for adding a method as an common play next handler."""

def real_decorator(func):
# Store the flag inside the function
# This will be used later to identify the method
if not hasattr(func, 'is_ocp_next_handler'):
func.is_ocp_next_handler = True

return func

return real_decorator


def ocp_pause():
"""Decorator for adding a method as an common play pause handler."""

def real_decorator(func):
# Store the flag inside the function
# This will be used later to identify the method
if not hasattr(func, 'is_ocp_pause_handler'):
func.is_ocp_pause_handler = True

return func

return real_decorator


def ocp_resume():
"""Decorator for adding a method as an common play resume handler."""

def real_decorator(func):
# Store the flag inside the function
# This will be used later to identify the method
if not hasattr(func, 'is_ocp_resume_handler'):
func.is_ocp_resume_handler = True

return func

return real_decorator


def ocp_featured_media():
"""Decorator for adding a method as an common play search handler."""

def real_decorator(func):
# Store the flag inside the function
# This will be used later to identify the method
if not hasattr(func, 'is_ocp_featured_handler'):
func.is_ocp_featured_handler = True

return func

return real_decorator
2 changes: 1 addition & 1 deletion ovos_workshop/skills/common_play.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from inspect import signature
from threading import Event
from ovos_workshop.skills.decorators.ocp import *
from ovos_workshop.decorators.ocp import *
from ovos_workshop.skills.ovos import OVOSSkill, MycroftSkill
from mycroft_bus_client import Message
from ovos_utils.log import LOG
Expand Down
Loading