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

feat/compat_handler_decorator #131

Merged
merged 3 commits into from
Sep 21, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions ovos_workshop/decorators/compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from functools import wraps


def backwards_compat(classic_core=None, pre_008=None, no_core=None):
"""
Decorator to run a different method if specific ovos-core versions are detected
"""

def backwards_compat_decorator(func):
is_classic = False
is_old = False
is_standalone = True
try:
from mycroft.version import CORE_VERSION_STR # all classic mycroft and ovos versions
is_classic = True
is_standalone = False

try:
from ovos_core.version import OVOS_VERSION_MINOR # ovos-core >= 0.0.8
is_classic = False
except ImportError:
is_old = True
try:
from mycroft.version import OVOS_VERSION_MINOR # ovos-core <= 0.0.7
is_classic = False
except:
is_standalone = True

except:
is_standalone = True

@wraps(func)
def func_wrapper(*args, **kwargs):
if is_classic and callable(classic_core):
return classic_core(*args, **kwargs)
if is_old and callable(pre_008):
return pre_008(*args, **kwargs)
if is_standalone and callable(no_core):
return no_core(*args, **kwargs)
return func(*args, **kwargs)

return func_wrapper

return backwards_compat_decorator
90 changes: 54 additions & 36 deletions ovos_workshop/skills/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,18 @@
from threading import Event, RLock
from typing import List, Optional, Dict, Callable, Union

from ovos_bus_client import MessageBusClient
from ovos_bus_client.session import SessionManager
from json_database import JsonStorage
from lingua_franca.format import pronounce_number, join_list
from lingua_franca.parse import yes_or_no, extract_number
from ovos_backend_client.api import EmailApi, MetricsApi
from ovos_bus_client.message import Message, dig_for_message
from ovos_config.config import Configuration
from ovos_config.locations import get_xdg_config_save_path

from ovos_bus_client import MessageBusClient
from ovos_bus_client.message import Message, dig_for_message
from ovos_bus_client.session import SessionManager
from ovos_utils import camel_case_split
from ovos_utils import classproperty
from ovos_utils.dialog import get_dialog, MustacheDialogRenderer
from ovos_utils.enclosure.api import EnclosureAPI
from ovos_utils.events import EventContainer, EventSchedulerInterface
Expand All @@ -52,9 +54,7 @@
from ovos_utils.parse import match_one
from ovos_utils.process_utils import RuntimeRequirements
from ovos_utils.skills import get_non_properties
from ovos_utils.sound import play_acknowledge_sound
from ovos_utils import classproperty

from ovos_workshop.decorators.compat import backwards_compat
from ovos_workshop.decorators.killable import AbortEvent
from ovos_workshop.decorators.killable import killable_event, \
AbortQuestion
Expand All @@ -64,6 +64,18 @@
from ovos_workshop.settings import SkillSettingsManager


def is_classic_core():
try:
from mycroft.version import OVOS_VERSION_STR
return False
except:
try:
import mycroft
return True
except:
return False


# backwards compat alias
class SkillNetworkRequirements(RuntimeRequirements):
def __init__(self, *args, **kwargs):
Expand All @@ -73,23 +85,6 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)


def is_classic_core() -> bool:
"""
Check if the current core is the classic mycroft-core
"""
try:
from mycroft.version import OVOS_VERSION_STR
return False # ovos-core
except ImportError:
try:
log_deprecation("Support for `mycroft_core` will be deprecated",
"0.1.0")
from mycroft.version import CORE_VERSION_STR
return True # mycroft-core
except ImportError:
return False # standalone


def simple_trace(stack_trace: List[str]) -> str:
"""
Generate a simplified traceback.
Expand Down Expand Up @@ -474,7 +469,7 @@ def _secondary_langs(self) -> List[str]:
work in regular mycroft-core it was made private!
"""
return [lang.lower() for lang in self.config_core.get(
'secondary_langs', []) if lang != self._core_lang]
'secondary_langs', []) if lang != self._core_lang]

# property not present in mycroft-core
@property
Expand Down Expand Up @@ -713,6 +708,26 @@ def _load_lang(self, root_directory: Optional[str] = None,
skill_id=self.skill_id)
return self._lang_resources[lang]

def __bind_classic(self, bus):
self._bus = bus
self.events.set_bus(bus)
self.intent_service.set_bus(bus)
self.event_scheduler.set_bus(bus)
self._enclosure.set_bus(bus)
self._register_system_event_handlers()
self._register_public_api()
log_deprecation("Support for mycroft-core is deprecated",
"0.1.0")
# inject ovos exclusive features in vanilla mycroft-core
# if possible
# limited support for missing skill deactivated event
# TODO - update ConverseTracker
ConverseTracker.connect_bus(self.bus) # pull/1468
self.add_event("converse.skill.deactivated",
self._handle_skill_deactivated,
speak_errors=False)

@backwards_compat(classic_core=__bind_classic)
def bind(self, bus: MessageBusClient):
"""
Register MessageBusClient with skill.
Expand All @@ -727,18 +742,6 @@ def bind(self, bus: MessageBusClient):
self._register_system_event_handlers()
self._register_public_api()

if is_classic_core():
log_deprecation("Support for mycroft-core is deprecated",
"0.1.0")
# inject ovos exclusive features in vanilla mycroft-core
# if possible
# limited support for missing skill deactivated event
# TODO - update ConverseTracker
ConverseTracker.connect_bus(self.bus) # pull/1468
self.add_event("converse.skill.deactivated",
self._handle_skill_deactivated,
speak_errors=False)

def _register_public_api(self):
"""
Find and register API methods decorated with `@api_method` and create a
Expand Down Expand Up @@ -1009,6 +1012,7 @@ def __get_response(self):
Returns:
str: user's response or None on a timeout
"""

# TODO: Support `message` signature like default?
def converse(utterances, lang=None):
converse.response = utterances[0] if utterances else None
Expand Down Expand Up @@ -1177,6 +1181,20 @@ def _real_wait_response(self, is_cancel, validator, on_fail, num_retries):
else:
self.bus.emit(msg)

@staticmethod
def acknowledge():
"""
Acknowledge a successful request.

This method plays a sound to acknowledge a request that does not
require a verbal response. This is intended to provide simple feedback
to the user that their request was handled successfully.
"""
# DEPRECATED - note that this is a staticmethod and uses the old endpoint
# the OVOSSkill class does things properly
from ovos_utils.sound import play_acknowledge_sound
return play_acknowledge_sound()

def ask_yesno(self, prompt: str,
data: Optional[dict] = None) -> Optional[str]:
"""
Expand Down
29 changes: 14 additions & 15 deletions ovos_workshop/skills/common_query_skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@

from ovos_utils.file_utils import resolve_resource_file
from ovos_utils.log import LOG
from ovos_workshop.skills.ovos import OVOSSkill, is_classic_core
from ovos_workshop.skills.ovos import OVOSSkill
from ovos_workshop.decorators.compat import backwards_compat


class CQSMatchLevel(IntEnum):
Expand Down Expand Up @@ -187,6 +188,17 @@ def __calc_confidence(self, match, phrase, level, answer):

return confidence

def __handle_query_classic(self, message):
"""does not perform self.speak, < 0.0.8 this is done by core itself"""
if message.data["skill_id"] != self.skill_id:
# Not for this skill!
return
phrase = message.data["phrase"]
data = message.data.get("callback_data") or {}
# Invoke derived class to provide playback data
self.CQS_action(phrase, data)

@backwards_compat(classic_core=__handle_query_classic, pre_008=__handle_query_classic)
def __handle_query_action(self, message):
"""Message handler for question:action.

Expand All @@ -199,20 +211,7 @@ def __handle_query_action(self, message):
phrase = message.data["phrase"]
data = message.data.get("callback_data") or {}
if data.get("answer"):
# check core version, ovos-core does this speak call itself up to version 0.0.8a4
core_speak = is_classic_core()
if not core_speak:
try:
from mycroft.version import OVOS_VERSION_MAJOR, OVOS_VERSION_MINOR, OVOS_VERSION_BUILD, OVOS_VERSIOM_ALPHA
if OVOS_VERSION_MAJOR == 0 and OVOS_VERSION_MINOR == 0 and OVOS_VERSION_BUILD < 8:
core_speak = True
elif OVOS_VERSION_MAJOR == 0 and OVOS_VERSION_MINOR == 0 and OVOS_VERSION_BUILD == 8 and \
OVOS_VERSIOM_ALPHA < 5:
core_speak = True
except ImportError:
pass
if not core_speak:
self.speak(data["answer"])
self.speak(data["answer"])
# Invoke derived class to provide playback data
self.CQS_action(phrase, data)

Expand Down
39 changes: 16 additions & 23 deletions ovos_workshop/skills/fallback.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@
import operator
from typing import Optional, List, Callable, Tuple

from ovos_config import Configuration

from ovos_bus_client import MessageBusClient
from ovos_utils.log import LOG
from ovos_utils.messagebus import get_handler_name, Message
from ovos_utils.metrics import Stopwatch
from ovos_utils.skills import get_non_properties
from ovos_config import Configuration
from ovos_workshop.decorators.compat import backwards_compat
from ovos_workshop.permissions import FallbackMode
from ovos_workshop.skills.ovos import OVOSSkill, is_classic_core
from ovos_workshop.skills.ovos import OVOSSkill


class _MutableFallback(type(OVOSSkill)):
Expand Down Expand Up @@ -59,31 +61,22 @@ class FallbackSkill(_MetaFB, metaclass=_MutableFallback):
A Fallback can either observe or consume an utterance. A consumed
utterance will not be seen by any other Fallback handlers.
"""
def __new__(cls, *args, **kwargs):
def __new__classic__(cls, *args, **kwargs):
if cls is FallbackSkill:
# direct instantiation of class, dynamic wizardry or unittests
# direct instantiation of class, dynamic wizardry for unittests
# return V2 as expected, V1 will eventually be dropped
return FallbackSkillV2(*args, **kwargs)
cls.__bases__ = (FallbackSkillV1, FallbackSkill, _MetaFB)
return super().__new__(cls, *args, **kwargs)

is_old = is_classic_core()
if not is_old:
try:
from mycroft.version import OVOS_VERSION_MAJOR, \
OVOS_VERSION_MINOR, OVOS_VERSION_BUILD, OVOS_VERSION_ALPHA
if OVOS_VERSION_MAJOR == 0 and OVOS_VERSION_MINOR == 0 and \
OVOS_VERSION_BUILD < 8:
is_old = True
elif OVOS_VERSION_MAJOR == 0 and OVOS_VERSION_MINOR == 0 and \
OVOS_VERSION_BUILD == 8 and 0 < OVOS_VERSION_ALPHA < 5:
is_old = True
except ImportError:
pass
if is_old:
LOG.debug("Using V1 Fallback")
cls.__bases__ = (FallbackSkillV1, FallbackSkill, _MetaFB)
else:
LOG.debug("Using V2 Fallback")
cls.__bases__ = (FallbackSkillV2, FallbackSkill, _MetaFB)
@backwards_compat(classic_core=__new__classic__,
pre_008=__new__classic__)
def __new__(cls, *args, **kwargs):
if cls is FallbackSkill:
# direct instantiation of class, dynamic wizardry for unittests
# return V2 as expected, V1 will eventually be dropped
return FallbackSkillV2(*args, **kwargs)
cls.__bases__ = (FallbackSkillV2, FallbackSkill, _MetaFB)
return super().__new__(cls, *args, **kwargs)

@classmethod
Expand Down
Loading
Loading