1
0
Fork 0
mirror of https://github.com/yweber/lodel2.git synced 2025-11-02 04:20:55 +01:00

Only indentation and stuff like that

This commit is contained in:
prieto 2017-02-10 14:15:46 +01:00
commit 3b4494cb7a
8 changed files with 406 additions and 349 deletions

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
@ -17,23 +17,25 @@ LodelContext.expose_modules(globals(), {
'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(MlNamedObject):
##@brief Instanciate an EmComponent
# @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.group = group
super().__init__(display_name, help_text)
def __str__(self):
if self.display_name is None:
return str(self.uid)
@ -42,34 +44,34 @@ class EmComponent(MlNamedObject):
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
#@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,49 +87,50 @@ 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
self.__fields = dict()
# @brief Stores EmFields instances indexed by field uid
self.__fields = dict()
self.group = group
if group is None:
warnings.warn("NO GROUP FOR EMCLASS %s" % uid)
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,38 +173,40 @@ 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__() )
# @param **field_kwargs : EmField constructor parameters ( see @ref EmField.__init__() )
def new_field(self, uid, data_handler, **field_kwargs):
assert_edit()
return self.add_field(EmField(uid, data_handler, self, **field_kwargs))
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())
for fuid in sorted(self.__fields.keys()):
payload += str(self.__fields[fuid].d_hash())
m.update(bytes(payload, 'utf-8'))
return int.from_bytes(m.digest(), byteorder='big')
def __str__(self):
return "<class EmClass %s>" % self.uid
def __repr__(self):
if not self.abstract:
abstract = ''
@ -209,80 +214,82 @@ 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(self.data_handler_cls)
##@brief Returne the uid of the emclass which contains this field
def get_emclass_uid(self):
return self._emclass.uid
# @warning Not complete !
# @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(MlNamedObject):
##@brief Create a new EmGroup
# @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):
# @param display_name MlString|str :
# @param help_text MlString|str :
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)
@ -291,11 +298,11 @@ class EmGroup(MlNamedObject):
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
@ -307,11 +314,11 @@ class EmGroup(MlNamedObject):
to_scan.append(new_dep)
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
@ -323,29 +330,31 @@ class EmGroup(MlNamedObject):
to_scan.append(new_app)
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);
##@brief Add components in a group
help = self.help_text
if help is None:
return None
return help.get(lang)
# @brief Add components in a group
# @param components list : EmComponent instances list
def add_components(self, components):
assert_edit()
@ -356,10 +365,11 @@ class EmGroup(MlNamedObject):
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()
@ -367,16 +377,17 @@ class EmGroup(MlNamedObject):
for group in grp:
self.add_dependencie(group)
return
except TypeError: pass
except TypeError:
pass
if grp.uid in self.require:
return
if self.__circular_dependencie(grp):
raise EditorialModelError("Circular dependencie detected, cannot add dependencie")
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):
@ -385,26 +396,27 @@ class EmGroup(MlNamedObject):
for group in grp:
self.add_applicant(group)
return
except TypeError: pass
except TypeError:
pass
if grp.uid in self.required_by:
return
if self.__circular_applicant(grp):
raise EditorialModelError("Circular applicant detected, cannot add applicant")
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:
@ -413,11 +425,11 @@ class EmGroup(MlNamedObject):
return self.display_name.get()
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)
@ -426,11 +438,11 @@ class EmGroup(MlNamedObject):
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

@ -15,23 +15,23 @@ 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(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, display_name = None, help_text = 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:
@ -39,10 +39,10 @@ class EditorialModel(MlNamedObject):
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:
@ -50,8 +50,8 @@ class EditorialModel(MlNamedObject):
return copy.copy(self.__classes[uid])
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:
@ -59,16 +59,15 @@ class EditorialModel(MlNamedObject):
return self.__classes[uid]
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())
##@brief EmGroups accessor
return list(self.__active_classes.keys())
# @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:
@ -76,10 +75,10 @@ class EditorialModel(MlNamedObject):
return copy.copy(self.__groups[uid])
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:
@ -87,26 +86,26 @@ class EditorialModel(MlNamedObject):
return self.__groups[uid]
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):
@ -117,24 +116,23 @@ class EditorialModel(MlNamedObject):
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")
@ -142,7 +140,7 @@ class EditorialModel(MlNamedObject):
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:
@ -159,13 +157,13 @@ class EditorialModel(MlNamedObject):
raise RuntimeError("No active class found. Abording")
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)
@ -181,7 +179,7 @@ class EditorialModel(MlNamedObject):
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):
@ -193,7 +191,7 @@ class EditorialModel(MlNamedObject):
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):
@ -205,15 +203,15 @@ class EditorialModel(MlNamedObject):
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 (
#@param **kwargs : EmClass constructor options (
# see @ref lodel.editorial_model.component.EmClass.__init__() )
def new_class(self, uid, **kwargs):
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__() )
@ -221,7 +219,7 @@ class EditorialModel(MlNamedObject):
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):
@ -229,14 +227,15 @@ class EditorialModel(MlNamedObject):
if isinstance(translator, str):
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
@ -247,7 +246,7 @@ class EditorialModel(MlNamedObject):
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
@ -259,12 +258,12 @@ class EditorialModel(MlNamedObject):
except ImportError:
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())
@ -273,7 +272,6 @@ class EditorialModel(MlNamedObject):
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

@ -4,11 +4,13 @@ from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.utils.mlstring': ['MlString']})
## @package lodel.mlnamedobject Lodel2 description of objects module
# @package lodel.mlnamedobject Lodel2 description of objects module
#
# Display name and Description of a lodel2 object
##@brief Class allows dislpay name and help text for lodel2 objects and fields
# @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):

View file

@ -5,40 +5,42 @@ 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.validator.validator': ['Validator', 'LODEL2_CONF_SPECS',
'confspec_append'],
'lodel.settings.settings_loader':['SettingsLoader']})
'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
# To see howto bootstrap Settings and use it in lodel instance see
# @ref lodel.settings
#
#
# @par Basic instance usage
# For example if a file defines confs like :
# <pre>
@ -50,15 +52,15 @@ class MetaSettings(type):
#
# @par Init sequence
# The initialization sequence is a bit tricky. In fact, plugins adds allowed
# configuration sections/values, but the list of plugins to load are in... the
# configuration sections/values, but the list of plugins to load are in... the
# settings.
# 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
# -# Ask a @ref lodel.settings.setting_loader.SettingsLoader to load all
# 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 )
# -# Merge plugins settings specification with the global lodel settings
# specs ( see @ref lodel.plugin )
# -# Fetch all settings from the merged settings specs
#
# @par Init sequence in practical
@ -68,39 +70,41 @@ class MetaSettings(type):
# -# @ref Settings.__populate_from_specs() (step 5)
# -# And finally @ref Settings.__confs_to_namedtuple()
#
# @todo handles default sections for variable sections (sections ending with
# @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']})
@ -144,9 +148,9 @@ class Settings(object, metaclass=MetaSettings):
else:
lodel2_specs = self.__conf_specs
self.__conf_specs = None
loader = SettingsLoader(self.__conf_dir)
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
@ -238,8 +248,8 @@ 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,35 +277,35 @@ 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'
nodename = 'Lodel2Settings'
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

@ -181,7 +181,7 @@ class SettingsLoader(object):
for key_id, filename in remains.items():
err_l.append(SettingsError(msg="Invalid configuration key", \
key_id=key_id, \
filename=filename))
filename =filename))
if len(err_l) > 0:
raise SettingsErrors(err_l)
else:

View file

@ -6,4 +6,4 @@ def get_utc_timestamp():
d = datetime.datetime.utcnow()
epoch = datetime.datetime(1970, 1, 1)
t = (d - epoch).total_seconds()
return t
return t

View file

@ -5,9 +5,9 @@ import hashlib
import json
##@brief Stores multilangage string
# @brief Stores multilangage string
class MlString(object):
__default_lang = 'eng'
langs = [
@ -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))
##@brief Return a translation given a lang
raise ValueError(
'<class str>, <class dict> or <class MlString> expected, but %s found' % type(arg))
# @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

@ -11,30 +11,34 @@ 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 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
# @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 ValidationError if validation fails, else it returns a properly
# casted value.
#@todo implement an IP validator and use it in multisite confspec
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
@ -48,7 +52,7 @@ class Validator(MlNamedObject):
display_name = name
super().__init__(display_name, help_text)
##@brief Call the validator
# @brief Call the validator
# @param value *
# @return properly casted value
# @throw ValidationError
@ -61,7 +65,7 @@ class Validator(MlNamedObject):
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
@ -75,12 +79,12 @@ class Validator(MlNamedObject):
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
@ -99,7 +103,7 @@ class Validator(MlNamedObject):
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
@ -111,12 +115,12 @@ class Validator(MlNamedObject):
res = ''
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)
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
@ -125,17 +129,17 @@ class Validator(MlNamedObject):
def create_re_validator(cls, pattern, validator_name, description=None):
def re_validator(value):
if not re.match(pattern, value):
raise ValidationError(\
"The value '%s' doesn't match the following pattern '%s'" \
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 = ''
@ -146,12 +150,16 @@ class Validator(MlNamedObject):
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 ValidationError("A string was expected but got '%s' " % value)
@ -159,7 +167,9 @@ def file_err_output(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
@ -171,49 +181,63 @@ def boolean_val(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 = Validator('strip')(value)
if not os.path.isdir(res):
raise ValidationError("Following 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 ValidationError( \
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 ValidationError( \
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 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 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 ValidationError("The value '%s' is not validated by : \
r\"%s\"" %(value, pattern))
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
@ -235,6 +259,7 @@ def host_val(value):
msg = "The value '%s' is not a valid host"
raise ValidationError(msg % value)
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)
@ -246,8 +271,8 @@ def custom_list_validator(value, validator_name, validator_kwargs=None):
# Default validators registration
#
Validator.register_validator('custom_list', custom_list_validator, \
'A list validator that takes a "validator_name" as argument')
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')
@ -261,11 +286,11 @@ 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('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('directory', directory_val,
'Directory path validator')
Validator.register_validator('loglevel', loglevel_val, 'Loglevel validator')
@ -273,48 +298,50 @@ 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.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 ','", \
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_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'), \
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', \
Validator.create_re_validator(
r'^https?://[^\./]+.[^\./]+/?.*$',
'http_url',
'Url validator')
##@brief Validator for Editorial model component
# @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])
@ -324,17 +351,20 @@ 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
# 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(), { \
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(), { \
'lodel.plugin.plugins': ['Plugin'], \
LodelContext.expose_modules(globals(), {
'lodel.plugin.plugins': ['Plugin'],
'lodel.plugin.exceptions': ['PluginError']})
if value is None:
return
@ -352,21 +382,21 @@ named '%s' that is a '%s' plugin"
return value
Validator.register_validator( \
'plugin', \
plugin_validator, \
Validator.register_validator(
'plugin',
plugin_validator,
'plugin name & type validator')
Validator.register_validator( \
'emfield', \
emfield_val, \
Validator.register_validator(
'emfield',
emfield_val,
'EmField name 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
@ -374,6 +404,8 @@ Validator.register_validator( \
#@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()
@ -381,19 +413,19 @@ 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, Validator('bool')),
'sitename': ('noname', Validator('strip')),
'runtest': (False, Validator('bool')),
},
'lodel2.logging.*' : {
'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)),
'maxbytes': (1024 * 10, Validator('int', none_is_valid=False)),
},
'lodel2.editorialmodel': {
'emfile': ('em.pickle', Validator('strip')),