#-*- coding: utf-8 -*- ## @package leobject API to access lodel datas # # This package contains abstract classes leapi.leclass.LeClass , leapi.letype.LeType, leapi.leapi._LeObject. # Those abstract classes are designed to be mother classes of dynamically generated classes ( see leapi.lefactory.LeFactory ) ## @package leapi.leobject # @brief Abstract class designed to be implemented by LeObject # # @note LeObject will be generated by leapi.lefactory.LeFactory import leapi from leapi.lecrud import _LeCrud, LeApiDataCheckError, LeApiQueryError from leapi.leclass import _LeClass from leapi.leobject import LeObjectError ## @brief Represent an EmType data instance # @note Is not a derivated class of LeClass because the concrete class will be a derivated class from LeClass class _LeType(_LeClass): ## @brief Stores selected fields with key = name _fields = list() ## @brief Allowed LeType superiors _superiors = list() ## @brief Stores the class of LeClass _leclass = None ## @brief Stores the EM uid _type_id = None ## @brief Instanciate a new LeType # @param lodel_id : The lodel id # @param **kwargs : Datas used to populate a LeType def __init__(self, lodel_id, **kwargs): if self._leclass is None: raise NotImplementedError("Abstract class") self.lodel_id, err = self._uid_fieldtype['lodel_id'].check_data_value(lodel_id) if isinstance(err, Exception): raise err if 'type_id' in kwargs: if self.__class__._type_id != int(kwargs['type_id']): raise RuntimeError("Trying to instanciate a %s with an type_id that is not correct"%self.__class__.__name__) if 'class_id' in kwargs: if self.__class__._class_id != int(kwargs['class_id']): raise RuntimeError("Trying to instanciate a %s with a clas_id that is not correct"%self.__class__.__name__) ## Populate the object from the datas received in kwargs err_l = dict() for name, value in kwargs.items(): if name not in self._fields: err_l[name] = AttributeError("No such field '%s' for %s"%(name, self.__class__.__name__)) else: cvalue, err = self.fieldtypes()[name].check_data_value(value) if isinstance(err, Exception): err_l[name] = err else: setattr(self, name, value) if len(err_l) > 0: raise LeApiDataCheckError("Invalid arguments given to constructor", err_l) @classmethod def leo_class(cls): return cls._leclass @classmethod def fieldlist(cls): return cls._fields @classmethod def get(cls, query_filters, field_list = None, order = None, group = None, limit = None, offset = 0): query_filters.append(('type_id', '=', cls._type_id)) return super().get(query_filters, field_list, order, group, limit, offset) @classmethod def fieldtypes(cls): return { fname: cls._fieldtypes[fname] for fname in cls._fieldtypes if fname in cls._fields } ## @brief Populate the LeType wih datas from DB # @param field_list None|list : List of fieldname to fetch. If None fetch all the missing datas def populate(self, field_list=None): if field_list == None: field_list = [ fname for fname in self._fields if not hasattr(self, fname) ] filters = [self._id_filter()] rel_filters = [] fdatas = self._datasource.select(self.__class__, field_list, filters, rel_filters) if fdatas is None or len(fdatas) == 0: raise LeApiQueryError("Error when trying to populate an object. For type %s id : %d"% (self.__class__.__name__, self.lodel_id)) for fname, fval in fdatas[0].items(): setattr(self, fname, fval) ## @brief Get a fieldname:value dict # @return A dict with field name as key and the field value as value def datas(self): return { fname: getattr(self, fname) for fname in self._fields if hasattr(self,fname) } ## @brief Get all the datas for this LeType # @return a dict with fieldname as key and field value as value # @warning Can represent a performance issue def all_datas(self): self.populate() return self.datas() ## @brief Delete current instance from DB def delete(self): _LeCrud._delete(self) ## @brief Add a superior # @param lesup LeObject : LeObject child class instance # @param nature str : Relation nature # @param del_if_exists bool : If true delete the superior if any before setting the new one # @return relation id if successfully created else returns false def add_superior(self, lesup, nature, del_if_exists = False): lehierarch = self.name2class('LeHierarch') if del_if_exists: prev_sup = lehierarch.get( [('lesub', '=', self), ('nature', '=', nature)], [ lehierarch.uidname() ] ) if len(prev_sup) > 0: for todel_sup in prev_sup: #This loop shoud be useless...but we never know todel_sup.delete() return lehierarch.insert({'lesup':lesup, 'lesub':self, 'nature':nature}) ## @brief Link the LeObject with another one (rel2type relations) # # @note This methods asser that self is the superior and leo_tolink the subordinate # # @param leo_tolink LeObject : LeObject child instance to link with # @param **datas : Relation attributes (if any) # @return a relation id if success def link_with(self, leo_tolink, datas): # Fetch rel2type leapi class r2t = self.name2class('LeRel2Type') class_name = r2t.relname(self, leo_tolink.__class__) r2tcls = self.name2class(class_name) if not r2tcls: raise ValueError("No rel2type possible between a '%s' as superior and a '%s' as subordinate" % (self._leclass.__name__, leo_tolink.__class__.__name__)) datas['lesup'] = self datas['lesub'] = leo_tolink return r2tcls.insert(datas, class_name) ## @brief Get the linked objects lodel_id # @param letype LeType : Filter the result with LeType child class (not instance) # @return a dict with LeType instance as key and dict{attr_name:attr_val...} as value # @todo unit tests def ___linked(self, letype): if leapi.letype.LeType not in letype.__bases__: raise ValueError("letype has to be a child class of LeType (not an instance) but %s found"%type(letype)) if letype in self._linked_types.keys(): get_sub = True elif self.__class__ in letype._linked_types.keys(): get_sub = False else: raise ValueError("The two LeType classes %s and %s are not linked with a rel2type field"%(self.__class__.__name__, letype.__name__)) return self._datasource.get_related(self, letype, get_sub) ## @brief Link this object with a LeObject as subordinate # @note shortcut for @ref leapi.leapi._LeObject.link_together() # @param lesub LeObject : The object to be linked with as subordinate # @param **rel_attr : keywords arguments for relations attributes # @return The relation_id if success else return False # @throw LeObjectError if the link is not valid # @throw LeObkectError if the link already exists # @throw AttributeError if an non existing relation attribute is given as argument # @throw ValueError if the relation attrivute value check fails # @see leapi.lefactory.LeFactory.link_together() def ___link(self, leo, **rel_attr): return leapi.lefactory.LeFactory.leobj_from_name('LeObject').link_together(self, leo, **rel_attr) ## @brief Returns linked subordinates in a rel2type given a wanted LeType child class # @param letype LeType(class) : The wanted LeType of result # @return A dict with LeType child class instance as key and dict {'id_relation':id, rel_attr_name:rel_attr_value, ...} # @throw LeObjectError if the relation is not possible # @see leapi.lefactory.LeFactory.linked_together() def ___linked_subordinates(self, letype): return leapi.lefactory.LeFactory.leobj_from_name('LeObject').linked_together(self, letype, True) ## @brief Remove a link with a subordinate # @param leo LeType : LeType child instance # @return True if a link has been deleted # @throw LeObjectError if the relation do not concern the current LeType def ___unlink_subordinate(self, id_relation): return leapi.lefactory.LeFactory.leobj_from_name('LeObject').linked_together(self, leo) ## @brief Remove a link bewteen this object and another # @param leo LeType : LeType child class instance # @todo unit tests def ___unlink(self, leo): if leo.__class__ in self._linked_types.keys(): sup = self sub = leo elif self.__class__ in leo._linked_types.keys(): sup = leo sub = self return self._datasource.del_related(sup, sub) ## @brief Add a superior # @param lesup LeType | LeRoot : LeType child class instance that will be the superior # @param nature str : The nature of the relation @ref EditorialModel.classtypes # @param rank str|int : The relation rank. Can be 'last', 'first' or an integer # @param replace_if_exists bool : if True delete the old superior and set the new one. If False and there is a superior raise an LeObjectQueryError # @return The relation ID or False if fails def ___superior_add(self, leo, nature, rank = 'last', replace_if_exists = False): return leapi.lefactory.LeFactory.leobj_from_name('LeObject').hierarchy_add(leo, self, nature, rank, replace_if_exists) ## @brief Delete a superior given a relation's natue # @param leo LeType | LeRoot : The superior to delete # @param nature str : The nature of the relation @ref EditorialModel.classtypes # @return True if deletion is a success def ___superior_del(self, leo, nature): return leapi.lefactory.leobj_from_name('LeObject').hierarchy_del(leo, self, nature) ## @brief Fetch superiors by depth # @return A list of LeObject ordered by depth (the first is the one with the bigger depth) def ___superiors(self): return leapi.lefactory.leobj_from_name('LeObject').hierarchy_get(self,nature, leo_is_sup = False) ## @brief Fetch subordinates ordered by rank # @return A list of LeObject ordered by rank def ___subordinates(self): return leapi.lefactory.leobj_from_name('LeObject').hierarchy_get(self,nature, leo_is_sup = True) ## @brief Update a LeType in db def ___db_update(self): return self.update(filters=[('lodel_id', '=', repr(self.lodel_id))], datas = self.datas)