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

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