1
0
Fork 0
mirror of https://github.com/yweber/lodel2.git synced 2026-02-14 13:52:02 +01:00

Merge branch 'lodel2-datahandlers'

This commit is contained in:
prieto 2017-02-17 09:28:12 +01:00
commit 5b09492048
39 changed files with 1173 additions and 1036 deletions

View file

@ -12,6 +12,8 @@ AC_CONFIG_FILES([Makefile \
lodel/leapi/datahandlers/Makefile \
lodel/plugin/Makefile \
lodel/settings/Makefile \
lodel/validator/Makefile \
lodel/mlnamedobject/Makefile \
lodel/utils/Makefile \
progs/Makefile \
progs/slim/Makefile \

View file

@ -691,7 +691,7 @@ user.new_field(
group = user_group, data_handler = 'password', internal = False)
#em.save('xmlfile', filename = 'examples/em_test.xml')
em.save('xmlfile', filename = 'editorial_models/em_simple.xml')
pickle_file = 'examples/em_simple.pickle'
em.save('picklefile', filename = pickle_file)
print("Output written in %s" % pickle_file)

View file

@ -1,4 +1,4 @@
SUBDIRS=auth editorial_model leapi plugin settings utils plugins
SUBDIRS=auth editorial_model leapi plugin settings utils plugins validator mlnamedobject
EXTRA_DIST = plugins
lodel_PYTHON = *.py
CLEANFILES = buildconf.py

View file

@ -1,6 +1,6 @@
#-*- coding: utf-8 -*-
##@package lodel.editorial_model.components
# @package lodel.editorial_model.components
#@brief Defines all @ref lodel2_em "EM" components
#@ingroup lodel2_em
@ -12,27 +12,29 @@ import hashlib
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.utils.mlstring': ['MlString'],
'lodel.mlnamedobject.mlnamedobject': ['MlNamedObject'],
'lodel.settings': ['Settings'],
'lodel.editorial_model.exceptions': ['EditorialModelError', 'assert_edit'],
'lodel.leapi.leobject': ['CLASS_ID_FIELDNAME']})
##@brief Abstract class to represent editorial model components
# @brief Abstract class to represent editorial model components
# @see EmClass EmField
# @todo forbid '.' in uid
#@ingroup lodel2_em
class EmComponent(object):
##@brief Instanciate an EmComponent
class EmComponent(MlNamedObject):
# @brief Instanciate an EmComponent
# @param uid str : uniq identifier
# @param display_name MlString|str|dict : component display_name
# @param help_text MlString|str|dict : help_text
def __init__(self, uid, display_name = None, help_text = None, group = None):
def __init__(self, uid, display_name=None, help_text=None, group=None):
if self.__class__ == EmComponent:
raise NotImplementedError('EmComponent is an abstract class')
self.uid = uid
self.display_name = None if display_name is None else MlString(display_name)
self.help_text = None if help_text is None else MlString(help_text)
self.group = group
super().__init__(display_name, help_text)
def __str__(self):
if self.display_name is None:
@ -42,34 +44,34 @@ class EmComponent(object):
def d_hash(self):
m = hashlib.md5()
for data in (
self.uid,
'NODISPNAME' if self.display_name is None else str(self.display_name.d_hash()),
'NOHELP' if self.help_text is None else str(self.help_text.d_hash()),
'NOGROUP' if self.group is None else str(self.group.d_hash()),
self.uid,
'NODISPNAME' if self.display_name is None else str(self.display_name.d_hash()),
'NOHELP' if self.help_text is None else str(self.help_text.d_hash()),
'NOGROUP' if self.group is None else str(self.group.d_hash()),
):
m.update(bytes(data, 'utf-8'))
return int.from_bytes(m.digest(), byteorder='big')
##@brief Handles editorial model objects classes
# @brief Handles editorial model objects classes
#@ingroup lodel2_em
class EmClass(EmComponent):
##@brief Instanciate a new EmClass
# @brief Instanciate a new EmClass
#@param uid str : uniq identifier
#@param display_name MlString|str|dict : component display_name
#@param abstract bool : set the class as asbtract if True
#@param pure_abstract bool : if True the EmClass will not be represented in
#leapi dyncode
# leapi dyncode
#@param parents list: parent EmClass list or uid list
#@param help_text MlString|str|dict : help_text
#@param datasources str|tuple|list : The datasource name ( see
#@ref lodel2_datasources ) or two names (first is read_only datasource the
#second is read write)
# second is read write)
def __init__(
self, uid, display_name = None, help_text = None, abstract = False,
parents = None, group = None, pure_abstract = False,
datasources = 'default'):
self, uid, display_name=None, help_text=None, abstract=False,
parents=None, group=None, pure_abstract=False,
datasources='default'):
super().__init__(uid, display_name, help_text, group)
self.abstract = bool(abstract)
@ -85,11 +87,12 @@ class EmClass(EmComponent):
parents = [parents]
for parent in parents:
if not isinstance(parent, EmClass):
raise ValueError("<class EmClass> expected in parents list, but %s found" % type(parent))
raise ValueError(
"<class EmClass> expected in parents list, but %s found" % type(parent))
else:
parents = list()
self.parents = parents
##@brief Stores EmFields instances indexed by field uid
# @brief Stores EmFields instances indexed by field uid
self.__fields = dict()
self.group = group
@ -98,36 +101,36 @@ class EmClass(EmComponent):
else:
group.add_components([self])
#Adding common field
# Adding common field
if not self.abstract:
self.new_field(
CLASS_ID_FIELDNAME,
display_name = {
display_name={
'eng': "LeObject subclass identifier",
'fre': "Identifiant de la class fille de LeObject"},
help_text = {
help_text={
'eng': "Allow to create instance of the good class when\
fetching arbitrary datas from DB"},
data_handler = 'LeobjectSubclassIdentifier',
internal = True,
group = group)
data_handler='LeobjectSubclassIdentifier',
internal=True,
group=group)
##@brief Property that represent a dict of all fields (the EmField defined in this class and all its parents)
# @brief Property that represent a dict of all fields (the EmField defined in this class and all its parents)
# @todo use Settings.editorialmodel.groups to determine wich fields should be returned
@property
def __all_fields(self):
res = dict()
for pfields in [ p.__all_fields for p in self.parents]:
for pfields in [p.__all_fields for p in self.parents]:
res.update(pfields)
res.update(self.__fields)
return res
##@brief RO access to datasource attribute
# @brief RO access to datasource attribute
@property
def datasource(self):
return self.__datasource
##@brief Return the list of all dependencies
# @brief Return the list of all dependencies
#
# Reccursive parents listing
@property
@ -140,29 +143,29 @@ class EmClass(EmComponent):
res |= parent.parents_recc
return res
##@brief EmField getter
# @brief EmField getter
# @param uid None | str : If None returns an iterator on EmField instances else return an EmField instance
# @param no_parents bool : If True returns only fields defined is this class and not the one defined in parents classes
# @return A list on EmFields instances (if uid is None) else return an EmField instance
# @todo use Settings.editorialmodel.groups to determine wich fields should be returned
def fields(self, uid = None, no_parents = False):
def fields(self, uid=None, no_parents=False):
fields = self.__fields if no_parents else self.__all_fields
try:
return list(fields.values()) if uid is None else fields[uid]
except KeyError:
raise EditorialModelError("No such EmField '%s'" % uid)
##@brief Keep in __fields only fields contained in active groups
# @brief Keep in __fields only fields contained in active groups
def _set_active_fields(self, active_groups):
if not Settings.editorialmodel.editormode:
active_fields = []
for grp_name, agrp in active_groups.items():
active_fields += [ emc for emc in agrp.components()
if isinstance(emc, EmField)]
self.__fields = { fname:fdh for fname, fdh in self.__fields.items()
if fdh in active_fields }
active_fields += [emc for emc in agrp.components()
if isinstance(emc, EmField)]
self.__fields = {fname: fdh for fname, fdh in self.__fields.items()
if fdh in active_fields}
##@brief Add a field to the EmClass
# @brief Add a field to the EmClass
# @param emfield EmField : an EmField instance
# @warning do not add an EmField allready in another class !
# @throw EditorialModelException if an EmField with same uid allready in this EmClass (overwritting allowed from parents)
@ -170,16 +173,18 @@ class EmClass(EmComponent):
def add_field(self, emfield):
assert_edit()
if emfield.uid in self.__fields:
raise EditorialModelError("Duplicated uid '%s' for EmField in this class ( %s )" % (emfield.uid, self))
raise EditorialModelError(
"Duplicated uid '%s' for EmField in this class ( %s )" % (emfield.uid, self))
# Incomplete field override check
if emfield.uid in self.__all_fields:
parent_field = self.__all_fields[emfield.uid]
if not emfield.data_handler_instance.can_override(parent_field.data_handler_instance):
raise AttributeError("'%s' field override a parent field, but data_handles are not compatible" % emfield.uid)
raise AttributeError(
"'%s' field override a parent field, but data_handles are not compatible" % emfield.uid)
self.__fields[emfield.uid] = emfield
return emfield
##@brief Create a new EmField and add it to the EmClass
# @brief Create a new EmField and add it to the EmClass
# @param data_handler str : A DataHandler name
# @param uid str : the EmField uniq id
# @param **field_kwargs : EmField constructor parameters ( see @ref EmField.__init__() )
@ -189,7 +194,7 @@ class EmClass(EmComponent):
def d_hash(self):
m = hashlib.md5()
payload = str(super().d_hash()) + ("1" if self.abstract else "0")
payload = str(super().d_hash()) + ("1" if self.abstract else "0")
for p in sorted(self.parents):
payload += str(p.d_hash())
@ -209,47 +214,47 @@ class EmClass(EmComponent):
abstract = 'PureAbstract'
else:
abstract = 'Abstract'
return "<class %s EmClass uid=%s>" % (abstract, repr(self.uid) )
return "<class %s EmClass uid=%s>" % (abstract, repr(self.uid))
##@brief Handles editorial model classes fields
# @brief Handles editorial model classes fields
#@ingroup lodel2_em
class EmField(EmComponent):
##@brief Instanciate a new EmField
# @brief Instanciate a new EmField
# @param uid str : uniq identifier
# @param display_name MlString|str|dict : field display_name
# @param data_handler str : A DataHandler name
# @param help_text MlString|str|dict : help text
# @param group EmGroup :
# @param **handler_kwargs : data handler arguments
def __init__(self, uid, data_handler, em_class = None, display_name = None, help_text = None, group = None, **handler_kwargs):
def __init__(self, uid, data_handler, em_class=None, display_name=None, help_text=None, group=None, **handler_kwargs):
from lodel.leapi.datahandlers.base_classes import DataHandler
super().__init__(uid, display_name, help_text, group)
##@brief The data handler name
# @brief The data handler name
self.data_handler_name = data_handler
##@brief The data handler class
# @brief The data handler class
self.data_handler_cls = DataHandler.from_name(data_handler)
##@brief The data handler instance associated with this EmField
# @brief The data handler instance associated with this EmField
self.data_handler_instance = self.data_handler_cls(**handler_kwargs)
##@brief Stores data handler instanciation options
# @brief Stores data handler instanciation options
self.data_handler_options = handler_kwargs
##@brief Stores the emclass that contains this field (set by EmClass.add_field() method)
# @brief Stores the emclass that contains this field (set by EmClass.add_field() method)
self._emclass = em_class
if self._emclass is None:
warnings.warn("No EmClass for field %s" %uid)
warnings.warn("No EmClass for field %s" % uid)
if group is None:
warnings.warn("No EmGroup for field %s" % uid)
else:
group.add_components([self])
##@brief Returns data_handler_name attribute
# @brief Returns data_handler_name attribute
def get_data_handler_name(self):
return copy.copy(self.data_handler_name)
##@brief Returns data_handler_cls attribute
# @brief Returns data_handler_cls attribute
def get_data_handler_cls(self):
return copy.copy(selfdata_handler_cls)
return copy.copy(self.data_handler_cls)
##@brief Returne the uid of the emclass which contains this field
def get_emclass_uid(self):
@ -259,44 +264,45 @@ class EmField(EmComponent):
# @todo Complete the hash when data handlers becomes available
def d_hash(self):
return int.from_bytes(hashlib.md5(
bytes(
"%s%s%s" % ( super().d_hash(),
self.data_handler_name,
self.data_handler_options),
'utf-8')
bytes(
"%s%s%s" % (super().d_hash(),
self.data_handler_name,
self.data_handler_options),
'utf-8')
).digest(), byteorder='big')
##@brief Handles functionnal group of EmComponents
# @brief Handles functionnal group of EmComponents
#@ingroup lodel2_em
class EmGroup(object):
##@brief Create a new EmGroup
class EmGroup(MlNamedObject):
# @brief Create a new EmGroup
# @note you should NEVER call the constructor yourself. Use Model.add_group instead
# @param uid str : Uniq identifier
# @param depends list : A list of EmGroup dependencies
# @param display_name MlString|str :
# @param help_text MlString|str :
def __init__(self, uid, depends = None, display_name = None, help_text = None):
def __init__(self, uid, depends=None, display_name=None, help_text=None):
self.uid = uid
##@brief Stores the list of groups that depends on this EmGroup indexed by uid
# @brief Stores the list of groups that depends on this EmGroup indexed by uid
self.required_by = dict()
##@brief Stores the list of dependencies (EmGroup) indexed by uid
# @brief Stores the list of dependencies (EmGroup) indexed by uid
self.require = dict()
##@brief Stores the list of EmComponent instances contained in this group
# @brief Stores the list of EmComponent instances contained in this group
self.__components = set()
super().__init__(display_name, help_text)
self.display_name = None if display_name is None else MlString(display_name)
self.help_text = None if help_text is None else MlString(help_text)
if depends is not None:
for grp in depends:
if not isinstance(grp, EmGroup):
raise ValueError("EmGroup expected in depends argument but %s found" % grp)
self.add_dependencie(grp)
##@brief Returns EmGroup dependencie
# @brief Returns EmGroup dependencie
# @param recursive bool : if True return all dependencies and their dependencies
# @return a dict of EmGroup identified by uid
def dependencies(self, recursive = False):
def dependencies(self, recursive=False):
res = copy.copy(self.require)
if not recursive:
return res
@ -309,10 +315,10 @@ class EmGroup(object):
res[new_dep.uid] = new_dep
return res
##@brief Returns EmGroup applicants
# @brief Returns EmGroup applicants
# @param recursive bool : if True return all dependencies and their dependencies
# @returns a dict of EmGroup identified by uid
def applicants(self, recursive = False):
def applicants(self, recursive=False):
res = copy.copy(self.required_by)
if not recursive:
return res
@ -325,28 +331,30 @@ class EmGroup(object):
res[new_app.uid] = new_app
return res
##@brief Returns EmGroup components
# @brief Returns EmGroup components
# @returns a copy of the set of components
def components(self):
return (self.__components).copy()
##@brief Returns EmGroup display_name
# @brief Returns EmGroup display_name
# @param lang str | None : If None return default lang translation
# @returns None if display_name is None, a str for display_name else
def get_display_name(self, lang=None):
name=self.display_name
if name is None : return None
return name.get(lang);
name = self.display_name
if name is None:
return None
return name.get(lang)
##@brief Returns EmGroup help_text
# @brief Returns EmGroup help_text
# @param lang str | None : If None return default lang translation
# @returns None if display_name is None, a str for display_name else
def get_help_text(self, lang=None):
help=self.help_text
if help is None : return None
return help.get(lang);
help = self.help_text
if help is None:
return None
return help.get(lang)
##@brief Add components in a group
# @brief Add components in a group
# @param components list : EmComponent instances list
def add_components(self, components):
assert_edit()
@ -357,10 +365,11 @@ class EmGroup(object):
msg %= (component, self)
warnings.warn(msg)
elif not isinstance(component, EmClass):
raise EditorialModelError("Expecting components to be a list of EmComponent, but %s found in the list" % type(component))
raise EditorialModelError(
"Expecting components to be a list of EmComponent, but %s found in the list" % type(component))
self.__components |= set(components)
##@brief Add a dependencie
# @brief Add a dependencie
# @param em_group EmGroup|iterable : an EmGroup instance or list of instance
def add_dependencie(self, grp):
assert_edit()
@ -368,7 +377,8 @@ class EmGroup(object):
for group in grp:
self.add_dependencie(group)
return
except TypeError: pass
except TypeError:
pass
if grp.uid in self.require:
return
@ -377,7 +387,7 @@ class EmGroup(object):
self.require[grp.uid] = grp
grp.required_by[self.uid] = self
##@brief Add a applicant
# @brief Add a applicant
# @param em_group EmGroup|iterable : an EmGroup instance or list of instance
# Useless ???
def add_applicant(self, grp):
@ -386,7 +396,8 @@ class EmGroup(object):
for group in grp:
self.add_applicant(group)
return
except TypeError: pass
except TypeError:
pass
if grp.uid in self.required_by:
return
@ -395,17 +406,17 @@ class EmGroup(object):
self.required_by[grp.uid] = grp
grp.require[self.uid] = self
##@brief Search for circular dependencie
# @brief Search for circular dependencie
# @return True if circular dep found else False
def __circular_dependencie(self, new_dep):
return self.uid in new_dep.dependencies(True)
##@brief Search for circular applicant
# @brief Search for circular applicant
# @return True if circular app found else False
def __circular_applicant(self, new_app):
return self.uid in new_app.applicants(True)
##@brief Fancy string representation of an EmGroup
# @brief Fancy string representation of an EmGroup
# @return a string
def __str__(self):
if self.display_name is None:
@ -416,9 +427,9 @@ class EmGroup(object):
def d_hash(self):
payload = "%s%s%s" % (
self.uid,
'NODNAME' if self.display_name is None else self.display_name.d_hash(),
'NOHELP' if self.help_text is None else self.help_text.d_hash()
self.uid,
'NODNAME' if self.display_name is None else self.display_name.d_hash(),
'NOHELP' if self.help_text is None else self.help_text.d_hash()
)
for recurs in (False, True):
deps = self.dependencies(recurs)
@ -427,11 +438,11 @@ class EmGroup(object):
for req_by_uid in self.required_by:
payload += req_by_uid
return int.from_bytes(
bytes(payload, 'utf-8'),
byteorder = 'big'
bytes(payload, 'utf-8'),
byteorder='big'
)
##@brief Complete string representation of an EmGroup
# @brief Complete string representation of an EmGroup
# @return a string
def __repr__(self):
return "<class EmGroup '%s' depends : [%s]>" % (self.uid, ', '.join([duid for duid in self.dependencies(False)]) )
return "<class EmGroup '%s' depends : [%s]>" % (self.uid, ', '.join([duid for duid in self.dependencies(False)]))

View file

@ -7,6 +7,7 @@ import copy
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.utils.mlstring': ['MlString'],
'lodel.mlnamedobject.mlnamedobject': ['MlNamedObject'],
'lodel.logger': 'logger',
'lodel.settings': ['Settings'],
'lodel.settings.utils': ['SettingsError'],
@ -14,29 +15,34 @@ LodelContext.expose_modules(globals(), {
'lodel.editorial_model.components': ['EmClass', 'EmField', 'EmGroup']})
##@brief Describe an editorial model
# @brief Describe an editorial model
#@ingroup lodel2_em
class EditorialModel(object):
class EditorialModel(MlNamedObject):
##@brief Create a new editorial model
# @brief Create a new editorial model
# @param name MlString|str|dict : the editorial model name
# @param description MlString|str|dict : the editorial model description
def __init__(self, name, description = None):
def __init__(self, name, description=None, display_name=None, help_text=None):
self.name = MlString(name)
self.description = MlString(description)
##@brief Stores all groups indexed by id
# @brief Stores all groups indexed by id
self.__groups = dict()
##@brief Stores all classes indexed by id
# @brief Stores all classes indexed by id
self.__classes = dict()
## @brief Stores all activated groups indexed by id
#  @brief Stores all activated groups indexed by id
self.__active_groups = dict()
## @brief Stores all activated classes indexed by id
#  @brief Stores all activated classes indexed by id
self.__active_classes = dict()
self.__set_actives()
if display_name is None:
display_name = name
if help_text is None:
help_text = description
super().__init__(display_name, help_text)
##@brief EmClass uids accessor
# @brief EmClass uids accessor
#@return a dict of emclasses
def all_classes(self, uid = None):
def all_classes(self, uid=None):
if uid is None:
return copy.copy(self.__classes)
else:
@ -45,7 +51,7 @@ class EditorialModel(object):
except KeyError:
raise EditorialModelException("EmClass not found : '%s'" % uid)
def all_classes_ref(self, uid = None):
def all_classes_ref(self, uid=None):
if uid is None:
return self.__classes
else:
@ -54,15 +60,14 @@ class EditorialModel(object):
except KeyError:
raise EditorialModelException("EmGroup not found : '%s'" % uid)
##@brief active EmClass uids accessor
# @brief active EmClass uids accessor
#@return a list of class uids
def active_classes_uids(self):
return list(self.__active_classes.keys())
return list(self.__active_classes.keys())
##@brief EmGroups accessor
# @brief EmGroups accessor
#@return a dict of groups
def all_groups(self, uid = None):
def all_groups(self, uid=None):
if uid is None:
return copy.copy(self.__groups)
else:
@ -71,9 +76,9 @@ class EditorialModel(object):
except KeyError:
raise EditorialModelException("EmGroup not found : '%s'" % uid)
##@brief EmGroups accessor
# @brief EmGroups accessor
#@return a dict of groups
def all_groups_ref(self, uid = None):
def all_groups_ref(self, uid=None):
if uid is None:
return self.__groups
else:
@ -82,25 +87,25 @@ class EditorialModel(object):
except KeyError:
raise EditorialModelException("EmGroup not found : '%s'" % uid)
##@brief active EmClass uids accessor
# @brief active EmClass uids accessor
#@return a list of class uids
def active_groups_uids(self):
return list(self.__active_groups.keys())
return list(self.__active_groups.keys())
##@brief EmClass accessor
# @brief EmClass accessor
#@param uid None | str : give this argument to get a specific EmClass
#@return if uid is given returns an EmClass else returns an EmClass
# iterator
#@todo use Settings.editorialmodel.groups to determine wich classes should
# be returned
def classes(self, uid = None):
def classes(self, uid=None):
try:
return self.__elt_getter( self.__active_classes,
uid)
return self.__elt_getter(self.__active_classes,
uid)
except KeyError:
raise EditorialModelException("EmClass not found : '%s'" % uid)
##@brief EmClass child list accessor
# @brief EmClass child list accessor
#@param uid str : the EmClass uid
#@return a set of EmClass
def get_class_childs(self, uid):
@ -111,24 +116,23 @@ class EditorialModel(object):
res.append(cls)
return set(res)
##@brief EmGroup getter
# @brief EmGroup getter
# @param uid None | str : give this argument to get a specific EmGroup
# @return if uid is given returns an EmGroup else returns an EmGroup iterator
def groups(self, uid = None):
def groups(self, uid=None):
try:
return self.__elt_getter( self.__active_groups,
uid)
return self.__elt_getter(self.__active_groups,
uid)
except KeyError:
raise EditorialModelException("EmGroup not found : '%s'" % uid)
##@brief Private getter for __groups or __classes
# @brief Private getter for __groups or __classes
# @see classes() groups()
def __elt_getter(self, elts, uid):
return list(elts.values()) if uid is None else elts[uid]
##@brief Update the EditorialModel.__active_groups and
#EditorialModel.__active_classes attibutes
# @brief Update the EditorialModel.__active_groups and
# EditorialModel.__active_classes attibutes
def __set_actives(self):
if Settings.editorialmodel.editormode:
logger.warning("All EM groups active because editormode in ON")
@ -136,7 +140,7 @@ class EditorialModel(object):
self.__active_groups = self.__groups
self.__active_classes = self.__classes
else:
#determine groups first
# determine groups first
self.__active_groups = dict()
self.__active_classes = dict()
for agrp in Settings.editorialmodel.groups:
@ -154,12 +158,12 @@ class EditorialModel(object):
for clsname, acls in self.__active_classes.items():
acls._set_active_fields(self.__active_groups)
##@brief EmField getter
# @brief EmField getter
# @param uid str : An EmField uid represented by "CLASSUID.FIELDUID"
# @return Fals or an EmField instance
#
# @todo delete it, useless...
def field(self, uid = None):
def field(self, uid=None):
spl = uid.split('.')
if len(spl) != 2:
raise ValueError("Malformed EmField identifier : '%s'" % uid)
@ -175,7 +179,7 @@ class EditorialModel(object):
pass
return False
##@brief Add a class to the editorial model
# @brief Add a class to the editorial model
# @param emclass EmClass : the EmClass instance to add
# @return emclass
def add_class(self, emclass):
@ -187,7 +191,7 @@ class EditorialModel(object):
self.__classes[emclass.uid] = emclass
return emclass
##@brief Add a group to the editorial model
# @brief Add a group to the editorial model
# @param emgroup EmGroup : the EmGroup instance to add
# @return emgroup
def add_group(self, emgroup):
@ -199,7 +203,7 @@ class EditorialModel(object):
self.__groups[emgroup.uid] = emgroup
return emgroup
##@brief Add a new EmClass to the editorial model
# @brief Add a new EmClass to the editorial model
#@param uid str : EmClass uid
#@param **kwargs : EmClass constructor options (
# see @ref lodel.editorial_model.component.EmClass.__init__() )
@ -207,7 +211,7 @@ class EditorialModel(object):
assert_edit()
return self.add_class(EmClass(uid, **kwargs))
##@brief Add a new EmGroup to the editorial model
# @brief Add a new EmGroup to the editorial model
#@param uid str : EmGroup uid
#@param *kwargs : EmGroup constructor keywords arguments (
# see @ref lodel.editorial_model.component.EmGroup.__init__() )
@ -215,7 +219,7 @@ class EditorialModel(object):
assert_edit()
return self.add_group(EmGroup(uid, **kwargs))
##@brief Save a model
# @brief Save a model
# @param translator module : The translator module to use
# @param **translator_args
def save(self, translator, **translator_kwargs):
@ -224,13 +228,14 @@ class EditorialModel(object):
translator = self.translator_from_name(translator)
return translator.save(self, **translator_kwargs)
##@brief Raise an error if lodel is not in EM edition mode
# @brief Raise an error if lodel is not in EM edition mode
@staticmethod
def raise_if_ro():
if not Settings.editorialmodel.editormode:
raise EditorialModelError("Lodel in not in EM editor mode. The EM is in read only state")
raise EditorialModelError(
"Lodel in not in EM editor mode. The EM is in read only state")
##@brief Load a model
# @brief Load a model
# @param translator module : The translator module to use
# @param **translator_args
@classmethod
@ -241,7 +246,7 @@ class EditorialModel(object):
res.__set_actives()
return res
##@brief Return a translator module given a translator name
# @brief Return a translator module given a translator name
# @param translator_name str : The translator name
# @return the translator python module
# @throw NameError if the translator does not exists
@ -254,11 +259,11 @@ class EditorialModel(object):
raise NameError("No translator named %s")
return mod
##@brief Lodel hash
# @brief Lodel hash
def d_hash(self):
payload = "%s%s" % (
self.name,
'NODESC' if self.description is None else self.description.d_hash()
self.name,
'NODESC' if self.description is None else self.description.d_hash()
)
for guid in sorted(self.__groups):
payload += str(self.__groups[guid].d_hash())
@ -267,7 +272,6 @@ class EditorialModel(object):
payload += str(self.__classes[cuid].d_hash())
return int.from_bytes(
hashlib.md5(bytes(payload, 'utf-8')).digest(),
byteorder='big'
hashlib.md5(bytes(payload, 'utf-8')).digest(),
byteorder='big'
)

View file

@ -12,34 +12,51 @@ import warnings
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.exceptions': ['LodelException', 'LodelExceptions',
'LodelFatalError', 'DataNoneValid', 'FieldValidationError'],
'lodel.leapi.datahandlers.exceptions': ['LodelDataHandlerConsistencyException', 'LodelDataHandlerException'],
'lodel.logger': 'logger'})
'lodel.exceptions': [
'LodelException',
'LodelExceptions',
'LodelFatalError',
'DataNoneValid',
'FieldValidationError'
],
'lodel.mlnamedobject.mlnamedobject': ['MlNamedObject'],
'lodel.leapi.datahandlers.exceptions': [
'LodelDataHandlerConsistencyException',
'LodelDataHandlerException'
],
'lodel.validator.validator': [
'ValidationError'
],
'lodel.logger': 'logger',
'lodel.utils.mlstring': ['MlString']})
##@brief Base class for all data handlers
#@ingroup lodel2_datahandlers
class DataHandler(object):
## @brief Base class for all data handlers
# @ingroup lodel2_datahandlers
class DataHandler(MlNamedObject):
base_type = "type"
_HANDLERS_MODULES = ('datas_base', 'datas', 'references')
##@brief Stores the DataHandler childs classes indexed by name
## @brief Stores the DataHandler childs classes indexed by name
_base_handlers = None
##@brief Stores custom datahandlers classes indexed by name
## @brief Stores custom datahandlers classes indexed by name
# @todo do it ! (like plugins, register handlers... blablabla)
__custom_handlers = dict()
help_text = 'Generic Field Data Handler'
display_name = "Generic Field"
options_spec = dict()
options_values = dict()
##@brief List fields that will be exposed to the construct_data_method
## @brief List fields that will be exposed to the construct_data_method
_construct_datas_deps = []
directly_editable = True
##@brief constructor
## @brief constructor
#
# @param internal False | str : define whether or not a field is internal
# @param immutable bool : indicates if the fieldtype has to be defined in child classes of LeObject or if it is
# designed globally and immutable
# @param **args
# @param immutable bool : indicates if the fieldtype has to be defined in child classes of
# LeObject or if it is designed globally and immutable
# @throw NotImplementedError if it is instanciated directly
def __init__(self, **kwargs):
if self.__class__ == DataHandler:
@ -54,9 +71,30 @@ class DataHandler(object):
self.default, error = self.check_data_value(kwargs['default'])
if error:
raise error
del(kwargs['default'])
del kwargs['default']
for argname, argval in kwargs.items():
setattr(self, argname, argval)
self.check_options()
display_name = kwargs.get('display_name',MlString(self.display_name))
help_text = kwargs.get('help_text', MlString(self.help_text))
super().__init__(display_name, help_text)
## @brief Sets properly casted and checked options for the datahandler
# @raises LodelDataHandlerNotAllowedOptionException when a passed option is not in the option
# specifications of the datahandler
def check_options(self):
for option_name, option_datas in self.options_spec.items():
if option_name in self.options_values:
# There is a configured option, we check its value
try:
self.options_values[option_name] = option_datas[1].check_value(
self.options_values[option_name])
except ValueError:
pass # TODO Deal with the case where the value used for an option is invalid
else:
# This option was not configured, we get the default value from the specs
self.options_values[option_name] = option_datas[0]
## Fieldtype name
@classmethod
@ -74,15 +112,15 @@ class DataHandler(object):
def is_primary_key(self):
return self.primary_key
##@brief checks if a fieldtype is internal
## @brief checks if a fieldtype is internal
# @return bool
def is_internal(self):
return self.internal is not False
##brief check if a value can be nullable
#@param value *
#@throw DataNoneValid if value is None and nullable. LodelExceptions if not nullable
#@return value (if not None)
## @brief check if a value can be nullable
# @param value *
# @throw DataNoneValid if value is None and nullable. LodelExceptions if not nullable
# @return value (if not None)
# @return value
def _check_data_value(self, value):
if value is None:
@ -91,9 +129,9 @@ class DataHandler(object):
raise DataNoneValid("None with a nullable. This exeption is allowed")
return value
##@brief calls the data_field (defined in derived class) _check_data_value() method
#@param value *
#@return tuple (value|None, None|error) value can be cast if NoneError
## @brief calls the data_field (defined in derived class) _check_data_value() method
# @param value *
# @return tuple (value|None, None|error) value can be cast if NoneError
def check_data_value(self, value):
try:
value = self._check_data_value(value)
@ -103,7 +141,7 @@ class DataHandler(object):
return None, expt
return value, None
##@brief checks if this class can override the given data handler
## @brief checks if this class can override the given data handler
# @param data_handler DataHandler
# @return bool
def can_override(self, data_handler):
@ -111,17 +149,17 @@ class DataHandler(object):
return False
return True
##@brief Build field value
#@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
## @brief Build field value
# @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
@ -136,41 +174,41 @@ class DataHandler(object):
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
## @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
#@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
## @brief Check datas consistency
# @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
## @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
## @brief make consistency after a query
# @param emcomponent EmComponent : An EmComponent child class instance
# @param fname : the field name
# @param datas dict : dict storing fields values
@ -179,7 +217,7 @@ class DataHandler(object):
def make_consistency(self, emcomponent, fname, datas):
pass
##@brief This method is use by plugins to register new data handlers
## @brief This method is use by plugins to register new data handlers
@classmethod
def register_new_handler(cls, name, data_handler):
if not inspect.isclass(data_handler):
@ -188,7 +226,7 @@ class DataHandler(object):
raise ValueError("A data handler HAS TO be a child class of DataHandler")
cls.__custom_handlers[name] = data_handler
##@brief Load all datahandlers
## @brief Load all datahandlers
@classmethod
def load_base_handlers(cls):
if cls._base_handlers is None:
@ -201,10 +239,11 @@ class DataHandler(object):
cls._base_handlers[name.lower()] = obj
return copy.copy(cls._base_handlers)
##@brief given a field type name, returns the associated python class
## @brief given a field type name, returns the associated python class
# @param fieldtype_name str : A field type name (not case sensitive)
# @return DataField child class
# @note To access custom data handlers it can be cool to prefix the handler name by plugin name for example ? (to ensure name unicity)
# @note To access custom data handlers it can be cool to prefix the handler name by plugin
# name for example ? (to ensure name unicity)
@classmethod
def from_name(cls, name):
cls.load_base_handlers()
@ -214,7 +253,24 @@ class DataHandler(object):
raise NameError("No data handlers named '%s'" % (name,))
return all_handlers[name]
##@brief Return the module name to import in order to use the datahandler
# @brief List all datahandlers
# @return a dict with, display_name for keys, and a dict for value
@classmethod
def list_data_handlers(cls):
cls.load_base_handlers()
all_handlers = dict(cls._base_handlers, **cls.__custom_handlers)
list_dh = dict()
for hdl in all_handlers:
list_dh[hdl.display_name] = {'help_text' : hdl.help_text,
'nullable' : hdl.nullable, \
'internal' : hdl.internal,
'immutable' : hdl.immutable, \
'primary_key' : hdl.primary_key, \
'options' : self.options_spec}
return list_dh
## @brief Return the module name to import in order to use the datahandler
# @param data_handler_name str : Data handler name
# @return a str
@classmethod
@ -222,62 +278,64 @@ class DataHandler(object):
name = name.lower()
handler_class = cls.from_name(name)
return '{module_name}.{class_name}'.format(
module_name=handler_class.__module__,
class_name=handler_class.__name__
module_name=handler_class.__module__,
class_name=handler_class.__name__
)
##@brief __hash__ implementation for fieldtypes
## @brief __hash__ implementation for fieldtypes
def __hash__(self):
hash_dats = [self.__class__.__module__]
for kdic in sorted([k for k in self.__dict__.keys() if not k.startswith('_')]):
hash_dats.append((kdic, getattr(self, kdic)))
return hash(tuple(hash_dats))
##@brief Base class for datas data handler (by opposition with references)
#@ingroup lodel2_datahandlers
## @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
## @brief Abstract class for all references
# @ingroup lodel2_datahandlers
#
# References are fields that stores a reference to another
# editorial object
#@todo Construct data implementation : transform the data into a LeObject
#instance
# @todo Construct data implementation : transform the data into a LeObject instance
class Reference(DataHandler):
base_type = "ref"
##@brief Instanciation
## @brief Instanciation
# @param allowed_classes list | None : list of allowed em classes if None no restriction
# @param back_reference tuple | None : tuple containing (LeObject child class, fieldname)
# @param internal bool : if False, the field is not internal
# @param **kwargs : other arguments
def __init__(self, allowed_classes=None, back_reference=None, internal=False, **kwargs):
self.__allowed_classes = set() if allowed_classes is None else set(allowed_classes)
self.allowed_classes = list() if allowed_classes is None else allowed_classes # For now usefull to jinja 2
# For now usefull to jinja 2
self.allowed_classes = list() if allowed_classes is None else allowed_classes
if back_reference is not None:
if len(back_reference) != 2:
raise ValueError("A tuple (classname, fieldname) expected but got '%s'" % back_reference)
#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]))
# 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
super().__init__(internal=internal, **kwargs)
##@brief Method designed to return an empty value for this kind of
#multipleref
## @brief Method designed to return an empty value for this kind of
# multipleref
@classmethod
def empty(cls):
return None
##@brief Property that takes value of a copy of the back_reference tuple
## @brief Property that takes value of a copy of the back_reference tuple
@property
def back_reference(self):
return copy.copy(self.__back_reference)
##@brief Property that takes value of datahandler of the backreference or
#None
## @brief Property that takes value of datahandler of the backreference or
# None
@property
def back_ref_datahandler(self):
if self.__back_reference is None:
@ -288,15 +346,15 @@ class Reference(DataHandler):
def linked_classes(self):
return copy.copy(self.__allowed_classes)
##@brief Set the back reference for this field.
## @brief Set the back reference for this field.
def _set_back_reference(self, back_reference):
self.__back_reference = back_reference
##@brief Check and cast value in appropriate type
#@param value *
#@throw FieldValidationError if value is an appropriate type
#@return value
#@todo implement the check when we have LeObject uid check value
## @brief Check and cast value in appropriate type
# @param value *
# @throw FieldValidationError if value is an appropriate type
# @return value
# @todo implement the check when we have LeObject uid check value
def _check_data_value(self, value):
from lodel.leapi.leobject import LeObject
value = super()._check_data_value(value)
@ -311,13 +369,13 @@ class Reference(DataHandler):
raise FieldValidationError("Reference datahandler can not check this value %s if any allowed_class is allowed." % value)
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 check for performance issue and check logics
#@warning composed uid capabilities broken here
## @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 check for performance issue and check logics
# @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):
@ -333,21 +391,21 @@ class Reference(DataHandler):
if not target_class.is_exist(value):
logger.warning('Object referenced does not exist')
return False
#target_uidfield = target_class.uid_fieldname()[0] #multi uid broken here
#obj = target_class.get([(target_uidfield, '=', value)])
#if len(obj) == 0:
# target_uidfield = target_class.uid_fieldname()[0] #multi uid broken here
# obj = target_class.get([(target_uidfield, '=', value)])
# if len(obj) == 0:
# logger.warning('Object referenced does not exist')
# return False
return True
##@brief Utility method designed to fetch referenced objects
#@param value mixed : the field value
#@throw NotImplementedError
## @brief Utility method designed to fetch referenced objects
# @param value mixed : the field value
# @throw NotImplementedError
def get_referenced(self, value):
raise NotImplementedError
##@brief This class represent a data_handler for single reference to another object
## @brief This class represent a data_handler for single reference to another object
#
# The fields using this data handlers are like "foreign key" on another object
class SingleRef(Reference):
@ -356,18 +414,18 @@ class SingleRef(Reference):
super().__init__(allowed_classes=allowed_classes, **kwargs)
##@brief Check and cast value in appropriate type
#@param value: *
#@throw FieldValidationError if value is unappropriate or can not be cast
#@return value
## @brief Check and cast value in appropriate type
# @param value: *
# @throw FieldValidationError if value is unappropriate or can not be cast
# @return value
def _check_data_value(self, value):
value = super()._check_data_value(value)
return value
##@brief Utility method designed to fetch referenced objects
#@param value mixed : the field value
#@return A LeObject child class instance
#@throw LodelDataHandlerConsistencyException if no referenced object found
## @brief Utility method designed to fetch referenced objects
# @param value mixed : the field value
# @return A LeObject child class instance
# @throw LodelDataHandlerConsistencyException if no referenced object found
def get_referenced(self, value):
for leo_cls in self.linked_classes:
res = leo_cls.get_from_uid(value)
@ -377,30 +435,30 @@ class SingleRef(Reference):
referenced object with uid %s" % value)
##@brief This class represent a data_handler for multiple references to another object
#@ingroup lodel2_datahandlers
## @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
class MultipleRef(Reference):
##
## @brief Constructor
# @param max_item int | None : indicate the maximum number of item referenced by this field, None mean no limit
def __init__(self, max_item=None, **kwargs):
self.max_item = max_item
super().__init__(**kwargs)
##@brief Method designed to return an empty value for this kind of
#multipleref
## @brief Method designed to return an empty value for this kind of
# multipleref
@classmethod
def empty(cls):
return []
##@brief Check and cast value in appropriate type
#@param value *
#@throw FieldValidationError if value is unappropriate or can not be cast
#@return value
#@TODO Writing test error for errors when stored multiple references in one field
## @brief Check and cast value in appropriate type
# @param value *
# @throw FieldValidationError if value is unappropriate or can not be cast
# @return value
# @TODO Writing test error for errors when stored multiple references in one field
def _check_data_value(self, value):
value = DataHandler._check_data_value(self, value)
if not hasattr(value, '__iter__'):
@ -420,11 +478,11 @@ class MultipleRef(Reference):
raise FieldValidationError("MultipleRef have for invalid values [%s] :" % (",".join(error_list)))
return new_val
##@brief Utility method designed to fetch referenced objects
#@param values mixed : the field values
#@return A list of LeObject child class instance
#@throw LodelDataHandlerConsistencyException if some referenced objects
#were not found
## @brief Utility method designed to fetch referenced objects
# @param values mixed : the field values
# @return A list of LeObject child class instance
# @throw LodelDataHandlerConsistencyException if some referenced objects
# were not found
def get_referenced(self, values):
if values is None or len(values) == 0:
return list()
@ -432,18 +490,19 @@ class MultipleRef(Reference):
values = set(values)
res = list()
for leo_cls in self.linked_classes:
uidname = leo_cls.uid_fieldname()[0] #MULTIPLE UID BROKEN HERE
uidname = leo_cls.uid_fieldname()[0] # MULTIPLE UID BROKEN HERE
tmp_res = leo_cls.get(('%s in (%s)' % (uidname, ','.join(
[str(l) for l in left]))))
left ^= set(( leo.uid() for leo in tmp_res))
left ^= set((leo.uid() for leo in tmp_res))
res += tmp_res
if len(left) == 0:
return res
raise LodelDataHandlerConsistencyException("Unable to find \
some referenced objects. Following uids were not found : %s" % ','.join(left))
## @brief Class designed to handle datas access will fieldtypes are constructing datas
#@ingroup lodel2_datahandlers
# @ingroup lodel2_datahandlers
#
# This class is designed to allow automatic scheduling of construct_data calls.
#
@ -457,15 +516,15 @@ class DatasConstructor(object):
# @param datas dict : dict with field name as key and field values as value
# @param fields_handler dict : dict with field name as key and data handler instance as value
def __init__(self, leobject, datas, fields_handler):
## Stores concerned class
# Stores concerned class
self._leobject = leobject
## Stores datas and constructed datas
# Stores datas and constructed datas
self._datas = copy.copy(datas)
## Stores fieldtypes
# Stores fieldtypes
self._fields_handler = fields_handler
## Stores list of fieldname for constructed datas
# Stores list of fieldname for constructed datas
self._constructed = []
## Stores construct calls list
# Stores construct calls list
self._construct_calls = []
## @brief Implements the dict.keys() method on instance
@ -488,3 +547,30 @@ class DatasConstructor(object):
self._datas[fname] = value
warnings.warn("Setting value of an DatasConstructor instance")
## @brief Class designed to handle an option of a DataHandler
class DatahandlerOption(MlNamedObject):
## @brief instanciates a new Datahandler option object
#
# @param id str
# @param display_name MlString
# @param help_text MlString
# @param validator function
def __init__(self, id, display_name, help_text, validator):
self.__id = id
self.__validator = validator
super().__init__(display_name, help_text)
@property
def id(self):
return self.__id
## @brief checks a value corresponding to this option is valid
# @param value
# @return casted value
def check_value(self, value):
try:
return self.__validator(value)
except ValidationError:
raise ValueError()

View file

@ -7,9 +7,9 @@ from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.leapi.datahandlers.datas_base': ['Boolean', 'Integer', 'Varchar',
'DateTime', 'Text', 'File'],
'DateTime', 'Text', 'File'],
'lodel.exceptions': ['LodelException', 'LodelExceptions',
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
##@brief Data field designed to handle formated strings
@ -26,7 +26,7 @@ build its content'
def __init__(self, format_string, field_list, **kwargs):
self._field_list = field_list
self._format_string = format_string
super().__init__(internal='automatic',**kwargs)
super().__init__(internal='automatic', **kwargs)
def _construct_data(self, emcomponent, fname, datas, cur_value):
ret = self._format_string % tuple(
@ -49,7 +49,7 @@ max_length and regex'
# @param **kwargs
def __init__(self, regex='', max_length=10, **kwargs):
self.regex = regex
self.compiled_re = re.compile(regex)#trigger an error if invalid regex
self.compiled_re = re.compile(regex) # trigger an error if invalid regex
super(self.__class__, self).__init__(max_length=max_length, **kwargs)
##@brief Check and cast value in appropriate type
@ -107,6 +107,7 @@ be internal")
cls = emcomponent.__class__
return cls.__name__
##@brief Data field designed to handle concatenated fields
class Concat(FormatString):
help = 'Automatic strings concatenation'
@ -116,11 +117,11 @@ class Concat(FormatString):
# @param field_list list : List of field to use
# @param separator str
# @param **kwargs
def __init__(self, field_list, separator = ' ', **kwargs):
def __init__(self, field_list, separator=' ', **kwargs):
format_string = separator.join(['%s' for _ in field_list])
super().__init__(
format_string = format_string, field_list = field_list, **kwargs)
super().__init__(format_string=format_string,
field_list=field_list,
**kwargs)
class Password(Varchar):
@ -129,7 +130,6 @@ class Password(Varchar):
pass
class VarcharList(Varchar):
help = 'DataHandler designed to make a list out of a string.'
base_type = 'varchar'
@ -140,7 +140,6 @@ class VarcharList(Varchar):
self.delimiter = str(delimiter)
super().__init__(**kwargs)
def construct_data(self, emcomponent, fname, datas, cur_value):
result = cur_value.split(self.delimiter)
return result

View file

@ -8,32 +8,33 @@ from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.leapi.datahandlers.base_classes': ['DataField'],
'lodel.exceptions': ['LodelException', 'LodelExceptions',
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
##@brief Data field designed to handle boolean values
## @brief Data field designed to handle boolean values
class Boolean(DataField):
help = 'A basic boolean field'
base_type = 'bool'
##@brief A boolean field
## @brief A boolean field
def __init__(self, **kwargs):
#if 'check_data_value' not in kwargs:
# kwargs['check_data_value'] = self._check_data_value
super().__init__(ftype='bool', **kwargs)
##@brief Check and cast value in appropriate type
#@param value *
#@throw FieldValidationError if value is unappropriate or can not be cast
#@return value
## @brief Check and cast value in appropriate type
# @param value *
# @throw FieldValidationError if value is unappropriate or can not be cast
# @return value
def _check_data_value(self, value):
value = super()._check_data_value(value)
if not isinstance(value, bool):
raise FieldValidationError("The value '%s' is not, and will never, be a boolean" % value)
return value
##@brief Data field designed to handle integer values
## @brief Data field designed to handle integer values
class Integer(DataField):
help = 'Basic integer field'
@ -41,14 +42,14 @@ class Integer(DataField):
cast_type = int
def __init__(self, **kwargs):
super().__init__( **kwargs)
super().__init__(**kwargs)
##@brief Check and cast value in appropriate type
## @brief Check and cast value in appropriate type
# @param value *
# @param strict bool : tells if the value must be an integer or a value that can be converted into an integer
# @throw FieldValidationError if value is unappropriate or can not be cast
# @return value
def _check_data_value(self, value, strict = False):
def _check_data_value(self, value, strict=False):
value = super()._check_data_value(value)
if (strict and not isinstance(value, int)):
raise FieldValidationError("The value '%s' is not a python type integer" % value)
@ -61,19 +62,20 @@ class Integer(DataField):
raise FieldValidationError("The value '%s' is not, and will never, be an integer" % value)
return value
##@brief Data field designed to handle string
## @brief Data field designed to handle string
class Varchar(DataField):
help = 'Basic string (varchar) field. Default size is 64 characters'
base_type = 'char'
##@brief A string field
## @brief A string field
# @brief max_length int: The maximum length of this field
def __init__(self, max_length=64, **kwargs):
self.max_length = int(max_length)
super().__init__(**kwargs)
##@brief checks if this class can override the given data handler
## @brief checks if this class can override the given data handler
# @param data_handler DataHandler
# @return bool
def can_override(self, data_handler):
@ -83,25 +85,26 @@ class Varchar(DataField):
return False
return True
##@brief Check and cast value in appropriate type
#@param value *
#@throw FieldValidationError if value is unappropriate or can not be cast
#@return value
## @brief Check and cast value in appropriate type
# @param value *
# @throw FieldValidationError if value is unappropriate or can not be cast
# @return value
def _check_data_value(self, value):
value = super()._check_data_value(value)
if not isinstance(value, str):
raise FieldValidationError("The value '%s' can't be a str" % value)
if len(value) > self.max_length:
raise FieldValidationError("The value '%s' is longer than the maximum length of this field (%s)" % (value, self.max_length))
raise FieldValidationError("The value '%s' is longer than the maximum length of this field (%s)" % (value, self.max_length))
return value
##@brief Data field designed to handle date & time
## @brief Data field designed to handle date & time
class DateTime(DataField):
help = 'A datetime field. Take two boolean options now_on_update and now_on_create'
base_type = 'datetime'
##@brief A datetime field
## @brief A datetime field
# @param now_on_update bool : If true, the date is set to NOW on update
# @param now_on_create bool : If true, the date is set to NEW on creation
# @param **kwargs
@ -111,13 +114,13 @@ class DateTime(DataField):
self.datetime_format = '%Y-%m-%d' if 'format' not in kwargs else kwargs['format']
super().__init__(**kwargs)
##@brief Check and cast value in appropriate type
#@param value *
#@throw FieldValidationError if value is unappropriate or can not be cast
#@return value
## @brief Check and cast value in appropriate type
# @param value *
# @throw FieldValidationError if value is unappropriate or can not be cast
# @return value
def _check_data_value(self, value):
value = super()._check_data_value(value)
if isinstance(value,str):
if isinstance(value, str):
try:
value = datetime.datetime.fromtimestamp(time.mktime(time.strptime(value, self.datetime_format)))
except ValueError:
@ -131,7 +134,8 @@ class DateTime(DataField):
return datetime.datetime.now()
return cur_value
##@brief Data field designed to handle long string
## @brief Data field designed to handle long string
class Text(DataField):
help = 'A text field (big string)'
base_type = 'text'
@ -139,22 +143,23 @@ class Text(DataField):
def __init__(self, **kwargs):
super(self.__class__, self).__init__(ftype='text', **kwargs)
##@brief Check and cast value in appropriate type
#@param value *
#@throw FieldValidationError if value is unappropriate or can not be cast
#@return value
## @brief Check and cast value in appropriate type
# @param value *
# @throw FieldValidationError if value is unappropriate or can not be cast
# @return value
def _check_data_value(self, value):
value = super()._check_data_value(value)
if not isinstance(value, str):
raise FieldValidationError("The content passed to this Text field is not a convertible to a string")
return value
##@brief Data field designed to handle Files
## @brief Data field designed to handle Files
class File(DataField):
base_type = 'file'
##@brief a file field
## @brief a file field
# @param upload_path str : None by default
# @param **kwargs
def __init__(self, upload_path=None, **kwargs):
@ -164,4 +169,3 @@ class File(DataField):
# @todo Add here a check for the validity of the given value (should have a correct path syntax)
def _check_data_value(self, value):
return super()._check_data_value(value)

View file

@ -1,5 +1,6 @@
def LodelDataHandlerException(Exception):
class LodelDataHandlerException(Exception):
pass
def LodelDataHandlerConsistencyException(LodelDataHandlerException):
class LodelDataHandlerConsistencyException(LodelDataHandlerException):
pass

View file

@ -3,35 +3,37 @@
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.leapi.datahandlers.base_classes': ['Reference', 'MultipleRef',
'SingleRef'],
'SingleRef'],
'lodel.logger': 'logger',
'lodel.exceptions': ['LodelException', 'LodelExceptions',
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
'LodelFatalError', 'DataNoneValid',
'FieldValidationError']})
class Link(SingleRef):
pass
##@brief Child class of MultipleRef where references are represented in the form of a python list
## @brief Child class of MultipleRef where references are represented in the form of a python list
class List(MultipleRef):
##@brief instanciates a list reference
## @brief instanciates a list reference
# @param max_length int
# @param kwargs
# - allowed_classes list | None : list of allowed em classes if None no restriction
# - internal bool
def __init__(self, max_length = None, **kwargs):
def __init__(self, max_length=None, **kwargs):
super().__init__(**kwargs)
@classmethod
def empty(cls):
return list()
##@brief Check and cast value in appropriate type
#@param value *
#@throw FieldValidationError if value is unappropriate or can not be cast
#@return value
## @brief Check and cast value in appropriate type
# @param value *
# @throw FieldValidationError if value is unappropriate or can not be cast
# @return value
def _check_data_value(self, value):
value = super()._check_data_value(value)
try:
@ -39,12 +41,12 @@ class List(MultipleRef):
except Exception as e:
raise FieldValidationError("Given iterable is not castable in \
a list : %s" % e)
return value
##@brief Child class of MultipleRef where references are represented in the form of a python set
## @brief Child class of MultipleRef where references are represented in the form of a python set
class Set(MultipleRef):
##@brief instanciates a set reference
## @brief instanciates a set reference
# @param kwargs : named arguments
# - allowed_classes list | None : list of allowed em classes if None no restriction
# - internal bool : if False, the field is not internal
@ -55,10 +57,10 @@ class Set(MultipleRef):
def empty(cls):
return set()
##@brief Check and cast value in appropriate type
#@param value *
#@throw FieldValidationError if value is unappropriate or can not be cast
#@return value
## @brief Check and cast value in appropriate type
# @param value *
# @throw FieldValidationError if value is unappropriate or can not be cast
# @return value
def _check_data_value(self, value):
value = super()._check_data_value(value)
try:
@ -67,10 +69,11 @@ class Set(MultipleRef):
raise FieldValidationError("Given iterable is not castable in \
a set : %s" % e)
##@brief Child class of MultipleRef where references are represented in the form of a python dict
## @brief Child class of MultipleRef where references are represented in the form of a python dict
class Map(MultipleRef):
##@brief instanciates a dict reference
## @brief instanciates a dict reference
# @param kwargs : named arguments
# - allowed_classes list | None : list of allowed em classes if None no restriction
# - internal bool : if False, the field is not internal
@ -81,38 +84,40 @@ class Map(MultipleRef):
def empty(cls):
return dict()
##@brief Check and cast value in appropriate type
#@param value *
#@throw FieldValidationError if value is unappropriate or can not be cast
#@return value
## @brief Check and cast value in appropriate type
# @param value *
# @throw FieldValidationError if value is unappropriate or can not be cast
# @return value
def _check_data_value(self, value):
value = super()._check_data_value(value)
if not isinstance(value, dict):
raise FieldValidationError("Values for dict fields should be dict")
return value
##@brief This Reference class is designed to handler hierarchy with some constraint
## @brief This Reference class is designed to handler hierarchy with some constraint
class Hierarch(MultipleRef):
directly_editable = False
##@brief Instanciate a data handler handling hierarchical relation with constraints
## @brief Instanciate a data handler handling hierarchical relation with constraints
# @param back_reference tuple : Here it is mandatory to have a back ref (like a parent field)
# @param max_depth int | None : limit of depth
# @param max_childs int | Nine : maximum number of childs by nodes
def __init__(self, back_reference, max_depth = None, max_childs = None, **kwargs):
super().__init__( back_reference = back_reference,
max_depth = max_depth,
max_childs = max_childs,
**kwargs)
def __init__(self, back_reference, max_depth=None, max_childs=None, **kwargs):
super().__init__(back_reference=back_reference,
max_depth=max_depth,
max_childs=max_childs,
**kwargs)
@classmethod
def empty(cls):
return tuple()
##@brief Check and cast value in appropriate type
#@param value *
#@throw FieldValidationError if value is unappropriate or can not be cast
#@return value
## @brief Check and cast value in appropriate type
# @param value *
# @throw FieldValidationError if value is unappropriate or can not be cast
# @return value
def _check_data_value(self, value):
value = super()._check_data_value(value)
if not (isinstance(value, list) or isinstance(value, str)):

View file

@ -11,29 +11,31 @@ LodelContext.expose_modules(globals(), {
'lodel.settings': 'Settings',
'lodel.settings.utils': 'SettingsError',
'lodel.leapi.query': ['LeInsertQuery', 'LeUpdateQuery', 'LeDeleteQuery',
'LeGetQuery'],
'LeGetQuery'],
'lodel.leapi.exceptions': ['LeApiError', 'LeApiErrors',
'LeApiDataCheckError', 'LeApiDataCheckErrors', 'LeApiQueryError',
'LeApiQueryErrors'],
'LeApiDataCheckError', 'LeApiDataCheckErrors', 'LeApiQueryError',
'LeApiQueryErrors'],
'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
'LodelScriptError', 'DatasourcePluginError'],
'LodelScriptError', 'DatasourcePluginError'],
'lodel.exceptions': ['LodelFatalError'],
'lodel.plugin.hooks': ['LodelHook'],
'lodel.plugin': ['Plugin', 'DatasourcePlugin'],
'lodel.leapi.datahandlers.base_classes': ['DatasConstructor', 'Reference']})
##@brief Stores the name of the field present in each LeObject that indicates
#the name of LeObject subclass represented by this object
# @brief Stores the name of the field present in each LeObject that indicates
# the name of LeObject subclass represented by this object
CLASS_ID_FIELDNAME = "classname"
##@brief Wrapper class for LeObject getter & setter
# @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
# @brief Construct a new LeObjectValues
# @param fieldnames_callback method
# @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
@ -41,14 +43,14 @@ class LeObjectValues(object):
self._setter = set_callback
self._getter = get_callback
##@brief Provide read access to datas values
# @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):
getter = super().__getattribute__('_getter')
return getter(fname)
##@brief Provide write access to datas values
# @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
@ -59,29 +61,29 @@ class LeObjectValues(object):
class LeObject(object):
##@brief boolean that tells if an object is abtract or not
# @brief boolean that tells if an object is abtract or not
_abstract = None
##@brief A dict that stores DataHandler instances indexed by field name
# @brief A dict that stores DataHandler instances indexed by field name
_fields = None
##@brief A tuple of fieldname (or a uniq fieldname) representing uid
# @brief A tuple of fieldname (or a uniq fieldname) representing uid
_uid = None
##@brief Read only datasource ( see @ref lodel2_datasources )
# @brief Read only datasource ( see @ref lodel2_datasources )
_ro_datasource = None
##@brief Read & write datasource ( see @ref lodel2_datasources )
# @brief Read & write datasource ( see @ref lodel2_datasources )
_rw_datasource = None
##@brief Store the list of child classes
# @brief Store the list of child classes
_child_classes = None
##@brief Name of the datasource plugin
# @brief Name of the datasource plugin
_datasource_name = None
def __new__(cls, **kwargs):
self = object.__new__(cls)
##@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
# @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
# @brief Datas accessor. Instance of @ref LeObjectValues
self.d = LeObjectValues(self.fieldnames, self.set_data, self.data)
for fieldname, fieldval in kwargs.items():
self.__datas[fieldname] = fieldval
@ -90,11 +92,12 @@ class LeObject(object):
self.__set_initialized()
return self
##@brief Construct an object representing an Editorial component
# @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__ )
raise NotImplementedError(
"%s is abstract, you cannot instanciate it." % self.__class__.__name__)
# Checks that uid is given
for uid_name in self._uid:
@ -105,7 +108,7 @@ class LeObject(object):
self.__initialized.append(uid_name)
# Processing given fields
allowed_fieldnames = self.fieldnames(include_ro = False)
allowed_fieldnames = self.fieldnames(include_ro=False)
err_list = dict()
for fieldname, fieldval in kwargs.items():
if fieldname not in allowed_fieldnames:
@ -119,31 +122,31 @@ class LeObject(object):
self.__datas[fieldname] = fieldval
self.__initialized.append(fieldname)
if len(err_list) > 0:
raise LeApiErrors(msg = "Unable to __init__ %s" % self.__class__,
exceptions = err_list)
raise LeApiErrors(msg="Unable to __init__ %s" % self.__class__,
exceptions=err_list)
self.__set_initialized()
#-----------------------------------#
# Fields datas handling methods #
#-----------------------------------#
##@brief Property method True if LeObject is initialized else False
# @brief Property method True if LeObject is initialized else False
@property
def initialized(self):
return self.__is_initialized
##@return The uid field name
# @return The uid field name
@classmethod
def uid_fieldname(cls):
return cls._uid
##@brief Return a list of fieldnames
# @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):
def fieldnames(cls, include_ro=False):
if not include_ro:
return [ fname for fname in cls._fields if not cls._fields[fname].is_internal() ]
return [fname for fname in cls._fields if not cls._fields[fname].is_internal()]
else:
return list(cls._fields.keys())
@ -151,7 +154,7 @@ class LeObject(object):
def name2objname(cls, name):
return name.title()
##@brief Return the datahandler asssociated with a LeObject field
# @brief Return the datahandler asssociated with a LeObject field
# @param fieldname str : The fieldname
# @return A data handler instance
#@todo update class of exception raised
@ -161,17 +164,17 @@ class LeObject(object):
raise NameError("No field named '%s' in %s" % (fieldname, cls.__name__))
return cls._fields[fieldname]
##@brief Getter for references datahandlers
# @brief Getter for references datahandlers
#@param with_backref bool : if true return only references with back_references
#@return <code>{'fieldname': datahandler, ...}</code>
@classmethod
def reference_handlers(cls, with_backref = True):
return { fname: fdh
for fname, fdh in cls.fields(True).items()
if fdh.is_reference() and \
(not with_backref or fdh.back_reference is not None)}
def reference_handlers(cls, with_backref=True):
return {fname: fdh
for fname, fdh in cls.fields(True).items()
if fdh.is_reference() and
(not with_backref or fdh.back_reference is not None)}
##@brief Return a LeObject child class from a name
# @brief Return a LeObject child class from a name
# @warning This method has to be called from dynamically generated LeObjects
# @param leobject_name str : LeObject name
# @return A LeObject child class
@ -183,14 +186,14 @@ class LeObject(object):
mod = importlib.import_module(cls.__module__)
try:
return getattr(mod, leobject_name)
except (AttributeError, TypeError) :
except (AttributeError, TypeError):
raise LeApiError("No LeObject named '%s'" % leobject_name)
@classmethod
def is_abstract(cls):
return cls._abstract
##@brief Field data handler getter
# @brief Field data handler getter
#@param fieldname str : The field name
#@return A datahandler instance
#@throw NameError if the field doesn't exist
@ -199,20 +202,22 @@ class LeObject(object):
try:
return cls._fields[fieldname]
except KeyError:
raise NameError("No field named '%s' in %s" % ( fieldname,
cls.__name__))
##@return A dict with fieldname as key and datahandler as instance
raise NameError("No field named '%s' in %s" % (fieldname,
cls.__name__))
# @return A dict with fieldname as key and datahandler as instance
@classmethod
def fields(cls, include_ro = False):
def fields(cls, include_ro=False):
if include_ro:
return copy.copy(cls._fields)
else:
return {fname:cls._fields[fname] for fname in cls._fields if not cls._fields[fname].is_internal()}
return {fname: cls._fields[fname] for fname in cls._fields\
if not cls._fields[fname].is_internal()}
##@brief Return the list of parents classes
# @brief Return the list of parents classes
#
#@note the first item of the list is the current class, the second is it's
#parent etc...
# parent etc...
#@param cls
#@warning multiple inheritance broken by this method
#@return a list of LeObject child classes
@ -222,23 +227,22 @@ class LeObject(object):
res = [cls]
cur = cls
while True:
cur = cur.__bases__[0] # Multiple inheritance broken HERE
cur = cur.__bases__[0] # Multiple inheritance broken HERE
if cur in (LeObject, object):
break
else:
res.append(cur)
return res
##@brief Return a tuple a child classes
# @brief Return a tuple a child classes
#@return a tuple of child classes
@classmethod
def child_classes(cls):
return copy.copy(cls._child_classes)
##@brief Return the parent class that is the "source" of uid
# @brief Return the parent class that is the "source" of uid
#
#The method goal is to return the parent class that defines UID.
# The method goal is to return the parent class that defines UID.
#@return a LeObject child class or false if no UID defined
@classmethod
def uid_source(cls):
@ -246,19 +250,19 @@ class LeObject(object):
return False
hierarch = cls.hierarch()
prev = hierarch[0]
uid_handlers = set( cls._fields[name] for name in cls._uid )
uid_handlers = set(cls._fields[name] for name in cls._uid)
for pcls in cls.hierarch()[1:]:
puid_handlers = set(cls._fields[name] for name in pcls._uid)
if set(pcls._uid) != set(prev._uid) \
or puid_handlers != uid_handlers:
or puid_handlers != uid_handlers:
break
prev = pcls
return prev
##@brief Initialise both datasources (ro and rw)
# @brief Initialise both datasources (ro and rw)
#
#This method is used once at dyncode load to replace the datasource string
#by a datasource instance to avoid doing this operation for each query
# This method is used once at dyncode load to replace the datasource string
# by a datasource instance to avoid doing this operation for each query
#@see LeObject::_init_datasource()
@classmethod
def _init_datasources(cls):
@ -266,7 +270,7 @@ class LeObject(object):
rw_ds = ro_ds = cls._datasource_name
else:
ro_ds, rw_ds = cls._datasource_name
#Read only datasource initialisation
# Read only datasource initialisation
cls._ro_datasource = DatasourcePlugin.init_datasource(ro_ds, True)
if cls._ro_datasource is None:
log_msg = "No read only datasource set for LeObject %s"
@ -276,7 +280,7 @@ class LeObject(object):
log_msg = "Read only datasource '%s' initialized for LeObject %s"
log_msg %= (ro_ds, cls.__name__)
logger.debug(log_msg)
#Read write datasource initialisation
# Read write datasource initialisation
cls._rw_datasource = DatasourcePlugin.init_datasource(rw_ds, False)
if cls._ro_datasource is None:
log_msg = "No read/write datasource set for LeObject %s"
@ -287,13 +291,13 @@ class LeObject(object):
log_msg %= (ro_ds, cls.__name__)
logger.debug(log_msg)
##@brief Return the uid of the current LeObject instance
# @brief Return the uid of the current LeObject instance
#@return the uid value
#@warning Broke multiple uid capabilities
def uid(self):
return self.data(self._uid[0])
##@brief Read only access to all datas
# @brief Read only access to all datas
# @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
# @param field_name str : field name
# @return the Value
@ -303,16 +307,16 @@ class LeObject(object):
if field_name not in self._fields.keys():
raise NameError("No such field in %s : %s" % (self.__class__.__name__, field_name))
if not self.initialized and field_name not in self.__initialized:
raise RuntimeError("The field %s is not initialized yet (and have no value)" % field_name)
raise RuntimeError(
"The field %s is not initialized yet (and have no value)" % field_name)
return self.__datas[field_name]
##@brief Read only access to all datas
# @brief Read only access to all datas
#@return a dict representing datas of current instance
def datas(self, internal = False):
return {fname:self.data(fname) for fname in self.fieldnames(internal)}
def datas(self, internal=False):
return {fname: self.data(fname) for fname in self.fieldnames(internal)}
##@brief Datas setter
# @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
@ -320,7 +324,7 @@ class LeObject(object):
# @throw NameError if fname is not valid
# @throw AttributeError if the field is not writtable
def set_data(self, fname, fval):
if fname not in self.fieldnames(include_ro = False):
if fname not in self.fieldnames(include_ro=False):
if fname not in self._fields.keys():
raise NameError("No such field in %s : %s" % (self.__class__.__name__, fname))
else:
@ -342,23 +346,23 @@ class LeObject(object):
# 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
# Revert change to be in valid state
del(self.__datas[fname])
del(self.__initialized[-1])
raise LeApiErrors("Data check error", {fname:err})
raise LeApiErrors("Data check error", {fname: err})
else:
self.__datas[fname] = val
##@brief Update the __initialized attribute according to LeObject internal state
# @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
expected_fields = self.fieldnames(include_ro=False) + self._uid
if set(expected_fields) == set(self.__initialized):
self.__is_initialized = True
##@brief Designed to be called when datas are modified
# @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
@ -366,7 +370,7 @@ class LeObject(object):
err_list = dict()
if self.__initialized is True:
# Data value check
for fname in self.fieldnames(include_ro = False):
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
@ -374,19 +378,19 @@ class LeObject(object):
self.__datas[fname] = val
# Data construction
if len(err_list) == 0:
for fname in self.fieldnames(include_ro = True):
for fname in self.fieldnames(include_ro=True):
try:
field = self._fields[fname]
self.__datas[fname] = field.construct_data( self,
fname,
self.__datas,
self.__datas[fname]
)
self.__datas[fname] = field.construct_data(self,
fname,
self.__datas,
self.__datas[fname]
)
except Exception as exp:
err_list[fname] = exp
# Datas consistency check
if len(err_list) == 0:
for fname in self.fieldnames(include_ro = True):
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):
@ -405,7 +409,7 @@ class LeObject(object):
# Other methods #
#--------------------#
##@brief Temporary method to set private fields attribute at dynamic code generation
# @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
@ -416,7 +420,7 @@ class LeObject(object):
def _set__fields(cls, field_list):
cls._fields = field_list
## @brief Check that datas are valid for this type
# @brief Check that datas are valid for this type
# @param datas dict : key == field name value are field values
# @param complete bool : if True expect that datas provide values for all non internal fields
# @param allow_internal bool : if True don't raise an error if a field is internal
@ -424,10 +428,10 @@ class LeObject(object):
# @return Checked datas
# @throw LeApiDataCheckError if errors reported during check
@classmethod
def check_datas_value(cls, datas, complete = False, allow_internal = True):
err_l = dict() #Error storing
correct = set() #valid fields name
mandatory = set() #mandatory fields name
def check_datas_value(cls, datas, complete=False, allow_internal=True):
err_l = dict() # Error storing
correct = set() # valid fields name
mandatory = set() # mandatory fields name
for fname, datahandler in cls._fields.items():
if allow_internal or not datahandler.is_internal():
correct.add(fname)
@ -436,15 +440,15 @@ class LeObject(object):
provided = set(datas.keys())
# searching for unknow fields
for u_f in provided - correct:
#Here we can check if the field is invalid or rejected because
# Here we can check if the field is invalid or rejected because
# it is internel
err_l[u_f] = AttributeError("Unknown or unauthorized field '%s'" % u_f)
# searching for missing mandatory fieldsa
for missing in mandatory - provided:
err_l[missing] = AttributeError("The data for field '%s' is missing" % missing)
#Checks datas
# Checks datas
checked_datas = dict()
for name, value in [ (name, value) for name, value in datas.items() if name in correct ]:
for name, value in [(name, value) for name, value in datas.items() if name in correct]:
dh = cls._fields[name]
res = dh.check_data_value(value)
checked_datas[name], err = res
@ -455,7 +459,7 @@ class LeObject(object):
raise LeApiDataCheckErrors("Error while checking datas", err_l)
return checked_datas
##@brief Check and prepare datas
# @brief Check and prepare datas
#
# @warning when complete = False we are not able to make construct_datas() and _check_data_consistency()
#
@ -478,7 +482,7 @@ construction and consitency when datas are not complete\n")
cls._check_datas_consistency(ret_datas)
return ret_datas
## @brief Construct datas values
# @brief Construct datas values
#
# @param cls
# @param datas dict : Datas that have been returned by LeCrud.check_datas_value() methods
@ -488,13 +492,13 @@ construction and consitency when datas are not complete\n")
def _construct_datas(cls, datas):
constructor = DatasConstructor(cls, datas, cls._fields)
ret = {
fname:constructor[fname]
for fname, ftype in cls._fields.items()
if not ftype.is_internal() or ftype.internal != 'autosql'
fname: constructor[fname]
for fname, ftype in cls._fields.items()
if not ftype.is_internal() or ftype.internal != 'autosql'
}
return ret
## @brief Check datas consistency
# @brief Check datas consistency
# 
# @warning assert that datas is complete
# @param cls
@ -512,28 +516,28 @@ construction and consitency when datas are not complete\n")
if len(err_l) > 0:
raise LeApiDataCheckError("Datas consistency checks fails", err_l)
## @brief Check datas consistency
# @brief Check datas consistency
# 
# @warning assert that datas is complete
# @param cls
# @param datas dict : Datas that have been returned by LeCrud.prepare_datas() method
# @param type_query str : Type of query to be performed , default value : insert
@classmethod
def make_consistency(cls, datas, type_query = 'insert'):
def make_consistency(cls, datas, type_query='insert'):
for fname, dh in cls._fields.items():
ret = dh.make_consistency(fname, datas, type_query)
## @brief Add a new instance of LeObject
# @brief Add a new instance of LeObject
# @return a new uid en case of success, False otherwise
@classmethod
def insert(cls, datas):
query = LeInsertQuery(cls)
return query.execute(datas)
## @brief Update an instance of LeObject
# @brief Update an instance of LeObject
#
#@param datas : list of new datas
def update(self, datas = None):
def update(self, datas=None):
datas = self.datas(internal=False) if datas is None else datas
uids = self._uid
query_filter = list()
@ -551,7 +555,7 @@ construction and consitency when datas are not complete\n")
return result
## @brief Delete an instance of LeObject
# @brief Delete an instance of LeObject
#
#@return 1 if the objet has been deleted
def delete(self):
@ -566,7 +570,7 @@ construction and consitency when datas are not complete\n")
return result
## @brief Delete instances of LeObject
# @brief Delete instances of LeObject
#@param query_filters list
#@returns the number of deleted items
@classmethod
@ -585,7 +589,7 @@ construction and consitency when datas are not complete\n")
deleted += result
return deleted
## @brief Get instances of LeObject
# @brief Get instances of LeObject
#
#@param query_filters dict : (filters, relational filters), with filters is a list of tuples : (FIELD, OPERATOR, VALUE) )
#@param field_list list|None : list of string representing fields see
@ -598,16 +602,16 @@ construction and consitency when datas are not complete\n")
@classmethod
def get(cls, query_filters, field_list=None, order=None, group=None, limit=None, offset=0):
if field_list is not None:
for uid in [ uidname
for uidname in cls.uid_fieldname()
if uidname not in field_list ]:
for uid in [uidname
for uidname in cls.uid_fieldname()
if uidname not in field_list]:
field_list.append(uid)
if CLASS_ID_FIELDNAME not in field_list:
field_list.append(CLASS_ID_FIELDNAME)
try:
query = LeGetQuery(
cls, query_filters = query_filters, field_list = field_list,
order = order, group = group, limit = limit, offset = offset)
cls, query_filters=query_filters, field_list=field_list,
order=order, group=group, limit=limit, offset=offset)
except ValueError as err:
raise err
@ -619,28 +623,28 @@ construction and consitency when datas are not complete\n")
objects = list()
for res in result:
res_cls = cls.name2class(res[CLASS_ID_FIELDNAME])
inst = res_cls.__new__(res_cls,**res)
inst = res_cls.__new__(res_cls, **res)
objects.append(inst)
return objects
##@brief Retrieve an object given an UID
# @brief Retrieve an object given an UID
#@todo broken multiple UID
@classmethod
def get_from_uid(cls, uid):
if cls.uid_fieldname() is None:
raise LodelFatalError(
"No uid defined for class %s" % cls.__name__)
uidname = cls.uid_fieldname()[0] #Brokes composed UID
res = cls.get([(uidname,'=', uid)])
uidname = cls.uid_fieldname()[0] # Brokes composed UID
res = cls.get([(uidname, '=', uid)])
#dedoublonnage vu que query ou la datasource est bugué
# dedoublonnage vu que query ou la datasource est bugué
if len(res) > 1:
res_cp = res
res = []
while len(res_cp) > 0:
cur_res = res_cp.pop()
if cur_res.uid() in [ r.uid() for r in res_cp]:
if cur_res.uid() in [r.uid() for r in res_cp]:
logger.error("DOUBLON detected in query results !!!")
else:
res.append(cur_res)
@ -651,7 +655,7 @@ object ! For class %s with uid value = %s" % (cls, uid))
return None
return res[0]
##@brief Checks if an object exists
# @brief Checks if an object exists
@classmethod
def is_exist(cls, uid):
if cls.uid_fieldname() is None:
@ -659,4 +663,3 @@ object ! For class %s with uid value = %s" % (cls, uid))
"No uid defined for class %s" % cls.__name__)
from .query import is_exist
return is_exist(cls, uid)

View file

@ -0,0 +1,2 @@
mlnamedobject_PYTHON= *.py
mlnamedobjectdir=$(pkgpythondir)/mlnamedobject

View file

View file

@ -0,0 +1,18 @@
#-*- coding:utf-8 -*-
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.utils.mlstring': ['MlString']})
# @package lodel.mlnamedobject Lodel2 description of objects module
#
# Display name and Description of a lodel2 object
# @brief Class allows display name and help text for lodel2 objects and fields
class MlNamedObject(object):
def __init__(self, display_name=None, help_text=None):
self.display_name = None if display_name is None else MlString(display_name)
self.help_text = None if help_text is None else MlString(help_text)

View file

@ -3,7 +3,7 @@ LodelContext.expose_modules(globals(), {
'lodel.plugin.plugins': ['Plugin'],
'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
'LodelScriptError', 'DatasourcePluginError'],
'lodel.settings.validator': ['SettingValidator'],
'lodel.validator.validator': ['Validator'],
'lodel.exceptions': ['LodelException', 'LodelExceptions',
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
@ -97,7 +97,7 @@ class DatasourcePlugin(Plugin):
'section': 'lodel2',
'key': 'datasource_connectors',
'default': 'dummy_datasource',
'validator': SettingValidator(
'validator': Validator(
'custom_list', none_is_valid = False,
validator_name = 'plugin', validator_kwargs = {
'ptype': _glob_typename,
@ -280,13 +280,13 @@ but %s is a %s" % (ds_name, pinstance.__class__.__name__))
#CONFSPEC = {
# 'lodel2.datasource.mysql.*' : {
# 'host': ( 'localhost',
# SettingValidator('host')),
# Validator('host')),
# 'db_name': ( 'lodel',
# SettingValidator('string')),
# Validator('string')),
# 'username': ( None,
# SettingValidator('string')),
# Validator('string')),
# 'password': ( None,
# SettingValidator('string')),
# Validator('string')),
# }
#}
#</pre>

View file

@ -3,7 +3,7 @@ LodelContext.expose_modules(globals(), {
'lodel.plugin.plugins': ['Plugin'],
'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
'LodelScriptError', 'DatasourcePluginError'],
'lodel.settings.validator': ['SettingValidator']})
'lodel.validator.validator': ['Validator']})
_glob_typename = 'extension'
@ -14,7 +14,7 @@ class Extension(Plugin):
'section': 'lodel2',
'key': 'extensions',
'default': None,
'validator': SettingValidator(
'validator': Validator(
'custom_list', none_is_valid = True,
validator_name = 'plugin', validator_kwargs = {
'ptype': _glob_typename,

View file

@ -3,7 +3,7 @@ LodelContext.expose_modules(globals(), {
'lodel.plugin.plugins': ['Plugin'],
'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
'LodelScriptError', 'DatasourcePluginError'],
'lodel.settings.validator': ['SettingValidator']})
'lodel.validator.validator': ['Validator']})
_glob_typename = 'ui'
@ -19,7 +19,7 @@ class InterfacePlugin(Plugin):
'section': 'lodel2',
'key': 'interface',
'default': None,
'validator': SettingValidator(
'validator': Validator(
'plugin', none_is_valid = True, ptype = _glob_typename)}
_type_conf_name = _glob_typename

View file

@ -3,7 +3,7 @@ LodelContext.expose_modules(globals(), {
'lodel.plugin.plugins': ['Plugin', 'MetaPlugType'],
'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
'LodelScriptError', 'DatasourcePluginError'],
'lodel.settings.validator': ['SettingValidator']})
'lodel.validator.validator': ['Validator']})
##@brief SessionHandlerPlugin metaclass designed to implements a wrapper
@ -53,7 +53,7 @@ class SessionHandlerPlugin(Plugin, metaclass=SessionPluginWrapper):
'section': 'lodel2',
'key': 'session_handler',
'default': None,
'validator': SettingValidator(
'validator': Validator(
'plugin', none_is_valid=False,ptype = _glob_typename)}
_type_conf_name = _glob_typename

View file

@ -1,6 +1,6 @@
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.settings.validator': ['SettingValidator']})
'lodel.validator.validator': ['Validator']})
__plugin_name__ = "dummy"
__version__ = '0.0.1' #or __version__ = [0,0,1]

View file

@ -2,11 +2,11 @@
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.settings.validator': ['SettingValidator']})
'lodel.validator.validator': ['Validator']})
CONFSPEC = {
'lodel2.section1': {
'key1': ( None,
SettingValidator('dummy'))
Validator('dummy'))
}
}

View file

@ -1,6 +1,6 @@
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.settings.validator': ['SettingValidator']})
'lodel.validator.validator': ['Validator']})
from .datasource import DummyDatasource as Datasource
__plugin_type__ = 'datasource'
@ -12,7 +12,7 @@ __plugin_deps__ = []
CONFSPEC = {
'lodel2.datasource.dummy_datasource.*' : {
'dummy': ( None,
SettingValidator('dummy'))}
Validator('dummy'))}
}

View file

@ -1,6 +1,6 @@
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.settings.validator': ['SettingValidator']})
'lodel.validator.validator': ['Validator']})
__plugin_name__ = 'filesystem_session'
__version__ = [0,0,1]

View file

@ -2,12 +2,12 @@
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.settings.validator': ['SettingValidator']})
'lodel.validator.validator': ['Validator']})
CONFSPEC = {
'lodel2.sessions':{
'directory': ('/tmp/', SettingValidator('path')),
'expiration': (900, SettingValidator('int')),
'file_template': ('lodel2_%s.sess', SettingValidator('dummy'))
'directory': ('/tmp/', Validator('path')),
'expiration': (900, Validator('int')),
'file_template': ('lodel2_%s.sess', Validator('dummy'))
}
}

View file

@ -2,7 +2,7 @@
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.settings.validator': ['SettingValidator']})
'lodel.validator.validator': ['Validator']})
##@brief Mongodb datasource plugin confspec
#@ingroup plugin_mongodb_datasource
@ -10,11 +10,11 @@ LodelContext.expose_modules(globals(), {
#Describe mongodb plugin configuration. Keys are :
CONFSPEC = {
'lodel2.datasource.mongodb_datasource.*':{
'read_only': (False, SettingValidator('bool')),
'host': ('localhost', SettingValidator('host')),
'port': (None, SettingValidator('string', none_is_valid = True)),
'db_name':('lodel', SettingValidator('string')),
'username': (None, SettingValidator('string')),
'password': (None, SettingValidator('string'))
'read_only': (False, Validator('bool')),
'host': ('localhost', Validator('host')),
'port': (None, Validator('string', none_is_valid = True)),
'db_name':('lodel', Validator('string')),
'username': (None, Validator('string')),
'password': (None, Validator('string'))
}
}

View file

@ -1,7 +1,7 @@
from lodel.context import LodelContext, ContextError
try:
LodelContext.expose_modules(globals(), {
'lodel.settings.validator': ['SettingValidator']})
'lodel.validator.validator': ['Validator']})
__plugin_name__ = "multisite"
__version__ = '0.0.1' #or __version__ = [0,0,1]
@ -13,8 +13,8 @@ try:
CONFSPEC = {
'lodel2.server': {
'port': (80,SettingValidator('int')),
'listen_addr': ('', SettingValidator('string')),
'port': (80,Validator('int')),
'listen_addr': ('', Validator('string')),
}
}

View file

@ -1,34 +1,34 @@
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.settings.validator': ['SettingValidator']})
'lodel.validator.validator': ['Validator']})
#Define a minimal confspec used by multisite loader
LODEL2_CONFSPECS = {
'lodel2': {
'debug': (True, SettingValidator('bool'))
'debug': (True, Validator('bool'))
},
'lodel2.server': {
'listen_address': ('127.0.0.1', SettingValidator('dummy')),
#'listen_address': ('', SettingValidator('ip')), #<-- not implemented
'listen_port': ( 1337, SettingValidator('int')),
'uwsgi_workers': (8, SettingValidator('int')),
'uwsgicmd': ('/usr/bin/uwsgi', SettingValidator('dummy')),
'virtualenv': (None, SettingValidator('path', none_is_valid = True)),
'listen_address': ('127.0.0.1', Validator('dummy')),
#'listen_address': ('', Validator('ip')), #<-- not implemented
'listen_port': ( 1337, Validator('int')),
'uwsgi_workers': (8, Validator('int')),
'uwsgicmd': ('/usr/bin/uwsgi', Validator('dummy')),
'virtualenv': (None, Validator('path', none_is_valid = True)),
},
'lodel2.logging.*' : {
'level': ( 'ERROR',
SettingValidator('loglevel')),
Validator('loglevel')),
'context': ( False,
SettingValidator('bool')),
Validator('bool')),
'filename': ( None,
SettingValidator('errfile', none_is_valid = True)),
Validator('errfile', none_is_valid = True)),
'backupcount': ( 10,
SettingValidator('int', none_is_valid = False)),
Validator('int', none_is_valid = False)),
'maxbytes': ( 1024*10,
SettingValidator('int', none_is_valid = False)),
Validator('int', none_is_valid = False)),
},
'lodel2.datasources.*': {
'read_only': (False, SettingValidator('bool')),
'identifier': ( None, SettingValidator('string')),
'read_only': (False, Validator('bool')),
'identifier': ( None, Validator('string')),
}
}

View file

@ -1,6 +1,6 @@
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.settings.validator': ['SettingValidator']})
'lodel.validator.validator': ['Validator']})
__plugin_name__ = 'ram_sessions'
__version__ = [0,0,1]
@ -11,7 +11,7 @@ __fullname__ = "RAM Session Store Plugin"
CONFSPEC = {
'lodel2.sessions':{
'expiration': (900, SettingValidator('int')),
'tokensize': (512, SettingValidator('int')),
'expiration': (900, Validator('int')),
'tokensize': (512, Validator('int')),
}
}

View file

@ -1,30 +1,30 @@
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.settings.validator': ['SettingValidator']})
'lodel.validator.validator': ['Validator']})
CONFSPEC = {
'lodel2.webui': {
'standalone': ( 'False',
SettingValidator('string')),
Validator('string')),
'listen_address': ( '127.0.0.1',
SettingValidator('dummy')),
Validator('dummy')),
'listen_port': ( '9090',
SettingValidator('int')),
Validator('int')),
'static_url': ( 'http://127.0.0.1/static/',
SettingValidator('regex', pattern = r'^https?://[^/].*$')),
Validator('regex', pattern = r'^https?://[^/].*$')),
'virtualenv': (None,
SettingValidator('path', none_is_valid=True)),
'uwsgicmd': ('/usr/bin/uwsgi', SettingValidator('dummy')),
'cookie_secret_key': ('ConfigureYourOwnCookieSecretKey', SettingValidator('dummy')),
'cookie_session_id': ('lodel', SettingValidator('dummy')),
'uwsgi_workers': (2, SettingValidator('int'))
Validator('path', none_is_valid=True)),
'uwsgicmd': ('/usr/bin/uwsgi', Validator('dummy')),
'cookie_secret_key': ('ConfigureYourOwnCookieSecretKey', Validator('dummy')),
'cookie_session_id': ('lodel', Validator('dummy')),
'uwsgi_workers': (2, Validator('int'))
},
'lodel2.webui.sessions': {
'directory': ( '/tmp',
SettingValidator('path')),
Validator('path')),
'expiration': ( 900,
SettingValidator('int')),
Validator('int')),
'file_template': ( 'lodel2_%s.sess',
SettingValidator('dummy')),
Validator('dummy')),
}
}

View file

@ -13,14 +13,14 @@
<h1 class="h1_lodel">Issue {{ obj.data('title') }} </h1>
<h2>{{ obj.data('subtitle') }}</h2>
{% set directors=person_class.get(("%s in (%s)") % (person_class.uid_fieldname()[0], obj.data('linked_directors')|join(','))) %}
<p><strong>Directors : </strong>{% for director in directors %} <a href="/{{ root_url }}/show_object?classname=Person&lodel_id={{ director.uid() }} " target="_blank" >{{ director.data('firstname')}} {{ director.data('lastname')}}</a> ; {% endfor %}</p>
<p><strong>Directors : </strong>{% for director in directors %} <a href="/{{ root_url }}/show_object?classname=Person&lodel_id={{ director.uid() }} " >{{ director.data('firstname')}} {{ director.data('lastname')}}</a> ; {% endfor %}</p>
{% set texts=my_classes.Text.get(("%s in (%s)") % (my_classes.Text.uid_fieldname()[0], obj.data('linked_texts')|join(','))) %}
{% set parts=my_classes.Part.get(("%s in (%s)") % (my_classes.Part.uid_fieldname()[0], obj.data('linked_parts')|join(','))) %}
{% if texts is not none: %}
<ul>
{% for text in texts %}
<li>
<h3><a href="/{{ root_url }}/show_object?classname={{ text.data('classname') }}&lodel_id={{ text.uid() }}" target="_blank" > {{ text.data('title') }}</a></h3>
<h3><a href="/{{ root_url }}/show_object?classname={{ text.data('classname') }}&lodel_id={{ text.uid() }}" > {{ text.data('title') }}</a></h3>
<h4>{{ text.data('subtitle') }}</h4>
{% set authors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], text.data('linked_persons')|join(','))) %}
<p>Authors : {% for author in authors %} {{ author.data('firstname')}} {{ author.data('lastname')}} ; {% endfor %} </p>
@ -34,7 +34,7 @@
<ul>
{% for part in parts %}
<li>
<h3><a href="/{{ root_url }}/show_object?classname={{ part.data('classname') }}&lodel_id={{ part.uid() }}" target="_blank"> {{ part.data('title') }}</a></h3>
<h3><a href="/{{ root_url }}/show_object?classname={{ part.data('classname') }}&lodel_id={{ part.uid() }}"> {{ part.data('title') }}</a></h3>
<h4>{{ part.data('subtitle') }}</h4>
{% set directors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], part.data('linked_directors')|join(','))) %}
<p>Directors : {% for director in directors %} {{ director.data('firstname')}} {{ director.data('lastname')}} ; {% endfor %} </p>
@ -43,7 +43,7 @@
<ul style="margin-left:20px">
{% for text in p_texts %}
<li>
<h3><a href="/{{ root_url }}/show_object?classname={{ text.data('classname') }}&lodel_id={{ text.uid() }}" target="_blank"> {{ text.data('title') }}</a></h3>
<h3><a href="/{{ root_url }}/show_object?classname={{ text.data('classname') }}&lodel_id={{ text.uid() }}" > {{ text.data('title') }}</a></h3>
<h4>{{ text.data('subtitle') }}</h4>
{% set authors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], text.data('linked_persons')|join(','))) %}
<p>Authors : {% for author in authors %} {{ author.data('firstname')}} {{ author.data('lastname')}} ; {% endfor %} </p>
@ -57,7 +57,7 @@
<ul>
{% for part in ss_parts %}
<li>
<h3><a href="/{{ root_url }}/show_object?classname={{ part.data('classname') }}&lodel_id={{ part.uid() }}" target="_blank"> {{ part.data('title') }}</a></h3>
<h3><a href="/{{ root_url }}/show_object?classname={{ part.data('classname') }}&lodel_id={{ part.uid() }}" > {{ part.data('title') }}</a></h3>
<h4>{{ part.data('subtitle') }}</h4>
{% set directors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], part.data('linked_directors')|join(','))) %}
<p>Directors : {% for director in directors %} {{ director.data('firstname')}} {{ director.data('lastname')}} ; {% endfor %} </p>
@ -66,7 +66,7 @@
<ul style="margin-left:20px">
{% for text in sp_texts %}
<li>
<h3><a href="/{{ root_url }}/show_object?classname={{ text.data('classname') }}&lodel_id={{ text.uid() }}" target="_blank"> {{ text.data('title') }}</a></h3>
<h3><a href="/{{ root_url }}/show_object?classname={{ text.data('classname') }}&lodel_id={{ text.uid() }}" > {{ text.data('title') }}</a></h3>
<h4>{{ text.data('subtitle') }}</h4>
{% set authors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], text.data('linked_persons')|join(','))) %}
<p>Authors : {% for author in authors %} {{ author.data('firstname')}} {{ author.data('lastname')}} ; {% endfor %} </p>

View file

@ -5,36 +5,38 @@ import os
import configparser
import copy
import warnings
import types # for dynamic bindings
import types # for dynamic bindings
from collections import namedtuple
from lodel.context import LodelContext
LodelContext.expose_modules(globals(),{
LodelContext.expose_modules(globals(), {
'lodel.logger': 'logger',
'lodel.settings.utils': ['SettingsError', 'SettingsErrors'],
'lodel.settings.validator': ['SettingValidator', 'LODEL2_CONF_SPECS',
'confspec_append'],
'lodel.settings.settings_loader':['SettingsLoader']})
'lodel.validator.validator': ['Validator', 'LODEL2_CONF_SPECS',
'confspec_append'],
'lodel.settings.settings_loader': ['SettingsLoader']})
## @package lodel.settings.settings Lodel2 settings module
#  @package lodel.settings.settings Lodel2 settings module
#
# Contains the class that handles the namedtuple tree of settings
##@brief A default python system lib path
# @brief A default python system lib path
PYTHON_SYS_LIB_PATH = '/usr/local/lib/python{major}.{minor}/'.format(
major = sys.version_info.major,
minor = sys.version_info.minor)
major=sys.version_info.major,
minor=sys.version_info.minor)
class MetaSettings(type):
@property
def s(self):
self.singleton_assert(True)
return self.instance.settings
##@brief Handles configuration load etc.
# @brief Handles configuration load etc.
#
# To see howto bootstrap Settings and use it in lodel instance see
# @ref lodel.settings
@ -55,10 +57,10 @@ class MetaSettings(type):
# Here is the conceptual presentation of Settings class initialization stages :
# -# Preloading (sets values like lodel2 library path or the plugins path)
# -# Ask a @ref lodel.settings.setting_loader.SettingsLoader to load all
#configurations files
# configurations files
# -# Fetch the list of plugins in the loaded settings
# -# Merge plugins settings specification with the global lodel settings
#specs ( see @ref lodel.plugin )
# specs ( see @ref lodel.plugin )
# -# Fetch all settings from the merged settings specs
#
# @par Init sequence in practical
@ -71,36 +73,38 @@ class MetaSettings(type):
# @todo handles default sections for variable sections (sections ending with
# '.*')
# @todo delete the first stage, the lib path HAVE TO BE HARDCODED. In fact
#when we will run lodel in production the lodel2 lib will be in the python path
# when we will run lodel in production the lodel2 lib will be in the python path
#@todo add log messages (now we can)
class Settings(object, metaclass=MetaSettings):
## @brief Stores the singleton instance
#  @brief Stores the singleton instance
instance = None
## @brief Instanciate the Settings singleton
#  @brief Instanciate the Settings singleton
# @param conf_dir str : The configuration directory
#@param custom_confspecs None | dict : if given overwrite default lodel2
#confspecs
def __init__(self, conf_dir, custom_confspecs = None):
self.singleton_assert() # check that it is the only instance
# confspecs
def __init__(self, conf_dir, custom_confspecs=None):
self.singleton_assert() # check that it is the only instance
Settings.instance = self
## @brief Configuration specification
#  @brief Configuration specification
#
# Initialized by Settings.__bootstrap() method
self.__conf_specs = custom_confspecs
## @brief Stores the configurations in namedtuple tree
#  @brief Stores the configurations in namedtuple tree
self.__confs = None
self.__conf_dir = conf_dir
self.__started = False
self.__bootstrap()
## @brief Get the named tuple representing configuration
#  @brief Get the named tuple representing configuration
@property
def settings(self):
return self.__confs.lodel2
## @brief Delete the singleton instance
#  @brief Delete the singleton instance
@classmethod
def stop(cls):
del(cls.instance)
@ -110,7 +114,7 @@ class Settings(object, metaclass=MetaSettings):
def started(cls):
return cls.instance is not None and cls.instance.__started
##@brief An utility method that raises if the singleton is not in a good
# @brief An utility method that raises if the singleton is not in a good
# state
#@param expect_instanciated bool : if True we expect that the class is
# allready instanciated, else not
@ -124,17 +128,17 @@ class Settings(object, metaclass=MetaSettings):
if cls.started():
raise RuntimeError("The Settings class is already started")
##@brief Saves a new configuration for section confname
# @brief Saves a new configuration for section confname
#@param confname is the name of the modified section
#@param confvalue is a dict with variables to save
#@param validator is a dict with adapted validator
@classmethod
def set(cls, confname, confvalue,validator):
def set(cls, confname, confvalue, validator):
loader = SettingsLoader(cls.instance.__conf_dir)
confkey=confname.rpartition('.')
confkey = confname.rpartition('.')
loader.setoption(confkey[0], confkey[2], confvalue, validator)
##@brief This method handles Settings instance bootstraping
# @brief This method handles Settings instance bootstraping
def __bootstrap(self):
LodelContext.expose_modules(globals(), {
'lodel.plugin.plugins': ['Plugin', 'PluginError']})
@ -146,7 +150,7 @@ class Settings(object, metaclass=MetaSettings):
self.__conf_specs = None
loader = SettingsLoader(self.__conf_dir)
plugin_list = []
for ptype_name,ptype in Plugin.plugin_types().items():
for ptype_name, ptype in Plugin.plugin_types().items():
pls = ptype.plist_confspecs()
lodel2_specs = confspec_append(lodel2_specs, **pls)
cur_list = loader.getoption(
@ -162,13 +166,15 @@ class Settings(object, metaclass=MetaSettings):
plugin_list += cur_list
except TypeError:
plugin_list += [cur_list]
#Checking confspecs
# Checking confspecs
for section in lodel2_specs:
if section.lower() != section:
raise SettingsError("Only lower case are allowed in section name (thank's ConfigParser...)")
raise SettingsError(
"Only lower case are allowed in section name (thank's ConfigParser...)")
for kname in lodel2_specs[section]:
if kname.lower() != kname:
raise SettingsError("Only lower case are allowed in section name (thank's ConfigParser...)")
raise SettingsError(
"Only lower case are allowed in section name (thank's ConfigParser...)")
# Starting the Plugins class
logger.debug("Starting lodel.plugin.Plugin class")
@ -181,13 +187,13 @@ class Settings(object, metaclass=MetaSettings):
specs.append(Plugin.get(plugin_name).confspecs)
except PluginError as e:
errors.append(SettingsError(msg=str(e)))
if len(errors) > 0: #Raise all plugins import errors
if len(errors) > 0: # Raise all plugins import errors
raise SettingsErrors(errors)
self.__conf_specs = self.__merge_specs(specs)
self.__populate_from_specs(self.__conf_specs, loader)
self.__started = True
##@brief Produce a configuration specification dict by merging all specifications
# @brief Produce a configuration specification dict by merging all specifications
#
# Merges global lodel2 conf spec from @ref lodel.settings.validator.LODEL2_CONF_SPECS
# and configuration specifications from loaded plugins
@ -198,31 +204,35 @@ class Settings(object, metaclass=MetaSettings):
for spec in specs:
for section in spec:
if section.lower() != section:
raise SettingsError("Only lower case are allowed in section name (thank's ConfigParser...)")
raise SettingsError(
"Only lower case are allowed in section name (thank's ConfigParser...)")
if section not in res:
res[section] = dict()
for kname in spec[section]:
if kname.lower() != kname:
raise SettingsError("Only lower case are allowed in section name (thank's ConfigParser...)")
raise SettingsError(
"Only lower case are allowed in section name (thank's ConfigParser...)")
if kname in res[section]:
raise SettingsError("Duplicated key '%s' in section '%s'" % (kname, section))
raise SettingsError("Duplicated key '%s' in section '%s'" %
(kname, section))
res[section.lower()][kname] = copy.copy(spec[section][kname])
return res
##@brief Populate the Settings instance with options values fetched with the loader from merged specs
# @brief Populate the Settings instance with options values fetched with the loader from merged specs
#
# Populate the __confs attribute
# @param specs dict : Settings specification dictionnary as returned by __merge_specs
# @param loader SettingsLoader : A SettingsLoader instance
def __populate_from_specs(self, specs, loader):
self.__confs = dict()
specs = copy.copy(specs) #Avoid destroying original specs dict (may be useless)
specs = copy.copy(specs) # Avoid destroying original specs dict (may be useless)
# Construct final specs dict replacing variable sections
# by the actual existing sections
variable_sections = [ section for section in specs if section.endswith('.*') ]
variable_sections = [section for section in specs if section.endswith('.*')]
for vsec in variable_sections:
preffix = vsec[:-2]
for section in loader.getsection(preffix, 'default'): #WARNING : hardcoded default section
# WARNING : hardcoded default section
for section in loader.getsection(preffix, 'default'):
specs[section] = copy.copy(specs[vsec])
del(specs[vsec])
# Fetching values for sections
@ -239,7 +249,7 @@ class Settings(object, metaclass=MetaSettings):
self.__confs_to_namedtuple()
pass
##@brief Transform the __confs attribute into imbricated namedtuple
# @brief Transform the __confs attribute into imbricated namedtuple
#
# For example an option named "foo" in a section named "hello.world" will
# be acessible with self.__confs.hello.world.foo
@ -257,7 +267,7 @@ class Settings(object, metaclass=MetaSettings):
section_name = ""
cur = section_tree
for sec_part in spl:
section_name += sec_part+'.'
section_name += sec_part + '.'
if sec_part not in cur:
cur[sec_part] = dict()
cur = cur[sec_part]
@ -267,7 +277,7 @@ class Settings(object, metaclass=MetaSettings):
raise SettingsError("Duplicated key for '%s.%s'" % (section_name, kname))
cur[kname] = kval
path = [ ('root', section_tree) ]
path = [('root', section_tree)]
visited = set()
curname = 'root'
@ -275,27 +285,27 @@ class Settings(object, metaclass=MetaSettings):
cur = section_tree
while True:
visited.add(nodename)
left = [ (kname, cur[kname])
for kname in cur
if nodename+'.'+kname.title() not in visited and isinstance(cur[kname], dict)
left = [(kname, cur[kname])
for kname in cur
if nodename + '.' + kname.title() not in visited and isinstance(cur[kname], dict)
]
if len(left) == 0:
name, leaf = path.pop()
typename = nodename.replace('.', '')
if len(path) == 0:
# END
self.__confs = self.__tree2namedtuple(leaf,typename)
self.__confs = self.__tree2namedtuple(leaf, typename)
break
else:
path[-1][1][name] = self.__tree2namedtuple(leaf,typename)
path[-1][1][name] = self.__tree2namedtuple(leaf, typename)
nodename = '.'.join(nodename.split('.')[:-1])
cur = path[-1][1]
else:
curname, cur = left[0]
path.append( (curname, cur) )
path.append((curname, cur))
nodename += '.' + curname.title()
##@brief Forge a named tuple given a conftree node
# @brief Forge a named tuple given a conftree node
# @param conftree dict : A conftree node
# @param name str
# @return a named tuple with fieldnames corresponding to conftree keys
@ -303,11 +313,13 @@ class Settings(object, metaclass=MetaSettings):
ResNamedTuple = namedtuple(name, conftree.keys())
return ResNamedTuple(**conftree)
class MetaSettingsRO(type):
def __getattr__(self, name):
return getattr(Settings.s, name)
## @brief A class that provide . notation read only access to configurations
#  @brief A class that provide . notation read only access to configurations
class SettingsRO(object, metaclass=MetaSettingsRO):
pass

View file

@ -8,8 +8,7 @@ from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.logger': 'logger',
'lodel.settings.utils': ['SettingsError', 'SettingsErrors'],
'lodel.settings.validator': ['SettingsValidationError']})
'lodel.settings.utils': ['SettingsError', 'SettingsErrors']})
##@brief Merges and loads configuration files
class SettingsLoader(object):
@ -23,10 +22,10 @@ class SettingsLoader(object):
##@brief Constructor
# @param conf_path str : conf.d path
def __init__(self,conf_path):
self.__conf_path=conf_path
self.__conf_sv=dict()
self.__conf=self.__merge()
def __init__(self, conf_path):
self.__conf_path = conf_path
self.__conf_sv = dict()
self.__conf = self.__merge()
# Stores errors
self.__errors_list = []
@ -39,23 +38,21 @@ class SettingsLoader(object):
', '.join(l_dir)))
for f_ini in l_dir:
config = configparser.ConfigParser(default_section = self.DEFAULT_SECTION ,interpolation=None)
config = configparser.ConfigParser(default_section=self.DEFAULT_SECTION, interpolation=None)
config.read(f_ini)
for section in [ s for s in config if s != self.DEFAULT_SECTION ]:
for section in [s for s in config if s != self.DEFAULT_SECTION]:
if section not in conf:
conf[section] = dict()
for param in config[section]:
if param not in conf[section]:
conf[section][param]=dict()
conf[section][param] = dict()
conf[section][param]['value'] = config[section][param]
conf[section][param]['file'] = f_ini
self.__conf_sv[section + ':' + param]=f_ini
self.__conf_sv[section + ':' + param] = f_ini
else:
raise SettingsError("Error redeclaration of key %s in section %s. Found in %s and %s" % (
section,
param,
f_ini,
conf[section][param]['file']))
raise SettingsError("Error redeclaration of key %s \
in section %s. Found in %s and %s" % (\
section, param, f_ini, conf[section][param]['file']))
return conf
##@brief Returns option if exists default_value else and validates
@ -65,8 +62,8 @@ class SettingsLoader(object):
# @param default_value *
# @param mandatory bool
# @return the option
def getoption(self,section,keyname,validator,default_value=None,mandatory=False):
conf=self.__conf
def getoption(self, section, keyname, validator, default_value=None, mandatory=False):
conf = self.__conf
if section not in conf:
conf[section] = dict()
@ -85,35 +82,31 @@ class SettingsLoader(object):
if result is None:
if default_value is None and mandatory:
msg = "Default value mandatory for option %s" % keyname
expt = SettingsError( msg = msg,
key_id = section+'.'+keyname,
filename = sec[keyname]['file'])
expt = SettingsError(msg=msg, key_id=section+'.'+keyname, \
filename=sec[keyname]['file'])
self.__errors_list.append(expt)
return
else:
sec[keyname]=dict()
sec[keyname] = dict()
sec[keyname]['value'] = default_value
sec[keyname]['file'] = SettingsLoader.DEFAULT_FILENAME
result = default_value
logger.debug("Using default value for configuration key %s:%s" % (
section, keyname))
logger.debug("Using default value for configuration key %s:%s" \
% (section, keyname))
try:
return validator(result)
except Exception as e:
# Generating nice exceptions
if False and sec[keyname]['file'] == SettingsLoader.DEFAULT_FILENAME:
expt = SettingsError( msg = 'Mandatory settings not found',
key_id = section+'.'+keyname)
expt = SettingsError(msg='Mandatory settings not found', \
key_id=section+'.'+keyname)
self.__errors_list.append(expt)
else:
expt = SettingsValidationError(
"For %s.%s : %s" %
(section, keyname,e)
)
expt2 = SettingsError( msg = str(expt),
key_id = section+'.'+keyname,
filename = sec[keyname]['file'])
#expt = ValidationError("For %s.%s : %s" % (section, keyname, e))
expt2 = SettingsError(msg=str(expt), \
key_id=section+'.'+keyname, \
filename=sec[keyname]['file'])
self.__errors_list.append(expt2)
return
@ -123,17 +116,17 @@ class SettingsLoader(object):
# @param value str
# @param validator callable : takes one argument value and raises validation fail
# @return the option
def setoption(self,section,keyname,value,validator):
f_conf=copy.copy(self.__conf[section][keyname]['file'])
def setoption(self, section, keyname, value, validator):
f_conf = copy.copy(self.__conf[section][keyname]['file'])
if f_conf == SettingsLoader.DEFAULT_FILENAME:
f_conf = self.__conf_path + '/generated.ini'
conf=self.__conf
conf = self.__conf
conf[section][keyname] = value
config = configparser.ConfigParser()
config.read(f_conf)
if section not in config:
config[section]={}
config[section] = {}
config[section][keyname] = validator(value)
with open(f_conf, 'w') as configfile:
@ -145,16 +138,16 @@ class SettingsLoader(object):
def saveconf(self, sections, validators):
for sec in sections:
for kname in sections[sec]:
self.setoption(sec,kname,sections[sec][kname],validators[sec][kname])
self.setoption(sec, kname, sections[sec][kname], validators[sec][kname])
##@brief Returns the section to be configured
# @param section_prefix str
# @param default_section str
# @return the section as dict()
def getsection(self,section_prefix,default_section=None):
conf=copy.copy(self.__conf)
def getsection(self, section_prefix, default_section=None):
conf = copy.copy(self.__conf)
sections=[]
sections = []
if section_prefix in conf:
sections.append(section_prefix)
for sect_names in conf:
@ -163,7 +156,7 @@ class SettingsLoader(object):
elif sect_names.startswith(section_prefix + '.'):
sections.append(sect_names)
if sections == [] and default_section:
sections.append(section_prefix + '.' + default_section)
sections.append(section_prefix + '.' + default_section)
elif sections == []:
raise NameError("Not existing settings section : %s" % section_prefix)
@ -185,11 +178,10 @@ class SettingsLoader(object):
remains = self.getremains()
err_l = self.__errors_list
for key_id, filename in remains.items():
err_l.append(SettingsError( msg = "Invalid configuration key",
key_id = key_id,
filename = filename))
err_l.append(SettingsError(msg="Invalid configuration key", \
key_id=key_id, \
filename =filename))
if len(err_l) > 0:
raise SettingsErrors(err_l)
else:
return

View file

@ -5,7 +5,7 @@ import hashlib
import json
##@brief Stores multilangage string
# @brief Stores multilangage string
class MlString(object):
__default_lang = 'eng'
@ -17,7 +17,7 @@ class MlString(object):
'esp',
]
##@brief Create a new MlString instance
# @brief Create a new MlString instance
# @param arg str | dict : Can be a json string, a string or a dict. It could be also a MlString object
def __init__(self, arg):
self.values = dict()
@ -31,11 +31,12 @@ class MlString(object):
elif isinstance(arg, MlString):
self.values = copy.copy(arg.values)
else:
raise ValueError('<class str>, <class dict> or <class MlString> expected, but %s found' % type(arg))
raise ValueError(
'<class str>, <class dict> or <class MlString> expected, but %s found' % type(arg))
##@brief Return a translation given a lang
# @brief Return a translation given a lang
# @param lang str | None : If None return default lang translation
def get(self, lang = None):
def get(self, lang=None):
lang = self.__default_lang if lang is None else lang
if not self.lang_is_valid(lang):
raise ValueError("Invalid lang : '%s'" % lang)
@ -44,7 +45,7 @@ class MlString(object):
else:
return str(self)
##@brief Set a translation
# @brief Set a translation
# @param lang str : the lang
# @param val str | None: the translation if None delete the translation
def set(self, lang, val):
@ -57,7 +58,7 @@ class MlString(object):
else:
self.values[lang] = val
##@brief Checks that given lang is valid
# @brief Checks that given lang is valid
# @param lang str : the lang
@classmethod
def lang_is_valid(cls, lang):
@ -65,16 +66,16 @@ class MlString(object):
raise ValueError('Invalid value for lang. Str expected but %s found' % type(lang))
return lang in cls.langs
##@brief Get or set the default lang
# @brief Get or set the default lang
@classmethod
def default_lang(cls, lang = None):
def default_lang(cls, lang=None):
if lang is None:
return cls.__default_lang
if not cls.lang_is_valid(lang):
raise ValueError('lang "%s" is not valid"' % lang)
cls.__default_lang = lang
##@brief Return a mlstring loaded from a json string
# @brief Return a mlstring loaded from a json string
# @param json_str str : Json string
@classmethod
def from_json(cls, json_str):
@ -89,13 +90,13 @@ class MlString(object):
def d_hash(self):
m = hashlib.md5()
for lang in sorted(list(self.values.keys())):
m.update(bytes(lang+";"+self.values[lang], 'utf-8'))
m.update(bytes(lang + ";" + self.values[lang], 'utf-8'))
return int.from_bytes(m.digest(), byteorder='big')
def __eq__(self, a):
return hash(self) == hash(a)
## @return The default langage translation or any available translation
# @return The default langage translation or any available translation
def __str__(self):
if self.__default_lang in self.values:
return self.values[self.__default_lang]

View file

@ -0,0 +1,2 @@
validator_PYTHON=*.py
validatordir=$(pkgpythondir)/validator

View file

@ -0,0 +1,6 @@
#-*- coding: utf-8 -*-
## @package lodel.validator Lodel2 validator package
#

View file

@ -9,55 +9,63 @@ import copy
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.mlnamedobject.mlnamedobject': ['MlNamedObject'],
'lodel.exceptions': ['LodelException', 'LodelExceptions',
'LodelFatalError', 'FieldValidationError']})
'LodelFatalError', 'FieldValidationError']})
## @package lodel.settings.validator Lodel2 settings validators/cast module
# @package lodel.settings.validator Lodel2 settings validators/cast module
#
# Validator are registered in the SettingValidator class.
# Validator are registered in the Validator class.
# @note to get a list of registered default validators just run
# <pre>$ python scripts/settings_validator.py</pre>
##@brief Exception class that should be raised when a validation fails
class SettingsValidationError(Exception):
# @brief Exception class that should be raised when a validation fails
class ValidationError(Exception):
pass
##@brief Handles settings validators
# @brief Handles settings validators
#
# Class instance are callable objects that takes a value argument (the value to validate). It raises
# a SettingsValidationError if validation fails, else it returns a properly
# a ValidationError if validation fails, else it returns a properly
# casted value.
#@todo implement an IP validator and use it in multisite confspec
class SettingValidator(object):
class Validator(MlNamedObject):
_validators = dict()
_description = dict()
##@brief Instanciate a validator
# @brief Instanciate a validator
#@param name str : validator name
#@param none_is_valid bool : if True None will be validated
#@param **kwargs : more arguement for the validator
def __init__(self, name, none_is_valid = False, **kwargs):
def __init__(self, name, none_is_valid=False, display_name=None, help_text=None, **kwargs):
if name is not None and name not in self._validators:
raise LodelFatalError("No validator named '%s'" % name)
self.__none_is_valid = none_is_valid
self.__name = name
self._opt_args = kwargs
if display_name is None:
display_name = name
super().__init__(display_name, help_text)
##@brief Call the validator
# @brief Call the validator
# @param value *
# @return properly casted value
# @throw SettingsValidationError
# @throw ValidationError
def __call__(self, value):
if self.__none_is_valid and value is None:
return None
try:
ret = self._validators[self.__name](value, **self._opt_args)
return ret
except Exception as e:
raise SettingsValidationError(e)
except Exception as exp:
raise ValidationError(exp)
##@brief Register a new validator
# @brief Register a new validator
# @param name str : validator name
# @param callback callable : the function that will validate a value
# @param description str
@ -71,75 +79,67 @@ class SettingValidator(object):
cls._validators[name] = callback
cls._description[name] = description
##@brief Get the validator list associated with description
# @brief Get the validator list associated with description
@classmethod
def validators_list(cls):
return copy.copy(cls._description)
##@brief Create and register a list validator
# @brief Create and register a list validator
# @param elt_validator callable : The validator that will be used for validate each elt value
# @param validator_name str
# @param description None | str
# @param separator str : The element separator
# @return A SettingValidator instance
# @return A Validator instance
@classmethod
def create_list_validator(cls, validator_name, elt_validator, description = None, separator = ','):
def create_list_validator(cls, validator_name, elt_validator, description=None, separator=','):
def list_validator(value):
res = list()
errors = list()
for elt in value.split(separator):
elt = elt_validator(elt)
if len(elt) > 0:
res.append(elt)
return res
description = "Convert value to an array" if description is None else description
cls.register_validator(
validator_name,
list_validator,
description)
cls.register_validator(validator_name, list_validator, description)
return cls(validator_name)
##@brief Create and register a list validator which reads an array and returns a string
# @brief Create and register a list validator which reads an array and returns a string
# @param elt_validator callable : The validator that will be used for validate each elt value
# @param validator_name str
# @param description None | str
# @param separator str : The element separator
# @return A SettingValidator instance
# @return A Validator instance
@classmethod
def create_write_list_validator(cls, validator_name, elt_validator, description = None, separator = ','):
def create_write_list_validator(cls, validator_name, elt_validator, description=None, separator=','):
def write_list_validator(value):
res = ''
errors = list()
for elt in value:
res += elt_validator(elt) + ','
return res[:len(res)-1]
return res[:len(res) - 1]
description = "Convert value to a string" if description is None else description
cls.register_validator(
validator_name,
write_list_validator,
description)
cls.register_validator(validator_name, write_list_validator, description)
return cls(validator_name)
##@brief Create and register a regular expression validator
# @brief Create and register a regular expression validator
# @param pattern str : regex pattern
# @param validator_name str : The validator name
# @param description str : Validator description
# @return a SettingValidator instance
# @return a Validator instance
@classmethod
def create_re_validator(cls, pattern, validator_name, description = None):
def create_re_validator(cls, pattern, validator_name, description=None):
def re_validator(value):
if not re.match(pattern, value):
raise SettingsValidationError("The value '%s' doesn't match the following pattern '%s'" % pattern)
raise ValidationError(
"The value '%s' doesn't match the following pattern '%s'"
% pattern)
return value
#registering the validator
cls.register_validator(
validator_name,
re_validator,
("Match value to '%s'" % pattern) if description is None else description)
# registering the validator
cls.register_validator(validator_name, re_validator,
("Match value to '%s'" % pattern)
if description is None else description)
return cls(validator_name)
## @return a list of registered validators
#  @return a list of registered validators
@classmethod
def validators_list_str(cls):
result = ''
@ -150,20 +150,26 @@ class SettingValidator(object):
result += "\n"
return result
##@brief Integer value validator callback
# @brief Integer value validator callback
def int_val(value):
return int(value)
##@brief Output file validator callback
# @brief Output file validator callback
# @return A file object (if filename is '-' return sys.stderr)
def file_err_output(value):
if not isinstance(value, str):
raise SettingsValidationError("A string was expected but got '%s' " % value)
raise ValidationError("A string was expected but got '%s' " % value)
if value == '-':
return None
return value
##@brief Boolean value validator callback
# @brief Boolean value validator callback
def boolean_val(value):
if isinstance(value, bool):
return value
@ -172,52 +178,66 @@ def boolean_val(value):
elif value.strip().lower() == 'false' or value.strip() == '0':
value = False
else:
raise SettingsValidationError("A boolean was expected but got '%s' " % value)
raise ValidationError("A boolean was expected but got '%s' " % value)
return bool(value)
##@brief Validate a directory path
# @brief Validate a directory path
def directory_val(value):
res = SettingValidator('strip')(value)
res = Validator('strip')(value)
if not os.path.isdir(res):
raise SettingsValidationError("Folowing path don't exists or is not a directory : '%s'"%res)
raise ValidationError("Following path don't exists or is not a directory : '%s'" % res)
return res
##@brief Validate a loglevel value
# @brief Validate a loglevel value
def loglevel_val(value):
valids = ['DEBUG', 'INFO', 'WARNING', 'SECURITY', 'ERROR', 'CRITICAL']
if value.upper() not in valids:
raise SettingsValidationError(
"The value '%s' is not a valid loglevel" % value)
raise ValidationError(
"The value '%s' is not a valid loglevel" % value)
return value.upper()
##@brief Validate a path
# @brief Validate a path
def path_val(value):
if value is None or not os.path.exists(value):
raise SettingsValidationError(
"path '%s' doesn't exists" % value)
raise ValidationError(
"path '%s' doesn't exists" % value)
return value
##@brief Validate None
# @brief Validate None
def none_val(value):
if value is None:
return None
raise SettingsValidationError("This settings cannot be set in configuration file")
raise ValidationError("This settings cannot be set in configuration file")
# @brief Validate a string
##@brief Validate a string
def str_val(value):
try:
return str(value)
except Exception as e:
raise SettingsValidationError("Not able to convert value to string : " + str(e))
except Exception as exp:
raise ValidationError("Can't to convert value to string: " + str(exp))
# @brief Validate using a regex
##@brief Validate using a regex
def regex_val(value, pattern):
if re.match(pattern, value) is None:
raise SettingsValidationError("The value '%s' is not validated by : \
r\"%s\"" %(value, pattern))
raise ValidationError("The value '%s' is not validated by : \
r\"%s\"" % (value, pattern))
return value
##@brief Validate a hostname (ipv4 or ipv6)
# @brief Validate a hostname (ipv4 or ipv6)
def host_val(value):
if value == 'localhost':
return value
@ -225,37 +245,103 @@ def host_val(value):
try:
socket.inet_aton(value)
return value
except (TypeError,OSError):
except (TypeError, OSError):
pass
try:
socket.inet_pton(socket.AF_INET6, value)
return value
except (TypeError,OSError):
except (TypeError, OSError):
pass
try:
socket.getaddrinfo(value, 80)
return value
except (TypeError,socket.gaierror):
except (TypeError, socket.gaierror):
msg = "The value '%s' is not a valid host"
raise SettingsValidationError(msg % value)
raise ValidationError(msg % value)
##@brief Validator for Editorial model component
def custom_list_validator(value, validator_name, validator_kwargs=None):
validator_kwargs = dict() if validator_kwargs is None else validator_kwargs
validator = Validator(validator_name, **validator_kwargs)
for item in value.split():
validator(item)
return value.split()
#
# Default validators registration
#
Validator.register_validator('custom_list', custom_list_validator,
'A list validator that takes a "validator_name" as argument')
Validator.register_validator('dummy', lambda value: value, 'Validate anything')
Validator.register_validator('none', none_val, 'Validate None')
Validator.register_validator('string', str_val, 'Validate string values')
Validator.register_validator('strip', str.strip, 'String trim')
Validator.register_validator('int', int_val, 'Integer value validator')
Validator.register_validator('bool', boolean_val, 'Boolean value validator')
Validator.register_validator('errfile', file_err_output,
'Error output file validator (return stderr if filename is "-")')
Validator.register_validator('directory', directory_val,
'Directory path validator')
Validator.register_validator('loglevel', loglevel_val, 'Loglevel validator')
Validator.register_validator('path', path_val, 'path validator')
Validator.register_validator('host', host_val, 'host validator')
Validator.register_validator('regex', regex_val,
'RegEx name validator (take re as argument)')
Validator.create_list_validator('list', Validator('strip'), description="Simple list validator. Validate a list of values separated by ','",
separator=',')
Validator.create_list_validator(
'directory_list',
Validator('directory'),
description="Validator for a list of directory path separated with ','",
separator=',')
Validator.create_write_list_validator(
'write_list',
Validator('directory'),
description="Validator for an array of values \
which will be set in a string, separated by ','",
separator=',')
Validator.create_re_validator(
r'^https?://[^\./]+.[^\./]+/?.*$',
'http_url',
'Url validator')
# @brief Validator for Editorial model component
#
# Designed to validate a conf that indicate a class.field in an EM
#@todo modified the hardcoded dyncode import (it's a warning)
def emfield_val(value):
LodelContext.expose_modules(globals(), {
'lodel.plugin.hooks': ['LodelHook']})
LodelContext.expose_modules(globals(),
{'lodel.plugin.hooks': ['LodelHook']})
spl = value.split('.')
if len(spl) != 2:
msg = "Expected a value in the form CLASSNAME.FIELDNAME but got : %s"
raise SettingsValidationError(msg % value)
value = tuple(spl)
#Late validation hook
# Late validation hook
@LodelHook('lodel2_dyncode_bootstraped')
def emfield_conf_check(hookname, caller, payload):
import leapi_dyncode as dyncode # <-- dirty & quick
classnames = { cls.__name__.lower():cls for cls in dyncode.dynclasses}
import leapi_dyncode as dyncode # <-- dirty & quick
classnames = {cls.__name__.lower(): cls for cls in dyncode.dynclasses}
if value[0].lower() not in classnames:
msg = "Following dynamic class do not exists in current EM : %s"
raise SettingsValidationError(msg % value[0])
@ -265,13 +351,16 @@ def emfield_val(value):
raise SettingsValidationError(msg % value)
return value
##@brief Validator for plugin name & optionnaly type
# @brief Validator for plugin name & optionnaly type
#
#Able to check that the value is a plugin and if it is of a specific type
def plugin_validator(value, ptype = None):
# Able to check that the value is a plugin and if it is of a specific type
def plugin_validator(value, ptype=None):
LodelContext.expose_modules(globals(), {
'lodel.plugin.hooks': ['LodelHook']})
value = copy.copy(value)
@LodelHook('lodel2_dyncode_bootstraped')
def plugin_type_checker(hookname, caller, payload):
LodelContext.expose_modules(globals(), {
@ -284,133 +373,39 @@ def plugin_validator(value, ptype = None):
except PluginError:
msg = "No plugin named %s found"
msg %= value
raise SettingsValidationError(msg)
raise ValidationError(msg)
if plugin._type_conf_name.lower() != ptype.lower():
msg = "A plugin of type '%s' was expected but found a plugin \
named '%s' that is a '%s' plugin"
msg %= (ptype, value, plugin._type_conf_name)
raise SettingsValidationError(msg)
raise ValidationError(msg)
return value
def custom_list_validator(value, validator_name, validator_kwargs = None):
validator_kwargs = dict() if validator_kwargs is None else validator_kwargs
validator = SettingValidator(validator_name, **validator_kwargs)
for item in value.split():
validator(item)
return value.split()
#
# Default validators registration
#
SettingValidator.register_validator(
Validator.register_validator(
'plugin',
plugin_validator,
'plugin name & type validator')
SettingValidator.register_validator(
'custom_list',
custom_list_validator,
'A list validator that takes a "validator_name" as argument')
SettingValidator.register_validator(
'dummy',
lambda value:value,
'Validate anything')
SettingValidator.register_validator(
'none',
none_val,
'Validate None')
SettingValidator.register_validator(
'string',
str_val,
'Validate string values')
SettingValidator.register_validator(
'strip',
str.strip,
'String trim')
SettingValidator.register_validator(
'int',
int_val,
'Integer value validator')
SettingValidator.register_validator(
'bool',
boolean_val,
'Boolean value validator')
SettingValidator.register_validator(
'errfile',
file_err_output,
'Error output file validator (return stderr if filename is "-")')
SettingValidator.register_validator(
'directory',
directory_val,
'Directory path validator')
SettingValidator.register_validator(
'loglevel',
loglevel_val,
'Loglevel validator')
SettingValidator.register_validator(
'path',
path_val,
'path validator')
SettingValidator.register_validator(
'host',
host_val,
'host validator')
SettingValidator.register_validator(
Validator.register_validator(
'emfield',
emfield_val,
'EmField name validator')
SettingValidator.register_validator(
'regex',
regex_val,
'RegEx name validator (take re as argument)')
SettingValidator.create_list_validator(
'list',
SettingValidator('strip'),
description = "Simple list validator. Validate a list of values separated by ','",
separator = ',')
SettingValidator.create_list_validator(
'directory_list',
SettingValidator('directory'),
description = "Validator for a list of directory path separated with ','",
separator = ',')
SettingValidator.create_write_list_validator(
'write_list',
SettingValidator('directory'),
description = "Validator for an array of values which will be set in a string, separated by ','",
separator = ',')
SettingValidator.create_re_validator(
r'^https?://[^\./]+.[^\./]+/?.*$',
'http_url',
'Url validator')
#
# Lodel 2 configuration specification
#
##@brief Append a piece of confspec
# @brief Append a piece of confspec
#@note orig is modified during the process
#@param orig dict : the confspec to update
#@param section str : section name
#@param key str
#@param validator SettingValidator : the validator to use to check this configuration key's value
#@param validator Validator : the validator to use to check this configuration key's value
#@param default
#@return new confspec
def confspec_append(orig, section, key, validator, default):
if section not in orig:
orig[section] = dict()
@ -418,41 +413,33 @@ def confspec_append(orig, section, key, validator, default):
orig[section][key] = (default, validator)
return orig
##@brief Global specifications for lodel2 settings
# @brief Global specifications for lodel2 settings
LODEL2_CONF_SPECS = {
'lodel2': {
'debug': ( True,
SettingValidator('bool')),
'sitename': ( 'noname',
SettingValidator('strip')),
'runtest': ( False,
SettingValidator('bool')),
'debug': (True, Validator('bool')),
'sitename': ('noname', Validator('strip')),
'runtest': (False, Validator('bool')),
},
'lodel2.logging.*' : {
'level': ( 'ERROR',
SettingValidator('loglevel')),
'context': ( False,
SettingValidator('bool')),
'filename': ( "-",
SettingValidator('errfile', none_is_valid = False)),
'backupcount': ( 5,
SettingValidator('int', none_is_valid = False)),
'maxbytes': ( 1024*10,
SettingValidator('int', none_is_valid = False)),
'lodel2.logging.*': {
'level': ('ERROR', Validator('loglevel')),
'context': (False, Validator('bool')),
'filename': ("-", Validator('errfile', none_is_valid=False)),
'backupcount': (5, Validator('int', none_is_valid=False)),
'maxbytes': (1024 * 10, Validator('int', none_is_valid=False)),
},
'lodel2.editorialmodel': {
'emfile': ( 'em.pickle', SettingValidator('strip')),
'emtranslator': ( 'picklefile', SettingValidator('strip')),
'dyncode': ( 'leapi_dyncode.py', SettingValidator('strip')),
'groups': ( '', SettingValidator('list')),
'editormode': ( False, SettingValidator('bool')),
'emfile': ('em.pickle', Validator('strip')),
'emtranslator': ('picklefile', Validator('strip')),
'dyncode': ('leapi_dyncode.py', Validator('strip')),
'groups': ('', Validator('list')),
'editormode': (False, Validator('bool')),
},
'lodel2.datasources.*': {
'read_only': (False, SettingValidator('bool')),
'identifier': ( None, SettingValidator('string')),
'read_only': (False, Validator('bool')),
'identifier': (None, Validator('string')),
},
'lodel2.auth': {
'login_classfield': ('user.login', SettingValidator('emfield')),
'pass_classfield': ('user.password', SettingValidator('emfield')),
'login_classfield': ('user.login', Validator('emfield')),
'pass_classfield': ('user.password', Validator('emfield')),
},
}

View file

@ -1,6 +1,6 @@
#-*- coding: utf-8 -*-
##@brief Loader for tests which do not need an lodel installation
# @brief Loader for tests which do not need an lodel installation
#
# Options
################
@ -19,20 +19,22 @@
#
#
import sys, os, os.path
import sys
import os
import os.path
import unittest
loader = unittest.TestLoader()
if ((len(sys.argv) > 1) and (sys.argv[1].startswith('-')) is False):
dpath = sys.argv[1]
dpath = sys.argv[1]
else:
dpath = '.'
dpath = '.'
suite = loader.discover('tests', pattern='nc_test*.py')
with open(dpath+'/nocontext_tests.log', 'w') as logfile:
with open(dpath + '/nocontext_tests.log', 'w') as logfile:
unittest.TextTestRunner(
logfile,
failfast = '-f' in sys.argv,
verbosity = 2 if '-v' in sys.argv else 1).run(suite)
logfile,
failfast='-f' in sys.argv,
verbosity=2 if '-v' in sys.argv else 1).run(suite)

View file

@ -2,5 +2,5 @@
import sys
import os, os.path
sys.path.append(os.path.dirname(os.getcwd()+'/..'))
from lodel.settings.validator import SettingValidator
print(SettingValidator.validators_list_str())
from lodel.validator.validator import Validator
print(Validator.validators_list_str())

View file

@ -5,52 +5,52 @@ from unittest import mock
from unittest.mock import patch
from lodel.exceptions import *
from lodel.settings.validator import *
from lodel.validator.validator import *
class SettingValidatorTestCase(unittest.TestCase):
class ValidatorTestCase(unittest.TestCase):
def test_init_basic(self):
""" Testing the SettingsValidator class instanciation"""
valid = SettingValidator('string')
valid = Validator('string')
#trying to call it
valid('test')
def test_init_badname(self):
""" Testing SettingValidator instanciation with non existing validator
""" Testing Validator instanciation with non existing validator
name"""
with self.assertRaises(LodelFatalError):
SettingValidator('qklfhsdufgsdyfugigsdfsdlcknsdp')
Validator('qklfhsdufgsdyfugigsdfsdlcknsdp')
def test_noneswitch(self):
""" Testing the none_is_valid switch given at validator instanciation
"""
none_invalid = SettingValidator('int')
none_valid = SettingValidator('int', none_is_valid = True)
none_invalid = Validator('int')
none_valid = Validator('int', none_is_valid = True)
none_valid(None)
with self.assertRaises(SettingsValidationError):
with self.assertRaises(ValidationError):
none_invalid(None)
def test_validator_registration(self):
""" Testing the register_validator method of SettingValidator """
""" Testing the register_validator method of Validator """
mockfun = mock.MagicMock()
vname = 'lfkjdshfkuhsdygsuuyfsduyf'
testval = 'foo'
SettingValidator.register_validator(vname, mockfun, 'test validator')
Validator.register_validator(vname, mockfun, 'test validator')
#Using registered validator
valid = SettingValidator(vname)
valid = Validator(vname)
valid(testval)
mockfun.assert_called_once_with(testval)
def test_validator_optargs_forwarding(self):
""" Testing the ability for SettingValidator to forward optional
""" Testing the ability for Validator to forward optional
arguments """
mockfun = mock.MagicMock()
vname = 'lkjdsfhsdiufhisduguig'
testval = 'azertyuiop'
SettingValidator.register_validator(vname, mockfun, 'test validator')
Validator.register_validator(vname, mockfun, 'test validator')
#Using registered validator with more arguments
valid = SettingValidator(vname,
valid = Validator(vname,
arga = 'a', argb = 42, argc = '1337')
valid(testval)
mockfun.assert_called_once_with(