refactored skill settings and fixed test
parent
6eb1c194cb
commit
37ada28dbd
|
@ -232,7 +232,7 @@ class MycroftSkill(object):
|
|||
try:
|
||||
return self._settings
|
||||
except:
|
||||
self._settings = SkillSettings(self._dir, self.name)
|
||||
self._settings = SkillSettings(self._dir)
|
||||
return self._settings
|
||||
|
||||
def bind(self, emitter):
|
||||
|
|
|
@ -44,33 +44,32 @@ class SkillSettings(dict):
|
|||
"""
|
||||
SkillSettings creates a dictionary that can easily be stored
|
||||
to file, serialized as json. It also syncs to the backend for
|
||||
skill configuration
|
||||
skill settings
|
||||
|
||||
Args:
|
||||
settings_file (str): Path to storage file
|
||||
"""
|
||||
def __init__(self, directory, name):
|
||||
def __init__(self, directory):
|
||||
super(SkillSettings, self).__init__()
|
||||
self.api = DeviceApi()
|
||||
self.name = name
|
||||
self._device_identity = self.api.identity.uuid
|
||||
|
||||
# set file paths
|
||||
self._settings_path = join(directory, 'settings.json')
|
||||
self._meta_path = join(directory, 'settingsmeta.json')
|
||||
self._identity_path = join(
|
||||
expanduser('~/.mycroft/skills/') + self.name, 'identity.json')
|
||||
self._hashed_meta_path = join(
|
||||
expanduser('~/.mycroft/skills/' + self.name), 'hashed_meta.json')
|
||||
self._api_path = "/" + self._device_identity + "/skill"
|
||||
|
||||
self.loaded_hash = hash(str(self))
|
||||
|
||||
# if settingsmeta.json exists
|
||||
if isfile(self._meta_path):
|
||||
self.settings_meta = self._load_settings_meta()
|
||||
self.settings = self._get_settings()
|
||||
self._send_settings_meta()
|
||||
self._patch_settings_meta()
|
||||
self._poll_skill_settings()
|
||||
self._load_skill_settings()
|
||||
# start polling timer
|
||||
Timer(60, self._poll_skill_settings).start()
|
||||
|
||||
@property
|
||||
def _is_stored(self):
|
||||
return hash(str(self)) == self.loaded_hash
|
||||
self.load_skill_settings()
|
||||
|
||||
def __getitem__(self, key):
|
||||
return super(SkillSettings, self).__getitem__(key)
|
||||
|
@ -81,102 +80,73 @@ class SkillSettings(dict):
|
|||
"""
|
||||
return super(SkillSettings, self).__setitem__(key, value)
|
||||
|
||||
def _load_settings_meta(self):
|
||||
with open(self._meta_path) as f:
|
||||
data = json.load(f)
|
||||
return data
|
||||
|
||||
def _skill_exist_in_backend(self):
|
||||
"""
|
||||
see if skill settings already exist in the backend
|
||||
"""
|
||||
skill_identity = self._get_skill_identity()
|
||||
for skill_setting in self.settings:
|
||||
if skill_identity == skill_setting["identifier"]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _send_settings_meta(self):
|
||||
"""
|
||||
If settings meta data exists and skill does not have a uuid,
|
||||
send settingsmeta.json to the backend and store uuid for skill
|
||||
send settingsmeta.json to the backend if skill doesn't
|
||||
already exist
|
||||
"""
|
||||
if isfile(self._meta_path):
|
||||
with open(self._meta_path) as f:
|
||||
self.settings_meta = json.load(f)
|
||||
|
||||
# If skill is loaded for the first time post metadata
|
||||
# to backend and store uuid for skill
|
||||
try:
|
||||
if not isfile(self._identity_path):
|
||||
if self._skill_exist_in_backend is False:
|
||||
response = self._put_metadata(self.settings_meta)
|
||||
self._store_uuid(response)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
def _patch_settings_meta(self):
|
||||
"""
|
||||
If settingsmeta.json is edited, do a PUT request to
|
||||
implement the changes
|
||||
TODO: allow structure changes i.e. ability to add more sections
|
||||
"""
|
||||
try:
|
||||
if isfile(self._meta_path):
|
||||
with open(self._meta_path, 'r') as f:
|
||||
self.settings_meta = json.load(f)
|
||||
hashed_meta = hash(str(self.settings_meta))
|
||||
|
||||
if isfile(self._hashed_meta_path):
|
||||
with open(self._hashed_meta_path, 'r') as f:
|
||||
hashed_data = json.load(f)
|
||||
|
||||
if hashed_data["hashed_meta"] != hashed_meta:
|
||||
skill_object = self._get_settings()
|
||||
skill_identity = self._get_skill_identity()
|
||||
|
||||
settings = {}
|
||||
for skill_setting in skill_object:
|
||||
if skill_setting['uuid'] == skill_identity:
|
||||
settings = skill_setting
|
||||
settings["skillMetadata"]["sections"] = \
|
||||
self.settings_meta["skillMetadata"]["sections"]
|
||||
|
||||
self._put_metadata(settings)
|
||||
else:
|
||||
if isfile(self._meta_path):
|
||||
with open(self._hashed_meta_path, 'w') as f:
|
||||
hashed_data = {
|
||||
"hashed_meta": hash(str(self.settings_meta))
|
||||
}
|
||||
json.dump(hashed_data, f)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
def _poll_skill_settings(self):
|
||||
"""
|
||||
If uuid exists for this skill poll to backend to
|
||||
If identifier exists for this skill poll to backend to
|
||||
request settings and store it if it changes
|
||||
TODO: implement as websocket
|
||||
"""
|
||||
if isfile(self._identity_path):
|
||||
skill_identity = self._get_skill_identity()
|
||||
|
||||
if self._skill_exist_in_backend:
|
||||
try:
|
||||
response = self._get_settings()
|
||||
|
||||
for skill_setting in response:
|
||||
if skill_setting['uuid'] == skill_identity:
|
||||
# update settings
|
||||
self.settings = self._get_settings()
|
||||
skill_identity = self._get_skill_identity()
|
||||
for skill_setting in self.settings:
|
||||
if skill_setting['identifier'] == skill_identity:
|
||||
sections = skill_setting['skillMetadata']['sections']
|
||||
for section in sections:
|
||||
for field in section["fields"]:
|
||||
self.__setitem__(field["name"], field["value"])
|
||||
|
||||
# store value if settings has changed from backend
|
||||
new_hash = hash(str(self))
|
||||
if new_hash != self.loaded_hash:
|
||||
self.store()
|
||||
self.loaded_hash = hash(str(self))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
self.store()
|
||||
# poll backend every 60 seconds for new settings
|
||||
Timer(60, self._poll_skill_settings).start()
|
||||
|
||||
def _get_skill_identity(self):
|
||||
"""
|
||||
returns the skill uuid
|
||||
returns the skill identifier
|
||||
"""
|
||||
try:
|
||||
with open(self._identity_path, 'r') as f:
|
||||
data = json.load(f)
|
||||
return data["uuid"]
|
||||
|
||||
return self.settings_meta["identifier"]
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return None
|
||||
|
||||
def _load_skill_settings(self):
|
||||
def load_skill_settings(self):
|
||||
"""
|
||||
If settings.json exist, open and read stored values into self
|
||||
"""
|
||||
|
@ -191,13 +161,6 @@ class SkillSettings(dict):
|
|||
# metadata to be able to edit later.
|
||||
logger.error(e)
|
||||
|
||||
def _store_uuid(self, uuid):
|
||||
"""
|
||||
Store uuid as identity.json in ~/.mycroft/skills/{skillname}
|
||||
"""
|
||||
with open(self._identity_path, 'w') as f:
|
||||
json.dump(uuid, f)
|
||||
|
||||
def _get_settings(self):
|
||||
"""
|
||||
Get skill settings for this device from backend
|
||||
|
@ -220,9 +183,7 @@ class SkillSettings(dict):
|
|||
|
||||
def store(self):
|
||||
"""
|
||||
Store dictionary to file if it has changed
|
||||
Store dictionary to file
|
||||
"""
|
||||
if not self._is_stored:
|
||||
with open(self._settings_path, 'w') as f:
|
||||
json.dump(self, f)
|
||||
self.loaded_hash = hash(str(self))
|
||||
|
|
|
@ -8,28 +8,21 @@ import unittest
|
|||
class SkillSettingsTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
try:
|
||||
remove(join(dirname(__file__), 'settings'))
|
||||
remove(join(dirname(__file__), 'settings', 'settings.json'))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def test_new(self):
|
||||
s = SkillSettings(join(dirname(__file__), 'settings.json'),
|
||||
"skill_name")
|
||||
s = SkillSettings(join(dirname(__file__), 'settings'))
|
||||
self.assertEqual(len(s), 0)
|
||||
|
||||
def test_add_value(self):
|
||||
s = SkillSettings(join(dirname(__file__), 'settings.json'),
|
||||
"skill_name")
|
||||
s = SkillSettings(join(dirname(__file__), 'settings'))
|
||||
s['test_val'] = 1
|
||||
|
||||
def test_load_existing(self):
|
||||
s = SkillSettings(join(dirname(__file__), 'settings'),
|
||||
"skill_name")
|
||||
self.assertEqual(len(s), 7)
|
||||
self.assertEqual(s['test_val'], 1)
|
||||
|
||||
def test_store(self):
|
||||
s = SkillSettings(join(dirname(__file__), 'settings'),
|
||||
"skill_name")
|
||||
s = SkillSettings(join(dirname(__file__), 'settings'))
|
||||
s['bool'] = True
|
||||
s['int'] = 42
|
||||
s['float'] = 4.2
|
||||
|
@ -37,51 +30,42 @@ class SkillSettingsTest(unittest.TestCase):
|
|||
s['list'] = ['batman', 2, True, 'superman']
|
||||
s.store()
|
||||
|
||||
s2 = SkillSettings(join(dirname(__file__), 'settings'),
|
||||
"skill_name")
|
||||
s2 = SkillSettings(join(dirname(__file__), 'settings'))
|
||||
for key in s:
|
||||
self.assertEqual(s[key], s2[key])
|
||||
|
||||
def test_update_list(self):
|
||||
s = SkillSettings(join(dirname(__file__), 'settings'),
|
||||
"skill_name")
|
||||
s = SkillSettings(join(dirname(__file__), 'settings'))
|
||||
s['l'] = ['a', 'b', 'c']
|
||||
s.store()
|
||||
s2 = SkillSettings(join(dirname(__file__), 'settings'),
|
||||
"skill_name")
|
||||
s2 = SkillSettings(join(dirname(__file__), 'settings'))
|
||||
self.assertEqual(s['l'], s2['l'])
|
||||
|
||||
# Update list
|
||||
s2['l'].append('d')
|
||||
s2.store()
|
||||
s3 = SkillSettings(join(dirname(__file__), 'settings'),
|
||||
"skill_name")
|
||||
s3 = SkillSettings(join(dirname(__file__), 'settings'))
|
||||
self.assertEqual(s2['l'], s3['l'])
|
||||
|
||||
def test_update_dict(self):
|
||||
s = SkillSettings(join(dirname(__file__), 'settings'),
|
||||
"skill_name")
|
||||
s = SkillSettings(join(dirname(__file__), 'settings'))
|
||||
s['d'] = {'a': 1, 'b': 2}
|
||||
s.store()
|
||||
s2 = SkillSettings(join(dirname(__file__), 'settings'),
|
||||
"skill_name")
|
||||
s2 = SkillSettings(join(dirname(__file__), 'settings'))
|
||||
self.assertEqual(s['d'], s2['d'])
|
||||
|
||||
# Update dict
|
||||
s2['d']['c'] = 3
|
||||
s2.store()
|
||||
s3 = SkillSettings(join(dirname(__file__), 'settings'),
|
||||
"skill_name")
|
||||
s3 = SkillSettings(join(dirname(__file__), 'settings'))
|
||||
self.assertEqual(s2['d'], s3['d'])
|
||||
|
||||
def test_no_change(self):
|
||||
s = SkillSettings(join(dirname(__file__), 'settings'),
|
||||
"skill_name")
|
||||
s = SkillSettings(join(dirname(__file__), 'settings'))
|
||||
s['d'] = {'a': 1, 'b': 2}
|
||||
s.store()
|
||||
|
||||
s2 = SkillSettings(join(dirname(__file__), 'settings'),
|
||||
"skill_name")
|
||||
s2 = SkillSettings(join(dirname(__file__), 'settings'))
|
||||
self.assertTrue(len(s) == len(s2))
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue