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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. #-*- coding: utf-8 -*-
  2. import importlib
  3. import warnings
  4. import copy
  5. from lodel.context import LodelContext
  6. LodelContext.expose_modules(globals(), {
  7. 'lodel.logger': 'logger',
  8. 'lodel.settings': 'Settings',
  9. 'lodel.settings.utils': 'SettingsError',
  10. 'lodel.leapi.query': ['LeInsertQuery', 'LeUpdateQuery', 'LeDeleteQuery',
  11. 'LeGetQuery'],
  12. 'lodel.leapi.exceptions': ['LeApiError', 'LeApiErrors',
  13. 'LeApiDataCheckError', 'LeApiDataCheckErrors', 'LeApiQueryError',
  14. 'LeApiQueryErrors'],
  15. 'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
  16. 'LodelScriptError', 'DatasourcePluginError'],
  17. 'lodel.exceptions': ['LodelFatalError'],
  18. 'lodel.plugin.hooks': ['LodelHook'],
  19. 'lodel.plugin': ['Plugin', 'DatasourcePlugin'],
  20. 'lodel.leapi.datahandlers.base_classes': ['DatasConstructor', 'Reference']})
  21. ##@brief Stores the name of the field present in each LeObject that indicates
  22. #the name of LeObject subclass represented by this object
  23. CLASS_ID_FIELDNAME = "classname"
  24. ##@brief Wrapper class for LeObject getter & setter
  25. #
  26. # This class intend to provide easy & friendly access to LeObject fields values
  27. # without name collision problems
  28. # @note Wrapped methods are : LeObject.data() & LeObject.set_data()
  29. class LeObjectValues(object):
  30. ##@brief Construct a new LeObjectValues
  31. # @param fieldnames_callback method
  32. # @param set_callback method : The LeObject.set_datas() method of corresponding LeObject class
  33. # @param get_callback method : The LeObject.get_datas() method of corresponding LeObject class
  34. def __init__(self, fieldnames_callback, set_callback, get_callback):
  35. self._setter = set_callback
  36. self._getter = get_callback
  37. ##@brief Provide read access to datas values
  38. # @note Read access should be provided for all fields
  39. # @param fname str : Field name
  40. def __getattribute__(self, fname):
  41. getter = super().__getattribute__('_getter')
  42. return getter(fname)
  43. ##@brief Provide write access to datas values
  44. # @note Write acces shouldn't be provided for internal or immutable fields
  45. # @param fname str : Field name
  46. # @param fval * : the field value
  47. def __setattribute__(self, fname, fval):
  48. setter = super().__getattribute__('_setter')
  49. return setter(fname, fval)
  50. class LeObject(object):
  51. ##@brief boolean that tells if an object is abtract or not
  52. _abstract = 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 Read only datasource ( see @ref lodel2_datasources )
  58. _ro_datasource = None
  59. ##@brief Read & write datasource ( see @ref lodel2_datasources )
  60. _rw_datasource = None
  61. ##@brief Store the list of child classes
  62. _child_classes = None
  63. ##@brief Name of the datasource plugin
  64. _datasource_name = None
  65. def __new__(cls, **kwargs):
  66. self = object.__new__(cls)
  67. ##@brief A dict that stores fieldvalues indexed by fieldname
  68. self.__datas = { fname:None for fname in self._fields }
  69. ##@brief Store a list of initianilized fields when instanciation not complete else store True
  70. self.__initialized = list()
  71. ##@brief Datas accessor. Instance of @ref LeObjectValues
  72. self.d = LeObjectValues(self.fieldnames, self.set_data, self.data)
  73. for fieldname, fieldval in kwargs.items():
  74. self.__datas[fieldname] = fieldval
  75. self.__initialized.append(fieldname)
  76. self.__is_initialized = False
  77. self.__set_initialized()
  78. return self
  79. ##@brief Construct an object representing an Editorial component
  80. # @note Can be considered as EmClass instance
  81. def __init__(self, **kwargs):
  82. if self._abstract:
  83. raise NotImplementedError("%s is abstract, you cannot instanciate it." % self.__class__.__name__ )
  84. # Checks that uid is given
  85. for uid_name in self._uid:
  86. if uid_name not in kwargs:
  87. raise LeApiError("Cannot instanciate a LeObject without it's identifier")
  88. self.__datas[uid_name] = kwargs[uid_name]
  89. del(kwargs[uid_name])
  90. self.__initialized.append(uid_name)
  91. # Processing given fields
  92. allowed_fieldnames = self.fieldnames(include_ro = False)
  93. err_list = dict()
  94. for fieldname, fieldval in kwargs.items():
  95. if fieldname not in allowed_fieldnames:
  96. if fieldname in self._fields:
  97. err_list[fieldname] = LeApiError(
  98. "Value given but the field is internal")
  99. else:
  100. err_list[fieldname] = LeApiError(
  101. "Unknown fieldname : '%s'" % fieldname)
  102. else:
  103. self.__datas[fieldname] = fieldval
  104. self.__initialized.append(fieldname)
  105. if len(err_list) > 0:
  106. raise LeApiErrors(msg = "Unable to __init__ %s" % self.__class__,
  107. exceptions = err_list)
  108. self.__set_initialized()
  109. #-----------------------------------#
  110. # Fields datas handling methods #
  111. #-----------------------------------#
  112. ##@brief Property method True if LeObject is initialized else False
  113. @property
  114. def initialized(self):
  115. return self.__is_initialized
  116. ##@return The uid field name
  117. @classmethod
  118. def uid_fieldname(cls):
  119. return cls._uid
  120. ##@brief Return a list of fieldnames
  121. # @param include_ro bool : if True include read only field names
  122. # @return a list of str
  123. @classmethod
  124. def fieldnames(cls, include_ro = False):
  125. if not include_ro:
  126. return [ fname for fname in cls._fields if not cls._fields[fname].is_internal() ]
  127. else:
  128. return list(cls._fields.keys())
  129. @classmethod
  130. def name2objname(cls, name):
  131. return name.title()
  132. ##@brief Return the datahandler asssociated with a LeObject field
  133. # @param fieldname str : The fieldname
  134. # @return A data handler instance
  135. #@todo update class of exception raised
  136. @classmethod
  137. def data_handler(cls, fieldname):
  138. if not fieldname in cls._fields:
  139. raise NameError("No field named '%s' in %s" % (fieldname, cls.__name__))
  140. return cls._fields[fieldname]
  141. ##@brief Getter for references datahandlers
  142. #@param with_backref bool : if true return only references with back_references
  143. #@return <code>{'fieldname': datahandler, ...}</code>
  144. @classmethod
  145. def reference_handlers(cls, with_backref = True):
  146. return { fname: fdh
  147. for fname, fdh in cls.fields(True).items()
  148. if fdh.is_reference() and \
  149. (not with_backref or fdh.back_reference is not None)}
  150. ##@brief Return a LeObject child class from a name
  151. # @warning This method has to be called from dynamically generated LeObjects
  152. # @param leobject_name str : LeObject name
  153. # @return A LeObject child class
  154. # @throw NameError if invalid name given
  155. @classmethod
  156. def name2class(cls, leobject_name):
  157. if cls.__module__ == 'lodel.leapi.leobject':
  158. raise NotImplementedError("Abstract method")
  159. mod = importlib.import_module(cls.__module__)
  160. try:
  161. return getattr(mod, leobject_name)
  162. except (AttributeError, TypeError) :
  163. raise LeApiError("No LeObject named '%s'" % leobject_name)
  164. @classmethod
  165. def is_abstract(cls):
  166. return cls._abstract
  167. ##@brief Field data handler getter
  168. #@param fieldname str : The field name
  169. #@return A datahandler instance
  170. #@throw NameError if the field doesn't exist
  171. @classmethod
  172. def field(cls, fieldname):
  173. try:
  174. return cls._fields[fieldname]
  175. except KeyError:
  176. raise NameError("No field named '%s' in %s" % ( fieldname,
  177. cls.__name__))
  178. ##@return A dict with fieldname as key and datahandler as instance
  179. @classmethod
  180. def fields(cls, include_ro = False):
  181. if include_ro:
  182. return copy.copy(cls._fields)
  183. else:
  184. return {fname:cls._fields[fname] for fname in cls._fields if not cls._fields[fname].is_internal()}
  185. ##@brief Return the list of parents classes
  186. #
  187. #@note the first item of the list is the current class, the second is it's
  188. #parent etc...
  189. #@param cls
  190. #@warning multiple inheritance broken by this method
  191. #@return a list of LeObject child classes
  192. #@todo multiple parent capabilities implementation
  193. @classmethod
  194. def hierarch(cls):
  195. res = [cls]
  196. cur = cls
  197. while True:
  198. cur = cur.__bases__[0] # Multiple inheritance broken HERE
  199. if cur in (LeObject, object):
  200. break
  201. else:
  202. res.append(cur)
  203. return res
  204. ##@brief Return a tuple a child classes
  205. #@return a tuple of child classes
  206. @classmethod
  207. def child_classes(cls):
  208. return copy.copy(cls._child_classes)
  209. ##@brief Return the parent class that is the "source" of uid
  210. #
  211. #The method goal is to return the parent class that defines UID.
  212. #@return a LeObject child class or false if no UID defined
  213. @classmethod
  214. def uid_source(cls):
  215. if cls._uid is None or len(cls._uid) == 0:
  216. return False
  217. hierarch = cls.hierarch()
  218. prev = hierarch[0]
  219. uid_handlers = set( cls._fields[name] for name in cls._uid )
  220. for pcls in cls.hierarch()[1:]:
  221. puid_handlers = set(cls._fields[name] for name in pcls._uid)
  222. if set(pcls._uid) != set(prev._uid) \
  223. or puid_handlers != uid_handlers:
  224. break
  225. prev = pcls
  226. return prev
  227. ##@brief Initialise both datasources (ro and rw)
  228. #
  229. #This method is used once at dyncode load to replace the datasource string
  230. #by a datasource instance to avoid doing this operation for each query
  231. #@see LeObject::_init_datasource()
  232. @classmethod
  233. def _init_datasources(cls):
  234. if isinstance(cls._datasource_name, str):
  235. rw_ds = ro_ds = cls._datasource_name
  236. else:
  237. ro_ds, rw_ds = cls._datasource_name
  238. #Read only datasource initialisation
  239. cls._ro_datasource = DatasourcePlugin.init_datasource(ro_ds, True)
  240. if cls._ro_datasource is None:
  241. log_msg = "No read only datasource set for LeObject %s"
  242. log_msg %= cls.__name__
  243. logger.debug(log_msg)
  244. else:
  245. log_msg = "Read only datasource '%s' initialized for LeObject %s"
  246. log_msg %= (ro_ds, cls.__name__)
  247. logger.debug(log_msg)
  248. #Read write datasource initialisation
  249. cls._rw_datasource = DatasourcePlugin.init_datasource(rw_ds, False)
  250. if cls._ro_datasource is None:
  251. log_msg = "No read/write datasource set for LeObject %s"
  252. log_msg %= cls.__name__
  253. logger.debug(log_msg)
  254. else:
  255. log_msg = "Read/write datasource '%s' initialized for LeObject %s"
  256. log_msg %= (ro_ds, cls.__name__)
  257. logger.debug(log_msg)
  258. ##@brief Return the uid of the current LeObject instance
  259. #@return the uid value
  260. #@warning Broke multiple uid capabilities
  261. def uid(self):
  262. return self.data(self._uid[0])
  263. ##@brief Read only access to all datas
  264. # @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
  265. # @param field_name str : field name
  266. # @return the Value
  267. # @throw RuntimeError if the field is not initialized yet
  268. # @throw NameError if name is not an existing field name
  269. def data(self, field_name):
  270. if field_name not in self._fields.keys():
  271. raise NameError("No such field in %s : %s" % (self.__class__.__name__, field_name))
  272. if not self.initialized and field_name not in self.__initialized:
  273. raise RuntimeError("The field %s is not initialized yet (and have no value)" % field_name)
  274. return self.__datas[field_name]
  275. ##@brief Read only access to all datas
  276. #@return a dict representing datas of current instance
  277. def datas(self, internal = False):
  278. return {fname:self.data(fname) for fname in self.fieldnames(internal)}
  279. ##@brief Datas setter
  280. # @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
  281. # @param fname str : field name
  282. # @param fval * : field value
  283. # @return the value that is really set
  284. # @throw NameError if fname is not valid
  285. # @throw AttributeError if the field is not writtable
  286. def set_data(self, fname, fval):
  287. if fname not in self.fieldnames(include_ro = False):
  288. if fname not in self._fields.keys():
  289. raise NameError("No such field in %s : %s" % (self.__class__.__name__, fname))
  290. else:
  291. raise AttributeError("The field %s is read only" % fname)
  292. self.__datas[fname] = fval
  293. if not self.initialized and fname not in self.__initialized:
  294. # Add field to initialized fields list
  295. self.__initialized.append(fname)
  296. self.__set_initialized()
  297. if self.initialized:
  298. # Running full value check
  299. ret = self.__check_modified_values()
  300. if ret is None:
  301. return self.__datas[fname]
  302. else:
  303. raise LeApiErrors("Data check error", ret)
  304. else:
  305. # Doing value check on modified field
  306. # We skip full validation here because the LeObject is not fully initialized yet
  307. val, err = self._fields[fname].check_data_value(fval)
  308. if isinstance(err, Exception):
  309. #Revert change to be in valid state
  310. del(self.__datas[fname])
  311. del(self.__initialized[-1])
  312. raise LeApiErrors("Data check error", {fname:err})
  313. else:
  314. self.__datas[fname] = val
  315. ##@brief Update the __initialized attribute according to LeObject internal state
  316. #
  317. # Check the list of initialized fields and set __initialized to True if all fields initialized
  318. def __set_initialized(self):
  319. if isinstance(self.__initialized, list):
  320. expected_fields = self.fieldnames(include_ro = False) + self._uid
  321. if set(expected_fields) == set(self.__initialized):
  322. self.__is_initialized = True
  323. ##@brief Designed to be called when datas are modified
  324. #
  325. # Make different checks on the LeObject given it's state (fully initialized or not)
  326. # @return None if checks succeded else return an exception list
  327. def __check_modified_values(self):
  328. err_list = dict()
  329. if self.__initialized is True:
  330. # Data value check
  331. for fname in self.fieldnames(include_ro = False):
  332. val, err = self._fields[fname].check_data_value(self.__datas[fname])
  333. if err is not None:
  334. err_list[fname] = err
  335. else:
  336. self.__datas[fname] = val
  337. # Data construction
  338. if len(err_list) == 0:
  339. for fname in self.fieldnames(include_ro = True):
  340. try:
  341. field = self._fields[fname]
  342. self.__datas[fname] = field.construct_data( self,
  343. fname,
  344. self.__datas,
  345. self.__datas[fname]
  346. )
  347. except Exception as exp:
  348. err_list[fname] = exp
  349. # Datas consistency check
  350. if len(err_list) == 0:
  351. for fname in self.fieldnames(include_ro = True):
  352. field = self._fields[fname]
  353. ret = field.check_data_consistency(self, fname, self.__datas)
  354. if isinstance(ret, Exception):
  355. err_list[fname] = ret
  356. else:
  357. # Data value check for initialized datas
  358. for fname in self.__initialized:
  359. val, err = self._fields[fname].check_data_value(self.__datas[fname])
  360. if err is not None:
  361. err_list[fname] = err
  362. else:
  363. self.__datas[fname] = val
  364. return err_list if len(err_list) > 0 else None
  365. #--------------------#
  366. # Other methods #
  367. #--------------------#
  368. ##@brief Temporary method to set private fields attribute at dynamic code generation
  369. #
  370. # This method is used in the generated dynamic code to set the _fields attribute
  371. # at the end of the dyncode parse
  372. # @warning This method is deleted once the dynamic code loaded
  373. # @param field_list list : list of EmField instance
  374. # @param cls
  375. @classmethod
  376. def _set__fields(cls, field_list):
  377. cls._fields = field_list
  378. ## @brief Check that datas are valid for this type
  379. # @param datas dict : key == field name value are field values
  380. # @param complete bool : if True expect that datas provide values for all non internal fields
  381. # @param allow_internal bool : if True don't raise an error if a field is internal
  382. # @param cls
  383. # @return Checked datas
  384. # @throw LeApiDataCheckError if errors reported during check
  385. @classmethod
  386. def check_datas_value(cls, datas, complete = False, allow_internal = True):
  387. err_l = dict() #Error storing
  388. correct = set() #valid fields name
  389. mandatory = set() #mandatory fields name
  390. for fname, datahandler in cls._fields.items():
  391. if allow_internal or not datahandler.is_internal():
  392. correct.add(fname)
  393. if complete and not hasattr(datahandler, 'default'):
  394. mandatory.add(fname)
  395. provided = set(datas.keys())
  396. # searching for unknow fields
  397. for u_f in provided - correct:
  398. #Here we can check if the field is invalid or rejected because
  399. # it is internel
  400. err_l[u_f] = AttributeError("Unknown or unauthorized field '%s'" % u_f)
  401. # searching for missing mandatory fieldsa
  402. for missing in mandatory - provided:
  403. err_l[missing] = AttributeError("The data for field '%s' is missing" % missing)
  404. #Checks datas
  405. checked_datas = dict()
  406. for name, value in [ (name, value) for name, value in datas.items() if name in correct ]:
  407. dh = cls._fields[name]
  408. res = dh.check_data_value(value)
  409. checked_datas[name], err = res
  410. if err:
  411. err_l[name] = err
  412. if len(err_l) > 0:
  413. raise LeApiDataCheckErrors("Error while checking datas", err_l)
  414. return checked_datas
  415. ##@brief Check and prepare datas
  416. #
  417. # @warning when complete = False we are not able to make construct_datas() and _check_data_consistency()
  418. #
  419. # @param datas dict : {fieldname : fieldvalue, ...}
  420. # @param complete bool : If True you MUST give all the datas
  421. # @param allow_internal : Wether or not interal fields are expected in datas
  422. # @param cls
  423. # @return Datas ready for use
  424. # @todo: complete is very unsafe, find a way to get rid of it
  425. @classmethod
  426. def prepare_datas(cls, datas, complete=False, allow_internal=True):
  427. if not complete:
  428. warnings.warn("\nActual implementation can make broken datas \
  429. construction and consitency when datas are not complete\n")
  430. ret_datas = cls.check_datas_value(datas, complete, allow_internal)
  431. if isinstance(ret_datas, Exception):
  432. raise ret_datas
  433. if complete:
  434. ret_datas = cls._construct_datas(ret_datas)
  435. cls._check_datas_consistency(ret_datas)
  436. return ret_datas
  437. ## @brief Construct datas values
  438. #
  439. # @param cls
  440. # @param datas dict : Datas that have been returned by LeCrud.check_datas_value() methods
  441. # @return A new dict of datas
  442. # @todo IMPLEMENTATION
  443. @classmethod
  444. def _construct_datas(cls, datas):
  445. constructor = DatasConstructor(cls, datas, cls._fields)
  446. ret = {
  447. fname:constructor[fname]
  448. for fname, ftype in cls._fields.items()
  449. if not ftype.is_internal() or ftype.internal != 'autosql'
  450. }
  451. return ret
  452. ## @brief Check datas consistency
  453. # @warning assert that datas is complete
  454. # @param cls
  455. # @param datas dict : Datas that have been returned by LeCrud._construct_datas() method
  456. # @throw LeApiDataCheckError if fails
  457. @classmethod
  458. def _check_datas_consistency(cls, datas):
  459. err_l = []
  460. err_l = dict()
  461. for fname, dh in cls._fields.items():
  462. ret = dh.check_data_consistency(cls, fname, datas)
  463. if isinstance(ret, Exception):
  464. err_l[fname] = ret
  465. if len(err_l) > 0:
  466. raise LeApiDataCheckError("Datas consistency checks fails", err_l)
  467. ## @brief Check datas consistency
  468. # @warning assert that datas is complete
  469. # @param cls
  470. # @param datas dict : Datas that have been returned by LeCrud.prepare_datas() method
  471. # @param type_query str : Type of query to be performed , default value : insert
  472. @classmethod
  473. def make_consistency(cls, datas, type_query = 'insert'):
  474. for fname, dh in cls._fields.items():
  475. ret = dh.make_consistency(fname, datas, type_query)
  476. ## @brief Add a new instance of LeObject
  477. # @return a new uid en case of success, False otherwise
  478. @classmethod
  479. def insert(cls, datas):
  480. query = LeInsertQuery(cls)
  481. return query.execute(datas)
  482. ## @brief Update an instance of LeObject
  483. #
  484. #@param datas : list of new datas
  485. def update(self, datas = None):
  486. datas = self.datas(internal=False) if datas is None else datas
  487. uids = self._uid
  488. query_filter = list()
  489. for uid in uids:
  490. query_filter.append((uid, '=', self.data(uid)))
  491. try:
  492. query = LeUpdateQuery(self.__class__, query_filter)
  493. except Exception as err:
  494. raise err
  495. try:
  496. result = query.execute(datas)
  497. except Exception as err:
  498. raise err
  499. return result
  500. ## @brief Delete an instance of LeObject
  501. #
  502. #@return 1 if the objet has been deleted
  503. def delete(self):
  504. uids = self._uid
  505. query_filter = list()
  506. for uid in uids:
  507. query_filter.append((uid, '=', self.data(uid)))
  508. query = LeDeleteQuery(self.__class__, query_filter)
  509. result = query.execute()
  510. return result
  511. ## @brief Delete instances of LeObject
  512. #@param query_filters list
  513. #@returns the number of deleted items
  514. @classmethod
  515. def delete_bundle(cls, query_filters):
  516. deleted = 0
  517. try:
  518. query = LeDeleteQuery(cls, query_filters)
  519. except Exception as err:
  520. raise err
  521. try:
  522. result = query.execute()
  523. except Exception as err:
  524. raise err
  525. if not result is None:
  526. deleted += result
  527. return deleted
  528. ## @brief Get instances of LeObject
  529. #
  530. #@param query_filters dict : (filters, relational filters), with filters is a list of tuples : (FIELD, OPERATOR, VALUE) )
  531. #@param field_list list|None : list of string representing fields see
  532. #@ref leobject_filters
  533. #@param order list : A list of field names or tuple (FIELDNAME,[ASC | DESC])
  534. #@param group list : A list of field names or tuple (FIELDNAME,[ASC | DESC])
  535. #@param limit int : The maximum number of returned results
  536. #@param offset int : offset
  537. #@return a list of items (lists of (fieldname, fieldvalue))
  538. @classmethod
  539. def get(cls, query_filters, field_list=None, order=None, group=None, limit=None, offset=0):
  540. if field_list is not None:
  541. for uid in [ uidname
  542. for uidname in cls.uid_fieldname()
  543. if uidname not in field_list ]:
  544. field_list.append(uid)
  545. if CLASS_ID_FIELDNAME not in field_list:
  546. field_list.append(CLASS_ID_FIELDNAME)
  547. try:
  548. query = LeGetQuery(
  549. cls, query_filters = query_filters, field_list = field_list,
  550. order = order, group = group, limit = limit, offset = offset)
  551. except ValueError as err:
  552. raise err
  553. try:
  554. result = query.execute()
  555. except Exception as err:
  556. raise err
  557. objects = list()
  558. for res in result:
  559. res_cls = cls.name2class(res[CLASS_ID_FIELDNAME])
  560. inst = res_cls.__new__(res_cls,**res)
  561. objects.append(inst)
  562. return objects
  563. ##@brief Retrieve an object given an UID
  564. #@todo broken multiple UID
  565. @classmethod
  566. def get_from_uid(cls, uid):
  567. if cls.uid_fieldname() is None:
  568. raise LodelFatalError(
  569. "No uid defined for class %s" % cls.__name__)
  570. uidname = cls.uid_fieldname()[0] #Brokes composed UID
  571. res = cls.get([(uidname,'=', uid)])
  572. #dedoublonnage vu que query ou la datasource est bugué
  573. if len(res) > 1:
  574. res_cp = res
  575. res = []
  576. while len(res_cp) > 0:
  577. cur_res = res_cp.pop()
  578. if cur_res.uid() in [ r.uid() for r in res_cp]:
  579. logger.error("DOUBLON detected in query results !!!")
  580. else:
  581. res.append(cur_res)
  582. if len(res) > 1:
  583. raise LodelFatalError("Get from uid returned more than one \
  584. object ! For class %s with uid value = %s" % (cls, uid))
  585. elif len(res) == 0:
  586. return None
  587. return res[0]
  588. ##@brief Checks if an object exists
  589. @classmethod
  590. def is_exist(cls, uid):
  591. if cls.uid_fieldname() is None:
  592. raise LodelFatalError(
  593. "No uid defined for class %s" % cls.__name__)
  594. from .query import is_exist
  595. return is_exist(cls, uid)