config.py
Go to the documentation of this file.
1 
2 # Copyright 2017 Mycroft AI Inc.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 #
16 
17 import re
18 import json
19 import inflection
20 from os.path import exists, isfile
21 from requests import RequestException
22 
23 from mycroft.util.json_helper import load_commented_json, merge_dict
24 from mycroft.util.log import LOG
25 
26 from .locations import (DEFAULT_CONFIG, SYSTEM_CONFIG, USER_CONFIG,
27  WEB_CONFIG_CACHE)
28 
29 
30 def is_remote_list(values):
31  ''' check if this list corresponds to a backend formatted collection of
32  dictionaries '''
33  for v in values:
34  if not isinstance(v, dict):
35  return False
36  if "@type" not in v.keys():
37  return False
38  return True
39 
40 
41 def translate_remote(config, setting):
42  """
43  Translate config names from server to equivalents usable
44  in mycroft-core.
45 
46  Args:
47  config: base config to populate
48  settings: remote settings to be translated
49  """
50  IGNORED_SETTINGS = ["uuid", "@type", "active", "user", "device"]
51 
52  for k, v in setting.items():
53  if k not in IGNORED_SETTINGS:
54  # Translate the CamelCase values stored remotely into the
55  # Python-style names used within mycroft-core.
56  key = inflection.underscore(re.sub(r"Setting(s)?", "", k))
57  if isinstance(v, dict):
58  config[key] = config.get(key, {})
59  translate_remote(config[key], v)
60  elif isinstance(v, list):
61  if is_remote_list(v):
62  if key not in config:
63  config[key] = {}
64  translate_list(config[key], v)
65  else:
66  config[key] = v
67  else:
68  config[key] = v
69 
70 
71 def translate_list(config, values):
72  """
73  Translate list formated by mycroft server.
74 
75  Args:
76  config (dict): target config
77  values (list): list from mycroft server config
78  """
79  for v in values:
80  module = v["@type"]
81  if v.get("active"):
82  config["module"] = module
83  config[module] = config.get(module, {})
84  translate_remote(config[module], v)
85 
86 
87 class LocalConf(dict):
88  """
89  Config dict from file.
90  """
91  def __init__(self, path):
92  super(LocalConf, self).__init__()
93  if path:
94  self.path = path
95  self.load_local(path)
96 
97  def load_local(self, path):
98  """
99  Load local json file into self.
100 
101  Args:
102  path (str): file to load
103  """
104  if exists(path) and isfile(path):
105  try:
106  config = load_commented_json(path)
107  for key in config:
108  self.__setitem__(key, config[key])
109 
110  LOG.debug("Configuration {} loaded".format(path))
111  except Exception as e:
112  LOG.error("Error loading configuration '{}'".format(path))
113  LOG.error(repr(e))
114  else:
115  LOG.debug("Configuration '{}' not defined, skipping".format(path))
116 
117  def store(self, path=None):
118  """
119  Cache the received settings locally. The cache will be used if
120  the remote is unreachable to load settings that are as close
121  to the user's as possible
122  """
123  path = path or self.path
124  with open(path, 'w') as f:
125  json.dump(self, f, indent=2)
126 
127  def merge(self, conf):
128  merge_dict(self, conf)
129 
130 
132  """
133  Config dict fetched from mycroft.ai
134  """
135  def __init__(self, cache=None):
136  super(RemoteConf, self).__init__(None)
137 
138  cache = cache or WEB_CONFIG_CACHE
139  from mycroft.api import is_paired
140  if not is_paired():
141  self.load_local(cache)
142  return
143 
144  try:
145  # Here to avoid cyclic import
146  from mycroft.api import DeviceApi
147  api = DeviceApi()
148  setting = api.get_settings()
149 
150  try:
151  location = api.get_location()
152  except RequestException as e:
153  LOG.error("RequestException fetching remote location: {}"
154  .format(str(e)))
155  if exists(cache) and isfile(cache):
156  location = load_commented_json(cache).get('location')
157 
158  if location:
159  setting["location"] = location
160  # Remove server specific entries
161  config = {}
162  translate_remote(config, setting)
163  for key in config:
164  self.__setitem__(key, config[key])
165  self.store(cache)
166 
167  except RequestException as e:
168  LOG.error("RequestException fetching remote configuration: {}"
169  .format(str(e)))
170  self.load_local(cache)
171 
172  except Exception as e:
173  LOG.error("Failed to fetch remote configuration: %s" % repr(e),
174  exc_info=True)
175  self.load_local(cache)
176 
177 
179  __config = {} # Cached config
180  __patch = {} # Patch config that skills can update to override config
181 
182  @staticmethod
183  def get(configs=None, cache=True):
184  """
185  Get configuration, returns cached instance if available otherwise
186  builds a new configuration dict.
187 
188  Args:
189  configs (list): List of configuration dicts
190  cache (boolean): True if the result should be cached
191  """
192  if Configuration.__config:
193  return Configuration.__config
194  else:
195  return Configuration.load_config_stack(configs, cache)
196 
197  @staticmethod
198  def load_config_stack(configs=None, cache=False):
199  """
200  load a stack of config dicts into a single dict
201 
202  Args:
203  configs (list): list of dicts to load
204  cache (boolean): True if result should be cached
205 
206  Returns: merged dict of all configuration files
207  """
208  if not configs:
209  configs = [LocalConf(DEFAULT_CONFIG), RemoteConf(),
210  LocalConf(SYSTEM_CONFIG), LocalConf(USER_CONFIG),
211  Configuration.__patch]
212  else:
213  # Handle strings in stack
214  for index, item in enumerate(configs):
215  if isinstance(item, str):
216  configs[index] = LocalConf(item)
217 
218  # Merge all configs into one
219  base = {}
220  for c in configs:
221  merge_dict(base, c)
222 
223  # copy into cache
224  if cache:
225  Configuration.__config.clear()
226  for key in base:
227  Configuration.__config[key] = base[key]
228  return Configuration.__config
229  else:
230  return base
231 
232  @staticmethod
233  def init(ws):
234  """
235  Setup websocket handlers to update config.
236 
237  Args:
238  ws: Websocket instance
239  """
240  ws.on("configuration.updated", Configuration.updated)
241  ws.on("configuration.patch", Configuration.patch)
242 
243  @staticmethod
244  def updated(message):
245  """
246  handler for configuration.updated, triggers an update
247  of cached config.
248  """
249  Configuration.load_config_stack(cache=True)
250 
251  @staticmethod
252  def patch(message):
253  """
254  patch the volatile dict usable by skills
255 
256  Args:
257  message: Messagebus message should contain a config
258  in the data payload.
259  """
260  config = message.data.get("config", {})
261  merge_dict(Configuration.__patch, config)
262  Configuration.load_config_stack(cache=True)
def translate_remote(config, setting)
Definition: config.py:41
def is_remote_list(values)
Definition: config.py:30
def is_paired(ignore_errors=True)
def __init__(self, cache=None)
Definition: config.py:135
def load_config_stack(configs=None, cache=False)
Definition: config.py:198
def load_commented_json(filename)
Definition: json_helper.py:35
def translate_list(config, values)
Definition: config.py:71
def get(configs=None, cache=True)
Definition: config.py:183
def store(self, path=None)
Definition: config.py:117
def merge_dict(base, delta)
Definition: json_helper.py:18
def get(phrase, lang=None, context=None)


mycroft_ros
Author(s):
autogenerated on Mon Apr 26 2021 02:35:40