暂无描述
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

leobject.py 25KB

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