1
0
Fork 0
mirror of https://github.com/yweber/lodel2.git synced 2026-03-15 15:52:02 +01:00
lodel2_mirror/lodel/leapi/leobject.py
Yann 747205a0fe LeFactory first implementation
This implementation is broken because of relation data_handler. Backreference is a broken concept and relation data_handler too.
2016-04-01 17:17:30 +02:00

262 lines
11 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#-*- coding: utf-8 -*-
class LeApiErrors(Exception):
## @brief Instanciate a new exceptions handling multiple exceptions
# @param msg str : Exception message
# @param exceptions dict : A list of data check Exception with concerned field (or stuff) as key
def __init__(self, msg = "Unknow error", exceptions = None):
self._msg = msg
self._exceptions = dict() if exceptions is None else exceptions
def __repr__(self):
return self.__str__()
def __str__(self):
msg = self._msg
for_iter = self._exceptions.items() if isinstance(self._exceptions, dict) else enumerate(self.__exceptions)
for obj, expt in for_iter:
msg += "\n\t{expt_obj} : ({expt_name}) {expt_msg}; ".format(
expt_obj = obj,
expt_name=expt.__class__.__name__,
expt_msg=str(expt)
)
return msg
## @brief When an error concern a query
class LeApiQueryError(LeApiErrors):
pass
## @brief When an error concerns a datas
class LeApiDataCheckError(LeApiErrors):
pass
## @brief Wrapper class for LeObject getter & setter
#
# This class intend to provide easy & friendly access to LeObject fields values
# without name collision problems
# @note Wrapped methods are : LeObject.data() & LeObject.set_data()
class LeObjectValues(object):
## @brief Construct a new LeObjectValues
# @param set_callback method : The LeObject.set_datas() method of corresponding LeObject class
# @param get_callback method : The LeObject.get_datas() method of corresponding LeObject class
def __init__(self, fieldnames_callback, set_callback, get_callback):
self.__setter = set_callback
self.__getter = get_callback
## @brief Provide read access to datas values
# @note Read access should be provided for all fields
# @param fname str : Field name
def __getattribute__(self, fname):
return self.__getter(fname)
## @brief Provide write access to datas values
# @note Write acces shouldn't be provided for internal or immutable fields
# @param fname str : Field name
# @param fval * : the field value
def __setattribute__(self, fname, fval):
return self.__setter(fname, fval)
class LeObject(object):
## @brief boolean that tells if an object is abtract or not
__abtract = None
## @brief A dict that stores DataHandler instances indexed by field name
__fields = None
## @brief A tuple of fieldname (or a uniq fieldname) representing uid
__uid = None
## @brief Construct an object representing an Editorial component
# @note Can be considered as EmClass instance
def __init__(self, **kwargs):
if self.__abstract:
raise NotImplementedError("%s is abstract, you cannot instanciate it." % self.__class__.__name__ )
## @brief A dict that stores fieldvalues indexed by fieldname
self.__datas = { fname:None for fname in self.__fields }
## @brief Store a list of initianilized fields when instanciation not complete else store True
self.__initialized = list()
## @brief Datas accessor. Instance of @ref LeObjectValues
self.d = LeObjectValues(self.fieldnames, self.set_data, self.data)
# Checks that uid is given
for uid_name in self.__uid:
if uid_name not in kwargs:
raise AttributeError("Cannot instanciate a LeObject without it's identifier")
self.__datas[uid_name] = kwargs[uid_name]
del(kwargs[uid_name])
self.__initialized.append(uid_name)
# Processing given fields
allowed_fieldnames = self.fieldnames(include_ro = False)
err_list = list()
for fieldname, fieldval in kwargs.items():
if fieldname not in allowed_fieldnames:
if fieldname in self.__fields:
err_list.append(
AttributeError("Value given for internal field : '%s'" % fieldname)
)
else:
err_list.append(
AttributeError("Unknown fieldname : '%s'" % fieldname)
)
else:
self.__datas[fieldame] = fieldval
self.__initialized = list()
self.set_initialized()
#-----------------------------------#
# Fields datas handling methods #
#-----------------------------------#
## @brief @property True if LeObject is initialized else False
@property
def initialized(self):
return not isinstance(self.__initialized, list)
## @brief Return a list of fieldnames
# @param include_ro bool : if True include read only field names
# @return a list of str
@classmethod
def fieldnames(cls, include_ro = False):
if not include_ro:
return [ fname for fname in self.__fields if not self.__fields[fname].is_internal() ]
else:
return list(self.__fields.keys())
@classmethod
def name2objname(cls, name):
return name.title()
## @brief Return the datahandler asssociated with a LeObject field
# @param fieldname str : The fieldname
# @return A data handler instance
@classmethod
def data_handler(cls, fieldname):
if not fieldname in cls.__fields:
raise NameError("No field named '%s' in %s" % (fieldname, cls.__name__))
return cls.__fields[fieldname]
## @brief Read only access to all datas
# @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
# @param name str : field name
# @return the Value
# @throw RuntimeError if the field is not initialized yet
# @throw NameError if name is not an existing field name
def data(self, field_name):
if field_name not in self.__fields.keys():
raise NameError("No such field in %s : %s" % (self.__class__.__name__, name))
if not self.initialized and name not in self.__initialized:
raise RuntimeError("The field %s is not initialized yet (and have no value)" % name)
return self.__datas[name]
## @brief Datas setter
# @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
# @param fname str : field name
# @param fval * : field value
# @return the value that is really set
# @throw NameError if fname is not valid
# @throw AttributeError if the field is not writtable
def set_data(self, fname, fval):
if field_name not in self.fieldnames(include_ro = False):
if field_name not in self.__fields.keys():
raise NameError("No such field in %s : %s" % (self.__class__.__name__, name))
else:
raise AttributeError("The field %s is read only" % fname)
self.__datas[fname] = fval
if not self.initialized and fname not in self.__initialized:
# Add field to initialized fields list
self.__initialized.append(fname)
self.__set_initialized()
if self.initialized:
# Running full value check
ret = self.__check_modified_values()
if ret is None:
return self.__datas[fname]
else:
raise LeApiErrors("Data check error", ret)
else:
# Doing value check on modified field
# We skip full validation here because the LeObject is not fully initialized yet
val, err = self.__fields[fname].check_data_value(fval)
if isinstance(err, Exception):
#Revert change to be in valid state
del(self.__datas[fname])
del(self.__initialized[-1])
raise LeApiErrors("Data check error", {fname:err})
else:
self.__datas[fname] = val
## @brief Update the __initialized attribute according to LeObject internal state
#
# Check the list of initialized fields and set __initialized to True if all fields initialized
def __set_initialized(self):
if isinstance(self.__initialized, list):
expected_fields = self.fieldnames(include_ro = False) + self.__uid
if set(expected_fields) == set(self.__initialized):
self.__initialized = True
## @brief Designed to be called when datas are modified
#
# Make different checks on the LeObject given it's state (fully initialized or not)
# @return None if checks succeded else return an exception list
def __check_modified_values(self):
err_list = dict()
if self.__initialized is True:
# Data value check
for fname in self.fieldnames(include_ro = False):
val, err = self.__fields[fname].check_data_value(self.__datas[fname])
if err is not None:
err_list[fname] = err
else:
self.__datas[fname] = val
# Data construction
if len(err_list) == 0:
for fname in self.fieldnames(include_ro = True):
try:
field = self.__fields[fname]
self.__datas[fname] = fields.construct_data( self,
fname,
self.__datas,
self.__datas[fname]
)
except Exception as e:
err_list[fname] = e
# Datas consistency check
if len(err_list) == 0:
for fname in self.fieldnames(include_ro = True):
field = self.__fields[fname]
ret = field.check_data_consistency(self, fname, self.__datas)
if isinstance(ret, Exception):
err_list[fname] = ret
else:
# Data value check for initialized datas
for fname in self.__initialized:
val, err = self.__fields[fname].check_data_value(self.__datas[fname])
if err is not None:
err_list[fname] = err
else:
self.__datas[fname] = val
return err_list if len(err_list) > 0 else None
#--------------------#
# Other methods #
#--------------------#
## @brief Temporary method to set private fields attribute at dynamic code generation
#
# This method is used in the generated dynamic code to set the __fields attribute
# at the end of the dyncode parse
# @warning This method is deleted once the dynamic code is parsed
# @param field_list list : list of EmField instance
@classmethod
def _set__fields(cls, field_list):
cls.__fields = field_list