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 27KB

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