1
0
Fork 0
mirror of https://github.com/yweber/lodel2.git synced 2026-07-13 10:41:59 +02: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/leapi/datahandlers/Makefile \
lodel/plugin/Makefile \ lodel/plugin/Makefile \
lodel/settings/Makefile \ lodel/settings/Makefile \
lodel/validator/Makefile \
lodel/mlnamedobject/Makefile \
lodel/utils/Makefile \ lodel/utils/Makefile \
progs/Makefile \ progs/Makefile \
progs/slim/Makefile \ progs/slim/Makefile \

View file

@ -691,7 +691,7 @@ user.new_field(
group = user_group, data_handler = 'password', internal = False) 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' pickle_file = 'examples/em_simple.pickle'
em.save('picklefile', filename = pickle_file) em.save('picklefile', filename = pickle_file)
print("Output written in %s" % 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 EXTRA_DIST = plugins
lodel_PYTHON = *.py lodel_PYTHON = *.py
CLEANFILES = buildconf.py CLEANFILES = buildconf.py

View file

@ -1,6 +1,6 @@
#-*- coding: utf-8 -*- #-*- coding: utf-8 -*-
##@package lodel.editorial_model.components # @package lodel.editorial_model.components
#@brief Defines all @ref lodel2_em "EM" components #@brief Defines all @ref lodel2_em "EM" components
#@ingroup lodel2_em #@ingroup lodel2_em
@ -12,17 +12,20 @@ import hashlib
from lodel.context import LodelContext from lodel.context import LodelContext
LodelContext.expose_modules(globals(), { LodelContext.expose_modules(globals(), {
'lodel.utils.mlstring': ['MlString'], 'lodel.utils.mlstring': ['MlString'],
'lodel.mlnamedobject.mlnamedobject': ['MlNamedObject'],
'lodel.settings': ['Settings'], 'lodel.settings': ['Settings'],
'lodel.editorial_model.exceptions': ['EditorialModelError', 'assert_edit'], 'lodel.editorial_model.exceptions': ['EditorialModelError', 'assert_edit'],
'lodel.leapi.leobject': ['CLASS_ID_FIELDNAME']}) '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 # @see EmClass EmField
# @todo forbid '.' in uid # @todo forbid '.' in uid
#@ingroup lodel2_em #@ingroup lodel2_em
class EmComponent(object):
##@brief Instanciate an EmComponent
class EmComponent(MlNamedObject):
# @brief Instanciate an EmComponent
# @param uid str : uniq identifier # @param uid str : uniq identifier
# @param display_name MlString|str|dict : component display_name # @param display_name MlString|str|dict : component display_name
# @param help_text MlString|str|dict : help_text # @param help_text MlString|str|dict : help_text
@ -30,9 +33,8 @@ class EmComponent(object):
if self.__class__ == EmComponent: if self.__class__ == EmComponent:
raise NotImplementedError('EmComponent is an abstract class') raise NotImplementedError('EmComponent is an abstract class')
self.uid = uid 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 self.group = group
super().__init__(display_name, help_text)
def __str__(self): def __str__(self):
if self.display_name is None: if self.display_name is None:
@ -51,11 +53,11 @@ class EmComponent(object):
return int.from_bytes(m.digest(), byteorder='big') return int.from_bytes(m.digest(), byteorder='big')
##@brief Handles editorial model objects classes # @brief Handles editorial model objects classes
#@ingroup lodel2_em #@ingroup lodel2_em
class EmClass(EmComponent): class EmClass(EmComponent):
##@brief Instanciate a new EmClass # @brief Instanciate a new EmClass
#@param uid str : uniq identifier #@param uid str : uniq identifier
#@param display_name MlString|str|dict : component display_name #@param display_name MlString|str|dict : component display_name
#@param abstract bool : set the class as asbtract if True #@param abstract bool : set the class as asbtract if True
@ -85,11 +87,12 @@ class EmClass(EmComponent):
parents = [parents] parents = [parents]
for parent in parents: for parent in parents:
if not isinstance(parent, EmClass): 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: else:
parents = list() parents = list()
self.parents = parents self.parents = parents
##@brief Stores EmFields instances indexed by field uid # @brief Stores EmFields instances indexed by field uid
self.__fields = dict() self.__fields = dict()
self.group = group self.group = group
@ -112,7 +115,7 @@ class EmClass(EmComponent):
internal=True, internal=True,
group=group) 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 # @todo use Settings.editorialmodel.groups to determine wich fields should be returned
@property @property
def __all_fields(self): def __all_fields(self):
@ -122,12 +125,12 @@ class EmClass(EmComponent):
res.update(self.__fields) res.update(self.__fields)
return res return res
##@brief RO access to datasource attribute # @brief RO access to datasource attribute
@property @property
def datasource(self): def datasource(self):
return self.__datasource return self.__datasource
##@brief Return the list of all dependencies # @brief Return the list of all dependencies
# #
# Reccursive parents listing # Reccursive parents listing
@property @property
@ -140,7 +143,7 @@ class EmClass(EmComponent):
res |= parent.parents_recc res |= parent.parents_recc
return res 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 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 # @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 # @return A list on EmFields instances (if uid is None) else return an EmField instance
@ -152,7 +155,7 @@ class EmClass(EmComponent):
except KeyError: except KeyError:
raise EditorialModelError("No such EmField '%s'" % uid) 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): def _set_active_fields(self, active_groups):
if not Settings.editorialmodel.editormode: if not Settings.editorialmodel.editormode:
active_fields = [] active_fields = []
@ -162,7 +165,7 @@ class EmClass(EmComponent):
self.__fields = {fname: fdh for fname, fdh in self.__fields.items() self.__fields = {fname: fdh for fname, fdh in self.__fields.items()
if fdh in active_fields} 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 # @param emfield EmField : an EmField instance
# @warning do not add an EmField allready in another class ! # @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) # @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): def add_field(self, emfield):
assert_edit() assert_edit()
if emfield.uid in self.__fields: 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 # Incomplete field override check
if emfield.uid in self.__all_fields: if emfield.uid in self.__all_fields:
parent_field = self.__all_fields[emfield.uid] parent_field = self.__all_fields[emfield.uid]
if not emfield.data_handler_instance.can_override(parent_field.data_handler_instance): 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 self.__fields[emfield.uid] = emfield
return 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 data_handler str : A DataHandler name
# @param uid str : the EmField uniq id # @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__() )
@ -212,11 +217,11 @@ class EmClass(EmComponent):
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 #@ingroup lodel2_em
class EmField(EmComponent): class EmField(EmComponent):
##@brief Instanciate a new EmField # @brief Instanciate a new EmField
# @param uid str : uniq identifier # @param uid str : uniq identifier
# @param display_name MlString|str|dict : field display_name # @param display_name MlString|str|dict : field display_name
# @param data_handler str : A DataHandler name # @param data_handler str : A DataHandler name
@ -226,15 +231,15 @@ class EmField(EmComponent):
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 from lodel.leapi.datahandlers.base_classes import DataHandler
super().__init__(uid, display_name, help_text, group) super().__init__(uid, display_name, help_text, group)
##@brief The data handler name # @brief The data handler name
self.data_handler_name = data_handler 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) 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) 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 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 self._emclass = em_class
if self._emclass is None: if self._emclass is None:
warnings.warn("No EmClass for field %s" % uid) warnings.warn("No EmClass for field %s" % uid)
@ -243,13 +248,13 @@ class EmField(EmComponent):
else: else:
group.add_components([self]) group.add_components([self])
##@brief Returns data_handler_name attribute # @brief Returns data_handler_name attribute
def get_data_handler_name(self): def get_data_handler_name(self):
return copy.copy(self.data_handler_name) 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): 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 ##@brief Returne the uid of the emclass which contains this field
def get_emclass_uid(self): def get_emclass_uid(self):
@ -266,11 +271,13 @@ class EmField(EmComponent):
'utf-8') 'utf-8')
).digest(), byteorder='big') ).digest(), byteorder='big')
##@brief Handles functionnal group of EmComponents # @brief Handles functionnal group of EmComponents
#@ingroup lodel2_em #@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 # @note you should NEVER call the constructor yourself. Use Model.add_group instead
# @param uid str : Uniq identifier # @param uid str : Uniq identifier
# @param depends list : A list of EmGroup dependencies # @param depends list : A list of EmGroup dependencies
@ -278,22 +285,21 @@ class EmGroup(object):
# @param help_text 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 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() 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() 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() 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: if depends is not None:
for grp in depends: for grp in depends:
if not isinstance(grp, EmGroup): if not isinstance(grp, EmGroup):
raise ValueError("EmGroup expected in depends argument but %s found" % grp) raise ValueError("EmGroup expected in depends argument but %s found" % grp)
self.add_dependencie(grp) self.add_dependencie(grp)
##@brief Returns EmGroup dependencie # @brief Returns EmGroup dependencie
# @param recursive bool : if True return all dependencies and their dependencies # @param recursive bool : if True return all dependencies and their dependencies
# @return a dict of EmGroup identified by uid # @return a dict of EmGroup identified by uid
def dependencies(self, recursive=False): def dependencies(self, recursive=False):
@ -309,7 +315,7 @@ class EmGroup(object):
res[new_dep.uid] = new_dep res[new_dep.uid] = new_dep
return res return res
##@brief Returns EmGroup applicants # @brief Returns EmGroup applicants
# @param recursive bool : if True return all dependencies and their dependencies # @param recursive bool : if True return all dependencies and their dependencies
# @returns a dict of EmGroup identified by uid # @returns a dict of EmGroup identified by uid
def applicants(self, recursive=False): def applicants(self, recursive=False):
@ -325,28 +331,30 @@ class EmGroup(object):
res[new_app.uid] = new_app res[new_app.uid] = new_app
return res return res
##@brief Returns EmGroup components # @brief Returns EmGroup components
# @returns a copy of the set of components # @returns a copy of the set of components
def components(self): def components(self):
return (self.__components).copy() return (self.__components).copy()
##@brief Returns EmGroup display_name # @brief Returns EmGroup display_name
# @param lang str | None : If None return default lang translation # @param lang str | None : If None return default lang translation
# @returns None if display_name is None, a str for display_name else # @returns None if display_name is None, a str for display_name else
def get_display_name(self, lang=None): def get_display_name(self, lang=None):
name = self.display_name name = self.display_name
if name is None : return None if name is None:
return name.get(lang); 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 # @param lang str | None : If None return default lang translation
# @returns None if display_name is None, a str for display_name else # @returns None if display_name is None, a str for display_name else
def get_help_text(self, lang=None): def get_help_text(self, lang=None):
help = self.help_text help = self.help_text
if help is None : return None if help is None:
return help.get(lang); return None
return help.get(lang)
##@brief Add components in a group # @brief Add components in a group
# @param components list : EmComponent instances list # @param components list : EmComponent instances list
def add_components(self, components): def add_components(self, components):
assert_edit() assert_edit()
@ -357,10 +365,11 @@ class EmGroup(object):
msg %= (component, self) msg %= (component, self)
warnings.warn(msg) warnings.warn(msg)
elif not isinstance(component, EmClass): 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) self.__components |= set(components)
##@brief Add a dependencie # @brief Add a dependencie
# @param em_group EmGroup|iterable : an EmGroup instance or list of instance # @param em_group EmGroup|iterable : an EmGroup instance or list of instance
def add_dependencie(self, grp): def add_dependencie(self, grp):
assert_edit() assert_edit()
@ -368,7 +377,8 @@ class EmGroup(object):
for group in grp: for group in grp:
self.add_dependencie(group) self.add_dependencie(group)
return return
except TypeError: pass except TypeError:
pass
if grp.uid in self.require: if grp.uid in self.require:
return return
@ -377,7 +387,7 @@ class EmGroup(object):
self.require[grp.uid] = grp self.require[grp.uid] = grp
grp.required_by[self.uid] = self 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 # @param em_group EmGroup|iterable : an EmGroup instance or list of instance
# Useless ??? # Useless ???
def add_applicant(self, grp): def add_applicant(self, grp):
@ -386,7 +396,8 @@ class EmGroup(object):
for group in grp: for group in grp:
self.add_applicant(group) self.add_applicant(group)
return return
except TypeError: pass except TypeError:
pass
if grp.uid in self.required_by: if grp.uid in self.required_by:
return return
@ -395,17 +406,17 @@ class EmGroup(object):
self.required_by[grp.uid] = grp self.required_by[grp.uid] = grp
grp.require[self.uid] = self grp.require[self.uid] = self
##@brief Search for circular dependencie # @brief Search for circular dependencie
# @return True if circular dep found else False # @return True if circular dep found else False
def __circular_dependencie(self, new_dep): def __circular_dependencie(self, new_dep):
return self.uid in new_dep.dependencies(True) 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 # @return True if circular app found else False
def __circular_applicant(self, new_app): def __circular_applicant(self, new_app):
return self.uid in new_app.applicants(True) 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 # @return a string
def __str__(self): def __str__(self):
if self.display_name is None: if self.display_name is None:
@ -431,7 +442,7 @@ class EmGroup(object):
byteorder='big' byteorder='big'
) )
##@brief Complete string representation of an EmGroup # @brief Complete string representation of an EmGroup
# @return a string # @return a string
def __repr__(self): 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 from lodel.context import LodelContext
LodelContext.expose_modules(globals(), { LodelContext.expose_modules(globals(), {
'lodel.utils.mlstring': ['MlString'], 'lodel.utils.mlstring': ['MlString'],
'lodel.mlnamedobject.mlnamedobject': ['MlNamedObject'],
'lodel.logger': 'logger', 'lodel.logger': 'logger',
'lodel.settings': ['Settings'], 'lodel.settings': ['Settings'],
'lodel.settings.utils': ['SettingsError'], 'lodel.settings.utils': ['SettingsError'],
@ -14,27 +15,32 @@ LodelContext.expose_modules(globals(), {
'lodel.editorial_model.components': ['EmClass', 'EmField', 'EmGroup']}) 'lodel.editorial_model.components': ['EmClass', 'EmField', 'EmGroup']})
##@brief Describe an editorial model # @brief Describe an editorial model
#@ingroup lodel2_em #@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 name MlString|str|dict : the editorial model name
# @param description MlString|str|dict : the editorial model description # @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.name = MlString(name)
self.description = MlString(description) self.description = MlString(description)
##@brief Stores all groups indexed by id # @brief Stores all groups indexed by id
self.__groups = dict() self.__groups = dict()
##@brief Stores all classes indexed by id # @brief Stores all classes indexed by id
self.__classes = dict() self.__classes = dict()
## @brief Stores all activated groups indexed by id #  @brief Stores all activated groups indexed by id
self.__active_groups = dict() 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.__active_classes = dict()
self.__set_actives() 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 #@return a dict of emclasses
def all_classes(self, uid=None): def all_classes(self, uid=None):
if uid is None: if uid is None:
@ -54,13 +60,12 @@ class EditorialModel(object):
except KeyError: except KeyError:
raise EditorialModelException("EmGroup not found : '%s'" % uid) raise EditorialModelException("EmGroup not found : '%s'" % uid)
##@brief active EmClass uids accessor # @brief active EmClass uids accessor
#@return a list of class uids #@return a list of class uids
def active_classes_uids(self): 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 #@return a dict of groups
def all_groups(self, uid=None): def all_groups(self, uid=None):
if uid is None: if uid is None:
@ -71,7 +76,7 @@ class EditorialModel(object):
except KeyError: except KeyError:
raise EditorialModelException("EmGroup not found : '%s'" % uid) raise EditorialModelException("EmGroup not found : '%s'" % uid)
##@brief EmGroups accessor # @brief EmGroups accessor
#@return a dict of groups #@return a dict of groups
def all_groups_ref(self, uid=None): def all_groups_ref(self, uid=None):
if uid is None: if uid is None:
@ -82,12 +87,12 @@ class EditorialModel(object):
except KeyError: except KeyError:
raise EditorialModelException("EmGroup not found : '%s'" % uid) raise EditorialModelException("EmGroup not found : '%s'" % uid)
##@brief active EmClass uids accessor # @brief active EmClass uids accessor
#@return a list of class uids #@return a list of class uids
def active_groups_uids(self): 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 #@param uid None | str : give this argument to get a specific EmClass
#@return if uid is given returns an EmClass else returns an EmClass #@return if uid is given returns an EmClass else returns an EmClass
# iterator # iterator
@ -100,7 +105,7 @@ class EditorialModel(object):
except KeyError: except KeyError:
raise EditorialModelException("EmClass not found : '%s'" % uid) raise EditorialModelException("EmClass not found : '%s'" % uid)
##@brief EmClass child list accessor # @brief EmClass child list accessor
#@param uid str : the EmClass uid #@param uid str : the EmClass uid
#@return a set of EmClass #@return a set of EmClass
def get_class_childs(self, uid): def get_class_childs(self, uid):
@ -111,8 +116,7 @@ class EditorialModel(object):
res.append(cls) res.append(cls)
return set(res) return set(res)
# @brief EmGroup getter
##@brief EmGroup getter
# @param uid None | str : give this argument to get a specific EmGroup # @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 # @return if uid is given returns an EmGroup else returns an EmGroup iterator
def groups(self, uid=None): def groups(self, uid=None):
@ -122,12 +126,12 @@ class EditorialModel(object):
except KeyError: except KeyError:
raise EditorialModelException("EmGroup not found : '%s'" % uid) raise EditorialModelException("EmGroup not found : '%s'" % uid)
##@brief Private getter for __groups or __classes # @brief Private getter for __groups or __classes
# @see classes() groups() # @see classes() groups()
def __elt_getter(self, elts, uid): def __elt_getter(self, elts, uid):
return list(elts.values()) if uid is None else elts[uid] return list(elts.values()) if uid is None else elts[uid]
##@brief Update the EditorialModel.__active_groups and # @brief Update the EditorialModel.__active_groups and
# EditorialModel.__active_classes attibutes # EditorialModel.__active_classes attibutes
def __set_actives(self): def __set_actives(self):
if Settings.editorialmodel.editormode: if Settings.editorialmodel.editormode:
@ -154,7 +158,7 @@ class EditorialModel(object):
for clsname, acls in self.__active_classes.items(): for clsname, acls in self.__active_classes.items():
acls._set_active_fields(self.__active_groups) acls._set_active_fields(self.__active_groups)
##@brief EmField getter # @brief EmField getter
# @param uid str : An EmField uid represented by "CLASSUID.FIELDUID" # @param uid str : An EmField uid represented by "CLASSUID.FIELDUID"
# @return Fals or an EmField instance # @return Fals or an EmField instance
# #
@ -175,7 +179,7 @@ class EditorialModel(object):
pass pass
return False 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 # @param emclass EmClass : the EmClass instance to add
# @return emclass # @return emclass
def add_class(self, emclass): def add_class(self, emclass):
@ -187,7 +191,7 @@ class EditorialModel(object):
self.__classes[emclass.uid] = emclass self.__classes[emclass.uid] = emclass
return 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 # @param emgroup EmGroup : the EmGroup instance to add
# @return emgroup # @return emgroup
def add_group(self, emgroup): def add_group(self, emgroup):
@ -199,7 +203,7 @@ class EditorialModel(object):
self.__groups[emgroup.uid] = emgroup self.__groups[emgroup.uid] = emgroup
return 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 uid str : EmClass uid
#@param **kwargs : EmClass constructor options ( #@param **kwargs : EmClass constructor options (
# see @ref lodel.editorial_model.component.EmClass.__init__() ) # see @ref lodel.editorial_model.component.EmClass.__init__() )
@ -207,7 +211,7 @@ class EditorialModel(object):
assert_edit() assert_edit()
return self.add_class(EmClass(uid, **kwargs)) 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 uid str : EmGroup uid
#@param *kwargs : EmGroup constructor keywords arguments ( #@param *kwargs : EmGroup constructor keywords arguments (
# see @ref lodel.editorial_model.component.EmGroup.__init__() ) # see @ref lodel.editorial_model.component.EmGroup.__init__() )
@ -215,7 +219,7 @@ class EditorialModel(object):
assert_edit() assert_edit()
return self.add_group(EmGroup(uid, **kwargs)) 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 module : The translator module to use
# @param **translator_args # @param **translator_args
def save(self, translator, **translator_kwargs): def save(self, translator, **translator_kwargs):
@ -224,13 +228,14 @@ class EditorialModel(object):
translator = self.translator_from_name(translator) translator = self.translator_from_name(translator)
return translator.save(self, **translator_kwargs) 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 @staticmethod
def raise_if_ro(): def raise_if_ro():
if not Settings.editorialmodel.editormode: 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 module : The translator module to use
# @param **translator_args # @param **translator_args
@classmethod @classmethod
@ -241,7 +246,7 @@ class EditorialModel(object):
res.__set_actives() res.__set_actives()
return res 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 # @param translator_name str : The translator name
# @return the translator python module # @return the translator python module
# @throw NameError if the translator does not exists # @throw NameError if the translator does not exists
@ -254,7 +259,7 @@ class EditorialModel(object):
raise NameError("No translator named %s") raise NameError("No translator named %s")
return mod return mod
##@brief Lodel hash # @brief Lodel hash
def d_hash(self): def d_hash(self):
payload = "%s%s" % ( payload = "%s%s" % (
self.name, self.name,
@ -270,4 +275,3 @@ class EditorialModel(object):
hashlib.md5(bytes(payload, 'utf-8')).digest(), hashlib.md5(bytes(payload, 'utf-8')).digest(),
byteorder='big' byteorder='big'
) )

View file

@ -12,15 +12,28 @@ import warnings
from lodel.context import LodelContext from lodel.context import LodelContext
LodelContext.expose_modules(globals(), { LodelContext.expose_modules(globals(), {
'lodel.exceptions': ['LodelException', 'LodelExceptions', 'lodel.exceptions': [
'LodelFatalError', 'DataNoneValid', 'FieldValidationError'], 'LodelException',
'lodel.leapi.datahandlers.exceptions': ['LodelDataHandlerConsistencyException', 'LodelDataHandlerException'], 'LodelExceptions',
'lodel.logger': 'logger'}) '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 ## @brief Base class for all data handlers
# @ingroup lodel2_datahandlers # @ingroup lodel2_datahandlers
class DataHandler(object): class DataHandler(MlNamedObject):
base_type = "type" base_type = "type"
_HANDLERS_MODULES = ('datas_base', 'datas', 'references') _HANDLERS_MODULES = ('datas_base', 'datas', 'references')
## @brief Stores the DataHandler childs classes indexed by name ## @brief Stores the DataHandler childs classes indexed by name
@ -30,16 +43,20 @@ class DataHandler(object):
__custom_handlers = dict() __custom_handlers = dict()
help_text = 'Generic Field Data Handler' 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 = [] _construct_datas_deps = []
directly_editable = True directly_editable = True
## @brief constructor ## @brief constructor
#
# @param internal False | str : define whether or not a field is internal # @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 # @param immutable bool : indicates if the fieldtype has to be defined in child classes of
# designed globally and immutable # LeObject or if it is designed globally and immutable
# @param **args
# @throw NotImplementedError if it is instanciated directly # @throw NotImplementedError if it is instanciated directly
def __init__(self, **kwargs): def __init__(self, **kwargs):
if self.__class__ == DataHandler: if self.__class__ == DataHandler:
@ -54,9 +71,30 @@ class DataHandler(object):
self.default, error = self.check_data_value(kwargs['default']) self.default, error = self.check_data_value(kwargs['default'])
if error: if error:
raise error raise error
del(kwargs['default']) del kwargs['default']
for argname, argval in kwargs.items(): for argname, argval in kwargs.items():
setattr(self, argname, argval) 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 ## Fieldtype name
@classmethod @classmethod
@ -79,7 +117,7 @@ class DataHandler(object):
def is_internal(self): def is_internal(self):
return self.internal is not False return self.internal is not False
##brief check if a value can be nullable ## @brief check if a value can be nullable
# @param value * # @param value *
# @throw DataNoneValid if value is None and nullable. LodelExceptions if not nullable # @throw DataNoneValid if value is None and nullable. LodelExceptions if not nullable
# @return value (if not None) # @return value (if not None)
@ -204,7 +242,8 @@ class DataHandler(object):
## @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) # @param fieldtype_name str : A field type name (not case sensitive)
# @return DataField child class # @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 @classmethod
def from_name(cls, name): def from_name(cls, name):
cls.load_base_handlers() cls.load_base_handlers()
@ -214,6 +253,23 @@ class DataHandler(object):
raise NameError("No data handlers named '%s'" % (name,)) raise NameError("No data handlers named '%s'" % (name,))
return all_handlers[name] return all_handlers[name]
# @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 ## @brief Return the module name to import in order to use the datahandler
# @param data_handler_name str : Data handler name # @param data_handler_name str : Data handler name
# @return a str # @return a str
@ -238,14 +294,13 @@ class DataHandler(object):
class DataField(DataHandler): class DataField(DataHandler):
pass pass
## @brief Abstract class for all references ## @brief Abstract class for all references
# @ingroup lodel2_datahandlers # @ingroup lodel2_datahandlers
# #
# References are fields that stores a reference to another # References are fields that stores a reference to another
# editorial object # editorial object
#@todo Construct data implementation : transform the data into a LeObject # @todo Construct data implementation : transform the data into a LeObject instance
#instance
class Reference(DataHandler): class Reference(DataHandler):
base_type = "ref" base_type = "ref"
@ -256,12 +311,15 @@ class Reference(DataHandler):
# @param **kwargs : other arguments # @param **kwargs : other arguments
def __init__(self, allowed_classes=None, back_reference=None, internal=False, **kwargs): 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 = 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 back_reference is not None:
if len(back_reference) != 2: if len(back_reference) != 2:
raise ValueError("A tuple (classname, fieldname) expected but got '%s'" % back_reference) 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): # if not issubclass(lodel.leapi.leobject.LeObject, back_reference[0])
# raise TypeError("Back reference was expected to be a tuple(<class LeObject>, str) but got : (%s, %s)" % (back_reference[0], back_reference[1])) # or not isinstance(back_reference[1], str):
# raise TypeError("Back reference was expected to be a tuple(<class LeObject>, str)
# but got : (%s, %s)" % (back_reference[0], back_reference[1]))
self.__back_reference = back_reference self.__back_reference = back_reference
super().__init__(internal=internal, **kwargs) super().__init__(internal=internal, **kwargs)
@ -384,7 +442,7 @@ referenced object with uid %s" % value)
# @note for the moment split on ',' chars # @note for the moment split on ',' chars
class MultipleRef(Reference): class MultipleRef(Reference):
## ## @brief Constructor
# @param max_item int | None : indicate the maximum number of item referenced by this field, None mean no limit # @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): def __init__(self, max_item=None, **kwargs):
self.max_item = max_item self.max_item = max_item
@ -442,6 +500,7 @@ class MultipleRef(Reference):
raise LodelDataHandlerConsistencyException("Unable to find \ raise LodelDataHandlerConsistencyException("Unable to find \
some referenced objects. Following uids were not found : %s" % ','.join(left)) some referenced objects. Following uids were not found : %s" % ','.join(left))
## @brief Class designed to handle datas access will fieldtypes are constructing datas ## @brief Class designed to handle datas access will fieldtypes are constructing datas
# @ingroup lodel2_datahandlers # @ingroup lodel2_datahandlers
# #
@ -457,15 +516,15 @@ class DatasConstructor(object):
# @param datas dict : dict with field name as key and field values as value # @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 # @param fields_handler dict : dict with field name as key and data handler instance as value
def __init__(self, leobject, datas, fields_handler): def __init__(self, leobject, datas, fields_handler):
## Stores concerned class # Stores concerned class
self._leobject = leobject self._leobject = leobject
## Stores datas and constructed datas # Stores datas and constructed datas
self._datas = copy.copy(datas) self._datas = copy.copy(datas)
## Stores fieldtypes # Stores fieldtypes
self._fields_handler = fields_handler self._fields_handler = fields_handler
## Stores list of fieldname for constructed datas # Stores list of fieldname for constructed datas
self._constructed = [] self._constructed = []
## Stores construct calls list # Stores construct calls list
self._construct_calls = [] self._construct_calls = []
## @brief Implements the dict.keys() method on instance ## @brief Implements the dict.keys() method on instance
@ -488,3 +547,30 @@ class DatasConstructor(object):
self._datas[fname] = value self._datas[fname] = value
warnings.warn("Setting value of an DatasConstructor instance") 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

@ -107,6 +107,7 @@ be internal")
cls = emcomponent.__class__ cls = emcomponent.__class__
return cls.__name__ return cls.__name__
##@brief Data field designed to handle concatenated fields ##@brief Data field designed to handle concatenated fields
class Concat(FormatString): class Concat(FormatString):
help = 'Automatic strings concatenation' help = 'Automatic strings concatenation'
@ -118,9 +119,9 @@ class Concat(FormatString):
# @param **kwargs # @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]) format_string = separator.join(['%s' for _ in field_list])
super().__init__( super().__init__(format_string=format_string,
format_string = format_string, field_list = field_list, **kwargs) field_list=field_list,
**kwargs)
class Password(Varchar): class Password(Varchar):
@ -129,7 +130,6 @@ class Password(Varchar):
pass pass
class VarcharList(Varchar): class VarcharList(Varchar):
help = 'DataHandler designed to make a list out of a string.' help = 'DataHandler designed to make a list out of a string.'
base_type = 'varchar' base_type = 'varchar'
@ -140,7 +140,6 @@ class VarcharList(Varchar):
self.delimiter = str(delimiter) self.delimiter = str(delimiter)
super().__init__(**kwargs) super().__init__(**kwargs)
def construct_data(self, emcomponent, fname, datas, cur_value): def construct_data(self, emcomponent, fname, datas, cur_value):
result = cur_value.split(self.delimiter) result = cur_value.split(self.delimiter)
return result return result

View file

@ -33,6 +33,7 @@ class Boolean(DataField):
raise FieldValidationError("The value '%s' is not, and will never, be a boolean" % value) raise FieldValidationError("The value '%s' is not, and will never, be a boolean" % value)
return value return value
## @brief Data field designed to handle integer values ## @brief Data field designed to handle integer values
class Integer(DataField): class Integer(DataField):
@ -61,6 +62,7 @@ class Integer(DataField):
raise FieldValidationError("The value '%s' is not, and will never, be an integer" % value) raise FieldValidationError("The value '%s' is not, and will never, be an integer" % value)
return value return value
## @brief Data field designed to handle string ## @brief Data field designed to handle string
class Varchar(DataField): class Varchar(DataField):
@ -95,6 +97,7 @@ class Varchar(DataField):
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 return value
## @brief Data field designed to handle date & time ## @brief Data field designed to handle date & time
class DateTime(DataField): class DateTime(DataField):
@ -131,6 +134,7 @@ class DateTime(DataField):
return datetime.datetime.now() return datetime.datetime.now()
return cur_value return cur_value
## @brief Data field designed to handle long string ## @brief Data field designed to handle long string
class Text(DataField): class Text(DataField):
help = 'A text field (big string)' help = 'A text field (big string)'
@ -149,6 +153,7 @@ class Text(DataField):
raise FieldValidationError("The content passed to this Text field is not a convertible to a string") raise FieldValidationError("The content passed to this Text field is not a convertible to a string")
return value return value
## @brief Data field designed to handle Files ## @brief Data field designed to handle Files
class File(DataField): class File(DataField):
@ -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) # @todo Add here a check for the validity of the given value (should have a correct path syntax)
def _check_data_value(self, value): def _check_data_value(self, value):
return super()._check_data_value(value) return super()._check_data_value(value)

View file

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

View file

@ -6,7 +6,9 @@ LodelContext.expose_modules(globals(), {
'SingleRef'], 'SingleRef'],
'lodel.logger': 'logger', 'lodel.logger': 'logger',
'lodel.exceptions': ['LodelException', 'LodelExceptions', 'lodel.exceptions': ['LodelException', 'LodelExceptions',
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']}) 'LodelFatalError', 'DataNoneValid',
'FieldValidationError']})
class Link(SingleRef): class Link(SingleRef):
pass pass
@ -39,7 +41,7 @@ class List(MultipleRef):
except Exception as e: except Exception as e:
raise FieldValidationError("Given iterable is not castable in \ raise FieldValidationError("Given iterable is not castable in \
a list : %s" % e) 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): class Set(MultipleRef):
@ -67,6 +69,7 @@ class Set(MultipleRef):
raise FieldValidationError("Given iterable is not castable in \ raise FieldValidationError("Given iterable is not castable in \
a set : %s" % e) 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): class Map(MultipleRef):
@ -91,10 +94,12 @@ class Map(MultipleRef):
raise FieldValidationError("Values for dict fields should be dict") raise FieldValidationError("Values for dict fields should be dict")
return value 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): class Hierarch(MultipleRef):
directly_editable = False 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 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_depth int | None : limit of depth

View file

@ -22,18 +22,20 @@ LodelContext.expose_modules(globals(), {
'lodel.plugin': ['Plugin', 'DatasourcePlugin'], 'lodel.plugin': ['Plugin', 'DatasourcePlugin'],
'lodel.leapi.datahandlers.base_classes': ['DatasConstructor', 'Reference']}) 'lodel.leapi.datahandlers.base_classes': ['DatasConstructor', 'Reference']})
##@brief Stores the name of the field present in each LeObject that indicates # @brief Stores the name of the field present in each LeObject that indicates
# the name of LeObject subclass represented by this object # the name of LeObject subclass represented by this object
CLASS_ID_FIELDNAME = "classname" 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 # This class intend to provide easy & friendly access to LeObject fields values
# without name collision problems # without name collision problems
# @note Wrapped methods are : LeObject.data() & LeObject.set_data() # @note Wrapped methods are : LeObject.data() & LeObject.set_data()
class LeObjectValues(object): class LeObjectValues(object):
##@brief Construct a new LeObjectValues # @brief Construct a new LeObjectValues
# @param fieldnames_callback method # @param fieldnames_callback method
# @param set_callback method : The LeObject.set_datas() method of corresponding LeObject class # @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 # @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._setter = set_callback
self._getter = get_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 # @note Read access should be provided for all fields
# @param fname str : Field name # @param fname str : Field name
def __getattribute__(self, fname): def __getattribute__(self, fname):
getter = super().__getattribute__('_getter') getter = super().__getattribute__('_getter')
return getter(fname) 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 # @note Write acces shouldn't be provided for internal or immutable fields
# @param fname str : Field name # @param fname str : Field name
# @param fval * : the field value # @param fval * : the field value
@ -59,29 +61,29 @@ class LeObjectValues(object):
class LeObject(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 _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 _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 _uid = None
##@brief Read only datasource ( see @ref lodel2_datasources ) # @brief Read only datasource ( see @ref lodel2_datasources )
_ro_datasource = None _ro_datasource = None
##@brief Read & write datasource ( see @ref lodel2_datasources ) # @brief Read & write datasource ( see @ref lodel2_datasources )
_rw_datasource = None _rw_datasource = None
##@brief Store the list of child classes # @brief Store the list of child classes
_child_classes = None _child_classes = None
##@brief Name of the datasource plugin # @brief Name of the datasource plugin
_datasource_name = None _datasource_name = None
def __new__(cls, **kwargs): def __new__(cls, **kwargs):
self = object.__new__(cls) self = object.__new__(cls)
##@brief A dict that stores fieldvalues indexed by fieldname # @brief A dict that stores fieldvalues indexed by fieldname
self.__datas = {fname: None for fname in self._fields} self.__datas = {fname: None for fname in self._fields}
##@brief Store a list of initianilized fields when instanciation not complete else store True # @brief Store a list of initianilized fields when instanciation not complete else store True
self.__initialized = list() 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) self.d = LeObjectValues(self.fieldnames, self.set_data, self.data)
for fieldname, fieldval in kwargs.items(): for fieldname, fieldval in kwargs.items():
self.__datas[fieldname] = fieldval self.__datas[fieldname] = fieldval
@ -90,11 +92,12 @@ class LeObject(object):
self.__set_initialized() self.__set_initialized()
return self 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 # @note Can be considered as EmClass instance
def __init__(self, **kwargs): def __init__(self, **kwargs):
if self._abstract: 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 # Checks that uid is given
for uid_name in self._uid: for uid_name in self._uid:
@ -127,17 +130,17 @@ class LeObject(object):
# Fields datas handling methods # # 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 @property
def initialized(self): def initialized(self):
return self.__is_initialized return self.__is_initialized
##@return The uid field name # @return The uid field name
@classmethod @classmethod
def uid_fieldname(cls): def uid_fieldname(cls):
return cls._uid 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 # @param include_ro bool : if True include read only field names
# @return a list of str # @return a list of str
@classmethod @classmethod
@ -151,7 +154,7 @@ class LeObject(object):
def name2objname(cls, name): def name2objname(cls, name):
return name.title() 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 # @param fieldname str : The fieldname
# @return A data handler instance # @return A data handler instance
#@todo update class of exception raised #@todo update class of exception raised
@ -161,17 +164,17 @@ class LeObject(object):
raise NameError("No field named '%s' in %s" % (fieldname, cls.__name__)) raise NameError("No field named '%s' in %s" % (fieldname, cls.__name__))
return cls._fields[fieldname] 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 #@param with_backref bool : if true return only references with back_references
#@return <code>{'fieldname': datahandler, ...}</code> #@return <code>{'fieldname': datahandler, ...}</code>
@classmethod @classmethod
def reference_handlers(cls, with_backref=True): def reference_handlers(cls, with_backref=True):
return {fname: fdh return {fname: fdh
for fname, fdh in cls.fields(True).items() for fname, fdh in cls.fields(True).items()
if fdh.is_reference() and \ if fdh.is_reference() and
(not with_backref or fdh.back_reference is not None)} (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 # @warning This method has to be called from dynamically generated LeObjects
# @param leobject_name str : LeObject name # @param leobject_name str : LeObject name
# @return A LeObject child class # @return A LeObject child class
@ -190,7 +193,7 @@ class LeObject(object):
def is_abstract(cls): def is_abstract(cls):
return cls._abstract return cls._abstract
##@brief Field data handler getter # @brief Field data handler getter
#@param fieldname str : The field name #@param fieldname str : The field name
#@return A datahandler instance #@return A datahandler instance
#@throw NameError if the field doesn't exist #@throw NameError if the field doesn't exist
@ -201,15 +204,17 @@ class LeObject(object):
except KeyError: except KeyError:
raise NameError("No field named '%s' in %s" % (fieldname, raise NameError("No field named '%s' in %s" % (fieldname,
cls.__name__)) cls.__name__))
##@return A dict with fieldname as key and datahandler as instance # @return A dict with fieldname as key and datahandler as instance
@classmethod @classmethod
def fields(cls, include_ro=False): def fields(cls, include_ro=False):
if include_ro: if include_ro:
return copy.copy(cls._fields) return copy.copy(cls._fields)
else: 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 #@note the first item of the list is the current class, the second is it's
# parent etc... # parent etc...
@ -229,14 +234,13 @@ class LeObject(object):
res.append(cur) res.append(cur)
return res return res
##@brief Return a tuple a child classes # @brief Return a tuple a child classes
#@return a tuple of child classes #@return a tuple of child classes
@classmethod @classmethod
def child_classes(cls): def child_classes(cls):
return copy.copy(cls._child_classes) 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 #@return a LeObject child class or false if no UID defined
@ -255,7 +259,7 @@ class LeObject(object):
prev = pcls prev = pcls
return prev 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 # 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 # by a datasource instance to avoid doing this operation for each query
@ -287,13 +291,13 @@ class LeObject(object):
log_msg %= (ro_ds, cls.__name__) log_msg %= (ro_ds, cls.__name__)
logger.debug(log_msg) 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 #@return the uid value
#@warning Broke multiple uid capabilities #@warning Broke multiple uid capabilities
def uid(self): def uid(self):
return self.data(self._uid[0]) 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 # @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
# @param field_name str : field name # @param field_name str : field name
# @return the Value # @return the Value
@ -303,16 +307,16 @@ class LeObject(object):
if field_name not in self._fields.keys(): if field_name not in self._fields.keys():
raise NameError("No such field in %s : %s" % (self.__class__.__name__, field_name)) raise NameError("No such field in %s : %s" % (self.__class__.__name__, field_name))
if not self.initialized and field_name not in self.__initialized: 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] 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 #@return a dict representing datas of current instance
def datas(self, internal=False): def datas(self, internal=False):
return {fname: self.data(fname) for fname in self.fieldnames(internal)} 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 # @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
# @param fname str : field name # @param fname str : field name
# @param fval * : field value # @param fval * : field value
@ -349,7 +353,7 @@ class LeObject(object):
else: else:
self.__datas[fname] = val 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 # Check the list of initialized fields and set __initialized to True if all fields initialized
def __set_initialized(self): def __set_initialized(self):
@ -358,7 +362,7 @@ class LeObject(object):
if set(expected_fields) == set(self.__initialized): if set(expected_fields) == set(self.__initialized):
self.__is_initialized = True 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) # Make different checks on the LeObject given it's state (fully initialized or not)
# @return None if checks succeded else return an exception list # @return None if checks succeded else return an exception list
@ -405,7 +409,7 @@ class LeObject(object):
# Other methods # # 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 # This method is used in the generated dynamic code to set the _fields attribute
# at the end of the dyncode parse # at the end of the dyncode parse
@ -416,7 +420,7 @@ class LeObject(object):
def _set__fields(cls, field_list): def _set__fields(cls, field_list):
cls._fields = 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 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 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 # @param allow_internal bool : if True don't raise an error if a field is internal
@ -455,7 +459,7 @@ class LeObject(object):
raise LeApiDataCheckErrors("Error while checking datas", err_l) raise LeApiDataCheckErrors("Error while checking datas", err_l)
return checked_datas 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() # @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) cls._check_datas_consistency(ret_datas)
return ret_datas return ret_datas
## @brief Construct datas values # @brief Construct datas values
# #
# @param cls # @param cls
# @param datas dict : Datas that have been returned by LeCrud.check_datas_value() methods # @param datas dict : Datas that have been returned by LeCrud.check_datas_value() methods
@ -494,7 +498,7 @@ construction and consitency when datas are not complete\n")
} }
return ret return ret
## @brief Check datas consistency # @brief Check datas consistency
#  # 
# @warning assert that datas is complete # @warning assert that datas is complete
# @param cls # @param cls
@ -512,7 +516,7 @@ construction and consitency when datas are not complete\n")
if len(err_l) > 0: if len(err_l) > 0:
raise LeApiDataCheckError("Datas consistency checks fails", err_l) raise LeApiDataCheckError("Datas consistency checks fails", err_l)
## @brief Check datas consistency # @brief Check datas consistency
#  # 
# @warning assert that datas is complete # @warning assert that datas is complete
# @param cls # @param cls
@ -523,14 +527,14 @@ construction and consitency when datas are not complete\n")
for fname, dh in cls._fields.items(): for fname, dh in cls._fields.items():
ret = dh.make_consistency(fname, datas, type_query) 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 # @return a new uid en case of success, False otherwise
@classmethod @classmethod
def insert(cls, datas): def insert(cls, datas):
query = LeInsertQuery(cls) query = LeInsertQuery(cls)
return query.execute(datas) return query.execute(datas)
## @brief Update an instance of LeObject # @brief Update an instance of LeObject
# #
#@param datas : list of new datas #@param datas : list of new datas
def update(self, datas=None): def update(self, datas=None):
@ -551,7 +555,7 @@ construction and consitency when datas are not complete\n")
return result return result
## @brief Delete an instance of LeObject # @brief Delete an instance of LeObject
# #
#@return 1 if the objet has been deleted #@return 1 if the objet has been deleted
def delete(self): def delete(self):
@ -566,7 +570,7 @@ construction and consitency when datas are not complete\n")
return result return result
## @brief Delete instances of LeObject # @brief Delete instances of LeObject
#@param query_filters list #@param query_filters list
#@returns the number of deleted items #@returns the number of deleted items
@classmethod @classmethod
@ -585,7 +589,7 @@ construction and consitency when datas are not complete\n")
deleted += result deleted += result
return deleted 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 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 #@param field_list list|None : list of string representing fields see
@ -624,7 +628,7 @@ construction and consitency when datas are not complete\n")
return objects return objects
##@brief Retrieve an object given an UID # @brief Retrieve an object given an UID
#@todo broken multiple UID #@todo broken multiple UID
@classmethod @classmethod
def get_from_uid(cls, uid): def get_from_uid(cls, uid):
@ -651,7 +655,7 @@ object ! For class %s with uid value = %s" % (cls, uid))
return None return None
return res[0] return res[0]
##@brief Checks if an object exists # @brief Checks if an object exists
@classmethod @classmethod
def is_exist(cls, uid): def is_exist(cls, uid):
if cls.uid_fieldname() is None: 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__) "No uid defined for class %s" % cls.__name__)
from .query import is_exist from .query import is_exist
return is_exist(cls, uid) 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.plugins': ['Plugin'],
'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError', 'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
'LodelScriptError', 'DatasourcePluginError'], 'LodelScriptError', 'DatasourcePluginError'],
'lodel.settings.validator': ['SettingValidator'], 'lodel.validator.validator': ['Validator'],
'lodel.exceptions': ['LodelException', 'LodelExceptions', 'lodel.exceptions': ['LodelException', 'LodelExceptions',
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']}) 'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
@ -97,7 +97,7 @@ class DatasourcePlugin(Plugin):
'section': 'lodel2', 'section': 'lodel2',
'key': 'datasource_connectors', 'key': 'datasource_connectors',
'default': 'dummy_datasource', 'default': 'dummy_datasource',
'validator': SettingValidator( 'validator': Validator(
'custom_list', none_is_valid = False, 'custom_list', none_is_valid = False,
validator_name = 'plugin', validator_kwargs = { validator_name = 'plugin', validator_kwargs = {
'ptype': _glob_typename, 'ptype': _glob_typename,
@ -280,13 +280,13 @@ but %s is a %s" % (ds_name, pinstance.__class__.__name__))
#CONFSPEC = { #CONFSPEC = {
# 'lodel2.datasource.mysql.*' : { # 'lodel2.datasource.mysql.*' : {
# 'host': ( 'localhost', # 'host': ( 'localhost',
# SettingValidator('host')), # Validator('host')),
# 'db_name': ( 'lodel', # 'db_name': ( 'lodel',
# SettingValidator('string')), # Validator('string')),
# 'username': ( None, # 'username': ( None,
# SettingValidator('string')), # Validator('string')),
# 'password': ( None, # 'password': ( None,
# SettingValidator('string')), # Validator('string')),
# } # }
#} #}
#</pre> #</pre>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -13,14 +13,14 @@
<h1 class="h1_lodel">Issue {{ obj.data('title') }} </h1> <h1 class="h1_lodel">Issue {{ obj.data('title') }} </h1>
<h2>{{ obj.data('subtitle') }}</h2> <h2>{{ obj.data('subtitle') }}</h2>
{% set directors=person_class.get(("%s in (%s)") % (person_class.uid_fieldname()[0], obj.data('linked_directors')|join(','))) %} {% 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 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(','))) %} {% 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: %} {% if texts is not none: %}
<ul> <ul>
{% for text in texts %} {% for text in texts %}
<li> <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> <h4>{{ text.data('subtitle') }}</h4>
{% set authors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], text.data('linked_persons')|join(','))) %} {% 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> <p>Authors : {% for author in authors %} {{ author.data('firstname')}} {{ author.data('lastname')}} ; {% endfor %} </p>
@ -34,7 +34,7 @@
<ul> <ul>
{% for part in parts %} {% for part in parts %}
<li> <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> <h4>{{ part.data('subtitle') }}</h4>
{% set directors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], part.data('linked_directors')|join(','))) %} {% 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> <p>Directors : {% for director in directors %} {{ director.data('firstname')}} {{ director.data('lastname')}} ; {% endfor %} </p>
@ -43,7 +43,7 @@
<ul style="margin-left:20px"> <ul style="margin-left:20px">
{% for text in p_texts %} {% for text in p_texts %}
<li> <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> <h4>{{ text.data('subtitle') }}</h4>
{% set authors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], text.data('linked_persons')|join(','))) %} {% 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> <p>Authors : {% for author in authors %} {{ author.data('firstname')}} {{ author.data('lastname')}} ; {% endfor %} </p>
@ -57,7 +57,7 @@
<ul> <ul>
{% for part in ss_parts %} {% for part in ss_parts %}
<li> <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> <h4>{{ part.data('subtitle') }}</h4>
{% set directors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], part.data('linked_directors')|join(','))) %} {% 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> <p>Directors : {% for director in directors %} {{ director.data('firstname')}} {{ director.data('lastname')}} ; {% endfor %} </p>
@ -66,7 +66,7 @@
<ul style="margin-left:20px"> <ul style="margin-left:20px">
{% for text in sp_texts %} {% for text in sp_texts %}
<li> <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> <h4>{{ text.data('subtitle') }}</h4>
{% set authors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], text.data('linked_persons')|join(','))) %} {% 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> <p>Authors : {% for author in authors %} {{ author.data('firstname')}} {{ author.data('lastname')}} ; {% endfor %} </p>

View file

@ -13,28 +13,30 @@ from lodel.context import LodelContext
LodelContext.expose_modules(globals(), { LodelContext.expose_modules(globals(), {
'lodel.logger': 'logger', 'lodel.logger': 'logger',
'lodel.settings.utils': ['SettingsError', 'SettingsErrors'], 'lodel.settings.utils': ['SettingsError', 'SettingsErrors'],
'lodel.settings.validator': ['SettingValidator', 'LODEL2_CONF_SPECS', 'lodel.validator.validator': ['Validator', 'LODEL2_CONF_SPECS',
'confspec_append'], 'confspec_append'],
'lodel.settings.settings_loader': ['SettingsLoader']}) '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 # 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( PYTHON_SYS_LIB_PATH = '/usr/local/lib/python{major}.{minor}/'.format(
major=sys.version_info.major, major=sys.version_info.major,
minor=sys.version_info.minor) minor=sys.version_info.minor)
class MetaSettings(type): class MetaSettings(type):
@property @property
def s(self): def s(self):
self.singleton_assert(True) self.singleton_assert(True)
return self.instance.settings 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 # @ref lodel.settings
@ -73,34 +75,36 @@ class MetaSettings(type):
# @todo delete the first stage, the lib path HAVE TO BE HARDCODED. In fact # @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) #@todo add log messages (now we can)
class Settings(object, metaclass=MetaSettings): class Settings(object, metaclass=MetaSettings):
## @brief Stores the singleton instance #  @brief Stores the singleton instance
instance = None instance = None
## @brief Instanciate the Settings singleton #  @brief Instanciate the Settings singleton
# @param conf_dir str : The configuration directory # @param conf_dir str : The configuration directory
#@param custom_confspecs None | dict : if given overwrite default lodel2 #@param custom_confspecs None | dict : if given overwrite default lodel2
# confspecs # confspecs
def __init__(self, conf_dir, custom_confspecs=None): def __init__(self, conf_dir, custom_confspecs=None):
self.singleton_assert() # check that it is the only instance self.singleton_assert() # check that it is the only instance
Settings.instance = self Settings.instance = self
## @brief Configuration specification #  @brief Configuration specification
# #
# Initialized by Settings.__bootstrap() method # Initialized by Settings.__bootstrap() method
self.__conf_specs = custom_confspecs self.__conf_specs = custom_confspecs
## @brief Stores the configurations in namedtuple tree #  @brief Stores the configurations in namedtuple tree
self.__confs = None self.__confs = None
self.__conf_dir = conf_dir self.__conf_dir = conf_dir
self.__started = False self.__started = False
self.__bootstrap() self.__bootstrap()
## @brief Get the named tuple representing configuration #  @brief Get the named tuple representing configuration
@property @property
def settings(self): def settings(self):
return self.__confs.lodel2 return self.__confs.lodel2
## @brief Delete the singleton instance #  @brief Delete the singleton instance
@classmethod @classmethod
def stop(cls): def stop(cls):
del(cls.instance) del(cls.instance)
@ -110,7 +114,7 @@ class Settings(object, metaclass=MetaSettings):
def started(cls): def started(cls):
return cls.instance is not None and cls.instance.__started 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 # state
#@param expect_instanciated bool : if True we expect that the class is #@param expect_instanciated bool : if True we expect that the class is
# allready instanciated, else not # allready instanciated, else not
@ -124,7 +128,7 @@ class Settings(object, metaclass=MetaSettings):
if cls.started(): if cls.started():
raise RuntimeError("The Settings class is already 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 confname is the name of the modified section
#@param confvalue is a dict with variables to save #@param confvalue is a dict with variables to save
#@param validator is a dict with adapted validator #@param validator is a dict with adapted validator
@ -134,7 +138,7 @@ class Settings(object, metaclass=MetaSettings):
confkey = confname.rpartition('.') confkey = confname.rpartition('.')
loader.setoption(confkey[0], confkey[2], confvalue, validator) 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): def __bootstrap(self):
LodelContext.expose_modules(globals(), { LodelContext.expose_modules(globals(), {
'lodel.plugin.plugins': ['Plugin', 'PluginError']}) 'lodel.plugin.plugins': ['Plugin', 'PluginError']})
@ -165,10 +169,12 @@ class Settings(object, metaclass=MetaSettings):
# Checking confspecs # Checking confspecs
for section in lodel2_specs: for section in lodel2_specs:
if section.lower() != section: 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]: for kname in lodel2_specs[section]:
if kname.lower() != kname: 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 # Starting the Plugins class
logger.debug("Starting lodel.plugin.Plugin class") logger.debug("Starting lodel.plugin.Plugin class")
@ -187,7 +193,7 @@ class Settings(object, metaclass=MetaSettings):
self.__populate_from_specs(self.__conf_specs, loader) self.__populate_from_specs(self.__conf_specs, loader)
self.__started = True 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 # Merges global lodel2 conf spec from @ref lodel.settings.validator.LODEL2_CONF_SPECS
# and configuration specifications from loaded plugins # and configuration specifications from loaded plugins
@ -198,18 +204,21 @@ class Settings(object, metaclass=MetaSettings):
for spec in specs: for spec in specs:
for section in spec: for section in spec:
if section.lower() != section: 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: if section not in res:
res[section] = dict() res[section] = dict()
for kname in spec[section]: for kname in spec[section]:
if kname.lower() != kname: 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]: 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]) res[section.lower()][kname] = copy.copy(spec[section][kname])
return res 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 # Populate the __confs attribute
# @param specs dict : Settings specification dictionnary as returned by __merge_specs # @param specs dict : Settings specification dictionnary as returned by __merge_specs
@ -222,7 +231,8 @@ class Settings(object, metaclass=MetaSettings):
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: for vsec in variable_sections:
preffix = vsec[:-2] 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]) specs[section] = copy.copy(specs[vsec])
del(specs[vsec]) del(specs[vsec])
# Fetching values for sections # Fetching values for sections
@ -239,7 +249,7 @@ class Settings(object, metaclass=MetaSettings):
self.__confs_to_namedtuple() self.__confs_to_namedtuple()
pass 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 # For example an option named "foo" in a section named "hello.world" will
# be acessible with self.__confs.hello.world.foo # be acessible with self.__confs.hello.world.foo
@ -295,7 +305,7 @@ class Settings(object, metaclass=MetaSettings):
path.append((curname, cur)) path.append((curname, cur))
nodename += '.' + curname.title() 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 conftree dict : A conftree node
# @param name str # @param name str
# @return a named tuple with fieldnames corresponding to conftree keys # @return a named tuple with fieldnames corresponding to conftree keys
@ -303,11 +313,13 @@ class Settings(object, metaclass=MetaSettings):
ResNamedTuple = namedtuple(name, conftree.keys()) ResNamedTuple = namedtuple(name, conftree.keys())
return ResNamedTuple(**conftree) return ResNamedTuple(**conftree)
class MetaSettingsRO(type): class MetaSettingsRO(type):
def __getattr__(self, name): def __getattr__(self, name):
return getattr(Settings.s, 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): class SettingsRO(object, metaclass=MetaSettingsRO):
pass pass

View file

@ -8,8 +8,7 @@ from lodel.context import LodelContext
LodelContext.expose_modules(globals(), { LodelContext.expose_modules(globals(), {
'lodel.logger': 'logger', 'lodel.logger': 'logger',
'lodel.settings.utils': ['SettingsError', 'SettingsErrors'], 'lodel.settings.utils': ['SettingsError', 'SettingsErrors']})
'lodel.settings.validator': ['SettingsValidationError']})
##@brief Merges and loads configuration files ##@brief Merges and loads configuration files
class SettingsLoader(object): class SettingsLoader(object):
@ -51,11 +50,9 @@ class SettingsLoader(object):
conf[section][param]['file'] = f_ini conf[section][param]['file'] = f_ini
self.__conf_sv[section + ':' + param] = f_ini self.__conf_sv[section + ':' + param] = f_ini
else: else:
raise SettingsError("Error redeclaration of key %s in section %s. Found in %s and %s" % ( raise SettingsError("Error redeclaration of key %s \
section, in section %s. Found in %s and %s" % (\
param, section, param, f_ini, conf[section][param]['file']))
f_ini,
conf[section][param]['file']))
return conf return conf
##@brief Returns option if exists default_value else and validates ##@brief Returns option if exists default_value else and validates
@ -85,8 +82,7 @@ class SettingsLoader(object):
if result is None: if result is None:
if default_value is None and mandatory: if default_value is None and mandatory:
msg = "Default value mandatory for option %s" % keyname msg = "Default value mandatory for option %s" % keyname
expt = SettingsError( msg = msg, expt = SettingsError(msg=msg, key_id=section+'.'+keyname, \
key_id = section+'.'+keyname,
filename=sec[keyname]['file']) filename=sec[keyname]['file'])
self.__errors_list.append(expt) self.__errors_list.append(expt)
return return
@ -95,24 +91,21 @@ class SettingsLoader(object):
sec[keyname]['value'] = default_value sec[keyname]['value'] = default_value
sec[keyname]['file'] = SettingsLoader.DEFAULT_FILENAME sec[keyname]['file'] = SettingsLoader.DEFAULT_FILENAME
result = default_value result = default_value
logger.debug("Using default value for configuration key %s:%s" % ( logger.debug("Using default value for configuration key %s:%s" \
section, keyname)) % (section, keyname))
try: try:
return validator(result) return validator(result)
except Exception as e: except Exception as e:
# Generating nice exceptions # Generating nice exceptions
if False and sec[keyname]['file'] == SettingsLoader.DEFAULT_FILENAME: if False and sec[keyname]['file'] == SettingsLoader.DEFAULT_FILENAME:
expt = SettingsError( msg = 'Mandatory settings not found', expt = SettingsError(msg='Mandatory settings not found', \
key_id=section+'.'+keyname) key_id=section+'.'+keyname)
self.__errors_list.append(expt) self.__errors_list.append(expt)
else: else:
expt = SettingsValidationError( #expt = ValidationError("For %s.%s : %s" % (section, keyname, e))
"For %s.%s : %s" % expt2 = SettingsError(msg=str(expt), \
(section, keyname,e) key_id=section+'.'+keyname, \
)
expt2 = SettingsError( msg = str(expt),
key_id = section+'.'+keyname,
filename=sec[keyname]['file']) filename=sec[keyname]['file'])
self.__errors_list.append(expt2) self.__errors_list.append(expt2)
return return
@ -185,11 +178,10 @@ class SettingsLoader(object):
remains = self.getremains() remains = self.getremains()
err_l = self.__errors_list err_l = self.__errors_list
for key_id, filename in remains.items(): for key_id, filename in remains.items():
err_l.append(SettingsError( msg = "Invalid configuration key", err_l.append(SettingsError(msg="Invalid configuration key", \
key_id = key_id, key_id=key_id, \
filename =filename)) filename =filename))
if len(err_l) > 0: if len(err_l) > 0:
raise SettingsErrors(err_l) raise SettingsErrors(err_l)
else: else:
return return

View file

@ -5,7 +5,7 @@ import hashlib
import json import json
##@brief Stores multilangage string # @brief Stores multilangage string
class MlString(object): class MlString(object):
__default_lang = 'eng' __default_lang = 'eng'
@ -17,7 +17,7 @@ class MlString(object):
'esp', '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 # @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): def __init__(self, arg):
self.values = dict() self.values = dict()
@ -31,9 +31,10 @@ class MlString(object):
elif isinstance(arg, MlString): elif isinstance(arg, MlString):
self.values = copy.copy(arg.values) self.values = copy.copy(arg.values)
else: 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 # @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 lang = self.__default_lang if lang is None else lang
@ -44,7 +45,7 @@ class MlString(object):
else: else:
return str(self) return str(self)
##@brief Set a translation # @brief Set a translation
# @param lang str : the lang # @param lang str : the lang
# @param val str | None: the translation if None delete the translation # @param val str | None: the translation if None delete the translation
def set(self, lang, val): def set(self, lang, val):
@ -57,7 +58,7 @@ class MlString(object):
else: else:
self.values[lang] = val self.values[lang] = val
##@brief Checks that given lang is valid # @brief Checks that given lang is valid
# @param lang str : the lang # @param lang str : the lang
@classmethod @classmethod
def lang_is_valid(cls, lang): def lang_is_valid(cls, lang):
@ -65,7 +66,7 @@ class MlString(object):
raise ValueError('Invalid value for lang. Str expected but %s found' % type(lang)) raise ValueError('Invalid value for lang. Str expected but %s found' % type(lang))
return lang in cls.langs return lang in cls.langs
##@brief Get or set the default lang # @brief Get or set the default lang
@classmethod @classmethod
def default_lang(cls, lang=None): def default_lang(cls, lang=None):
if lang is None: if lang is None:
@ -74,7 +75,7 @@ class MlString(object):
raise ValueError('lang "%s" is not valid"' % lang) raise ValueError('lang "%s" is not valid"' % lang)
cls.__default_lang = 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 # @param json_str str : Json string
@classmethod @classmethod
def from_json(cls, json_str): def from_json(cls, json_str):
@ -95,7 +96,7 @@ class MlString(object):
def __eq__(self, a): def __eq__(self, a):
return hash(self) == hash(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): def __str__(self):
if self.__default_lang in self.values: if self.__default_lang in self.values:
return self.values[self.__default_lang] 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 from lodel.context import LodelContext
LodelContext.expose_modules(globals(), { LodelContext.expose_modules(globals(), {
'lodel.mlnamedobject.mlnamedobject': ['MlNamedObject'],
'lodel.exceptions': ['LodelException', 'LodelExceptions', '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 # @note to get a list of registered default validators just run
# <pre>$ python scripts/settings_validator.py</pre> # <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 SettingsValidationError(Exception):
class ValidationError(Exception):
pass 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 # 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. # casted value.
#@todo implement an IP validator and use it in multisite confspec #@todo implement an IP validator and use it in multisite confspec
class SettingValidator(object):
class Validator(MlNamedObject):
_validators = dict() _validators = dict()
_description = dict() _description = dict()
##@brief Instanciate a validator # @brief Instanciate a validator
#@param name str : validator name #@param name str : validator name
#@param none_is_valid bool : if True None will be validated #@param none_is_valid bool : if True None will be validated
#@param **kwargs : more arguement for the validator #@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: if name is not None and name not in self._validators:
raise LodelFatalError("No validator named '%s'" % name) raise LodelFatalError("No validator named '%s'" % name)
self.__none_is_valid = none_is_valid self.__none_is_valid = none_is_valid
self.__name = name self.__name = name
self._opt_args = kwargs 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 * # @param value *
# @return properly casted value # @return properly casted value
# @throw SettingsValidationError # @throw ValidationError
def __call__(self, value): def __call__(self, value):
if self.__none_is_valid and value is None: if self.__none_is_valid and value is None:
return None return None
try: try:
ret = self._validators[self.__name](value, **self._opt_args) ret = self._validators[self.__name](value, **self._opt_args)
return ret return ret
except Exception as e: except Exception as exp:
raise SettingsValidationError(e) raise ValidationError(exp)
##@brief Register a new validator # @brief Register a new validator
# @param name str : validator name # @param name str : validator name
# @param callback callable : the function that will validate a value # @param callback callable : the function that will validate a value
# @param description str # @param description str
@ -71,75 +79,67 @@ class SettingValidator(object):
cls._validators[name] = callback cls._validators[name] = callback
cls._description[name] = description cls._description[name] = description
##@brief Get the validator list associated with description # @brief Get the validator list associated with description
@classmethod @classmethod
def validators_list(cls): def validators_list(cls):
return copy.copy(cls._description) 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 elt_validator callable : The validator that will be used for validate each elt value
# @param validator_name str # @param validator_name str
# @param description None | str # @param description None | str
# @param separator str : The element separator # @param separator str : The element separator
# @return A SettingValidator instance # @return A Validator instance
@classmethod @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): def list_validator(value):
res = list() res = list()
errors = list()
for elt in value.split(separator): for elt in value.split(separator):
elt = elt_validator(elt) elt = elt_validator(elt)
if len(elt) > 0: if len(elt) > 0:
res.append(elt) res.append(elt)
return res return res
description = "Convert value to an array" if description is None else description description = "Convert value to an array" if description is None else description
cls.register_validator( cls.register_validator(validator_name, list_validator, description)
validator_name,
list_validator,
description)
return cls(validator_name) 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 elt_validator callable : The validator that will be used for validate each elt value
# @param validator_name str # @param validator_name str
# @param description None | str # @param description None | str
# @param separator str : The element separator # @param separator str : The element separator
# @return A SettingValidator instance # @return A Validator instance
@classmethod @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): def write_list_validator(value):
res = '' res = ''
errors = list()
for elt in value: for elt in value:
res += elt_validator(elt) + ',' 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 description = "Convert value to a string" if description is None else description
cls.register_validator( cls.register_validator(validator_name, write_list_validator, description)
validator_name,
write_list_validator,
description)
return cls(validator_name) 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 pattern str : regex pattern
# @param validator_name str : The validator name # @param validator_name str : The validator name
# @param description str : Validator description # @param description str : Validator description
# @return a SettingValidator instance # @return a Validator instance
@classmethod @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): def re_validator(value):
if not re.match(pattern, 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 return value
# registering the validator # registering the validator
cls.register_validator( cls.register_validator(validator_name, re_validator,
validator_name, ("Match value to '%s'" % pattern)
re_validator, if description is None else description)
("Match value to '%s'" % pattern) if description is None else description)
return cls(validator_name) return cls(validator_name)
#  @return a list of registered validators
## @return a list of registered validators
@classmethod @classmethod
def validators_list_str(cls): def validators_list_str(cls):
result = '' result = ''
@ -150,20 +150,26 @@ class SettingValidator(object):
result += "\n" result += "\n"
return result return result
##@brief Integer value validator callback # @brief Integer value validator callback
def int_val(value): def int_val(value):
return int(value) return int(value)
##@brief Output file validator callback # @brief Output file validator callback
# @return A file object (if filename is '-' return sys.stderr) # @return A file object (if filename is '-' return sys.stderr)
def file_err_output(value): def file_err_output(value):
if not isinstance(value, str): 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 == '-': if value == '-':
return None return None
return value return value
##@brief Boolean value validator callback # @brief Boolean value validator callback
def boolean_val(value): def boolean_val(value):
if isinstance(value, bool): if isinstance(value, bool):
return value return value
@ -172,52 +178,66 @@ def boolean_val(value):
elif value.strip().lower() == 'false' or value.strip() == '0': elif value.strip().lower() == 'false' or value.strip() == '0':
value = False value = False
else: else:
raise SettingsValidationError("A boolean was expected but got '%s' " % value) raise ValidationError("A boolean was expected but got '%s' " % value)
return bool(value) return bool(value)
##@brief Validate a directory path # @brief Validate a directory path
def directory_val(value): def directory_val(value):
res = SettingValidator('strip')(value) res = Validator('strip')(value)
if not os.path.isdir(res): 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 return res
##@brief Validate a loglevel value # @brief Validate a loglevel value
def loglevel_val(value): def loglevel_val(value):
valids = ['DEBUG', 'INFO', 'WARNING', 'SECURITY', 'ERROR', 'CRITICAL'] valids = ['DEBUG', 'INFO', 'WARNING', 'SECURITY', 'ERROR', 'CRITICAL']
if value.upper() not in valids: if value.upper() not in valids:
raise SettingsValidationError( raise ValidationError(
"The value '%s' is not a valid loglevel" % value) "The value '%s' is not a valid loglevel" % value)
return value.upper() return value.upper()
##@brief Validate a path # @brief Validate a path
def path_val(value): def path_val(value):
if value is None or not os.path.exists(value): if value is None or not os.path.exists(value):
raise SettingsValidationError( raise ValidationError(
"path '%s' doesn't exists" % value) "path '%s' doesn't exists" % value)
return value return value
##@brief Validate None # @brief Validate None
def none_val(value): def none_val(value):
if value is None: if value is None:
return 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): def str_val(value):
try: try:
return str(value) return str(value)
except Exception as e: except Exception as exp:
raise SettingsValidationError("Not able to convert value to string : " + str(e)) 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): def regex_val(value, pattern):
if re.match(pattern, value) is None: if re.match(pattern, value) is None:
raise SettingsValidationError("The value '%s' is not validated by : \ raise ValidationError("The value '%s' is not validated by : \
r\"%s\"" % (value, pattern)) r\"%s\"" % (value, pattern))
return value return value
##@brief Validate a hostname (ipv4 or ipv6) # @brief Validate a hostname (ipv4 or ipv6)
def host_val(value): def host_val(value):
if value == 'localhost': if value == 'localhost':
return value return value
@ -237,21 +257,87 @@ def host_val(value):
return value return value
except (TypeError, socket.gaierror): except (TypeError, socket.gaierror):
msg = "The value '%s' is not a valid host" 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 # Designed to validate a conf that indicate a class.field in an EM
#@todo modified the hardcoded dyncode import (it's a warning) #@todo modified the hardcoded dyncode import (it's a warning)
def emfield_val(value): def emfield_val(value):
LodelContext.expose_modules(globals(), { LodelContext.expose_modules(globals(),
'lodel.plugin.hooks': ['LodelHook']}) {'lodel.plugin.hooks': ['LodelHook']})
spl = value.split('.') spl = value.split('.')
if len(spl) != 2: if len(spl) != 2:
msg = "Expected a value in the form CLASSNAME.FIELDNAME but got : %s" msg = "Expected a value in the form CLASSNAME.FIELDNAME but got : %s"
raise SettingsValidationError(msg % value) raise SettingsValidationError(msg % value)
value = tuple(spl) value = tuple(spl)
# Late validation hook # Late validation hook
@LodelHook('lodel2_dyncode_bootstraped') @LodelHook('lodel2_dyncode_bootstraped')
def emfield_conf_check(hookname, caller, payload): def emfield_conf_check(hookname, caller, payload):
import leapi_dyncode as dyncode # <-- dirty & quick import leapi_dyncode as dyncode # <-- dirty & quick
@ -265,13 +351,16 @@ def emfield_val(value):
raise SettingsValidationError(msg % value) raise SettingsValidationError(msg % value)
return 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): def plugin_validator(value, ptype=None):
LodelContext.expose_modules(globals(), { LodelContext.expose_modules(globals(), {
'lodel.plugin.hooks': ['LodelHook']}) 'lodel.plugin.hooks': ['LodelHook']})
value = copy.copy(value) value = copy.copy(value)
@LodelHook('lodel2_dyncode_bootstraped') @LodelHook('lodel2_dyncode_bootstraped')
def plugin_type_checker(hookname, caller, payload): def plugin_type_checker(hookname, caller, payload):
LodelContext.expose_modules(globals(), { LodelContext.expose_modules(globals(), {
@ -284,133 +373,39 @@ def plugin_validator(value, ptype = None):
except PluginError: except PluginError:
msg = "No plugin named %s found" msg = "No plugin named %s found"
msg %= value msg %= value
raise SettingsValidationError(msg) raise ValidationError(msg)
if plugin._type_conf_name.lower() != ptype.lower(): if plugin._type_conf_name.lower() != ptype.lower():
msg = "A plugin of type '%s' was expected but found a plugin \ msg = "A plugin of type '%s' was expected but found a plugin \
named '%s' that is a '%s' plugin" named '%s' that is a '%s' plugin"
msg %= (ptype, value, plugin._type_conf_name) msg %= (ptype, value, plugin._type_conf_name)
raise SettingsValidationError(msg) raise ValidationError(msg)
return value 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()
# Validator.register_validator(
# Default validators registration
#
SettingValidator.register_validator(
'plugin', 'plugin',
plugin_validator, plugin_validator,
'plugin name & type validator') 'plugin name & type validator')
SettingValidator.register_validator( Validator.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(
'emfield', 'emfield',
emfield_val, emfield_val,
'EmField name validator') '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 # Lodel 2 configuration specification
# #
##@brief Append a piece of confspec # @brief Append a piece of confspec
#@note orig is modified during the process #@note orig is modified during the process
#@param orig dict : the confspec to update #@param orig dict : the confspec to update
#@param section str : section name #@param section str : section name
#@param key str #@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 #@param default
#@return new confspec #@return new confspec
def confspec_append(orig, section, key, validator, default): def confspec_append(orig, section, key, validator, default):
if section not in orig: if section not in orig:
orig[section] = dict() orig[section] = dict()
@ -418,41 +413,33 @@ def confspec_append(orig, section, key, validator, default):
orig[section][key] = (default, validator) orig[section][key] = (default, validator)
return orig return orig
##@brief Global specifications for lodel2 settings # @brief Global specifications for lodel2 settings
LODEL2_CONF_SPECS = { LODEL2_CONF_SPECS = {
'lodel2': { 'lodel2': {
'debug': ( True, 'debug': (True, Validator('bool')),
SettingValidator('bool')), 'sitename': ('noname', Validator('strip')),
'sitename': ( 'noname', 'runtest': (False, Validator('bool')),
SettingValidator('strip')),
'runtest': ( False,
SettingValidator('bool')),
}, },
'lodel2.logging.*': { 'lodel2.logging.*': {
'level': ( 'ERROR', 'level': ('ERROR', Validator('loglevel')),
SettingValidator('loglevel')), 'context': (False, Validator('bool')),
'context': ( False, 'filename': ("-", Validator('errfile', none_is_valid=False)),
SettingValidator('bool')), 'backupcount': (5, Validator('int', none_is_valid=False)),
'filename': ( "-", 'maxbytes': (1024 * 10, Validator('int', none_is_valid=False)),
SettingValidator('errfile', none_is_valid = False)),
'backupcount': ( 5,
SettingValidator('int', none_is_valid = False)),
'maxbytes': ( 1024*10,
SettingValidator('int', none_is_valid = False)),
}, },
'lodel2.editorialmodel': { 'lodel2.editorialmodel': {
'emfile': ( 'em.pickle', SettingValidator('strip')), 'emfile': ('em.pickle', Validator('strip')),
'emtranslator': ( 'picklefile', SettingValidator('strip')), 'emtranslator': ('picklefile', Validator('strip')),
'dyncode': ( 'leapi_dyncode.py', SettingValidator('strip')), 'dyncode': ('leapi_dyncode.py', Validator('strip')),
'groups': ( '', SettingValidator('list')), 'groups': ('', Validator('list')),
'editormode': ( False, SettingValidator('bool')), 'editormode': (False, Validator('bool')),
}, },
'lodel2.datasources.*': { 'lodel2.datasources.*': {
'read_only': (False, SettingValidator('bool')), 'read_only': (False, Validator('bool')),
'identifier': ( None, SettingValidator('string')), 'identifier': (None, Validator('string')),
}, },
'lodel2.auth': { 'lodel2.auth': {
'login_classfield': ('user.login', SettingValidator('emfield')), 'login_classfield': ('user.login', Validator('emfield')),
'pass_classfield': ('user.password', SettingValidator('emfield')), 'pass_classfield': ('user.password', Validator('emfield')),
}, },
} }

View file

@ -1,6 +1,6 @@
#-*- coding: utf-8 -*- #-*- 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 # Options
################ ################
@ -19,7 +19,9 @@
# #
# #
import sys, os, os.path import sys
import os
import os.path
import unittest import unittest

View file

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

View file

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