diff --git a/.github/workflows/setup_tests.yml b/.github/workflows/setup_tests.yml index 760760c92..67d4553ec 100644 --- a/.github/workflows/setup_tests.yml +++ b/.github/workflows/setup_tests.yml @@ -9,7 +9,7 @@ jobs: core_and_skills: strategy: matrix: - python-version: [ '3.8', '3.9', '3.10', '3.11', '3.12' ] + python-version: [ '3.10', '3.11', '3.12' ] runs-on: ubuntu-latest timeout-minutes: 15 steps: @@ -27,10 +27,10 @@ jobs: pip install wheel - name: Test Core Module Installation run: | - pip install .[core_modules] + pip install .[core-modules] - name: Test Skills Installation run: | - pip install .[skills_required,skills_essential,skills_default,skills_extended] + pip install .[skills-required,skills-essential,skills-default,skills-extended] pi_image_3_10: runs-on: ubuntu-latest timeout-minutes: 90 diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index fa6c7fff4..086e5f0d8 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -32,7 +32,7 @@ jobs: sudo apt update sudo apt install -y gcc libfann-dev swig libssl-dev portaudio19-dev git libpulse-dev pip install wheel - pip install . -r requirements/test.txt + pip install .[test] env: GITHUB_TOKEN: ${{secrets.neon_token}} diff --git a/Dockerfile b/Dockerfile index b2a54ec06..a30cbc704 100644 --- a/Dockerfile +++ b/Dockerfile @@ -57,5 +57,5 @@ HEALTHCHECK CMD "/opt/neon/healthcheck.sh" CMD ["/root/run.sh"] FROM base AS default_skills -RUN pip install --no-cache-dir .[skills_required,skills_essential,skills_default,skills_extended] +RUN pip install --no-cache-dir .[skills-required,skills-essential,skills-default,skills-extended] # Default skills from configuration are installed at container creation diff --git a/README.md b/README.md index 0a457e9ec..7cdbbb13e 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ # Welcome to Neon AI Neon AI is an open source voice assistant. Follow these instructions to start using Neon on your computer. If you are using a Mycroft Mark2 or Raspberry Pi, -you may use the prebuilt image available [on our website](https://neon.ai/Download). +you may use the prebuilt image available with the [latest Neon OS release](https://github.com/NeonGeckoCom/neon-os/releases/latest). # Quick Start The fastest method for getting started with Neon is to run the modules in Docker containers. diff --git a/docker_overlay/etc/neon/neon.yaml b/docker_overlay/etc/neon/neon.yaml index f70153cd9..638bcfb41 100644 --- a/docker_overlay/etc/neon/neon.yaml +++ b/docker_overlay/etc/neon/neon.yaml @@ -1,7 +1,9 @@ language: supported_langs: - en - - uk +# secondary_langs defines what languages are loaded in ovos-core intent services +secondary_langs: [en-us] + skills: wait_for_internet: false extra_directories: @@ -45,4 +47,4 @@ ready_settings: - skills signal: use_signal_files: false - patch_imports: false \ No newline at end of file + patch_imports: false diff --git a/neon_core/__init__.py b/neon_core/__init__.py index 8b113cfae..837699d77 100644 --- a/neon_core/__init__.py +++ b/neon_core/__init__.py @@ -31,9 +31,11 @@ environ["OVOS_DEFAULT_CONFIG"] = join(dirname(__file__), "configuration", "neon.yaml") +environ.setdefault('OVOS_CONFIG_BASE_FOLDER', "neon") +environ.setdefault('OVOS_CONFIG_FILENAME', "neon.yaml") # Patching deprecation warnings -# TODO: Deprecate after migration to ovos-workshop 1.0+ and ovos-core 0.1+ +# TODO: Deprecate after migration to ovos-workshop 1.0+ and ovos-core 0.3.0 import ovos_workshop.resource_files import ovos_core.intent_services.stop_service from ovos_utils.bracket_expansion import expand_template diff --git a/neon_core/cli.py b/neon_core/cli.py index cf7dbd89d..4035101f8 100644 --- a/neon_core/cli.py +++ b/neon_core/cli.py @@ -35,6 +35,7 @@ from threading import Thread from click_default_group import DefaultGroup from neon_utils.packaging_utils import get_neon_core_version +from ovos_utils.log import deprecated _DEFAULT_CONFIG_FILE = join(dirname(__file__), "configuration", "neon.yaml") environ.setdefault("OVOS_CONFIG_BASE_FOLDER", "neon") @@ -61,6 +62,10 @@ def neon_core_cli(version: bool = False): click.echo(f"Neon version {get_neon_core_version()}") +@deprecated( + "start_neon is deprecated; use containers or start services individually", + "25.10.0", +) @neon_core_cli.command(help="Start Neon Core") def start_neon(): from neon_core.run_neon import start_neon @@ -70,6 +75,10 @@ def start_neon(): click.echo("Neon Started") +@deprecated( + "stop_neon is deprecated; use containers or start services individually", + "25.10.0", +) @neon_core_cli.command(help="Stop Neon Core") def stop_neon(): from neon_core.run_neon import stop_neon diff --git a/neon_core/configuration/__init__.py b/neon_core/configuration/__init__.py index 7f62c6811..95ecc92f3 100644 --- a/neon_core/configuration/__init__.py +++ b/neon_core/configuration/__init__.py @@ -29,7 +29,7 @@ from ovos_config.config import Configuration from os.path import exists, isdir, dirname from os import makedirs -from ovos_utils.log import LOG +from ovos_utils.log import LOG, deprecated """ Neon modules should import config from this module since module_overrides will @@ -37,6 +37,7 @@ """ +@deprecated("get_private_keys is deprecated without replacement", "25.10.0") def get_private_keys(): return Configuration().get("keys", {}) @@ -61,4 +62,4 @@ def patch_config(config: dict = None): local_config.store() Configuration().reload() import mycroft.configuration - mycroft.configuration.Configuration().reload() \ No newline at end of file + mycroft.configuration.Configuration().reload() diff --git a/neon_core/configuration/mark_2/neon.yaml b/neon_core/configuration/mark_2/neon.yaml index 45c8a20b9..ca8e65053 100644 --- a/neon_core/configuration/mark_2/neon.yaml +++ b/neon_core/configuration/mark_2/neon.yaml @@ -8,68 +8,22 @@ hana: # TODO: Patched to prevent resource warnings secondary_langs: [en-us] -# TODO Below block patching for 2025.08 Release -enable_old_audioservice: True -audio_parsers: - blacklist: - - gender -padatious: - intent_cache: ~/.local/share/mycroft/intent_cache - train_delay: 4 - single_thread: false - regex_only: false - conf_high: 0.95 - conf_med: 0.75 - conf_low: 0.50 -intents: - pipeline: - - stop_high - - converse - - ocp_high - - padatious_high - - adapt_high - - ocp_medium - - fallback_high - - stop_medium - - adapt_medium - - padatious_medium - - adapt_low - - common_qa - - fallback_medium - - padatious_low - - fallback_low - play_wav_cmdline: "play %1" play_mp3_cmdline: "play %1" play_ogg_cmdline: "play %1" g2p: module: "dummy" tts: - # TODO: Patching 2025.08 Release - pulse_duck: false - preload_fallback: false - module: neon-tts-plugin-coqui-remote fallback_module: coqui - neon-tts-plugin-larynx-server: - host: "https://larynx.2022.us" - mozilla_remote: - api_url: "https://mtts.2022.us/api/tts" Audio: backends: OCP: type: ovos_common_play active: true dbus_type: system - # TODO: Patching 2025.08 Release - default-backend: OCP stt: fallback_module: ovos-stt-plugin-vosk - # TODO: Patching 2025.08 Release - module: ovos-stt-plugin-server - ovos-stt-plugin-server: - url: https://whisper.neonaiservices.com/stt - confirm_listening: true listener: VAD: @@ -226,6 +180,3 @@ language: utterance_transformers: neon_utterance_translator_plugin: enable_detector: False - # TODO: Patching 2025.08 Release - neon_utterance_normalizer_plugin: - active: True diff --git a/neon_core/configuration/neon.yaml b/neon_core/configuration/neon.yaml index aabb77f2f..2a072058b 100644 --- a/neon_core/configuration/neon.yaml +++ b/neon_core/configuration/neon.yaml @@ -4,7 +4,7 @@ enable_old_audioservice: True # Plugin Configuration utterance_transformers: neon_utterance_translator_plugin: - active: True + active: False enable_detector: True neon_utterance_normalizer_plugin: active: True @@ -18,7 +18,9 @@ stt: ovos-stt-plugin-vosk: model: /home/neon/.local/share/neon/vosk-model-small-en-us-0.15 ovos-stt-plugin-server: - url: https://whisper.neonaiservices.com/stt + urls: + - https://whisper.neonaiservices.com/stt + - https://stt.smartgic.io/fasterwhisper/stt tts: pulse_duck: false preload_fallback: false @@ -338,4 +340,4 @@ location: code: America/Los_Angeles name: Pacific Standard Time dstOffset: 3600000 - offset: -28800000 \ No newline at end of file + offset: -28800000 diff --git a/neon_core/configuration/opi5/neon.yaml b/neon_core/configuration/opi5/neon.yaml index 8ae85bfb9..cb543af5d 100644 --- a/neon_core/configuration/opi5/neon.yaml +++ b/neon_core/configuration/opi5/neon.yaml @@ -6,10 +6,6 @@ g2p: tts: module: neon-tts-plugin-coqui-remote fallback_module: coqui - neon-tts-plugin-larynx-server: - host: "https://larynx.2022.us" - mozilla_remote: - api_url: "https://mtts.2022.us/api/tts" Audio: backends: OCP: @@ -162,4 +158,4 @@ system: - websocket:host language: detection_module: None - translation_module: libretranslate_plug \ No newline at end of file + translation_module: libretranslate_plug diff --git a/neon_core/configuration/rpi4/neon.yaml b/neon_core/configuration/rpi4/neon.yaml index 8e9e62ea4..d47eb002c 100644 --- a/neon_core/configuration/rpi4/neon.yaml +++ b/neon_core/configuration/rpi4/neon.yaml @@ -6,10 +6,6 @@ g2p: tts: module: neon-tts-plugin-coqui-remote fallback_module: coqui - neon-tts-plugin-larynx-server: - host: "https://larynx.2022.us" - mozilla_remote: - api_url: "https://mtts.2022.us/api/tts" Audio: backends: OCP: @@ -166,4 +162,4 @@ language: translation_module: libretranslate_plug utterance_transformers: neon_utterance_translator_plugin: - enable_detector: False \ No newline at end of file + enable_detector: False diff --git a/neon_core/language/__init__.py b/neon_core/language/__init__.py index a2e5de798..f8d759a6a 100644 --- a/neon_core/language/__init__.py +++ b/neon_core/language/__init__.py @@ -28,10 +28,8 @@ import os -from ovos_plugin_manager.language import OVOSLangDetectionFactory as DetectorFactory -from ovos_plugin_manager.language import OVOSLangTranslationFactory as TranslatorFactory - -from neon_core.configuration import Configuration +from ovos_utils.log import deprecated +from ovos_config.config import Configuration def get_lang_config(): @@ -44,6 +42,7 @@ def get_lang_config(): return lang_config +@deprecated("get_language_dir is deprecated without replacement", "25.10.0") def get_language_dir(base_path, lang="en-us"): """ checks for all language variations and returns best path """ lang_path = os.path.join(base_path, lang) diff --git a/neon_core/run_neon.py b/neon_core/run_neon.py index d541175d5..b37964b5b 100644 --- a/neon_core/run_neon.py +++ b/neon_core/run_neon.py @@ -43,6 +43,9 @@ from ovos_utils.gui import is_gui_running from neon_utils.log_utils import remove_old_logs, archive_logs, LOG, \ get_log_file_for_module +from ovos_utils.log import log_deprecation + +log_deprecation("This script is deprecated. Recommended method is to use containers", "25.10.0") LOG_FILES = {} PROCESSES = {} diff --git a/neon_core/skills/service.py b/neon_core/skills/service.py index c69a061a3..b93fe559a 100644 --- a/neon_core/skills/service.py +++ b/neon_core/skills/service.py @@ -34,7 +34,7 @@ from ovos_bus_client import Message, MessageBusClient from ovos_config.locale import set_default_lang, set_default_tz from ovos_config.config import Configuration -from ovos_utils.log import LOG, log_deprecation +from ovos_utils.log import LOG from ovos_plugin_manager.skills import get_plugin_skills, get_skill_directories from ovos_utils.process_utils import StatusCallbackMap, ProcessState from neon_utils.metrics_utils import announce_connection @@ -106,14 +106,6 @@ def __init__(self, stopping_hook=self.callbacks.on_stopping) self.skill_manager.name = "skill_manager" - @property - def status(self): - log_deprecation("This reference is deprecated. Use " - "`NeonSkillService.skill_manager.status` directly.", - "24.7.0") - return self.skill_manager.status - - def check_health(self): """ Check the health of the skills service and get an error state if the diff --git a/neon_core/skills/skill_manager.py b/neon_core/skills/skill_manager.py index c2d5b86bd..9853cdb86 100644 --- a/neon_core/skills/skill_manager.py +++ b/neon_core/skills/skill_manager.py @@ -28,24 +28,46 @@ from os import makedirs from os.path import isdir, join, expanduser +from threading import Thread from ovos_utils.xdg_utils import xdg_data_home -from ovos_utils.log import LOG - +from ovos_utils.log import LOG, deprecated +from ovos_bus_client.message import Message from ovos_core.skill_manager import SkillManager class NeonSkillManager(SkillManager): + def _sync_skill_loading_state(self): + """ + Override to wait for configured ready settings before announcing the + service is ready + """ + SkillManager._sync_skill_loading_state(self) + LOG.info( + "Waiting for skill ready settings" + ) # TODO Log is only for debugging + self._wait_until_skills_ready() + + # Start a background thread to check for configured ready settings + # while allowing the skills service to continue initialization + ready_event_thread = Thread(target=self._check_device_ready) + ready_event_thread.daemon = True + ready_event_thread.start() + @deprecated("Legacy skills are deprecated and this method should not " + "be used.", "25.10.1") def get_default_skills_dir(self): """ Go through legacy config params to locate the default skill directory """ skill_config = self.config["skills"] - skill_dir = skill_config.get("directory") or \ - skill_config.get("extra_directories") - skill_dir = skill_dir[0] if isinstance(skill_dir, list) and \ - len(skill_dir) > 0 else skill_dir or \ - join(xdg_data_home(), "neon", "skills") + skill_dir = skill_config.get("directory") or skill_config.get( + "extra_directories" + ) + skill_dir = ( + skill_dir[0] + if isinstance(skill_dir, list) and len(skill_dir) > 0 + else skill_dir or join(xdg_data_home(), "neon", "skills") + ) skill_dir = expanduser(skill_dir) if not isdir(skill_dir): @@ -61,20 +83,70 @@ def get_default_skills_dir(self): return skill_dir - def _load_new_skills(self, *args, **kwargs): - # Override load method for config module checks - SkillManager._load_new_skills(self, *args, **kwargs) - def _get_plugin_skill_loader(self, skill_id, init_bus=True): assert self.bus is not None if not init_bus: LOG.debug("Ignoring request not to bind bus") return SkillManager._get_plugin_skill_loader(self, skill_id, True) - def run(self): - """Load skills and update periodically from disk and internet.""" - from os import environ - environ.setdefault('OVOS_CONFIG_BASE_FOLDER', "neon") - environ.setdefault('OVOS_CONFIG_FILENAME', "neon.yaml") - LOG.debug("set default configuration to `neon/neon.yaml`") - SkillManager.run(self) + # Re-implement support for internet and network skill load + def _wait_until_skills_ready(self): + """ + Block until configured network and internet skills are loaded to + delay skills service reporting ready. + """ + ready_settings = self.config.get("ready_settings", ["skills"]) + if "network_skills" in ready_settings: + if not self._network_loaded.wait(self._network_skill_timeout): + LOG.error("Timeout waiting for network skills to load") + return False + if "internet_skills" in ready_settings: + if not self._internet_loaded.wait(self._network_skill_timeout): + LOG.error("Timeout waiting for internet skills to load") + return False + LOG.debug("Configured skill load conditions met") + return True + + def _check_device_ready(self): + while not self._wait_until_skills_ready(): + LOG.warning("Skills not ready, still waiting...") + ready_settings = self.config.get("ready_settings", ["skills"]) + valid_services = ( + "skills", + "voice", + "audio", + "gui_service", + "internet", + ) + ready_services = { + s: False for s in ready_settings if s in valid_services + } + LOG.info(f"Waiting for services: {ready_services}") + while not all(ready_services.values()): + for service in ready_services: + if not ready_services[service]: + resp = self.bus.wait_for_response( + Message( + f"mycroft.{service}.is_ready", + context={ + "source": ["skills"], + "destination": [service], + }, + ) + ) + LOG.debug( + resp.data + if resp + else f"No response for service={service}" + ) + service_ready = resp and resp.data.get("status") + if service_ready: + LOG.info(f"{service} reports ready") + ready_services[service] = service_ready + LOG.info(f"All configured ready settings met: {ready_services}") + self.bus.emit( + Message( + "mycroft.ready", + context={"source": ["skills"], "destination": valid_services}, + ) + ) diff --git a/neon_core/util/runtime_utils.py b/neon_core/util/runtime_utils.py deleted file mode 100644 index 51f968e63..000000000 --- a/neon_core/util/runtime_utils.py +++ /dev/null @@ -1,44 +0,0 @@ -# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework -# All trademark and other rights reserved by their respective owners -# Copyright 2008-2025 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. - -from ovos_utils.log import deprecated - - -@deprecated("ovos.conf meta configuration is deprecated. Use envvars " - "`OVOS_CONFIG_FILENAME`, `OVOS_CONFIG_BASE_FOLDER`, and " - "`OVOS_DEFAULT_CONFIG`.", "24.11.0") -def use_neon_core(func): - """ - Wrapper to ensure call originates from neon_core for stack checks. - This is used for ovos-utils config platform detection which uses the stack - to determine which module config to return. - """ - def wrapper(*args, **kwargs): - return func(*args, **kwargs) - return wrapper - diff --git a/neon_core/util/skill_utils.py b/neon_core/util/skill_utils.py index fc8d0cb8b..b34af4e49 100644 --- a/neon_core/util/skill_utils.py +++ b/neon_core/util/skill_utils.py @@ -33,12 +33,12 @@ from os.path import expanduser, join, isdir, dirname, islink from typing import List -from ovos_utils.xdg_utils import xdg_data_home -from ovos_utils.log import LOG +from ovos_utils.log import LOG, deprecated from ovos_config.config import Configuration +@deprecated("Internal function is deprecated", "25.09.01") def _write_pip_constraints_to_file(output_file: str): """ Writes out a constraints file for OSM to use to prevent broken dependencies @@ -67,6 +67,7 @@ def _write_pip_constraints_to_file(output_file: str): LOG.info(f"Wrote core constraints to file: {output_file}") +@deprecated("Internal function is deprecated", "25.09.01") def _install_skill_pip(skill_package: str, constraints_file: str) -> bool: """ Pip install the specified package @@ -92,16 +93,12 @@ def install_skills_from_list(skills_to_install: list, config: dict = None): :param skills_to_install: list of skills to install :param config: optional dict configuration """ - constraints_file = join(xdg_data_home(), "neon", "constraints.txt") - _write_pip_constraints_to_file(constraints_file) - - for spec in skills_to_install: - if "://" in spec and "git+" not in spec: - LOG.error(f"Got an invalid package spec to install: {spec}") - elif not _install_skill_pip(spec, constraints_file): - LOG.error(f"Pip installation failed for: {spec}") - - LOG.info(f"Installed {len(skills_to_install)} skills") + from neon_utils.packaging_utils import install_packages_from_pip + ret_code = install_packages_from_pip("neon-core", skills_to_install, True) + if ret_code == 0: + LOG.info(f"Installed {len(skills_to_install)} skills") + else: + LOG.error(f"Error installing skills: {ret_code}") def install_skills_default(config: dict = None): diff --git a/neon_core/version.py b/neon_core/version.py index 4ecc66620..696d5570d 100644 --- a/neon_core/version.py +++ b/neon_core/version.py @@ -26,4 +26,4 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = "25.8.18" +__version__ = "26.8.3" diff --git a/requirements/core_modules.txt b/requirements/core_modules.txt index c509b84d3..a1289e5a0 100644 --- a/requirements/core_modules.txt +++ b/requirements/core_modules.txt @@ -3,4 +3,4 @@ neon-messagebus~=2.1 neon-enclosure~=1.8 neon-speech~=4.5 neon-gui~=1.4 -neon-audio~=1.6 +neon-audio~=1.7,>=1.7.1 diff --git a/requirements/pi.txt b/requirements/pi.txt index 7f2b4be37..5122fabb9 100644 --- a/requirements/pi.txt +++ b/requirements/pi.txt @@ -22,7 +22,7 @@ neon-stt-plugin-google-cloud-streaming~=2.0 neon-tts-plugin-coqui-remote~=0.1 neon-stt-plugin-nemo~=0.1 ovos-tts-plugin-server~=0.0.2 -ovos-stt-plugin-server~=0.0.3 +ovos-stt-plugin-server~=0.1 ovos-tts-plugin-piper~=0.0.2 # Fallback plugins @@ -36,7 +36,7 @@ neon-phal-plugin-reset~=0.5 neon-phal-plugin-device-updater~=0.3,>=0.3.1 neon-phal-plugin-fan~=0.1,>=0.1.1 neon-phal-plugin-switches~=0.0.6 -neon-phal-plugin-linear_led~=0.2,>=0.2.4 +neon-phal-plugin-linear_led~=0.3 neon-phal-plugin-gui-network-client~=0.0.6 neon-phal-plugin-system~=1.3 ovos-phal-plugin-balena-wifi~=1.0.0 @@ -46,15 +46,13 @@ ovos-phal-plugin-oauth~=0.1 ovos-phal-plugin-alsa~=0.0,>=0.0.3 ovos-phal-plugin-connectivity-events~=0.1 ovos-phal-plugin-homeassistant~=0.1 -ovos-phal-plugin-wallpaper-manager~=0.2,>=0.2.1 +ovos-phal-plugin-wallpaper-manager~=0.2,>=0.2.6 ovos-phal-plugin-ipgeo~=0.1 ovos-phal-plugin-mk1~=0.1 -# TODO: Stable spec for mk1 plugin -# ovos-phal-plugin-gpsd @ git+https://github.com/OpenVoiceOS/ovos-PHAL-plugin-gpsd ovos-gui-plugin-shell-companion~=1.0 # Pi-specific skills -neon-skill-core_ready~=0.2 +neon-skill-core_ready~=0.3 neon-skill-update~=3.1,>=3.1.2 ovos-skill-homescreen~=0.0.3 diff --git a/requirements/requirements.txt b/requirements/requirements.txt index a7c131397..17695505a 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,19 +1,21 @@ -# ovos-core version pinned for compat. with patches in NeonCore -ovos-core[lgpl]~=0.0.8 -# padacioso==0.1.3a2 +ovos-core[lgpl]~=0.2 -# TODO ovos-classifiers required for common-play -ovos-classifiers - -neon-utils[network]~=1.13 +neon-utils[network]~=1.14,>=1.14.1 ovos-utils~=0.0,>=0.0.38 ovos-bus-client~=0.0,>=0.0.10 neon-transformers~=1.0 ovos-config~=0.0,>=0.0.12 ovos-plugin-manager~=0.0,>=0.0.25 -ovos-backend-client<=2.0 + +# TODO ovos-classifiers required for common-play +ovos-classifiers + +# TODO: Remove when run_neon.py is deprecated psutil~=5.6 +# TODO: Patching `setuptools` imports +setuptools<82.0 + click~=8.0 click-default-group~=1.2 diff --git a/requirements/skills_default.txt b/requirements/skills_default.txt index 4a5767e8d..e186e451d 100644 --- a/requirements/skills_default.txt +++ b/requirements/skills_default.txt @@ -1,17 +1,17 @@ skill-ddg~=0.0.2 -neon-skill-alerts~=3.0,>=3.0.1 -neon-skill-caffeinewiz~=2.0 #,>=2.0.1a1 -neon-skill-data_controls~=2.0,>=2.0.3 +neon-skill-alerts~=3.1 +neon-skill-caffeinewiz~=3.0 +neon-skill-data_controls~=2.1 neon-skill-fallback_wolfram_alpha~=3.0 -neon-skill-personal~=1.0,>=1.0.3 -neon-skill-speak~=2.0 #,>=2.0.4a1 -neon-skill-speed_test~=1.0,>=1.0.4 -neon-skill-spelling~=1.1,>=1.1.1 -neon-skill-stock~=2.0 -neon-skill-support_helper~=1.2,>=1.2.4 +neon-skill-personal~=2.0 +neon-skill-speak~=2.1 +neon-skill-speed_test~=2.0 +neon-skill-spelling~=1.2 +neon-skill-stock~=3.0 +neon-skill-support_helper~=1.3 neon-skill-user_settings~=1.1 -neon-skill-weather~=3.1 +neon-skill-weather~=4.0 ovos-skill-wikipedia~=0.0 neon-skill-free_music_archive~=1.1 -neon-skill-local_music~=3.2,>=3.2.1a1 +neon-skill-local_music~=3.3 neon-skill-fallback_llm~=2.1 diff --git a/requirements/skills_essential.txt b/requirements/skills_essential.txt index 267f2a37d..76ccd68b8 100644 --- a/requirements/skills_essential.txt +++ b/requirements/skills_essential.txt @@ -1,5 +1,5 @@ -neon-skill-about~=1.0,>=1.0.3 -neon-skill-date_time~=1.1,>=1.1.1 +neon-skill-about~=1.0,>=1.0.4 +neon-skill-date_time~=2.0 neon-skill-demo~=1.0,>=1.0.4 neon-skill-device_controls~=1.2 -neon-skill-ip-address~=1.3 \ No newline at end of file +neon-skill-ip-address~=1.3 diff --git a/requirements/skills_extended.txt b/requirements/skills_extended.txt index 352830d09..f9d2bab65 100644 --- a/requirements/skills_extended.txt +++ b/requirements/skills_extended.txt @@ -1,4 +1,4 @@ -neon-skill-audio_recording~=0.0.1 #,>=0.0.2a1 +neon-skill-audio_recording~=1.0 #neon-skill-custom_conversation>=0.0.1a10,<0.0.2 neon-skill-instructions~=1.0 neon-skill-launcher~=1.1 diff --git a/setup.py b/setup.py index d12ae17b8..1c12c6125 100644 --- a/setup.py +++ b/setup.py @@ -88,13 +88,6 @@ def get_requirements(requirements_filename: str): "test": get_requirements("test.txt"), "pi": get_requirements("pi.txt"), "docker": get_requirements("docker.txt"), - # # TODO: Deprecate `_`-specified extras after 2024.5 - "core_modules": get_requirements("core_modules.txt"), - "skills_required": get_requirements("skills_required.txt"), - "skills_essential": get_requirements("skills_essential.txt"), - "skills_default": get_requirements("skills_default.txt"), - "skills_extended": get_requirements("skills_extended.txt"), - # PEP503 specified normalization to `-` "core-modules": get_requirements("core_modules.txt"), "skills-required": get_requirements("skills_required.txt"), "skills-essential": get_requirements("skills_essential.txt"), diff --git a/test/pi_setup_3_10.sh b/test/pi_setup_3_10.sh index 8d754538a..86cdc6e61 100644 --- a/test/pi_setup_3_10.sh +++ b/test/pi_setup_3_10.sh @@ -44,6 +44,6 @@ python3.10 -m venv "/core/venv" || exit 11 . /core/venv/bin/activate pip install --upgrade pip wheel -pip install ".[core_modules,skills_required,skills_essential,skills_default,skills_extended,pi]" || exit 11 +pip install ".[core-modules,skills-required,skills-essential,skills-default,skills-extended,pi]" || exit 11 -cp -rf /core/test/pi_image_overlay/* / \ No newline at end of file +cp -rf /core/test/pi_image_overlay/* / diff --git a/test/pi_setup_3_11.sh b/test/pi_setup_3_11.sh index bc03f7040..03a5a46a5 100644 --- a/test/pi_setup_3_11.sh +++ b/test/pi_setup_3_11.sh @@ -46,6 +46,6 @@ python3.11 -m venv "/core/venv" || exit 11 pip install --upgrade pip wheel pip install https://github.com/smartgic/python-tflite-runtime/releases/download/2.13.0-cp311/tflite_runtime-2.13.0-cp311-cp311-linux_aarch64.whl #pip install https://whl.smartgic.io/tflite_runtime-2.13.0-cp311-cp311-linux_aarch64.whl -pip install ".[core_modules,skills_required,skills_essential,skills_default,skills_extended,pi]" || exit 11 +pip install --no-cache-dir ".[core-modules,skills-required,skills-essential,skills-default,skills-extended,pi]" || exit 11 -cp -rf /core/test/pi_image_overlay/* / \ No newline at end of file +cp -rf /core/test/pi_image_overlay/* / diff --git a/test/test_configuration.py b/test/test_configuration.py index 6ef7d9f34..f8b6483ef 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -29,11 +29,6 @@ import os import shutil import unittest -import yaml - -from copy import deepcopy -# from pprint import pformat -# from ovos_utils.log import LOG class ConfigurationTests(unittest.TestCase): @@ -42,110 +37,16 @@ class ConfigurationTests(unittest.TestCase): @classmethod def setUpClass(cls) -> None: os.environ["XDG_CONFIG_HOME"] = cls.CONFIG_PATH - os.environ["OVOS_CONFIG_BASE_FOLDER"] = "neon" - os.environ["OVOS_CONFIG_FILENAME"] = "neon.yaml" - # from neon_core.util.runtime_utils import use_neon_core - # from neon_utils.configuration_utils import init_config_dir - # use_neon_core(init_config_dir)() - - # import neon_core - # assert isinstance(neon_core.CORE_VERSION_STR, str) - # assert os.path.isfile(os.path.join(cls.CONFIG_PATH, - # "OpenVoiceOS", "ovos.conf")) - - # from ovos_config.meta import get_ovos_config - # from neon_core.configuration import Configuration - # ovos_config = use_neon_core(get_ovos_config)() - # LOG.info(pformat(ovos_config)) - # assert ovos_config['config_filename'] == 'neon.yaml' - # assert os.path.basename(ovos_config['default_config_path']) == "neon.yaml" - # assert Configuration.default.path == ovos_config['default_config_path'] @classmethod def tearDownClass(cls) -> None: if os.path.exists(cls.CONFIG_PATH): shutil.rmtree(cls.CONFIG_PATH) os.environ.pop("XDG_CONFIG_HOME") - # os.environ.pop("OVOS_CONFIG_BASE_FOLDER") - # os.environ.pop("OVOS_CONFIG_FILENAME") - - def test_neon_core_config_init(self): - from neon_core.configuration import Configuration - from mycroft.configuration import Configuration as MycroftConfig - # TODO: Replace test after ovos_utils YML config compat. - from neon_core.util.runtime_utils import use_neon_core - - configuration = Configuration() - self.assertEqual(os.path.basename(Configuration.default.path), - "neon.yaml") - self.assertIsInstance(configuration, dict) - self.assertEqual(configuration, use_neon_core(MycroftConfig)()) - - from ovos_config.config import read_mycroft_config - self.assertEqual(configuration, use_neon_core(read_mycroft_config)()) def test_patch_config(self): - from os.path import join - import json - - test_config_dir = os.path.join(os.path.dirname(__file__), "config") - os.makedirs(test_config_dir, exist_ok=True) - os.environ["XDG_CONFIG_HOME"] = test_config_dir - - from neon_core.util.runtime_utils import use_neon_core - from neon_utils.configuration_utils import init_config_dir - use_neon_core(init_config_dir)() - from mycroft.configuration import Configuration - from mycroft.configuration.locations import DEFAULT_CONFIG - self.assertTrue(DEFAULT_CONFIG.endswith("neon.yaml")) - self.assertTrue(Configuration.default.path == DEFAULT_CONFIG, - Configuration.default.path) - # with open(join(test_config_dir, "OpenVoiceOS", 'ovos.conf')) as f: - # ovos_conf = json.load(f) - # self.assertEqual(ovos_conf['submodule_mappings']['neon_core'], - # "neon_core") - # self.assertIsInstance(ovos_conf['module_overrides']['neon_core'], dict) - from neon_core.configuration import patch_config - test_config = {"new_key": {'val': True}} - patch_config(test_config) - self.assertEqual(Configuration(), use_neon_core(Configuration)()) - conf_file = os.path.join(test_config_dir, 'neon', - 'neon.yaml') - self.assertTrue(os.path.isfile(conf_file)) - with open(conf_file) as f: - config = yaml.safe_load(f) - for k in config: - if isinstance(k, dict): - for s in k: - self.assertEqual(config[k][s], Configuration()[k][s], - Configuration()[k][s]) - else: - self.assertEqual(config[k], Configuration()[k], - Configuration()[k]) - - self.assertTrue(config['new_key']['val']) - - test_config = deepcopy(config) - test_config["new_key"]["val"] = False - test_config['skills'] = \ - {'auto_update': not Configuration()['skills']['auto_update']} - valid_val = test_config['skills']['auto_update'] - self.assertNotEqual(config, test_config) - patch_config(test_config) - conf_file = os.path.join(test_config_dir, 'neon', - 'neon.yaml') - with open(conf_file) as f: - config = yaml.safe_load(f) - self.assertEqual(config, test_config) - self.assertEqual(config['skills']['auto_update'], valid_val) - self.assertFalse(config['new_key']['val']) - self.assertEqual(config['skills']['auto_update'], - Configuration()['skills']['auto_update']) - - shutil.rmtree(test_config_dir) - # os.environ.pop("XDG_CONFIG_HOME") - + # TODO if __name__ == '__main__': unittest.main() diff --git a/test/test_language.py b/test/test_language.py index d7e87ad70..b981c727d 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -82,22 +82,5 @@ def test_get_language_dir_invalid(self): self.assertEqual(get_language_dir(base_dir, "ru-ru"), os.path.join(base_dir, "ru-ru")) - def test_translator(self): - from neon_core.language import TranslatorFactory - translator = TranslatorFactory.create( - {"translation_module": "libretranslate_plug"}) - self.assertIsInstance(translator, LanguageTranslator) - output = translator.translate("hello", "es-es", "en-us") - self.assertEqual(output.lower(), "hola") - - def test_detector(self): - from neon_core.language import DetectorFactory - detector = DetectorFactory.create( - {"detection_module": "libretranslate_detection_plug"}) - self.assertIsInstance(detector, LanguageDetector) - lang = detector.detect("hello") - self.assertEqual(lang, "en") - - if __name__ == '__main__': unittest.main() diff --git a/test/test_run_modules.py b/test/test_run_modules.py deleted file mode 100644 index dad682a8c..000000000 --- a/test/test_run_modules.py +++ /dev/null @@ -1,106 +0,0 @@ -# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework -# All trademark and other rights reserved by their respective owners -# Copyright 2008-2025 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 os -import unittest - -from multiprocessing import Process -from time import time, sleep -from ovos_bus_client import MessageBusClient, Message -from neon_speech.__main__ import main as neon_speech_main -from neon_audio.__main__ import main as neon_audio_main -from neon_messagebus.service import NeonBusService - - -AUDIO_FILE_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "audio_files") - - -# TODO: Depreciate this test; covered in test_run_neon -class TestModules(unittest.TestCase): - bus_thread = None - speech_thread = None - audio_thread = None - - @classmethod - def setUpClass(cls) -> None: - cls.messagebus_service = NeonBusService() - cls.messagebus_service.start() - cls.speech_thread = Process(target=neon_speech_main, daemon=False) - cls.audio_thread = Process(target=neon_audio_main, daemon=False) - cls.bus_thread.start() - cls.speech_thread.start() - cls.audio_thread.start() - sleep(60) # TODO: This shouldn't be necessary? DM - cls.bus = MessageBusClient() - cls.bus.run_in_thread() - - @classmethod - def tearDownClass(cls) -> None: - cls.bus.close() - cls.messagebus_service.shutdown() - cls.speech_thread.terminate() - cls.audio_thread.terminate() - - def setUp(self): - self.bus.connected_event.wait(30) - while not self.bus.started_running: - sleep(1) - - def test_get_stt_valid_file(self): - self.assertTrue(self.speech_thread.is_alive()) - context = {"client": "tester", - "ident": "12345", - "user": "TestRunner"} - stt_resp = self.bus.wait_for_response(Message("neon.get_stt", {"audio_file": os.path.join(AUDIO_FILE_PATH, - "stop.wav")}, - context), context["ident"]) - self.assertEqual(stt_resp.context, context) - self.assertIsInstance(stt_resp.data.get("parser_data"), dict) - self.assertIsInstance(stt_resp.data.get("transcripts"), list) - self.assertIn("stop", stt_resp.data.get("transcripts")) - - def test_get_tts_valid_default(self): - self.assertTrue(self.audio_thread.is_alive()) - text = "This is a test" - context = {"client": "tester", - "ident": str(time()), - "user": "TestRunner"} - tts_resp = self.bus.wait_for_response(Message("neon.get_tts", {"text": text}, context), - context["ident"], timeout=60) - self.assertEqual(tts_resp.context, context) - responses = tts_resp.data - self.assertIsInstance(responses, dict, responses) - print(responses) - self.assertEqual(len(responses), 1) - resp = list(responses.values())[0] - self.assertIsInstance(resp, dict) - self.assertEqual(resp.get("sentence"), text) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/test_run_neon.py b/test/test_run_neon.py index f4416759a..6f8bf4bcc 100644 --- a/test/test_run_neon.py +++ b/test/test_run_neon.py @@ -95,7 +95,7 @@ def test_speech_module(self): i = 0 response = self.bus.wait_for_response(Message('mycroft.voice.is_ready')) while not response.data['status'] and i < 10: - LOG.warning(f"Speech not ready when core reported ready!") + LOG.warning("Speech not ready when core reported ready!") sleep(5) response = self.bus.wait_for_response(Message('mycroft.voice.is_ready')) i += 1 diff --git a/test/test_skill_utils.py b/test/test_skill_utils.py index c796b00a0..57797a785 100644 --- a/test/test_skill_utils.py +++ b/test/test_skill_utils.py @@ -32,7 +32,6 @@ import unittest from os.path import dirname, join, exists, isdir from unittest.mock import patch -from unittest import skip sys.path.append(os.path.dirname(os.path.dirname(__file__))) @@ -43,7 +42,7 @@ ] TEST_SKILLS_WITH_PIP = [ - "https://github.com/NeonGeckoCom/skill-date_time/tree/dev", +# "https://github.com/NeonGeckoCom/skill-date_time/tree/dev", "git+https://github.com/NeonGeckoCom/malls-parser-skill", "neon-skill-support_helper" ] @@ -81,24 +80,6 @@ def test_get_skills_from_remote_list(self): self.assertTrue(all(skill.startswith("https://github.com") for skill in skills_list)) - @skip("Installation from Git is deprecated") - def test_install_skills_from_list_no_auth(self): - from neon_core.util.skill_utils import install_skills_from_list - install_skills_from_list(TEST_SKILLS_NO_AUTH, SKILL_CONFIG) - skill_dirs = [d for d in os.listdir(SKILL_DIR) - if os.path.isdir(os.path.join(SKILL_DIR, d))] - self.assertEqual(len(skill_dirs), len(TEST_SKILLS_NO_AUTH)) - self.assertIn("alerts.neon.neongeckocom", skill_dirs) - - @skip("Installation from Git is deprecated") - def test_install_skills_from_list_with_auth(self): - from neon_core.util.skill_utils import install_skills_from_list - install_skills_from_list(TEST_SKILLS_WITH_AUTH, SKILL_CONFIG) - skill_dirs = [d for d in os.listdir(SKILL_DIR) - if os.path.isdir(os.path.join(SKILL_DIR, d))] - self.assertEqual(len(skill_dirs), len(TEST_SKILLS_WITH_AUTH)) - self.assertIn("i-like-brands.neon.neongeckocom", skill_dirs) - @patch("neon_core.util.skill_utils.install_skills_from_list") def test_install_skills_default(self, install_skills): from neon_core.util.skill_utils import install_skills_default,\ @@ -119,17 +100,6 @@ def test_install_skills_with_pip(self): returned = os.system("pip show neon-skill-support-helper") self.assertEqual(returned, 0) - @skip("OSM skill installation deprecated") - def test_get_neon_skills_data(self): - from neon_core.util.skill_utils import get_neon_skills_data - from ovos_skills_manager.github.utils import normalize_github_url - neon_skills = get_neon_skills_data() - self.assertIsInstance(neon_skills, dict) - for skill in neon_skills: - self.assertIsInstance(neon_skills[skill], dict) - self.assertEqual(skill, - normalize_github_url(neon_skills[skill]["url"])) - def test_write_pip_constraints_to_file(self): from neon_core.util.skill_utils import _write_pip_constraints_to_file from neon_utils.packaging_utils import get_package_dependencies @@ -149,77 +119,6 @@ def test_write_pip_constraints_to_file(self): os.remove(test_outfile) - # def test_set_osm_constraints_file(self): - # import ovos_skills_manager.requirements - # from neon_core.util.skill_utils import set_osm_constraints_file - # set_osm_constraints_file(__file__) - # self.assertEqual(ovos_skills_manager.requirements.DEFAULT_CONSTRAINTS, - # __file__) - - @skip("Skill class patching is deprecated") - def test_skill_class_patches(self): - import neon_core.skills # Import to do all the patching - from neon_utils.skills.mycroft_skill import PatchedMycroftSkill - from mycroft.skills import MycroftSkill - from mycroft import MycroftSkill as MycroftSkill1 - from mycroft.skills.mycroft_skill import MycroftSkill as MycroftSkill2 - from mycroft.skills.core import MycroftSkill as MycroftSkill3 - from mycroft.skills.fallback_skill import FallbackSkill - from mycroft import FallbackSkill as FallbackSkill1 - from mycroft.skills.core import FallbackSkill as FallbackSkill2 - from mycroft.skills.common_play_skill import CommonPlaySkill - from mycroft.skills.common_query_skill import CommonQuerySkill - from mycroft.skills.common_iot_skill import CommonIoTSkill - - self.assertEqual(MycroftSkill, PatchedMycroftSkill) - self.assertEqual(MycroftSkill1, PatchedMycroftSkill) - self.assertEqual(MycroftSkill2, PatchedMycroftSkill) - self.assertEqual(MycroftSkill3, PatchedMycroftSkill) - - self.assertEqual(FallbackSkill1, FallbackSkill) - self.assertEqual(FallbackSkill2, FallbackSkill) - - from neon_utils.skills.neon_skill import NeonSkill - # self.assertTrue(issubclass(FallbackSkill, NeonSkill)) - self.assertTrue(issubclass(CommonPlaySkill, PatchedMycroftSkill)) - self.assertTrue(issubclass(CommonQuerySkill, PatchedMycroftSkill)) - self.assertTrue(issubclass(CommonIoTSkill, PatchedMycroftSkill)) - - from ovos_workshop.skills.mycroft_skill import MycroftSkill as Patched - from ovos_workshop.skills import MycroftSkill as Patched2 - # from ovos_workshop.skills.ovos import MycroftSkill as Patched3 - self.assertEqual(Patched, PatchedMycroftSkill) - self.assertEqual(Patched2, PatchedMycroftSkill) - # self.assertEqual(Patched3, PatchedMycroftSkill) - - from ovos_workshop.skills.ovos import OVOSSkill - from ovos_workshop.skills import OVOSSkill as OVOSSkill2 - # self.assertTrue(issubclass(OVOSSkill, PatchedMycroftSkill)) - self.assertEqual(OVOSSkill, OVOSSkill2) - - from neon_utils.skills import NeonFallbackSkill, NeonSkill - # self.assertTrue(issubclass(NeonFallbackSkill, PatchedMycroftSkill)) - # self.assertTrue(issubclass(NeonSkill, PatchedMycroftSkill)) - self.assertTrue(issubclass(NeonFallbackSkill, OVOSSkill)) - # self.assertTrue(issubclass(NeonFallbackSkill, NeonSkill)) - - from neon_utils.skills.neon_fallback_skill import NeonFallbackSkill as \ - NeonFallbackSkill2 - from neon_utils.skills.neon_skill import NeonSkill as NeonSkill2 - self.assertEqual(NeonFallbackSkill, NeonFallbackSkill2) - self.assertEqual(NeonSkill, NeonSkill2) - - # from ovos_workshop.skills.common_play import OVOSCommonPlaybackSkill - # self.assertTrue(issubclass(OVOSCommonPlaybackSkill, - # PatchedMycroftSkill)) - # - # try: - # from ovos_workshop.skills.common_query_skill import CommonQuerySkill - # self.assertTrue(issubclass(CommonQuerySkill, PatchedMycroftSkill)) - # except ModuleNotFoundError: - # # Class added in ovos-workwhop 0.0.12 - # pass - @patch("neon_core.util.skill_utils.Configuration") def test_update_default_resources(self, config): from neon_core.util.skill_utils import update_default_resources diff --git a/test/test_skills_module.py b/test/test_skills_module.py index ff20c68eb..1aeec77cd 100644 --- a/test/test_skills_module.py +++ b/test/test_skills_module.py @@ -32,12 +32,12 @@ import unittest import wave +from pytest import mark +from mock import Mock, patch from copy import deepcopy from os.path import join, dirname, expanduser, isdir from threading import Event from time import time - -from unittest.mock import Mock, patch from ovos_bus_client import Message from ovos_utils.messagebus import FakeBus from ovos_utils.xdg_utils import xdg_data_home @@ -66,13 +66,7 @@ class TestSkillService(unittest.TestCase): @classmethod def setUpClass(cls) -> None: - # from neon_core.util.runtime_utils import use_neon_core - # from neon_utils.configuration_utils import init_config_dir os.environ["XDG_CONFIG_HOME"] = cls.config_dir - os.environ["OVOS_CONFIG_BASE_FOLDER"] = "neon" - os.environ["OVOS_CONFIG_FILENAME"] = "neon.yaml" - # use_neon_core(init_config_dir)() - # assert os.path.isdir(cls.config_dir) @classmethod def tearDownClass(cls) -> None: @@ -80,12 +74,10 @@ def tearDownClass(cls) -> None: if os.path.exists(cls.config_dir): shutil.rmtree(cls.config_dir) - # @patch("neon_core.skills.skill_store.SkillsStore.install_default_skills") @patch("ovos_core.skill_manager.SkillManager.run") def test_neon_skills_service(self, run): from neon_core.skills.service import NeonSkillService from neon_core.skills.skill_manager import NeonSkillManager - # from mycroft.util.process_utils import ProcessState config = {"skills": { "disable_osm": False, @@ -186,16 +178,10 @@ class TestIntentService(unittest.TestCase): @classmethod def setUpClass(cls) -> None: - # from neon_core.util.runtime_utils import use_neon_core - # from neon_utils.configuration_utils import init_config_dir - # Import to set default config path import neon_core os.environ["XDG_CONFIG_HOME"] = cls.test_config_dir - os.environ["OVOS_CONFIG_BASE_FOLDER"] = "neon" - os.environ["OVOS_CONFIG_FILENAME"] = "neon.yaml" - # use_neon_core(init_config_dir)() import ovos_config import importlib importlib.reload(ovos_config.meta) @@ -209,6 +195,10 @@ def setUpClass(cls) -> None: importlib.reload(ovos_config) assert ovos_config.config.Configuration.default.path == meta['default_config_path'] + # Patch configuration + ovos_config.config.Configuration()[ + 'utterance_transformers']['neon_utterance_translator_plugin'] = {"active": True} + from neon_core.skills.intent_service import NeonIntentService cls.intent_service = NeonIntentService(cls.bus) assert set(cls.intent_service.config['utterance_transformers'].keys()) \ @@ -219,8 +209,6 @@ def setUpClass(cls) -> None: def tearDownClass(cls) -> None: cls.intent_service.shutdown() os.environ.pop("XDG_CONFIG_HOME") - os.environ.pop("OVOS_CONFIG_BASE_FOLDER") - os.environ.pop("OVOS_CONFIG_FILENAME") shutil.rmtree(cls.test_config_dir) def test_save_utterance_transcription(self): @@ -367,41 +355,15 @@ class TestSkillManager(unittest.TestCase): @classmethod def setUpClass(cls) -> None: - from neon_core.util.runtime_utils import use_neon_core - from neon_utils.configuration_utils import init_config_dir os.environ["XDG_CONFIG_HOME"] = cls.config_dir - os.environ["OVOS_CONFIG_BASE_FOLDER"] = "neon" - os.environ["OVOS_CONFIG_FILENAME"] = "neon.yaml" - use_neon_core(init_config_dir)() @classmethod def tearDownClass(cls) -> None: os.environ.pop("XDG_CONFIG_HOME") - os.environ.pop("OVOS_CONFIG_BASE_FOLDER") - os.environ.pop("OVOS_CONFIG_FILENAME") if os.path.isdir(cls.config_dir): shutil.rmtree(cls.config_dir) - # @patch("neon_core.skills.skill_store.SkillsStore.install_default_skills") - # @patch("mycroft.skills.skill_manager.SkillManager.run") - # def test_download_or_update_defaults(self, patched_run, patched_installer): - # from neon_core.configuration import patch_config - # patch_config({"skills": {"auto_update": True}}) - # - # from neon_core.skills.skill_manager import NeonSkillManager - # manager = NeonSkillManager(FakeBus()) - # self.assertTrue(manager.config["skills"]["auto_update"]) - # manager.run() - # patched_run.assert_called_once() - # patched_installer.assert_called_once() - # - # patched_installer.reset_mock() - # manager.config.update({"skills": {"auto_update": False}}) - # manager.download_or_update_defaults() - # patched_installer.assert_not_called() - # manager.stop() - - # @patch("neon_core.skills.skill_store.SkillsStore.install_default_skills") + @mark.skip("Skill directory handling is deprecated") @patch("ovos_core.skill_manager.SkillManager.run") def test_get_default_skills_dir(self, _): from neon_core.skills.skill_manager import NeonSkillManager @@ -440,189 +402,39 @@ def test_get_default_skills_dir(self, _): self.assertEqual(default_dir, expanduser('~/neon-skills')) self.assertTrue(isdir(expanduser("~/neon-skills"))) + def test_wait_until_skills_ready(self): + from neon_core.skills.skill_manager import NeonSkillManager + manager = NeonSkillManager(FakeBus()) + manager._network_skill_timeout = 1 + + # No ready settings is ready + manager.config['ready_settings'] = [] + self.assertTrue(manager._wait_until_skills_ready()) + + # Check network skills not ready + manager.config['ready_settings'] = ['network_skills'] + self.assertFalse(manager._wait_until_skills_ready()) + + # Check internet skills not ready + manager.config['ready_settings'].append('internet_skills') + self.assertFalse(manager._wait_until_skills_ready()) + + # Check skills are loaded + manager._network_loaded.set() + manager._internet_loaded.set() + self.assertTrue(manager._wait_until_skills_ready()) + + def test_check_device_ready(self): + from neon_core.skills.skill_manager import NeonSkillManager + manager = NeonSkillManager(FakeBus()) + + on_ready = Mock() + manager.bus.on("mycroft.ready", on_ready) -# class TestSkillStore(unittest.TestCase): -# essential = ["https://github.com/OpenVoiceOS/skill-ovos-homescreen/tree/main"] -# config = { -# "disable_osm": False, -# "auto_update": True, -# "auto_update_interval": 1, -# "appstore_sync_interval": 1, -# "neon_token": None, -# "essential_skills": essential, -# "install_default": True, -# "install_essential": True, -# "default_skills": "https://raw.githubusercontent.com/NeonGeckoCom/" -# "neon_skills/TEST_ShortSkillsList/skill_lists/" -# "TEST-SHORTLIST" -# } -# skill_dir = join(dirname(__file__), "skill_module_skills") -# bus = FakeBus() -# -# @classmethod -# def setUpClass(cls) -> None: -# import mycroft.skills.event_scheduler -# mocked_scheduler = MockEventSchedulerInterface -# mycroft.skills.event_scheduler.EventSchedulerInterface = \ -# mocked_scheduler -# import neon_core.skills.skill_store -# importlib.reload(neon_core.skills.skill_store) -# -# from neon_core.skills.skill_store import SkillsStore -# cls.skill_store = SkillsStore(cls.skill_dir, cls.config, cls.bus) -# -# @classmethod -# def tearDownClass(cls) -> None: -# cls.skill_store.shutdown() -# -# def test_00_store_init(self): -# self.assertEqual(self.skill_store.config, self.config) -# self.assertFalse(self.skill_store.disabled) -# self.assertEqual(self.skill_store.skills_dir, self.skill_dir) -# self.assertEqual(self.skill_store.bus, self.bus) -# self.assertIsNotNone(self.skill_store.osm) -# self.assertIsInstance(self.skill_store.scheduler, -# MockEventSchedulerInterface) -# self.assertEqual( -# self.skill_store.scheduler.schedule_repeating_event.call_count, 2) -# -# def test_schedule_sync(self): -# pass -# -# def test_schedule_update(self): -# pass -# -# def test_handle_update(self): -# pass -# -# def test_handle_sync_appstores(self): -# pass -# -# def test_handle_load_osm(self): -# from ovos_skills_manager import OVOSSkillsManager -# self.skill_store.disabled = True -# self.assertIsNone(self.skill_store.load_osm()) -# -# self.skill_store.disabled = False -# self.assertIsInstance(self.skill_store.load_osm(), OVOSSkillsManager) -# -# def test_essential_skills(self): -# self.assertFalse(self.skill_store.disabled) -# self.assertEqual(len(self.skill_store.essential_skills), -# len(self.essential)) -# -# def test_default_skills(self): -# self.assertFalse(self.skill_store.disabled) -# self.assertIsInstance(self.skill_store.default_skills, list) -# self.assertGreater(len(self.skill_store.default_skills), 0) -# -# def test_authenticate_neon(self): -# pass -# -# def test_deauthenticate_neon(self): -# pass -# -# def test_get_skill_entry(self): -# # TODO: Implement skills by ID after fixing in OSM -# # TODO: Support missing branch specs -# from ovos_skills_manager import SkillEntry -# url = "https://github.com/OpenVoiceOS/skill-ovos-homescreen/tree/main" -# # skill_id = "skill-ovos-homescreen.openvoiceos" -# url_entry = self.skill_store.get_skill_entry(url) -# self.assertIsInstance(url_entry, SkillEntry) -# # id_entry = self.skill_store.get_skill_entry(skill_id) -# # self.assertIsInstance(id_entry, SkillEntry) -# # self.assertEqual(url_entry.skill_name, id_entry.skill_name) -# -# def test_get_remote_entries(self): -# from neon_core.util.skill_utils import get_remote_entries -# test_urls = { -# "https://raw.githubusercontent.com/NeonGeckoCom/neon_skills/master/skill_lists/DEFAULT-SKILLS", -# "https://raw.githubusercontent.com/NeonGeckoCom/neon_skills/master/skill_lists/DEFAULT-PREMIUM-SKILLS" -# } -# for url in test_urls: -# self.assertEqual(self.skill_store.get_remote_entries(url), -# get_remote_entries(url)) -# -# def test_parse_config_entry(self): -# # TODO: Implement skills by ID after fixing in OSM -# from ovos_skills_manager import SkillEntry -# self.skill_store.osm.disable_appstore("local") -# -# valid_entry_url = self.config["default_skills"] -# valid_entry_list_url = self.config["essential_skills"] -# # valid_entry_list_id = ["skill-ovos-homescreen.openvoiceos", -# # "caffeinewiz.neon.neongeckocom"] -# -# self.skill_store.disabled = True -# self.assertEqual(self.skill_store._parse_config_entry(valid_entry_url), -# list()) -# self.skill_store.disabled = False -# -# # with self.assertRaises(ValueError): -# # self.skill_store._parse_config_entry(valid_entry_list_id[0]) -# -# with self.assertRaises(ValueError): -# self.skill_store._parse_config_entry(None) -# -# default_entries = self.skill_store._parse_config_entry(valid_entry_url) -# self.assertIsInstance(default_entries, list) -# self.assertTrue(all([isinstance(x, SkillEntry) -# for x in default_entries]), default_entries) -# -# essential_entries = \ -# self.skill_store._parse_config_entry(valid_entry_list_url) -# self.assertIsInstance(essential_entries, list) -# self.assertEqual(len(essential_entries), 1, essential_entries) -# self.assertIsInstance(essential_entries[0], SkillEntry) -# -# # list_entries = \ -# # self.skill_store._parse_config_entry(valid_entry_list_id) -# # self.assertIsInstance(list_entries, list) -# # self.assertEqual(len(list_entries), 2, list_entries) -# # self.assertTrue(all([isinstance(x, SkillEntry) -# # for x in list_entries]), list_entries) -# -# def test_install_skill(self): -# skill_entry = Mock() -# install_dir = self.skill_dir -# -# def skill_entry_installer(*_, **kwargs): -# self.assertEqual(kwargs["folder"], install_dir) -# if kwargs.get("update"): -# return True -# return False -# -# self.skill_store.disabled = True -# self.assertFalse(self.skill_store.install_skill(skill_entry)) -# self.skill_store.disabled = False -# -# skill_entry.install = skill_entry_installer -# self.assertFalse(self.skill_store.install_skill(skill_entry)) -# -# install_dir = "/tmp" -# self.assertTrue(self.skill_store.install_skill(skill_entry, "/tmp", -# update=True)) -# -# def test_install_default_skills(self): -# install_skill = Mock() -# real_install_skill = self.skill_store.install_skill -# self.skill_store.install_skill = install_skill -# -# self.skill_store.disabled = True -# self.assertEqual(self.skill_store.install_default_skills(), list()) -# self.assertEqual(self.skill_store.install_default_skills(True), list()) -# self.skill_store.disabled = False -# -# install_skill.reset_mock() -# skills = self.skill_store.install_default_skills(False) -# self.assertEqual(install_skill.call_count, len(skills)) -# -# install_skill.reset_mock() -# skills = self.skill_store.install_default_skills(True) -# self.assertEqual(install_skill.call_count, len(skills)) -# -# self.skill_store.install_skill = real_install_skill + # No services to wait for + manager.config['ready_settings'] = [] + manager._check_device_ready() + on_ready.assert_called_once() if __name__ == "__main__":