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 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. #-*- coding: utf-8 -*-
  2. ## @package leobject API to access lodel datas
  3. #
  4. # This package contains abstract classes leobject.leclass.LeClass , leobject.letype.LeType, leobject.leobject._LeObject.
  5. # Those abstract classes are designed to be mother classes of dynamically generated classes ( see leobject.lefactory.LeFactory )
  6. ## @package leobject.leobject
  7. # @brief Abstract class designed to be implemented by LeObject
  8. #
  9. # @note LeObject will be generated by leobject.lefactory.LeFactory
  10. import re
  11. from EditorialModel.types import EmType
  12. ## @brief Main class to handle objects defined by the types of an Editorial Model
  13. class _LeObject(object):
  14. ## @brief The editorial model
  15. _model = None
  16. ## @brief The datasource
  17. _datasource = None
  18. ## @brief maps em uid with LeType or LeClass keys are uid values are LeObject childs classes
  19. _me_uid = dict()
  20. _query_re = None
  21. _query_operators = ['=', '<=', '>=', '!=', '<', '>', ' in ', ' not in ']
  22. ## @brief Instantiate with a Model and a DataSource
  23. # @param **kwargs dict : datas usefull to instanciate a _LeObject
  24. def __init__(self, **kwargs):
  25. raise NotImplementedError("Abstract constructor")
  26. ## @brief Given a ME uid return the corresponding LeClass or LeType class
  27. # @return a LeType or LeClass child class
  28. # @throw KeyError if no corresponding child classes
  29. def uid2leobj(cls, uid):
  30. uid = int(uid)
  31. if uid not in cls._me_uid:
  32. raise KeyError("No LeType or LeClass child classes with uid '%d'"%uid)
  33. return cls._me_uid[uid]
  34. ## @brief update an existing LeObject
  35. # @param lodel_id int | (int): lodel_id of the object(s) where to apply changes
  36. # @param data dict: dictionnary of field:value to save
  37. # @param update_filters string | (string): list of string of update filters
  38. # @return okay bool: True on success, it will raise on failure
  39. def update(self, lodel_id, data, update_filters=None):
  40. if not lodel_id:
  41. lodel_id = ()
  42. elif isinstance(lodel_id, int):
  43. lodel_id = (lodel_id)
  44. try:
  45. checked_data = self._check_data(data)
  46. datasource_filters = self._prepare_filters(update_filters)
  47. okay = self.datasource.update(lodel_id, checked_data, datasource_filters)
  48. except:
  49. raise
  50. return okay
  51. ## @brief delete an existing LeObject
  52. # @param lodel_id int | (int): lodel_id of the object(s) to delete
  53. # @param delete_filters string | (string): list of string of delete filters
  54. # @return okay bool: True on success, it will raise on failure
  55. def delete(self, lodel_id, delete_filters=None):
  56. if not lodel_id:
  57. lodel_id = ()
  58. elif isinstance(lodel_id, int):
  59. lodel_id = (lodel_id)
  60. try:
  61. datasource_filters = self._prepare_filters(delete_filters)
  62. okay = self.datasource.delete(lodel_id, datasource_filters)
  63. except:
  64. raise
  65. return okay
  66. ## @brief make a search to retrieve a collection of LeObject
  67. # @param query_filters list : list of string of query filters (or tuple (FIELD, OPERATOR, VALUE) )
  68. # @param field_list list|None : list of string representing fields
  69. # @param typename str : The name of the LeType we want
  70. # @param classname str : The name of the LeClass we want
  71. # @return responses ({string:*}): a list of dict with field:value
  72. def get(self, query_filters, field_list = None, typename = None, classname = None):
  73. letype,leclass = self._prepare_target(typename, classname)
  74. #Fetching LeType
  75. if typename is None:
  76. if 'type_id' not in field_list:
  77. field_list.append('type_id')
  78. #Checking field_list
  79. if field_list is None:
  80. if not (letype is None):
  81. flist = letype._fields
  82. elif not (leclass is None):
  83. flist = leclass._fieldtypes.keys()
  84. else:
  85. flist = EditorialModel.classtype.common_fields.keys()
  86. else:
  87. LeFactory._check_fields(letype, leclass, field_list)
  88. #preparing filters
  89. filters, relationnal_filters = self._prepare_filters(query_filters, letype, leclass)
  90. #Fetching datas from datasource
  91. datas = self._datasource.get(emclass, emtype, field_list, filters, relational_filters)
  92. #Instanciating corresponding LeType child classes with datas
  93. result = list()
  94. for leobj_datas in datas:
  95. letype = self.uid2leobj(datas['type_id']) if letype is None else letype
  96. result.append(letype(datas))
  97. return result
  98. ## @brief Preparing letype and leclass arguments
  99. #
  100. # This function will do multiple things :
  101. # - Convert string to LeType or LeClass child instances
  102. # - If both letype and leclass given, check that letype inherit from leclass
  103. #  - If only a letype is given, fetch the parent leclass
  104. # @note If we give only a leclass as argument returned letype will be None
  105. # @note Its possible to give letype=None and leclass=None. In this case the method will return tuple(None,None)
  106. # @param letype LeType|str|None : LeType child instant or its name
  107. # @param leclass LeClass|str|None : LeClass child instant or its name
  108. # @return a tuple with 2 python classes (LeTypeChild, LeClassChild)
  109. @staticmethod
  110. def _prepare_targets(letype = None , leclass = None):
  111. if not(leclass is None):
  112. if isinstance(leclass, str):
  113. leclass = leobject.lefactory.LeFactory.leobj_from_name(leclass)
  114. if not isinstance(leclass, LeClass) or leclass.__class__ == leobject.leclass.LeClass:
  115. raise ValueError("None | str | LeType child class excpected, but got : %s"%type(letype))
  116. if not(letype is None):
  117. if isinstance(letype, str):
  118. letype = leobject.lefactory.LeFactory.leobj_from_name(letype)
  119. if not isinstance(letype, LeType) or letype.__class__ == leobject.letype.LeType:
  120. raise ValueError("None | str | LeType child class excpected, but got : %s"%type(letype))
  121. if leclass is None:
  122. leclass = letype._leclass
  123. elif leclass != letype._leclass:
  124. raise ValueError("LeType child class %s does'nt inherite from LeClass %s"%(letype.__name__, leclass.__name))
  125. return (letype, leclass)
  126. ## @brief Check if a fieldname is valid
  127. # @param letype LeType|None : The concerned type (or None)
  128. # @param leclass LeClass|None : The concerned class (or None)
  129. # @param fields list : List of string representing fields
  130. # @throw LeObjectQueryError if their is some problems
  131. # @throw AttributeError if letype is not from the leclass class
  132. @staticmethod
  133. def _check_fields(letype, leclass, fields):
  134. #Checking that fields in the query_filters are correct
  135. if letype is None and leclass is None:
  136. #Only fields from the object table are allowed
  137. for field in fields:
  138. if field not in EditorialModel.classtype.common_fields.keys():
  139. raise LeObjectQueryError("Not typename and no classname given, but the field %s is not in the common_fields list"%field)
  140. else:
  141. if letype is None:
  142. field_l = leclass._fieldtypes.keys()
  143. else:
  144. if not (leclass is None):
  145. if letype._leclass != leclass:
  146. raise AttributeError("The EmType %s is not a specialisation of the EmClass %s"%(typename, classname))
  147. field_l = letype._fields
  148. #Checks that fields are in this type
  149. for field in fields:
  150. if field not in fields_l:
  151. raise LeObjectQueryError("No field named '%s' in '%s'"%(field, typename))
  152. pass
  153. ## @brief Prepare filters for datasource
  154. #
  155. # This method divide filters in two categories :
  156. # - filters : standart FIELDNAME OP VALUE filter
  157. # - relationnal_filters : filter on object relation RELATION_NATURE OP VALUE
  158. #
  159. # Both categories of filters are represented in the same way, a tuple with 3 elements (NAME|NAT , OP, VALUE )
  160. #
  161. # @warning This method assume that letype and leclass are returned from _LeObject._prepare_targets() method
  162. # @param filters_l list : This list can contain str "FIELDNAME OP VALUE" and tuples (FIELDNAME, OP, VALUE)
  163. # @param letype LeType|None : needed to check filters
  164. # @param leclass LeClass|None : needed to check filters
  165. # @return a tuple(FILTERS, RELATIONNAL_FILTERS°
  166. @staticmethod
  167. def _prepare_filters(filters_l, letype = None, leclass = None):
  168. filters = list()
  169. for fil in filters_l:
  170. if len(fil) == 3 and not isinstance(fil, str):
  171. filters.append(tuple(fil))
  172. else:
  173. filters.append(_LeObject._split_filter(fil))
  174. #Checking relational filters (for the moment fields like superior.NATURE)
  175. relational_filters = [ (LeFactory._nature_from_relational_field(field), operator, value) for field, operator, value in filters if LeFactory._field_is_relational(field)]
  176. filters = [f for f in filters if not self._field_is_relational(f[0])]
  177. #Checking the rest of the fields
  178. LeFactory._check_fields(letype, leclass, [ f[0] for f in filters ])
  179. return (filters, relationnal_filters)
  180. ## @brief Check if a field is relational or not
  181. # @param field str : the field to test
  182. # @return True if the field is relational else False
  183. @staticmethod
  184. def _field_is_relational(field):
  185. return field.startwith('superior.')
  186. ## @brief Check that a relational field is valid
  187. # @param field str : a relational field
  188. # @return a nature
  189. @staticmethod
  190. def _nature_from_relational_field(field):
  191. spl = field.split('.')
  192. if len(spl) != 2:
  193. raise LeObjectQueryError("The relationalfield '%s' is not valid"%field)
  194. nature = spl[-1]
  195. if nature not in EditorialModel.classtypes.EmNature.getall():
  196. raise LeObjectQueryError("'%s' is not a valid nature in the field %s"%(nature, field))
  197. return nature
  198. ## @brief Check and split a query filter
  199. # @note The query_filter format is "FIELD OPERATOR VALUE"
  200. # @param query_filter str : A query_filter string
  201. # @param cls
  202. # @return a tuple (FIELD, OPERATOR, VALUE)
  203. @classmethod
  204. def _split_filter(cls, query_filter):
  205. if cls._query_re is None:
  206. cls._compile_query_re()
  207. matches = cls._query_re.match(query_filter)
  208. if not matches:
  209. raise ValueError("The query_filter '%s' seems to be invalid"%query_filter)
  210. result = (matches.group('field'), re.sub(r'\s', ' ', matches.group('operator'), count=0), matches.group('value').strip())
  211. for r in result:
  212. if len(r) == 0:
  213. raise ValueError("The query_filter '%s' seems to be invalid"%query_filter)
  214. return result
  215. ## @brief Compile the regex for query_filter processing
  216. # @note Set _LeObject._query_re
  217. @classmethod
  218. def _compile_query_re(cls):
  219. op_re_piece = '(?P<operator>(%s)'%cls._query_operators[0].replace(' ', '\s')
  220. for operator in cls._query_operators[1:]:
  221. op_re_piece += '|(%s)'%operator.replace(' ', '\s')
  222. op_re_piece += ')'
  223. cls._query_re = re.compile('^\s*(?P<field>(superior\.)?[a-z_][a-z0-9\-_]*)\s*'+op_re_piece+'\s*(?P<value>[^<>=!].*)\s*$', flags=re.IGNORECASE)
  224. pass
  225. class LeObjectError(Exception):
  226. pass
  227. class LeObjectQueryError(LeObjectError):
  228. pass