Skip to content

Commit

Permalink
Fix/decouple from mycroft (#21)
Browse files Browse the repository at this point in the history
* fix/do not require mycroft

* fix/decouple_decorators_from_skills

being in the skills module was causing cyclic imports due to helper skill classes importing mycroft

* build tests

Co-authored-by: jarbasai <[email protected]>
  • Loading branch information
NeonJarbas and JarbasAl authored Jun 2, 2022
1 parent b6fbe4f commit 9ca2018
Show file tree
Hide file tree
Showing 18 changed files with 378 additions and 366 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/build_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.head_ref }}
- name: Setup Python
uses: actions/setup-python@v1
with:
Expand Down
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
Loading

0 comments on commit 9ca2018

Please sign in to comment.