diff --git a/mycroft/skills/mycroft_skill/mycroft_skill.py b/mycroft/skills/mycroft_skill/mycroft_skill.py index b0aa023bfb..0db9064e45 100644 --- a/mycroft/skills/mycroft_skill/mycroft_skill.py +++ b/mycroft/skills/mycroft_skill/mycroft_skill.py @@ -44,7 +44,7 @@ from mycroft.util.log import LOG from .event_container import EventContainer, create_wrapper, get_handler_name from ..event_scheduler import EventSchedulerInterface from ..intent_service_interface import IntentServiceInterface -from ..settings import get_local_settings, save_settings +from ..settings import save_settings, Settings from ..skill_data import ( load_vocabulary, load_regex, @@ -129,7 +129,7 @@ class MycroftSkill: # Get directory of skill self.root_dir = dirname(abspath(sys.modules[self.__module__].__file__)) if use_settings: - self.settings = get_local_settings(self.root_dir, self.name) + self.settings = Settings(self) else: self.settings = None self.settings_change_callback = None diff --git a/mycroft/skills/settings.py b/mycroft/skills/settings.py index 4d46cabfeb..ebd1da5288 100644 --- a/mycroft/skills/settings.py +++ b/mycroft/skills/settings.py @@ -74,14 +74,17 @@ ONE_MINUTE = 60 def get_local_settings(skill_dir, skill_name) -> dict: """Build a dictionary using the JSON string stored in settings.json.""" settings_path = Path(skill_dir).joinpath('settings.json') - with open(str(settings_path)) as settings_file: - try: - skill_settings = json.load(settings_file) - # TODO change to check for JSONDecodeError in 19.08 - except Exception: - log_msg = 'Failed to load {} settings from settings.json' - LOG.exception(log_msg.format(skill_name)) - skill_settings = {} + if settings_path.is_file(): + with open(str(settings_path)) as settings_file: + try: + skill_settings = json.load(settings_file) + # TODO change to check for JSONDecodeError in 19.08 + except Exception: + log_msg = 'Failed to load {} settings from settings.json' + LOG.exception(log_msg.format(skill_name)) + skill_settings = {} + else: + skill_settings = {} return skill_settings @@ -362,3 +365,36 @@ class SkillSettingsDownloader: data={skill_gid: remote_settings} ) self.bus.emit(message) + + +# TODO: remove in 20.02 +class Settings: + def __init__(self, skill): + self._skill = skill + self._settings = get_local_settings(skill.root_dir, skill.name) + + def __getattr__(self, attr): + if attr not in ['store', 'set_changed_callback']: + return getattr(self._settings, attr) + else: + return super().getattr(attr) + + def __setitem__(self, key, val): + self._settings[key] = val + + def __getitem__(self, key): + return self._settings[key] + + def __iter__(self): + return iter(self._settings) + + def __contains__(self, key): + return key in self._settings + + def store(self, force=False): + LOG.warning('DEPRECATED - use mycroft.skills.settings.save_settings()') + save_settings(self._skill.root_dir, self._settings) + + def set_changed_callback(self, callback): + LOG.warning('DEPRECATED - set the settings_changed_callback attribute') + self._skill.settings_change_callback = callback diff --git a/test/unittests/skills/test_settings.py b/test/unittests/skills/test_settings.py index a698ad10da..21a705e956 100644 --- a/test/unittests/skills/test_settings.py +++ b/test/unittests/skills/test_settings.py @@ -13,13 +13,15 @@ # limitations under the License. # import json -from unittest.mock import call, Mock, patch -from os import remove +import tempfile from pathlib import Path +from unittest import TestCase +from unittest.mock import call, Mock, patch from mycroft.skills.settings import ( SkillSettingsDownloader, - SettingsMetaUploader + SettingsMetaUploader, + Settings ) from ..base import MycroftUnitTestBase @@ -287,3 +289,55 @@ class TestSettingsDownloader(MycroftUnitTestBase): [remote_skill_settings], self.message_bus_mock.message_data ) + + +class TestSettings(TestCase): + def setUp(self) -> None: + temp_dir = tempfile.mkdtemp() + self.temp_dir = Path(temp_dir) + self.skill_mock = Mock() + self.skill_mock.root_dir = str(self.temp_dir) + self.skill_mock.name = 'test_skill' + + def test_empty_settings(self): + settings = Settings(self.skill_mock) + self.assertDictEqual(settings._settings, {}) + + def test_settings_file_exists(self): + settings_path = self.temp_dir.joinpath('settings.json') + with open(settings_path, 'w') as settings_file: + settings_file.write('{"foo": "bar"}\n') + + settings = Settings(self.skill_mock) + self.assertDictEqual(settings._settings, {'foo': 'bar'}) + self.assertEqual(settings['foo'], 'bar') + self.assertNotIn('store', settings) + self.assertIn('foo', settings) + + def test_change_settings(self): + settings = Settings(self.skill_mock) + settings['foo'] = 'bar' + self.assertDictEqual(settings._settings, {'foo': 'bar'}) + self.assertIn('foo', settings) + + def test_store_settings(self): + settings = Settings(self.skill_mock) + settings['foo'] = 'bar' + settings.store() + settings_path = self.temp_dir.joinpath('settings.json') + with open(settings_path) as settings_file: + file_contents = settings_file.read() + + self.assertEqual(file_contents, '{"foo": "bar"}') + + def test_set_changed_callback(self): + def test_callback(): + pass + + settings = Settings(self.skill_mock) + settings.set_changed_callback(test_callback) + + self.assertEqual( + self.skill_mock.settings_change_callback, + test_callback + )