1
0
Fork 0
mirror of https://github.com/yweber/lodel2.git synced 2025-11-22 05:36:54 +01:00

Merge branch 'newlodel' of git.labocleo.org:lodel2 into newlodel

This commit is contained in:
prieto 2016-04-21 10:30:16 +02:00
commit c8a6d5f99a
12 changed files with 192 additions and 119 deletions

View file

@ -1,7 +0,0 @@
#-*- coding: utf-8 -*-
import imp
import lodel.settings
from lodel.settings.settings import Settings
Settings.bootstrap(conf_file = 'settings_local.ini', conf_dir = 'globconf.d')
imp.reload(lodel.settings)

View file

@ -1 +1,3 @@
__author__ = 'roland' #-*- coding: utf-8 -*-
from .utils.starter import init_lodel

View file

@ -4,6 +4,9 @@ import hashlib
import importlib import importlib
from lodel.utils.mlstring import MlString from lodel.utils.mlstring import MlString
from lodel.logger import logger
from lodel.settings import Settings
from lodel.settings.utils import SettingsError
from lodel.editorial_model.exceptions import * from lodel.editorial_model.exceptions import *
from lodel.editorial_model.components import EmClass, EmField, EmGroup from lodel.editorial_model.components import EmClass, EmField, EmGroup
@ -21,13 +24,21 @@ class EditorialModel(object):
self.__groups = dict() self.__groups = dict()
##@brief Stores all classes indexed by id ##@brief Stores all classes indexed by id
self.__classes = dict() self.__classes = dict()
## @brief Stores all activated groups indexed by id
self.__active_groups = dict()
## @brief Stores all activated classes indexed by id
self.__active_classes = dict()
##@brief EmClass accessor ##@brief EmClass accessor
# @param uid None | str : give this argument to get a specific EmClass #@param uid None | str : give this argument to get a specific EmClass
# @return if uid is given returns an EmClass else returns an EmClass iterator #@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: try:
return self.__elt_getter(self.__classes, uid) return self.__elt_getter( self.__active_classes,
uid)
except KeyError: except KeyError:
raise EditorialModelException("EmClass not found : '%s'" % uid) raise EditorialModelException("EmClass not found : '%s'" % uid)
@ -36,10 +47,34 @@ class EditorialModel(object):
# @return if uid is given returns an EmGroup else returns an EmGroup iterator # @return if uid is given returns an EmGroup else returns an EmGroup iterator
def groups(self, uid = None): def groups(self, uid = None):
try: try:
return self.__elt_getter(self.__groups, uid) return self.__elt_getter( self.__active_groups,
uid)
except KeyError: except KeyError:
raise EditorialModelException("EmGroup not found : '%s'" % uid) raise EditorialModelException("EmGroup not found : '%s'" % uid)
##@brief Private getter for __groups or __classes
# @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
def __set_actives(self):
if Settings.editorialmodel.editormode:
# all groups & classes actives because we are in editor mode
self.__active_groups = self.__groups
self.__active_classes = self.__classes
else:
#determine groups first
self.__active_groups = dict()
for agrp in Settings.editorialmodel.groups:
if agrp not in self.__groups:
raise SettingsError('Invalid group found in settings : %s' % agrp)
grp = self.__groups[agrp]
self.__active_groups[grp.uid] = grp
for acls in grp.components():
self.__active_classes[acls.uid] = acls
##@brief EmField getter ##@brief EmField getter
# @param uid str : An EmField uid represented by "CLASSUID.FIELDUID" # @param uid str : An EmField uid represented by "CLASSUID.FIELDUID"
# @return Fals or an EmField instance # @return Fals or an EmField instance
@ -65,6 +100,7 @@ class EditorialModel(object):
# @param emclass EmClass : the EmClass instance to add # @param emclass EmClass : the EmClass instance to add
# @return emclass # @return emclass
def add_class(self, emclass): def add_class(self, emclass):
self.raise_if_ro()
if not isinstance(emclass, EmClass): if not isinstance(emclass, EmClass):
raise ValueError("<class EmClass> expected but got %s " % type(emclass)) raise ValueError("<class EmClass> expected but got %s " % type(emclass))
if emclass.uid in self.classes(): if emclass.uid in self.classes():
@ -76,6 +112,7 @@ class EditorialModel(object):
# @param emgroup EmGroup : the EmGroup instance to add # @param emgroup EmGroup : the EmGroup instance to add
# @return emgroup # @return emgroup
def add_group(self, emgroup): def add_group(self, emgroup):
self.raise_if_ro()
if not isinstance(emgroup, EmGroup): if not isinstance(emgroup, EmGroup):
raise ValueError("<class EmGroup> expected but got %s" % type(emgroup)) raise ValueError("<class EmGroup> expected but got %s" % type(emgroup))
if emgroup.uid in self.groups(): if emgroup.uid in self.groups():
@ -84,25 +121,36 @@ class EditorialModel(object):
return emgroup return emgroup
##@brief Add a new EmClass to the editorial model ##@brief Add a new EmClass to the editorial model
# @param uid str : EmClass uid #@param uid str : EmClass uid
# @param **kwargs : EmClass constructor options ( see @ref lodel.editorial_model.component.EmClass.__init__() ) #@param **kwargs : EmClass constructor options (
# see @ref lodel.editorial_model.component.EmClass.__init__() )
def new_class(self, uid, **kwargs): def new_class(self, uid, **kwargs):
self.raise_if_ro()
return self.add_class(EmClass(uid, **kwargs)) return self.add_class(EmClass(uid, **kwargs))
##@brief Add a new EmGroup to the editorial model ##@brief Add a new EmGroup to the editorial model
# @param uid str : EmGroup uid #@param uid str : EmGroup uid
# @param *kwargs : EmGroup constructor keywords arguments (see @ref lodel.editorial_model.component.EmGroup.__init__() ) #@param *kwargs : EmGroup constructor keywords arguments (
# see @ref lodel.editorial_model.component.EmGroup.__init__() )
def new_group(self, uid, **kwargs): def new_group(self, uid, **kwargs):
self.raise_if_ro()
return self.add_group(EmGroup(uid, **kwargs)) return self.add_group(EmGroup(uid, **kwargs))
# @brief Save a model ##@brief Save a model
# @param translator module : The translator module to use # @param translator module : The translator module to use
# @param **translator_args # @param **translator_args
def save(self, translator, **translator_kwargs): def save(self, translator, **translator_kwargs):
self.raise_if_ro()
if isinstance(translator, str): if isinstance(translator, str):
translator = self.translator_from_name(translator) translator = self.translator_from_name(translator)
return translator.save(self, **translator_kwargs) return translator.save(self, **translator_kwargs)
##@brief Raise an error if lodel is not in EM edition mode
@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")
##@brief Load a model ##@brief Load a model
# @param translator module : The translator module to use # @param translator module : The translator module to use
# @param **translator_args # @param **translator_args
@ -125,12 +173,6 @@ class EditorialModel(object):
raise NameError("No translator named %s") raise NameError("No translator named %s")
return mod return mod
##@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 Lodel hash ##@brief Lodel hash
def d_hash(self): def d_hash(self):
payload = "%s%s" % ( payload = "%s%s" % (

View file

@ -17,9 +17,9 @@ class FieldValidationError(Exception):
##@brief Base class for all data handlers ##@brief Base class for all data handlers
class DataHandler(object): class DataHandler(object):
__HANDLERS_MODULES = ('datas_base', 'datas', 'references') _HANDLERS_MODULES = ('datas_base', 'datas', 'references')
##@brief Stores the DataHandler childs classes indexed by name ##@brief Stores the DataHandler childs classes indexed by name
__base_handlers = None _base_handlers = None
##@brief Stores custom datahandlers classes indexed by name ##@brief Stores custom datahandlers classes indexed by name
# @todo do it ! (like plugins, register handlers... blablabla) # @todo do it ! (like plugins, register handlers... blablabla)
__custom_handlers = dict() __custom_handlers = dict()
@ -132,15 +132,15 @@ class DataHandler(object):
@classmethod @classmethod
def load_base_handlers(cls): def load_base_handlers(cls):
if cls.__base_handlers is None: if cls._base_handlers is None:
cls.__base_handlers = dict() cls._base_handlers = dict()
for module_name in cls.__HANDLERS_MODULES: for module_name in cls._HANDLERS_MODULES:
module = importlib.import_module('lodel.leapi.datahandlers.%s' % module_name) module = importlib.import_module('lodel.leapi.datahandlers.%s' % module_name)
for name, obj in inspect.getmembers(module): for name, obj in inspect.getmembers(module):
if inspect.isclass(obj): if inspect.isclass(obj):
logger.debug("Load data handler %s.%s" % (obj.__module__, obj.__name__)) logger.debug("Load data handler %s.%s" % (obj.__module__, obj.__name__))
cls.__base_handlers[name.lower()] = obj cls._base_handlers[name.lower()] = obj
return copy.copy(cls.__base_handlers) return copy.copy(cls._base_handlers)
##@brief given a field type name, returns the associated python class ##@brief given a field type name, returns the associated python class
# @param fieldtype_name str : A field type name (not case sensitive) # @param fieldtype_name str : A field type name (not case sensitive)
@ -149,10 +149,11 @@ class DataHandler(object):
# @note To access custom data handlers it can be cool to preffix the handler name by plugin name for example ? (to ensure name unicity) # @note To access custom data handlers it can be cool to preffix the handler name by plugin name for example ? (to ensure name unicity)
@classmethod @classmethod
def from_name(cls, name): def from_name(cls, name):
cls.load_base_handlers()
name = name.lower() name = name.lower()
if name not in cls.__base_handlers: if name not in cls._base_handlers:
raise NameError("No data handlers named '%s'" % (name,)) raise NameError("No data handlers named '%s'" % (name,))
return cls.__base_handlers[name] return cls._base_handlers[name]
##@brief Return the module name to import in order to use the datahandler ##@brief Return the module name to import in order to use the datahandler
# @param data_handler_name str : Data handler name # @param data_handler_name str : Data handler name

View file

@ -34,14 +34,5 @@
# </pre> # </pre>
# #
from lodel.settings.settings import SettingsRO as Settings
import warnings
from lodel.settings.settings import Settings as SettingsHandler
settings = SettingsHandler.instance
if settings is None:
Settings = None
else:
Settings = settings.confs.lodel2

View file

@ -23,6 +23,12 @@ PYTHON_SYS_LIB_PATH = '/usr/local/lib/python{major}.{minor}/'.format(
major = sys.version_info.major, major = sys.version_info.major,
minor = sys.version_info.minor) minor = sys.version_info.minor)
class MetaSettings(type):
@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
@ -61,43 +67,57 @@ PYTHON_SYS_LIB_PATH = '/usr/local/lib/python{major}.{minor}/'.format(
# '.*') # '.*')
# @todo delete the first stage, the lib path HAVE TO BE HARDCODED. In fact # @todo delete the first stage, the lib path HAVE TO BE HARDCODED. In fact
#when we will run lodel in production the lodel2 lib will be in the python path #when we will run lodel in production the lodel2 lib will be in the python path
class Settings(object): class Settings(object, metaclass=MetaSettings):
##@brief global conf specsification (default_value + validator) ## @brief Stores the singleton instance
_conf_preload = {
'lib_path': ( PYTHON_SYS_LIB_PATH+'/lodel2/',
SettingValidator('directory')),
'plugins_path': ( PYTHON_SYS_LIB_PATH+'lodel2/plugins/',
SettingValidator('directory_list')),
}
instance = None instance = None
##@brief Should be called only by the boostrap classmethod ## @brief Instanciate the Settings singleton
# @param conf_file str : Path to the global lodel2 configuration file # @param conf_dir str : The configuration directory
# @param conf_dir str : Path to the conf directory def __init__(self, conf_dir):
def __init__(self, conf_file, conf_dir): self.singleton_assert() # check that it is the only instance
self.__confs = dict() Settings.instance = self
## @brief Configuration specification
#
# Initialized by Settings.__bootstrap() method
self.__conf_specs = None
## @brief Stores the configurations in namedtuple tree
self.__confs = None
self.__conf_dir = conf_dir self.__conf_dir = conf_dir
self.__load_bootstrap_conf(conf_file)
# now we should have the self.__confs['lodel2']['plugins_paths']
# and self.__confs['lodel2']['lib_path'] set
self.__bootstrap() self.__bootstrap()
##@brief Stores as class attribute a Settings instance ## @brief Get the named tuple representing configuration
@classmethod
def bootstrap(cls, conf_file = None, conf_dir = None):
if cls.instance is None:
if conf_file is None and conf_dir is None:
warnings.warn("Lodel instance without settings !!!")
else:
cls.instance = cls(conf_file, conf_dir)
return cls.instance
##@brief Configuration keys accessor
# @return All confs organised into named tuples
@property @property
def confs(self): def settings(self):
return copy.copy(self.__confs) return self.__confs.lodel2
## @brief Delete the singleton instance
@classmethod
def stop(cls):
del(cls.instance)
cls.instance = None
@classmethod
def started(cls):
return cls.instance is not None
##@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
# @throw RuntimeError
@classmethod
def singleton_assert(cls, expect_instanciated = False):
if expect_instanciated:
if not cls.started():
raise RuntimeError("The Settings class is not started yet")
else:
if cls.started():
raise RuntimeError("The Settings class is allready started")
@classmethod
def set(cls, confname, confvalue):
pass
##@brief This method handlers Settings instance bootstraping ##@brief This method handlers Settings instance bootstraping
def __bootstrap(self): def __bootstrap(self):
@ -109,15 +129,24 @@ class Settings(object):
if kname.lower() != kname: if kname.lower() != kname:
raise SettingsError("Only lower case are allowed in section name (thank's ConfigParser...)") raise SettingsError("Only lower case are allowed in section name (thank's ConfigParser...)")
# Load specs for the plugins list and plugins_path list conf keys
plugins_opt_specs = lodel2_specs['lodel2']['plugins'] plugins_opt_specs = lodel2_specs['lodel2']['plugins']
plugins_path_opt_specs = lodel2_specs['lodel2']['plugins_path']
# Init the settings loader # Init the settings loader
loader = SettingsLoader(self.__conf_dir) loader = SettingsLoader(self.__conf_dir)
# fetching list of plugins to load # fetching list of plugins to load
plugins_list = loader.getoption('lodel2', 'plugins', plugins_opt_specs[1], plugins_opt_specs[0], False) plugins_list = loader.getoption( 'lodel2',
'plugins',
plugins_opt_specs[1],
plugins_opt_specs[0],
False)
plugins_path = loader.getoption( 'lodel2',
'plugins_path',
plugins_path_opt_specs[1],
plugins_path_opt_specs[0],
False)
# Starting the Plugins class # Starting the Plugins class
Plugins.bootstrap(self.__confs['lodel2']['plugins_path']) Plugins.bootstrap(plugins_path)
# Fetching conf specs from plugins # Fetching conf specs from plugins
specs = [lodel2_specs] specs = [lodel2_specs]
errors = list() errors = list()
@ -128,8 +157,8 @@ class Settings(object):
errors.append(e) errors.append(e)
if len(errors) > 0: #Raise all plugins import errors if len(errors) > 0: #Raise all plugins import errors
raise SettingsErrors(errors) raise SettingsErrors(errors)
specs = self.__merge_specs(specs) self.__conf_specs = self.__merge_specs(specs)
self.__populate_from_specs(specs, loader) self.__populate_from_specs(self.__conf_specs, loader)
##@brief Produce a configuration specification dict by merging all specifications ##@brief Produce a configuration specification dict by merging all specifications
# #
@ -159,6 +188,7 @@ class Settings(object):
# @param specs dict : Settings specification dictionnary as returned by __merge_specs # @param specs dict : Settings specification dictionnary as returned by __merge_specs
# @param loader SettingsLoader : A SettingsLoader instance # @param loader SettingsLoader : A SettingsLoader instance
def __populate_from_specs(self, specs, loader): def __populate_from_specs(self, specs, loader):
self.__confs = dict()
specs = copy.copy(specs) #Avoid destroying original specs dict (may be useless) specs = copy.copy(specs) #Avoid destroying original specs dict (may be useless)
# Construct final specs dict replacing variable sections # Construct final specs dict replacing variable sections
# by the actual existing sections # by the actual existing sections
@ -242,39 +272,11 @@ class Settings(object):
ResNamedTuple = namedtuple(name, conftree.keys()) ResNamedTuple = namedtuple(name, conftree.keys())
return ResNamedTuple(**conftree) return ResNamedTuple(**conftree)
##@brief Load base global configurations keys class MetaSettingsRO(type):
# def __getattr__(self, name):
# Base configurations keys are : return getattr(Settings.s, name)
# - lodel2 lib path
# - lodel2 plugins path
#
# @note return nothing but set the __confs attribute
# @see Settings._conf_preload
def __load_bootstrap_conf(self, conf_file):
config = configparser.ConfigParser()
config.read(conf_file)
sections = config.sections()
if len(sections) != 1 or sections[0].lower() != 'lodel2':
raise SettingsError("Global conf error, expected lodel2 section not found")
#Load default values in result
res = dict()
for keyname, (default, _) in self._conf_preload.items():
res[keyname] = default
confs = config[sections[0]]
errors = []
for name in confs:
if name not in res:
errors.append( SettingsError(
"Unknow field",
"lodel2.%s" % name,
conf_file))
try:
res[name] = self._conf_preload[name][1](confs[name])
except Exception as e:
errors.append(SettingsError(str(e), name, conf_file))
if len(errors) > 0:
raise SettingsErrors(errors)
self.__confs['lodel2'] = res
## @brief A class that provide . notation read only access to configurations
class SettingsRO(object, metaclass=MetaSettingsRO):
pass

View file

@ -58,7 +58,10 @@ class SettingsLoader(object):
if keyname in sec: if keyname in sec:
optionstr=sec[keyname] optionstr=sec[keyname]
option=validator(sec[keyname]) option=validator(sec[keyname])
try:
del self.__conf_sv[section + ':' + keyname] del self.__conf_sv[section + ':' + keyname]
except KeyError: #allready fetched
pass
return option return option
elif mandatory: elif mandatory:
raise SettingsError("Default value mandatory for option %s" % keyname) raise SettingsError("Default value mandatory for option %s" % keyname)

View file

@ -148,6 +148,11 @@ def loglevel_val(value):
raise SettingsValidationError("The value '%s' is not a valid loglevel") raise SettingsValidationError("The value '%s' is not a valid loglevel")
return value.upper() return value.upper()
def path_val(value):
if not os.path.exists(value):
raise SettingsValidationError("The value '%s' is not a valid path")
return value
# #
# Default validators registration # Default validators registration
# #
@ -182,6 +187,11 @@ SettingValidator.register_validator(
loglevel_val, loglevel_val,
'Loglevel validator') 'Loglevel validator')
SettingValidator.register_validator(
'path',
path_val,
'path validator')
SettingValidator.create_list_validator( SettingValidator.create_list_validator(
'list', 'list',
SettingValidator('strip'), SettingValidator('strip'),
@ -208,10 +218,14 @@ LODEL2_CONF_SPECS = {
'lodel2': { 'lodel2': {
'debug': ( True, 'debug': ( True,
SettingValidator('bool')), SettingValidator('bool')),
'plugins_path': ( None,
SettingValidator('list')),
'plugins': ( "", 'plugins': ( "",
SettingValidator('list')), SettingValidator('list')),
'sitename': ( 'noname', 'sitename': ( 'noname',
SettingValidator('strip')), SettingValidator('strip')),
'lib_path': ( None,
SettingValidator('path')),
}, },
'lodel2.logging.*' : { 'lodel2.logging.*' : {
'level': ( 'ERROR', 'level': ( 'ERROR',

View file

@ -4,9 +4,7 @@
# This file should be imported in every tests files # This file should be imported in every tests files
# #
import imp
import lodel.settings
from lodel.settings.settings import Settings from lodel.settings.settings import Settings
Settings.bootstrap(conf_file = 'settings_local.ini', conf_dir = 'globconf.d')
imp.reload(lodel.settings) if not Settings.started():
Settings('globconf.d')

View file

@ -1,3 +1,5 @@
[lodel2.foo.bar] [lodel2.foo.bar]
foo = 42 foo = 42
foobar = hello world foobar = hello world
foo_bar = foobar
foo.bar = barfoo

View file

@ -3,6 +3,7 @@
import unittest import unittest
from unittest import mock from unittest import mock
"""
import tests.loader_utils import tests.loader_utils
from lodel.settings.settings import Settings from lodel.settings.settings import Settings
@ -12,3 +13,4 @@ class SettingsTestCase(unittest.TestCase):
def test_init(self): def test_init(self):
settings = Settings('tests/settings/settings_tests.ini', 'tests/settings/settings_tests_conf.d') settings = Settings('tests/settings/settings_tests.ini', 'tests/settings/settings_tests_conf.d')
pass pass
"""

View file

@ -58,6 +58,29 @@ class SettingsLoaderTestCase(unittest.TestCase):
self.assertEqual(value, "42") self.assertEqual(value, "42")
value = loader.getoption('lodel2.foo.bar', 'foobar', dummy_validator) value = loader.getoption('lodel2.foo.bar', 'foobar', dummy_validator)
self.assertEqual(value, "hello world") self.assertEqual(value, "hello world")
value = loader.getoption('lodel2.foo.bar', 'foo_bar', dummy_validator)
self.assertEqual(value, "foobar")
value = loader.getoption('lodel2.foo.bar', 'foo.bar', dummy_validator)
self.assertEqual(value, "barfoo")
def test_getoption_multiple_time(self):
""" Testing the behavior when doing 2 time the same call to getoption """
loader = SettingsLoader('tests/settings/settings_examples/simple.conf.d')
value = loader.getoption('lodel2.foo.bar', 'foo', dummy_validator)
value = loader.getoption('lodel2.foo.bar', 'foo', dummy_validator)
value = loader.getoption('lodel2.foo.bar', 'foo', dummy_validator)
value = loader.getoption('lodel2.foo.bar', 'foo', dummy_validator)
def test_geoption_default_value(self):
""" Testing behavior of default value in getoption """
loader = SettingsLoader('tests/settings/settings_examples/simple.conf.d')
# for non existing keys in file
value = loader.getoption('lodel2.foo.bar', 'foofoofoo', dummy_validator, 'hello 42', False)
self.assertEqual(value, 'hello 42')
# for non existing section in file
value = loader.getoption('lodel2.foofoo', 'foofoofoo', dummy_validator, 'hello 42', False)
self.assertEqual(value, 'hello 42')
def test_getoption_complex(self): def test_getoption_complex(self):
""" Testing behavior of getoption with less simple files & confs """ """ Testing behavior of getoption with less simple files & confs """