Bez popisu
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

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