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.

query.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. #-*- coding: utf-8 -*-
  2. import re
  3. from .leobject import LeObject, LeApiErrors, LeApiDataCheckError
  4. from lodel.plugin.hooks import LodelHook
  5. class LeQueryError(Exception):
  6. ##@brief Instanciate a new exceptions handling multiple exceptions
  7. # @param msg str : Exception message
  8. # @param exceptions dict : A list of data check Exception with concerned field (or stuff) as key
  9. def __init__(self, msg = "Unknow error", exceptions = None):
  10. self._msg = msg
  11. self._exceptions = dict() if exceptions is None else exceptions
  12. def __repr__(self):
  13. return self.__str__()
  14. def __str__(self):
  15. msg = self._msg
  16. for_iter = self._exceptions.items() if isinstance(self._exceptions, dict) else enumerate(self.__exceptions)
  17. for obj, expt in for_iter:
  18. msg += "\n\t{expt_obj} : ({expt_name}) {expt_msg}; ".format(
  19. expt_obj = obj,
  20. expt_name=expt.__class__.__name__,
  21. expt_msg=str(expt)
  22. )
  23. return msg
  24. class LeQuery(object):
  25. ##@brief Hookname prefix
  26. _hook_prefix = None
  27. ##@brief arguments for the LeObject.check_data_value()
  28. _data_check_args = { 'complete': False, 'allow_internal': False }
  29. ##@brief Abstract constructor
  30. # @param target_class LeObject : class of object the query is about
  31. def __init__(self, target_class):
  32. if hook_prefix is None:
  33. raise NotImplementedError("Abstract class")
  34. if not issubclass(target_class, LeObject):
  35. raise TypeError("target class has to be a child class of LeObject")
  36. self.__target_class = target_class
  37. ##@brief Execute a query and return the result
  38. # @param **datas
  39. # @return the query result
  40. # @see LeQuery.__query()
  41. #
  42. # @note maybe the datasource in not an argument but should be determined
  43. #elsewhere
  44. def execute(self, datasource, **datas = None):
  45. if len(datas) > 0:
  46. self.__target_class.check_datas_value(datas, **self._data_check_args)
  47. self.__target_class.prepare_datas() #not yet implemented
  48. if self._hook_prefix is None:
  49. raise NotImplementedError("Abstract method")
  50. LodelHook.call_hook( self._hook_prefix+'_pre',
  51. self.__target_class,
  52. datas)
  53. ret = self.__query(datasource, **datas)
  54. ret = LodelHook.call_hook( self._hook_prefix+'_post',
  55. self.__target_class,
  56. ret)
  57. return ret
  58. ##@brief Childs classes implements this method to execute the query
  59. # @param **datas
  60. # @return query result
  61. def __query(self, **datas):
  62. raise NotImplementedError("Asbtract method")
  63. class LeFilteredQuery(LeQuery):
  64. ##@brief The available operators used in query definitions
  65. query_operators = [
  66. '=',
  67. '<=',
  68. '>=',
  69. '!=',
  70. '<',
  71. '>',
  72. 'in',
  73. 'not in',
  74. 'like',
  75. 'not like']
  76. ##@brief Abtract constructor for queries with filter
  77. # @param target_class LeObject : class of object the query is about
  78. # @param query_filters list : with a tuple (only one filter) or a list of tuple
  79. # or a dict: {OP,list(filters)} with OP = 'OR' or 'AND
  80. # For tuple (FIELD,OPERATOR,VALUE)
  81. def __init__(self, target_class, query_filter):
  82. super().__init__(target_class)
  83. ##@brief The query filter
  84. self.__query_filter = None
  85. self.set_query_filter(query_filter)
  86. ##@brief Set the query filter for a query
  87. def set_query_filter(self, query_filter):
  88. #
  89. # Query filter check & prepare
  90. # query_filters can be a tuple (only one filter), a list of tuple
  91. # or a dict: {OP,list(filters)} with OP = 'OR' or 'AND
  92. # For tuple (FIELD,OPERATOR,VALUE)
  93. # FIELD has to be in the field_names list of target class
  94. # OPERATOR in query_operator attribute
  95. # VALUE has to be a correct value for FIELD
  96. fieldnames = self.__target_class.fieldnames()
  97. # Recursive method which checks filters
  98. def check_tuple(tupl, fieldnames, target_class):
  99. if isinstance(tupl, tuple):
  100. if tupl[0] not in fieldnames:
  101. return False
  102. if tupl[1] not in self.query_operators:
  103. return False
  104. if not isinstance(tupl[2], target_class.datahandler(tupl[0])):
  105. return False
  106. return True
  107. elif isinstance(tupl,dict):
  108. return check_tuple(tupl[1])
  109. elif isinstance(tupl,list):
  110. for tup in tupl:
  111. return check_tuple(tup)
  112. else:
  113. raise TypeError("Wrong filters for query")
  114. check_ok=check_tuple(query_filter, fieldnames, self.__target_class)
  115. if check_ok:
  116. self.__query_filter = query_filter
  117. def execute(self, datasource, **datas = None):
  118. super().execute(datasource, **datas)
  119. ##@brief A query to insert a new object
  120. class LeInsertQuery(LeQuery):
  121. _hook_prefix = 'leapi_insert_'
  122. _data_check_args = { 'complete': True, 'allow_internal': False }
  123. def __init__(self, target_class):
  124. super().__init__(target_class)
  125. ## @brief Implements an insert query operation, with only one insertion
  126. # @param **datas : datas to be inserted
  127. def __query(self, datasource, **datas):
  128. nb_inserted = datasource.insert(self.__target_class,**datas)
  129. if nb_inserted < 0:
  130. raise LeQueryError("Insertion error")
  131. return nb_inserted
  132. ## @brief Implements an insert query operation, with multiple insertions
  133. # @param datas : list of **datas to be inserted
  134. def __query(self, datasource, datas):
  135. nb_inserted = datasource.insert_multi(self.__target_class,datas_list)
  136. if nb_inserted < 0:
  137. raise LeQueryError("Multiple insertions error")
  138. return nb_inserted
  139. ## @brief Execute the insert query
  140. def execute(self, datasource, **datas):
  141. super().execute(datasource, **datas)
  142. ##@brief A query to update datas for a given object
  143. class LeUpdateQuery(LeFilteredQuery):
  144. _hook_prefix = 'leapi_update_'
  145. _data_check_args = { 'complete': True, 'allow_internal': False }
  146. def __init__(self, target_class, query_filter):
  147. super().__init__(target_class, query_filter)
  148. ##@brief Implements an update query
  149. # @param **datas : datas to update
  150. # @returns the number of updated items
  151. # @exception when the number of updated items is not as expected
  152. def __query(self, datasource, **datas):
  153. # select _uid corresponding to query_filter
  154. l_uids=datasource.select(self.__target_class,list(self.__target_class.getuid()),query_filter,None, None, None, None, 0, False)
  155. # list of dict l_uids : _uid(s) of the objects to be updated, corresponding datas
  156. nb_updated = datasource.update(self.__target_class,l_uids, **datas)
  157. if (nb_updated != len(l_uids):
  158. raise LeQueryError("Number of updated items: %d is not as expected: %d " % (nb_updated, len(l_uids)))
  159. return nb_updated
  160. ## @brief Execute the update query
  161. def execute(self, datasource, **datas):
  162. super().execute(datasource, **datas)
  163. ##@brief A query to delete an object
  164. class LeDeleteQuery(LeFilteredQuery):
  165. _hook_prefix = 'leapi_delete_'
  166. def __init__(self, target_class, query_filter):
  167. super().__init__(target_class, query_filter)
  168. ## @brief Execute the delete query
  169. def execute(self, datasource):
  170. super().execute()
  171. ##@brief Implements delete query operations
  172. # @returns the number of deleted items
  173. # @exception when the number of deleted items is not as expected
  174. def __query(self, datasource):
  175. # select _uid corresponding to query_filter
  176. l_uids=datasource.select(self.__target_class,list(self.__target_class.getuid()),query_filter,None, None, None, None, 0, False)
  177. # list of dict l_uids : _uid(s) of the objects to be deleted
  178. nb_deleted = datasource.update(self.__target_class,l_uids, **datas)
  179. if (nb_deleted != len(l_uids):
  180. raise LeQueryError("Number of deleted items %d is not as expected %d " % (nb_deleted, len(l_uids)))
  181. return nb_deleted
  182. class LeGetQuery(LeFilteredQuery):
  183. _hook_prefix = 'leapi_get_'
  184. ##@brief Instanciate a new get query
  185. # @param target_class LeObject : class of object the query is about
  186. # @param query_filters dict : {OP, list of query filters }
  187. # or tuple (FIELD, OPERATOR, VALUE) )
  188. # @param field_list list|None : list of string representing fields see @ref leobject_filters
  189. # @param order list : A list of field names or tuple (FIELDNAME, [ASC | DESC])
  190. # @param group list : A list of field names or tuple (FIELDNAME, [ASC | DESC])
  191. # @param limit int : The maximum number of returned results
  192. # @param offset int : offset
  193. def __init__(self, target_class, query_filter, **kwargs):
  194. super().__init__(target_class, query_filter)
  195. ##@brief The fields to get
  196. self.__field_list = None
  197. ##@brief An equivalent to the SQL ORDER BY
  198. self.__order = None
  199. ##@brief An equivalent to the SQL GROUP BY
  200. self.__group = None
  201. ##@brief An equivalent to the SQL LIMIT x
  202. self.__limit = None
  203. ##@brief An equivalent to the SQL LIMIT x, OFFSET
  204. self.__offset = 0
  205. # Checking kwargs and assigning default values if there is some
  206. for argname in kwargs:
  207. if argname not in ('order', 'group', 'limit', 'offset'):
  208. raise TypeError("Unexpected argument '%s'" % argname)
  209. if 'field_list' not in kwargs:
  210. #field_list = target_class.get_field_list
  211. field_list = target_class.fieldnames()
  212. else:
  213. #target_class.check_fields(kwargs['field_list'])
  214. field_list = kwargs['field_list']
  215. if 'order' in kwargs:
  216. #check kwargs['order']
  217. self.__order = kwargs['order']
  218. if 'group' in kwargs:
  219. #check kwargs['group']
  220. self.__group = kwargs['group']
  221. if 'limit' in kwargs:
  222. try:
  223. self.__limit = int(kwargs[limit])
  224. if self.__limit <= 0:
  225. raise ValueError()
  226. except ValueError:
  227. raise ValueError("limit argument expected to be an interger > 0")
  228. if 'offset' in kwargs:
  229. try:
  230. self.__offset = int(kwargs['offset'])
  231. if self.__offset < 0:
  232. raise ValueError()
  233. except ValueError:
  234. raise ValueError("offset argument expected to be an integer >= 0")
  235. ##@brief Execute the get query
  236. def execute(self, datasource):
  237. super().execute(datasource)
  238. ##@brief Implements select query operations
  239. # @returns a list containing the item(s)
  240. def __query(self, datasource):
  241. # select datas corresponding to query_filter
  242. l_datas=datasource.select(self.__target_class,list(self.field_list),self.query_filter,None, self.__order, self.__group, self.__limit, self.offset, False)
  243. return l_datas