data.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. # data, data_definition, config_data, module_config_data and ui_config_data classes
  2. # we might want to split these up :)
  3. import ast
  4. class DataNotFoundError(Exception): pass
  5. class DataTypeError(Exception): pass
  6. def merge(orig, new):
  7. """Merges the contents of new into orig, think recursive update()
  8. orig and new must both be dicts. If an element value is None in
  9. new it will be removed in orig."""
  10. for kn in new.keys():
  11. if kn in orig:
  12. if new[kn]:
  13. if type(new[kn]) == dict:
  14. merge(orig[kn], new[kn])
  15. else:
  16. orig[kn] = new[kn]
  17. else:
  18. orig.remove(kn)
  19. else:
  20. orig[kn] = new[kn]
  21. def find(element, identifier):
  22. """Returns the subelement in the given data element, raises DataNotFoundError if not found"""
  23. id_parts = identifier.split("/")
  24. id_parts[:] = (value for value in id_parts if value != "")
  25. cur_el = element
  26. for id in id_parts:
  27. if type(cur_el) == dict and id in cur_el.keys():
  28. cur_el = cur_el[id]
  29. else:
  30. raise DataNotFoundError(identifier + " in " + str(element))
  31. return cur_el
  32. def set(element, identifier, value):
  33. id_parts = identifier.split("/")
  34. id_parts[:] = (value for value in id_parts if value != "")
  35. cur_el = element
  36. for id in id_parts[:-1]:
  37. if id in cur_el.keys():
  38. cur_el = cur_el[id]
  39. else:
  40. cur_el[id] = {}
  41. cur_el = cur_el[id]
  42. cur_el[id_parts[-1]] = value
  43. return element
  44. def unset(element, identifier):
  45. id_parts = identifier.split("/")
  46. id_parts[:] = (value for value in id_parts if value != "")
  47. cur_el = element
  48. for id in id_parts[:-1]:
  49. if id in cur_el.keys():
  50. cur_el = cur_el[id]
  51. else:
  52. cur_el[id] = {}
  53. cur_el = cur_el[id]
  54. cur_el[id_parts[-1]] = None
  55. return element
  56. def find_no_exc(element, identifier):
  57. """Returns the subelement in the given data element, returns None if not found"""
  58. id_parts = identifier.split("/")
  59. id_parts[:] = (value for value in id_parts if value != "")
  60. cur_el = element
  61. for id in id_parts:
  62. if type(cur_el) == dict and id in cur_el.keys():
  63. cur_el = cur_el[id]
  64. else:
  65. return None
  66. return cur_el
  67. def find_spec(element, identifier):
  68. """find the data definition for the given identifier
  69. returns either a map with 'item_name' etc, or a list of those"""
  70. id_parts = identifier.split("/")
  71. id_parts[:] = (value for value in id_parts if value != "")
  72. cur_el = element
  73. for id in id_parts:
  74. if type(cur_el) == dict and id in cur_el.keys():
  75. cur_el = cur_el[id]
  76. elif type(cur_el) == dict and 'item_name' in cur_el.keys() and cur_el['item_name'] == id:
  77. pass
  78. elif type(cur_el) == list:
  79. found = False
  80. for cur_el_item in cur_el:
  81. if cur_el_item['item_name'] == id and 'item_default' in cur_el_item.keys():
  82. cur_el = cur_el_item
  83. found = True
  84. if not found:
  85. raise DataNotFoundError(id + " in " + str(cur_el))
  86. else:
  87. raise DataNotFoundError(id + " in " + str(cur_el))
  88. return cur_el
  89. def check_type(specification, value):
  90. """Returns true if the value is of the correct type given the
  91. specification"""
  92. if type(specification) == list:
  93. data_type = "list"
  94. else:
  95. data_type = specification['item_type']
  96. if data_type == "integer" and type(value) != int:
  97. raise DataTypeError(str(value) + " should be an integer")
  98. elif data_type == "real" and type(value) != double:
  99. raise DataTypeError(str(value) + " should be a real")
  100. elif data_type == "boolean" and type(value) != boolean:
  101. raise DataTypeError(str(value) + " should be a boolean")
  102. elif data_type == "string" and type(value) != str:
  103. raise DataTypeError(str(value) + " should be a string")
  104. elif data_type == "list":
  105. if type(value) != list:
  106. raise DataTypeError(str(value) + " should be a list, not a " + str(value.__class__.__name__))
  107. else:
  108. # todo: check subtypes etc
  109. for element in value:
  110. check_type(specification['list_item_spec'], element)
  111. elif data_type == "map" and type(value) != dict:
  112. # todo: check subtypes etc
  113. raise DataTypeError(str(value) + " should be a map")
  114. def spec_name_list(spec, prefix="", recurse=False):
  115. """Returns a full list of all possible item identifiers in the
  116. specification (part)"""
  117. result = []
  118. if prefix != "" and not prefix.endswith("/"):
  119. prefix += "/"
  120. if type(spec) == dict:
  121. for name in spec:
  122. result.append(prefix + name + "/")
  123. if recurse:
  124. result.extend(spec_name_list(spec[name],name, recurse))
  125. elif type(spec) == list:
  126. for list_el in spec:
  127. if 'item_name' in list_el:
  128. if list_el['item_type'] == dict:
  129. if recurse:
  130. result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse))
  131. else:
  132. name = list_el['item_name']
  133. if list_el['item_type'] in ["list", "map"]:
  134. name += "/"
  135. result.append(name)
  136. return result
  137. def parse_value_str(value_str):
  138. try:
  139. return ast.literal_eval(value_str)
  140. except ValueError as ve:
  141. # simply return the string itself
  142. return value_str
  143. except SyntaxError as ve:
  144. # simply return the string itself
  145. return value_str
  146. class ConfigData:
  147. def __init__(self, specification):
  148. self.specification = specification
  149. self.data = {}
  150. def get_item_list(self, identifier = None):
  151. if identifier:
  152. spec = find_spec(self.specification, identifier)
  153. return spec_name_list(spec, identifier + "/")
  154. return spec_name_list(self.specification)
  155. def get_value(self, identifier):
  156. """Returns a tuple where the first item is the value at the
  157. given identifier, and the second item is a bool which is
  158. true if the value is an unset default"""
  159. value = find_no_exc(self.data, identifier)
  160. if value:
  161. return value, False
  162. spec = find_spec(self.specification, identifier)
  163. if spec and 'item_default' in spec:
  164. return spec['item_default'], True
  165. return None, False
  166. class UIConfigData():
  167. def __init__(self, name, cc):
  168. self.module_name = name
  169. data_spec = self.get_data_specification(cc)
  170. self.config = ConfigData(data_spec)
  171. self.get_config_data(cc)
  172. self.config_changes = {}
  173. def get_config_data(self, cc):
  174. cc.group_sendmsg({ "command": ["get_config", self.module_name] }, "ConfigManager")
  175. answer, env = cc.group_recvmsg(False)
  176. if 'result' in answer.keys() and type(answer['result']) == list:
  177. # TODO: with the new cc implementation, replace "1" by 1
  178. if answer['result'][0] == "1":
  179. # todo: exception
  180. print("Error: " + str(answer['result'][1]))
  181. else:
  182. self.config.data = answer['result'][1]
  183. else:
  184. # XX todo: raise exc
  185. print("Error: unexpected answer from config manager:")
  186. print(answer)
  187. def send_changes(self, cc):
  188. """Sends the changes configuration values to the config manager.
  189. If the command succeeds, the changes are re-requested and
  190. the changed list is reset"""
  191. if self.module_name and self.module_name != "":
  192. cc.group_sendmsg({ "command": [ "set_config", self.module_name, self.config_changes ]}, "ConfigManager")
  193. else:
  194. cc.group_sendmsg({ "command": [ "set_config", self.config_changes ]}, "ConfigManager")
  195. answer, env = cc.group_recvmsg(False)
  196. if 'result' in answer and type(answer['result']) == list:
  197. if answer['result'][0] == 0:
  198. # ok
  199. self.get_config_data(cc)
  200. self.config_changes = {}
  201. else:
  202. print("Error committing changes: " + answer['result'][1])
  203. else:
  204. print("Error: unexpected answer: " + str(answer))
  205. def get_data_specification(self, cc):
  206. cc.group_sendmsg({ "command": ["get_data_spec", self.module_name] }, "ConfigManager")
  207. answer, env = cc.group_recvmsg(False)
  208. if 'result' in answer.keys() and type(answer['result']) == list:
  209. # TODO: with the new cc implementation, replace "1" by 1
  210. if answer['result'][0] == "1":
  211. # todo: exception
  212. print("Error: " + str(answer['result'][1]))
  213. return None
  214. else:
  215. return answer['result'][1]
  216. else:
  217. # XX todo: raise exc
  218. print("Error: unexpected answer from config manager:")
  219. print(answer)
  220. return None
  221. def set(self, identifier, value):
  222. # check against definition
  223. spec = find_spec(identifier)
  224. check_type(spec, value)
  225. set(self.config_changes, identifier, value)
  226. def get_value(self, identifier):
  227. """Returns a three-tuple, where the first item is the value
  228. (or None), the second is a boolean specifying whether
  229. the value is the default value, and the third is a boolean
  230. specifying whether the value is an uncommitted change"""
  231. value = find_no_exc(self.config_changes, identifier)
  232. if value:
  233. return value, False, True
  234. value, default = self.config.get_value(identifier)
  235. if value:
  236. return value, default, False
  237. return None, False, False
  238. def get_value_map_single(self, identifier, entry):
  239. """Returns a single entry for a value_map, where the value is
  240. not a part of a bigger map"""
  241. result_part = {}
  242. result_part['name'] = entry['item_name']
  243. result_part['type'] = entry['item_type']
  244. value, default, modified = self.get_value(identifier)
  245. # should we check type and only set int, double, bool and string here?
  246. result_part['value'] = value
  247. result_part['default'] = default
  248. result_part['modified'] = modified
  249. return result_part
  250. def get_value_map(self, identifier, entry):
  251. """Returns a single entry for a value_map, where the value is
  252. a part of a bigger map"""
  253. result_part = {}
  254. result_part['name'] = entry['item_name']
  255. result_part['type'] = entry['item_type']
  256. value, default, modified = self.get_value(identifier + "/" + entry['item_name'])
  257. # should we check type and only set int, double, bool and string here?
  258. result_part['value'] = value
  259. result_part['default'] = default
  260. result_part['modified'] = modified
  261. return result_part
  262. def get_value_maps(self, identifier = None):
  263. """Returns a list of maps, containing the following values:
  264. name: name of the entry (string)
  265. type: string containing the type of the value (or 'module')
  266. value: value of the entry if it is a string, int, double or bool
  267. modified: true if the value is a local change
  268. default: true if the value has been changed
  269. Throws DataNotFoundError if the identifier is bad
  270. """
  271. spec = find_spec(self.config.specification, identifier)
  272. result = []
  273. if type(spec) == dict:
  274. # either the top-level list of modules or a spec map
  275. if 'item_name' in spec:
  276. result_part = self.get_value_map_single(identifier, spec)
  277. if result_part['type'] == "list":
  278. values = self.get_value(identifier)[0]
  279. if values:
  280. for value in values:
  281. result_part2 = {}
  282. li_spec = spec['list_item_spec']
  283. result_part2['name'] = li_spec['item_name']
  284. result_part2['value'] = value
  285. result_part2['type'] = li_spec['item_type']
  286. result_part2['default'] = False
  287. result_part2['modified'] = False
  288. result.append(result_part2)
  289. else:
  290. result.append(result_part)
  291. else:
  292. for name in spec:
  293. result_part = {}
  294. result_part['name'] = name
  295. result_part['type'] = "module"
  296. result_part['value'] = None
  297. result_part['default'] = False
  298. result_part['modified'] = False
  299. result.append(result_part)
  300. elif type(spec) == list:
  301. for entry in spec:
  302. if type(entry) == dict and 'item_name' in entry:
  303. result.append(self.get_value_map(identifier, entry))
  304. return result
  305. def add(self, identifier, value_str):
  306. data_spec = find_spec(self.config.specification, identifier)
  307. value = parse_value_str(value_str)
  308. check_type(data_spec, [value])
  309. cur_list = find_no_exc(self.config_changes, identifier)
  310. if not cur_list:
  311. cur_list = find_no_exc(self.config.data, identifier)
  312. if not cur_list:
  313. cur_list = []
  314. if value not in cur_list:
  315. cur_list.append(value)
  316. set(self.config_changes, identifier, cur_list)
  317. def remove(self, identifier, value_str):
  318. data_spec = find_spec(self.config.specification, identifier)
  319. value = parse_value_str(value_str)
  320. check_type(data_spec, [value])
  321. cur_list = find_no_exc(self.config_changes, identifier)
  322. if not cur_list:
  323. cur_list = find_no_exc(self.config.data, identifier)
  324. if not cur_list:
  325. cur_list = []
  326. if value in cur_list:
  327. cur_list.remove(value)
  328. set(self.config_changes, identifier, cur_list)
  329. def set(self, identifier, value_str):
  330. data_spec = find_spec(self.config.specification, identifier)
  331. value = parse_value_str(value_str)
  332. check_type(data_spec, value)
  333. set(self.config_changes, identifier, value)
  334. def unset(self, identifier):
  335. # todo: check whether the value is optional?
  336. unset(self.config_changes, identifier)
  337. def revert(self):
  338. self.config_changes = {}
  339. def commit(self, cc):
  340. self.send_changes(cc)