diff --git a/ovos_workshop/decorators/compat.py b/ovos_workshop/decorators/compat.py index a9952b9..d0c88c5 100644 --- a/ovos_workshop/decorators/compat.py +++ b/ovos_workshop/decorators/compat.py @@ -1,44 +1,3 @@ -from functools import wraps +# TODO - remove import next alpha +from ovos_utils import backwards_compat - -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 diff --git a/ovos_workshop/resource_files.py b/ovos_workshop/resource_files.py index b146d12..62bb408 100644 --- a/ovos_workshop/resource_files.py +++ b/ovos_workshop/resource_files.py @@ -357,6 +357,9 @@ def _read(self) -> str: continue yield line + def __iter__(self): + return iter(self._read()) + class QmlFile(ResourceFile): def _locate(self): diff --git a/ovos_workshop/skills/base.py b/ovos_workshop/skills/base.py index dca2940..c0a3305 100644 --- a/ovos_workshop/skills/base.py +++ b/ovos_workshop/skills/base.py @@ -36,6 +36,7 @@ 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 backwards_compat from ovos_utils import camel_case_split from ovos_utils import classproperty from ovos_utils.dialog import get_dialog, MustacheDialogRenderer @@ -54,7 +55,6 @@ from ovos_utils.parse import match_one from ovos_utils.process_utils import RuntimeRequirements from ovos_utils.skills import get_non_properties -from ovos_workshop.decorators.compat import backwards_compat from ovos_workshop.decorators.killable import AbortEvent from ovos_workshop.decorators.killable import killable_event, \ AbortQuestion @@ -846,9 +846,8 @@ def detach(self): """ Detach all intents for this skill from the intent_service. """ - for (name, _) in self.intent_service: - name = f'{self.skill_id}:{name}' - self.intent_service.detach_intent(name) + for name in self.intent_service.intent_names: + self.intent_service.remove_intent(name) def initialize(self): """ @@ -1540,16 +1539,7 @@ def remove_event(self, name: str) -> bool: """ return self.events.remove(name) - def _register_adapt_intent(self, - intent_parser: Union[IntentBuilder, Intent, str], - handler: callable): - """ - Register an adapt intent. - - Args: - intent_parser: Intent object to parse utterance for the handler. - handler (func): function to register with intent - """ + def _validate_intent_name(self, intent_parser: Union[IntentBuilder, Intent], handler: callable): # Default to the handler's function name if none given is_anonymous = not intent_parser.name name = intent_parser.name or handler.__name__ @@ -1560,15 +1550,48 @@ def _register_adapt_intent(self, while name in self.intent_service.intent_names: nbr += 1 name = f'{original_name}{nbr}' + elif name in self.intent_service.intent_names and \ not self.intent_service.intent_is_detached(name): raise ValueError(f'The intent name {name} is already taken') + return name + def _register_adapt_intent_classic(self, + intent_parser: Union[IntentBuilder, Intent, str], + handler: callable): + """ + < ovos-core 0.0.8 munging happened skill side, + since 0.0.8 pipeline plugins are skill_id aware out of the box + any needed munging happens plugin side + """ + name = self._validate_intent_name(intent_parser, handler) + + # old mycroft needs things munged before registering munge_intent_parser(intent_parser, name, self.skill_id) self.intent_service.register_adapt_intent(name, intent_parser) if handler: - self.add_event(intent_parser.name, handler, - 'mycroft.skill.handler') + self.add_event(intent_parser.name, handler, 'mycroft.skill.handler') + + @backwards_compat(classic_core=_register_adapt_intent_classic, + pre_008=_register_adapt_intent_classic) + def _register_adapt_intent(self, + intent_parser: Union[IntentBuilder, Intent, str], + handler: callable): + """ + < ovos-core 0.0.8 munging happened skill side, + since 0.0.8 pipeline plugins are skill_id aware out of the box + any needed munging happens plugin side + """ + name = self._validate_intent_name(intent_parser, handler) + + intent = self.intent_service.register_keyword_intent(name=name, + required=intent_parser.requires, + at_least_one=intent_parser.at_least_one, + optional=intent_parser.optional, + lang=self.lang, + skill_id=self.skill_id) + if handler: + self.add_event(intent.intent_message, handler, 'mycroft.skill.handler') def register_intent(self, intent_parser: Union[IntentBuilder, Intent, str], handler: callable): @@ -1616,16 +1639,21 @@ def register_intent_file(self, intent_file: str, handler: callable): handler: function to register with intent """ for lang in self._native_langs: - name = f'{self.skill_id}:{intent_file}' resources = self._load_lang(self.res_dir, lang) resource_file = ResourceFile(resources.types.intent, intent_file) if resource_file.file_path is None: self.log.error(f'Unable to find "{intent_file}"') continue - filename = str(resource_file.file_path) - self.intent_service.register_padatious_intent(name, filename, lang) + + samples = list(resource_file) + + name = intent_file.replace(".intent", "") # not munged! + intent = self.intent_service.register_intent(name=name, + samples=samples, + lang=lang, + skill_id=self.skill_id) if handler: - self.add_event(name, handler, 'mycroft.skill.handler') + self.add_event(intent.intent_message, handler, 'mycroft.skill.handler') def register_entity_file(self, entity_file: str): """ @@ -1651,10 +1679,10 @@ def register_entity_file(self, entity_file: str): if entity.file_path is None: self.log.error(f'Unable to find "{entity_file}"') continue - filename = str(entity.file_path) - name = f"{self.skill_id}:{basename(entity_file)}_" \ - f"{md5(entity_file.encode('utf-8')).hexdigest()}" - self.intent_service.register_padatious_entity(name, filename, lang) + samples = list(entity) + name = basename(entity_file) + self.intent_service.register_entity(name=name, samples=samples, + lang=lang, skill_id=self.skill_id) def handle_enable_intent(self, message: Message): """ @@ -1662,8 +1690,8 @@ def handle_enable_intent(self, message: Message): @param message: `mycroft.skill.enable_intent` Message """ intent_name = message.data['intent_name'] - for (name, _) in self.intent_service.detached_intents: - if name == intent_name: + for intent in self.intent_service.intents: + if intent.name == intent_name and intent.detached: return self.enable_intent(intent_name) def handle_disable_intent(self, message: Message): @@ -1672,8 +1700,8 @@ def handle_disable_intent(self, message: Message): @param message: `mycroft.skill.disable_intent` Message """ intent_name = message.data['intent_name'] - for (name, _) in self.intent_service.registered_intents: - if name == intent_name: + for intent in self.intent_service.intents: + if intent.name == intent_name and not intent.detached: return self.disable_intent(intent_name) def disable_intent(self, intent_name: str) -> bool: @@ -1686,16 +1714,9 @@ def disable_intent(self, intent_name: str) -> bool: Returns: bool: True if disabled, False if it wasn't registered """ - if intent_name in self.intent_service: + if intent_name in self.intent_service.intent_names: self.log.info('Disabling intent ' + intent_name) - name = f'{self.skill_id}:{intent_name}' - self.intent_service.detach_intent(name) - - langs = [self._core_lang] + self._secondary_langs - for lang in langs: - lang_intent_name = f'{name}_{lang}' - self.intent_service.detach_intent(lang_intent_name) - return True + return self.intent_service.disable_intent(intent_name) else: self.log.error(f'Could not disable {intent_name}, it hasn\'t been registered.') return False @@ -1710,15 +1731,9 @@ def enable_intent(self, intent_name: str) -> bool: Returns: bool: True if enabled, False if it wasn't registered """ - intent = self.intent_service.get_intent(intent_name) - if intent: - if ".intent" in intent_name: - self.register_intent_file(intent_name, None) - else: - intent.name = intent_name - self.register_intent(intent, None) + if intent_name in self.intent_service.intent_names: self.log.debug(f'Enabling intent {intent_name}') - return True + return self.intent_service.enable_intent(intent_name) else: self.log.error(f'Could not enable {intent_name}, it hasn\'t been registered.') return False @@ -1738,7 +1753,7 @@ def set_context(self, context: str, word: str = '', origin: str = ''): raise ValueError('Word should be a string') context = self._alphanumeric_skill_id + context - self.intent_service.set_adapt_context(context, word, origin) + self.intent_service.set_context(context, word, origin) def remove_context(self, context: str): """ @@ -1747,7 +1762,7 @@ def remove_context(self, context: str): if not isinstance(context, str): raise ValueError('context should be a string') context = self._alphanumeric_skill_id + context - self.intent_service.remove_adapt_context(context) + self.intent_service.remove_context(context) def handle_set_cross_context(self, message: Message): """ @@ -1803,10 +1818,10 @@ def register_vocabulary(self, entity: str, entity_type: str, @param entity_type: Intent handler entity name to associate entity to @param lang: language of `entity` (default self.lang) """ - keyword_type = self._alphanumeric_skill_id + entity_type - lang = lang or self.lang - self.intent_service.register_adapt_keyword(keyword_type, entity, - lang=lang) + self.intent_service.register_keyword(name=entity_type, + samples=[entity], + lang=lang or self.lang, + skill_id=self.skill_id) def register_regex(self, regex_str: str, lang: Optional[str] = None): """ @@ -1815,9 +1830,9 @@ def register_regex(self, regex_str: str, lang: Optional[str] = None): @param lang: language of regex_str (default self.lang) """ self.log.debug('registering regex string: ' + regex_str) - regex = munge_regex(regex_str, self.skill_id) - re.compile(regex) # validate regex - self.intent_service.register_adapt_regex(regex, lang=lang or self.lang) + name = "" # TODO - get from regex_str + self.intent_service.register_regex_entity(name="", samples=[regex_str], + lang=lang or self.lang, skill_id=self.skill_id) def speak(self, utterance: str, expect_response: bool = False, wait: bool = False, meta: Optional[dict] = None): @@ -1939,8 +1954,8 @@ def load_vocab_files(self, root_directory: Optional[str] = None): for line in skill_vocabulary[vocab_type]: entity = line[0] aliases = line[1:] - self.intent_service.register_adapt_keyword( - vocab_type, entity, aliases, lang) + self.intent_service.register_keyword(name=vocab_type, samples=[entity] + aliases, + lang=lang, skill_id=self.skill_id) def load_regex_files(self, root_directory=None): """ Load regex files found under the skill directory.""" @@ -1950,7 +1965,9 @@ def load_regex_files(self, root_directory=None): if resources.types.regex.base_directory is not None: regexes = resources.load_skill_regex(self._alphanumeric_skill_id) for regex in regexes: - self.intent_service.register_adapt_regex(regex, lang) + name = "" # TODO extract from regex + self.intent_service.register_regex_entity(name=name, samples=[regex], + lang=lang, skill_id=self.skill_id) def __handle_stop(self, message): """Handler for the "mycroft.stop" signal. Runs the user defined diff --git a/ovos_workshop/skills/common_query_skill.py b/ovos_workshop/skills/common_query_skill.py index f3757e4..1fc68cc 100644 --- a/ovos_workshop/skills/common_query_skill.py +++ b/ovos_workshop/skills/common_query_skill.py @@ -17,7 +17,7 @@ from ovos_utils.file_utils import resolve_resource_file from ovos_utils.log import LOG from ovos_workshop.skills.ovos import OVOSSkill -from ovos_workshop.decorators.compat import backwards_compat +from ovos_utils import backwards_compat class CQSMatchLevel(IntEnum): diff --git a/ovos_workshop/skills/fallback.py b/ovos_workshop/skills/fallback.py index 963f65a..057c3e3 100644 --- a/ovos_workshop/skills/fallback.py +++ b/ovos_workshop/skills/fallback.py @@ -22,7 +22,7 @@ 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_workshop.decorators.compat import backwards_compat +from ovos_utils import backwards_compat from ovos_workshop.permissions import FallbackMode from ovos_workshop.skills.ovos import OVOSSkill diff --git a/ovos_workshop/skills/mycroft_skill.py b/ovos_workshop/skills/mycroft_skill.py index 07e7abc..bc3fd86 100644 --- a/ovos_workshop/skills/mycroft_skill.py +++ b/ovos_workshop/skills/mycroft_skill.py @@ -19,7 +19,7 @@ from ovos_bus_client import MessageBusClient, Message from ovos_utils.log import LOG, log_deprecation, deprecated -from ovos_workshop.decorators.compat import backwards_compat +from ovos_utils import backwards_compat from ovos_workshop.skills.base import BaseSkill, is_classic_core diff --git a/ovos_workshop/skills/ovos.py b/ovos_workshop/skills/ovos.py index 7dca9a0..02cb587 100644 --- a/ovos_workshop/skills/ovos.py +++ b/ovos_workshop/skills/ovos.py @@ -11,7 +11,7 @@ from ovos_utils.skills.audioservice import OCPInterface from ovos_utils.skills.settings import PrivateSettings from ovos_utils.sound import play_audio -from ovos_workshop.decorators.compat import backwards_compat +from ovos_utils import backwards_compat from ovos_workshop.decorators.layers import IntentLayers from ovos_workshop.resource_files import SkillResources from ovos_workshop.skills.base import BaseSkill