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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  1. #-*- coding: utf-8 -*-
  2. import re
  3. import copy
  4. import inspect
  5. import warnings
  6. from .exceptions import *
  7. from lodel.plugin.hooks import LodelHook
  8. from lodel import logger
  9. ##@todo check datas when running query
  10. class LeQuery(object):
  11. ##@brief Hookname prefix
  12. _hook_prefix = None
  13. ##@brief arguments for the LeObject.check_data_value()
  14. _data_check_args = { 'complete': False, 'allow_internal': False }
  15. ##@brief Abstract constructor
  16. # @param target_class LeObject : class of object the query is about
  17. def __init__(self, target_class):
  18. from .leobject import LeObject
  19. if self._hook_prefix is None:
  20. raise NotImplementedError("Abstract class")
  21. if not inspect.isclass(target_class) or \
  22. not issubclass(target_class, LeObject):
  23. raise TypeError("target class has to be a child class of LeObject but %s given"% target_class)
  24. self._target_class = target_class
  25. self._ro_datasource = target_class._ro_datasource
  26. self._rw_datasource = target_class._rw_datasource
  27. ##@brief Execute a query and return the result
  28. #@param **datas
  29. #@return the query result
  30. #@see LeQuery._query()
  31. #@todo check that the check_datas_value is not duplicated/useless
  32. def execute(self, datas):
  33. if not datas is None:
  34. self._target_class.check_datas_value(
  35. datas,
  36. **self._data_check_args)
  37. self._target_class.prepare_datas(datas) #not yet implemented
  38. if self._hook_prefix is None:
  39. raise NotImplementedError("Abstract method")
  40. LodelHook.call_hook( self._hook_prefix+'_pre',
  41. self._target_class,
  42. datas)
  43. ret = self._query(datas = datas)
  44. ret = LodelHook.call_hook( self._hook_prefix+'_post',
  45. self._target_class,
  46. ret)
  47. return ret
  48. ##@brief Childs classes implements this method to execute the query
  49. #@param **datas
  50. #@return query result
  51. def _query(self, **datas):
  52. raise NotImplementedError("Asbtract method")
  53. ##@return a dict with query infos
  54. def dump_infos(self):
  55. return {'target_class': self._target_class}
  56. def __repr__(self):
  57. ret = "<{classname} target={target_class}>"
  58. return ret.format(
  59. classname=self.__class__.__name__,
  60. target_class = self._target_class)
  61. ##@brief Abstract class handling query with filters
  62. class LeFilteredQuery(LeQuery):
  63. ##@brief The available operators used in query definitions
  64. _query_operators = [
  65. ' = ',
  66. ' <= ',
  67. ' >= ',
  68. ' != ',
  69. ' < ',
  70. ' > ',
  71. ' in ',
  72. ' not in ',
  73. ' like ',
  74. ' not like ']
  75. ##@brief Regular expression to process filters
  76. _query_re = None
  77. ##@brief Abtract constructor for queries with filter
  78. #@param target_class LeObject : class of object the query is about
  79. #@param query_filters list : with a tuple (only one filter) or a list of
  80. # tuple or a dict: {OP,list(filters)} with OP = 'OR' or 'AND for tuple
  81. # (FIELD,OPERATOR,VALUE)
  82. def __init__(self, target_class, query_filters = None):
  83. super().__init__(target_class)
  84. ##@brief The query filter tuple(std_filter, relational_filters)
  85. self._query_filter = None
  86. ##@brief Stores potential subqueries (used when a query implies
  87. # more than one datasource.
  88. #
  89. # Subqueries are tuple(target_class_ref_field, LeGetQuery)
  90. self.subqueries = None
  91. query_filters = [] if query_filters is None else query_filters
  92. self.set_query_filter(query_filters)
  93. ##@brief Abstract FilteredQuery execution method
  94. #
  95. # This method takes care to execute subqueries before calling super execute
  96. def execute(self, datas = None):
  97. #copy originals filters
  98. orig_filters = copy.copy(self._query_filter)
  99. std_filters, rel_filters = self._query_filter
  100. for rfield, subq in self.subqueries:
  101. subq_res = subq.execute()
  102. std_filters.append(
  103. (rfield, ' in ', subq_res))
  104. self._query_filter = (std_filters, rel_filters)
  105. try:
  106. filters, rel_filters = self._query_filter
  107. res = super().execute(datas)
  108. except Exception as e:
  109. #restoring filters even if an exception is raised
  110. self.__query_filter = orig_filters
  111. raise e #reraise
  112. #restoring filters
  113. self._query_filter = orig_filters
  114. return res
  115. ##@brief Add filter(s) to the query
  116. #
  117. # This method is also able to slice query if different datasources are
  118. # implied in the request
  119. #
  120. #@param query_filter list|tuple|str : A single filter or a list of filters
  121. #@see LeFilteredQuery._prepare_filters()
  122. #@warning Does not support multiple UID
  123. def set_query_filter(self, query_filter):
  124. if isinstance(query_filter, str):
  125. query_filter = [query_filter]
  126. #Query filter prepration
  127. filters_orig , rel_filters = self._prepare_filters(query_filter)
  128. # Here we now that each relational filter concern only one datasource
  129. # thank's to _prepare_relational_fields
  130. #Multiple datasources detection
  131. self_ds_name = self._target_class._datasource_name
  132. result_rel_filters = list() # The filters that will stay in the query
  133. other_ds_filters = dict()
  134. for rfilter in rel_filters:
  135. (rfield, ref_dict), op, value = rfilter
  136. #rfield : the field in self._target_class
  137. tmp_rel_filter = dict() #designed to stores rel_field of same DS
  138. # First step : simplification
  139. # Trying to delete relational filters done on referenced class uid
  140. for tclass, tfield in copy.copy(ref_dict).items():
  141. #tclass : reference target class
  142. #tfield : referenced field from target class
  143. #
  144. # !!!WARNING!!!
  145. # The line below brake multi UID support
  146. #
  147. if tfield == tclass.uid_fieldname()[0]:
  148. #This relational filter can be simplified as
  149. # ref_field, op, value
  150. # Note : we will have to dedup filters_orig
  151. filters_orig.append((rfield, op, value))
  152. del(ref_dict[tclass])
  153. if len(ref_dict) == 0:
  154. continue
  155. #Determine what to do with other relational filters given
  156. # referenced class datasource
  157. #Remember : each class in a relational filter has the same
  158. # datasource
  159. tclass = list(ref_dict.keys())[0]
  160. cur_ds = tclass._datasource_name
  161. if cur_ds == self_ds_name:
  162. # Same datasource, the filter stay is self query
  163. result_rel_filters.append(((rfield, ref_dict), op, value))
  164. else:
  165. # Different datasource, we will have to create a subquery
  166. if cur_ds not in other_ds_filters:
  167. other_ds_filters[cur_ds] = list()
  168. other_ds_filters[cur_ds].append(
  169. ((rfield, ref_dict), op, value))
  170. #deduplication of std filters
  171. if not isinstance(filters_orig, set):
  172. filters_orig = set(filters_orig)
  173. filters_orig = list(filters_orig)
  174. # Sets _query_filter attribute of self query
  175. self._query_filter = (filters_orig, result_rel_filters)
  176. #Sub queries creation
  177. subq = list()
  178. for ds, rfilters in other_ds_filters.items():
  179. for rfilter in rfilters:
  180. (rfield, ref_dict), op, value = rfilter
  181. for tclass, tfield in ref_dict.items():
  182. query = LeGetQuery(
  183. target_class = tclass,
  184. query_filters = [(tfield, op, value)],
  185. field_list = [tfield])
  186. subq.append((rfield, query))
  187. self.subqueries = subq
  188. ##@return informations
  189. def dump_infos(self):
  190. ret = super().dump_infos()
  191. ret['query_filter'] = self._query_filter
  192. ret['subqueries'] = self.subqueries
  193. return ret
  194. def __repr__(self):
  195. res = "<{classname} target={target_class} query_filter={query_filter}"
  196. res = res.format(
  197. classname=self.__class__.__name__,
  198. query_filter = self._query_filter,
  199. target_class = self._target_class)
  200. if len(self.subqueries) > 0:
  201. for n,subq in enumerate(self.subqueries):
  202. res += "\n\tSubquerie %d : %s"
  203. res %= (n, subq)
  204. res += '>'
  205. return res
  206. ## @brief Prepare filters for datasource
  207. #
  208. #A filter can be a string or a tuple with len = 3.
  209. #
  210. #This method divide filters in two categories :
  211. #
  212. #@par Simple filters
  213. #
  214. #Those filters concerns fields that represent object values (a title,
  215. #the content, etc.) They are composed of three elements : FIELDNAME OP
  216. # VALUE . Where :
  217. #- FIELDNAME is the name of the field
  218. #- OP is one of the authorized comparison operands ( see
  219. #@ref LeFilteredQuery.query_operators )
  220. #- VALUE is... a value
  221. #
  222. #@par Relational filters
  223. #
  224. #Those filters concerns on reference fields ( see the corresponding
  225. #abstract datahandler @ref lodel.leapi.datahandlers.base_classes.Reference)
  226. #The filter as quite the same composition than simple filters :
  227. # FIELDNAME[.REF_FIELD] OP VALUE . Where :
  228. #- FIELDNAME is the name of the reference field
  229. #- REF_FIELD is an optionnal addon to the base field. It indicate on wich
  230. #field of the referenced object the comparison as to be done. If no
  231. #REF_FIELD is indicated the comparison will be done on identifier.
  232. #
  233. #@param cls
  234. #@param filters_l list : This list of str or tuple (or both)
  235. #@return a tuple(FILTERS, RELATIONNAL_FILTERS
  236. #@todo move this doc in another place (a dedicated page ?)
  237. #@warning Does not supports multiple UID for an EmClass
  238. def _prepare_filters(self, filters_l):
  239. filters = list()
  240. res_filters = list()
  241. rel_filters = list()
  242. err_l = dict()
  243. #Splitting in tuple if necessary
  244. for i,fil in enumerate(filters_l):
  245. if len(fil) == 3 and not isinstance(fil, str):
  246. filters.append(tuple(fil))
  247. else:
  248. try:
  249. filters.append(self.split_filter(fil))
  250. except ValueError as e:
  251. err_l["filter %d" % i] = e
  252. for field, operator, value in filters:
  253. err_key = "%s %s %s" % (field, operator, value) #to push in err_l
  254. # Spliting field name to be able to detect a relational field
  255. field_spl = field.split('.')
  256. if len(field_spl) == 2:
  257. field, ref_field = field_spl
  258. elif len(field_spl) == 1:
  259. ref_field = None
  260. else:
  261. err_l[field] = NameError( "'%s' is not a valid relational \
  262. field name" % fieldname)
  263. continue
  264. # Checking field against target_class
  265. ret = self._check_field(self._target_class, field)
  266. if isinstance(ret, Exception):
  267. err_l[field] = ret
  268. continue
  269. field_datahandler = self._target_class.field(field)
  270. if isinstance(field_datahandler, Exception):
  271. err_l[field] = error
  272. continue
  273. if ref_field is not None and not field_datahandler.is_reference():
  274. # inconsistency
  275. err_l[field] = NameError( "The field '%s' in %s is not \
  276. a relational field, but %s.%s was present in the filter"
  277. % ( field,
  278. self._target_class.__name__,
  279. field,
  280. ref_field))
  281. if field_datahandler.is_reference():
  282. #Relationnal field
  283. if ref_field is None:
  284. # ref_field default value
  285. #
  286. # !!! WARNING !!!
  287. # This piece of code does not supports multiple UID for an
  288. # emclass
  289. #
  290. ref_uid = [
  291. lc._uid[0] for lc in field_datahandler.linked_classes]
  292. if len(set(ref_uid)) == 1:
  293. ref_field = ref_uid[0]
  294. else:
  295. if len(ref_uid) > 1:
  296. msg = "The referenced classes are identified by \
  297. fields with different name. Unable to determine wich field to use for the \
  298. reference"
  299. else:
  300. msg = "Unknow error when trying to determine wich \
  301. field to use for the relational filter"
  302. err_l[err_key] = RuntimeError(msg)
  303. continue
  304. # Prepare relational field
  305. ret = self._prepare_relational_fields(field, ref_field)
  306. if isinstance(ret, Exception):
  307. err_l[err_key] = ret
  308. continue
  309. else:
  310. rel_filters.append((ret, operator, value))
  311. else:
  312. # Casting value given datahandler
  313. value, error = field_datahandler._check_data_value(value)
  314. res_filters.append((field,operator, value))
  315. if len(err_l) > 0:
  316. raise LeApiDataCheckErrors(
  317. "Error while preparing filters : ",
  318. err_l)
  319. return (res_filters, rel_filters)
  320. ## @brief Check and split a query filter
  321. # @note The query_filter format is "FIELD OPERATOR VALUE"
  322. # @param query_filter str : A query_filter string
  323. # @param cls
  324. # @return a tuple (FIELD, OPERATOR, VALUE)
  325. @classmethod
  326. def split_filter(cls, query_filter):
  327. if cls._query_re is None:
  328. cls.__compile_query_re()
  329. matches = cls._query_re.match(query_filter)
  330. if not matches:
  331. msg = "The query_filter '%s' seems to be invalid"
  332. raise ValueError(msg % query_filter)
  333. result = (
  334. matches.group('field'),
  335. re.sub(r'\s', ' ', matches.group('operator'), count=0),
  336. matches.group('value').strip())
  337. result = [r.strip() for r in result]
  338. for r in result:
  339. if len(r) == 0:
  340. msg = "The query_filter '%s' seems to be invalid"
  341. raise ValueError(msg % query_filter)
  342. return result
  343. ## @brief Compile the regex for query_filter processing
  344. # @note Set _LeObject._query_re
  345. @classmethod
  346. def __compile_query_re(cls):
  347. op_re_piece = '(?P<operator>(%s)'
  348. op_re_piece %= cls._query_operators[0].replace(' ', '\s')
  349. for operator in cls._query_operators[1:]:
  350. op_re_piece += '|(%s)'%operator.replace(' ', '\s')
  351. op_re_piece += ')'
  352. re_full = '^\s*(?P<field>([a-z_][a-z0-9\-_]*\.)?[a-z_][a-z0-9\-_]*)\s*'
  353. re_full += op_re_piece+'\s*(?P<value>.*)\s*$'
  354. cls._query_re = re.compile(re_full, flags=re.IGNORECASE)
  355. pass
  356. @classmethod
  357. def _check_field(cls, target_class, fieldname):
  358. try:
  359. target_class.field(fieldname)
  360. except NameError as e:
  361. msg = "No field named '%s' in %s'"
  362. msg %= (fieldname, target_class.__name__)
  363. return NameError(msg)
  364. ##@brief Prepare a relational filter
  365. #
  366. #Relational filters are composed of a tuple like the simple filters
  367. #but the first element of this tuple is a tuple to :
  368. #
  369. #<code>( (FIELDNAME, {REF_CLASS: REF_FIELD}), OP, VALUE)</code>
  370. # Where :
  371. #- FIELDNAME is the field name is the target class
  372. #- the second element is a dict with :
  373. # - REF_CLASS as key. It's a LeObject child class
  374. # - REF_FIELD as value. The name of the referenced field in the REF_CLASS
  375. #
  376. #Visibly the REF_FIELD value of the dict will vary only when
  377. #no REF_FIELD is explicitly given in the filter string notation
  378. #and REF_CLASSES has differents uid
  379. #
  380. #@par String notation examples
  381. #<pre>contributeur IN (1,2,3,5)</pre> will be transformed into :
  382. #<pre>(
  383. # (
  384. # contributeur,
  385. # {
  386. # auteur: 'lodel_id',
  387. # traducteur: 'lodel_id'
  388. # }
  389. # ),
  390. # ' IN ',
  391. # [ 1,2,3,5 ])</pre>
  392. #@todo move the documentation to another place
  393. #
  394. #@param fieldname str : The relational field name
  395. #@param ref_field str|None : The referenced field name (if None use
  396. #uniq identifiers as referenced field
  397. #@return a well formed relational filter tuple or an Exception instance
  398. def _prepare_relational_fields(self, fieldname, ref_field = None):
  399. datahandler = self._target_class.field(fieldname)
  400. # now we are going to fetch the referenced class to see if the
  401. # reference field is valid
  402. ref_classes = datahandler.linked_classes
  403. ref_dict = dict()
  404. if ref_field is None:
  405. for ref_class in ref_classes:
  406. ref_dict[ref_class] = ref_class.uid_fieldname
  407. else:
  408. r_ds = None
  409. for ref_class in ref_classes:
  410. if r_ds is None:
  411. r_ds = ref_class._datasource_name
  412. elif ref_class._datasource_name != r_ds:
  413. return RuntimeError("All referenced class doesn't have the\
  414. same datasource. Query not possible")
  415. if ref_field in ref_class.fieldnames(True):
  416. ref_dict[ref_class] = ref_field
  417. else:
  418. msg = "Warning the class %s is not considered in \
  419. the relational filter %s"
  420. msg %= (ref_class.__name__, ref_field)
  421. logger.debug(msg)
  422. if len(ref_dict) == 0:
  423. return NameError( "No field named '%s' in referenced objects %s"
  424. % (ref_field,
  425. ','.join([rc.__name__ for rc in ref_classes])))
  426. return (fieldname, ref_dict)
  427. ##@brief A query to insert a new object
  428. class LeInsertQuery(LeQuery):
  429. _hook_prefix = 'leapi_insert_'
  430. _data_check_args = { 'complete': True, 'allow_internal': False }
  431. def __init__(self, target_class):
  432. super().__init__(target_class)
  433. ## @brief Implements an insert query operation, with only one insertion
  434. # @param new_datas : datas to be inserted
  435. def _query(self, datas):
  436. datas = self._target_class.prepare_datas(datas, True, False)
  437. id_inserted = self._rw_datasource.insert(self._target_class,datas)
  438. return id_inserted
  439. """
  440. ## @brief Implements an insert query operation, with multiple insertions
  441. # @param datas : list of **datas to be inserted
  442. def _query(self, datas):
  443. nb_inserted = self._datasource.insert_multi(
  444. self._target_class,datas_list)
  445. if nb_inserted < 0:
  446. raise LeApiQueryError("Multiple insertions error")
  447. return nb_inserted
  448. """
  449. ## @brief Execute the insert query
  450. def execute(self, datas):
  451. return super().execute(datas = datas)
  452. ##@brief A query to update datas for a given object
  453. #
  454. #@todo Change behavior, Huge optimization problem when updating using filters
  455. #and not instance. We have to run a GET and then 1 update by fecthed object...
  456. class LeUpdateQuery(LeFilteredQuery):
  457. _hook_prefix = 'leapi_update_'
  458. _data_check_args = { 'complete': True, 'allow_internal': False }
  459. ##@brief Instanciate an update query
  460. #
  461. #If a class and not an instance is given, no query_filters are expected
  462. #and the update will be fast and simple. Else we have to run a get query
  463. #before updating (to fetch datas, update them and then, construct them
  464. #and check their consistency)
  465. #@param target LeObject clas or instance
  466. #@param query_filters list|None
  467. #@todo change strategy with instance update. We have to accept datas for
  468. #the execute method
  469. def __init__(self, target, query_filters = None):
  470. ##@brief This attr is set only if the target argument is an
  471. #instance of a LeObject subclass
  472. self.__leobject_instance_datas = None
  473. target_class = target
  474. if not inspect.isclass(target):
  475. if query_filters is not None:
  476. msg = "No query_filters accepted when an instance is given as \
  477. target to LeUpdateQuery constructor"
  478. raise AttributeError(msg)
  479. target_class = target.__class__
  480. if target_class.initialized:
  481. self.__leobject_instance_datas = target.datas(True)
  482. else:
  483. query_filters = [(target._uid[0], '=', target.uid())]
  484. super().__init__(target_class, query_filters)
  485. ##@brief Implements an update query
  486. #@param filters list : see @ref LeFilteredQuery
  487. #@param rel_filters list : see @ref LeFilteredQuery
  488. #@param datas dict : datas to update
  489. #@returns the number of updated items
  490. #@todo change stategy for instance update. Datas should be allowed
  491. #for execute method (and query)
  492. def _query(self, datas):
  493. uid_name = self._target_class._uid[0]
  494. if self.__leobject_instance_datas is not None:
  495. #Instance update
  496. #Building query_filter
  497. filters = [(
  498. uid_name,
  499. '=',
  500. str(self.__leobject_instance_datas[uid_name]))]
  501. res = self._rw_datasource.update(
  502. self._target_class, filters, [],
  503. self.__leobject_instance_datas)
  504. else:
  505. #Update by filters, we have to fetch datas before updating
  506. res = self._ro_datasource.select(
  507. self._target_class, self._target_class.fieldnames(True),
  508. self._query_filter[0],
  509. self._query_filter[1])
  510. #Checking and constructing datas
  511. upd_datas = dict()
  512. for res_data in res:
  513. res_data.update(datas)
  514. res_datas = self._target_class.prepare_datas(
  515. res_data, True, True)
  516. filters = [(uid_name, '=', res_data[uid_name])]
  517. res = self._rw_datasource.update(
  518. self._target_class, filters, [],
  519. res_datas)
  520. return res
  521. ## @brief Execute the update query
  522. def execute(self, datas = None):
  523. if self.__leobject_instance_datas is not None and datas is not None:
  524. raise LeApiQueryError("No datas expected when running an update \
  525. query on an instance")
  526. if self.__leobject_instance_datas is None and datas is None:
  527. raise LeApiQueryError("Datas are mandatory when running an update \
  528. query on a class with filters")
  529. return super().execute(datas = datas)
  530. ##@brief A query to delete an object
  531. class LeDeleteQuery(LeFilteredQuery):
  532. _hook_prefix = 'leapi_delete_'
  533. def __init__(self, target_class, query_filter):
  534. super().__init__(target_class, query_filter)
  535. ## @brief Execute the delete query
  536. def execute(self, datas = None):
  537. return super().execute()
  538. ##@brief Implements delete query operations
  539. #@param filters list : see @ref LeFilteredQuery
  540. #@param rel_filters list : see @ref LeFilteredQuery
  541. #@returns the number of deleted items
  542. def _query(self, datas = None):
  543. filters, rel_filters = self._query_filter
  544. nb_deleted = self._rw_datasource.delete(
  545. self._target_class, filters, rel_filters)
  546. return nb_deleted
  547. class LeGetQuery(LeFilteredQuery):
  548. _hook_prefix = 'leapi_get_'
  549. ##@brief Instanciate a new get query
  550. #@param target_class LeObject : class of object the query is about
  551. #@param query_filters dict : {OP, list of query filters }
  552. # or tuple (FIELD, OPERATOR, VALUE) )
  553. #@param field_list list|None : list of string representing fields see
  554. # @ref leobject_filters
  555. #@param order list : A list of field names or tuple (FIELDNAME,[ASC | DESC])
  556. #@param group list : A list of field names or tuple (FIELDNAME,[ASC | DESC])
  557. #@param limit int : The maximum number of returned results
  558. #@param offset int : offset
  559. def __init__(self, target_class, query_filters, **kwargs):
  560. super().__init__(target_class, query_filters)
  561. ##@brief The fields to get
  562. self._field_list = None
  563. ##@brief An equivalent to the SQL ORDER BY
  564. self._order = None
  565. ##@brief An equivalent to the SQL GROUP BY
  566. self._group = None
  567. ##@brief An equivalent to the SQL LIMIT x
  568. self._limit = None
  569. ##@brief An equivalent to the SQL LIMIT x, OFFSET
  570. self._offset = 0
  571. # Checking kwargs and assigning default values if there is some
  572. for argname in kwargs:
  573. if argname not in (
  574. 'field_list', 'order', 'group', 'limit', 'offset'):
  575. raise TypeError("Unexpected argument '%s'" % argname)
  576. if 'field_list' not in kwargs:
  577. self.set_field_list(target_class.fieldnames(include_ro = True))
  578. else:
  579. self.set_field_list(kwargs['field_list'])
  580. if 'order' in kwargs:
  581. #check kwargs['order']
  582. self._order = kwargs['order']
  583. if 'group' in kwargs:
  584. #check kwargs['group']
  585. self._group = kwargs['group']
  586. if 'limit' in kwargs and kwargs['limit'] is not None:
  587. try:
  588. self._limit = int(kwargs['limit'])
  589. if self._limit <= 0:
  590. raise ValueError()
  591. except ValueError:
  592. msg = "limit argument expected to be an interger > 0"
  593. raise ValueError(msg)
  594. if 'offset' in kwargs:
  595. try:
  596. self._offset = int(kwargs['offset'])
  597. if self._offset < 0:
  598. raise ValueError()
  599. except ValueError:
  600. msg = "offset argument expected to be an integer >= 0"
  601. raise ValueError(msg)
  602. ##@brief Set the field list
  603. # @param field_list list | None : If None use all fields
  604. # @return None
  605. # @throw LeApiQueryError if unknown field given
  606. def set_field_list(self, field_list):
  607. err_l = dict()
  608. if field_list is not None:
  609. for fieldname in field_list:
  610. ret = self._check_field(self._target_class, fieldname)
  611. if isinstance(ret, Exception):
  612. msg = "No field named '%s' in %s"
  613. msg %= (fieldname, self._target_class.__name__)
  614. expt = NameError(msg)
  615. err_l[fieldname] = expt
  616. if len(err_l) > 0:
  617. msg = "Error while setting field_list in a get query"
  618. raise LeApiQueryErrors(msg = msg, exceptions = err_l)
  619. self._field_list = list(set(field_list))
  620. ##@brief Execute the get query
  621. def execute(self, datas = None):
  622. return super().execute()
  623. ##@brief Implements select query operations
  624. # @returns a list containing the item(s)
  625. def _query(self, datas = None):
  626. # select datas corresponding to query_filter
  627. fl = list(self._field_list) if self._field_list is not None else None
  628. l_datas=self._ro_datasource.select(
  629. target = self._target_class,
  630. field_list = fl,
  631. filters = self._query_filter[0],
  632. relational_filters = self._query_filter[1],
  633. order = self._order,
  634. group = self._group,
  635. limit = self._limit,
  636. offset = self._offset)
  637. return l_datas
  638. ##@return a dict with query infos
  639. def dump_infos(self):
  640. ret = super().dump_infos()
  641. ret.update( { 'field_list' : self._field_list,
  642. 'order' : self._order,
  643. 'group' : self._group,
  644. 'limit' : self._limit,
  645. 'offset': self._offset,
  646. })
  647. return ret
  648. def __repr__(self):
  649. res = "<LeGetQuery target={target_class} filter={query_filter} \
  650. field_list={field_list} order={order} group={group} limit={limit} \
  651. offset={offset}"
  652. res = res.format(**self.dump_infos())
  653. if len(self.subqueries) > 0:
  654. for n,subq in enumerate(self.subqueries):
  655. res += "\n\tSubquerie %d : %s"
  656. res %= (n, subq)
  657. res += ">"
  658. return res