From 2525efcd8c1d13de092cb8f18a3c8ab4fe660e1b Mon Sep 17 00:00:00 2001 From: Daniel McKnight Date: Fri, 29 Aug 2025 16:11:27 -0700 Subject: [PATCH 1/4] Refactor into a structure that supports editable installation --- .github/workflows/skill_tests.yml | 5 +- __init__.py | 7 +- neon_skill_speed_test/__init__.py | 87 +++++++++++++++++++ .../en-us/dialog/notify_testing.dialog | 0 .../locale}/en-us/dialog/results.dialog | 0 .../locale}/en-us/dialog/start_test.dialog | 0 .../en-us/intent/run_speed_test.intent | 0 .../requirements.txt | 0 setup.py | 7 +- 9 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 neon_skill_speed_test/__init__.py rename {locale => neon_skill_speed_test/locale}/en-us/dialog/notify_testing.dialog (100%) rename {locale => neon_skill_speed_test/locale}/en-us/dialog/results.dialog (100%) rename {locale => neon_skill_speed_test/locale}/en-us/dialog/start_test.dialog (100%) rename {locale => neon_skill_speed_test/locale}/en-us/intent/run_speed_test.intent (100%) rename requirements.txt => requirements/requirements.txt (100%) diff --git a/.github/workflows/skill_tests.yml b/.github/workflows/skill_tests.yml index 9a09e30..dc81d87 100644 --- a/.github/workflows/skill_tests.yml +++ b/.github/workflows/skill_tests.yml @@ -14,9 +14,12 @@ jobs: uses: neongeckocom/.github/.github/workflows/skill_test_intents.yml@master with: test_padatious: true + skill_entrypoint: skill-speed_test.neongeckocom skill_resource_tests: uses: neongeckocom/.github/.github/workflows/skill_test_resources.yml@master + with: + skill_entrypoint: skill-speed_test.neongeckocom skill_install_tests: uses: neongeckocom/.github/.github/workflows/skill_test_installation.yml@master with: - test_osm: false \ No newline at end of file + test_osm: false diff --git a/__init__.py b/__init__.py index 1b5c916..427615a 100644 --- a/__init__.py +++ b/__init__.py @@ -39,16 +39,11 @@ class SpeedTestSkill(NeonSkill): def __init__(self, **kwargs): NeonSkill.__init__(self, **kwargs) self._test = None - try: - server = self.test.get_best_server() - LOG.debug(f"Selected: {server}") - except Exception as e: - LOG.exception(e) # TODO: Support configured server @property def test(self): - # TODO: Handle 403 errors + # TODO: Handle 403 (rate-limiting) errors self._test = self._test or speedtest.Speedtest() return self._test diff --git a/neon_skill_speed_test/__init__.py b/neon_skill_speed_test/__init__.py new file mode 100644 index 0000000..1b5c916 --- /dev/null +++ b/neon_skill_speed_test/__init__.py @@ -0,0 +1,87 @@ +# 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 speedtest + +from ovos_utils import classproperty +from ovos_utils.log import LOG +from ovos_utils.process_utils import RuntimeRequirements +from neon_utils.skills.neon_skill import NeonSkill +from ovos_workshop.decorators import intent_handler + + +class SpeedTestSkill(NeonSkill): + def __init__(self, **kwargs): + NeonSkill.__init__(self, **kwargs) + self._test = None + try: + server = self.test.get_best_server() + LOG.debug(f"Selected: {server}") + except Exception as e: + LOG.exception(e) + # TODO: Support configured server + + @property + def test(self): + # TODO: Handle 403 errors + self._test = self._test or speedtest.Speedtest() + return self._test + + @classproperty + def runtime_requirements(self): + return RuntimeRequirements(network_before_load=False, + internet_before_load=False, + gui_before_load=False, + requires_internet=True, + requires_network=True, + requires_gui=False, + no_internet_fallback=False, + no_network_fallback=False, + no_gui_fallback=True) + + @intent_handler("run_speed_test.intent") + def handle_run_speed_test(self, message): + self.speak_dialog("start_test") + self.bus.emit(message.forward( + "ovos.notification.api.set.controlled", + {"sender": self.skill_id, + "text": self.translate("notify_testing")})) + self.test.download() + self.test.upload() + res = self.test.results.dict() + # TODO: Better rounding logic for kbps vs Mbps vs Gbps + down = round(res['download']/1000000) + up = round(res['upload']/1000000) + ping = round(res['ping']) + LOG.info(res) + self.bus.emit(message.forward( + "ovos.notification.api.remove.controlled")) + self.speak_dialog("results", {'down': down, 'up': up, 'ping': ping}) + + def stop(self): + pass diff --git a/locale/en-us/dialog/notify_testing.dialog b/neon_skill_speed_test/locale/en-us/dialog/notify_testing.dialog similarity index 100% rename from locale/en-us/dialog/notify_testing.dialog rename to neon_skill_speed_test/locale/en-us/dialog/notify_testing.dialog diff --git a/locale/en-us/dialog/results.dialog b/neon_skill_speed_test/locale/en-us/dialog/results.dialog similarity index 100% rename from locale/en-us/dialog/results.dialog rename to neon_skill_speed_test/locale/en-us/dialog/results.dialog diff --git a/locale/en-us/dialog/start_test.dialog b/neon_skill_speed_test/locale/en-us/dialog/start_test.dialog similarity index 100% rename from locale/en-us/dialog/start_test.dialog rename to neon_skill_speed_test/locale/en-us/dialog/start_test.dialog diff --git a/locale/en-us/intent/run_speed_test.intent b/neon_skill_speed_test/locale/en-us/intent/run_speed_test.intent similarity index 100% rename from locale/en-us/intent/run_speed_test.intent rename to neon_skill_speed_test/locale/en-us/intent/run_speed_test.intent diff --git a/requirements.txt b/requirements/requirements.txt similarity index 100% rename from requirements.txt rename to requirements/requirements.txt diff --git a/setup.py b/setup.py index 76b78d3..806879e 100644 --- a/setup.py +++ b/setup.py @@ -30,14 +30,14 @@ from os import getenv, path, walk SKILL_NAME = "skill-speed_test" -SKILL_PKG = SKILL_NAME.replace('-', '_') +SKILL_PKG = "neon_" + SKILL_NAME.replace('-', '_') # skill_id=package_name:SkillClass PLUGIN_ENTRY_POINT = f'{SKILL_NAME}.neongeckocom={SKILL_PKG}:SpeedTestSkill' BASE_PATH = path.abspath(path.dirname(__file__)) def get_requirements(requirements_filename: str): - requirements_file = path.join(BASE_PATH, requirements_filename) + requirements_file = path.join(BASE_PATH, "requirements", requirements_filename) with open(requirements_file, 'r', encoding='utf-8') as r: requirements = r.readlines() requirements = [r.strip() for r in requirements if r.strip() @@ -89,12 +89,11 @@ def find_resource_files(): url=f'https://github.com/NeonGeckoCom/{SKILL_NAME}', license='BSD-3-Clause', install_requires=get_requirements("requirements.txt"), - extras_require={"test": get_requirements("requirements/test.txt")}, + extras_require={"test": get_requirements("test.txt")}, author='Neongecko', author_email='developers@neon.ai', long_description=long_description, long_description_content_type="text/markdown", - package_dir={SKILL_PKG: ""}, packages=[SKILL_PKG], package_data={SKILL_PKG: find_resource_files()}, include_package_data=True, From b065b8d5388d157b96239380617fc819a814c59e Mon Sep 17 00:00:00 2001 From: Daniel McKnight Date: Fri, 29 Aug 2025 16:16:36 -0700 Subject: [PATCH 2/4] Update neon-minerva test dependency for OVOS intent test compat --- requirements/test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/test.txt b/requirements/test.txt index f2a694a..55b7220 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1 +1 @@ -neon-minerva[padatious]~=0.3 +neon-minerva[padatious]~=0.3,>=0.3.1a2 From 222a4e95304e025f53befb5fe17432cb3c60f9c2 Mon Sep 17 00:00:00 2001 From: Daniel McKnight Date: Fri, 29 Aug 2025 16:20:04 -0700 Subject: [PATCH 3/4] Fix resource file path in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 806879e..6748c8f 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ def get_requirements(requirements_filename: str): def find_resource_files(): resource_base_dirs = ("locale", "ui", "vocab", "dialog", "regex") - base_dir = BASE_PATH + base_dir = path.join(BASE_PATH, SKILL_PKG) package_data = ["skill.json"] for res in resource_base_dirs: if path.isdir(path.join(base_dir, res)): From f89cde92534bebf3e2882e70f0eb867276d5e04e Mon Sep 17 00:00:00 2001 From: Daniel McKnight Date: Fri, 29 Aug 2025 16:29:09 -0700 Subject: [PATCH 4/4] Fix change to __init__.py --- __init__.py | 82 ------------------------------- neon_skill_speed_test/__init__.py | 5 -- 2 files changed, 87 deletions(-) delete mode 100644 __init__.py diff --git a/__init__.py b/__init__.py deleted file mode 100644 index 427615a..0000000 --- a/__init__.py +++ /dev/null @@ -1,82 +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 speedtest - -from ovos_utils import classproperty -from ovos_utils.log import LOG -from ovos_utils.process_utils import RuntimeRequirements -from neon_utils.skills.neon_skill import NeonSkill -from ovos_workshop.decorators import intent_handler - - -class SpeedTestSkill(NeonSkill): - def __init__(self, **kwargs): - NeonSkill.__init__(self, **kwargs) - self._test = None - # TODO: Support configured server - - @property - def test(self): - # TODO: Handle 403 (rate-limiting) errors - self._test = self._test or speedtest.Speedtest() - return self._test - - @classproperty - def runtime_requirements(self): - return RuntimeRequirements(network_before_load=False, - internet_before_load=False, - gui_before_load=False, - requires_internet=True, - requires_network=True, - requires_gui=False, - no_internet_fallback=False, - no_network_fallback=False, - no_gui_fallback=True) - - @intent_handler("run_speed_test.intent") - def handle_run_speed_test(self, message): - self.speak_dialog("start_test") - self.bus.emit(message.forward( - "ovos.notification.api.set.controlled", - {"sender": self.skill_id, - "text": self.translate("notify_testing")})) - self.test.download() - self.test.upload() - res = self.test.results.dict() - # TODO: Better rounding logic for kbps vs Mbps vs Gbps - down = round(res['download']/1000000) - up = round(res['upload']/1000000) - ping = round(res['ping']) - LOG.info(res) - self.bus.emit(message.forward( - "ovos.notification.api.remove.controlled")) - self.speak_dialog("results", {'down': down, 'up': up, 'ping': ping}) - - def stop(self): - pass diff --git a/neon_skill_speed_test/__init__.py b/neon_skill_speed_test/__init__.py index 1b5c916..766d3e1 100644 --- a/neon_skill_speed_test/__init__.py +++ b/neon_skill_speed_test/__init__.py @@ -39,11 +39,6 @@ class SpeedTestSkill(NeonSkill): def __init__(self, **kwargs): NeonSkill.__init__(self, **kwargs) self._test = None - try: - server = self.test.get_best_server() - LOG.debug(f"Selected: {server}") - except Exception as e: - LOG.exception(e) # TODO: Support configured server @property