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

Merge branch 'lodel2-datahandlers'

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

View file

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

View file

@ -272,7 +272,7 @@ article.new_field( 'author_note',
group = editorial_group, group = editorial_group,
data_handler = 'text' data_handler = 'text'
) )
# Classe Review # Classe Review
review = em.new_class( 'review', review = em.new_class( 'review',
display_name = 'Review', display_name = 'Review',
group = editorial_group, group = editorial_group,
@ -439,7 +439,7 @@ issue.new_field( 'print_pub_date',
}, },
data_handler = 'datetime', data_handler = 'datetime',
group = editorial_group, group = editorial_group,
) )
issue.new_field( 'e_pub_date', issue.new_field( 'e_pub_date',
display_name = { display_name = {
'eng': 'Electronic publication date', 'eng': 'Electronic publication date',
@ -447,7 +447,7 @@ issue.new_field( 'e_pub_date',
}, },
data_handler = 'datetime', data_handler = 'datetime',
group = editorial_group, group = editorial_group,
) )
issue.new_field( 'abstract', issue.new_field( 'abstract',
display_name = { display_name = {
'eng': 'Abstract', 'eng': 'Abstract',
@ -455,7 +455,7 @@ issue.new_field( 'abstract',
}, },
data_handler = 'text', data_handler = 'text',
group = editorial_group, group = editorial_group,
) )
issue.new_field( 'collection', issue.new_field( 'collection',
display_name = { display_name = {
'eng': 'Collection', 'eng': 'Collection',
@ -691,7 +691,7 @@ user.new_field(
group = user_group, data_handler = 'password', internal = False) group = user_group, data_handler = 'password', internal = False)
#em.save('xmlfile', filename = 'examples/em_test.xml') em.save('xmlfile', filename = 'editorial_models/em_simple.xml')
pickle_file = 'examples/em_simple.pickle' pickle_file = 'examples/em_simple.pickle'
em.save('picklefile', filename = pickle_file) em.save('picklefile', filename = pickle_file)
print("Output written in %s" % pickle_file) print("Output written in %s" % pickle_file)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,28 +8,27 @@ from lodel.context import LodelContext
LodelContext.expose_modules(globals(), { LodelContext.expose_modules(globals(), {
'lodel.logger': 'logger', 'lodel.logger': 'logger',
'lodel.settings.utils': ['SettingsError', 'SettingsErrors'], 'lodel.settings.utils': ['SettingsError', 'SettingsErrors']})
'lodel.settings.validator': ['SettingsValidationError']})
##@brief Merges and loads configuration files ##@brief Merges and loads configuration files
class SettingsLoader(object): class SettingsLoader(object):
## To avoid the DEFAULT section whose values are found in all sections, we ## To avoid the DEFAULT section whose values are found in all sections, we
# have to give it an unsual name # have to give it an unsual name
DEFAULT_SECTION = 'lodel2_default_passaway_tip' DEFAULT_SECTION = 'lodel2_default_passaway_tip'
## @brief Virtual filename when default value is used ## @brief Virtual filename when default value is used
DEFAULT_FILENAME = 'default_value' DEFAULT_FILENAME = 'default_value'
##@brief Constructor ##@brief Constructor
# @param conf_path str : conf.d path # @param conf_path str : conf.d path
def __init__(self,conf_path): def __init__(self, conf_path):
self.__conf_path=conf_path self.__conf_path = conf_path
self.__conf_sv=dict() self.__conf_sv = dict()
self.__conf=self.__merge() self.__conf = self.__merge()
# Stores errors # Stores errors
self.__errors_list = [] self.__errors_list = []
##@brief Lists and merges files in settings_loader.conf_path ##@brief Lists and merges files in settings_loader.conf_path
# @return dict() # @return dict()
def __merge(self): def __merge(self):
@ -38,26 +37,24 @@ class SettingsLoader(object):
logger.debug("SettingsLoader found those settings files : %s" % ( logger.debug("SettingsLoader found those settings files : %s" % (
', '.join(l_dir))) ', '.join(l_dir)))
for f_ini in l_dir: for f_ini in l_dir:
config = configparser.ConfigParser(default_section = self.DEFAULT_SECTION ,interpolation=None) config = configparser.ConfigParser(default_section=self.DEFAULT_SECTION, interpolation=None)
config.read(f_ini) config.read(f_ini)
for section in [ s for s in config if s != self.DEFAULT_SECTION ]: for section in [s for s in config if s != self.DEFAULT_SECTION]:
if section not in conf: if section not in conf:
conf[section] = dict() conf[section] = dict()
for param in config[section]: for param in config[section]:
if param not in conf[section]: if param not in conf[section]:
conf[section][param]=dict() conf[section][param] = dict()
conf[section][param]['value'] = config[section][param] conf[section][param]['value'] = config[section][param]
conf[section][param]['file'] = f_ini conf[section][param]['file'] = f_ini
self.__conf_sv[section + ':' + param]=f_ini self.__conf_sv[section + ':' + param] = f_ini
else: else:
raise SettingsError("Error redeclaration of key %s in section %s. Found in %s and %s" % ( raise SettingsError("Error redeclaration of key %s \
section, in section %s. Found in %s and %s" % (\
param, section, param, f_ini, conf[section][param]['file']))
f_ini,
conf[section][param]['file']))
return conf return conf
##@brief Returns option if exists default_value else and validates ##@brief Returns option if exists default_value else and validates
# @param section str : name of the section # @param section str : name of the section
# @param keyname str # @param keyname str
@ -65,8 +62,8 @@ class SettingsLoader(object):
# @param default_value * # @param default_value *
# @param mandatory bool # @param mandatory bool
# @return the option # @return the option
def getoption(self,section,keyname,validator,default_value=None,mandatory=False): def getoption(self, section, keyname, validator, default_value=None, mandatory=False):
conf=self.__conf conf = self.__conf
if section not in conf: if section not in conf:
conf[section] = dict() conf[section] = dict()
@ -85,35 +82,31 @@ class SettingsLoader(object):
if result is None: if result is None:
if default_value is None and mandatory: if default_value is None and mandatory:
msg = "Default value mandatory for option %s" % keyname msg = "Default value mandatory for option %s" % keyname
expt = SettingsError( msg = msg, expt = SettingsError(msg=msg, key_id=section+'.'+keyname, \
key_id = section+'.'+keyname, filename=sec[keyname]['file'])
filename = sec[keyname]['file'])
self.__errors_list.append(expt) self.__errors_list.append(expt)
return return
else: else:
sec[keyname]=dict() sec[keyname] = dict()
sec[keyname]['value'] = default_value sec[keyname]['value'] = default_value
sec[keyname]['file'] = SettingsLoader.DEFAULT_FILENAME sec[keyname]['file'] = SettingsLoader.DEFAULT_FILENAME
result = default_value result = default_value
logger.debug("Using default value for configuration key %s:%s" % ( logger.debug("Using default value for configuration key %s:%s" \
section, keyname)) % (section, keyname))
try: try:
return validator(result) return validator(result)
except Exception as e: except Exception as e:
# Generating nice exceptions # Generating nice exceptions
if False and sec[keyname]['file'] == SettingsLoader.DEFAULT_FILENAME: if False and sec[keyname]['file'] == SettingsLoader.DEFAULT_FILENAME:
expt = SettingsError( msg = 'Mandatory settings not found', expt = SettingsError(msg='Mandatory settings not found', \
key_id = section+'.'+keyname) key_id=section+'.'+keyname)
self.__errors_list.append(expt) self.__errors_list.append(expt)
else: else:
expt = SettingsValidationError( #expt = ValidationError("For %s.%s : %s" % (section, keyname, e))
"For %s.%s : %s" % expt2 = SettingsError(msg=str(expt), \
(section, keyname,e) key_id=section+'.'+keyname, \
) filename=sec[keyname]['file'])
expt2 = SettingsError( msg = str(expt),
key_id = section+'.'+keyname,
filename = sec[keyname]['file'])
self.__errors_list.append(expt2) self.__errors_list.append(expt2)
return return
@ -123,38 +116,38 @@ class SettingsLoader(object):
# @param value str # @param value str
# @param validator callable : takes one argument value and raises validation fail # @param validator callable : takes one argument value and raises validation fail
# @return the option # @return the option
def setoption(self,section,keyname,value,validator): def setoption(self, section, keyname, value, validator):
f_conf=copy.copy(self.__conf[section][keyname]['file']) f_conf = copy.copy(self.__conf[section][keyname]['file'])
if f_conf == SettingsLoader.DEFAULT_FILENAME: if f_conf == SettingsLoader.DEFAULT_FILENAME:
f_conf = self.__conf_path + '/generated.ini' f_conf = self.__conf_path + '/generated.ini'
conf=self.__conf conf = self.__conf
conf[section][keyname] = value conf[section][keyname] = value
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.read(f_conf) config.read(f_conf)
if section not in config: if section not in config:
config[section]={} config[section] = {}
config[section][keyname] = validator(value) config[section][keyname] = validator(value)
with open(f_conf, 'w') as configfile: with open(f_conf, 'w') as configfile:
config.write(configfile) config.write(configfile)
##@brief Saves new partial configuration. Writes in the conf files corresponding ##@brief Saves new partial configuration. Writes in the conf files corresponding
# @param sections dict # @param sections dict
# @param validators dict of callable : takes one argument value and raises validation fail # @param validators dict of callable : takes one argument value and raises validation fail
def saveconf(self, sections, validators): def saveconf(self, sections, validators):
for sec in sections: for sec in sections:
for kname in sections[sec]: for kname in sections[sec]:
self.setoption(sec,kname,sections[sec][kname],validators[sec][kname]) self.setoption(sec, kname, sections[sec][kname], validators[sec][kname])
##@brief Returns the section to be configured ##@brief Returns the section to be configured
# @param section_prefix str # @param section_prefix str
# @param default_section str # @param default_section str
# @return the section as dict() # @return the section as dict()
def getsection(self,section_prefix,default_section=None): def getsection(self, section_prefix, default_section=None):
conf=copy.copy(self.__conf) conf = copy.copy(self.__conf)
sections=[] sections = []
if section_prefix in conf: if section_prefix in conf:
sections.append(section_prefix) sections.append(section_prefix)
for sect_names in conf: for sect_names in conf:
@ -162,34 +155,33 @@ class SettingsLoader(object):
pass pass
elif sect_names.startswith(section_prefix + '.'): elif sect_names.startswith(section_prefix + '.'):
sections.append(sect_names) sections.append(sect_names)
if sections == [] and default_section: if sections == [] and default_section:
sections.append(section_prefix + '.' + default_section) sections.append(section_prefix + '.' + default_section)
elif sections == []: elif sections == []:
raise NameError("Not existing settings section : %s" % section_prefix) raise NameError("Not existing settings section : %s" % section_prefix)
return sections return sections
##@brief Returns invalid settings ##@brief Returns invalid settings
# #
# This method returns all the settings that was not fecthed by # This method returns all the settings that was not fecthed by
# getsection() method. For the Settings object it allows to know # getsection() method. For the Settings object it allows to know
# the list of invalids settings keys # the list of invalids settings keys
# @return a dict with SECTION_NAME+":"+KEY_NAME as key and the filename # @return a dict with SECTION_NAME+":"+KEY_NAME as key and the filename
# where the settings was found as value # where the settings was found as value
def getremains(self): def getremains(self):
return self.__conf_sv return self.__conf_sv
##@brief Raise a SettingsErrors exception if some confs remains ##@brief Raise a SettingsErrors exception if some confs remains
#@note typically used at the end of Settings bootstrap #@note typically used at the end of Settings bootstrap
def raise_errors(self): def raise_errors(self):
remains = self.getremains() remains = self.getremains()
err_l = self.__errors_list err_l = self.__errors_list
for key_id, filename in remains.items(): for key_id, filename in remains.items():
err_l.append(SettingsError( msg = "Invalid configuration key", err_l.append(SettingsError(msg="Invalid configuration key", \
key_id = key_id, key_id=key_id, \
filename = filename)) filename =filename))
if len(err_l) > 0: if len(err_l) > 0:
raise SettingsErrors(err_l) raise SettingsErrors(err_l)
else: else:
return return

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,12 +1,12 @@
#-*- coding: utf-8 -*- #-*- coding: utf-8 -*-
##@brief Loader for tests which do not need an lodel installation # @brief Loader for tests which do not need an lodel installation
# #
# Options # Options
################ ################
# #
# @note We can pass the path to a directory to write results file, nocontext_tests.log # @note We can pass the path to a directory to write results file, nocontext_tests.log
# It has to be at first, otherwise it will not be taken # It has to be at first, otherwise it will not be taken
# and the default one, current directory, will be used. # and the default one, current directory, will be used.
# The results are not displayed, only stored in nocontext_tests.log # The results are not displayed, only stored in nocontext_tests.log
# #
@ -19,20 +19,22 @@
# #
# #
import sys, os, os.path import sys
import os
import os.path
import unittest import unittest
loader = unittest.TestLoader() loader = unittest.TestLoader()
if ((len(sys.argv) > 1) and (sys.argv[1].startswith('-')) is False): if ((len(sys.argv) > 1) and (sys.argv[1].startswith('-')) is False):
dpath = sys.argv[1] dpath = sys.argv[1]
else: else:
dpath = '.' dpath = '.'
suite = loader.discover('tests', pattern='nc_test*.py') suite = loader.discover('tests', pattern='nc_test*.py')
with open(dpath+'/nocontext_tests.log', 'w') as logfile: with open(dpath + '/nocontext_tests.log', 'w') as logfile:
unittest.TextTestRunner( unittest.TextTestRunner(
logfile, logfile,
failfast = '-f' in sys.argv, failfast='-f' in sys.argv,
verbosity = 2 if '-v' in sys.argv else 1).run(suite) verbosity=2 if '-v' in sys.argv else 1).run(suite)

View file

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

View file

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