mirror of
https://github.com/yweber/lodel2.git
synced 2025-11-27 15:56:53 +01:00
Commenting & bufixing in datahandlers & queries
This commit is contained in:
parent
38fda3ce43
commit
20c1627bda
7 changed files with 185 additions and 107 deletions
|
|
@ -1,2 +1,62 @@
|
|||
from lodel.leapi.datahandlers.base_classes import DataHandler
|
||||
DataHandler.load_base_handlers()
|
||||
|
||||
##@defgroup lodel2_datahandlers Datahandlers
|
||||
#@ingroup lodel2_leapi
|
||||
#@ingroup lodel2_em
|
||||
|
||||
##@defgroup lodel2_dh_checks Datahandlers datas checking
|
||||
#@ingroup lodel2_datahandlers
|
||||
|
||||
##@package lodel.leapi.datahandlers Lodel2 datahandlers
|
||||
#
|
||||
#Datahandlers are object that handles datas check, construction and
|
||||
#consistency check
|
||||
#
|
||||
|
||||
|
||||
##@page lodel2_dh_checks_page Datahandlers datas checking
|
||||
#@ingroup lodel2_dh_checks
|
||||
#
|
||||
#@section lodel2_dh_check_mech Datas checking mechanism
|
||||
#
|
||||
#The data checking mechanism is divided into 3 stages :
|
||||
# 1. **value checking** : a basic value check. Example : is len(value) < 52
|
||||
# 2. **data construction** : for datas that needs to be modified. Example :
|
||||
#a date that will be transformed into a Datetime object associated with
|
||||
#a timezone
|
||||
# 3. **data consistency checking** : perform a consistency checking on the
|
||||
#object from the "point of view" of the current field. Example : is the given
|
||||
#lodel_id an identifier of a Article
|
||||
#
|
||||
#@subsection lodel2_dh_check_impl Implementation
|
||||
#
|
||||
#Those three stages are implemented by 3 datahandlers methods :
|
||||
# - @ref base_classes.DataHandler.check_data_value() "check_data_value"
|
||||
# - @ref base_classes.DataHandler.construct_data() "construct_data"
|
||||
# - @ref base_classes.DataHandler.check_data_value() "check_data_consitency"
|
||||
#
|
||||
#@note To ensure the calls of the base classes methods child classes implements
|
||||
#those method with a '_' preffix :
|
||||
# - @ref base_classes.DataHandler._check_data_value "_check_data_value()"
|
||||
# - @ref base_classes.DataHandler._construct_data() "_construct_data"
|
||||
# - @ref base_classes.DataHandler._check_data_value() "_check_data_consitency"
|
||||
#
|
||||
#Examples of child classes can be found @ref datahandlers.datas "here"
|
||||
#
|
||||
#@subsubsection lodel2_dh_datas_construction Datas construction
|
||||
#
|
||||
#Datas construction is a bit tricky. When constructing a data handled by a
|
||||
#datahandler we may need to have access to other datas in the object (see
|
||||
#@ref base_classes.DataHandler.construct_data() "construct_data() arguments").
|
||||
#
|
||||
#The problem reside in the construction order, if we need other datas we have
|
||||
#to be sure that they are allready constructed. To achieve this goal the datas
|
||||
#dict given as arguement to
|
||||
#@ref base_classes.DataHandler.construct_data() "construct_data()" is an
|
||||
#@ref base_classes.DatasConstructor "DatasConstructor" instance. This class
|
||||
#check if a data is constructed when trying to access to it, if not it runs
|
||||
#the corresponding construct_data() (and have a circular dependencies detection
|
||||
#mechanism)
|
||||
#
|
||||
#@see base_classes.DatasConstructor.
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class FieldValidationError(Exception):
|
|||
pass
|
||||
|
||||
##@brief Base class for all data handlers
|
||||
#@ingroup lodel2_datahandlers
|
||||
class DataHandler(object):
|
||||
|
||||
_HANDLERS_MODULES = ('datas_base', 'datas', 'references')
|
||||
|
|
@ -75,7 +76,10 @@ class DataHandler(object):
|
|||
return self.internal is not False
|
||||
|
||||
##@brief calls the data_field defined _check_data_value() method
|
||||
# @return tuple (value, error|None)
|
||||
#@ingroup lodel2_dh_checks
|
||||
#@warning DO NOT REIMPLEMENT THIS METHOD IN A CUSTOM DATAHANDLER (see
|
||||
#@ref _construct_data() and @ref lodel2_dh_check_impl )
|
||||
#@return tuple (value, error|None)
|
||||
def check_data_value(self, value):
|
||||
if value is None:
|
||||
if not self.nullable:
|
||||
|
|
@ -83,6 +87,10 @@ class DataHandler(object):
|
|||
|
||||
return None, None
|
||||
return self._check_data_value(value)
|
||||
|
||||
##@brief Designed to be implemented in child classes
|
||||
def _check_data_value(self, value):
|
||||
return value, None
|
||||
|
||||
##@brief checks if this class can override the given data handler
|
||||
# @param data_handler DataHandler
|
||||
|
|
@ -93,35 +101,66 @@ class DataHandler(object):
|
|||
return True
|
||||
|
||||
##@brief Build field value
|
||||
# @param emcomponent EmComponent : An EmComponent child class instance
|
||||
# @param fname str : The field name
|
||||
# @param datas dict : dict storing fields values (from the component)
|
||||
# @param cur_value : the value from the current field (identified by fieldname)
|
||||
# @return the value
|
||||
# @throw RunTimeError if data construction fails
|
||||
#@ingroup lodel2_dh_checks
|
||||
#@warning DO NOT REIMPLEMENT THIS METHOD IN A CUSTOM DATAHANDLER (see
|
||||
#@ref _construct_data() and @ref lodel2_dh_check_impl )
|
||||
#@param emcomponent EmComponent : An EmComponent child class instance
|
||||
#@param fname str : The field name
|
||||
#@param datas dict : dict storing fields values (from the component)
|
||||
#@param cur_value : the value from the current field (identified by fieldname)
|
||||
#@return the value
|
||||
#@throw RunTimeError if data construction fails
|
||||
#@todo raise something else
|
||||
def construct_data(self, emcomponent, fname, datas, cur_value):
|
||||
emcomponent_fields = emcomponent.fields()
|
||||
data_handler = None
|
||||
if fname in emcomponent_fields:
|
||||
data_handler = emcomponent_fields[fname]
|
||||
|
||||
|
||||
new_val = cur_value
|
||||
if fname in datas.keys():
|
||||
return cur_value
|
||||
pass
|
||||
elif data_handler is not None and hasattr(data_handler, 'default'):
|
||||
return data_handler.default
|
||||
new_val = data_handler.default
|
||||
elif data_handler is not None and data_handler.nullable:
|
||||
return None
|
||||
new_val = None
|
||||
return self._construct_data(emcomponent, fname, datas, new_val)
|
||||
|
||||
##@brief Designed to be reimplemented by child classes
|
||||
#@param emcomponent EmComponent : An EmComponent child class instance
|
||||
#@param fname str : The field name
|
||||
#@param datas dict : dict storing fields values (from the component)
|
||||
#@param cur_value : the value from the current field (identified by fieldname)
|
||||
#@return the value
|
||||
#@see construct_data() lodel2_dh_check_impl
|
||||
def _construct_data(self, empcomponent, fname, datas, cur_value):
|
||||
return cur_value
|
||||
|
||||
|
||||
##@brief Check datas consistency
|
||||
# @param emcomponent EmComponent : An EmComponent child class instance
|
||||
# @param fname : the field name
|
||||
# @param datas dict : dict storing fields values
|
||||
# @return an Exception instance if fails else True
|
||||
# @todo A implémenter
|
||||
#@ingroup lodel2_dh_checks
|
||||
#@warning DO NOT REIMPLEMENT THIS METHOD IN A CUSTOM DATAHANDLER (see
|
||||
#@ref _construct_data() and @ref lodel2_dh_check_impl )
|
||||
#@warning the datas argument looks like a dict but is not a dict
|
||||
#see @ref base_classes.DatasConstructor "DatasConstructor" and
|
||||
#@ref lodel2_dh_datas_construction "Datas construction section"
|
||||
#@param emcomponent EmComponent : An EmComponent child class instance
|
||||
#@param fname : the field name
|
||||
#@param datas dict : dict storing fields values
|
||||
#@return an Exception instance if fails else True
|
||||
#@todo A implémenter
|
||||
def check_data_consistency(self, emcomponent, fname, datas):
|
||||
return self._check_data_consistency(emcomponent, fname, datas)
|
||||
|
||||
##@brief Designed to be reimplemented by child classes
|
||||
#@param emcomponent EmComponent : An EmComponent child class instance
|
||||
#@param fname : the field name
|
||||
#@param datas dict : dict storing fields values
|
||||
#@return an Exception instance if fails else True
|
||||
#@see check_data_consistency() lodel2_dh_check_impl
|
||||
def _check_data_consistency(self, emcomponent, fname, datas):
|
||||
return True
|
||||
|
||||
|
||||
##@brief make consistency after a query
|
||||
# @param emcomponent EmComponent : An EmComponent child class instance
|
||||
# @param fname : the field name
|
||||
|
|
@ -139,7 +178,8 @@ class DataHandler(object):
|
|||
if not issubclass(data_handler, DataHandler):
|
||||
raise ValueError("A data handler HAS TO be a child class of DataHandler")
|
||||
cls.__custom_handlers[name] = data_handler
|
||||
|
||||
|
||||
##@brief Load all datahandlers
|
||||
@classmethod
|
||||
def load_base_handlers(cls):
|
||||
if cls._base_handlers is None:
|
||||
|
|
@ -185,13 +225,23 @@ class DataHandler(object):
|
|||
return hash(tuple(hash_dats))
|
||||
|
||||
##@brief Base class for datas data handler (by opposition with references)
|
||||
#@ingroup lodel2_datahandlers
|
||||
class DataField(DataHandler):
|
||||
pass
|
||||
|
||||
##@brief Abstract class for all references
|
||||
#@ingroup lodel2_datahandlers
|
||||
#
|
||||
# References are fields that stores a reference to another
|
||||
# editorial object
|
||||
#
|
||||
#
|
||||
#@todo Check data implementation : check_data = is value an UID or an
|
||||
#LeObject child instance
|
||||
#@todo Construct data implementation : transform the data into a LeObject
|
||||
#instance
|
||||
#@todo Check data consistency implementation : check that LeObject instance
|
||||
#is from an allowed class
|
||||
class Reference(DataHandler):
|
||||
base_type="ref"
|
||||
|
||||
|
|
@ -210,8 +260,6 @@ class Reference(DataHandler):
|
|||
#if not issubclass(lodel.leapi.leobject.LeObject, back_reference[0]) or not isinstance(back_reference[1], str):
|
||||
# raise TypeError("Back reference was expected to be a tuple(<class LeObject>, str) but got : (%s, %s)" % (back_reference[0], back_reference[1]))
|
||||
self.__back_reference = back_reference
|
||||
self.back_ref = back_reference
|
||||
|
||||
super().__init__(internal=internal, **kwargs)
|
||||
|
||||
@property
|
||||
|
|
@ -227,11 +275,15 @@ class Reference(DataHandler):
|
|||
self.__back_reference = back_reference
|
||||
|
||||
##@brief Check value
|
||||
# @param value *
|
||||
# @return tuple(value, exception)
|
||||
# @todo implement the check when we have LeObject to check value
|
||||
def _check_data_value(self, value):
|
||||
return value, None
|
||||
#@param value *
|
||||
#@return tuple(value, exception)
|
||||
#@todo implement the check when we have LeObject to check value
|
||||
def check_data_value(self, value):
|
||||
print('REFERENCE check_data value')
|
||||
return super().check_data_value(value)
|
||||
|
||||
|
||||
|
||||
if isinstance(value, lodel.editorial_model.components.EmClass):
|
||||
value = [value]
|
||||
for elt in value:
|
||||
|
|
@ -243,29 +295,36 @@ class Reference(DataHandler):
|
|||
return value
|
||||
|
||||
##@brief Check datas consistency
|
||||
# @param emcomponent EmComponent : An EmComponent child class instance
|
||||
# @param fname : the field name
|
||||
# @param datas dict : dict storing fields values
|
||||
# @return an Exception instance if fails else True
|
||||
# @todo A implémenter
|
||||
#@param emcomponent EmComponent : An EmComponent child class instance
|
||||
#@param fname : the field name
|
||||
#@param datas dict : dict storing fields values
|
||||
#@return an Exception instance if fails else True
|
||||
#@todo check for performance issue and check logics
|
||||
#@todo Implements consistency checking on value : Check that the given value
|
||||
#points onto an allowed class
|
||||
#@warning composed uid capabilities broken here
|
||||
def check_data_consistency(self, emcomponent, fname, datas):
|
||||
rep = super().check_data_consistency(emcomponent, fname, datas)
|
||||
if isinstance(rep, Exception):
|
||||
return rep
|
||||
if self.back_reference is None:
|
||||
return True
|
||||
#Checking back reference consistency
|
||||
|
||||
# !! Reimplement instance fetching in construct data !!
|
||||
dh = emcomponent.field(fname)
|
||||
logger.debug(dh)
|
||||
logger.info('Warning : multiple uid capabilities are broken here')
|
||||
uid = datas[emcomponent.uid_fieldname()[0]]
|
||||
target_class = self.back_ref[0]
|
||||
target_field = self.back_ref[1]
|
||||
target_uidfield = traget_class.uid_fieldname()[0]
|
||||
value = datas[emcomponent.data(fname)]
|
||||
uid = datas[emcomponent.uid_fieldname()[0]] #multi uid broken here
|
||||
target_class = self.back_reference[0]
|
||||
target_field = self.back_reference[1]
|
||||
target_uidfield = target_class.uid_fieldname()[0] #multi uid broken here
|
||||
value = datas[fname]
|
||||
|
||||
obj = target_class.get((target_uidfield , '=', value))
|
||||
obj = target_class.get([(target_uidfield , '=', value)])
|
||||
|
||||
if len(obj) == 0:
|
||||
logger.warning('Object referenced does not exist')
|
||||
return False
|
||||
|
||||
obj.set_data(target_field, uid)
|
||||
obj.update()
|
||||
return True
|
||||
|
||||
##@brief This class represent a data_handler for single reference to another object
|
||||
|
|
@ -285,6 +344,7 @@ class SingleRef(Reference):
|
|||
|
||||
|
||||
##@brief This class represent a data_handler for multiple references to another object
|
||||
#@ingroup lodel2_datahandlers
|
||||
#
|
||||
# The fields using this data handlers are like SingleRef but can store multiple references in one field
|
||||
# @note for the moment split on ',' chars
|
||||
|
|
@ -297,22 +357,28 @@ class MultipleRef(Reference):
|
|||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
def _check_data_value(self, value):
|
||||
def check_data_value(self, value):
|
||||
value, expt = super().check_data_value(value)
|
||||
if expt is not None:
|
||||
#error in parent
|
||||
return value, expt
|
||||
elif value is None:
|
||||
#none value
|
||||
return value, expt
|
||||
|
||||
expt = None
|
||||
|
||||
if isinstance(value, str):
|
||||
value, expt = super()._check_data_value(value)
|
||||
elif not hasattr(value, '__iter__'):
|
||||
return None, FieldValidationError("MultipleRef has to be an iterable or a string")
|
||||
return None, FieldValidationError("MultipleRef has to be an iterable or a string, '%s' found" % value)
|
||||
if self.max_item is not None:
|
||||
if self.max_item < len(value):
|
||||
return None, FieldValidationError("Too many items")
|
||||
return value, expt
|
||||
|
||||
def check_data_consistency(self, emcomponent, fname, datas):
|
||||
return True
|
||||
|
||||
def construct_data(self, emcomponent, fname, datas, cur_value):
|
||||
cur_value = super().construct_data(emcomponent, fname, datas, cur_value)
|
||||
if cur_value == 'None' or cur_value is None or cur_value == '':
|
||||
return None
|
||||
emcomponent_fields = emcomponent.fields()
|
||||
|
|
@ -336,14 +402,14 @@ class MultipleRef(Reference):
|
|||
l_value = None
|
||||
|
||||
if l_value is not None:
|
||||
if self.back_ref is not None:
|
||||
br_class = self.back_ref[0]
|
||||
if self.back_reference is not None:
|
||||
br_class = self.back_reference[0]
|
||||
for br_id in l_value:
|
||||
query_filters = list()
|
||||
query_filters.append((br_class.uid_fieldname()[0], '=', br_id))
|
||||
br_obj = br_class.get(query_filters)
|
||||
if len(br_obj) != 0:
|
||||
br_list = br_obj[0].data(self.back_ref[1])
|
||||
br_list = br_obj[0].data(self.back_reference[1])
|
||||
if br_list is None:
|
||||
br_list = list()
|
||||
if br_id not in br_list:
|
||||
|
|
@ -355,14 +421,15 @@ class MultipleRef(Reference):
|
|||
# @param emcomponent EmComponent : An EmComponent child class instance
|
||||
# @param fname : the field name
|
||||
# @param datas dict : dict storing fields values
|
||||
"""
|
||||
def make_consistency(self, emcomponent, fname, datas):
|
||||
dh = emcomponent.field(fname)
|
||||
|
||||
logger.info('Warning : multiple uid capabilities are broken here')
|
||||
uid = datas[emcomponent.uid_fieldname()[0]]
|
||||
if self.back_ref is not None:
|
||||
target_class = self.back_ref[0]
|
||||
target_field = self.back_ref[1]
|
||||
if self.back_reference is not None:
|
||||
target_class = self.back_reference[0]
|
||||
target_field = self.back_reference[1]
|
||||
target_uidfield = target_class.uid_fieldname()[0]
|
||||
|
||||
l_value = datas[fname]
|
||||
|
|
@ -382,8 +449,10 @@ class MultipleRef(Reference):
|
|||
l_uids_ref.append(uid)
|
||||
obj[0].set_data(target_field, l_uids_ref)
|
||||
obj[0].update()
|
||||
"""
|
||||
|
||||
## @brief Class designed to handle datas access will fieldtypes are constructing datas
|
||||
#@ingroup lodel2_datahandlers
|
||||
#
|
||||
# This class is designed to allow automatic scheduling of construct_data calls.
|
||||
#
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ build its content'
|
|||
self._format_string = format_string
|
||||
super().__init__(internal='automatic',**kwargs)
|
||||
|
||||
def construct_data(self, emcomponent, fname, datas, cur_value):
|
||||
def _construct_data(self, emcomponent, fname, datas, cur_value):
|
||||
ret = self._format_string % tuple(
|
||||
datas[fname] for fname in self._field_list)
|
||||
if len(ret) > self.max_length:
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ class DateTime(DataField):
|
|||
error = ValueError("Tue value has to be a string or a datetime")
|
||||
return value, error
|
||||
|
||||
def construct_data(self, emcomponent, fname, datas, cur_value):
|
||||
def _construct_data(self, emcomponent, fname, datas, cur_value):
|
||||
if (self.now_on_create and cur_value is None) or self.now_on_update:
|
||||
return datetime.datetime.now()
|
||||
return cur_value
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ class List(MultipleRef):
|
|||
# @param value *
|
||||
# @return tuple(value, exception)
|
||||
def _check_data_value(self, value):
|
||||
print("LIST check_datavalue")
|
||||
if isinstance(value, list) or isinstance(value, str):
|
||||
val, expt = super()._check_data_value(value)
|
||||
else:
|
||||
|
|
@ -30,9 +31,6 @@ class List(MultipleRef):
|
|||
|
||||
return val, expt
|
||||
|
||||
def construct_data(self, emcomponent, fname, datas, cur_value):
|
||||
return super().construct_data(emcomponent, fname, datas, cur_value)
|
||||
|
||||
##@brief Child class of MultipleRef where references are represented in the form of a python set
|
||||
class Set(MultipleRef):
|
||||
|
||||
|
|
@ -53,9 +51,6 @@ class Set(MultipleRef):
|
|||
return None, FieldValidationError("Set or string expected for a set field")
|
||||
return val, expt
|
||||
|
||||
def construct_data(self, emcomponent, fname, datas, cur_value):
|
||||
return super().construct_data(emcomponent, fname, datas, cur_value)
|
||||
|
||||
##@brief Child class of MultipleRef where references are represented in the form of a python dict
|
||||
class Map(MultipleRef):
|
||||
|
||||
|
|
@ -77,46 +72,6 @@ class Map(MultipleRef):
|
|||
None if isinstance(expt, Exception) else value,
|
||||
expt)
|
||||
|
||||
def construct_data(self, emcomponent, fname, datas, cur_value):
|
||||
logger.info('WARNING : not well implemented...list are stored...not dict')
|
||||
if cur_value == 'None' or cur_value is None or cur_value == '':
|
||||
return None
|
||||
emcomponent_fields = emcomponent.fields()
|
||||
data_handler = None
|
||||
if fname in emcomponent_fields:
|
||||
data_handler = emcomponent_fields[fname]
|
||||
u_fname = emcomponent.uid_fieldname()
|
||||
uidtype = emcomponent.field(u_fname[0]) if isinstance(u_fname, list) else emcomponent.field(u_fname)
|
||||
|
||||
if isinstance(cur_value, str):
|
||||
value = cur_value.split(',')
|
||||
l_value = [uidtype.cast_type(uid) for uid in value]
|
||||
elif isinstance(cur_value, list):
|
||||
l_value = list()
|
||||
for value in cur_value:
|
||||
if isinstance(value,uidtype.cast_type):
|
||||
l_value.append(value)
|
||||
else:
|
||||
raise ValueError("The items must be of the same type, string or %s" % (emcomponent.__name__))
|
||||
else:
|
||||
l_value = None
|
||||
|
||||
if l_value is not None:
|
||||
if self.back_ref is not None:
|
||||
br_class = self.back_ref[0]
|
||||
for br_id in l_value:
|
||||
query_filters = list()
|
||||
query_filters.append((br_class.uid_fieldname()[0], '=', br_id))
|
||||
br_obj = br_class.get(query_filters)
|
||||
if len(br_obj) != 0:
|
||||
br_list = br_obj[0].data(self.back_ref[1])
|
||||
if br_list is None:
|
||||
br_list = list()
|
||||
if br_id not in br_list:
|
||||
br_list.append(br_id)
|
||||
logger.info('The referenced object has to be updated')
|
||||
return l_value
|
||||
|
||||
##@brief This Reference class is designed to handler hierarchy with some constraint
|
||||
class Hierarch(MultipleRef):
|
||||
|
||||
|
|
@ -137,6 +92,3 @@ class Hierarch(MultipleRef):
|
|||
else:
|
||||
return None, FieldValidationError("Set or string expected for a set field")
|
||||
return val, expt
|
||||
|
||||
def construct_data(self, emcomponent, fname, datas, cur_value):
|
||||
return super().construct_data(emcomponent, fname, datas, cur_value)
|
||||
|
|
@ -474,8 +474,6 @@ class LeInsertQuery(LeQuery):
|
|||
def _query(self, datas):
|
||||
datas = self._target_class.prepare_datas(datas, True, False)
|
||||
id_inserted = self._rw_datasource.insert(self._target_class,datas)
|
||||
# To put in a hook ??
|
||||
self._target_class.make_consistency(datas=res_datas)
|
||||
return id_inserted
|
||||
"""
|
||||
## @brief Implements an insert query operation, with multiple insertions
|
||||
|
|
@ -566,8 +564,6 @@ target to LeUpdateQuery constructor"
|
|||
res = self._rw_datasource.update(
|
||||
self._target_class, filters, [],
|
||||
res_datas)
|
||||
# To put in a hook ??
|
||||
self._target_class.make_consistency(datas=res_datas)
|
||||
return res
|
||||
|
||||
## @brief Execute the update query
|
||||
|
|
|
|||
|
|
@ -105,7 +105,8 @@ class LeQueryDatasourceTestCase(unittest.TestCase):
|
|||
[(('alias', {cls: 'firstname'}), '=', 'foo')])
|
||||
self.check_nocall(read = False, exclude = ['delete'])
|
||||
self.check_nocall(read = True)
|
||||
|
||||
|
||||
@unittest.skip("Waiting references checks stack implementation")
|
||||
def test_insert(self):
|
||||
""" Testing LeInsertQuery mocking datasource """
|
||||
cls = self.dyncode['Person']
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue