暫無描述
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 27KB

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