No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

leobject.py 25KB

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