From 4d3b87010485dd772f6cf40806c9b0210d07f220 Mon Sep 17 00:00:00 2001 From: Daniel McKnight Date: Thu, 9 May 2024 14:41:42 -0700 Subject: [PATCH] Remove unused patches Replace remaining `mycroft` imports with new package paths Cleanup unused imports --- neon_core/__init__.py | 12 - neon_core/launcher.py | 4 +- neon_core/skills/__init__.py | 8 +- neon_core/skills/intent_service.py | 2 +- neon_core/skills/patched_common_query.py | 290 ----------------------- neon_core/util/skill_utils.py | 1 - 6 files changed, 5 insertions(+), 312 deletions(-) delete mode 100644 neon_core/skills/patched_common_query.py diff --git a/neon_core/__init__.py b/neon_core/__init__.py index 05109769d..718d1b001 100644 --- a/neon_core/__init__.py +++ b/neon_core/__init__.py @@ -25,15 +25,3 @@ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -# TODO: Patching for ovos-core 0.0.7 -import ovos_utils.messagebus -from ovos_utils.events import get_handler_name, create_wrapper, EventContainer -ovos_utils.messagebus.get_handler_name = get_handler_name -ovos_utils.messagebus.create_wrapper = create_wrapper -ovos_utils.messagebus.EventContainer = EventContainer - - -# TODO: Patching out Backend Client -from ovos_backend_client.backends.offline import OfflineBackend diff --git a/neon_core/launcher.py b/neon_core/launcher.py index 726d2ab54..a7d48c92f 100644 --- a/neon_core/launcher.py +++ b/neon_core/launcher.py @@ -26,8 +26,8 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from mycroft.lock import Lock -from mycroft.util import wait_for_exit_signal, reset_sigint_handler +from ovos_utils import wait_for_exit_signal +from ovos_utils.process_utils import reset_sigint_handler, PIDLock as Lock from neon_audio.service import NeonPlaybackService from neon_messagebus.service import NeonBusService diff --git a/neon_core/skills/__init__.py b/neon_core/skills/__init__.py index 2175c01ba..23c3edf30 100644 --- a/neon_core/skills/__init__.py +++ b/neon_core/skills/__init__.py @@ -26,12 +26,8 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from neon_core.skills.decorators import intent_handler, intent_file_handler, \ - resting_screen_handler, conversational_intent - -# TODO: Resolve remote config bug in skill settings -import neon_core.skills.patched_skill_settings - +# TODO: Deprecate `conversational_intent` +from neon_core.skills.decorators import conversational_intent # Backwards-compat import from ovos_workshop.decorators import intent_handler, intent_file_handler, \ diff --git a/neon_core/skills/intent_service.py b/neon_core/skills/intent_service.py index f7535f63f..1c5c35165 100644 --- a/neon_core/skills/intent_service.py +++ b/neon_core/skills/intent_service.py @@ -46,7 +46,7 @@ from neon_core.configuration import Configuration from neon_core.language import get_lang_config -from ovos_core.intent_services import IntentService, ConverseService +from ovos_core.intent_services import IntentService, ConverseService, IntentMatch try: from neon_utterance_translator_plugin import UtteranceTranslator diff --git a/neon_core/skills/patched_common_query.py b/neon_core/skills/patched_common_query.py deleted file mode 100644 index 11c58f95f..000000000 --- a/neon_core/skills/patched_common_query.py +++ /dev/null @@ -1,290 +0,0 @@ -# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework -# All trademark and other rights reserved by their respective owners -# Copyright 2008-2022 Neongecko.com Inc. -# Contributors: Daniel McKnight, Guy Daniels, Elon Gasper, Richard Leeds, -# Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo -# BSD-3 License -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from this -# software without specific prior written permission. -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR -# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import re -import time -from dataclasses import dataclass -from itertools import chain -from threading import Event -from typing import Dict -from ovos_bus_client.session import SessionManager -from ovos_bus_client.message import Message, dig_for_message -from ovos_utils import flatten_list -from ovos_bus_client.apis.enclosure import EnclosureAPI -from ovos_bus_client.util import get_message_lang -from ovos_utils.log import LOG -from ovos_utils.messagebus import get_message_lang -from ovos_workshop.resource_files import CoreResources - -from mycroft.skills.intent_services.base import IntentMatch - -# TODO: Timeout from config -# TODO: Port to ovos-core -EXTENSION_TIME = 15 -MIN_RESPONSE_WAIT = 3 - - -@dataclass -class Query: - session_id: str - query: str - replies: list = None - extensions: list = None - query_time: float = time.time() - timeout_time: float = time.time() + 1 - responses_gathered: Event = Event() - completed: Event = Event() - answered: bool = False - - -class CommonQuery: - def __init__(self, bus): - self.bus = bus - self.skill_id = "common_query.neongeckocom" # fake skill - self.active_queries: Dict[str, Query] = dict() - self.enclosure = EnclosureAPI(self.bus, self.skill_id) - self._vocabs = {} - self.bus.on('question:query.response', self.handle_query_response) - self.bus.on('common_query.question', self.handle_question) - # TODO: Register available CommonQuery skills - - def voc_match(self, utterance, voc_filename, lang, exact=False): - """Determine if the given utterance contains the vocabulary provided. - - By default the method checks if the utterance contains the given vocab - thereby allowing the user to say things like "yes, please" and still - match against "Yes.voc" containing only "yes". An exact match can be - requested. - - The method checks the "res/text/{lang}" folder of mycroft-core. - The result is cached to avoid hitting the disk each time the method is called. - - Args: - utterance (str): Utterance to be tested - voc_filename (str): Name of vocabulary file (e.g. 'yes' for - 'res/text/en-us/yes.voc') - lang (str): Language code, defaults to self.lang - exact (bool): Whether the vocab must exactly match the utterance - - Returns: - bool: True if the utterance has the given vocabulary it - """ - match = False - - if lang not in self._vocabs: - resources = CoreResources(language=lang) - vocab = resources.load_vocabulary_file(voc_filename) - self._vocabs[lang] = list(chain(*vocab)) - - if utterance: - if exact: - # Check for exact match - match = any(i.strip() == utterance - for i in self._vocabs[lang]) - else: - # Check for matches against complete words - match = any([re.match(r'.*\b' + i + r'\b.*', utterance) - for i in self._vocabs[lang]]) - - return match - - def is_question_like(self, utterance, lang): - # skip utterances with less than 3 words - if len(utterance.split(" ")) < 3: - return False - # skip utterances meant for common play - if self.voc_match(utterance, "common_play", lang): - return False - return True - - def match(self, utterances, lang, message): - """Send common query request and select best response - - Args: - utterances (list): List of tuples, - utterances and normalized version - lang (str): Language code - message: Message for session context - Returns: - IntentMatch or None - """ - # we call flatten in case someone is sending the old style list of tuples - utterances = flatten_list(utterances) - match = None - for utterance in utterances: - if self.is_question_like(utterance, lang): - message.data["lang"] = lang # only used for speak - message.data["utterance"] = utterance - answered = self.handle_question(message) - if answered: - match = IntentMatch('CommonQuery', None, {}, None) - break - return match - - def handle_question(self, message): - """ Send the phrase to the CommonQuerySkills and prepare for handling - the replies. - """ - utt = message.data.get('utterance') - sid = SessionManager.get(message).session_id - # TODO: Why are defaults not creating new objects on init? - query = Query(session_id=sid, query=utt, replies=[], extensions=[], - query_time=time.time(), timeout_time=time.time() + 1, - responses_gathered=Event(), completed=Event(), - answered=False) - assert query.responses_gathered.is_set() is False - assert query.completed.is_set() is False - self.active_queries[sid] = query - self.enclosure.mouth_think() - - LOG.info(f'Searching for {utt}') - # Send the query to anyone listening for them - msg = message.reply('question:query', data={'phrase': utt}) - if "skill_id" not in msg.context: - msg.context["skill_id"] = self.skill_id - self.bus.emit(msg) - - query.timeout_time = time.time() + 1 - timeout = False - while not query.responses_gathered.wait(EXTENSION_TIME): - if time.time() > query.timeout_time + 1: - LOG.debug(f"Timeout gathering responses ({query.session_id})") - timeout = True - break - - # forcefully timeout if search is still going - if timeout: - LOG.warning(f"Timed out getting responses for: {query.query}") - self._query_timeout(message) - if not query.completed.wait(10): - raise TimeoutError("Timed out processing responses") - answered = bool(query.answered) - self.active_queries.pop(sid) - LOG.debug(f"answered={answered}|" - f"remaining active_queries={len(self.active_queries)}") - return answered - - def handle_query_response(self, message): - search_phrase = message.data['phrase'] - skill_id = message.data['skill_id'] - searching = message.data.get('searching') - answer = message.data.get('answer') - - query = self.active_queries.get(SessionManager.get(message).session_id) - if not query: - LOG.warning(f"No active query for: {search_phrase}") - # Manage requests for time to complete searches - if searching: - LOG.debug(f"{skill_id} is searching") - # request extending the timeout by EXTENSION_TIME - query.timeout_time = time.time() + EXTENSION_TIME - # TODO: Perhaps block multiple extensions? - if skill_id not in query.extensions: - query.extensions.append(skill_id) - else: - # Search complete, don't wait on this skill any longer - if answer: - LOG.info(f'Answer from {skill_id}') - query.replies.append(message.data) - - # Remove the skill from list of timeout extensions - if skill_id in query.extensions: - LOG.debug(f"Done waiting for {skill_id}") - query.extensions.remove(skill_id) - - time_to_wait = query.query_time + MIN_RESPONSE_WAIT - time.time() - if time_to_wait > 0: - LOG.debug(f"Waiting {time_to_wait}s before checking extensions") - query.responses_gathered.wait(time_to_wait) - # not waiting for any more skills - if not query.extensions: - LOG.debug(f"No more skills to wait for ({query.session_id})") - query.responses_gathered.set() - - def _query_timeout(self, message): - query = self.active_queries.get(SessionManager.get(message).session_id) - LOG.info(f'Check responses with {len(query.replies)} replies') - search_phrase = message.data.get('phrase', "") - if query.extensions: - query.extensions = [] - self.enclosure.mouth_reset() - - # Look at any replies that arrived before the timeout - # Find response(s) with the highest confidence - best = None - ties = [] - for response in query.replies: - if not best or response['conf'] > best['conf']: - best = response - ties = [] - elif response['conf'] == best['conf']: - ties.append(response) - - if best: - if ties: - # TODO: Ask user to pick between ties or do it automagically - pass - - # invoke best match - self.speak(best['answer'], message) - LOG.info('Handling with: ' + str(best['skill_id'])) - cb = best.get('callback_data') or {} - self.bus.emit(message.forward('question:action', - data={'skill_id': best['skill_id'], - 'phrase': search_phrase, - 'callback_data': cb})) - query.answered = True - else: - query.answered = False - query.completed.set() - - def speak(self, utterance, message=None): - """Speak a sentence. - - Args: - utterance (str): sentence mycroft should speak - """ - # registers the skill as being active - self.enclosure.register(self.skill_id) - - message = message or dig_for_message() - lang = get_message_lang(message) - data = {'utterance': utterance, - 'expect_response': False, - 'meta': {"skill": self.skill_id}, - 'lang': lang} - - m = message.forward("speak", data) if message \ - else Message("speak", data) - m.context["skill_id"] = self.skill_id - self.bus.emit(m) - - -import mycroft.skills.intent_services.commonqa_service -mycroft.skills.intent_services.commonqa_service.CommonQAService = CommonQuery -mycroft.skills.intent_service.CommonQAService = CommonQuery diff --git a/neon_core/util/skill_utils.py b/neon_core/util/skill_utils.py index f8b249271..68d77192e 100644 --- a/neon_core/util/skill_utils.py +++ b/neon_core/util/skill_utils.py @@ -26,7 +26,6 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import json import re from copy import copy