1
0
Fork 0
mirror of https://github.com/yweber/lodel2.git synced 2026-07-14 19:21: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/plugin/Makefile \
lodel/settings/Makefile \
lodel/validator/Makefile \
lodel/mlnamedobject/Makefile \
lodel/utils/Makefile \
progs/Makefile \
progs/slim/Makefile \

View file

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

View file

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

View file

@ -1,6 +1,6 @@
#-*- coding: utf-8 -*-
##@package lodel.editorial_model.components
# @package lodel.editorial_model.components
#@brief Defines all @ref lodel2_em "EM" components
#@ingroup lodel2_em
@ -12,17 +12,20 @@ import hashlib
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.utils.mlstring': ['MlString'],
'lodel.mlnamedobject.mlnamedobject': ['MlNamedObject'],
'lodel.settings': ['Settings'],
'lodel.editorial_model.exceptions': ['EditorialModelError', 'assert_edit'],
'lodel.leapi.leobject': ['CLASS_ID_FIELDNAME']})
##@brief Abstract class to represent editorial model components
# @brief Abstract class to represent editorial model components
# @see EmClass EmField
# @todo forbid '.' in uid
#@ingroup lodel2_em
class EmComponent(object):
##@brief Instanciate an EmComponent
class EmComponent(MlNamedObject):
# @brief Instanciate an EmComponent
# @param uid str : uniq identifier
# @param display_name MlString|str|dict : component display_name
# @param help_text MlString|str|dict : help_text
@ -30,9 +33,8 @@ class EmComponent(object):
if self.__class__ == EmComponent:
raise NotImplementedError('EmComponent is an abstract class')
self.uid = uid
self.display_name = None if display_name is None else MlString(display_name)
self.help_text = None if help_text is None else MlString(help_text)
self.group = group
super().__init__(display_name, help_text)
def __str__(self):
if self.display_name is None:
@ -51,11 +53,11 @@ class EmComponent(object):
return int.from_bytes(m.digest(), byteorder='big')
##@brief Handles editorial model objects classes
# @brief Handles editorial model objects classes
#@ingroup lodel2_em
class EmClass(EmComponent):
##@brief Instanciate a new EmClass
# @brief Instanciate a new EmClass
#@param uid str : uniq identifier
#@param display_name MlString|str|dict : component display_name
#@param abstract bool : set the class as asbtract if True
@ -85,11 +87,12 @@ class EmClass(EmComponent):
parents = [parents]
for parent in parents:
if not isinstance(parent, EmClass):
raise ValueError("<class EmClass> expected in parents list, but %s found" % type(parent))
raise ValueError(
"<class EmClass> expected in parents list, but %s found" % type(parent))
else:
parents = list()
self.parents = parents
##@brief Stores EmFields instances indexed by field uid
# @brief Stores EmFields instances indexed by field uid
self.__fields = dict()
self.group = group
@ -112,7 +115,7 @@ class EmClass(EmComponent):
internal=True,
group=group)
##@brief Property that represent a dict of all fields (the EmField defined in this class and all its parents)
# @brief Property that represent a dict of all fields (the EmField defined in this class and all its parents)
# @todo use Settings.editorialmodel.groups to determine wich fields should be returned
@property
def __all_fields(self):
@ -122,12 +125,12 @@ class EmClass(EmComponent):
res.update(self.__fields)
return res
##@brief RO access to datasource attribute
# @brief RO access to datasource attribute
@property
def datasource(self):
return self.__datasource
##@brief Return the list of all dependencies
# @brief Return the list of all dependencies
#
# Reccursive parents listing
@property
@ -140,7 +143,7 @@ class EmClass(EmComponent):
res |= parent.parents_recc
return res
##@brief EmField getter
# @brief EmField getter
# @param uid None | str : If None returns an iterator on EmField instances else return an EmField instance
# @param no_parents bool : If True returns only fields defined is this class and not the one defined in parents classes
# @return A list on EmFields instances (if uid is None) else return an EmField instance
@ -152,7 +155,7 @@ class EmClass(EmComponent):
except KeyError:
raise EditorialModelError("No such EmField '%s'" % uid)
##@brief Keep in __fields only fields contained in active groups
# @brief Keep in __fields only fields contained in active groups
def _set_active_fields(self, active_groups):
if not Settings.editorialmodel.editormode:
active_fields = []
@ -162,7 +165,7 @@ class EmClass(EmComponent):
self.__fields = {fname: fdh for fname, fdh in self.__fields.items()
if fdh in active_fields}
##@brief Add a field to the EmClass
# @brief Add a field to the EmClass
# @param emfield EmField : an EmField instance
# @warning do not add an EmField allready in another class !
# @throw EditorialModelException if an EmField with same uid allready in this EmClass (overwritting allowed from parents)
@ -170,16 +173,18 @@ class EmClass(EmComponent):
def add_field(self, emfield):
assert_edit()
if emfield.uid in self.__fields:
raise EditorialModelError("Duplicated uid '%s' for EmField in this class ( %s )" % (emfield.uid, self))
raise EditorialModelError(
"Duplicated uid '%s' for EmField in this class ( %s )" % (emfield.uid, self))
# Incomplete field override check
if emfield.uid in self.__all_fields:
parent_field = self.__all_fields[emfield.uid]
if not emfield.data_handler_instance.can_override(parent_field.data_handler_instance):
raise AttributeError("'%s' field override a parent field, but data_handles are not compatible" % emfield.uid)
raise AttributeError(
"'%s' field override a parent field, but data_handles are not compatible" % emfield.uid)
self.__fields[emfield.uid] = emfield
return emfield
##@brief Create a new EmField and add it to the EmClass
# @brief Create a new EmField and add it to the EmClass
# @param data_handler str : A DataHandler name
# @param uid str : the EmField uniq id
# @param **field_kwargs : EmField constructor parameters ( see @ref EmField.__init__() )
@ -212,11 +217,11 @@ class EmClass(EmComponent):
return "<class %s EmClass uid=%s>" % (abstract, repr(self.uid))
##@brief Handles editorial model classes fields
# @brief Handles editorial model classes fields
#@ingroup lodel2_em
class EmField(EmComponent):
##@brief Instanciate a new EmField
# @brief Instanciate a new EmField
# @param uid str : uniq identifier
# @param display_name MlString|str|dict : field display_name
# @param data_handler str : A DataHandler name
@ -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):
from lodel.leapi.datahandlers.base_classes import DataHandler
super().__init__(uid, display_name, help_text, group)
##@brief The data handler name
# @brief The data handler name
self.data_handler_name = data_handler
##@brief The data handler class
# @brief The data handler class
self.data_handler_cls = DataHandler.from_name(data_handler)
##@brief The data handler instance associated with this EmField
# @brief The data handler instance associated with this EmField
self.data_handler_instance = self.data_handler_cls(**handler_kwargs)
##@brief Stores data handler instanciation options
# @brief Stores data handler instanciation options
self.data_handler_options = handler_kwargs
##@brief Stores the emclass that contains this field (set by EmClass.add_field() method)
# @brief Stores the emclass that contains this field (set by EmClass.add_field() method)
self._emclass = em_class
if self._emclass is None:
warnings.warn("No EmClass for field %s" % uid)
@ -243,13 +248,13 @@ class EmField(EmComponent):
else:
group.add_components([self])
##@brief Returns data_handler_name attribute
# @brief Returns data_handler_name attribute
def get_data_handler_name(self):
return copy.copy(self.data_handler_name)
##@brief Returns data_handler_cls attribute
# @brief Returns data_handler_cls attribute
def get_data_handler_cls(self):
return copy.copy(selfdata_handler_cls)
return copy.copy(self.data_handler_cls)
##@brief Returne the uid of the emclass which contains this field
def get_emclass_uid(self):
@ -266,11 +271,13 @@ class EmField(EmComponent):
'utf-8')
).digest(), byteorder='big')
##@brief Handles functionnal group of EmComponents
# @brief Handles functionnal group of EmComponents
#@ingroup lodel2_em
class EmGroup(object):
##@brief Create a new EmGroup
class EmGroup(MlNamedObject):
# @brief Create a new EmGroup
# @note you should NEVER call the constructor yourself. Use Model.add_group instead
# @param uid str : Uniq identifier
# @param depends list : A list of EmGroup dependencies
@ -278,22 +285,21 @@ class EmGroup(object):
# @param help_text MlString|str :
def __init__(self, uid, depends=None, display_name=None, help_text=None):
self.uid = uid
##@brief Stores the list of groups that depends on this EmGroup indexed by uid
# @brief Stores the list of groups that depends on this EmGroup indexed by uid
self.required_by = dict()
##@brief Stores the list of dependencies (EmGroup) indexed by uid
# @brief Stores the list of dependencies (EmGroup) indexed by uid
self.require = dict()
##@brief Stores the list of EmComponent instances contained in this group
# @brief Stores the list of EmComponent instances contained in this group
self.__components = set()
super().__init__(display_name, help_text)
self.display_name = None if display_name is None else MlString(display_name)
self.help_text = None if help_text is None else MlString(help_text)
if depends is not None:
for grp in depends:
if not isinstance(grp, EmGroup):
raise ValueError("EmGroup expected in depends argument but %s found" % grp)
self.add_dependencie(grp)
##@brief Returns EmGroup dependencie
# @brief Returns EmGroup dependencie
# @param recursive bool : if True return all dependencies and their dependencies
# @return a dict of EmGroup identified by uid
def dependencies(self, recursive=False):
@ -309,7 +315,7 @@ class EmGroup(object):
res[new_dep.uid] = new_dep
return res
##@brief Returns EmGroup applicants
# @brief Returns EmGroup applicants
# @param recursive bool : if True return all dependencies and their dependencies
# @returns a dict of EmGroup identified by uid
def applicants(self, recursive=False):
@ -325,28 +331,30 @@ class EmGroup(object):
res[new_app.uid] = new_app
return res
##@brief Returns EmGroup components
# @brief Returns EmGroup components
# @returns a copy of the set of components
def components(self):
return (self.__components).copy()
##@brief Returns EmGroup display_name
# @brief Returns EmGroup display_name
# @param lang str | None : If None return default lang translation
# @returns None if display_name is None, a str for display_name else
def get_display_name(self, lang=None):
name = self.display_name
if name is None : return None
return name.get(lang);
if name is None:
return None
return name.get(lang)
##@brief Returns EmGroup help_text
# @brief Returns EmGroup help_text
# @param lang str | None : If None return default lang translation
# @returns None if display_name is None, a str for display_name else
def get_help_text(self, lang=None):
help = self.help_text
if help is None : return None
return help.get(lang);
if help is None:
return None
return help.get(lang)
##@brief Add components in a group
# @brief Add components in a group
# @param components list : EmComponent instances list
def add_components(self, components):
assert_edit()
@ -357,10 +365,11 @@ class EmGroup(object):
msg %= (component, self)
warnings.warn(msg)
elif not isinstance(component, EmClass):
raise EditorialModelError("Expecting components to be a list of EmComponent, but %s found in the list" % type(component))
raise EditorialModelError(
"Expecting components to be a list of EmComponent, but %s found in the list" % type(component))
self.__components |= set(components)
##@brief Add a dependencie
# @brief Add a dependencie
# @param em_group EmGroup|iterable : an EmGroup instance or list of instance
def add_dependencie(self, grp):
assert_edit()
@ -368,7 +377,8 @@ class EmGroup(object):
for group in grp:
self.add_dependencie(group)
return
except TypeError: pass
except TypeError:
pass
if grp.uid in self.require:
return
@ -377,7 +387,7 @@ class EmGroup(object):
self.require[grp.uid] = grp
grp.required_by[self.uid] = self
##@brief Add a applicant
# @brief Add a applicant
# @param em_group EmGroup|iterable : an EmGroup instance or list of instance
# Useless ???
def add_applicant(self, grp):
@ -386,7 +396,8 @@ class EmGroup(object):
for group in grp:
self.add_applicant(group)
return
except TypeError: pass
except TypeError:
pass
if grp.uid in self.required_by:
return
@ -395,17 +406,17 @@ class EmGroup(object):
self.required_by[grp.uid] = grp
grp.require[self.uid] = self
##@brief Search for circular dependencie
# @brief Search for circular dependencie
# @return True if circular dep found else False
def __circular_dependencie(self, new_dep):
return self.uid in new_dep.dependencies(True)
##@brief Search for circular applicant
# @brief Search for circular applicant
# @return True if circular app found else False
def __circular_applicant(self, new_app):
return self.uid in new_app.applicants(True)
##@brief Fancy string representation of an EmGroup
# @brief Fancy string representation of an EmGroup
# @return a string
def __str__(self):
if self.display_name is None:
@ -431,7 +442,7 @@ class EmGroup(object):
byteorder='big'
)
##@brief Complete string representation of an EmGroup
# @brief Complete string representation of an EmGroup
# @return a string
def __repr__(self):
return "<class EmGroup '%s' depends : [%s]>" % (self.uid, ', '.join([duid for duid in self.dependencies(False)]))

View file

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

View file

@ -12,15 +12,28 @@ import warnings
from lodel.context import LodelContext
LodelContext.expose_modules(globals(), {
'lodel.exceptions': ['LodelException', 'LodelExceptions',
'LodelFatalError', 'DataNoneValid', 'FieldValidationError'],
'lodel.leapi.datahandlers.exceptions': ['LodelDataHandlerConsistencyException', 'LodelDataHandlerException'],
'lodel.logger': 'logger'})
'lodel.exceptions': [
'LodelException',
'LodelExceptions',
'LodelFatalError',
'DataNoneValid',
'FieldValidationError'
],
'lodel.mlnamedobject.mlnamedobject': ['MlNamedObject'],
'lodel.leapi.datahandlers.exceptions': [
'LodelDataHandlerConsistencyException',
'LodelDataHandlerException'
],
'lodel.validator.validator': [
'ValidationError'
],
'lodel.logger': 'logger',
'lodel.utils.mlstring': ['MlString']})
## @brief Base class for all data handlers
# @ingroup lodel2_datahandlers
class DataHandler(object):
class DataHandler(MlNamedObject):
base_type = "type"
_HANDLERS_MODULES = ('datas_base', 'datas', 'references')
## @brief Stores the DataHandler childs classes indexed by name
@ -30,16 +43,20 @@ class DataHandler(object):
__custom_handlers = dict()
help_text = 'Generic Field Data Handler'
display_name = "Generic Field"
options_spec = dict()
options_values = dict()
## @brief List fields that will be exposed to the construct_data_method
_construct_datas_deps = []
directly_editable = True
## @brief constructor
#
# @param internal False | str : define whether or not a field is internal
# @param immutable bool : indicates if the fieldtype has to be defined in child classes of LeObject or if it is
# designed globally and immutable
# @param **args
# @param immutable bool : indicates if the fieldtype has to be defined in child classes of
# LeObject or if it is designed globally and immutable
# @throw NotImplementedError if it is instanciated directly
def __init__(self, **kwargs):
if self.__class__ == DataHandler:
@ -54,9 +71,30 @@ class DataHandler(object):
self.default, error = self.check_data_value(kwargs['default'])
if error:
raise error
del(kwargs['default'])
del kwargs['default']
for argname, argval in kwargs.items():
setattr(self, argname, argval)
self.check_options()
display_name = kwargs.get('display_name',MlString(self.display_name))
help_text = kwargs.get('help_text', MlString(self.help_text))
super().__init__(display_name, help_text)
## @brief Sets properly casted and checked options for the datahandler
# @raises LodelDataHandlerNotAllowedOptionException when a passed option is not in the option
# specifications of the datahandler
def check_options(self):
for option_name, option_datas in self.options_spec.items():
if option_name in self.options_values:
# There is a configured option, we check its value
try:
self.options_values[option_name] = option_datas[1].check_value(
self.options_values[option_name])
except ValueError:
pass # TODO Deal with the case where the value used for an option is invalid
else:
# This option was not configured, we get the default value from the specs
self.options_values[option_name] = option_datas[0]
## Fieldtype name
@classmethod
@ -79,7 +117,7 @@ class DataHandler(object):
def is_internal(self):
return self.internal is not False
##brief check if a value can be nullable
## @brief check if a value can be nullable
# @param value *
# @throw DataNoneValid if value is None and nullable. LodelExceptions if not nullable
# @return value (if not None)
@ -204,7 +242,8 @@ class DataHandler(object):
## @brief given a field type name, returns the associated python class
# @param fieldtype_name str : A field type name (not case sensitive)
# @return DataField child class
# @note To access custom data handlers it can be cool to prefix the handler name by plugin name for example ? (to ensure name unicity)
# @note To access custom data handlers it can be cool to prefix the handler name by plugin
# name for example ? (to ensure name unicity)
@classmethod
def from_name(cls, name):
cls.load_base_handlers()
@ -214,6 +253,23 @@ class DataHandler(object):
raise NameError("No data handlers named '%s'" % (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
# @param data_handler_name str : Data handler name
# @return a str
@ -238,14 +294,13 @@ class DataHandler(object):
class DataField(DataHandler):
pass
## @brief Abstract class for all references
# @ingroup lodel2_datahandlers
#
# References are fields that stores a reference to another
# editorial object
#@todo Construct data implementation : transform the data into a LeObject
#instance
# @todo Construct data implementation : transform the data into a LeObject instance
class Reference(DataHandler):
base_type = "ref"
@ -256,12 +311,15 @@ class Reference(DataHandler):
# @param **kwargs : other arguments
def __init__(self, allowed_classes=None, back_reference=None, internal=False, **kwargs):
self.__allowed_classes = set() if allowed_classes is None else set(allowed_classes)
self.allowed_classes = list() if allowed_classes is None else allowed_classes # For now usefull to jinja 2
# For now usefull to jinja 2
self.allowed_classes = list() if allowed_classes is None else allowed_classes
if back_reference is not None:
if len(back_reference) != 2:
raise ValueError("A tuple (classname, fieldname) expected but got '%s'" % back_reference)
#if not issubclass(lodel.leapi.leobject.LeObject, back_reference[0]) or not isinstance(back_reference[1], str):
# raise TypeError("Back reference was expected to be a tuple(<class LeObject>, str) but got : (%s, %s)" % (back_reference[0], back_reference[1]))
# if not issubclass(lodel.leapi.leobject.LeObject, back_reference[0])
# or not isinstance(back_reference[1], str):
# raise TypeError("Back reference was expected to be a tuple(<class LeObject>, str)
# but got : (%s, %s)" % (back_reference[0], back_reference[1]))
self.__back_reference = back_reference
super().__init__(internal=internal, **kwargs)
@ -384,7 +442,7 @@ referenced object with uid %s" % value)
# @note for the moment split on ',' chars
class MultipleRef(Reference):
##
## @brief Constructor
# @param max_item int | None : indicate the maximum number of item referenced by this field, None mean no limit
def __init__(self, max_item=None, **kwargs):
self.max_item = max_item
@ -442,6 +500,7 @@ class MultipleRef(Reference):
raise LodelDataHandlerConsistencyException("Unable to find \
some referenced objects. Following uids were not found : %s" % ','.join(left))
## @brief Class designed to handle datas access will fieldtypes are constructing datas
# @ingroup lodel2_datahandlers
#
@ -457,15 +516,15 @@ class DatasConstructor(object):
# @param datas dict : dict with field name as key and field values as value
# @param fields_handler dict : dict with field name as key and data handler instance as value
def __init__(self, leobject, datas, fields_handler):
## Stores concerned class
# Stores concerned class
self._leobject = leobject
## Stores datas and constructed datas
# Stores datas and constructed datas
self._datas = copy.copy(datas)
## Stores fieldtypes
# Stores fieldtypes
self._fields_handler = fields_handler
## Stores list of fieldname for constructed datas
# Stores list of fieldname for constructed datas
self._constructed = []
## Stores construct calls list
# Stores construct calls list
self._construct_calls = []
## @brief Implements the dict.keys() method on instance
@ -488,3 +547,30 @@ class DatasConstructor(object):
self._datas[fname] = value
warnings.warn("Setting value of an DatasConstructor instance")
## @brief Class designed to handle an option of a DataHandler
class DatahandlerOption(MlNamedObject):
## @brief instanciates a new Datahandler option object
#
# @param id str
# @param display_name MlString
# @param help_text MlString
# @param validator function
def __init__(self, id, display_name, help_text, validator):
self.__id = id
self.__validator = validator
super().__init__(display_name, help_text)
@property
def id(self):
return self.__id
## @brief checks a value corresponding to this option is valid
# @param value
# @return casted value
def check_value(self, value):
try:
return self.__validator(value)
except ValidationError:
raise ValueError()

View file

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

View file

@ -33,6 +33,7 @@ class Boolean(DataField):
raise FieldValidationError("The value '%s' is not, and will never, be a boolean" % value)
return value
## @brief Data field designed to handle integer values
class Integer(DataField):
@ -61,6 +62,7 @@ class Integer(DataField):
raise FieldValidationError("The value '%s' is not, and will never, be an integer" % value)
return value
## @brief Data field designed to handle string
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))
return value
## @brief Data field designed to handle date & time
class DateTime(DataField):
@ -131,6 +134,7 @@ class DateTime(DataField):
return datetime.datetime.now()
return cur_value
## @brief Data field designed to handle long string
class Text(DataField):
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")
return value
## @brief Data field designed to handle Files
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)
def _check_data_value(self, value):
return super()._check_data_value(value)

View file

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

View file

@ -6,7 +6,9 @@ LodelContext.expose_modules(globals(), {
'SingleRef'],
'lodel.logger': 'logger',
'lodel.exceptions': ['LodelException', 'LodelExceptions',
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
'LodelFatalError', 'DataNoneValid',
'FieldValidationError']})
class Link(SingleRef):
pass
@ -39,7 +41,7 @@ class List(MultipleRef):
except Exception as e:
raise FieldValidationError("Given iterable is not castable in \
a list : %s" % e)
return value
## @brief Child class of MultipleRef where references are represented in the form of a python set
class Set(MultipleRef):
@ -67,6 +69,7 @@ class Set(MultipleRef):
raise FieldValidationError("Given iterable is not castable in \
a set : %s" % e)
## @brief Child class of MultipleRef where references are represented in the form of a python dict
class Map(MultipleRef):
@ -91,10 +94,12 @@ class Map(MultipleRef):
raise FieldValidationError("Values for dict fields should be dict")
return value
## @brief This Reference class is designed to handler hierarchy with some constraint
class Hierarch(MultipleRef):
directly_editable = False
## @brief Instanciate a data handler handling hierarchical relation with constraints
# @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

View file

@ -22,18 +22,20 @@ LodelContext.expose_modules(globals(), {
'lodel.plugin': ['Plugin', 'DatasourcePlugin'],
'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
CLASS_ID_FIELDNAME = "classname"
##@brief Wrapper class for LeObject getter & setter
# @brief Wrapper class for LeObject getter & setter
#
# This class intend to provide easy & friendly access to LeObject fields values
# without name collision problems
# @note Wrapped methods are : LeObject.data() & LeObject.set_data()
class LeObjectValues(object):
##@brief Construct a new LeObjectValues
# @brief Construct a new LeObjectValues
# @param fieldnames_callback method
# @param set_callback method : The LeObject.set_datas() method of corresponding LeObject class
# @param get_callback method : The LeObject.get_datas() method of corresponding LeObject class
@ -41,14 +43,14 @@ class LeObjectValues(object):
self._setter = set_callback
self._getter = get_callback
##@brief Provide read access to datas values
# @brief Provide read access to datas values
# @note Read access should be provided for all fields
# @param fname str : Field name
def __getattribute__(self, fname):
getter = super().__getattribute__('_getter')
return getter(fname)
##@brief Provide write access to datas values
# @brief Provide write access to datas values
# @note Write acces shouldn't be provided for internal or immutable fields
# @param fname str : Field name
# @param fval * : the field value
@ -59,29 +61,29 @@ class LeObjectValues(object):
class LeObject(object):
##@brief boolean that tells if an object is abtract or not
# @brief boolean that tells if an object is abtract or not
_abstract = None
##@brief A dict that stores DataHandler instances indexed by field name
# @brief A dict that stores DataHandler instances indexed by field name
_fields = None
##@brief A tuple of fieldname (or a uniq fieldname) representing uid
# @brief A tuple of fieldname (or a uniq fieldname) representing uid
_uid = None
##@brief Read only datasource ( see @ref lodel2_datasources )
# @brief Read only datasource ( see @ref lodel2_datasources )
_ro_datasource = None
##@brief Read & write datasource ( see @ref lodel2_datasources )
# @brief Read & write datasource ( see @ref lodel2_datasources )
_rw_datasource = None
##@brief Store the list of child classes
# @brief Store the list of child classes
_child_classes = None
##@brief Name of the datasource plugin
# @brief Name of the datasource plugin
_datasource_name = None
def __new__(cls, **kwargs):
self = object.__new__(cls)
##@brief A dict that stores fieldvalues indexed by fieldname
# @brief A dict that stores fieldvalues indexed by fieldname
self.__datas = {fname: None for fname in self._fields}
##@brief Store a list of initianilized fields when instanciation not complete else store True
# @brief Store a list of initianilized fields when instanciation not complete else store True
self.__initialized = list()
##@brief Datas accessor. Instance of @ref LeObjectValues
# @brief Datas accessor. Instance of @ref LeObjectValues
self.d = LeObjectValues(self.fieldnames, self.set_data, self.data)
for fieldname, fieldval in kwargs.items():
self.__datas[fieldname] = fieldval
@ -90,11 +92,12 @@ class LeObject(object):
self.__set_initialized()
return self
##@brief Construct an object representing an Editorial component
# @brief Construct an object representing an Editorial component
# @note Can be considered as EmClass instance
def __init__(self, **kwargs):
if self._abstract:
raise NotImplementedError("%s is abstract, you cannot instanciate it." % self.__class__.__name__ )
raise NotImplementedError(
"%s is abstract, you cannot instanciate it." % self.__class__.__name__)
# Checks that uid is given
for uid_name in self._uid:
@ -127,17 +130,17 @@ class LeObject(object):
# Fields datas handling methods #
#-----------------------------------#
##@brief Property method True if LeObject is initialized else False
# @brief Property method True if LeObject is initialized else False
@property
def initialized(self):
return self.__is_initialized
##@return The uid field name
# @return The uid field name
@classmethod
def uid_fieldname(cls):
return cls._uid
##@brief Return a list of fieldnames
# @brief Return a list of fieldnames
# @param include_ro bool : if True include read only field names
# @return a list of str
@classmethod
@ -151,7 +154,7 @@ class LeObject(object):
def name2objname(cls, name):
return name.title()
##@brief Return the datahandler asssociated with a LeObject field
# @brief Return the datahandler asssociated with a LeObject field
# @param fieldname str : The fieldname
# @return A data handler instance
#@todo update class of exception raised
@ -161,17 +164,17 @@ class LeObject(object):
raise NameError("No field named '%s' in %s" % (fieldname, cls.__name__))
return cls._fields[fieldname]
##@brief Getter for references datahandlers
# @brief Getter for references datahandlers
#@param with_backref bool : if true return only references with back_references
#@return <code>{'fieldname': datahandler, ...}</code>
@classmethod
def reference_handlers(cls, with_backref=True):
return {fname: fdh
for fname, fdh in cls.fields(True).items()
if fdh.is_reference() and \
if fdh.is_reference() and
(not with_backref or fdh.back_reference is not None)}
##@brief Return a LeObject child class from a name
# @brief Return a LeObject child class from a name
# @warning This method has to be called from dynamically generated LeObjects
# @param leobject_name str : LeObject name
# @return A LeObject child class
@ -190,7 +193,7 @@ class LeObject(object):
def is_abstract(cls):
return cls._abstract
##@brief Field data handler getter
# @brief Field data handler getter
#@param fieldname str : The field name
#@return A datahandler instance
#@throw NameError if the field doesn't exist
@ -201,15 +204,17 @@ class LeObject(object):
except KeyError:
raise NameError("No field named '%s' in %s" % (fieldname,
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
def fields(cls, include_ro=False):
if include_ro:
return copy.copy(cls._fields)
else:
return {fname:cls._fields[fname] for fname in cls._fields if not cls._fields[fname].is_internal()}
return {fname: cls._fields[fname] for fname in cls._fields\
if not cls._fields[fname].is_internal()}
##@brief Return the list of parents classes
# @brief Return the list of parents classes
#
#@note the first item of the list is the current class, the second is it's
# parent etc...
@ -229,14 +234,13 @@ class LeObject(object):
res.append(cur)
return res
##@brief Return a tuple a child classes
# @brief Return a tuple a child classes
#@return a tuple of child classes
@classmethod
def child_classes(cls):
return copy.copy(cls._child_classes)
##@brief Return the parent class that is the "source" of uid
# @brief Return the parent class that is the "source" of uid
#
# The method goal is to return the parent class that defines UID.
#@return a LeObject child class or false if no UID defined
@ -255,7 +259,7 @@ class LeObject(object):
prev = pcls
return prev
##@brief Initialise both datasources (ro and rw)
# @brief Initialise both datasources (ro and rw)
#
# This method is used once at dyncode load to replace the datasource string
# by a datasource instance to avoid doing this operation for each query
@ -287,13 +291,13 @@ class LeObject(object):
log_msg %= (ro_ds, cls.__name__)
logger.debug(log_msg)
##@brief Return the uid of the current LeObject instance
# @brief Return the uid of the current LeObject instance
#@return the uid value
#@warning Broke multiple uid capabilities
def uid(self):
return self.data(self._uid[0])
##@brief Read only access to all datas
# @brief Read only access to all datas
# @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
# @param field_name str : field name
# @return the Value
@ -303,16 +307,16 @@ class LeObject(object):
if field_name not in self._fields.keys():
raise NameError("No such field in %s : %s" % (self.__class__.__name__, field_name))
if not self.initialized and field_name not in self.__initialized:
raise RuntimeError("The field %s is not initialized yet (and have no value)" % field_name)
raise RuntimeError(
"The field %s is not initialized yet (and have no value)" % field_name)
return self.__datas[field_name]
##@brief Read only access to all datas
# @brief Read only access to all datas
#@return a dict representing datas of current instance
def datas(self, internal=False):
return {fname: self.data(fname) for fname in self.fieldnames(internal)}
##@brief Datas setter
# @brief Datas setter
# @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
# @param fname str : field name
# @param fval * : field value
@ -349,7 +353,7 @@ class LeObject(object):
else:
self.__datas[fname] = val
##@brief Update the __initialized attribute according to LeObject internal state
# @brief Update the __initialized attribute according to LeObject internal state
#
# Check the list of initialized fields and set __initialized to True if all fields initialized
def __set_initialized(self):
@ -358,7 +362,7 @@ class LeObject(object):
if set(expected_fields) == set(self.__initialized):
self.__is_initialized = True
##@brief Designed to be called when datas are modified
# @brief Designed to be called when datas are modified
#
# Make different checks on the LeObject given it's state (fully initialized or not)
# @return None if checks succeded else return an exception list
@ -405,7 +409,7 @@ class LeObject(object):
# Other methods #
#--------------------#
##@brief Temporary method to set private fields attribute at dynamic code generation
# @brief Temporary method to set private fields attribute at dynamic code generation
#
# This method is used in the generated dynamic code to set the _fields attribute
# at the end of the dyncode parse
@ -416,7 +420,7 @@ class LeObject(object):
def _set__fields(cls, field_list):
cls._fields = field_list
## @brief Check that datas are valid for this type
# @brief Check that datas are valid for this type
# @param datas dict : key == field name value are field values
# @param complete bool : if True expect that datas provide values for all non internal fields
# @param allow_internal bool : if True don't raise an error if a field is internal
@ -455,7 +459,7 @@ class LeObject(object):
raise LeApiDataCheckErrors("Error while checking datas", err_l)
return checked_datas
##@brief Check and prepare datas
# @brief Check and prepare datas
#
# @warning when complete = False we are not able to make construct_datas() and _check_data_consistency()
#
@ -478,7 +482,7 @@ construction and consitency when datas are not complete\n")
cls._check_datas_consistency(ret_datas)
return ret_datas
## @brief Construct datas values
# @brief Construct datas values
#
# @param cls
# @param datas dict : Datas that have been returned by LeCrud.check_datas_value() methods
@ -494,7 +498,7 @@ construction and consitency when datas are not complete\n")
}
return ret
## @brief Check datas consistency
# @brief Check datas consistency
# 
# @warning assert that datas is complete
# @param cls
@ -512,7 +516,7 @@ construction and consitency when datas are not complete\n")
if len(err_l) > 0:
raise LeApiDataCheckError("Datas consistency checks fails", err_l)
## @brief Check datas consistency
# @brief Check datas consistency
# 
# @warning assert that datas is complete
# @param cls
@ -523,14 +527,14 @@ construction and consitency when datas are not complete\n")
for fname, dh in cls._fields.items():
ret = dh.make_consistency(fname, datas, type_query)
## @brief Add a new instance of LeObject
# @brief Add a new instance of LeObject
# @return a new uid en case of success, False otherwise
@classmethod
def insert(cls, datas):
query = LeInsertQuery(cls)
return query.execute(datas)
## @brief Update an instance of LeObject
# @brief Update an instance of LeObject
#
#@param datas : list of new datas
def update(self, datas=None):
@ -551,7 +555,7 @@ construction and consitency when datas are not complete\n")
return result
## @brief Delete an instance of LeObject
# @brief Delete an instance of LeObject
#
#@return 1 if the objet has been deleted
def delete(self):
@ -566,7 +570,7 @@ construction and consitency when datas are not complete\n")
return result
## @brief Delete instances of LeObject
# @brief Delete instances of LeObject
#@param query_filters list
#@returns the number of deleted items
@classmethod
@ -585,7 +589,7 @@ construction and consitency when datas are not complete\n")
deleted += result
return deleted
## @brief Get instances of LeObject
# @brief Get instances of LeObject
#
#@param query_filters dict : (filters, relational filters), with filters is a list of tuples : (FIELD, OPERATOR, VALUE) )
#@param field_list list|None : list of string representing fields see
@ -624,7 +628,7 @@ construction and consitency when datas are not complete\n")
return objects
##@brief Retrieve an object given an UID
# @brief Retrieve an object given an UID
#@todo broken multiple UID
@classmethod
def get_from_uid(cls, uid):
@ -651,7 +655,7 @@ object ! For class %s with uid value = %s" % (cls, uid))
return None
return res[0]
##@brief Checks if an object exists
# @brief Checks if an object exists
@classmethod
def is_exist(cls, uid):
if cls.uid_fieldname() is None:
@ -659,4 +663,3 @@ object ! For class %s with uid value = %s" % (cls, uid))
"No uid defined for class %s" % cls.__name__)
from .query import is_exist
return is_exist(cls, uid)

View file

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

View file

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
#-*- coding: utf-8 -*-
##@brief Loader for tests which do not need an lodel installation
# @brief Loader for tests which do not need an lodel installation
#
# Options
################
@ -19,7 +19,9 @@
#
#
import sys, os, os.path
import sys
import os
import os.path
import unittest

View file

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

View file

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