Skip to content

Commit

Permalink
Skills module tests, docstrings, and annotations (#108)
Browse files Browse the repository at this point in the history
  • Loading branch information
NeonDaniel authored Jul 10, 2023
1 parent 3fd73dc commit 2d5c1d7
Show file tree
Hide file tree
Showing 49 changed files with 2,132 additions and 1,092 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,11 @@ jobs:
python -m pip install build wheel
- name: Install ovos workshop
run: |
pip install .
pip install -e .
- name: Install test dependencies
run: |
sudo apt install libssl-dev libfann-dev portaudio19-dev libpulse-dev
pip install ovos-core>=0.0.6a17
pip install pytest pytest-timeout pytest-cov adapt-parser~=0.5
pip install -r requirements/test.txt
- name: Run unittests
run: |
pytest --cov=ovos_workshop --cov-report xml test/unittests
Expand Down
5 changes: 3 additions & 2 deletions ovos_workshop/skills/active.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ def bind(self, bus):
self.make_active()

def handle_skill_deactivated(self, message=None):
""" skill is always in active skill list,
ie, converse is always called """
"""
skill is always in active skill list, ie, converse is always called
"""
self.make_active()


97 changes: 60 additions & 37 deletions ovos_workshop/skills/auto_translatable.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,70 @@
from abc import ABC

from ovos_config import Configuration
from ovos_plugin_manager.language import OVOSLangDetectionFactory, OVOSLangTranslationFactory
from ovos_utils import get_handler_name
from ovos_utils.log import LOG

from ovos_workshop.resource_files import SkillResources
from ovos_workshop.skills.common_query_skill import CommonQuerySkill
from ovos_workshop.skills.ovos import OVOSSkill, OVOSFallbackSkill
from ovos_workshop.skills.ovos import OVOSSkill
from ovos_workshop.skills.fallback import FallbackSkillV2


class UniversalSkill(OVOSSkill):
''' Skill that auto translates input/output from any language
"""
Skill that auto translates input/output from any language
intent handlers are ensured to receive utterances in self.internal_language
intent handlers are expected to produce utterances in self.internal_language
self.speak will always translate utterances from self.internal_lang to self.lang
self.speak will always translate utterances from
self.internal_lang to self.lang
NOTE: self.lang reflects the original query language
but received utterances are always in self.internal_language
'''
"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.lang_detector = OVOSLangDetectionFactory.create()
self.translator = OVOSLangTranslationFactory.create()

self.internal_language = None # the skill internally only works in this language
self.translate_tags = True # __tags__ private value will be translated (adapt entities)
self.translate_keys = ["utterance", "utterances"] # keys added here will have values translated in message.data
# the skill internally only works in this language
self.internal_language = None
# __tags__ private value will be translated (adapt entities)
self.translate_tags = True
# keys added here will have values translated in message.data
self.translate_keys = ["utterance", "utterances"]

# autodetect will detect the lang of the utterance regardless of what has been reported
# to test just type in the cli in another language and watch answers still coming
# autodetect will detect the lang of the utterance regardless of what
# has been reported to test just type in the cli in another language
# and watch answers still coming
self.autodetect = False # TODO from mycroft.conf
if self.internal_language is None:
lang = Configuration().get("lang", "en-us")
LOG.warning(f"UniversalSkill are expected to specify their internal_language, casting to {lang}")
LOG.warning(f"UniversalSkill are expected to specify their "
f"internal_language, casting to {lang}")
self.internal_language = lang

def _load_lang(self, root_directory=None, lang=None):
"""unlike base skill class all resources are in self.internal_language by default
instead of self.lang (which comes from message)
"""
unlike base skill class all resources are in self.internal_language by
default instead of self.lang (which comes from message)
this ensures things like self.dialog_render reflect self.internal_lang
"""
lang = lang or self.internal_language # self.lang in base class
root_directory = root_directory or self.res_dir
if lang not in self._lang_resources:
self._lang_resources[lang] = SkillResources(root_directory, lang, skill_id=self.skill_id)
self._lang_resources[lang] = SkillResources(root_directory, lang,
skill_id=self.skill_id)
return self._lang_resources[lang]

def detect_language(self, utterance):
try:
return self.lang_detector.detect(utterance)
except:
except Exception as e:
LOG.error(e)
# self.lang to account for lang defined in message
return self.lang.split("-")[0]

Expand All @@ -61,7 +74,8 @@ def translate_utterance(self, text, target_lang, sauce_lang=None):
else:
sauce_lang = sauce_lang or self.detect_language(text)
if sauce_lang.split("-")[0] != target_lang:
translated = self.translator.translate(text, source=sauce_lang, target=target_lang)
translated = self.translator.translate(text, source=sauce_lang,
target=target_lang)
LOG.info("translated " + text + " to " + translated)
return translated
return text
Expand All @@ -76,11 +90,13 @@ def _translate_message(self, message):
return message

translation_data = {"original": {}, "translated": {},
"source_lang": sauce_lang, "internal_lang": self.internal_language}
"source_lang": sauce_lang,
"internal_lang": self.internal_language}

def _do_tx(thing):
if isinstance(thing, str):
thing = self.translate_utterance(thing, target_lang=out_lang, sauce_lang=sauce_lang)
thing = self.translate_utterance(thing, target_lang=out_lang,
sauce_lang=sauce_lang)
elif isinstance(thing, list):
thing = [_do_tx(t) for t in thing]
elif isinstance(thing, dict):
Expand All @@ -90,16 +106,19 @@ def _do_tx(thing):
for key in self.translate_keys:
if key in message.data:
translation_data["original"][key] = message.data[key]
translation_data["translated"][key] = message.data[key] = _do_tx(message.data[key])
translation_data["translated"][key] = message.data[key] = \
_do_tx(message.data[key])

# special case
if self.translate_tags:
translation_data["original"]["__tags__"] = message.data["__tags__"]
for idx, token in enumerate(message.data["__tags__"]):
message.data["__tags__"][idx] = self.translate_utterance(token.get("key", ""),
target_lang=out_lang,
sauce_lang=sauce_lang)
translation_data["translated"]["__tags__"] = message.data["__tags__"]
message.data["__tags__"][idx] = \
self.translate_utterance(token.get("key", ""),
target_lang=out_lang,
sauce_lang=sauce_lang)
translation_data["translated"]["__tags__"] = \
message.data["__tags__"]

message.context["translation_data"] = translation_data
return message
Expand Down Expand Up @@ -138,18 +157,20 @@ def speak(self, utterance, *args, **kwargs):
super().speak(utterance, *args, **kwargs)


class UniversalFallback(UniversalSkill, OVOSFallbackSkill):
''' Fallback Skill that auto translates input/output from any language
class UniversalFallback(UniversalSkill, FallbackSkillV2):
"""
Fallback Skill that auto translates input/output from any language
fallback handlers are ensured to receive utterances in self.internal_language
fallback handlers are expected to produce utterances in self.internal_language
fallback handlers are ensured to receive utterances and expected to produce
responses in self.internal_language
self.speak will always translate utterances from self.internal_lang to self.lang
self.speak will always translate utterances from
self.internal_lang to self.lang
NOTE: self.lang reflects the original query language
but received utterances are always in self.internal_language
'''
"""

def create_universal_fallback_handler(self, handler):
def universal_fallback_handler(message):
Expand All @@ -165,22 +186,25 @@ def universal_fallback_handler(message):

def register_fallback(self, handler, priority):
handler = self.create_universal_fallback_handler(handler)
super().register_fallback(handler, priority)
FallbackSkillV2.register_fallback(self, handler, priority)


class UniversalCommonQuerySkill(UniversalSkill, CommonQuerySkill):
''' CommonQuerySkill that auto translates input/output from any language
class UniversalCommonQuerySkill(UniversalSkill, CommonQuerySkill, ABC):
"""
CommonQuerySkill that auto translates input/output from any language
CQS_match_query_phrase and CQS_action are ensured to received phrase in self.internal_language
CQS_match_query_phrase and CQS_action are ensured to received phrase in
self.internal_language
CQS_match_query_phrase is assumed to return a response in self.internal_lang
it will be translated back before speaking
CQS_match_query_phrase is assumed to return a response in self.internal_lang
it will be translated back before speaking
self.speak will always translate utterances from self.internal_lang to self.lang
self.speak will always translate utterances from
self.internal_lang to self.lang
NOTE: self.lang reflects the original query language
but received utterances are always in self.internal_language
'''
"""

def __handle_query_action(self, message):
"""Message handler for question:action.
Expand Down Expand Up @@ -220,4 +244,3 @@ def __get_cq(self, search_phrase):
def remove_noise(self, phrase, lang=None):
"""remove noise to produce essence of question"""
return super().remove_noise(phrase, self.internal_language)

Loading

0 comments on commit 2d5c1d7

Please sign in to comment.