No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

leobject.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. #-*- coding: utf-8 -*-
  2. class LeApiErrors(Exception):
  3. ## @brief Instanciate a new exceptions handling multiple exceptions
  4. # @param msg str : Exception message
  5. # @param exceptions dict : A list of data check Exception with concerned field (or stuff) as key
  6. def __init__(self, msg = "Unknow error", exceptions = None):
  7. self._msg = msg
  8. self._exceptions = dict() if exceptions is None else exceptions
  9. def __repr__(self):
  10. return self.__str__()
  11. def __str__(self):
  12. msg = self._msg
  13. for_iter = self._exceptions.items() if isinstance(self._exceptions, dict) else enumerate(self.__exceptions)
  14. for obj, expt in for_iter:
  15. msg += "\n\t{expt_obj} : ({expt_name}) {expt_msg}; ".format(
  16. expt_obj = obj,
  17. expt_name=expt.__class__.__name__,
  18. expt_msg=str(expt)
  19. )
  20. return msg
  21. ## @brief When an error concern a query
  22. class LeApiQueryError(LeApiErrors):
  23. pass
  24. ## @brief When an error concerns a datas
  25. class LeApiDataCheckError(LeApiErrors):
  26. pass
  27. ## @brief Wrapper class for LeObject getter & setter
  28. #
  29. # This class intend to provide easy & friendly access to LeObject fields values
  30. # without name collision problems
  31. # @note Wrapped methods are : LeObject.data() & LeObject.set_data()
  32. class LeObjectValues(object):
  33. ## @brief Construct a new LeObjectValues
  34. # @param set_callback method : The LeObject.set_datas() method of corresponding LeObject class
  35. # @param get_callback method : The LeObject.get_datas() method of corresponding LeObject class
  36. def __init__(self, fieldnames_callback, set_callback, get_callback):
  37. self.__setter = set_callback
  38. self.__getter = get_callback
  39. ## @brief Provide read access to datas values
  40. # @note Read access should be provided for all fields
  41. # @param fname str : Field name
  42. def __getattribute__(self, fname):
  43. return self.__getter(fname)
  44. ## @brief Provide write access to datas values
  45. # @note Write acces shouldn't be provided for internal or immutable fields
  46. # @param fname str : Field name
  47. # @param fval * : the field value
  48. def __setattribute__(self, fname, fval):
  49. return self.__setter(fname, fval)
  50. class LeObject(object):
  51. ## @brief boolean that tells if an object is abtract or not
  52. __abtract = None
  53. ## @brief A dict that stores DataHandler instances indexed by field name
  54. __fields = None
  55. ## @brief A tuple of fieldname (or a uniq fieldname) representing uid
  56. __uid = None
  57. ## @brief Construct an object representing an Editorial component
  58. # @note Can be considered as EmClass instance
  59. def __init__(self, **kwargs):
  60. if self.__abstract:
  61. raise NotImplementedError("%s is abstract, you cannot instanciate it." % self.__class__.__name__ )
  62. ## @brief A dict that stores fieldvalues indexed by fieldname
  63. self.__datas = { fname:None for fname in self.__fields }
  64. ## @brief Store a list of initianilized fields when instanciation not complete else store True
  65. self.__initialized = list()
  66. ## @brief Datas accessor. Instance of @ref LeObjectValues
  67. self.d = LeObjectValues(self.fieldnames, self.set_data, self.data)
  68. # Checks that uid is given
  69. for uid_name in self.__uid:
  70. if uid_name not in kwargs:
  71. raise AttributeError("Cannot instanciate a LeObject without it's identifier")
  72. self.__datas[uid_name] = kwargs[uid_name]
  73. del(kwargs[uid_name])
  74. self.__initialized.append(uid_name)
  75. # Processing given fields
  76. allowed_fieldnames = self.fieldnames(include_ro = False)
  77. err_list = list()
  78. for fieldname, fieldval in kwargs.items():
  79. if fieldname not in allowed_fieldnames:
  80. if fieldname in self.__fields:
  81. err_list.append(
  82. AttributeError("Value given for internal field : '%s'" % fieldname)
  83. )
  84. else:
  85. err_list.append(
  86. AttributeError("Unknown fieldname : '%s'" % fieldname)
  87. )
  88. else:
  89. self.__datas[fieldame] = fieldval
  90. self.__initialized = list()
  91. self.set_initialized()
  92. #-----------------------------------#
  93. # Fields datas handling methods #
  94. #-----------------------------------#
  95. ## @brief @property True if LeObject is initialized else False
  96. @property
  97. def initialized(self):
  98. return not isinstance(self.__initialized, list)
  99. ## @brief Return a list of fieldnames
  100. # @param include_ro bool : if True include read only field names
  101. # @return a list of str
  102. @classmethod
  103. def fieldnames(cls, include_ro = False):
  104. if not include_ro:
  105. return [ fname for fname in self.__fields if not self.__fields[fname].is_internal() ]
  106. else:
  107. return list(self.__fields.keys())
  108. @classmethod
  109. def name2objname(cls, name):
  110. return name.title()
  111. ## @brief Return the datahandler asssociated with a LeObject field
  112. # @param fieldname str : The fieldname
  113. # @return A data handler instance
  114. @classmethod
  115. def data_handler(cls, fieldname):
  116. if not fieldname in cls.__fields:
  117. raise NameError("No field named '%s' in %s" % (fieldname, cls.__name__))
  118. return cls.__fields[fieldname]
  119. ## @brief Read only access to all datas
  120. # @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
  121. # @param name str : field name
  122. # @return the Value
  123. # @throw RuntimeError if the field is not initialized yet
  124. # @throw NameError if name is not an existing field name
  125. def data(self, field_name):
  126. if field_name not in self.__fields.keys():
  127. raise NameError("No such field in %s : %s" % (self.__class__.__name__, name))
  128. if not self.initialized and name not in self.__initialized:
  129. raise RuntimeError("The field %s is not initialized yet (and have no value)" % name)
  130. return self.__datas[name]
  131. ## @brief Datas setter
  132. # @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
  133. # @param fname str : field name
  134. # @param fval * : field value
  135. # @return the value that is really set
  136. # @throw NameError if fname is not valid
  137. # @throw AttributeError if the field is not writtable
  138. def set_data(self, fname, fval):
  139. if field_name not in self.fieldnames(include_ro = False):
  140. if field_name not in self.__fields.keys():
  141. raise NameError("No such field in %s : %s" % (self.__class__.__name__, name))
  142. else:
  143. raise AttributeError("The field %s is read only" % fname)
  144. self.__datas[fname] = fval
  145. if not self.initialized and fname not in self.__initialized:
  146. # Add field to initialized fields list
  147. self.__initialized.append(fname)
  148. self.__set_initialized()
  149. if self.initialized:
  150. # Running full value check
  151. ret = self.__check_modified_values()
  152. if ret is None:
  153. return self.__datas[fname]
  154. else:
  155. raise LeApiErrors("Data check error", ret)
  156. else:
  157. # Doing value check on modified field
  158. # We skip full validation here because the LeObject is not fully initialized yet
  159. val, err = self.__fields[fname].check_data_value(fval)
  160. if isinstance(err, Exception):
  161. #Revert change to be in valid state
  162. del(self.__datas[fname])
  163. del(self.__initialized[-1])
  164. raise LeApiErrors("Data check error", {fname:err})
  165. else:
  166. self.__datas[fname] = val
  167. ## @brief Update the __initialized attribute according to LeObject internal state
  168. #
  169. # Check the list of initialized fields and set __initialized to True if all fields initialized
  170. def __set_initialized(self):
  171. if isinstance(self.__initialized, list):
  172. expected_fields = self.fieldnames(include_ro = False) + self.__uid
  173. if set(expected_fields) == set(self.__initialized):
  174. self.__initialized = True
  175. ## @brief Designed to be called when datas are modified
  176. #
  177. # Make different checks on the LeObject given it's state (fully initialized or not)
  178. # @return None if checks succeded else return an exception list
  179. def __check_modified_values(self):
  180. err_list = dict()
  181. if self.__initialized is True:
  182. # Data value check
  183. for fname in self.fieldnames(include_ro = False):
  184. val, err = self.__fields[fname].check_data_value(self.__datas[fname])
  185. if err is not None:
  186. err_list[fname] = err
  187. else:
  188. self.__datas[fname] = val
  189. # Data construction
  190. if len(err_list) == 0:
  191. for fname in self.fieldnames(include_ro = True):
  192. try:
  193. field = self.__fields[fname]
  194. self.__datas[fname] = fields.construct_data( self,
  195. fname,
  196. self.__datas,
  197. self.__datas[fname]
  198. )
  199. except Exception as e:
  200. err_list[fname] = e
  201. # Datas consistency check
  202. if len(err_list) == 0:
  203. for fname in self.fieldnames(include_ro = True):
  204. field = self.__fields[fname]
  205. ret = field.check_data_consistency(self, fname, self.__datas)
  206. if isinstance(ret, Exception):
  207. err_list[fname] = ret
  208. else:
  209. # Data value check for initialized datas
  210. for fname in self.__initialized:
  211. val, err = self.__fields[fname].check_data_value(self.__datas[fname])
  212. if err is not None:
  213. err_list[fname] = err
  214. else:
  215. self.__datas[fname] = val
  216. return err_list if len(err_list) > 0 else None
  217. #--------------------#
  218. # Other methods #
  219. #--------------------#
  220. ## @brief Temporary method to set private fields attribute at dynamic code generation
  221. #
  222. # This method is used in the generated dynamic code to set the __fields attribute
  223. # at the end of the dyncode parse
  224. # @warning This method is deleted once the dynamic code is parsed
  225. # @param field_list list : list of EmField instance
  226. @classmethod
  227. def _set__fields(cls, field_list):
  228. cls.__fields = field_list