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:
commit
5b09492048
39 changed files with 1173 additions and 1036 deletions
|
|
@ -12,6 +12,8 @@ AC_CONFIG_FILES([Makefile \
|
|||
lodel/leapi/datahandlers/Makefile \
|
||||
lodel/plugin/Makefile \
|
||||
lodel/settings/Makefile \
|
||||
lodel/validator/Makefile \
|
||||
lodel/mlnamedobject/Makefile \
|
||||
lodel/utils/Makefile \
|
||||
progs/Makefile \
|
||||
progs/slim/Makefile \
|
||||
|
|
|
|||
|
|
@ -272,7 +272,7 @@ article.new_field( 'author_note',
|
|||
group = editorial_group,
|
||||
data_handler = 'text'
|
||||
)
|
||||
# Classe Review
|
||||
# Classe Review
|
||||
review = em.new_class( 'review',
|
||||
display_name = 'Review',
|
||||
group = editorial_group,
|
||||
|
|
@ -439,7 +439,7 @@ issue.new_field( 'print_pub_date',
|
|||
},
|
||||
data_handler = 'datetime',
|
||||
group = editorial_group,
|
||||
)
|
||||
)
|
||||
issue.new_field( 'e_pub_date',
|
||||
display_name = {
|
||||
'eng': 'Electronic publication date',
|
||||
|
|
@ -447,7 +447,7 @@ issue.new_field( 'e_pub_date',
|
|||
},
|
||||
data_handler = 'datetime',
|
||||
group = editorial_group,
|
||||
)
|
||||
)
|
||||
issue.new_field( 'abstract',
|
||||
display_name = {
|
||||
'eng': 'Abstract',
|
||||
|
|
@ -455,7 +455,7 @@ issue.new_field( 'abstract',
|
|||
},
|
||||
data_handler = 'text',
|
||||
group = editorial_group,
|
||||
)
|
||||
)
|
||||
issue.new_field( 'collection',
|
||||
display_name = {
|
||||
'eng': 'Collection',
|
||||
|
|
@ -691,7 +691,7 @@ user.new_field(
|
|||
group = user_group, data_handler = 'password', internal = False)
|
||||
|
||||
|
||||
#em.save('xmlfile', filename = 'examples/em_test.xml')
|
||||
em.save('xmlfile', filename = 'editorial_models/em_simple.xml')
|
||||
pickle_file = 'examples/em_simple.pickle'
|
||||
em.save('picklefile', filename = pickle_file)
|
||||
print("Output written in %s" % pickle_file)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
SUBDIRS=auth editorial_model leapi plugin settings utils plugins
|
||||
SUBDIRS=auth editorial_model leapi plugin settings utils plugins validator mlnamedobject
|
||||
EXTRA_DIST = plugins
|
||||
lodel_PYTHON = *.py
|
||||
CLEANFILES = buildconf.py
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#-*- coding: utf-8 -*-
|
||||
|
||||
##@package lodel.editorial_model.components
|
||||
# @package lodel.editorial_model.components
|
||||
#@brief Defines all @ref lodel2_em "EM" components
|
||||
#@ingroup lodel2_em
|
||||
|
||||
|
|
@ -12,28 +12,30 @@ import hashlib
|
|||
from lodel.context import LodelContext
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.utils.mlstring': ['MlString'],
|
||||
'lodel.mlnamedobject.mlnamedobject': ['MlNamedObject'],
|
||||
'lodel.settings': ['Settings'],
|
||||
'lodel.editorial_model.exceptions': ['EditorialModelError', 'assert_edit'],
|
||||
'lodel.leapi.leobject': ['CLASS_ID_FIELDNAME']})
|
||||
|
||||
##@brief Abstract class to represent editorial model components
|
||||
# @brief Abstract class to represent editorial model components
|
||||
# @see EmClass EmField
|
||||
# @todo forbid '.' in uid
|
||||
#@ingroup lodel2_em
|
||||
class EmComponent(object):
|
||||
|
||||
##@brief Instanciate an EmComponent
|
||||
|
||||
|
||||
class EmComponent(MlNamedObject):
|
||||
|
||||
# @brief Instanciate an EmComponent
|
||||
# @param uid str : uniq identifier
|
||||
# @param display_name MlString|str|dict : component display_name
|
||||
# @param help_text MlString|str|dict : help_text
|
||||
def __init__(self, uid, display_name = None, help_text = None, group = None):
|
||||
def __init__(self, uid, display_name=None, help_text=None, group=None):
|
||||
if self.__class__ == EmComponent:
|
||||
raise NotImplementedError('EmComponent is an abstract class')
|
||||
self.uid = uid
|
||||
self.display_name = None if display_name is None else MlString(display_name)
|
||||
self.help_text = None if help_text is None else MlString(help_text)
|
||||
self.group = group
|
||||
|
||||
super().__init__(display_name, help_text)
|
||||
|
||||
def __str__(self):
|
||||
if self.display_name is None:
|
||||
return str(self.uid)
|
||||
|
|
@ -42,34 +44,34 @@ class EmComponent(object):
|
|||
def d_hash(self):
|
||||
m = hashlib.md5()
|
||||
for data in (
|
||||
self.uid,
|
||||
'NODISPNAME' if self.display_name is None else str(self.display_name.d_hash()),
|
||||
'NOHELP' if self.help_text is None else str(self.help_text.d_hash()),
|
||||
'NOGROUP' if self.group is None else str(self.group.d_hash()),
|
||||
self.uid,
|
||||
'NODISPNAME' if self.display_name is None else str(self.display_name.d_hash()),
|
||||
'NOHELP' if self.help_text is None else str(self.help_text.d_hash()),
|
||||
'NOGROUP' if self.group is None else str(self.group.d_hash()),
|
||||
):
|
||||
m.update(bytes(data, 'utf-8'))
|
||||
return int.from_bytes(m.digest(), byteorder='big')
|
||||
|
||||
|
||||
##@brief Handles editorial model objects classes
|
||||
# @brief Handles editorial model objects classes
|
||||
#@ingroup lodel2_em
|
||||
class EmClass(EmComponent):
|
||||
|
||||
##@brief Instanciate a new EmClass
|
||||
|
||||
# @brief Instanciate a new EmClass
|
||||
#@param uid str : uniq identifier
|
||||
#@param display_name MlString|str|dict : component display_name
|
||||
#@param abstract bool : set the class as asbtract if True
|
||||
#@param pure_abstract bool : if True the EmClass will not be represented in
|
||||
#leapi dyncode
|
||||
# leapi dyncode
|
||||
#@param parents list: parent EmClass list or uid list
|
||||
#@param help_text MlString|str|dict : help_text
|
||||
#@param datasources str|tuple|list : The datasource name ( see
|
||||
#@param datasources str|tuple|list : The datasource name ( see
|
||||
#@ref lodel2_datasources ) or two names (first is read_only datasource the
|
||||
#second is read write)
|
||||
# second is read write)
|
||||
def __init__(
|
||||
self, uid, display_name = None, help_text = None, abstract = False,
|
||||
parents = None, group = None, pure_abstract = False,
|
||||
datasources = 'default'):
|
||||
self, uid, display_name=None, help_text=None, abstract=False,
|
||||
parents=None, group=None, pure_abstract=False,
|
||||
datasources='default'):
|
||||
|
||||
super().__init__(uid, display_name, help_text, group)
|
||||
self.abstract = bool(abstract)
|
||||
|
|
@ -85,49 +87,50 @@ class EmClass(EmComponent):
|
|||
parents = [parents]
|
||||
for parent in parents:
|
||||
if not isinstance(parent, EmClass):
|
||||
raise ValueError("<class EmClass> expected in parents list, but %s found" % type(parent))
|
||||
raise ValueError(
|
||||
"<class EmClass> expected in parents list, but %s found" % type(parent))
|
||||
else:
|
||||
parents = list()
|
||||
self.parents = parents
|
||||
##@brief Stores EmFields instances indexed by field uid
|
||||
self.__fields = dict()
|
||||
|
||||
# @brief Stores EmFields instances indexed by field uid
|
||||
self.__fields = dict()
|
||||
|
||||
self.group = group
|
||||
if group is None:
|
||||
warnings.warn("NO GROUP FOR EMCLASS %s" % uid)
|
||||
else:
|
||||
group.add_components([self])
|
||||
|
||||
#Adding common field
|
||||
|
||||
# Adding common field
|
||||
if not self.abstract:
|
||||
self.new_field(
|
||||
CLASS_ID_FIELDNAME,
|
||||
display_name = {
|
||||
display_name={
|
||||
'eng': "LeObject subclass identifier",
|
||||
'fre': "Identifiant de la class fille de LeObject"},
|
||||
help_text = {
|
||||
help_text={
|
||||
'eng': "Allow to create instance of the good class when\
|
||||
fetching arbitrary datas from DB"},
|
||||
data_handler = 'LeobjectSubclassIdentifier',
|
||||
internal = True,
|
||||
group = group)
|
||||
data_handler='LeobjectSubclassIdentifier',
|
||||
internal=True,
|
||||
group=group)
|
||||
|
||||
##@brief Property that represent a dict of all fields (the EmField defined in this class and all its parents)
|
||||
# @brief Property that represent a dict of all fields (the EmField defined in this class and all its parents)
|
||||
# @todo use Settings.editorialmodel.groups to determine wich fields should be returned
|
||||
@property
|
||||
def __all_fields(self):
|
||||
res = dict()
|
||||
for pfields in [ p.__all_fields for p in self.parents]:
|
||||
for pfields in [p.__all_fields for p in self.parents]:
|
||||
res.update(pfields)
|
||||
res.update(self.__fields)
|
||||
return res
|
||||
|
||||
##@brief RO access to datasource attribute
|
||||
|
||||
# @brief RO access to datasource attribute
|
||||
@property
|
||||
def datasource(self):
|
||||
return self.__datasource
|
||||
|
||||
##@brief Return the list of all dependencies
|
||||
# @brief Return the list of all dependencies
|
||||
#
|
||||
# Reccursive parents listing
|
||||
@property
|
||||
|
|
@ -140,29 +143,29 @@ class EmClass(EmComponent):
|
|||
res |= parent.parents_recc
|
||||
return res
|
||||
|
||||
##@brief EmField getter
|
||||
# @brief EmField getter
|
||||
# @param uid None | str : If None returns an iterator on EmField instances else return an EmField instance
|
||||
# @param no_parents bool : If True returns only fields defined is this class and not the one defined in parents classes
|
||||
# @return A list on EmFields instances (if uid is None) else return an EmField instance
|
||||
# @todo use Settings.editorialmodel.groups to determine wich fields should be returned
|
||||
def fields(self, uid = None, no_parents = False):
|
||||
def fields(self, uid=None, no_parents=False):
|
||||
fields = self.__fields if no_parents else self.__all_fields
|
||||
try:
|
||||
return list(fields.values()) if uid is None else fields[uid]
|
||||
except KeyError:
|
||||
raise EditorialModelError("No such EmField '%s'" % uid)
|
||||
|
||||
##@brief Keep in __fields only fields contained in active groups
|
||||
|
||||
# @brief Keep in __fields only fields contained in active groups
|
||||
def _set_active_fields(self, active_groups):
|
||||
if not Settings.editorialmodel.editormode:
|
||||
active_fields = []
|
||||
for grp_name, agrp in active_groups.items():
|
||||
active_fields += [ emc for emc in agrp.components()
|
||||
if isinstance(emc, EmField)]
|
||||
self.__fields = { fname:fdh for fname, fdh in self.__fields.items()
|
||||
if fdh in active_fields }
|
||||
active_fields += [emc for emc in agrp.components()
|
||||
if isinstance(emc, EmField)]
|
||||
self.__fields = {fname: fdh for fname, fdh in self.__fields.items()
|
||||
if fdh in active_fields}
|
||||
|
||||
##@brief Add a field to the EmClass
|
||||
# @brief Add a field to the EmClass
|
||||
# @param emfield EmField : an EmField instance
|
||||
# @warning do not add an EmField allready in another class !
|
||||
# @throw EditorialModelException if an EmField with same uid allready in this EmClass (overwritting allowed from parents)
|
||||
|
|
@ -170,38 +173,40 @@ class EmClass(EmComponent):
|
|||
def add_field(self, emfield):
|
||||
assert_edit()
|
||||
if emfield.uid in self.__fields:
|
||||
raise EditorialModelError("Duplicated uid '%s' for EmField in this class ( %s )" % (emfield.uid, self))
|
||||
raise EditorialModelError(
|
||||
"Duplicated uid '%s' for EmField in this class ( %s )" % (emfield.uid, self))
|
||||
# Incomplete field override check
|
||||
if emfield.uid in self.__all_fields:
|
||||
parent_field = self.__all_fields[emfield.uid]
|
||||
if not emfield.data_handler_instance.can_override(parent_field.data_handler_instance):
|
||||
raise AttributeError("'%s' field override a parent field, but data_handles are not compatible" % emfield.uid)
|
||||
raise AttributeError(
|
||||
"'%s' field override a parent field, but data_handles are not compatible" % emfield.uid)
|
||||
self.__fields[emfield.uid] = emfield
|
||||
return emfield
|
||||
|
||||
##@brief Create a new EmField and add it to the EmClass
|
||||
|
||||
# @brief Create a new EmField and add it to the EmClass
|
||||
# @param data_handler str : A DataHandler name
|
||||
# @param uid str : the EmField uniq id
|
||||
# @param **field_kwargs : EmField constructor parameters ( see @ref EmField.__init__() )
|
||||
# @param **field_kwargs : EmField constructor parameters ( see @ref EmField.__init__() )
|
||||
def new_field(self, uid, data_handler, **field_kwargs):
|
||||
assert_edit()
|
||||
return self.add_field(EmField(uid, data_handler, self, **field_kwargs))
|
||||
|
||||
def d_hash(self):
|
||||
m = hashlib.md5()
|
||||
payload = str(super().d_hash()) + ("1" if self.abstract else "0")
|
||||
payload = str(super().d_hash()) + ("1" if self.abstract else "0")
|
||||
|
||||
for p in sorted(self.parents):
|
||||
payload += str(p.d_hash())
|
||||
for fuid in sorted(self.__fields.keys()):
|
||||
payload += str(self.__fields[fuid].d_hash())
|
||||
|
||||
|
||||
m.update(bytes(payload, 'utf-8'))
|
||||
return int.from_bytes(m.digest(), byteorder='big')
|
||||
|
||||
def __str__(self):
|
||||
return "<class EmClass %s>" % self.uid
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
if not self.abstract:
|
||||
abstract = ''
|
||||
|
|
@ -209,94 +214,95 @@ class EmClass(EmComponent):
|
|||
abstract = 'PureAbstract'
|
||||
else:
|
||||
abstract = 'Abstract'
|
||||
return "<class %s EmClass uid=%s>" % (abstract, repr(self.uid) )
|
||||
return "<class %s EmClass uid=%s>" % (abstract, repr(self.uid))
|
||||
|
||||
|
||||
##@brief Handles editorial model classes fields
|
||||
# @brief Handles editorial model classes fields
|
||||
#@ingroup lodel2_em
|
||||
class EmField(EmComponent):
|
||||
|
||||
##@brief Instanciate a new EmField
|
||||
# @brief Instanciate a new EmField
|
||||
# @param uid str : uniq identifier
|
||||
# @param display_name MlString|str|dict : field display_name
|
||||
# @param data_handler str : A DataHandler name
|
||||
# @param help_text MlString|str|dict : help text
|
||||
# @param group EmGroup :
|
||||
# @param **handler_kwargs : data handler arguments
|
||||
def __init__(self, uid, data_handler, em_class = None, display_name = None, help_text = None, group = None, **handler_kwargs):
|
||||
def __init__(self, uid, data_handler, em_class=None, display_name=None, help_text=None, group=None, **handler_kwargs):
|
||||
from lodel.leapi.datahandlers.base_classes import DataHandler
|
||||
super().__init__(uid, display_name, help_text, group)
|
||||
##@brief The data handler name
|
||||
# @brief The data handler name
|
||||
self.data_handler_name = data_handler
|
||||
##@brief The data handler class
|
||||
# @brief The data handler class
|
||||
self.data_handler_cls = DataHandler.from_name(data_handler)
|
||||
##@brief The data handler instance associated with this EmField
|
||||
# @brief The data handler instance associated with this EmField
|
||||
self.data_handler_instance = self.data_handler_cls(**handler_kwargs)
|
||||
##@brief Stores data handler instanciation options
|
||||
# @brief Stores data handler instanciation options
|
||||
self.data_handler_options = handler_kwargs
|
||||
##@brief Stores the emclass that contains this field (set by EmClass.add_field() method)
|
||||
# @brief Stores the emclass that contains this field (set by EmClass.add_field() method)
|
||||
self._emclass = em_class
|
||||
if self._emclass is None:
|
||||
warnings.warn("No EmClass for field %s" %uid)
|
||||
warnings.warn("No EmClass for field %s" % uid)
|
||||
if group is None:
|
||||
warnings.warn("No EmGroup for field %s" % uid)
|
||||
else:
|
||||
group.add_components([self])
|
||||
|
||||
##@brief Returns data_handler_name attribute
|
||||
# @brief Returns data_handler_name attribute
|
||||
def get_data_handler_name(self):
|
||||
return copy.copy(self.data_handler_name)
|
||||
|
||||
##@brief Returns data_handler_cls attribute
|
||||
|
||||
# @brief Returns data_handler_cls attribute
|
||||
def get_data_handler_cls(self):
|
||||
return copy.copy(selfdata_handler_cls)
|
||||
|
||||
return copy.copy(self.data_handler_cls)
|
||||
|
||||
##@brief Returne the uid of the emclass which contains this field
|
||||
def get_emclass_uid(self):
|
||||
return self._emclass.uid
|
||||
|
||||
|
||||
# @warning Not complete !
|
||||
# @todo Complete the hash when data handlers becomes available
|
||||
def d_hash(self):
|
||||
return int.from_bytes(hashlib.md5(
|
||||
bytes(
|
||||
"%s%s%s" % ( super().d_hash(),
|
||||
self.data_handler_name,
|
||||
self.data_handler_options),
|
||||
'utf-8')
|
||||
bytes(
|
||||
"%s%s%s" % (super().d_hash(),
|
||||
self.data_handler_name,
|
||||
self.data_handler_options),
|
||||
'utf-8')
|
||||
).digest(), byteorder='big')
|
||||
|
||||
##@brief Handles functionnal group of EmComponents
|
||||
# @brief Handles functionnal group of EmComponents
|
||||
#@ingroup lodel2_em
|
||||
class EmGroup(object):
|
||||
|
||||
##@brief Create a new EmGroup
|
||||
|
||||
|
||||
class EmGroup(MlNamedObject):
|
||||
|
||||
# @brief Create a new EmGroup
|
||||
# @note you should NEVER call the constructor yourself. Use Model.add_group instead
|
||||
# @param uid str : Uniq identifier
|
||||
# @param depends list : A list of EmGroup dependencies
|
||||
# @param display_name MlString|str :
|
||||
# @param help_text MlString|str :
|
||||
def __init__(self, uid, depends = None, display_name = None, help_text = None):
|
||||
# @param display_name MlString|str :
|
||||
# @param help_text MlString|str :
|
||||
def __init__(self, uid, depends=None, display_name=None, help_text=None):
|
||||
self.uid = uid
|
||||
##@brief Stores the list of groups that depends on this EmGroup indexed by uid
|
||||
# @brief Stores the list of groups that depends on this EmGroup indexed by uid
|
||||
self.required_by = dict()
|
||||
##@brief Stores the list of dependencies (EmGroup) indexed by uid
|
||||
# @brief Stores the list of dependencies (EmGroup) indexed by uid
|
||||
self.require = dict()
|
||||
##@brief Stores the list of EmComponent instances contained in this group
|
||||
# @brief Stores the list of EmComponent instances contained in this group
|
||||
self.__components = set()
|
||||
super().__init__(display_name, help_text)
|
||||
|
||||
self.display_name = None if display_name is None else MlString(display_name)
|
||||
self.help_text = None if help_text is None else MlString(help_text)
|
||||
if depends is not None:
|
||||
for grp in depends:
|
||||
if not isinstance(grp, EmGroup):
|
||||
raise ValueError("EmGroup expected in depends argument but %s found" % grp)
|
||||
self.add_dependencie(grp)
|
||||
|
||||
##@brief Returns EmGroup dependencie
|
||||
|
||||
# @brief Returns EmGroup dependencie
|
||||
# @param recursive bool : if True return all dependencies and their dependencies
|
||||
# @return a dict of EmGroup identified by uid
|
||||
def dependencies(self, recursive = False):
|
||||
def dependencies(self, recursive=False):
|
||||
res = copy.copy(self.require)
|
||||
if not recursive:
|
||||
return res
|
||||
|
|
@ -308,11 +314,11 @@ class EmGroup(object):
|
|||
to_scan.append(new_dep)
|
||||
res[new_dep.uid] = new_dep
|
||||
return res
|
||||
|
||||
##@brief Returns EmGroup applicants
|
||||
|
||||
# @brief Returns EmGroup applicants
|
||||
# @param recursive bool : if True return all dependencies and their dependencies
|
||||
# @returns a dict of EmGroup identified by uid
|
||||
def applicants(self, recursive = False):
|
||||
def applicants(self, recursive=False):
|
||||
res = copy.copy(self.required_by)
|
||||
if not recursive:
|
||||
return res
|
||||
|
|
@ -324,29 +330,31 @@ class EmGroup(object):
|
|||
to_scan.append(new_app)
|
||||
res[new_app.uid] = new_app
|
||||
return res
|
||||
|
||||
##@brief Returns EmGroup components
|
||||
|
||||
# @brief Returns EmGroup components
|
||||
# @returns a copy of the set of components
|
||||
def components(self):
|
||||
return (self.__components).copy()
|
||||
|
||||
##@brief Returns EmGroup display_name
|
||||
# @brief Returns EmGroup display_name
|
||||
# @param lang str | None : If None return default lang translation
|
||||
# @returns None if display_name is None, a str for display_name else
|
||||
def get_display_name(self, lang=None):
|
||||
name=self.display_name
|
||||
if name is None : return None
|
||||
return name.get(lang);
|
||||
name = self.display_name
|
||||
if name is None:
|
||||
return None
|
||||
return name.get(lang)
|
||||
|
||||
##@brief Returns EmGroup help_text
|
||||
# @brief Returns EmGroup help_text
|
||||
# @param lang str | None : If None return default lang translation
|
||||
# @returns None if display_name is None, a str for display_name else
|
||||
def get_help_text(self, lang=None):
|
||||
help=self.help_text
|
||||
if help is None : return None
|
||||
return help.get(lang);
|
||||
|
||||
##@brief Add components in a group
|
||||
help = self.help_text
|
||||
if help is None:
|
||||
return None
|
||||
return help.get(lang)
|
||||
|
||||
# @brief Add components in a group
|
||||
# @param components list : EmComponent instances list
|
||||
def add_components(self, components):
|
||||
assert_edit()
|
||||
|
|
@ -357,10 +365,11 @@ class EmGroup(object):
|
|||
msg %= (component, self)
|
||||
warnings.warn(msg)
|
||||
elif not isinstance(component, EmClass):
|
||||
raise EditorialModelError("Expecting components to be a list of EmComponent, but %s found in the list" % type(component))
|
||||
raise EditorialModelError(
|
||||
"Expecting components to be a list of EmComponent, but %s found in the list" % type(component))
|
||||
self.__components |= set(components)
|
||||
|
||||
##@brief Add a dependencie
|
||||
# @brief Add a dependencie
|
||||
# @param em_group EmGroup|iterable : an EmGroup instance or list of instance
|
||||
def add_dependencie(self, grp):
|
||||
assert_edit()
|
||||
|
|
@ -368,16 +377,17 @@ class EmGroup(object):
|
|||
for group in grp:
|
||||
self.add_dependencie(group)
|
||||
return
|
||||
except TypeError: pass
|
||||
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
if grp.uid in self.require:
|
||||
return
|
||||
if self.__circular_dependencie(grp):
|
||||
raise EditorialModelError("Circular dependencie detected, cannot add dependencie")
|
||||
self.require[grp.uid] = grp
|
||||
grp.required_by[self.uid] = self
|
||||
|
||||
##@brief Add a applicant
|
||||
|
||||
# @brief Add a applicant
|
||||
# @param em_group EmGroup|iterable : an EmGroup instance or list of instance
|
||||
# Useless ???
|
||||
def add_applicant(self, grp):
|
||||
|
|
@ -386,26 +396,27 @@ class EmGroup(object):
|
|||
for group in grp:
|
||||
self.add_applicant(group)
|
||||
return
|
||||
except TypeError: pass
|
||||
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
if grp.uid in self.required_by:
|
||||
return
|
||||
if self.__circular_applicant(grp):
|
||||
raise EditorialModelError("Circular applicant detected, cannot add applicant")
|
||||
self.required_by[grp.uid] = grp
|
||||
grp.require[self.uid] = self
|
||||
|
||||
##@brief Search for circular dependencie
|
||||
|
||||
# @brief Search for circular dependencie
|
||||
# @return True if circular dep found else False
|
||||
def __circular_dependencie(self, new_dep):
|
||||
return self.uid in new_dep.dependencies(True)
|
||||
|
||||
##@brief Search for circular applicant
|
||||
|
||||
# @brief Search for circular applicant
|
||||
# @return True if circular app found else False
|
||||
def __circular_applicant(self, new_app):
|
||||
return self.uid in new_app.applicants(True)
|
||||
|
||||
##@brief Fancy string representation of an EmGroup
|
||||
# @brief Fancy string representation of an EmGroup
|
||||
# @return a string
|
||||
def __str__(self):
|
||||
if self.display_name is None:
|
||||
|
|
@ -414,11 +425,11 @@ class EmGroup(object):
|
|||
return self.display_name.get()
|
||||
|
||||
def d_hash(self):
|
||||
|
||||
|
||||
payload = "%s%s%s" % (
|
||||
self.uid,
|
||||
'NODNAME' if self.display_name is None else self.display_name.d_hash(),
|
||||
'NOHELP' if self.help_text is None else self.help_text.d_hash()
|
||||
self.uid,
|
||||
'NODNAME' if self.display_name is None else self.display_name.d_hash(),
|
||||
'NOHELP' if self.help_text is None else self.help_text.d_hash()
|
||||
)
|
||||
for recurs in (False, True):
|
||||
deps = self.dependencies(recurs)
|
||||
|
|
@ -427,11 +438,11 @@ class EmGroup(object):
|
|||
for req_by_uid in self.required_by:
|
||||
payload += req_by_uid
|
||||
return int.from_bytes(
|
||||
bytes(payload, 'utf-8'),
|
||||
byteorder = 'big'
|
||||
bytes(payload, 'utf-8'),
|
||||
byteorder='big'
|
||||
)
|
||||
|
||||
##@brief Complete string representation of an EmGroup
|
||||
|
||||
# @brief Complete string representation of an EmGroup
|
||||
# @return a string
|
||||
def __repr__(self):
|
||||
return "<class EmGroup '%s' depends : [%s]>" % (self.uid, ', '.join([duid for duid in self.dependencies(False)]) )
|
||||
return "<class EmGroup '%s' depends : [%s]>" % (self.uid, ', '.join([duid for duid in self.dependencies(False)]))
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import copy
|
|||
from lodel.context import LodelContext
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.utils.mlstring': ['MlString'],
|
||||
'lodel.mlnamedobject.mlnamedobject': ['MlNamedObject'],
|
||||
'lodel.logger': 'logger',
|
||||
'lodel.settings': ['Settings'],
|
||||
'lodel.settings.utils': ['SettingsError'],
|
||||
|
|
@ -14,29 +15,34 @@ LodelContext.expose_modules(globals(), {
|
|||
'lodel.editorial_model.components': ['EmClass', 'EmField', 'EmGroup']})
|
||||
|
||||
|
||||
##@brief Describe an editorial model
|
||||
# @brief Describe an editorial model
|
||||
#@ingroup lodel2_em
|
||||
class EditorialModel(object):
|
||||
|
||||
##@brief Create a new editorial model
|
||||
class EditorialModel(MlNamedObject):
|
||||
|
||||
# @brief Create a new editorial model
|
||||
# @param name MlString|str|dict : the editorial model name
|
||||
# @param description MlString|str|dict : the editorial model description
|
||||
def __init__(self, name, description = None):
|
||||
def __init__(self, name, description=None, display_name=None, help_text=None):
|
||||
self.name = MlString(name)
|
||||
self.description = MlString(description)
|
||||
##@brief Stores all groups indexed by id
|
||||
# @brief Stores all groups indexed by id
|
||||
self.__groups = dict()
|
||||
##@brief Stores all classes indexed by id
|
||||
# @brief Stores all classes indexed by id
|
||||
self.__classes = dict()
|
||||
## @brief Stores all activated groups indexed by id
|
||||
# @brief Stores all activated groups indexed by id
|
||||
self.__active_groups = dict()
|
||||
## @brief Stores all activated classes indexed by id
|
||||
# @brief Stores all activated classes indexed by id
|
||||
self.__active_classes = dict()
|
||||
self.__set_actives()
|
||||
|
||||
##@brief EmClass uids accessor
|
||||
if display_name is None:
|
||||
display_name = name
|
||||
if help_text is None:
|
||||
help_text = description
|
||||
super().__init__(display_name, help_text)
|
||||
|
||||
# @brief EmClass uids accessor
|
||||
#@return a dict of emclasses
|
||||
def all_classes(self, uid = None):
|
||||
def all_classes(self, uid=None):
|
||||
if uid is None:
|
||||
return copy.copy(self.__classes)
|
||||
else:
|
||||
|
|
@ -44,8 +50,8 @@ class EditorialModel(object):
|
|||
return copy.copy(self.__classes[uid])
|
||||
except KeyError:
|
||||
raise EditorialModelException("EmClass not found : '%s'" % uid)
|
||||
|
||||
def all_classes_ref(self, uid = None):
|
||||
|
||||
def all_classes_ref(self, uid=None):
|
||||
if uid is None:
|
||||
return self.__classes
|
||||
else:
|
||||
|
|
@ -53,16 +59,15 @@ class EditorialModel(object):
|
|||
return self.__classes[uid]
|
||||
except KeyError:
|
||||
raise EditorialModelException("EmGroup not found : '%s'" % uid)
|
||||
|
||||
##@brief active EmClass uids accessor
|
||||
|
||||
# @brief active EmClass uids accessor
|
||||
#@return a list of class uids
|
||||
def active_classes_uids(self):
|
||||
return list(self.__active_classes.keys())
|
||||
|
||||
|
||||
##@brief EmGroups accessor
|
||||
return list(self.__active_classes.keys())
|
||||
|
||||
# @brief EmGroups accessor
|
||||
#@return a dict of groups
|
||||
def all_groups(self, uid = None):
|
||||
def all_groups(self, uid=None):
|
||||
if uid is None:
|
||||
return copy.copy(self.__groups)
|
||||
else:
|
||||
|
|
@ -70,10 +75,10 @@ class EditorialModel(object):
|
|||
return copy.copy(self.__groups[uid])
|
||||
except KeyError:
|
||||
raise EditorialModelException("EmGroup not found : '%s'" % uid)
|
||||
|
||||
##@brief EmGroups accessor
|
||||
|
||||
# @brief EmGroups accessor
|
||||
#@return a dict of groups
|
||||
def all_groups_ref(self, uid = None):
|
||||
def all_groups_ref(self, uid=None):
|
||||
if uid is None:
|
||||
return self.__groups
|
||||
else:
|
||||
|
|
@ -81,26 +86,26 @@ class EditorialModel(object):
|
|||
return self.__groups[uid]
|
||||
except KeyError:
|
||||
raise EditorialModelException("EmGroup not found : '%s'" % uid)
|
||||
|
||||
##@brief active EmClass uids accessor
|
||||
|
||||
# @brief active EmClass uids accessor
|
||||
#@return a list of class uids
|
||||
def active_groups_uids(self):
|
||||
return list(self.__active_groups.keys())
|
||||
return list(self.__active_groups.keys())
|
||||
|
||||
##@brief EmClass accessor
|
||||
# @brief EmClass accessor
|
||||
#@param uid None | str : give this argument to get a specific EmClass
|
||||
#@return if uid is given returns an EmClass else returns an EmClass
|
||||
# iterator
|
||||
#@todo use Settings.editorialmodel.groups to determine wich classes should
|
||||
# be returned
|
||||
def classes(self, uid = None):
|
||||
def classes(self, uid=None):
|
||||
try:
|
||||
return self.__elt_getter( self.__active_classes,
|
||||
uid)
|
||||
return self.__elt_getter(self.__active_classes,
|
||||
uid)
|
||||
except KeyError:
|
||||
raise EditorialModelException("EmClass not found : '%s'" % uid)
|
||||
|
||||
##@brief EmClass child list accessor
|
||||
|
||||
# @brief EmClass child list accessor
|
||||
#@param uid str : the EmClass uid
|
||||
#@return a set of EmClass
|
||||
def get_class_childs(self, uid):
|
||||
|
|
@ -111,24 +116,23 @@ class EditorialModel(object):
|
|||
res.append(cls)
|
||||
return set(res)
|
||||
|
||||
|
||||
##@brief EmGroup getter
|
||||
# @brief EmGroup getter
|
||||
# @param uid None | str : give this argument to get a specific EmGroup
|
||||
# @return if uid is given returns an EmGroup else returns an EmGroup iterator
|
||||
def groups(self, uid = None):
|
||||
def groups(self, uid=None):
|
||||
try:
|
||||
return self.__elt_getter( self.__active_groups,
|
||||
uid)
|
||||
return self.__elt_getter(self.__active_groups,
|
||||
uid)
|
||||
except KeyError:
|
||||
raise EditorialModelException("EmGroup not found : '%s'" % uid)
|
||||
|
||||
##@brief Private getter for __groups or __classes
|
||||
|
||||
# @brief Private getter for __groups or __classes
|
||||
# @see classes() groups()
|
||||
def __elt_getter(self, elts, uid):
|
||||
return list(elts.values()) if uid is None else elts[uid]
|
||||
|
||||
##@brief Update the EditorialModel.__active_groups and
|
||||
#EditorialModel.__active_classes attibutes
|
||||
|
||||
# @brief Update the EditorialModel.__active_groups and
|
||||
# EditorialModel.__active_classes attibutes
|
||||
def __set_actives(self):
|
||||
if Settings.editorialmodel.editormode:
|
||||
logger.warning("All EM groups active because editormode in ON")
|
||||
|
|
@ -136,7 +140,7 @@ class EditorialModel(object):
|
|||
self.__active_groups = self.__groups
|
||||
self.__active_classes = self.__classes
|
||||
else:
|
||||
#determine groups first
|
||||
# determine groups first
|
||||
self.__active_groups = dict()
|
||||
self.__active_classes = dict()
|
||||
for agrp in Settings.editorialmodel.groups:
|
||||
|
|
@ -153,13 +157,13 @@ class EditorialModel(object):
|
|||
raise RuntimeError("No active class found. Abording")
|
||||
for clsname, acls in self.__active_classes.items():
|
||||
acls._set_active_fields(self.__active_groups)
|
||||
|
||||
##@brief EmField getter
|
||||
|
||||
# @brief EmField getter
|
||||
# @param uid str : An EmField uid represented by "CLASSUID.FIELDUID"
|
||||
# @return Fals or an EmField instance
|
||||
#
|
||||
# @todo delete it, useless...
|
||||
def field(self, uid = None):
|
||||
def field(self, uid=None):
|
||||
spl = uid.split('.')
|
||||
if len(spl) != 2:
|
||||
raise ValueError("Malformed EmField identifier : '%s'" % uid)
|
||||
|
|
@ -175,7 +179,7 @@ class EditorialModel(object):
|
|||
pass
|
||||
return False
|
||||
|
||||
##@brief Add a class to the editorial model
|
||||
# @brief Add a class to the editorial model
|
||||
# @param emclass EmClass : the EmClass instance to add
|
||||
# @return emclass
|
||||
def add_class(self, emclass):
|
||||
|
|
@ -187,7 +191,7 @@ class EditorialModel(object):
|
|||
self.__classes[emclass.uid] = emclass
|
||||
return emclass
|
||||
|
||||
##@brief Add a group to the editorial model
|
||||
# @brief Add a group to the editorial model
|
||||
# @param emgroup EmGroup : the EmGroup instance to add
|
||||
# @return emgroup
|
||||
def add_group(self, emgroup):
|
||||
|
|
@ -199,15 +203,15 @@ class EditorialModel(object):
|
|||
self.__groups[emgroup.uid] = emgroup
|
||||
return emgroup
|
||||
|
||||
##@brief Add a new EmClass to the editorial model
|
||||
# @brief Add a new EmClass to the editorial model
|
||||
#@param uid str : EmClass uid
|
||||
#@param **kwargs : EmClass constructor options (
|
||||
#@param **kwargs : EmClass constructor options (
|
||||
# see @ref lodel.editorial_model.component.EmClass.__init__() )
|
||||
def new_class(self, uid, **kwargs):
|
||||
assert_edit()
|
||||
return self.add_class(EmClass(uid, **kwargs))
|
||||
|
||||
##@brief Add a new EmGroup to the editorial model
|
||||
|
||||
# @brief Add a new EmGroup to the editorial model
|
||||
#@param uid str : EmGroup uid
|
||||
#@param *kwargs : EmGroup constructor keywords arguments (
|
||||
# see @ref lodel.editorial_model.component.EmGroup.__init__() )
|
||||
|
|
@ -215,7 +219,7 @@ class EditorialModel(object):
|
|||
assert_edit()
|
||||
return self.add_group(EmGroup(uid, **kwargs))
|
||||
|
||||
##@brief Save a model
|
||||
# @brief Save a model
|
||||
# @param translator module : The translator module to use
|
||||
# @param **translator_args
|
||||
def save(self, translator, **translator_kwargs):
|
||||
|
|
@ -223,14 +227,15 @@ class EditorialModel(object):
|
|||
if isinstance(translator, str):
|
||||
translator = self.translator_from_name(translator)
|
||||
return translator.save(self, **translator_kwargs)
|
||||
|
||||
##@brief Raise an error if lodel is not in EM edition mode
|
||||
|
||||
# @brief Raise an error if lodel is not in EM edition mode
|
||||
@staticmethod
|
||||
def raise_if_ro():
|
||||
if not Settings.editorialmodel.editormode:
|
||||
raise EditorialModelError("Lodel in not in EM editor mode. The EM is in read only state")
|
||||
raise EditorialModelError(
|
||||
"Lodel in not in EM editor mode. The EM is in read only state")
|
||||
|
||||
##@brief Load a model
|
||||
# @brief Load a model
|
||||
# @param translator module : The translator module to use
|
||||
# @param **translator_args
|
||||
@classmethod
|
||||
|
|
@ -241,7 +246,7 @@ class EditorialModel(object):
|
|||
res.__set_actives()
|
||||
return res
|
||||
|
||||
##@brief Return a translator module given a translator name
|
||||
# @brief Return a translator module given a translator name
|
||||
# @param translator_name str : The translator name
|
||||
# @return the translator python module
|
||||
# @throw NameError if the translator does not exists
|
||||
|
|
@ -253,12 +258,12 @@ class EditorialModel(object):
|
|||
except ImportError:
|
||||
raise NameError("No translator named %s")
|
||||
return mod
|
||||
|
||||
##@brief Lodel hash
|
||||
|
||||
# @brief Lodel hash
|
||||
def d_hash(self):
|
||||
payload = "%s%s" % (
|
||||
self.name,
|
||||
'NODESC' if self.description is None else self.description.d_hash()
|
||||
self.name,
|
||||
'NODESC' if self.description is None else self.description.d_hash()
|
||||
)
|
||||
for guid in sorted(self.__groups):
|
||||
payload += str(self.__groups[guid].d_hash())
|
||||
|
|
@ -267,7 +272,6 @@ class EditorialModel(object):
|
|||
payload += str(self.__classes[cuid].d_hash())
|
||||
|
||||
return int.from_bytes(
|
||||
hashlib.md5(bytes(payload, 'utf-8')).digest(),
|
||||
byteorder='big'
|
||||
hashlib.md5(bytes(payload, 'utf-8')).digest(),
|
||||
byteorder='big'
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,34 +12,51 @@ import warnings
|
|||
from lodel.context import LodelContext
|
||||
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.exceptions': ['LodelException', 'LodelExceptions',
|
||||
'LodelFatalError', 'DataNoneValid', 'FieldValidationError'],
|
||||
'lodel.leapi.datahandlers.exceptions': ['LodelDataHandlerConsistencyException', 'LodelDataHandlerException'],
|
||||
'lodel.logger': 'logger'})
|
||||
'lodel.exceptions': [
|
||||
'LodelException',
|
||||
'LodelExceptions',
|
||||
'LodelFatalError',
|
||||
'DataNoneValid',
|
||||
'FieldValidationError'
|
||||
],
|
||||
'lodel.mlnamedobject.mlnamedobject': ['MlNamedObject'],
|
||||
'lodel.leapi.datahandlers.exceptions': [
|
||||
'LodelDataHandlerConsistencyException',
|
||||
'LodelDataHandlerException'
|
||||
],
|
||||
'lodel.validator.validator': [
|
||||
'ValidationError'
|
||||
],
|
||||
'lodel.logger': 'logger',
|
||||
'lodel.utils.mlstring': ['MlString']})
|
||||
|
||||
|
||||
##@brief Base class for all data handlers
|
||||
#@ingroup lodel2_datahandlers
|
||||
class DataHandler(object):
|
||||
## @brief Base class for all data handlers
|
||||
# @ingroup lodel2_datahandlers
|
||||
class DataHandler(MlNamedObject):
|
||||
base_type = "type"
|
||||
_HANDLERS_MODULES = ('datas_base', 'datas', 'references')
|
||||
##@brief Stores the DataHandler childs classes indexed by name
|
||||
## @brief Stores the DataHandler childs classes indexed by name
|
||||
_base_handlers = None
|
||||
##@brief Stores custom datahandlers classes indexed by name
|
||||
## @brief Stores custom datahandlers classes indexed by name
|
||||
# @todo do it ! (like plugins, register handlers... blablabla)
|
||||
__custom_handlers = dict()
|
||||
|
||||
help_text = 'Generic Field Data Handler'
|
||||
display_name = "Generic Field"
|
||||
options_spec = dict()
|
||||
options_values = dict()
|
||||
|
||||
##@brief List fields that will be exposed to the construct_data_method
|
||||
## @brief List fields that will be exposed to the construct_data_method
|
||||
_construct_datas_deps = []
|
||||
|
||||
directly_editable = True
|
||||
##@brief constructor
|
||||
|
||||
## @brief constructor
|
||||
#
|
||||
# @param internal False | str : define whether or not a field is internal
|
||||
# @param immutable bool : indicates if the fieldtype has to be defined in child classes of LeObject or if it is
|
||||
# designed globally and immutable
|
||||
# @param **args
|
||||
# @param immutable bool : indicates if the fieldtype has to be defined in child classes of
|
||||
# LeObject or if it is designed globally and immutable
|
||||
# @throw NotImplementedError if it is instanciated directly
|
||||
def __init__(self, **kwargs):
|
||||
if self.__class__ == DataHandler:
|
||||
|
|
@ -54,9 +71,30 @@ class DataHandler(object):
|
|||
self.default, error = self.check_data_value(kwargs['default'])
|
||||
if error:
|
||||
raise error
|
||||
del(kwargs['default'])
|
||||
del kwargs['default']
|
||||
for argname, argval in kwargs.items():
|
||||
setattr(self, argname, argval)
|
||||
self.check_options()
|
||||
|
||||
display_name = kwargs.get('display_name',MlString(self.display_name))
|
||||
help_text = kwargs.get('help_text', MlString(self.help_text))
|
||||
super().__init__(display_name, help_text)
|
||||
|
||||
## @brief Sets properly casted and checked options for the datahandler
|
||||
# @raises LodelDataHandlerNotAllowedOptionException when a passed option is not in the option
|
||||
# specifications of the datahandler
|
||||
def check_options(self):
|
||||
for option_name, option_datas in self.options_spec.items():
|
||||
if option_name in self.options_values:
|
||||
# There is a configured option, we check its value
|
||||
try:
|
||||
self.options_values[option_name] = option_datas[1].check_value(
|
||||
self.options_values[option_name])
|
||||
except ValueError:
|
||||
pass # TODO Deal with the case where the value used for an option is invalid
|
||||
else:
|
||||
# This option was not configured, we get the default value from the specs
|
||||
self.options_values[option_name] = option_datas[0]
|
||||
|
||||
## Fieldtype name
|
||||
@classmethod
|
||||
|
|
@ -74,15 +112,15 @@ class DataHandler(object):
|
|||
def is_primary_key(self):
|
||||
return self.primary_key
|
||||
|
||||
##@brief checks if a fieldtype is internal
|
||||
## @brief checks if a fieldtype is internal
|
||||
# @return bool
|
||||
def is_internal(self):
|
||||
return self.internal is not False
|
||||
|
||||
##brief check if a value can be nullable
|
||||
#@param value *
|
||||
#@throw DataNoneValid if value is None and nullable. LodelExceptions if not nullable
|
||||
#@return value (if not None)
|
||||
## @brief check if a value can be nullable
|
||||
# @param value *
|
||||
# @throw DataNoneValid if value is None and nullable. LodelExceptions if not nullable
|
||||
# @return value (if not None)
|
||||
# @return value
|
||||
def _check_data_value(self, value):
|
||||
if value is None:
|
||||
|
|
@ -91,9 +129,9 @@ class DataHandler(object):
|
|||
raise DataNoneValid("None with a nullable. This exeption is allowed")
|
||||
return value
|
||||
|
||||
##@brief calls the data_field (defined in derived class) _check_data_value() method
|
||||
#@param value *
|
||||
#@return tuple (value|None, None|error) value can be cast if NoneError
|
||||
## @brief calls the data_field (defined in derived class) _check_data_value() method
|
||||
# @param value *
|
||||
# @return tuple (value|None, None|error) value can be cast if NoneError
|
||||
def check_data_value(self, value):
|
||||
try:
|
||||
value = self._check_data_value(value)
|
||||
|
|
@ -103,7 +141,7 @@ class DataHandler(object):
|
|||
return None, expt
|
||||
return value, None
|
||||
|
||||
##@brief checks if this class can override the given data handler
|
||||
## @brief checks if this class can override the given data handler
|
||||
# @param data_handler DataHandler
|
||||
# @return bool
|
||||
def can_override(self, data_handler):
|
||||
|
|
@ -111,17 +149,17 @@ class DataHandler(object):
|
|||
return False
|
||||
return True
|
||||
|
||||
##@brief Build field value
|
||||
#@ingroup lodel2_dh_checks
|
||||
#@warning DO NOT REIMPLEMENT THIS METHOD IN A CUSTOM DATAHANDLER (see
|
||||
#@ref _construct_data() and @ref lodel2_dh_check_impl )
|
||||
#@param emcomponent EmComponent : An EmComponent child class instance
|
||||
#@param fname str : The field name
|
||||
#@param datas dict : dict storing fields values (from the component)
|
||||
#@param cur_value : the value from the current field (identified by fieldname)
|
||||
#@return the value
|
||||
#@throw RunTimeError if data construction fails
|
||||
#@todo raise something else
|
||||
## @brief Build field value
|
||||
# @ingroup lodel2_dh_checks
|
||||
# @warning DO NOT REIMPLEMENT THIS METHOD IN A CUSTOM DATAHANDLER (see
|
||||
# @ref _construct_data() and @ref lodel2_dh_check_impl )
|
||||
# @param emcomponent EmComponent : An EmComponent child class instance
|
||||
# @param fname str : The field name
|
||||
# @param datas dict : dict storing fields values (from the component)
|
||||
# @param cur_value : the value from the current field (identified by fieldname)
|
||||
# @return the value
|
||||
# @throw RunTimeError if data construction fails
|
||||
# @todo raise something else
|
||||
def construct_data(self, emcomponent, fname, datas, cur_value):
|
||||
emcomponent_fields = emcomponent.fields()
|
||||
data_handler = None
|
||||
|
|
@ -136,41 +174,41 @@ class DataHandler(object):
|
|||
new_val = None
|
||||
return self._construct_data(emcomponent, fname, datas, new_val)
|
||||
|
||||
##@brief Designed to be reimplemented by child classes
|
||||
#@param emcomponent EmComponent : An EmComponent child class instance
|
||||
#@param fname str : The field name
|
||||
#@param datas dict : dict storing fields values (from the component)
|
||||
#@param cur_value : the value from the current field (identified by fieldname)
|
||||
#@return the value
|
||||
#@see construct_data() lodel2_dh_check_impl
|
||||
## @brief Designed to be reimplemented by child classes
|
||||
# @param emcomponent EmComponent : An EmComponent child class instance
|
||||
# @param fname str : The field name
|
||||
# @param datas dict : dict storing fields values (from the component)
|
||||
# @param cur_value : the value from the current field (identified by fieldname)
|
||||
# @return the value
|
||||
# @see construct_data() lodel2_dh_check_impl
|
||||
def _construct_data(self, empcomponent, fname, datas, cur_value):
|
||||
return cur_value
|
||||
|
||||
##@brief Check datas consistency
|
||||
#@ingroup lodel2_dh_checks
|
||||
#@warning DO NOT REIMPLEMENT THIS METHOD IN A CUSTOM DATAHANDLER (see
|
||||
#@ref _construct_data() and @ref lodel2_dh_check_impl )
|
||||
#@warning the datas argument looks like a dict but is not a dict
|
||||
#see @ref base_classes.DatasConstructor "DatasConstructor" and
|
||||
#@ref lodel2_dh_datas_construction "Datas construction section"
|
||||
#@param emcomponent EmComponent : An EmComponent child class instance
|
||||
#@param fname : the field name
|
||||
#@param datas dict : dict storing fields values
|
||||
#@return an Exception instance if fails else True
|
||||
#@todo A implémenter
|
||||
## @brief Check datas consistency
|
||||
# @ingroup lodel2_dh_checks
|
||||
# @warning DO NOT REIMPLEMENT THIS METHOD IN A CUSTOM DATAHANDLER (see
|
||||
# @ref _construct_data() and @ref lodel2_dh_check_impl )
|
||||
# @warning the datas argument looks like a dict but is not a dict
|
||||
# see @ref base_classes.DatasConstructor "DatasConstructor" and
|
||||
# @ref lodel2_dh_datas_construction "Datas construction section"
|
||||
# @param emcomponent EmComponent : An EmComponent child class instance
|
||||
# @param fname : the field name
|
||||
# @param datas dict : dict storing fields values
|
||||
# @return an Exception instance if fails else True
|
||||
# @todo A implémenter
|
||||
def check_data_consistency(self, emcomponent, fname, datas):
|
||||
return self._check_data_consistency(emcomponent, fname, datas)
|
||||
|
||||
##@brief Designed to be reimplemented by child classes
|
||||
#@param emcomponent EmComponent : An EmComponent child class instance
|
||||
#@param fname : the field name
|
||||
#@param datas dict : dict storing fields values
|
||||
#@return an Exception instance if fails else True
|
||||
#@see check_data_consistency() lodel2_dh_check_impl
|
||||
## @brief Designed to be reimplemented by child classes
|
||||
# @param emcomponent EmComponent : An EmComponent child class instance
|
||||
# @param fname : the field name
|
||||
# @param datas dict : dict storing fields values
|
||||
# @return an Exception instance if fails else True
|
||||
# @see check_data_consistency() lodel2_dh_check_impl
|
||||
def _check_data_consistency(self, emcomponent, fname, datas):
|
||||
return True
|
||||
|
||||
##@brief make consistency after a query
|
||||
## @brief make consistency after a query
|
||||
# @param emcomponent EmComponent : An EmComponent child class instance
|
||||
# @param fname : the field name
|
||||
# @param datas dict : dict storing fields values
|
||||
|
|
@ -179,7 +217,7 @@ class DataHandler(object):
|
|||
def make_consistency(self, emcomponent, fname, datas):
|
||||
pass
|
||||
|
||||
##@brief This method is use by plugins to register new data handlers
|
||||
## @brief This method is use by plugins to register new data handlers
|
||||
@classmethod
|
||||
def register_new_handler(cls, name, data_handler):
|
||||
if not inspect.isclass(data_handler):
|
||||
|
|
@ -188,7 +226,7 @@ class DataHandler(object):
|
|||
raise ValueError("A data handler HAS TO be a child class of DataHandler")
|
||||
cls.__custom_handlers[name] = data_handler
|
||||
|
||||
##@brief Load all datahandlers
|
||||
## @brief Load all datahandlers
|
||||
@classmethod
|
||||
def load_base_handlers(cls):
|
||||
if cls._base_handlers is None:
|
||||
|
|
@ -201,10 +239,11 @@ class DataHandler(object):
|
|||
cls._base_handlers[name.lower()] = obj
|
||||
return copy.copy(cls._base_handlers)
|
||||
|
||||
##@brief given a field type name, returns the associated python class
|
||||
## @brief given a field type name, returns the associated python class
|
||||
# @param fieldtype_name str : A field type name (not case sensitive)
|
||||
# @return DataField child class
|
||||
# @note To access custom data handlers it can be cool to prefix the handler name by plugin name for example ? (to ensure name unicity)
|
||||
# @note To access custom data handlers it can be cool to prefix the handler name by plugin
|
||||
# name for example ? (to ensure name unicity)
|
||||
@classmethod
|
||||
def from_name(cls, name):
|
||||
cls.load_base_handlers()
|
||||
|
|
@ -214,7 +253,24 @@ class DataHandler(object):
|
|||
raise NameError("No data handlers named '%s'" % (name,))
|
||||
return all_handlers[name]
|
||||
|
||||
##@brief Return the module name to import in order to use the datahandler
|
||||
# @brief List all datahandlers
|
||||
# @return a dict with, display_name for keys, and a dict for value
|
||||
@classmethod
|
||||
def list_data_handlers(cls):
|
||||
cls.load_base_handlers()
|
||||
all_handlers = dict(cls._base_handlers, **cls.__custom_handlers)
|
||||
list_dh = dict()
|
||||
for hdl in all_handlers:
|
||||
list_dh[hdl.display_name] = {'help_text' : hdl.help_text,
|
||||
'nullable' : hdl.nullable, \
|
||||
'internal' : hdl.internal,
|
||||
'immutable' : hdl.immutable, \
|
||||
'primary_key' : hdl.primary_key, \
|
||||
'options' : self.options_spec}
|
||||
|
||||
return list_dh
|
||||
|
||||
## @brief Return the module name to import in order to use the datahandler
|
||||
# @param data_handler_name str : Data handler name
|
||||
# @return a str
|
||||
@classmethod
|
||||
|
|
@ -222,62 +278,64 @@ class DataHandler(object):
|
|||
name = name.lower()
|
||||
handler_class = cls.from_name(name)
|
||||
return '{module_name}.{class_name}'.format(
|
||||
module_name=handler_class.__module__,
|
||||
class_name=handler_class.__name__
|
||||
module_name=handler_class.__module__,
|
||||
class_name=handler_class.__name__
|
||||
)
|
||||
|
||||
##@brief __hash__ implementation for fieldtypes
|
||||
## @brief __hash__ implementation for fieldtypes
|
||||
def __hash__(self):
|
||||
hash_dats = [self.__class__.__module__]
|
||||
for kdic in sorted([k for k in self.__dict__.keys() if not k.startswith('_')]):
|
||||
hash_dats.append((kdic, getattr(self, kdic)))
|
||||
return hash(tuple(hash_dats))
|
||||
|
||||
##@brief Base class for datas data handler (by opposition with references)
|
||||
#@ingroup lodel2_datahandlers
|
||||
## @brief Base class for datas data handler (by opposition with references)
|
||||
# @ingroup lodel2_datahandlers
|
||||
class DataField(DataHandler):
|
||||
pass
|
||||
|
||||
##@brief Abstract class for all references
|
||||
#@ingroup lodel2_datahandlers
|
||||
|
||||
## @brief Abstract class for all references
|
||||
# @ingroup lodel2_datahandlers
|
||||
#
|
||||
# References are fields that stores a reference to another
|
||||
# editorial object
|
||||
#@todo Construct data implementation : transform the data into a LeObject
|
||||
#instance
|
||||
|
||||
# @todo Construct data implementation : transform the data into a LeObject instance
|
||||
class Reference(DataHandler):
|
||||
base_type = "ref"
|
||||
|
||||
##@brief Instanciation
|
||||
## @brief Instanciation
|
||||
# @param allowed_classes list | None : list of allowed em classes if None no restriction
|
||||
# @param back_reference tuple | None : tuple containing (LeObject child class, fieldname)
|
||||
# @param internal bool : if False, the field is not internal
|
||||
# @param **kwargs : other arguments
|
||||
def __init__(self, allowed_classes=None, back_reference=None, internal=False, **kwargs):
|
||||
self.__allowed_classes = set() if allowed_classes is None else set(allowed_classes)
|
||||
self.allowed_classes = list() if allowed_classes is None else allowed_classes # For now usefull to jinja 2
|
||||
# For now usefull to jinja 2
|
||||
self.allowed_classes = list() if allowed_classes is None else allowed_classes
|
||||
if back_reference is not None:
|
||||
if len(back_reference) != 2:
|
||||
raise ValueError("A tuple (classname, fieldname) expected but got '%s'" % back_reference)
|
||||
#if not issubclass(lodel.leapi.leobject.LeObject, back_reference[0]) or not isinstance(back_reference[1], str):
|
||||
# raise TypeError("Back reference was expected to be a tuple(<class LeObject>, str) but got : (%s, %s)" % (back_reference[0], back_reference[1]))
|
||||
# if not issubclass(lodel.leapi.leobject.LeObject, back_reference[0])
|
||||
# or not isinstance(back_reference[1], str):
|
||||
# raise TypeError("Back reference was expected to be a tuple(<class LeObject>, str)
|
||||
# but got : (%s, %s)" % (back_reference[0], back_reference[1]))
|
||||
self.__back_reference = back_reference
|
||||
super().__init__(internal=internal, **kwargs)
|
||||
|
||||
##@brief Method designed to return an empty value for this kind of
|
||||
#multipleref
|
||||
## @brief Method designed to return an empty value for this kind of
|
||||
# multipleref
|
||||
@classmethod
|
||||
def empty(cls):
|
||||
return None
|
||||
|
||||
##@brief Property that takes value of a copy of the back_reference tuple
|
||||
## @brief Property that takes value of a copy of the back_reference tuple
|
||||
@property
|
||||
def back_reference(self):
|
||||
return copy.copy(self.__back_reference)
|
||||
|
||||
##@brief Property that takes value of datahandler of the backreference or
|
||||
#None
|
||||
## @brief Property that takes value of datahandler of the backreference or
|
||||
# None
|
||||
@property
|
||||
def back_ref_datahandler(self):
|
||||
if self.__back_reference is None:
|
||||
|
|
@ -288,15 +346,15 @@ class Reference(DataHandler):
|
|||
def linked_classes(self):
|
||||
return copy.copy(self.__allowed_classes)
|
||||
|
||||
##@brief Set the back reference for this field.
|
||||
## @brief Set the back reference for this field.
|
||||
def _set_back_reference(self, back_reference):
|
||||
self.__back_reference = back_reference
|
||||
|
||||
##@brief Check and cast value in appropriate type
|
||||
#@param value *
|
||||
#@throw FieldValidationError if value is an appropriate type
|
||||
#@return value
|
||||
#@todo implement the check when we have LeObject uid check value
|
||||
## @brief Check and cast value in appropriate type
|
||||
# @param value *
|
||||
# @throw FieldValidationError if value is an appropriate type
|
||||
# @return value
|
||||
# @todo implement the check when we have LeObject uid check value
|
||||
def _check_data_value(self, value):
|
||||
from lodel.leapi.leobject import LeObject
|
||||
value = super()._check_data_value(value)
|
||||
|
|
@ -311,13 +369,13 @@ class Reference(DataHandler):
|
|||
raise FieldValidationError("Reference datahandler can not check this value %s if any allowed_class is allowed." % value)
|
||||
return value
|
||||
|
||||
##@brief Check datas consistency
|
||||
#@param emcomponent EmComponent : An EmComponent child class instance
|
||||
#@param fname : the field name
|
||||
#@param datas dict : dict storing fields values
|
||||
#@return an Exception instance if fails else True
|
||||
#@todo check for performance issue and check logics
|
||||
#@warning composed uid capabilities broken here
|
||||
## @brief Check datas consistency
|
||||
# @param emcomponent EmComponent : An EmComponent child class instance
|
||||
# @param fname : the field name
|
||||
# @param datas dict : dict storing fields values
|
||||
# @return an Exception instance if fails else True
|
||||
# @todo check for performance issue and check logics
|
||||
# @warning composed uid capabilities broken here
|
||||
def check_data_consistency(self, emcomponent, fname, datas):
|
||||
rep = super().check_data_consistency(emcomponent, fname, datas)
|
||||
if isinstance(rep, Exception):
|
||||
|
|
@ -333,21 +391,21 @@ class Reference(DataHandler):
|
|||
if not target_class.is_exist(value):
|
||||
logger.warning('Object referenced does not exist')
|
||||
return False
|
||||
#target_uidfield = target_class.uid_fieldname()[0] #multi uid broken here
|
||||
#obj = target_class.get([(target_uidfield, '=', value)])
|
||||
#if len(obj) == 0:
|
||||
# target_uidfield = target_class.uid_fieldname()[0] #multi uid broken here
|
||||
# obj = target_class.get([(target_uidfield, '=', value)])
|
||||
# if len(obj) == 0:
|
||||
# logger.warning('Object referenced does not exist')
|
||||
# return False
|
||||
return True
|
||||
|
||||
##@brief Utility method designed to fetch referenced objects
|
||||
#@param value mixed : the field value
|
||||
#@throw NotImplementedError
|
||||
|
||||
## @brief Utility method designed to fetch referenced objects
|
||||
# @param value mixed : the field value
|
||||
# @throw NotImplementedError
|
||||
def get_referenced(self, value):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
##@brief This class represent a data_handler for single reference to another object
|
||||
## @brief This class represent a data_handler for single reference to another object
|
||||
#
|
||||
# The fields using this data handlers are like "foreign key" on another object
|
||||
class SingleRef(Reference):
|
||||
|
|
@ -356,18 +414,18 @@ class SingleRef(Reference):
|
|||
super().__init__(allowed_classes=allowed_classes, **kwargs)
|
||||
|
||||
|
||||
##@brief Check and cast value in appropriate type
|
||||
#@param value: *
|
||||
#@throw FieldValidationError if value is unappropriate or can not be cast
|
||||
#@return value
|
||||
## @brief Check and cast value in appropriate type
|
||||
# @param value: *
|
||||
# @throw FieldValidationError if value is unappropriate or can not be cast
|
||||
# @return value
|
||||
def _check_data_value(self, value):
|
||||
value = super()._check_data_value(value)
|
||||
return value
|
||||
|
||||
##@brief Utility method designed to fetch referenced objects
|
||||
#@param value mixed : the field value
|
||||
#@return A LeObject child class instance
|
||||
#@throw LodelDataHandlerConsistencyException if no referenced object found
|
||||
## @brief Utility method designed to fetch referenced objects
|
||||
# @param value mixed : the field value
|
||||
# @return A LeObject child class instance
|
||||
# @throw LodelDataHandlerConsistencyException if no referenced object found
|
||||
def get_referenced(self, value):
|
||||
for leo_cls in self.linked_classes:
|
||||
res = leo_cls.get_from_uid(value)
|
||||
|
|
@ -377,30 +435,30 @@ class SingleRef(Reference):
|
|||
referenced object with uid %s" % value)
|
||||
|
||||
|
||||
##@brief This class represent a data_handler for multiple references to another object
|
||||
#@ingroup lodel2_datahandlers
|
||||
## @brief This class represent a data_handler for multiple references to another object
|
||||
# @ingroup lodel2_datahandlers
|
||||
#
|
||||
# The fields using this data handlers are like SingleRef but can store multiple references in one field
|
||||
# @note for the moment split on ',' chars
|
||||
class MultipleRef(Reference):
|
||||
|
||||
##
|
||||
## @brief Constructor
|
||||
# @param max_item int | None : indicate the maximum number of item referenced by this field, None mean no limit
|
||||
def __init__(self, max_item=None, **kwargs):
|
||||
self.max_item = max_item
|
||||
super().__init__(**kwargs)
|
||||
|
||||
##@brief Method designed to return an empty value for this kind of
|
||||
#multipleref
|
||||
## @brief Method designed to return an empty value for this kind of
|
||||
# multipleref
|
||||
@classmethod
|
||||
def empty(cls):
|
||||
return []
|
||||
|
||||
##@brief Check and cast value in appropriate type
|
||||
#@param value *
|
||||
#@throw FieldValidationError if value is unappropriate or can not be cast
|
||||
#@return value
|
||||
#@TODO Writing test error for errors when stored multiple references in one field
|
||||
## @brief Check and cast value in appropriate type
|
||||
# @param value *
|
||||
# @throw FieldValidationError if value is unappropriate or can not be cast
|
||||
# @return value
|
||||
# @TODO Writing test error for errors when stored multiple references in one field
|
||||
def _check_data_value(self, value):
|
||||
value = DataHandler._check_data_value(self, value)
|
||||
if not hasattr(value, '__iter__'):
|
||||
|
|
@ -408,7 +466,7 @@ class MultipleRef(Reference):
|
|||
if self.max_item is not None:
|
||||
if self.max_item < len(value):
|
||||
raise FieldValidationError("Too many items")
|
||||
new_val = list()
|
||||
new_val = list()
|
||||
error_list = list()
|
||||
for i, v in enumerate(value):
|
||||
try:
|
||||
|
|
@ -420,11 +478,11 @@ class MultipleRef(Reference):
|
|||
raise FieldValidationError("MultipleRef have for invalid values [%s] :" % (",".join(error_list)))
|
||||
return new_val
|
||||
|
||||
##@brief Utility method designed to fetch referenced objects
|
||||
#@param values mixed : the field values
|
||||
#@return A list of LeObject child class instance
|
||||
#@throw LodelDataHandlerConsistencyException if some referenced objects
|
||||
#were not found
|
||||
## @brief Utility method designed to fetch referenced objects
|
||||
# @param values mixed : the field values
|
||||
# @return A list of LeObject child class instance
|
||||
# @throw LodelDataHandlerConsistencyException if some referenced objects
|
||||
# were not found
|
||||
def get_referenced(self, values):
|
||||
if values is None or len(values) == 0:
|
||||
return list()
|
||||
|
|
@ -432,18 +490,19 @@ class MultipleRef(Reference):
|
|||
values = set(values)
|
||||
res = list()
|
||||
for leo_cls in self.linked_classes:
|
||||
uidname = leo_cls.uid_fieldname()[0] #MULTIPLE UID BROKEN HERE
|
||||
uidname = leo_cls.uid_fieldname()[0] # MULTIPLE UID BROKEN HERE
|
||||
tmp_res = leo_cls.get(('%s in (%s)' % (uidname, ','.join(
|
||||
[str(l) for l in left]))))
|
||||
left ^= set(( leo.uid() for leo in tmp_res))
|
||||
left ^= set((leo.uid() for leo in tmp_res))
|
||||
res += tmp_res
|
||||
if len(left) == 0:
|
||||
return res
|
||||
raise LodelDataHandlerConsistencyException("Unable to find \
|
||||
some referenced objects. Following uids were not found : %s" % ','.join(left))
|
||||
|
||||
|
||||
## @brief Class designed to handle datas access will fieldtypes are constructing datas
|
||||
#@ingroup lodel2_datahandlers
|
||||
# @ingroup lodel2_datahandlers
|
||||
#
|
||||
# This class is designed to allow automatic scheduling of construct_data calls.
|
||||
#
|
||||
|
|
@ -457,15 +516,15 @@ class DatasConstructor(object):
|
|||
# @param datas dict : dict with field name as key and field values as value
|
||||
# @param fields_handler dict : dict with field name as key and data handler instance as value
|
||||
def __init__(self, leobject, datas, fields_handler):
|
||||
## Stores concerned class
|
||||
# Stores concerned class
|
||||
self._leobject = leobject
|
||||
## Stores datas and constructed datas
|
||||
# Stores datas and constructed datas
|
||||
self._datas = copy.copy(datas)
|
||||
## Stores fieldtypes
|
||||
# Stores fieldtypes
|
||||
self._fields_handler = fields_handler
|
||||
## Stores list of fieldname for constructed datas
|
||||
# Stores list of fieldname for constructed datas
|
||||
self._constructed = []
|
||||
## Stores construct calls list
|
||||
# Stores construct calls list
|
||||
self._construct_calls = []
|
||||
|
||||
## @brief Implements the dict.keys() method on instance
|
||||
|
|
@ -488,3 +547,30 @@ class DatasConstructor(object):
|
|||
self._datas[fname] = value
|
||||
warnings.warn("Setting value of an DatasConstructor instance")
|
||||
|
||||
|
||||
## @brief Class designed to handle an option of a DataHandler
|
||||
class DatahandlerOption(MlNamedObject):
|
||||
|
||||
## @brief instanciates a new Datahandler option object
|
||||
#
|
||||
# @param id str
|
||||
# @param display_name MlString
|
||||
# @param help_text MlString
|
||||
# @param validator function
|
||||
def __init__(self, id, display_name, help_text, validator):
|
||||
self.__id = id
|
||||
self.__validator = validator
|
||||
super().__init__(display_name, help_text)
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self.__id
|
||||
|
||||
## @brief checks a value corresponding to this option is valid
|
||||
# @param value
|
||||
# @return casted value
|
||||
def check_value(self, value):
|
||||
try:
|
||||
return self.__validator(value)
|
||||
except ValidationError:
|
||||
raise ValueError()
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ from lodel.context import LodelContext
|
|||
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.leapi.datahandlers.datas_base': ['Boolean', 'Integer', 'Varchar',
|
||||
'DateTime', 'Text', 'File'],
|
||||
'DateTime', 'Text', 'File'],
|
||||
'lodel.exceptions': ['LodelException', 'LodelExceptions',
|
||||
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
|
||||
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
|
||||
|
||||
|
||||
##@brief Data field designed to handle formated strings
|
||||
|
|
@ -26,7 +26,7 @@ build its content'
|
|||
def __init__(self, format_string, field_list, **kwargs):
|
||||
self._field_list = field_list
|
||||
self._format_string = format_string
|
||||
super().__init__(internal='automatic',**kwargs)
|
||||
super().__init__(internal='automatic', **kwargs)
|
||||
|
||||
def _construct_data(self, emcomponent, fname, datas, cur_value):
|
||||
ret = self._format_string % tuple(
|
||||
|
|
@ -49,7 +49,7 @@ max_length and regex'
|
|||
# @param **kwargs
|
||||
def __init__(self, regex='', max_length=10, **kwargs):
|
||||
self.regex = regex
|
||||
self.compiled_re = re.compile(regex)#trigger an error if invalid regex
|
||||
self.compiled_re = re.compile(regex) # trigger an error if invalid regex
|
||||
super(self.__class__, self).__init__(max_length=max_length, **kwargs)
|
||||
|
||||
##@brief Check and cast value in appropriate type
|
||||
|
|
@ -106,7 +106,8 @@ be internal")
|
|||
if not inspect.isclass(emcomponent):
|
||||
cls = emcomponent.__class__
|
||||
return cls.__name__
|
||||
|
||||
|
||||
|
||||
##@brief Data field designed to handle concatenated fields
|
||||
class Concat(FormatString):
|
||||
help = 'Automatic strings concatenation'
|
||||
|
|
@ -116,11 +117,11 @@ class Concat(FormatString):
|
|||
# @param field_list list : List of field to use
|
||||
# @param separator str
|
||||
# @param **kwargs
|
||||
def __init__(self, field_list, separator = ' ', **kwargs):
|
||||
def __init__(self, field_list, separator=' ', **kwargs):
|
||||
format_string = separator.join(['%s' for _ in field_list])
|
||||
super().__init__(
|
||||
format_string = format_string, field_list = field_list, **kwargs)
|
||||
|
||||
super().__init__(format_string=format_string,
|
||||
field_list=field_list,
|
||||
**kwargs)
|
||||
|
||||
|
||||
class Password(Varchar):
|
||||
|
|
@ -129,7 +130,6 @@ class Password(Varchar):
|
|||
pass
|
||||
|
||||
|
||||
|
||||
class VarcharList(Varchar):
|
||||
help = 'DataHandler designed to make a list out of a string.'
|
||||
base_type = 'varchar'
|
||||
|
|
@ -140,7 +140,6 @@ class VarcharList(Varchar):
|
|||
self.delimiter = str(delimiter)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
def construct_data(self, emcomponent, fname, datas, cur_value):
|
||||
result = cur_value.split(self.delimiter)
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -8,32 +8,33 @@ from lodel.context import LodelContext
|
|||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.leapi.datahandlers.base_classes': ['DataField'],
|
||||
'lodel.exceptions': ['LodelException', 'LodelExceptions',
|
||||
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
|
||||
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
|
||||
|
||||
|
||||
##@brief Data field designed to handle boolean values
|
||||
## @brief Data field designed to handle boolean values
|
||||
class Boolean(DataField):
|
||||
|
||||
help = 'A basic boolean field'
|
||||
base_type = 'bool'
|
||||
|
||||
##@brief A boolean field
|
||||
## @brief A boolean field
|
||||
def __init__(self, **kwargs):
|
||||
#if 'check_data_value' not in kwargs:
|
||||
# kwargs['check_data_value'] = self._check_data_value
|
||||
super().__init__(ftype='bool', **kwargs)
|
||||
|
||||
##@brief Check and cast value in appropriate type
|
||||
#@param value *
|
||||
#@throw FieldValidationError if value is unappropriate or can not be cast
|
||||
#@return value
|
||||
## @brief Check and cast value in appropriate type
|
||||
# @param value *
|
||||
# @throw FieldValidationError if value is unappropriate or can not be cast
|
||||
# @return value
|
||||
def _check_data_value(self, value):
|
||||
value = super()._check_data_value(value)
|
||||
if not isinstance(value, bool):
|
||||
raise FieldValidationError("The value '%s' is not, and will never, be a boolean" % value)
|
||||
return value
|
||||
|
||||
##@brief Data field designed to handle integer values
|
||||
|
||||
## @brief Data field designed to handle integer values
|
||||
class Integer(DataField):
|
||||
|
||||
help = 'Basic integer field'
|
||||
|
|
@ -41,14 +42,14 @@ class Integer(DataField):
|
|||
cast_type = int
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__( **kwargs)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
##@brief Check and cast value in appropriate type
|
||||
## @brief Check and cast value in appropriate type
|
||||
# @param value *
|
||||
# @param strict bool : tells if the value must be an integer or a value that can be converted into an integer
|
||||
# @throw FieldValidationError if value is unappropriate or can not be cast
|
||||
# @return value
|
||||
def _check_data_value(self, value, strict = False):
|
||||
def _check_data_value(self, value, strict=False):
|
||||
value = super()._check_data_value(value)
|
||||
if (strict and not isinstance(value, int)):
|
||||
raise FieldValidationError("The value '%s' is not a python type integer" % value)
|
||||
|
|
@ -61,19 +62,20 @@ class Integer(DataField):
|
|||
raise FieldValidationError("The value '%s' is not, and will never, be an integer" % value)
|
||||
return value
|
||||
|
||||
##@brief Data field designed to handle string
|
||||
|
||||
## @brief Data field designed to handle string
|
||||
class Varchar(DataField):
|
||||
|
||||
help = 'Basic string (varchar) field. Default size is 64 characters'
|
||||
base_type = 'char'
|
||||
|
||||
##@brief A string field
|
||||
## @brief A string field
|
||||
# @brief max_length int: The maximum length of this field
|
||||
def __init__(self, max_length=64, **kwargs):
|
||||
self.max_length = int(max_length)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
##@brief checks if this class can override the given data handler
|
||||
## @brief checks if this class can override the given data handler
|
||||
# @param data_handler DataHandler
|
||||
# @return bool
|
||||
def can_override(self, data_handler):
|
||||
|
|
@ -83,25 +85,26 @@ class Varchar(DataField):
|
|||
return False
|
||||
return True
|
||||
|
||||
##@brief Check and cast value in appropriate type
|
||||
#@param value *
|
||||
#@throw FieldValidationError if value is unappropriate or can not be cast
|
||||
#@return value
|
||||
## @brief Check and cast value in appropriate type
|
||||
# @param value *
|
||||
# @throw FieldValidationError if value is unappropriate or can not be cast
|
||||
# @return value
|
||||
def _check_data_value(self, value):
|
||||
value = super()._check_data_value(value)
|
||||
if not isinstance(value, str):
|
||||
raise FieldValidationError("The value '%s' can't be a str" % value)
|
||||
if len(value) > self.max_length:
|
||||
raise FieldValidationError("The value '%s' is longer than the maximum length of this field (%s)" % (value, self.max_length))
|
||||
raise FieldValidationError("The value '%s' is longer than the maximum length of this field (%s)" % (value, self.max_length))
|
||||
return value
|
||||
|
||||
##@brief Data field designed to handle date & time
|
||||
|
||||
## @brief Data field designed to handle date & time
|
||||
class DateTime(DataField):
|
||||
|
||||
help = 'A datetime field. Take two boolean options now_on_update and now_on_create'
|
||||
base_type = 'datetime'
|
||||
|
||||
##@brief A datetime field
|
||||
## @brief A datetime field
|
||||
# @param now_on_update bool : If true, the date is set to NOW on update
|
||||
# @param now_on_create bool : If true, the date is set to NEW on creation
|
||||
# @param **kwargs
|
||||
|
|
@ -111,13 +114,13 @@ class DateTime(DataField):
|
|||
self.datetime_format = '%Y-%m-%d' if 'format' not in kwargs else kwargs['format']
|
||||
super().__init__(**kwargs)
|
||||
|
||||
##@brief Check and cast value in appropriate type
|
||||
#@param value *
|
||||
#@throw FieldValidationError if value is unappropriate or can not be cast
|
||||
#@return value
|
||||
## @brief Check and cast value in appropriate type
|
||||
# @param value *
|
||||
# @throw FieldValidationError if value is unappropriate or can not be cast
|
||||
# @return value
|
||||
def _check_data_value(self, value):
|
||||
value = super()._check_data_value(value)
|
||||
if isinstance(value,str):
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
value = datetime.datetime.fromtimestamp(time.mktime(time.strptime(value, self.datetime_format)))
|
||||
except ValueError:
|
||||
|
|
@ -131,7 +134,8 @@ class DateTime(DataField):
|
|||
return datetime.datetime.now()
|
||||
return cur_value
|
||||
|
||||
##@brief Data field designed to handle long string
|
||||
|
||||
## @brief Data field designed to handle long string
|
||||
class Text(DataField):
|
||||
help = 'A text field (big string)'
|
||||
base_type = 'text'
|
||||
|
|
@ -139,22 +143,23 @@ class Text(DataField):
|
|||
def __init__(self, **kwargs):
|
||||
super(self.__class__, self).__init__(ftype='text', **kwargs)
|
||||
|
||||
##@brief Check and cast value in appropriate type
|
||||
#@param value *
|
||||
#@throw FieldValidationError if value is unappropriate or can not be cast
|
||||
#@return value
|
||||
## @brief Check and cast value in appropriate type
|
||||
# @param value *
|
||||
# @throw FieldValidationError if value is unappropriate or can not be cast
|
||||
# @return value
|
||||
def _check_data_value(self, value):
|
||||
value = super()._check_data_value(value)
|
||||
if not isinstance(value, str):
|
||||
raise FieldValidationError("The content passed to this Text field is not a convertible to a string")
|
||||
return value
|
||||
|
||||
##@brief Data field designed to handle Files
|
||||
|
||||
## @brief Data field designed to handle Files
|
||||
class File(DataField):
|
||||
|
||||
base_type = 'file'
|
||||
|
||||
##@brief a file field
|
||||
## @brief a file field
|
||||
# @param upload_path str : None by default
|
||||
# @param **kwargs
|
||||
def __init__(self, upload_path=None, **kwargs):
|
||||
|
|
@ -164,4 +169,3 @@ class File(DataField):
|
|||
# @todo Add here a check for the validity of the given value (should have a correct path syntax)
|
||||
def _check_data_value(self, value):
|
||||
return super()._check_data_value(value)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
def LodelDataHandlerException(Exception):
|
||||
class LodelDataHandlerException(Exception):
|
||||
pass
|
||||
|
||||
def LodelDataHandlerConsistencyException(LodelDataHandlerException):
|
||||
|
||||
class LodelDataHandlerConsistencyException(LodelDataHandlerException):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -3,35 +3,37 @@
|
|||
from lodel.context import LodelContext
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.leapi.datahandlers.base_classes': ['Reference', 'MultipleRef',
|
||||
'SingleRef'],
|
||||
'SingleRef'],
|
||||
'lodel.logger': 'logger',
|
||||
'lodel.exceptions': ['LodelException', 'LodelExceptions',
|
||||
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
|
||||
'LodelFatalError', 'DataNoneValid',
|
||||
'FieldValidationError']})
|
||||
|
||||
|
||||
class Link(SingleRef):
|
||||
pass
|
||||
|
||||
|
||||
##@brief Child class of MultipleRef where references are represented in the form of a python list
|
||||
## @brief Child class of MultipleRef where references are represented in the form of a python list
|
||||
class List(MultipleRef):
|
||||
|
||||
##@brief instanciates a list reference
|
||||
## @brief instanciates a list reference
|
||||
# @param max_length int
|
||||
# @param kwargs
|
||||
# - allowed_classes list | None : list of allowed em classes if None no restriction
|
||||
# - internal bool
|
||||
|
||||
def __init__(self, max_length = None, **kwargs):
|
||||
def __init__(self, max_length=None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@classmethod
|
||||
def empty(cls):
|
||||
return list()
|
||||
|
||||
##@brief Check and cast value in appropriate type
|
||||
#@param value *
|
||||
#@throw FieldValidationError if value is unappropriate or can not be cast
|
||||
#@return value
|
||||
## @brief Check and cast value in appropriate type
|
||||
# @param value *
|
||||
# @throw FieldValidationError if value is unappropriate or can not be cast
|
||||
# @return value
|
||||
def _check_data_value(self, value):
|
||||
value = super()._check_data_value(value)
|
||||
try:
|
||||
|
|
@ -39,12 +41,12 @@ class List(MultipleRef):
|
|||
except Exception as e:
|
||||
raise FieldValidationError("Given iterable is not castable in \
|
||||
a list : %s" % e)
|
||||
return value
|
||||
|
||||
##@brief Child class of MultipleRef where references are represented in the form of a python set
|
||||
|
||||
## @brief Child class of MultipleRef where references are represented in the form of a python set
|
||||
class Set(MultipleRef):
|
||||
|
||||
##@brief instanciates a set reference
|
||||
## @brief instanciates a set reference
|
||||
# @param kwargs : named arguments
|
||||
# - allowed_classes list | None : list of allowed em classes if None no restriction
|
||||
# - internal bool : if False, the field is not internal
|
||||
|
|
@ -55,10 +57,10 @@ class Set(MultipleRef):
|
|||
def empty(cls):
|
||||
return set()
|
||||
|
||||
##@brief Check and cast value in appropriate type
|
||||
#@param value *
|
||||
#@throw FieldValidationError if value is unappropriate or can not be cast
|
||||
#@return value
|
||||
## @brief Check and cast value in appropriate type
|
||||
# @param value *
|
||||
# @throw FieldValidationError if value is unappropriate or can not be cast
|
||||
# @return value
|
||||
def _check_data_value(self, value):
|
||||
value = super()._check_data_value(value)
|
||||
try:
|
||||
|
|
@ -66,11 +68,12 @@ class Set(MultipleRef):
|
|||
except Exception as e:
|
||||
raise FieldValidationError("Given iterable is not castable in \
|
||||
a set : %s" % e)
|
||||
|
||||
##@brief Child class of MultipleRef where references are represented in the form of a python dict
|
||||
|
||||
|
||||
## @brief Child class of MultipleRef where references are represented in the form of a python dict
|
||||
class Map(MultipleRef):
|
||||
|
||||
##@brief instanciates a dict reference
|
||||
## @brief instanciates a dict reference
|
||||
# @param kwargs : named arguments
|
||||
# - allowed_classes list | None : list of allowed em classes if None no restriction
|
||||
# - internal bool : if False, the field is not internal
|
||||
|
|
@ -81,38 +84,40 @@ class Map(MultipleRef):
|
|||
def empty(cls):
|
||||
return dict()
|
||||
|
||||
##@brief Check and cast value in appropriate type
|
||||
#@param value *
|
||||
#@throw FieldValidationError if value is unappropriate or can not be cast
|
||||
#@return value
|
||||
## @brief Check and cast value in appropriate type
|
||||
# @param value *
|
||||
# @throw FieldValidationError if value is unappropriate or can not be cast
|
||||
# @return value
|
||||
def _check_data_value(self, value):
|
||||
value = super()._check_data_value(value)
|
||||
if not isinstance(value, dict):
|
||||
raise FieldValidationError("Values for dict fields should be dict")
|
||||
return value
|
||||
|
||||
##@brief This Reference class is designed to handler hierarchy with some constraint
|
||||
|
||||
## @brief This Reference class is designed to handler hierarchy with some constraint
|
||||
class Hierarch(MultipleRef):
|
||||
|
||||
directly_editable = False
|
||||
##@brief Instanciate a data handler handling hierarchical relation with constraints
|
||||
|
||||
## @brief Instanciate a data handler handling hierarchical relation with constraints
|
||||
# @param back_reference tuple : Here it is mandatory to have a back ref (like a parent field)
|
||||
# @param max_depth int | None : limit of depth
|
||||
# @param max_childs int | Nine : maximum number of childs by nodes
|
||||
def __init__(self, back_reference, max_depth = None, max_childs = None, **kwargs):
|
||||
super().__init__( back_reference = back_reference,
|
||||
max_depth = max_depth,
|
||||
max_childs = max_childs,
|
||||
**kwargs)
|
||||
def __init__(self, back_reference, max_depth=None, max_childs=None, **kwargs):
|
||||
super().__init__(back_reference=back_reference,
|
||||
max_depth=max_depth,
|
||||
max_childs=max_childs,
|
||||
**kwargs)
|
||||
|
||||
@classmethod
|
||||
def empty(cls):
|
||||
return tuple()
|
||||
|
||||
##@brief Check and cast value in appropriate type
|
||||
#@param value *
|
||||
#@throw FieldValidationError if value is unappropriate or can not be cast
|
||||
#@return value
|
||||
## @brief Check and cast value in appropriate type
|
||||
# @param value *
|
||||
# @throw FieldValidationError if value is unappropriate or can not be cast
|
||||
# @return value
|
||||
def _check_data_value(self, value):
|
||||
value = super()._check_data_value(value)
|
||||
if not (isinstance(value, list) or isinstance(value, str)):
|
||||
|
|
|
|||
|
|
@ -11,77 +11,79 @@ LodelContext.expose_modules(globals(), {
|
|||
'lodel.settings': 'Settings',
|
||||
'lodel.settings.utils': 'SettingsError',
|
||||
'lodel.leapi.query': ['LeInsertQuery', 'LeUpdateQuery', 'LeDeleteQuery',
|
||||
'LeGetQuery'],
|
||||
'LeGetQuery'],
|
||||
'lodel.leapi.exceptions': ['LeApiError', 'LeApiErrors',
|
||||
'LeApiDataCheckError', 'LeApiDataCheckErrors', 'LeApiQueryError',
|
||||
'LeApiQueryErrors'],
|
||||
'LeApiDataCheckError', 'LeApiDataCheckErrors', 'LeApiQueryError',
|
||||
'LeApiQueryErrors'],
|
||||
'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
|
||||
'LodelScriptError', 'DatasourcePluginError'],
|
||||
'LodelScriptError', 'DatasourcePluginError'],
|
||||
'lodel.exceptions': ['LodelFatalError'],
|
||||
'lodel.plugin.hooks': ['LodelHook'],
|
||||
'lodel.plugin': ['Plugin', 'DatasourcePlugin'],
|
||||
'lodel.leapi.datahandlers.base_classes': ['DatasConstructor', 'Reference']})
|
||||
|
||||
##@brief Stores the name of the field present in each LeObject that indicates
|
||||
#the name of LeObject subclass represented by this object
|
||||
# @brief Stores the name of the field present in each LeObject that indicates
|
||||
# the name of LeObject subclass represented by this object
|
||||
CLASS_ID_FIELDNAME = "classname"
|
||||
|
||||
##@brief Wrapper class for LeObject getter & setter
|
||||
# @brief Wrapper class for LeObject getter & setter
|
||||
#
|
||||
# This class intend to provide easy & friendly access to LeObject fields values
|
||||
# This class intend to provide easy & friendly access to LeObject fields values
|
||||
# without name collision problems
|
||||
# @note Wrapped methods are : LeObject.data() & LeObject.set_data()
|
||||
|
||||
|
||||
class LeObjectValues(object):
|
||||
|
||||
##@brief Construct a new LeObjectValues
|
||||
|
||||
# @brief Construct a new LeObjectValues
|
||||
# @param fieldnames_callback method
|
||||
# @param set_callback method : The LeObject.set_datas() method of corresponding LeObject class
|
||||
# @param get_callback method : The LeObject.get_datas() method of corresponding LeObject class
|
||||
def __init__(self, fieldnames_callback, set_callback, get_callback):
|
||||
self._setter = set_callback
|
||||
self._getter = get_callback
|
||||
|
||||
##@brief Provide read access to datas values
|
||||
|
||||
# @brief Provide read access to datas values
|
||||
# @note Read access should be provided for all fields
|
||||
# @param fname str : Field name
|
||||
def __getattribute__(self, fname):
|
||||
getter = super().__getattribute__('_getter')
|
||||
return getter(fname)
|
||||
|
||||
##@brief Provide write access to datas values
|
||||
|
||||
# @brief Provide write access to datas values
|
||||
# @note Write acces shouldn't be provided for internal or immutable fields
|
||||
# @param fname str : Field name
|
||||
# @param fval * : the field value
|
||||
def __setattribute__(self, fname, fval):
|
||||
setter = super().__getattribute__('_setter')
|
||||
return setter(fname, fval)
|
||||
|
||||
|
||||
|
||||
class LeObject(object):
|
||||
|
||||
##@brief boolean that tells if an object is abtract or not
|
||||
|
||||
# @brief boolean that tells if an object is abtract or not
|
||||
_abstract = None
|
||||
##@brief A dict that stores DataHandler instances indexed by field name
|
||||
# @brief A dict that stores DataHandler instances indexed by field name
|
||||
_fields = None
|
||||
##@brief A tuple of fieldname (or a uniq fieldname) representing uid
|
||||
_uid = None
|
||||
##@brief Read only datasource ( see @ref lodel2_datasources )
|
||||
# @brief A tuple of fieldname (or a uniq fieldname) representing uid
|
||||
_uid = None
|
||||
# @brief Read only datasource ( see @ref lodel2_datasources )
|
||||
_ro_datasource = None
|
||||
##@brief Read & write datasource ( see @ref lodel2_datasources )
|
||||
# @brief Read & write datasource ( see @ref lodel2_datasources )
|
||||
_rw_datasource = None
|
||||
##@brief Store the list of child classes
|
||||
# @brief Store the list of child classes
|
||||
_child_classes = None
|
||||
##@brief Name of the datasource plugin
|
||||
# @brief Name of the datasource plugin
|
||||
_datasource_name = None
|
||||
|
||||
def __new__(cls, **kwargs):
|
||||
|
||||
|
||||
self = object.__new__(cls)
|
||||
##@brief A dict that stores fieldvalues indexed by fieldname
|
||||
self.__datas = { fname:None for fname in self._fields }
|
||||
##@brief Store a list of initianilized fields when instanciation not complete else store True
|
||||
# @brief A dict that stores fieldvalues indexed by fieldname
|
||||
self.__datas = {fname: None for fname in self._fields}
|
||||
# @brief Store a list of initianilized fields when instanciation not complete else store True
|
||||
self.__initialized = list()
|
||||
##@brief Datas accessor. Instance of @ref LeObjectValues
|
||||
# @brief Datas accessor. Instance of @ref LeObjectValues
|
||||
self.d = LeObjectValues(self.fieldnames, self.set_data, self.data)
|
||||
for fieldname, fieldval in kwargs.items():
|
||||
self.__datas[fieldname] = fieldval
|
||||
|
|
@ -90,11 +92,12 @@ class LeObject(object):
|
|||
self.__set_initialized()
|
||||
return self
|
||||
|
||||
##@brief Construct an object representing an Editorial component
|
||||
# @brief Construct an object representing an Editorial component
|
||||
# @note Can be considered as EmClass instance
|
||||
def __init__(self, **kwargs):
|
||||
if self._abstract:
|
||||
raise NotImplementedError("%s is abstract, you cannot instanciate it." % self.__class__.__name__ )
|
||||
raise NotImplementedError(
|
||||
"%s is abstract, you cannot instanciate it." % self.__class__.__name__)
|
||||
|
||||
# Checks that uid is given
|
||||
for uid_name in self._uid:
|
||||
|
|
@ -105,7 +108,7 @@ class LeObject(object):
|
|||
self.__initialized.append(uid_name)
|
||||
|
||||
# Processing given fields
|
||||
allowed_fieldnames = self.fieldnames(include_ro = False)
|
||||
allowed_fieldnames = self.fieldnames(include_ro=False)
|
||||
err_list = dict()
|
||||
for fieldname, fieldval in kwargs.items():
|
||||
if fieldname not in allowed_fieldnames:
|
||||
|
|
@ -119,39 +122,39 @@ class LeObject(object):
|
|||
self.__datas[fieldname] = fieldval
|
||||
self.__initialized.append(fieldname)
|
||||
if len(err_list) > 0:
|
||||
raise LeApiErrors(msg = "Unable to __init__ %s" % self.__class__,
|
||||
exceptions = err_list)
|
||||
raise LeApiErrors(msg="Unable to __init__ %s" % self.__class__,
|
||||
exceptions=err_list)
|
||||
self.__set_initialized()
|
||||
|
||||
|
||||
#-----------------------------------#
|
||||
# Fields datas handling methods #
|
||||
#-----------------------------------#
|
||||
|
||||
##@brief Property method True if LeObject is initialized else False
|
||||
# @brief Property method True if LeObject is initialized else False
|
||||
@property
|
||||
def initialized(self):
|
||||
return self.__is_initialized
|
||||
|
||||
##@return The uid field name
|
||||
|
||||
# @return The uid field name
|
||||
@classmethod
|
||||
def uid_fieldname(cls):
|
||||
return cls._uid
|
||||
|
||||
##@brief Return a list of fieldnames
|
||||
# @brief Return a list of fieldnames
|
||||
# @param include_ro bool : if True include read only field names
|
||||
# @return a list of str
|
||||
@classmethod
|
||||
def fieldnames(cls, include_ro = False):
|
||||
def fieldnames(cls, include_ro=False):
|
||||
if not include_ro:
|
||||
return [ fname for fname in cls._fields if not cls._fields[fname].is_internal() ]
|
||||
return [fname for fname in cls._fields if not cls._fields[fname].is_internal()]
|
||||
else:
|
||||
return list(cls._fields.keys())
|
||||
|
||||
|
||||
@classmethod
|
||||
def name2objname(cls, name):
|
||||
return name.title()
|
||||
|
||||
##@brief Return the datahandler asssociated with a LeObject field
|
||||
|
||||
# @brief Return the datahandler asssociated with a LeObject field
|
||||
# @param fieldname str : The fieldname
|
||||
# @return A data handler instance
|
||||
#@todo update class of exception raised
|
||||
|
|
@ -160,18 +163,18 @@ class LeObject(object):
|
|||
if not fieldname in cls._fields:
|
||||
raise NameError("No field named '%s' in %s" % (fieldname, cls.__name__))
|
||||
return cls._fields[fieldname]
|
||||
|
||||
##@brief Getter for references datahandlers
|
||||
|
||||
# @brief Getter for references datahandlers
|
||||
#@param with_backref bool : if true return only references with back_references
|
||||
#@return <code>{'fieldname': datahandler, ...}</code>
|
||||
@classmethod
|
||||
def reference_handlers(cls, with_backref = True):
|
||||
return { fname: fdh
|
||||
for fname, fdh in cls.fields(True).items()
|
||||
if fdh.is_reference() and \
|
||||
(not with_backref or fdh.back_reference is not None)}
|
||||
|
||||
##@brief Return a LeObject child class from a name
|
||||
def reference_handlers(cls, with_backref=True):
|
||||
return {fname: fdh
|
||||
for fname, fdh in cls.fields(True).items()
|
||||
if fdh.is_reference() and
|
||||
(not with_backref or fdh.back_reference is not None)}
|
||||
|
||||
# @brief Return a LeObject child class from a name
|
||||
# @warning This method has to be called from dynamically generated LeObjects
|
||||
# @param leobject_name str : LeObject name
|
||||
# @return A LeObject child class
|
||||
|
|
@ -183,14 +186,14 @@ class LeObject(object):
|
|||
mod = importlib.import_module(cls.__module__)
|
||||
try:
|
||||
return getattr(mod, leobject_name)
|
||||
except (AttributeError, TypeError) :
|
||||
except (AttributeError, TypeError):
|
||||
raise LeApiError("No LeObject named '%s'" % leobject_name)
|
||||
|
||||
|
||||
@classmethod
|
||||
def is_abstract(cls):
|
||||
return cls._abstract
|
||||
|
||||
##@brief Field data handler getter
|
||||
|
||||
# @brief Field data handler getter
|
||||
#@param fieldname str : The field name
|
||||
#@return A datahandler instance
|
||||
#@throw NameError if the field doesn't exist
|
||||
|
|
@ -199,20 +202,22 @@ class LeObject(object):
|
|||
try:
|
||||
return cls._fields[fieldname]
|
||||
except KeyError:
|
||||
raise NameError("No field named '%s' in %s" % ( fieldname,
|
||||
cls.__name__))
|
||||
##@return A dict with fieldname as key and datahandler as instance
|
||||
raise NameError("No field named '%s' in %s" % (fieldname,
|
||||
cls.__name__))
|
||||
# @return A dict with fieldname as key and datahandler as instance
|
||||
|
||||
@classmethod
|
||||
def fields(cls, include_ro = False):
|
||||
def fields(cls, include_ro=False):
|
||||
if include_ro:
|
||||
return copy.copy(cls._fields)
|
||||
else:
|
||||
return {fname:cls._fields[fname] for fname in cls._fields if not cls._fields[fname].is_internal()}
|
||||
|
||||
##@brief Return the list of parents classes
|
||||
return {fname: cls._fields[fname] for fname in cls._fields\
|
||||
if not cls._fields[fname].is_internal()}
|
||||
|
||||
# @brief Return the list of parents classes
|
||||
#
|
||||
#@note the first item of the list is the current class, the second is it's
|
||||
#parent etc...
|
||||
# parent etc...
|
||||
#@param cls
|
||||
#@warning multiple inheritance broken by this method
|
||||
#@return a list of LeObject child classes
|
||||
|
|
@ -222,23 +227,22 @@ class LeObject(object):
|
|||
res = [cls]
|
||||
cur = cls
|
||||
while True:
|
||||
cur = cur.__bases__[0] # Multiple inheritance broken HERE
|
||||
cur = cur.__bases__[0] # Multiple inheritance broken HERE
|
||||
if cur in (LeObject, object):
|
||||
break
|
||||
else:
|
||||
res.append(cur)
|
||||
return res
|
||||
|
||||
##@brief Return a tuple a child classes
|
||||
|
||||
# @brief Return a tuple a child classes
|
||||
#@return a tuple of child classes
|
||||
@classmethod
|
||||
def child_classes(cls):
|
||||
return copy.copy(cls._child_classes)
|
||||
|
||||
|
||||
##@brief Return the parent class that is the "source" of uid
|
||||
# @brief Return the parent class that is the "source" of uid
|
||||
#
|
||||
#The method goal is to return the parent class that defines UID.
|
||||
# The method goal is to return the parent class that defines UID.
|
||||
#@return a LeObject child class or false if no UID defined
|
||||
@classmethod
|
||||
def uid_source(cls):
|
||||
|
|
@ -246,19 +250,19 @@ class LeObject(object):
|
|||
return False
|
||||
hierarch = cls.hierarch()
|
||||
prev = hierarch[0]
|
||||
uid_handlers = set( cls._fields[name] for name in cls._uid )
|
||||
uid_handlers = set(cls._fields[name] for name in cls._uid)
|
||||
for pcls in cls.hierarch()[1:]:
|
||||
puid_handlers = set(cls._fields[name] for name in pcls._uid)
|
||||
if set(pcls._uid) != set(prev._uid) \
|
||||
or puid_handlers != uid_handlers:
|
||||
or puid_handlers != uid_handlers:
|
||||
break
|
||||
prev = pcls
|
||||
return prev
|
||||
|
||||
##@brief Initialise both datasources (ro and rw)
|
||||
|
||||
# @brief Initialise both datasources (ro and rw)
|
||||
#
|
||||
#This method is used once at dyncode load to replace the datasource string
|
||||
#by a datasource instance to avoid doing this operation for each query
|
||||
# This method is used once at dyncode load to replace the datasource string
|
||||
# by a datasource instance to avoid doing this operation for each query
|
||||
#@see LeObject::_init_datasource()
|
||||
@classmethod
|
||||
def _init_datasources(cls):
|
||||
|
|
@ -266,7 +270,7 @@ class LeObject(object):
|
|||
rw_ds = ro_ds = cls._datasource_name
|
||||
else:
|
||||
ro_ds, rw_ds = cls._datasource_name
|
||||
#Read only datasource initialisation
|
||||
# Read only datasource initialisation
|
||||
cls._ro_datasource = DatasourcePlugin.init_datasource(ro_ds, True)
|
||||
if cls._ro_datasource is None:
|
||||
log_msg = "No read only datasource set for LeObject %s"
|
||||
|
|
@ -276,7 +280,7 @@ class LeObject(object):
|
|||
log_msg = "Read only datasource '%s' initialized for LeObject %s"
|
||||
log_msg %= (ro_ds, cls.__name__)
|
||||
logger.debug(log_msg)
|
||||
#Read write datasource initialisation
|
||||
# Read write datasource initialisation
|
||||
cls._rw_datasource = DatasourcePlugin.init_datasource(rw_ds, False)
|
||||
if cls._ro_datasource is None:
|
||||
log_msg = "No read/write datasource set for LeObject %s"
|
||||
|
|
@ -286,14 +290,14 @@ class LeObject(object):
|
|||
log_msg = "Read/write datasource '%s' initialized for LeObject %s"
|
||||
log_msg %= (ro_ds, cls.__name__)
|
||||
logger.debug(log_msg)
|
||||
|
||||
##@brief Return the uid of the current LeObject instance
|
||||
|
||||
# @brief Return the uid of the current LeObject instance
|
||||
#@return the uid value
|
||||
#@warning Broke multiple uid capabilities
|
||||
def uid(self):
|
||||
return self.data(self._uid[0])
|
||||
|
||||
##@brief Read only access to all datas
|
||||
# @brief Read only access to all datas
|
||||
# @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
|
||||
# @param field_name str : field name
|
||||
# @return the Value
|
||||
|
|
@ -303,16 +307,16 @@ class LeObject(object):
|
|||
if field_name not in self._fields.keys():
|
||||
raise NameError("No such field in %s : %s" % (self.__class__.__name__, field_name))
|
||||
if not self.initialized and field_name not in self.__initialized:
|
||||
raise RuntimeError("The field %s is not initialized yet (and have no value)" % field_name)
|
||||
raise RuntimeError(
|
||||
"The field %s is not initialized yet (and have no value)" % field_name)
|
||||
return self.__datas[field_name]
|
||||
|
||||
##@brief Read only access to all datas
|
||||
|
||||
# @brief Read only access to all datas
|
||||
#@return a dict representing datas of current instance
|
||||
def datas(self, internal = False):
|
||||
return {fname:self.data(fname) for fname in self.fieldnames(internal)}
|
||||
|
||||
|
||||
##@brief Datas setter
|
||||
def datas(self, internal=False):
|
||||
return {fname: self.data(fname) for fname in self.fieldnames(internal)}
|
||||
|
||||
# @brief Datas setter
|
||||
# @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
|
||||
# @param fname str : field name
|
||||
# @param fval * : field value
|
||||
|
|
@ -320,7 +324,7 @@ class LeObject(object):
|
|||
# @throw NameError if fname is not valid
|
||||
# @throw AttributeError if the field is not writtable
|
||||
def set_data(self, fname, fval):
|
||||
if fname not in self.fieldnames(include_ro = False):
|
||||
if fname not in self.fieldnames(include_ro=False):
|
||||
if fname not in self._fields.keys():
|
||||
raise NameError("No such field in %s : %s" % (self.__class__.__name__, fname))
|
||||
else:
|
||||
|
|
@ -342,23 +346,23 @@ class LeObject(object):
|
|||
# We skip full validation here because the LeObject is not fully initialized yet
|
||||
val, err = self._fields[fname].check_data_value(fval)
|
||||
if isinstance(err, Exception):
|
||||
#Revert change to be in valid state
|
||||
# Revert change to be in valid state
|
||||
del(self.__datas[fname])
|
||||
del(self.__initialized[-1])
|
||||
raise LeApiErrors("Data check error", {fname:err})
|
||||
raise LeApiErrors("Data check error", {fname: err})
|
||||
else:
|
||||
self.__datas[fname] = val
|
||||
|
||||
##@brief Update the __initialized attribute according to LeObject internal state
|
||||
|
||||
# @brief Update the __initialized attribute according to LeObject internal state
|
||||
#
|
||||
# Check the list of initialized fields and set __initialized to True if all fields initialized
|
||||
def __set_initialized(self):
|
||||
if isinstance(self.__initialized, list):
|
||||
expected_fields = self.fieldnames(include_ro = False) + self._uid
|
||||
expected_fields = self.fieldnames(include_ro=False) + self._uid
|
||||
if set(expected_fields) == set(self.__initialized):
|
||||
self.__is_initialized = True
|
||||
|
||||
##@brief Designed to be called when datas are modified
|
||||
# @brief Designed to be called when datas are modified
|
||||
#
|
||||
# Make different checks on the LeObject given it's state (fully initialized or not)
|
||||
# @return None if checks succeded else return an exception list
|
||||
|
|
@ -366,7 +370,7 @@ class LeObject(object):
|
|||
err_list = dict()
|
||||
if self.__initialized is True:
|
||||
# Data value check
|
||||
for fname in self.fieldnames(include_ro = False):
|
||||
for fname in self.fieldnames(include_ro=False):
|
||||
val, err = self._fields[fname].check_data_value(self.__datas[fname])
|
||||
if err is not None:
|
||||
err_list[fname] = err
|
||||
|
|
@ -374,19 +378,19 @@ class LeObject(object):
|
|||
self.__datas[fname] = val
|
||||
# Data construction
|
||||
if len(err_list) == 0:
|
||||
for fname in self.fieldnames(include_ro = True):
|
||||
for fname in self.fieldnames(include_ro=True):
|
||||
try:
|
||||
field = self._fields[fname]
|
||||
self.__datas[fname] = field.construct_data( self,
|
||||
fname,
|
||||
self.__datas,
|
||||
self.__datas[fname]
|
||||
)
|
||||
self.__datas[fname] = field.construct_data(self,
|
||||
fname,
|
||||
self.__datas,
|
||||
self.__datas[fname]
|
||||
)
|
||||
except Exception as exp:
|
||||
err_list[fname] = exp
|
||||
# Datas consistency check
|
||||
if len(err_list) == 0:
|
||||
for fname in self.fieldnames(include_ro = True):
|
||||
for fname in self.fieldnames(include_ro=True):
|
||||
field = self._fields[fname]
|
||||
ret = field.check_data_consistency(self, fname, self.__datas)
|
||||
if isinstance(ret, Exception):
|
||||
|
|
@ -404,8 +408,8 @@ class LeObject(object):
|
|||
#--------------------#
|
||||
# Other methods #
|
||||
#--------------------#
|
||||
|
||||
##@brief Temporary method to set private fields attribute at dynamic code generation
|
||||
|
||||
# @brief Temporary method to set private fields attribute at dynamic code generation
|
||||
#
|
||||
# This method is used in the generated dynamic code to set the _fields attribute
|
||||
# at the end of the dyncode parse
|
||||
|
|
@ -415,8 +419,8 @@ class LeObject(object):
|
|||
@classmethod
|
||||
def _set__fields(cls, field_list):
|
||||
cls._fields = field_list
|
||||
|
||||
## @brief Check that datas are valid for this type
|
||||
|
||||
# @brief Check that datas are valid for this type
|
||||
# @param datas dict : key == field name value are field values
|
||||
# @param complete bool : if True expect that datas provide values for all non internal fields
|
||||
# @param allow_internal bool : if True don't raise an error if a field is internal
|
||||
|
|
@ -424,10 +428,10 @@ class LeObject(object):
|
|||
# @return Checked datas
|
||||
# @throw LeApiDataCheckError if errors reported during check
|
||||
@classmethod
|
||||
def check_datas_value(cls, datas, complete = False, allow_internal = True):
|
||||
err_l = dict() #Error storing
|
||||
correct = set() #valid fields name
|
||||
mandatory = set() #mandatory fields name
|
||||
def check_datas_value(cls, datas, complete=False, allow_internal=True):
|
||||
err_l = dict() # Error storing
|
||||
correct = set() # valid fields name
|
||||
mandatory = set() # mandatory fields name
|
||||
for fname, datahandler in cls._fields.items():
|
||||
if allow_internal or not datahandler.is_internal():
|
||||
correct.add(fname)
|
||||
|
|
@ -436,15 +440,15 @@ class LeObject(object):
|
|||
provided = set(datas.keys())
|
||||
# searching for unknow fields
|
||||
for u_f in provided - correct:
|
||||
#Here we can check if the field is invalid or rejected because
|
||||
# Here we can check if the field is invalid or rejected because
|
||||
# it is internel
|
||||
err_l[u_f] = AttributeError("Unknown or unauthorized field '%s'" % u_f)
|
||||
# searching for missing mandatory fieldsa
|
||||
for missing in mandatory - provided:
|
||||
err_l[missing] = AttributeError("The data for field '%s' is missing" % missing)
|
||||
#Checks datas
|
||||
# Checks datas
|
||||
checked_datas = dict()
|
||||
for name, value in [ (name, value) for name, value in datas.items() if name in correct ]:
|
||||
for name, value in [(name, value) for name, value in datas.items() if name in correct]:
|
||||
dh = cls._fields[name]
|
||||
res = dh.check_data_value(value)
|
||||
checked_datas[name], err = res
|
||||
|
|
@ -455,10 +459,10 @@ class LeObject(object):
|
|||
raise LeApiDataCheckErrors("Error while checking datas", err_l)
|
||||
return checked_datas
|
||||
|
||||
##@brief Check and prepare datas
|
||||
#
|
||||
# @brief Check and prepare datas
|
||||
#
|
||||
# @warning when complete = False we are not able to make construct_datas() and _check_data_consistency()
|
||||
#
|
||||
#
|
||||
# @param datas dict : {fieldname : fieldvalue, ...}
|
||||
# @param complete bool : If True you MUST give all the 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)
|
||||
return ret_datas
|
||||
|
||||
## @brief Construct datas values
|
||||
# @brief Construct datas values
|
||||
#
|
||||
# @param cls
|
||||
# @param datas dict : Datas that have been returned by LeCrud.check_datas_value() methods
|
||||
|
|
@ -488,13 +492,13 @@ construction and consitency when datas are not complete\n")
|
|||
def _construct_datas(cls, datas):
|
||||
constructor = DatasConstructor(cls, datas, cls._fields)
|
||||
ret = {
|
||||
fname:constructor[fname]
|
||||
for fname, ftype in cls._fields.items()
|
||||
if not ftype.is_internal() or ftype.internal != 'autosql'
|
||||
fname: constructor[fname]
|
||||
for fname, ftype in cls._fields.items()
|
||||
if not ftype.is_internal() or ftype.internal != 'autosql'
|
||||
}
|
||||
return ret
|
||||
|
||||
## @brief Check datas consistency
|
||||
|
||||
# @brief Check datas consistency
|
||||
#
|
||||
# @warning assert that datas is complete
|
||||
# @param cls
|
||||
|
|
@ -511,29 +515,29 @@ construction and consitency when datas are not complete\n")
|
|||
|
||||
if len(err_l) > 0:
|
||||
raise LeApiDataCheckError("Datas consistency checks fails", err_l)
|
||||
|
||||
## @brief Check datas consistency
|
||||
|
||||
# @brief Check datas consistency
|
||||
#
|
||||
# @warning assert that datas is complete
|
||||
# @param cls
|
||||
# @param datas dict : Datas that have been returned by LeCrud.prepare_datas() method
|
||||
# @param type_query str : Type of query to be performed , default value : insert
|
||||
@classmethod
|
||||
def make_consistency(cls, datas, type_query = 'insert'):
|
||||
def make_consistency(cls, datas, type_query='insert'):
|
||||
for fname, dh in cls._fields.items():
|
||||
ret = dh.make_consistency(fname, datas, type_query)
|
||||
|
||||
## @brief Add a new instance of LeObject
|
||||
|
||||
# @brief Add a new instance of LeObject
|
||||
# @return a new uid en case of success, False otherwise
|
||||
@classmethod
|
||||
def insert(cls, datas):
|
||||
query = LeInsertQuery(cls)
|
||||
return query.execute(datas)
|
||||
|
||||
## @brief Update an instance of LeObject
|
||||
# @brief Update an instance of LeObject
|
||||
#
|
||||
#@param datas : list of new datas
|
||||
def update(self, datas = None):
|
||||
#@param datas : list of new datas
|
||||
def update(self, datas=None):
|
||||
datas = self.datas(internal=False) if datas is None else datas
|
||||
uids = self._uid
|
||||
query_filter = list()
|
||||
|
|
@ -543,15 +547,15 @@ construction and consitency when datas are not complete\n")
|
|||
query = LeUpdateQuery(self.__class__, query_filter)
|
||||
except Exception as err:
|
||||
raise err
|
||||
|
||||
|
||||
try:
|
||||
result = query.execute(datas)
|
||||
except Exception as err:
|
||||
raise err
|
||||
|
||||
return result
|
||||
|
||||
## @brief Delete an instance of LeObject
|
||||
|
||||
# @brief Delete an instance of LeObject
|
||||
#
|
||||
#@return 1 if the objet has been deleted
|
||||
def delete(self):
|
||||
|
|
@ -565,8 +569,8 @@ construction and consitency when datas are not complete\n")
|
|||
result = query.execute()
|
||||
|
||||
return result
|
||||
|
||||
## @brief Delete instances of LeObject
|
||||
|
||||
# @brief Delete instances of LeObject
|
||||
#@param query_filters list
|
||||
#@returns the number of deleted items
|
||||
@classmethod
|
||||
|
|
@ -576,7 +580,7 @@ construction and consitency when datas are not complete\n")
|
|||
query = LeDeleteQuery(cls, query_filters)
|
||||
except Exception as err:
|
||||
raise err
|
||||
|
||||
|
||||
try:
|
||||
result = query.execute()
|
||||
except Exception as err:
|
||||
|
|
@ -584,11 +588,11 @@ construction and consitency when datas are not complete\n")
|
|||
if not result is None:
|
||||
deleted += result
|
||||
return deleted
|
||||
|
||||
## @brief Get instances of LeObject
|
||||
|
||||
# @brief Get instances of LeObject
|
||||
#
|
||||
#@param query_filters dict : (filters, relational filters), with filters is a list of tuples : (FIELD, OPERATOR, VALUE) )
|
||||
#@param field_list list|None : list of string representing fields see
|
||||
#@param field_list list|None : list of string representing fields see
|
||||
#@ref leobject_filters
|
||||
#@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])
|
||||
|
|
@ -598,49 +602,49 @@ construction and consitency when datas are not complete\n")
|
|||
@classmethod
|
||||
def get(cls, query_filters, field_list=None, order=None, group=None, limit=None, offset=0):
|
||||
if field_list is not None:
|
||||
for uid in [ uidname
|
||||
for uidname in cls.uid_fieldname()
|
||||
if uidname not in field_list ]:
|
||||
for uid in [uidname
|
||||
for uidname in cls.uid_fieldname()
|
||||
if uidname not in field_list]:
|
||||
field_list.append(uid)
|
||||
if CLASS_ID_FIELDNAME not in field_list:
|
||||
field_list.append(CLASS_ID_FIELDNAME)
|
||||
try:
|
||||
query = LeGetQuery(
|
||||
cls, query_filters = query_filters, field_list = field_list,
|
||||
order = order, group = group, limit = limit, offset = offset)
|
||||
cls, query_filters=query_filters, field_list=field_list,
|
||||
order=order, group=group, limit=limit, offset=offset)
|
||||
except ValueError as err:
|
||||
raise err
|
||||
|
||||
|
||||
try:
|
||||
result = query.execute()
|
||||
except Exception as err:
|
||||
raise err
|
||||
|
||||
|
||||
objects = list()
|
||||
for res in result:
|
||||
res_cls = cls.name2class(res[CLASS_ID_FIELDNAME])
|
||||
inst = res_cls.__new__(res_cls,**res)
|
||||
inst = res_cls.__new__(res_cls, **res)
|
||||
objects.append(inst)
|
||||
|
||||
|
||||
return objects
|
||||
|
||||
##@brief Retrieve an object given an UID
|
||||
|
||||
# @brief Retrieve an object given an UID
|
||||
#@todo broken multiple UID
|
||||
@classmethod
|
||||
def get_from_uid(cls, uid):
|
||||
if cls.uid_fieldname() is None:
|
||||
raise LodelFatalError(
|
||||
"No uid defined for class %s" % cls.__name__)
|
||||
uidname = cls.uid_fieldname()[0] #Brokes composed UID
|
||||
res = cls.get([(uidname,'=', uid)])
|
||||
|
||||
#dedoublonnage vu que query ou la datasource est bugué
|
||||
uidname = cls.uid_fieldname()[0] # Brokes composed UID
|
||||
res = cls.get([(uidname, '=', uid)])
|
||||
|
||||
# dedoublonnage vu que query ou la datasource est bugué
|
||||
if len(res) > 1:
|
||||
res_cp = res
|
||||
res = []
|
||||
while len(res_cp) > 0:
|
||||
cur_res = res_cp.pop()
|
||||
if cur_res.uid() in [ r.uid() for r in res_cp]:
|
||||
if cur_res.uid() in [r.uid() for r in res_cp]:
|
||||
logger.error("DOUBLON detected in query results !!!")
|
||||
else:
|
||||
res.append(cur_res)
|
||||
|
|
@ -651,7 +655,7 @@ object ! For class %s with uid value = %s" % (cls, uid))
|
|||
return None
|
||||
return res[0]
|
||||
|
||||
##@brief Checks if an object exists
|
||||
# @brief Checks if an object exists
|
||||
@classmethod
|
||||
def is_exist(cls, uid):
|
||||
if cls.uid_fieldname() is None:
|
||||
|
|
@ -659,4 +663,3 @@ object ! For class %s with uid value = %s" % (cls, uid))
|
|||
"No uid defined for class %s" % cls.__name__)
|
||||
from .query import is_exist
|
||||
return is_exist(cls, uid)
|
||||
|
||||
|
|
|
|||
2
lodel/mlnamedobject/Makefile.am
Normal file
2
lodel/mlnamedobject/Makefile.am
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
mlnamedobject_PYTHON= *.py
|
||||
mlnamedobjectdir=$(pkgpythondir)/mlnamedobject
|
||||
0
lodel/mlnamedobject/__init__.py
Normal file
0
lodel/mlnamedobject/__init__.py
Normal file
18
lodel/mlnamedobject/mlnamedobject.py
Normal file
18
lodel/mlnamedobject/mlnamedobject.py
Normal 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)
|
||||
|
|
@ -3,7 +3,7 @@ LodelContext.expose_modules(globals(), {
|
|||
'lodel.plugin.plugins': ['Plugin'],
|
||||
'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
|
||||
'LodelScriptError', 'DatasourcePluginError'],
|
||||
'lodel.settings.validator': ['SettingValidator'],
|
||||
'lodel.validator.validator': ['Validator'],
|
||||
'lodel.exceptions': ['LodelException', 'LodelExceptions',
|
||||
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
|
||||
|
||||
|
|
@ -97,7 +97,7 @@ class DatasourcePlugin(Plugin):
|
|||
'section': 'lodel2',
|
||||
'key': 'datasource_connectors',
|
||||
'default': 'dummy_datasource',
|
||||
'validator': SettingValidator(
|
||||
'validator': Validator(
|
||||
'custom_list', none_is_valid = False,
|
||||
validator_name = 'plugin', validator_kwargs = {
|
||||
'ptype': _glob_typename,
|
||||
|
|
@ -280,13 +280,13 @@ but %s is a %s" % (ds_name, pinstance.__class__.__name__))
|
|||
#CONFSPEC = {
|
||||
# 'lodel2.datasource.mysql.*' : {
|
||||
# 'host': ( 'localhost',
|
||||
# SettingValidator('host')),
|
||||
# Validator('host')),
|
||||
# 'db_name': ( 'lodel',
|
||||
# SettingValidator('string')),
|
||||
# Validator('string')),
|
||||
# 'username': ( None,
|
||||
# SettingValidator('string')),
|
||||
# Validator('string')),
|
||||
# 'password': ( None,
|
||||
# SettingValidator('string')),
|
||||
# Validator('string')),
|
||||
# }
|
||||
#}
|
||||
#</pre>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ LodelContext.expose_modules(globals(), {
|
|||
'lodel.plugin.plugins': ['Plugin'],
|
||||
'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
|
||||
'LodelScriptError', 'DatasourcePluginError'],
|
||||
'lodel.settings.validator': ['SettingValidator']})
|
||||
'lodel.validator.validator': ['Validator']})
|
||||
|
||||
_glob_typename = 'extension'
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ class Extension(Plugin):
|
|||
'section': 'lodel2',
|
||||
'key': 'extensions',
|
||||
'default': None,
|
||||
'validator': SettingValidator(
|
||||
'validator': Validator(
|
||||
'custom_list', none_is_valid = True,
|
||||
validator_name = 'plugin', validator_kwargs = {
|
||||
'ptype': _glob_typename,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ LodelContext.expose_modules(globals(), {
|
|||
'lodel.plugin.plugins': ['Plugin'],
|
||||
'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
|
||||
'LodelScriptError', 'DatasourcePluginError'],
|
||||
'lodel.settings.validator': ['SettingValidator']})
|
||||
'lodel.validator.validator': ['Validator']})
|
||||
|
||||
_glob_typename = 'ui'
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ class InterfacePlugin(Plugin):
|
|||
'section': 'lodel2',
|
||||
'key': 'interface',
|
||||
'default': None,
|
||||
'validator': SettingValidator(
|
||||
'validator': Validator(
|
||||
'plugin', none_is_valid = True, ptype = _glob_typename)}
|
||||
|
||||
_type_conf_name = _glob_typename
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ LodelContext.expose_modules(globals(), {
|
|||
'lodel.plugin.plugins': ['Plugin', 'MetaPlugType'],
|
||||
'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
|
||||
'LodelScriptError', 'DatasourcePluginError'],
|
||||
'lodel.settings.validator': ['SettingValidator']})
|
||||
'lodel.validator.validator': ['Validator']})
|
||||
|
||||
|
||||
##@brief SessionHandlerPlugin metaclass designed to implements a wrapper
|
||||
|
|
@ -53,7 +53,7 @@ class SessionHandlerPlugin(Plugin, metaclass=SessionPluginWrapper):
|
|||
'section': 'lodel2',
|
||||
'key': 'session_handler',
|
||||
'default': None,
|
||||
'validator': SettingValidator(
|
||||
'validator': Validator(
|
||||
'plugin', none_is_valid=False,ptype = _glob_typename)}
|
||||
|
||||
_type_conf_name = _glob_typename
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from lodel.context import LodelContext
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings.validator': ['SettingValidator']})
|
||||
'lodel.validator.validator': ['Validator']})
|
||||
|
||||
__plugin_name__ = "dummy"
|
||||
__version__ = '0.0.1' #or __version__ = [0,0,1]
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
from lodel.context import LodelContext
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings.validator': ['SettingValidator']})
|
||||
'lodel.validator.validator': ['Validator']})
|
||||
|
||||
CONFSPEC = {
|
||||
'lodel2.section1': {
|
||||
'key1': ( None,
|
||||
SettingValidator('dummy'))
|
||||
Validator('dummy'))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from lodel.context import LodelContext
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings.validator': ['SettingValidator']})
|
||||
'lodel.validator.validator': ['Validator']})
|
||||
from .datasource import DummyDatasource as Datasource
|
||||
|
||||
__plugin_type__ = 'datasource'
|
||||
|
|
@ -12,7 +12,7 @@ __plugin_deps__ = []
|
|||
CONFSPEC = {
|
||||
'lodel2.datasource.dummy_datasource.*' : {
|
||||
'dummy': ( None,
|
||||
SettingValidator('dummy'))}
|
||||
Validator('dummy'))}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from lodel.context import LodelContext
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings.validator': ['SettingValidator']})
|
||||
'lodel.validator.validator': ['Validator']})
|
||||
|
||||
__plugin_name__ = 'filesystem_session'
|
||||
__version__ = [0,0,1]
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
from lodel.context import LodelContext
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings.validator': ['SettingValidator']})
|
||||
'lodel.validator.validator': ['Validator']})
|
||||
|
||||
CONFSPEC = {
|
||||
'lodel2.sessions':{
|
||||
'directory': ('/tmp/', SettingValidator('path')),
|
||||
'expiration': (900, SettingValidator('int')),
|
||||
'file_template': ('lodel2_%s.sess', SettingValidator('dummy'))
|
||||
'directory': ('/tmp/', Validator('path')),
|
||||
'expiration': (900, Validator('int')),
|
||||
'file_template': ('lodel2_%s.sess', Validator('dummy'))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
from lodel.context import LodelContext
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings.validator': ['SettingValidator']})
|
||||
'lodel.validator.validator': ['Validator']})
|
||||
|
||||
##@brief Mongodb datasource plugin confspec
|
||||
#@ingroup plugin_mongodb_datasource
|
||||
|
|
@ -10,11 +10,11 @@ LodelContext.expose_modules(globals(), {
|
|||
#Describe mongodb plugin configuration. Keys are :
|
||||
CONFSPEC = {
|
||||
'lodel2.datasource.mongodb_datasource.*':{
|
||||
'read_only': (False, SettingValidator('bool')),
|
||||
'host': ('localhost', SettingValidator('host')),
|
||||
'port': (None, SettingValidator('string', none_is_valid = True)),
|
||||
'db_name':('lodel', SettingValidator('string')),
|
||||
'username': (None, SettingValidator('string')),
|
||||
'password': (None, SettingValidator('string'))
|
||||
'read_only': (False, Validator('bool')),
|
||||
'host': ('localhost', Validator('host')),
|
||||
'port': (None, Validator('string', none_is_valid = True)),
|
||||
'db_name':('lodel', Validator('string')),
|
||||
'username': (None, Validator('string')),
|
||||
'password': (None, Validator('string'))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from lodel.context import LodelContext, ContextError
|
||||
try:
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings.validator': ['SettingValidator']})
|
||||
'lodel.validator.validator': ['Validator']})
|
||||
|
||||
__plugin_name__ = "multisite"
|
||||
__version__ = '0.0.1' #or __version__ = [0,0,1]
|
||||
|
|
@ -13,8 +13,8 @@ try:
|
|||
|
||||
CONFSPEC = {
|
||||
'lodel2.server': {
|
||||
'port': (80,SettingValidator('int')),
|
||||
'listen_addr': ('', SettingValidator('string')),
|
||||
'port': (80,Validator('int')),
|
||||
'listen_addr': ('', Validator('string')),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,34 +1,34 @@
|
|||
from lodel.context import LodelContext
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings.validator': ['SettingValidator']})
|
||||
'lodel.validator.validator': ['Validator']})
|
||||
|
||||
#Define a minimal confspec used by multisite loader
|
||||
LODEL2_CONFSPECS = {
|
||||
'lodel2': {
|
||||
'debug': (True, SettingValidator('bool'))
|
||||
'debug': (True, Validator('bool'))
|
||||
},
|
||||
'lodel2.server': {
|
||||
'listen_address': ('127.0.0.1', SettingValidator('dummy')),
|
||||
#'listen_address': ('', SettingValidator('ip')), #<-- not implemented
|
||||
'listen_port': ( 1337, SettingValidator('int')),
|
||||
'uwsgi_workers': (8, SettingValidator('int')),
|
||||
'uwsgicmd': ('/usr/bin/uwsgi', SettingValidator('dummy')),
|
||||
'virtualenv': (None, SettingValidator('path', none_is_valid = True)),
|
||||
'listen_address': ('127.0.0.1', Validator('dummy')),
|
||||
#'listen_address': ('', Validator('ip')), #<-- not implemented
|
||||
'listen_port': ( 1337, Validator('int')),
|
||||
'uwsgi_workers': (8, Validator('int')),
|
||||
'uwsgicmd': ('/usr/bin/uwsgi', Validator('dummy')),
|
||||
'virtualenv': (None, Validator('path', none_is_valid = True)),
|
||||
},
|
||||
'lodel2.logging.*' : {
|
||||
'level': ( 'ERROR',
|
||||
SettingValidator('loglevel')),
|
||||
Validator('loglevel')),
|
||||
'context': ( False,
|
||||
SettingValidator('bool')),
|
||||
Validator('bool')),
|
||||
'filename': ( None,
|
||||
SettingValidator('errfile', none_is_valid = True)),
|
||||
Validator('errfile', none_is_valid = True)),
|
||||
'backupcount': ( 10,
|
||||
SettingValidator('int', none_is_valid = False)),
|
||||
Validator('int', none_is_valid = False)),
|
||||
'maxbytes': ( 1024*10,
|
||||
SettingValidator('int', none_is_valid = False)),
|
||||
Validator('int', none_is_valid = False)),
|
||||
},
|
||||
'lodel2.datasources.*': {
|
||||
'read_only': (False, SettingValidator('bool')),
|
||||
'identifier': ( None, SettingValidator('string')),
|
||||
'read_only': (False, Validator('bool')),
|
||||
'identifier': ( None, Validator('string')),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from lodel.context import LodelContext
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings.validator': ['SettingValidator']})
|
||||
'lodel.validator.validator': ['Validator']})
|
||||
|
||||
__plugin_name__ = 'ram_sessions'
|
||||
__version__ = [0,0,1]
|
||||
|
|
@ -11,7 +11,7 @@ __fullname__ = "RAM Session Store Plugin"
|
|||
|
||||
CONFSPEC = {
|
||||
'lodel2.sessions':{
|
||||
'expiration': (900, SettingValidator('int')),
|
||||
'tokensize': (512, SettingValidator('int')),
|
||||
'expiration': (900, Validator('int')),
|
||||
'tokensize': (512, Validator('int')),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
from lodel.context import LodelContext
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings.validator': ['SettingValidator']})
|
||||
'lodel.validator.validator': ['Validator']})
|
||||
|
||||
CONFSPEC = {
|
||||
'lodel2.webui': {
|
||||
'standalone': ( 'False',
|
||||
SettingValidator('string')),
|
||||
Validator('string')),
|
||||
'listen_address': ( '127.0.0.1',
|
||||
SettingValidator('dummy')),
|
||||
Validator('dummy')),
|
||||
'listen_port': ( '9090',
|
||||
SettingValidator('int')),
|
||||
Validator('int')),
|
||||
'static_url': ( 'http://127.0.0.1/static/',
|
||||
SettingValidator('regex', pattern = r'^https?://[^/].*$')),
|
||||
Validator('regex', pattern = r'^https?://[^/].*$')),
|
||||
'virtualenv': (None,
|
||||
SettingValidator('path', none_is_valid=True)),
|
||||
'uwsgicmd': ('/usr/bin/uwsgi', SettingValidator('dummy')),
|
||||
'cookie_secret_key': ('ConfigureYourOwnCookieSecretKey', SettingValidator('dummy')),
|
||||
'cookie_session_id': ('lodel', SettingValidator('dummy')),
|
||||
'uwsgi_workers': (2, SettingValidator('int'))
|
||||
Validator('path', none_is_valid=True)),
|
||||
'uwsgicmd': ('/usr/bin/uwsgi', Validator('dummy')),
|
||||
'cookie_secret_key': ('ConfigureYourOwnCookieSecretKey', Validator('dummy')),
|
||||
'cookie_session_id': ('lodel', Validator('dummy')),
|
||||
'uwsgi_workers': (2, Validator('int'))
|
||||
},
|
||||
'lodel2.webui.sessions': {
|
||||
'directory': ( '/tmp',
|
||||
SettingValidator('path')),
|
||||
Validator('path')),
|
||||
'expiration': ( 900,
|
||||
SettingValidator('int')),
|
||||
Validator('int')),
|
||||
'file_template': ( 'lodel2_%s.sess',
|
||||
SettingValidator('dummy')),
|
||||
Validator('dummy')),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
{% set objects = my_classes.Issue.get(('%s = %s') % (uidfield, lodel_id)) %}
|
||||
{% set person_class = leapi.name2class('Person') %}
|
||||
{% set obj = objects.pop() %}
|
||||
{% block content %}
|
||||
{% block content %}
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/{{ root_url }}/">Home</a></li>
|
||||
<li><a href="/{{ root_url }}/collection">Collections</a></li>
|
||||
|
|
@ -13,14 +13,14 @@
|
|||
<h1 class="h1_lodel">Issue {{ obj.data('title') }} </h1>
|
||||
<h2>{{ obj.data('subtitle') }}</h2>
|
||||
{% set directors=person_class.get(("%s in (%s)") % (person_class.uid_fieldname()[0], obj.data('linked_directors')|join(','))) %}
|
||||
<p><strong>Directors : </strong>{% for director in directors %} <a href="/{{ root_url }}/show_object?classname=Person&lodel_id={{ director.uid() }} " target="_blank" >{{ director.data('firstname')}} {{ director.data('lastname')}}</a> ; {% endfor %}</p>
|
||||
<p><strong>Directors : </strong>{% for director in directors %} <a href="/{{ root_url }}/show_object?classname=Person&lodel_id={{ director.uid() }} " >{{ director.data('firstname')}} {{ director.data('lastname')}}</a> ; {% endfor %}</p>
|
||||
{% set texts=my_classes.Text.get(("%s in (%s)") % (my_classes.Text.uid_fieldname()[0], obj.data('linked_texts')|join(','))) %}
|
||||
{% set parts=my_classes.Part.get(("%s in (%s)") % (my_classes.Part.uid_fieldname()[0], obj.data('linked_parts')|join(','))) %}
|
||||
{% if texts is not none: %}
|
||||
<ul>
|
||||
{% for text in texts %}
|
||||
<li>
|
||||
<h3><a href="/{{ root_url }}/show_object?classname={{ text.data('classname') }}&lodel_id={{ text.uid() }}" target="_blank" > {{ text.data('title') }}</a></h3>
|
||||
<h3><a href="/{{ root_url }}/show_object?classname={{ text.data('classname') }}&lodel_id={{ text.uid() }}" > {{ text.data('title') }}</a></h3>
|
||||
<h4>{{ text.data('subtitle') }}</h4>
|
||||
{% set authors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], text.data('linked_persons')|join(','))) %}
|
||||
<p>Authors : {% for author in authors %} {{ author.data('firstname')}} {{ author.data('lastname')}} ; {% endfor %} </p>
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
<ul>
|
||||
{% for part in parts %}
|
||||
<li>
|
||||
<h3><a href="/{{ root_url }}/show_object?classname={{ part.data('classname') }}&lodel_id={{ part.uid() }}" target="_blank"> {{ part.data('title') }}</a></h3>
|
||||
<h3><a href="/{{ root_url }}/show_object?classname={{ part.data('classname') }}&lodel_id={{ part.uid() }}"> {{ part.data('title') }}</a></h3>
|
||||
<h4>{{ part.data('subtitle') }}</h4>
|
||||
{% set directors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], part.data('linked_directors')|join(','))) %}
|
||||
<p>Directors : {% for director in directors %} {{ director.data('firstname')}} {{ director.data('lastname')}} ; {% endfor %} </p>
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
<ul style="margin-left:20px">
|
||||
{% for text in p_texts %}
|
||||
<li>
|
||||
<h3><a href="/{{ root_url }}/show_object?classname={{ text.data('classname') }}&lodel_id={{ text.uid() }}" target="_blank"> {{ text.data('title') }}</a></h3>
|
||||
<h3><a href="/{{ root_url }}/show_object?classname={{ text.data('classname') }}&lodel_id={{ text.uid() }}" > {{ text.data('title') }}</a></h3>
|
||||
<h4>{{ text.data('subtitle') }}</h4>
|
||||
{% set authors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], text.data('linked_persons')|join(','))) %}
|
||||
<p>Authors : {% for author in authors %} {{ author.data('firstname')}} {{ author.data('lastname')}} ; {% endfor %} </p>
|
||||
|
|
@ -57,7 +57,7 @@
|
|||
<ul>
|
||||
{% for part in ss_parts %}
|
||||
<li>
|
||||
<h3><a href="/{{ root_url }}/show_object?classname={{ part.data('classname') }}&lodel_id={{ part.uid() }}" target="_blank"> {{ part.data('title') }}</a></h3>
|
||||
<h3><a href="/{{ root_url }}/show_object?classname={{ part.data('classname') }}&lodel_id={{ part.uid() }}" > {{ part.data('title') }}</a></h3>
|
||||
<h4>{{ part.data('subtitle') }}</h4>
|
||||
{% set directors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], part.data('linked_directors')|join(','))) %}
|
||||
<p>Directors : {% for director in directors %} {{ director.data('firstname')}} {{ director.data('lastname')}} ; {% endfor %} </p>
|
||||
|
|
@ -66,7 +66,7 @@
|
|||
<ul style="margin-left:20px">
|
||||
{% for text in sp_texts %}
|
||||
<li>
|
||||
<h3><a href="/{{ root_url }}/show_object?classname={{ text.data('classname') }}&lodel_id={{ text.uid() }}" target="_blank"> {{ text.data('title') }}</a></h3>
|
||||
<h3><a href="/{{ root_url }}/show_object?classname={{ text.data('classname') }}&lodel_id={{ text.uid() }}" > {{ text.data('title') }}</a></h3>
|
||||
<h4>{{ text.data('subtitle') }}</h4>
|
||||
{% set authors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], text.data('linked_persons')|join(','))) %}
|
||||
<p>Authors : {% for author in authors %} {{ author.data('firstname')}} {{ author.data('lastname')}} ; {% endfor %} </p>
|
||||
|
|
@ -84,4 +84,4 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -5,40 +5,42 @@ import os
|
|||
import configparser
|
||||
import copy
|
||||
import warnings
|
||||
import types # for dynamic bindings
|
||||
import types # for dynamic bindings
|
||||
from collections import namedtuple
|
||||
|
||||
from lodel.context import LodelContext
|
||||
|
||||
LodelContext.expose_modules(globals(),{
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.logger': 'logger',
|
||||
'lodel.settings.utils': ['SettingsError', 'SettingsErrors'],
|
||||
'lodel.settings.validator': ['SettingValidator', 'LODEL2_CONF_SPECS',
|
||||
'confspec_append'],
|
||||
'lodel.settings.settings_loader':['SettingsLoader']})
|
||||
|
||||
'lodel.validator.validator': ['Validator', 'LODEL2_CONF_SPECS',
|
||||
'confspec_append'],
|
||||
'lodel.settings.settings_loader': ['SettingsLoader']})
|
||||
|
||||
## @package lodel.settings.settings Lodel2 settings module
|
||||
|
||||
# @package lodel.settings.settings Lodel2 settings module
|
||||
#
|
||||
# Contains the class that handles the namedtuple tree of settings
|
||||
|
||||
##@brief A default python system lib path
|
||||
# @brief A default python system lib path
|
||||
PYTHON_SYS_LIB_PATH = '/usr/local/lib/python{major}.{minor}/'.format(
|
||||
|
||||
major = sys.version_info.major,
|
||||
minor = sys.version_info.minor)
|
||||
major=sys.version_info.major,
|
||||
minor=sys.version_info.minor)
|
||||
|
||||
|
||||
class MetaSettings(type):
|
||||
|
||||
@property
|
||||
def s(self):
|
||||
self.singleton_assert(True)
|
||||
return self.instance.settings
|
||||
|
||||
##@brief Handles configuration load etc.
|
||||
# @brief Handles configuration load etc.
|
||||
#
|
||||
# To see howto bootstrap Settings and use it in lodel instance see
|
||||
# To see howto bootstrap Settings and use it in lodel instance see
|
||||
# @ref lodel.settings
|
||||
#
|
||||
#
|
||||
# @par Basic instance usage
|
||||
# For example if a file defines confs like :
|
||||
# <pre>
|
||||
|
|
@ -50,15 +52,15 @@ class MetaSettings(type):
|
|||
#
|
||||
# @par Init sequence
|
||||
# The initialization sequence is a bit tricky. In fact, plugins adds allowed
|
||||
# configuration sections/values, but the list of plugins to load are in... the
|
||||
# configuration sections/values, but the list of plugins to load are in... the
|
||||
# settings.
|
||||
# Here is the conceptual presentation of Settings class initialization stages :
|
||||
# -# Preloading (sets values like lodel2 library path or the plugins path)
|
||||
# -# Ask a @ref lodel.settings.setting_loader.SettingsLoader to load all
|
||||
#configurations files
|
||||
# -# Ask a @ref lodel.settings.setting_loader.SettingsLoader to load all
|
||||
# configurations files
|
||||
# -# Fetch the list of plugins in the loaded settings
|
||||
# -# Merge plugins settings specification with the global lodel settings
|
||||
#specs ( see @ref lodel.plugin )
|
||||
# -# Merge plugins settings specification with the global lodel settings
|
||||
# specs ( see @ref lodel.plugin )
|
||||
# -# Fetch all settings from the merged settings specs
|
||||
#
|
||||
# @par Init sequence in practical
|
||||
|
|
@ -68,39 +70,41 @@ class MetaSettings(type):
|
|||
# -# @ref Settings.__populate_from_specs() (step 5)
|
||||
# -# And finally @ref Settings.__confs_to_namedtuple()
|
||||
#
|
||||
# @todo handles default sections for variable sections (sections ending with
|
||||
# @todo handles default sections for variable sections (sections ending with
|
||||
# '.*')
|
||||
# @todo delete the first stage, the lib path HAVE TO BE HARDCODED. In fact
|
||||
#when we will run lodel in production the lodel2 lib will be in the python path
|
||||
# when we will run lodel in production the lodel2 lib will be in the python path
|
||||
#@todo add log messages (now we can)
|
||||
|
||||
|
||||
class Settings(object, metaclass=MetaSettings):
|
||||
|
||||
## @brief Stores the singleton instance
|
||||
# @brief Stores the singleton instance
|
||||
instance = None
|
||||
|
||||
## @brief Instanciate the Settings singleton
|
||||
|
||||
# @brief Instanciate the Settings singleton
|
||||
# @param conf_dir str : The configuration directory
|
||||
#@param custom_confspecs None | dict : if given overwrite default lodel2
|
||||
#confspecs
|
||||
def __init__(self, conf_dir, custom_confspecs = None):
|
||||
self.singleton_assert() # check that it is the only instance
|
||||
# confspecs
|
||||
def __init__(self, conf_dir, custom_confspecs=None):
|
||||
self.singleton_assert() # check that it is the only instance
|
||||
Settings.instance = self
|
||||
## @brief Configuration specification
|
||||
# @brief Configuration specification
|
||||
#
|
||||
# Initialized by Settings.__bootstrap() method
|
||||
self.__conf_specs = custom_confspecs
|
||||
## @brief Stores the configurations in namedtuple tree
|
||||
# @brief Stores the configurations in namedtuple tree
|
||||
self.__confs = None
|
||||
self.__conf_dir = conf_dir
|
||||
self.__started = False
|
||||
self.__bootstrap()
|
||||
|
||||
## @brief Get the named tuple representing configuration
|
||||
|
||||
# @brief Get the named tuple representing configuration
|
||||
@property
|
||||
def settings(self):
|
||||
return self.__confs.lodel2
|
||||
|
||||
## @brief Delete the singleton instance
|
||||
|
||||
# @brief Delete the singleton instance
|
||||
@classmethod
|
||||
def stop(cls):
|
||||
del(cls.instance)
|
||||
|
|
@ -110,7 +114,7 @@ class Settings(object, metaclass=MetaSettings):
|
|||
def started(cls):
|
||||
return cls.instance is not None and cls.instance.__started
|
||||
|
||||
##@brief An utility method that raises if the singleton is not in a good
|
||||
# @brief An utility method that raises if the singleton is not in a good
|
||||
# state
|
||||
#@param expect_instanciated bool : if True we expect that the class is
|
||||
# allready instanciated, else not
|
||||
|
|
@ -124,17 +128,17 @@ class Settings(object, metaclass=MetaSettings):
|
|||
if cls.started():
|
||||
raise RuntimeError("The Settings class is already started")
|
||||
|
||||
##@brief Saves a new configuration for section confname
|
||||
# @brief Saves a new configuration for section confname
|
||||
#@param confname is the name of the modified section
|
||||
#@param confvalue is a dict with variables to save
|
||||
#@param validator is a dict with adapted validator
|
||||
@classmethod
|
||||
def set(cls, confname, confvalue,validator):
|
||||
def set(cls, confname, confvalue, validator):
|
||||
loader = SettingsLoader(cls.instance.__conf_dir)
|
||||
confkey=confname.rpartition('.')
|
||||
confkey = confname.rpartition('.')
|
||||
loader.setoption(confkey[0], confkey[2], confvalue, validator)
|
||||
|
||||
##@brief This method handles Settings instance bootstraping
|
||||
# @brief This method handles Settings instance bootstraping
|
||||
def __bootstrap(self):
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.plugin.plugins': ['Plugin', 'PluginError']})
|
||||
|
|
@ -144,9 +148,9 @@ class Settings(object, metaclass=MetaSettings):
|
|||
else:
|
||||
lodel2_specs = self.__conf_specs
|
||||
self.__conf_specs = None
|
||||
loader = SettingsLoader(self.__conf_dir)
|
||||
loader = SettingsLoader(self.__conf_dir)
|
||||
plugin_list = []
|
||||
for ptype_name,ptype in Plugin.plugin_types().items():
|
||||
for ptype_name, ptype in Plugin.plugin_types().items():
|
||||
pls = ptype.plist_confspecs()
|
||||
lodel2_specs = confspec_append(lodel2_specs, **pls)
|
||||
cur_list = loader.getoption(
|
||||
|
|
@ -162,13 +166,15 @@ class Settings(object, metaclass=MetaSettings):
|
|||
plugin_list += cur_list
|
||||
except TypeError:
|
||||
plugin_list += [cur_list]
|
||||
#Checking confspecs
|
||||
# Checking confspecs
|
||||
for section in lodel2_specs:
|
||||
if section.lower() != section:
|
||||
raise SettingsError("Only lower case are allowed in section name (thank's ConfigParser...)")
|
||||
raise SettingsError(
|
||||
"Only lower case are allowed in section name (thank's ConfigParser...)")
|
||||
for kname in lodel2_specs[section]:
|
||||
if kname.lower() != kname:
|
||||
raise SettingsError("Only lower case are allowed in section name (thank's ConfigParser...)")
|
||||
raise SettingsError(
|
||||
"Only lower case are allowed in section name (thank's ConfigParser...)")
|
||||
|
||||
# Starting the Plugins class
|
||||
logger.debug("Starting lodel.plugin.Plugin class")
|
||||
|
|
@ -181,13 +187,13 @@ class Settings(object, metaclass=MetaSettings):
|
|||
specs.append(Plugin.get(plugin_name).confspecs)
|
||||
except PluginError as e:
|
||||
errors.append(SettingsError(msg=str(e)))
|
||||
if len(errors) > 0: #Raise all plugins import errors
|
||||
if len(errors) > 0: # Raise all plugins import errors
|
||||
raise SettingsErrors(errors)
|
||||
self.__conf_specs = self.__merge_specs(specs)
|
||||
self.__populate_from_specs(self.__conf_specs, loader)
|
||||
self.__started = True
|
||||
|
||||
##@brief Produce a configuration specification dict by merging all specifications
|
||||
|
||||
# @brief Produce a configuration specification dict by merging all specifications
|
||||
#
|
||||
# Merges global lodel2 conf spec from @ref lodel.settings.validator.LODEL2_CONF_SPECS
|
||||
# and configuration specifications from loaded plugins
|
||||
|
|
@ -198,31 +204,35 @@ class Settings(object, metaclass=MetaSettings):
|
|||
for spec in specs:
|
||||
for section in spec:
|
||||
if section.lower() != section:
|
||||
raise SettingsError("Only lower case are allowed in section name (thank's ConfigParser...)")
|
||||
raise SettingsError(
|
||||
"Only lower case are allowed in section name (thank's ConfigParser...)")
|
||||
if section not in res:
|
||||
res[section] = dict()
|
||||
for kname in spec[section]:
|
||||
if kname.lower() != kname:
|
||||
raise SettingsError("Only lower case are allowed in section name (thank's ConfigParser...)")
|
||||
raise SettingsError(
|
||||
"Only lower case are allowed in section name (thank's ConfigParser...)")
|
||||
if kname in res[section]:
|
||||
raise SettingsError("Duplicated key '%s' in section '%s'" % (kname, section))
|
||||
raise SettingsError("Duplicated key '%s' in section '%s'" %
|
||||
(kname, section))
|
||||
res[section.lower()][kname] = copy.copy(spec[section][kname])
|
||||
return res
|
||||
|
||||
##@brief Populate the Settings instance with options values fetched with the loader from merged specs
|
||||
|
||||
# @brief Populate the Settings instance with options values fetched with the loader from merged specs
|
||||
#
|
||||
# Populate the __confs attribute
|
||||
# @param specs dict : Settings specification dictionnary as returned by __merge_specs
|
||||
# @param loader SettingsLoader : A SettingsLoader instance
|
||||
def __populate_from_specs(self, specs, loader):
|
||||
self.__confs = dict()
|
||||
specs = copy.copy(specs) #Avoid destroying original specs dict (may be useless)
|
||||
specs = copy.copy(specs) # Avoid destroying original specs dict (may be useless)
|
||||
# Construct final specs dict replacing variable sections
|
||||
# by the actual existing sections
|
||||
variable_sections = [ section for section in specs if section.endswith('.*') ]
|
||||
variable_sections = [section for section in specs if section.endswith('.*')]
|
||||
for vsec in variable_sections:
|
||||
preffix = vsec[:-2]
|
||||
for section in loader.getsection(preffix, 'default'): #WARNING : hardcoded default section
|
||||
# WARNING : hardcoded default section
|
||||
for section in loader.getsection(preffix, 'default'):
|
||||
specs[section] = copy.copy(specs[vsec])
|
||||
del(specs[vsec])
|
||||
# Fetching values for sections
|
||||
|
|
@ -238,8 +248,8 @@ class Settings(object, metaclass=MetaSettings):
|
|||
|
||||
self.__confs_to_namedtuple()
|
||||
pass
|
||||
|
||||
##@brief Transform the __confs attribute into imbricated namedtuple
|
||||
|
||||
# @brief Transform the __confs attribute into imbricated namedtuple
|
||||
#
|
||||
# For example an option named "foo" in a section named "hello.world" will
|
||||
# be acessible with self.__confs.hello.world.foo
|
||||
|
|
@ -257,7 +267,7 @@ class Settings(object, metaclass=MetaSettings):
|
|||
section_name = ""
|
||||
cur = section_tree
|
||||
for sec_part in spl:
|
||||
section_name += sec_part+'.'
|
||||
section_name += sec_part + '.'
|
||||
if sec_part not in cur:
|
||||
cur[sec_part] = dict()
|
||||
cur = cur[sec_part]
|
||||
|
|
@ -267,35 +277,35 @@ class Settings(object, metaclass=MetaSettings):
|
|||
raise SettingsError("Duplicated key for '%s.%s'" % (section_name, kname))
|
||||
cur[kname] = kval
|
||||
|
||||
path = [ ('root', section_tree) ]
|
||||
path = [('root', section_tree)]
|
||||
visited = set()
|
||||
|
||||
|
||||
curname = 'root'
|
||||
nodename = 'Lodel2Settings'
|
||||
cur = section_tree
|
||||
while True:
|
||||
visited.add(nodename)
|
||||
left = [ (kname, cur[kname])
|
||||
for kname in cur
|
||||
if nodename+'.'+kname.title() not in visited and isinstance(cur[kname], dict)
|
||||
left = [(kname, cur[kname])
|
||||
for kname in cur
|
||||
if nodename + '.' + kname.title() not in visited and isinstance(cur[kname], dict)
|
||||
]
|
||||
if len(left) == 0:
|
||||
name, leaf = path.pop()
|
||||
typename = nodename.replace('.', '')
|
||||
if len(path) == 0:
|
||||
# END
|
||||
self.__confs = self.__tree2namedtuple(leaf,typename)
|
||||
self.__confs = self.__tree2namedtuple(leaf, typename)
|
||||
break
|
||||
else:
|
||||
path[-1][1][name] = self.__tree2namedtuple(leaf,typename)
|
||||
path[-1][1][name] = self.__tree2namedtuple(leaf, typename)
|
||||
nodename = '.'.join(nodename.split('.')[:-1])
|
||||
cur = path[-1][1]
|
||||
else:
|
||||
curname, cur = left[0]
|
||||
path.append( (curname, cur) )
|
||||
path.append((curname, cur))
|
||||
nodename += '.' + curname.title()
|
||||
|
||||
##@brief Forge a named tuple given a conftree node
|
||||
|
||||
# @brief Forge a named tuple given a conftree node
|
||||
# @param conftree dict : A conftree node
|
||||
# @param name str
|
||||
# @return a named tuple with fieldnames corresponding to conftree keys
|
||||
|
|
@ -303,11 +313,13 @@ class Settings(object, metaclass=MetaSettings):
|
|||
ResNamedTuple = namedtuple(name, conftree.keys())
|
||||
return ResNamedTuple(**conftree)
|
||||
|
||||
|
||||
class MetaSettingsRO(type):
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(Settings.s, name)
|
||||
|
||||
|
||||
## @brief A class that provide . notation read only access to configurations
|
||||
|
||||
# @brief A class that provide . notation read only access to configurations
|
||||
class SettingsRO(object, metaclass=MetaSettingsRO):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -8,28 +8,27 @@ from lodel.context import LodelContext
|
|||
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.logger': 'logger',
|
||||
'lodel.settings.utils': ['SettingsError', 'SettingsErrors'],
|
||||
'lodel.settings.validator': ['SettingsValidationError']})
|
||||
'lodel.settings.utils': ['SettingsError', 'SettingsErrors']})
|
||||
|
||||
##@brief Merges and loads configuration files
|
||||
class SettingsLoader(object):
|
||||
|
||||
|
||||
## To avoid the DEFAULT section whose values are found in all sections, we
|
||||
# have to give it an unsual name
|
||||
DEFAULT_SECTION = 'lodel2_default_passaway_tip'
|
||||
|
||||
|
||||
## @brief Virtual filename when default value is used
|
||||
DEFAULT_FILENAME = 'default_value'
|
||||
|
||||
##@brief Constructor
|
||||
# @param conf_path str : conf.d path
|
||||
def __init__(self,conf_path):
|
||||
self.__conf_path=conf_path
|
||||
self.__conf_sv=dict()
|
||||
self.__conf=self.__merge()
|
||||
def __init__(self, conf_path):
|
||||
self.__conf_path = conf_path
|
||||
self.__conf_sv = dict()
|
||||
self.__conf = self.__merge()
|
||||
# Stores errors
|
||||
self.__errors_list = []
|
||||
|
||||
|
||||
##@brief Lists and merges files in settings_loader.conf_path
|
||||
# @return dict()
|
||||
def __merge(self):
|
||||
|
|
@ -38,26 +37,24 @@ class SettingsLoader(object):
|
|||
logger.debug("SettingsLoader found those settings files : %s" % (
|
||||
', '.join(l_dir)))
|
||||
|
||||
for f_ini in l_dir:
|
||||
config = configparser.ConfigParser(default_section = self.DEFAULT_SECTION ,interpolation=None)
|
||||
for f_ini in l_dir:
|
||||
config = configparser.ConfigParser(default_section=self.DEFAULT_SECTION, interpolation=None)
|
||||
config.read(f_ini)
|
||||
for section in [ s for s in config if s != self.DEFAULT_SECTION ]:
|
||||
for section in [s for s in config if s != self.DEFAULT_SECTION]:
|
||||
if section not in conf:
|
||||
conf[section] = dict()
|
||||
for param in config[section]:
|
||||
if param not in conf[section]:
|
||||
conf[section][param]=dict()
|
||||
if param not in conf[section]:
|
||||
conf[section][param] = dict()
|
||||
conf[section][param]['value'] = config[section][param]
|
||||
conf[section][param]['file'] = f_ini
|
||||
self.__conf_sv[section + ':' + param]=f_ini
|
||||
self.__conf_sv[section + ':' + param] = f_ini
|
||||
else:
|
||||
raise SettingsError("Error redeclaration of key %s in section %s. Found in %s and %s" % (
|
||||
section,
|
||||
param,
|
||||
f_ini,
|
||||
conf[section][param]['file']))
|
||||
raise SettingsError("Error redeclaration of key %s \
|
||||
in section %s. Found in %s and %s" % (\
|
||||
section, param, f_ini, conf[section][param]['file']))
|
||||
return conf
|
||||
|
||||
|
||||
##@brief Returns option if exists default_value else and validates
|
||||
# @param section str : name of the section
|
||||
# @param keyname str
|
||||
|
|
@ -65,8 +62,8 @@ class SettingsLoader(object):
|
|||
# @param default_value *
|
||||
# @param mandatory bool
|
||||
# @return the option
|
||||
def getoption(self,section,keyname,validator,default_value=None,mandatory=False):
|
||||
conf=self.__conf
|
||||
def getoption(self, section, keyname, validator, default_value=None, mandatory=False):
|
||||
conf = self.__conf
|
||||
if section not in conf:
|
||||
conf[section] = dict()
|
||||
|
||||
|
|
@ -85,35 +82,31 @@ class SettingsLoader(object):
|
|||
if result is None:
|
||||
if default_value is None and mandatory:
|
||||
msg = "Default value mandatory for option %s" % keyname
|
||||
expt = SettingsError( msg = msg,
|
||||
key_id = section+'.'+keyname,
|
||||
filename = sec[keyname]['file'])
|
||||
expt = SettingsError(msg=msg, key_id=section+'.'+keyname, \
|
||||
filename=sec[keyname]['file'])
|
||||
self.__errors_list.append(expt)
|
||||
return
|
||||
else:
|
||||
sec[keyname]=dict()
|
||||
sec[keyname] = dict()
|
||||
sec[keyname]['value'] = default_value
|
||||
sec[keyname]['file'] = SettingsLoader.DEFAULT_FILENAME
|
||||
result = default_value
|
||||
logger.debug("Using default value for configuration key %s:%s" % (
|
||||
section, keyname))
|
||||
logger.debug("Using default value for configuration key %s:%s" \
|
||||
% (section, keyname))
|
||||
|
||||
try:
|
||||
return validator(result)
|
||||
except Exception as e:
|
||||
# Generating nice exceptions
|
||||
if False and sec[keyname]['file'] == SettingsLoader.DEFAULT_FILENAME:
|
||||
expt = SettingsError( msg = 'Mandatory settings not found',
|
||||
key_id = section+'.'+keyname)
|
||||
expt = SettingsError(msg='Mandatory settings not found', \
|
||||
key_id=section+'.'+keyname)
|
||||
self.__errors_list.append(expt)
|
||||
else:
|
||||
expt = SettingsValidationError(
|
||||
"For %s.%s : %s" %
|
||||
(section, keyname,e)
|
||||
)
|
||||
expt2 = SettingsError( msg = str(expt),
|
||||
key_id = section+'.'+keyname,
|
||||
filename = sec[keyname]['file'])
|
||||
#expt = ValidationError("For %s.%s : %s" % (section, keyname, e))
|
||||
expt2 = SettingsError(msg=str(expt), \
|
||||
key_id=section+'.'+keyname, \
|
||||
filename=sec[keyname]['file'])
|
||||
self.__errors_list.append(expt2)
|
||||
return
|
||||
|
||||
|
|
@ -123,38 +116,38 @@ class SettingsLoader(object):
|
|||
# @param value str
|
||||
# @param validator callable : takes one argument value and raises validation fail
|
||||
# @return the option
|
||||
def setoption(self,section,keyname,value,validator):
|
||||
f_conf=copy.copy(self.__conf[section][keyname]['file'])
|
||||
def setoption(self, section, keyname, value, validator):
|
||||
f_conf = copy.copy(self.__conf[section][keyname]['file'])
|
||||
if f_conf == SettingsLoader.DEFAULT_FILENAME:
|
||||
f_conf = self.__conf_path + '/generated.ini'
|
||||
|
||||
conf=self.__conf
|
||||
conf = self.__conf
|
||||
conf[section][keyname] = value
|
||||
config = configparser.ConfigParser()
|
||||
config.read(f_conf)
|
||||
if section not in config:
|
||||
config[section]={}
|
||||
config[section] = {}
|
||||
config[section][keyname] = validator(value)
|
||||
|
||||
|
||||
with open(f_conf, 'w') as configfile:
|
||||
config.write(configfile)
|
||||
|
||||
|
||||
##@brief Saves new partial configuration. Writes in the conf files corresponding
|
||||
# @param sections dict
|
||||
# @param validators dict of callable : takes one argument value and raises validation fail
|
||||
def saveconf(self, sections, validators):
|
||||
for sec in sections:
|
||||
for kname in sections[sec]:
|
||||
self.setoption(sec,kname,sections[sec][kname],validators[sec][kname])
|
||||
|
||||
self.setoption(sec, kname, sections[sec][kname], validators[sec][kname])
|
||||
|
||||
##@brief Returns the section to be configured
|
||||
# @param section_prefix str
|
||||
# @param default_section str
|
||||
# @return the section as dict()
|
||||
def getsection(self,section_prefix,default_section=None):
|
||||
conf=copy.copy(self.__conf)
|
||||
|
||||
sections=[]
|
||||
def getsection(self, section_prefix, default_section=None):
|
||||
conf = copy.copy(self.__conf)
|
||||
|
||||
sections = []
|
||||
if section_prefix in conf:
|
||||
sections.append(section_prefix)
|
||||
for sect_names in conf:
|
||||
|
|
@ -162,34 +155,33 @@ class SettingsLoader(object):
|
|||
pass
|
||||
elif sect_names.startswith(section_prefix + '.'):
|
||||
sections.append(sect_names)
|
||||
if sections == [] and default_section:
|
||||
sections.append(section_prefix + '.' + default_section)
|
||||
if sections == [] and default_section:
|
||||
sections.append(section_prefix + '.' + default_section)
|
||||
elif sections == []:
|
||||
raise NameError("Not existing settings section : %s" % section_prefix)
|
||||
|
||||
|
||||
return sections
|
||||
|
||||
|
||||
##@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
|
||||
# the list of invalids settings keys
|
||||
# @return a dict with SECTION_NAME+":"+KEY_NAME as key and the filename
|
||||
# where the settings was found as value
|
||||
def getremains(self):
|
||||
return self.__conf_sv
|
||||
|
||||
|
||||
##@brief Raise a SettingsErrors exception if some confs remains
|
||||
#@note typically used at the end of Settings bootstrap
|
||||
def raise_errors(self):
|
||||
remains = self.getremains()
|
||||
err_l = self.__errors_list
|
||||
for key_id, filename in remains.items():
|
||||
err_l.append(SettingsError( msg = "Invalid configuration key",
|
||||
key_id = key_id,
|
||||
filename = filename))
|
||||
err_l.append(SettingsError(msg="Invalid configuration key", \
|
||||
key_id=key_id, \
|
||||
filename =filename))
|
||||
if len(err_l) > 0:
|
||||
raise SettingsErrors(err_l)
|
||||
else:
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ def get_utc_timestamp():
|
|||
d = datetime.datetime.utcnow()
|
||||
epoch = datetime.datetime(1970, 1, 1)
|
||||
t = (d - epoch).total_seconds()
|
||||
return t
|
||||
return t
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import hashlib
|
|||
import json
|
||||
|
||||
|
||||
##@brief Stores multilangage string
|
||||
# @brief Stores multilangage string
|
||||
class MlString(object):
|
||||
|
||||
|
||||
__default_lang = 'eng'
|
||||
|
||||
langs = [
|
||||
|
|
@ -17,7 +17,7 @@ class MlString(object):
|
|||
'esp',
|
||||
]
|
||||
|
||||
##@brief Create a new MlString instance
|
||||
# @brief Create a new MlString instance
|
||||
# @param arg str | dict : Can be a json string, a string or a dict. It could be also a MlString object
|
||||
def __init__(self, arg):
|
||||
self.values = dict()
|
||||
|
|
@ -31,11 +31,12 @@ class MlString(object):
|
|||
elif isinstance(arg, MlString):
|
||||
self.values = copy.copy(arg.values)
|
||||
else:
|
||||
raise ValueError('<class str>, <class dict> or <class MlString> expected, but %s found' % type(arg))
|
||||
|
||||
##@brief Return a translation given a lang
|
||||
raise ValueError(
|
||||
'<class str>, <class dict> or <class MlString> expected, but %s found' % type(arg))
|
||||
|
||||
# @brief Return a translation given a lang
|
||||
# @param lang str | None : If None return default lang translation
|
||||
def get(self, lang = None):
|
||||
def get(self, lang=None):
|
||||
lang = self.__default_lang if lang is None else lang
|
||||
if not self.lang_is_valid(lang):
|
||||
raise ValueError("Invalid lang : '%s'" % lang)
|
||||
|
|
@ -44,7 +45,7 @@ class MlString(object):
|
|||
else:
|
||||
return str(self)
|
||||
|
||||
##@brief Set a translation
|
||||
# @brief Set a translation
|
||||
# @param lang str : the lang
|
||||
# @param val str | None: the translation if None delete the translation
|
||||
def set(self, lang, val):
|
||||
|
|
@ -57,7 +58,7 @@ class MlString(object):
|
|||
else:
|
||||
self.values[lang] = val
|
||||
|
||||
##@brief Checks that given lang is valid
|
||||
# @brief Checks that given lang is valid
|
||||
# @param lang str : the lang
|
||||
@classmethod
|
||||
def lang_is_valid(cls, lang):
|
||||
|
|
@ -65,16 +66,16 @@ class MlString(object):
|
|||
raise ValueError('Invalid value for lang. Str expected but %s found' % type(lang))
|
||||
return lang in cls.langs
|
||||
|
||||
##@brief Get or set the default lang
|
||||
# @brief Get or set the default lang
|
||||
@classmethod
|
||||
def default_lang(cls, lang = None):
|
||||
def default_lang(cls, lang=None):
|
||||
if lang is None:
|
||||
return cls.__default_lang
|
||||
if not cls.lang_is_valid(lang):
|
||||
raise ValueError('lang "%s" is not valid"' % lang)
|
||||
cls.__default_lang = lang
|
||||
|
||||
##@brief Return a mlstring loaded from a json string
|
||||
|
||||
# @brief Return a mlstring loaded from a json string
|
||||
# @param json_str str : Json string
|
||||
@classmethod
|
||||
def from_json(cls, json_str):
|
||||
|
|
@ -89,13 +90,13 @@ class MlString(object):
|
|||
def d_hash(self):
|
||||
m = hashlib.md5()
|
||||
for lang in sorted(list(self.values.keys())):
|
||||
m.update(bytes(lang+";"+self.values[lang], 'utf-8'))
|
||||
m.update(bytes(lang + ";" + self.values[lang], 'utf-8'))
|
||||
return int.from_bytes(m.digest(), byteorder='big')
|
||||
|
||||
def __eq__(self, a):
|
||||
return hash(self) == hash(a)
|
||||
|
||||
## @return The default langage translation or any available translation
|
||||
|
||||
# @return The default langage translation or any available translation
|
||||
def __str__(self):
|
||||
if self.__default_lang in self.values:
|
||||
return self.values[self.__default_lang]
|
||||
|
|
|
|||
2
lodel/validator/Makefile.am
Normal file
2
lodel/validator/Makefile.am
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
validator_PYTHON=*.py
|
||||
validatordir=$(pkgpythondir)/validator
|
||||
6
lodel/validator/__init__.py
Normal file
6
lodel/validator/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#-*- coding: utf-8 -*-
|
||||
|
||||
## @package lodel.validator Lodel2 validator package
|
||||
#
|
||||
|
||||
|
||||
|
|
@ -9,55 +9,63 @@ import copy
|
|||
|
||||
from lodel.context import LodelContext
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.mlnamedobject.mlnamedobject': ['MlNamedObject'],
|
||||
'lodel.exceptions': ['LodelException', 'LodelExceptions',
|
||||
'LodelFatalError', 'FieldValidationError']})
|
||||
'LodelFatalError', 'FieldValidationError']})
|
||||
|
||||
## @package lodel.settings.validator Lodel2 settings validators/cast module
|
||||
# @package lodel.settings.validator Lodel2 settings validators/cast module
|
||||
#
|
||||
# Validator are registered in the SettingValidator class.
|
||||
# Validator are registered in the Validator class.
|
||||
# @note to get a list of registered default validators just run
|
||||
# <pre>$ python scripts/settings_validator.py</pre>
|
||||
|
||||
##@brief Exception class that should be raised when a validation fails
|
||||
class SettingsValidationError(Exception):
|
||||
# @brief Exception class that should be raised when a validation fails
|
||||
|
||||
|
||||
class ValidationError(Exception):
|
||||
pass
|
||||
|
||||
##@brief Handles settings validators
|
||||
# @brief Handles settings validators
|
||||
#
|
||||
# Class instance are callable objects that takes a value argument (the value to validate). It raises
|
||||
# a SettingsValidationError if validation fails, else it returns a properly
|
||||
# a ValidationError if validation fails, else it returns a properly
|
||||
# casted value.
|
||||
#@todo implement an IP validator and use it in multisite confspec
|
||||
class SettingValidator(object):
|
||||
|
||||
|
||||
|
||||
class Validator(MlNamedObject):
|
||||
|
||||
_validators = dict()
|
||||
_description = dict()
|
||||
|
||||
##@brief Instanciate a validator
|
||||
|
||||
# @brief Instanciate a validator
|
||||
#@param name str : validator name
|
||||
#@param none_is_valid bool : if True None will be validated
|
||||
#@param **kwargs : more arguement for the validator
|
||||
def __init__(self, name, none_is_valid = False, **kwargs):
|
||||
def __init__(self, name, none_is_valid=False, display_name=None, help_text=None, **kwargs):
|
||||
if name is not None and name not in self._validators:
|
||||
raise LodelFatalError("No validator named '%s'" % name)
|
||||
self.__none_is_valid = none_is_valid
|
||||
self.__name = name
|
||||
self._opt_args = kwargs
|
||||
if display_name is None:
|
||||
display_name = name
|
||||
super().__init__(display_name, help_text)
|
||||
|
||||
##@brief Call the validator
|
||||
# @brief Call the validator
|
||||
# @param value *
|
||||
# @return properly casted value
|
||||
# @throw SettingsValidationError
|
||||
# @throw ValidationError
|
||||
def __call__(self, value):
|
||||
if self.__none_is_valid and value is None:
|
||||
return None
|
||||
try:
|
||||
ret = self._validators[self.__name](value, **self._opt_args)
|
||||
return ret
|
||||
except Exception as e:
|
||||
raise SettingsValidationError(e)
|
||||
|
||||
##@brief Register a new validator
|
||||
except Exception as exp:
|
||||
raise ValidationError(exp)
|
||||
|
||||
# @brief Register a new validator
|
||||
# @param name str : validator name
|
||||
# @param callback callable : the function that will validate a value
|
||||
# @param description str
|
||||
|
|
@ -70,76 +78,68 @@ class SettingValidator(object):
|
|||
raise TypeError("Callable expected but got %s" % type(callback))
|
||||
cls._validators[name] = callback
|
||||
cls._description[name] = description
|
||||
|
||||
##@brief Get the validator list associated with description
|
||||
|
||||
# @brief Get the validator list associated with description
|
||||
@classmethod
|
||||
def validators_list(cls):
|
||||
return copy.copy(cls._description)
|
||||
|
||||
##@brief Create and register a list validator
|
||||
# @brief Create and register a list validator
|
||||
# @param elt_validator callable : The validator that will be used for validate each elt value
|
||||
# @param validator_name str
|
||||
# @param description None | str
|
||||
# @param separator str : The element separator
|
||||
# @return A SettingValidator instance
|
||||
# @return A Validator instance
|
||||
@classmethod
|
||||
def create_list_validator(cls, validator_name, elt_validator, description = None, separator = ','):
|
||||
def create_list_validator(cls, validator_name, elt_validator, description=None, separator=','):
|
||||
def list_validator(value):
|
||||
res = list()
|
||||
errors = list()
|
||||
for elt in value.split(separator):
|
||||
elt = elt_validator(elt)
|
||||
if len(elt) > 0:
|
||||
res.append(elt)
|
||||
return res
|
||||
description = "Convert value to an array" if description is None else description
|
||||
cls.register_validator(
|
||||
validator_name,
|
||||
list_validator,
|
||||
description)
|
||||
cls.register_validator(validator_name, list_validator, description)
|
||||
return cls(validator_name)
|
||||
|
||||
##@brief Create and register a list validator which reads an array and returns a string
|
||||
|
||||
# @brief Create and register a list validator which reads an array and returns a string
|
||||
# @param elt_validator callable : The validator that will be used for validate each elt value
|
||||
# @param validator_name str
|
||||
# @param description None | str
|
||||
# @param separator str : The element separator
|
||||
# @return A SettingValidator instance
|
||||
# @return A Validator instance
|
||||
@classmethod
|
||||
def create_write_list_validator(cls, validator_name, elt_validator, description = None, separator = ','):
|
||||
def create_write_list_validator(cls, validator_name, elt_validator, description=None, separator=','):
|
||||
def write_list_validator(value):
|
||||
res = ''
|
||||
errors = list()
|
||||
for elt in value:
|
||||
res += elt_validator(elt) + ','
|
||||
return res[:len(res)-1]
|
||||
return res[:len(res) - 1]
|
||||
description = "Convert value to a string" if description is None else description
|
||||
cls.register_validator(
|
||||
validator_name,
|
||||
write_list_validator,
|
||||
description)
|
||||
cls.register_validator(validator_name, write_list_validator, description)
|
||||
return cls(validator_name)
|
||||
|
||||
##@brief Create and register a regular expression validator
|
||||
|
||||
# @brief Create and register a regular expression validator
|
||||
# @param pattern str : regex pattern
|
||||
# @param validator_name str : The validator name
|
||||
# @param description str : Validator description
|
||||
# @return a SettingValidator instance
|
||||
# @return a Validator instance
|
||||
@classmethod
|
||||
def create_re_validator(cls, pattern, validator_name, description = None):
|
||||
def create_re_validator(cls, pattern, validator_name, description=None):
|
||||
def re_validator(value):
|
||||
if not re.match(pattern, value):
|
||||
raise SettingsValidationError("The value '%s' doesn't match the following pattern '%s'" % pattern)
|
||||
raise ValidationError(
|
||||
"The value '%s' doesn't match the following pattern '%s'"
|
||||
% pattern)
|
||||
return value
|
||||
#registering the validator
|
||||
cls.register_validator(
|
||||
validator_name,
|
||||
re_validator,
|
||||
("Match value to '%s'" % pattern) if description is None else description)
|
||||
# registering the validator
|
||||
cls.register_validator(validator_name, re_validator,
|
||||
("Match value to '%s'" % pattern)
|
||||
if description is None else description)
|
||||
return cls(validator_name)
|
||||
|
||||
|
||||
## @return a list of registered validators
|
||||
# @return a list of registered validators
|
||||
@classmethod
|
||||
def validators_list_str(cls):
|
||||
result = ''
|
||||
|
|
@ -150,20 +150,26 @@ class SettingValidator(object):
|
|||
result += "\n"
|
||||
return result
|
||||
|
||||
##@brief Integer value validator callback
|
||||
# @brief Integer value validator callback
|
||||
|
||||
|
||||
def int_val(value):
|
||||
return int(value)
|
||||
|
||||
##@brief Output file validator callback
|
||||
# @brief Output file validator callback
|
||||
# @return A file object (if filename is '-' return sys.stderr)
|
||||
|
||||
|
||||
def file_err_output(value):
|
||||
if not isinstance(value, str):
|
||||
raise SettingsValidationError("A string was expected but got '%s' " % value)
|
||||
raise ValidationError("A string was expected but got '%s' " % value)
|
||||
if value == '-':
|
||||
return None
|
||||
return value
|
||||
|
||||
##@brief Boolean value validator callback
|
||||
# @brief Boolean value validator callback
|
||||
|
||||
|
||||
def boolean_val(value):
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
|
|
@ -172,52 +178,66 @@ def boolean_val(value):
|
|||
elif value.strip().lower() == 'false' or value.strip() == '0':
|
||||
value = False
|
||||
else:
|
||||
raise SettingsValidationError("A boolean was expected but got '%s' " % value)
|
||||
raise ValidationError("A boolean was expected but got '%s' " % value)
|
||||
return bool(value)
|
||||
|
||||
##@brief Validate a directory path
|
||||
# @brief Validate a directory path
|
||||
|
||||
|
||||
def directory_val(value):
|
||||
res = SettingValidator('strip')(value)
|
||||
res = Validator('strip')(value)
|
||||
if not os.path.isdir(res):
|
||||
raise SettingsValidationError("Folowing path don't exists or is not a directory : '%s'"%res)
|
||||
raise ValidationError("Following path don't exists or is not a directory : '%s'" % res)
|
||||
return res
|
||||
|
||||
##@brief Validate a loglevel value
|
||||
# @brief Validate a loglevel value
|
||||
|
||||
|
||||
def loglevel_val(value):
|
||||
valids = ['DEBUG', 'INFO', 'WARNING', 'SECURITY', 'ERROR', 'CRITICAL']
|
||||
if value.upper() not in valids:
|
||||
raise SettingsValidationError(
|
||||
"The value '%s' is not a valid loglevel" % value)
|
||||
raise ValidationError(
|
||||
"The value '%s' is not a valid loglevel" % value)
|
||||
return value.upper()
|
||||
|
||||
##@brief Validate a path
|
||||
# @brief Validate a path
|
||||
|
||||
|
||||
def path_val(value):
|
||||
if value is None or not os.path.exists(value):
|
||||
raise SettingsValidationError(
|
||||
"path '%s' doesn't exists" % value)
|
||||
raise ValidationError(
|
||||
"path '%s' doesn't exists" % value)
|
||||
return value
|
||||
|
||||
##@brief Validate None
|
||||
# @brief Validate None
|
||||
|
||||
|
||||
def none_val(value):
|
||||
if value is None:
|
||||
return None
|
||||
raise SettingsValidationError("This settings cannot be set in configuration file")
|
||||
raise ValidationError("This settings cannot be set in configuration file")
|
||||
|
||||
# @brief Validate a string
|
||||
|
||||
|
||||
##@brief Validate a string
|
||||
def str_val(value):
|
||||
try:
|
||||
return str(value)
|
||||
except Exception as e:
|
||||
raise SettingsValidationError("Not able to convert value to string : " + str(e))
|
||||
except Exception as exp:
|
||||
raise ValidationError("Can't to convert value to string: " + str(exp))
|
||||
|
||||
# @brief Validate using a regex
|
||||
|
||||
|
||||
##@brief Validate using a regex
|
||||
def regex_val(value, pattern):
|
||||
if re.match(pattern, value) is None:
|
||||
raise SettingsValidationError("The value '%s' is not validated by : \
|
||||
r\"%s\"" %(value, pattern))
|
||||
raise ValidationError("The value '%s' is not validated by : \
|
||||
r\"%s\"" % (value, pattern))
|
||||
return value
|
||||
|
||||
##@brief Validate a hostname (ipv4 or ipv6)
|
||||
# @brief Validate a hostname (ipv4 or ipv6)
|
||||
|
||||
|
||||
def host_val(value):
|
||||
if value == 'localhost':
|
||||
return value
|
||||
|
|
@ -225,37 +245,103 @@ def host_val(value):
|
|||
try:
|
||||
socket.inet_aton(value)
|
||||
return value
|
||||
except (TypeError,OSError):
|
||||
except (TypeError, OSError):
|
||||
pass
|
||||
try:
|
||||
socket.inet_pton(socket.AF_INET6, value)
|
||||
return value
|
||||
except (TypeError,OSError):
|
||||
except (TypeError, OSError):
|
||||
pass
|
||||
try:
|
||||
socket.getaddrinfo(value, 80)
|
||||
return value
|
||||
except (TypeError,socket.gaierror):
|
||||
except (TypeError, socket.gaierror):
|
||||
msg = "The value '%s' is not a valid host"
|
||||
raise SettingsValidationError(msg % value)
|
||||
raise ValidationError(msg % value)
|
||||
|
||||
##@brief Validator for Editorial model component
|
||||
|
||||
def custom_list_validator(value, validator_name, validator_kwargs=None):
|
||||
validator_kwargs = dict() if validator_kwargs is None else validator_kwargs
|
||||
validator = Validator(validator_name, **validator_kwargs)
|
||||
for item in value.split():
|
||||
validator(item)
|
||||
return value.split()
|
||||
|
||||
#
|
||||
# Default validators registration
|
||||
#
|
||||
|
||||
Validator.register_validator('custom_list', custom_list_validator,
|
||||
'A list validator that takes a "validator_name" as argument')
|
||||
|
||||
Validator.register_validator('dummy', lambda value: value, 'Validate anything')
|
||||
|
||||
Validator.register_validator('none', none_val, 'Validate None')
|
||||
|
||||
Validator.register_validator('string', str_val, 'Validate string values')
|
||||
|
||||
Validator.register_validator('strip', str.strip, 'String trim')
|
||||
|
||||
Validator.register_validator('int', int_val, 'Integer value validator')
|
||||
|
||||
Validator.register_validator('bool', boolean_val, 'Boolean value validator')
|
||||
|
||||
Validator.register_validator('errfile', file_err_output,
|
||||
'Error output file validator (return stderr if filename is "-")')
|
||||
|
||||
Validator.register_validator('directory', directory_val,
|
||||
'Directory path validator')
|
||||
|
||||
Validator.register_validator('loglevel', loglevel_val, 'Loglevel validator')
|
||||
|
||||
Validator.register_validator('path', path_val, 'path validator')
|
||||
|
||||
Validator.register_validator('host', host_val, 'host validator')
|
||||
|
||||
Validator.register_validator('regex', regex_val,
|
||||
'RegEx name validator (take re as argument)')
|
||||
|
||||
Validator.create_list_validator('list', Validator('strip'), description="Simple list validator. Validate a list of values separated by ','",
|
||||
separator=',')
|
||||
|
||||
Validator.create_list_validator(
|
||||
'directory_list',
|
||||
Validator('directory'),
|
||||
description="Validator for a list of directory path separated with ','",
|
||||
separator=',')
|
||||
|
||||
Validator.create_write_list_validator(
|
||||
'write_list',
|
||||
Validator('directory'),
|
||||
description="Validator for an array of values \
|
||||
which will be set in a string, separated by ','",
|
||||
separator=',')
|
||||
|
||||
Validator.create_re_validator(
|
||||
r'^https?://[^\./]+.[^\./]+/?.*$',
|
||||
'http_url',
|
||||
'Url validator')
|
||||
|
||||
# @brief Validator for Editorial model component
|
||||
#
|
||||
# Designed to validate a conf that indicate a class.field in an EM
|
||||
#@todo modified the hardcoded dyncode import (it's a warning)
|
||||
|
||||
|
||||
def emfield_val(value):
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.plugin.hooks': ['LodelHook']})
|
||||
LodelContext.expose_modules(globals(),
|
||||
{'lodel.plugin.hooks': ['LodelHook']})
|
||||
spl = value.split('.')
|
||||
if len(spl) != 2:
|
||||
msg = "Expected a value in the form CLASSNAME.FIELDNAME but got : %s"
|
||||
raise SettingsValidationError(msg % value)
|
||||
value = tuple(spl)
|
||||
#Late validation hook
|
||||
# Late validation hook
|
||||
|
||||
@LodelHook('lodel2_dyncode_bootstraped')
|
||||
def emfield_conf_check(hookname, caller, payload):
|
||||
import leapi_dyncode as dyncode # <-- dirty & quick
|
||||
classnames = { cls.__name__.lower():cls for cls in dyncode.dynclasses}
|
||||
import leapi_dyncode as dyncode # <-- dirty & quick
|
||||
classnames = {cls.__name__.lower(): cls for cls in dyncode.dynclasses}
|
||||
if value[0].lower() not in classnames:
|
||||
msg = "Following dynamic class do not exists in current EM : %s"
|
||||
raise SettingsValidationError(msg % value[0])
|
||||
|
|
@ -265,13 +351,16 @@ def emfield_val(value):
|
|||
raise SettingsValidationError(msg % value)
|
||||
return value
|
||||
|
||||
##@brief Validator for plugin name & optionnaly type
|
||||
# @brief Validator for plugin name & optionnaly type
|
||||
#
|
||||
#Able to check that the value is a plugin and if it is of a specific type
|
||||
def plugin_validator(value, ptype = None):
|
||||
# Able to check that the value is a plugin and if it is of a specific type
|
||||
|
||||
|
||||
def plugin_validator(value, ptype=None):
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.plugin.hooks': ['LodelHook']})
|
||||
value = copy.copy(value)
|
||||
|
||||
@LodelHook('lodel2_dyncode_bootstraped')
|
||||
def plugin_type_checker(hookname, caller, payload):
|
||||
LodelContext.expose_modules(globals(), {
|
||||
|
|
@ -284,133 +373,39 @@ def plugin_validator(value, ptype = None):
|
|||
except PluginError:
|
||||
msg = "No plugin named %s found"
|
||||
msg %= value
|
||||
raise SettingsValidationError(msg)
|
||||
raise ValidationError(msg)
|
||||
if plugin._type_conf_name.lower() != ptype.lower():
|
||||
msg = "A plugin of type '%s' was expected but found a plugin \
|
||||
named '%s' that is a '%s' plugin"
|
||||
msg %= (ptype, value, plugin._type_conf_name)
|
||||
raise SettingsValidationError(msg)
|
||||
raise ValidationError(msg)
|
||||
return value
|
||||
|
||||
def custom_list_validator(value, validator_name, validator_kwargs = None):
|
||||
validator_kwargs = dict() if validator_kwargs is None else validator_kwargs
|
||||
validator = SettingValidator(validator_name, **validator_kwargs)
|
||||
for item in value.split():
|
||||
validator(item)
|
||||
return value.split()
|
||||
|
||||
#
|
||||
# Default validators registration
|
||||
#
|
||||
|
||||
SettingValidator.register_validator(
|
||||
Validator.register_validator(
|
||||
'plugin',
|
||||
plugin_validator,
|
||||
'plugin name & type validator')
|
||||
|
||||
SettingValidator.register_validator(
|
||||
'custom_list',
|
||||
custom_list_validator,
|
||||
'A list validator that takes a "validator_name" as argument')
|
||||
|
||||
SettingValidator.register_validator(
|
||||
'dummy',
|
||||
lambda value:value,
|
||||
'Validate anything')
|
||||
|
||||
SettingValidator.register_validator(
|
||||
'none',
|
||||
none_val,
|
||||
'Validate None')
|
||||
|
||||
SettingValidator.register_validator(
|
||||
'string',
|
||||
str_val,
|
||||
'Validate string values')
|
||||
|
||||
SettingValidator.register_validator(
|
||||
'strip',
|
||||
str.strip,
|
||||
'String trim')
|
||||
|
||||
SettingValidator.register_validator(
|
||||
'int',
|
||||
int_val,
|
||||
'Integer value validator')
|
||||
|
||||
SettingValidator.register_validator(
|
||||
'bool',
|
||||
boolean_val,
|
||||
'Boolean value validator')
|
||||
|
||||
SettingValidator.register_validator(
|
||||
'errfile',
|
||||
file_err_output,
|
||||
'Error output file validator (return stderr if filename is "-")')
|
||||
|
||||
SettingValidator.register_validator(
|
||||
'directory',
|
||||
directory_val,
|
||||
'Directory path validator')
|
||||
|
||||
SettingValidator.register_validator(
|
||||
'loglevel',
|
||||
loglevel_val,
|
||||
'Loglevel validator')
|
||||
|
||||
SettingValidator.register_validator(
|
||||
'path',
|
||||
path_val,
|
||||
'path validator')
|
||||
|
||||
SettingValidator.register_validator(
|
||||
'host',
|
||||
host_val,
|
||||
'host validator')
|
||||
|
||||
SettingValidator.register_validator(
|
||||
Validator.register_validator(
|
||||
'emfield',
|
||||
emfield_val,
|
||||
'EmField name validator')
|
||||
|
||||
SettingValidator.register_validator(
|
||||
'regex',
|
||||
regex_val,
|
||||
'RegEx name validator (take re as argument)')
|
||||
|
||||
SettingValidator.create_list_validator(
|
||||
'list',
|
||||
SettingValidator('strip'),
|
||||
description = "Simple list validator. Validate a list of values separated by ','",
|
||||
separator = ',')
|
||||
|
||||
SettingValidator.create_list_validator(
|
||||
'directory_list',
|
||||
SettingValidator('directory'),
|
||||
description = "Validator for a list of directory path separated with ','",
|
||||
separator = ',')
|
||||
SettingValidator.create_write_list_validator(
|
||||
'write_list',
|
||||
SettingValidator('directory'),
|
||||
description = "Validator for an array of values which will be set in a string, separated by ','",
|
||||
separator = ',')
|
||||
SettingValidator.create_re_validator(
|
||||
r'^https?://[^\./]+.[^\./]+/?.*$',
|
||||
'http_url',
|
||||
'Url validator')
|
||||
|
||||
#
|
||||
# Lodel 2 configuration specification
|
||||
#
|
||||
|
||||
##@brief Append a piece of confspec
|
||||
# @brief Append a piece of confspec
|
||||
#@note orig is modified during the process
|
||||
#@param orig dict : the confspec to update
|
||||
#@param section str : section name
|
||||
#@param key str
|
||||
#@param validator SettingValidator : the validator to use to check this configuration key's value
|
||||
#@param validator Validator : the validator to use to check this configuration key's value
|
||||
#@param default
|
||||
#@return new confspec
|
||||
|
||||
|
||||
def confspec_append(orig, section, key, validator, default):
|
||||
if section not in orig:
|
||||
orig[section] = dict()
|
||||
|
|
@ -418,41 +413,33 @@ def confspec_append(orig, section, key, validator, default):
|
|||
orig[section][key] = (default, validator)
|
||||
return orig
|
||||
|
||||
##@brief Global specifications for lodel2 settings
|
||||
# @brief Global specifications for lodel2 settings
|
||||
LODEL2_CONF_SPECS = {
|
||||
'lodel2': {
|
||||
'debug': ( True,
|
||||
SettingValidator('bool')),
|
||||
'sitename': ( 'noname',
|
||||
SettingValidator('strip')),
|
||||
'runtest': ( False,
|
||||
SettingValidator('bool')),
|
||||
'debug': (True, Validator('bool')),
|
||||
'sitename': ('noname', Validator('strip')),
|
||||
'runtest': (False, Validator('bool')),
|
||||
},
|
||||
'lodel2.logging.*' : {
|
||||
'level': ( 'ERROR',
|
||||
SettingValidator('loglevel')),
|
||||
'context': ( False,
|
||||
SettingValidator('bool')),
|
||||
'filename': ( "-",
|
||||
SettingValidator('errfile', none_is_valid = False)),
|
||||
'backupcount': ( 5,
|
||||
SettingValidator('int', none_is_valid = False)),
|
||||
'maxbytes': ( 1024*10,
|
||||
SettingValidator('int', none_is_valid = False)),
|
||||
'lodel2.logging.*': {
|
||||
'level': ('ERROR', Validator('loglevel')),
|
||||
'context': (False, Validator('bool')),
|
||||
'filename': ("-", Validator('errfile', none_is_valid=False)),
|
||||
'backupcount': (5, Validator('int', none_is_valid=False)),
|
||||
'maxbytes': (1024 * 10, Validator('int', none_is_valid=False)),
|
||||
},
|
||||
'lodel2.editorialmodel': {
|
||||
'emfile': ( 'em.pickle', SettingValidator('strip')),
|
||||
'emtranslator': ( 'picklefile', SettingValidator('strip')),
|
||||
'dyncode': ( 'leapi_dyncode.py', SettingValidator('strip')),
|
||||
'groups': ( '', SettingValidator('list')),
|
||||
'editormode': ( False, SettingValidator('bool')),
|
||||
'emfile': ('em.pickle', Validator('strip')),
|
||||
'emtranslator': ('picklefile', Validator('strip')),
|
||||
'dyncode': ('leapi_dyncode.py', Validator('strip')),
|
||||
'groups': ('', Validator('list')),
|
||||
'editormode': (False, Validator('bool')),
|
||||
},
|
||||
'lodel2.datasources.*': {
|
||||
'read_only': (False, SettingValidator('bool')),
|
||||
'identifier': ( None, SettingValidator('string')),
|
||||
'read_only': (False, Validator('bool')),
|
||||
'identifier': (None, Validator('string')),
|
||||
},
|
||||
'lodel2.auth': {
|
||||
'login_classfield': ('user.login', SettingValidator('emfield')),
|
||||
'pass_classfield': ('user.password', SettingValidator('emfield')),
|
||||
'login_classfield': ('user.login', Validator('emfield')),
|
||||
'pass_classfield': ('user.password', Validator('emfield')),
|
||||
},
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
#-*- 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
|
||||
################
|
||||
#
|
||||
#
|
||||
# @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.
|
||||
# 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
|
||||
|
||||
|
||||
loader = unittest.TestLoader()
|
||||
|
||||
if ((len(sys.argv) > 1) and (sys.argv[1].startswith('-')) is False):
|
||||
dpath = sys.argv[1]
|
||||
dpath = sys.argv[1]
|
||||
else:
|
||||
dpath = '.'
|
||||
dpath = '.'
|
||||
|
||||
suite = loader.discover('tests', pattern='nc_test*.py')
|
||||
with open(dpath+'/nocontext_tests.log', 'w') as logfile:
|
||||
with open(dpath + '/nocontext_tests.log', 'w') as logfile:
|
||||
unittest.TextTestRunner(
|
||||
logfile,
|
||||
failfast = '-f' in sys.argv,
|
||||
verbosity = 2 if '-v' in sys.argv else 1).run(suite)
|
||||
logfile,
|
||||
failfast='-f' in sys.argv,
|
||||
verbosity=2 if '-v' in sys.argv else 1).run(suite)
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@
|
|||
import sys
|
||||
import os, os.path
|
||||
sys.path.append(os.path.dirname(os.getcwd()+'/..'))
|
||||
from lodel.settings.validator import SettingValidator
|
||||
print(SettingValidator.validators_list_str())
|
||||
from lodel.validator.validator import Validator
|
||||
print(Validator.validators_list_str())
|
||||
|
|
|
|||
|
|
@ -5,52 +5,52 @@ from unittest import mock
|
|||
from unittest.mock import patch
|
||||
|
||||
from lodel.exceptions import *
|
||||
from lodel.settings.validator import *
|
||||
from lodel.validator.validator import *
|
||||
|
||||
class SettingValidatorTestCase(unittest.TestCase):
|
||||
class ValidatorTestCase(unittest.TestCase):
|
||||
|
||||
def test_init_basic(self):
|
||||
""" Testing the SettingsValidator class instanciation"""
|
||||
valid = SettingValidator('string')
|
||||
valid = Validator('string')
|
||||
#trying to call it
|
||||
valid('test')
|
||||
|
||||
def test_init_badname(self):
|
||||
""" Testing SettingValidator instanciation with non existing validator
|
||||
""" Testing Validator instanciation with non existing validator
|
||||
name"""
|
||||
with self.assertRaises(LodelFatalError):
|
||||
SettingValidator('qklfhsdufgsdyfugigsdfsdlcknsdp')
|
||||
Validator('qklfhsdufgsdyfugigsdfsdlcknsdp')
|
||||
|
||||
def test_noneswitch(self):
|
||||
""" Testing the none_is_valid switch given at validator instanciation
|
||||
"""
|
||||
none_invalid = SettingValidator('int')
|
||||
none_valid = SettingValidator('int', none_is_valid = True)
|
||||
none_invalid = Validator('int')
|
||||
none_valid = Validator('int', none_is_valid = True)
|
||||
|
||||
none_valid(None)
|
||||
with self.assertRaises(SettingsValidationError):
|
||||
with self.assertRaises(ValidationError):
|
||||
none_invalid(None)
|
||||
|
||||
def test_validator_registration(self):
|
||||
""" Testing the register_validator method of SettingValidator """
|
||||
""" Testing the register_validator method of Validator """
|
||||
mockfun = mock.MagicMock()
|
||||
vname = 'lfkjdshfkuhsdygsuuyfsduyf'
|
||||
testval = 'foo'
|
||||
SettingValidator.register_validator(vname, mockfun, 'test validator')
|
||||
Validator.register_validator(vname, mockfun, 'test validator')
|
||||
#Using registered validator
|
||||
valid = SettingValidator(vname)
|
||||
valid = Validator(vname)
|
||||
valid(testval)
|
||||
mockfun.assert_called_once_with(testval)
|
||||
|
||||
def test_validator_optargs_forwarding(self):
|
||||
""" Testing the ability for SettingValidator to forward optional
|
||||
""" Testing the ability for Validator to forward optional
|
||||
arguments """
|
||||
mockfun = mock.MagicMock()
|
||||
vname = 'lkjdsfhsdiufhisduguig'
|
||||
testval = 'azertyuiop'
|
||||
SettingValidator.register_validator(vname, mockfun, 'test validator')
|
||||
Validator.register_validator(vname, mockfun, 'test validator')
|
||||
#Using registered validator with more arguments
|
||||
valid = SettingValidator(vname,
|
||||
valid = Validator(vname,
|
||||
arga = 'a', argb = 42, argc = '1337')
|
||||
valid(testval)
|
||||
mockfun.assert_called_once_with(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue