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

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