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
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.components import EmClass, EmField, EmGroup
@ -21,13 +24,21 @@ class EditorialModel(object):
self.__groups = dict()
##@brief Stores all classes indexed by id
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
# @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
#@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):
try:
return self.__elt_getter(self.__classes, uid)
return self.__elt_getter( self.__active_classes,
uid)
except KeyError:
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
def groups(self, uid = None):
try:
return self.__elt_getter(self.__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
# @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
# @param uid str : An EmField uid represented by "CLASSUID.FIELDUID"
# @return Fals or an EmField instance
@ -65,6 +100,7 @@ class EditorialModel(object):
# @param emclass EmClass : the EmClass instance to add
# @return emclass
def add_class(self, emclass):
self.raise_if_ro()
if not isinstance(emclass, EmClass):
raise ValueError("<class EmClass> expected but got %s " % type(emclass))
if emclass.uid in self.classes():
@ -76,6 +112,7 @@ class EditorialModel(object):
# @param emgroup EmGroup : the EmGroup instance to add
# @return emgroup
def add_group(self, emgroup):
self.raise_if_ro()
if not isinstance(emgroup, EmGroup):
raise ValueError("<class EmGroup> expected but got %s" % type(emgroup))
if emgroup.uid in self.groups():
@ -84,25 +121,36 @@ class EditorialModel(object):
return emgroup
##@brief Add a new EmClass to the editorial model
# @param uid str : EmClass uid
# @param **kwargs : EmClass constructor options ( see @ref lodel.editorial_model.component.EmClass.__init__() )
#@param uid str : EmClass uid
#@param **kwargs : EmClass constructor options (
# see @ref lodel.editorial_model.component.EmClass.__init__() )
def new_class(self, uid, **kwargs):
self.raise_if_ro()
return self.add_class(EmClass(uid, **kwargs))
##@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__() )
#@param uid str : EmGroup uid
#@param *kwargs : EmGroup constructor keywords arguments (
# see @ref lodel.editorial_model.component.EmGroup.__init__() )
def new_group(self, uid, **kwargs):
self.raise_if_ro()
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):
self.raise_if_ro()
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
@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
# @param translator module : The translator module to use
# @param **translator_args
@ -125,12 +173,6 @@ class EditorialModel(object):
raise NameError("No translator named %s")
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
def d_hash(self):
payload = "%s%s" % (

View file

@ -17,9 +17,9 @@ class FieldValidationError(Exception):
##@brief Base class for all data handlers
class DataHandler(object):
__HANDLERS_MODULES = ('datas_base', 'datas', 'references')
_HANDLERS_MODULES = ('datas_base', 'datas', 'references')
##@brief Stores the DataHandler childs classes indexed by name
__base_handlers = None
_base_handlers = None
##@brief Stores custom datahandlers classes indexed by name
# @todo do it ! (like plugins, register handlers... blablabla)
__custom_handlers = dict()
@ -132,15 +132,15 @@ class DataHandler(object):
@classmethod
def load_base_handlers(cls):
if cls.__base_handlers is None:
cls.__base_handlers = dict()
for module_name in cls.__HANDLERS_MODULES:
if cls._base_handlers is None:
cls._base_handlers = dict()
for module_name in cls._HANDLERS_MODULES:
module = importlib.import_module('lodel.leapi.datahandlers.%s' % module_name)
for name, obj in inspect.getmembers(module):
if inspect.isclass(obj):
logger.debug("Load data handler %s.%s" % (obj.__module__, obj.__name__))
cls.__base_handlers[name.lower()] = obj
return copy.copy(cls.__base_handlers)
cls._base_handlers[name.lower()] = obj
return copy.copy(cls._base_handlers)
##@brief given a field type name, returns the associated python class
# @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)
@classmethod
def from_name(cls, name):
cls.load_base_handlers()
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,))
return cls.__base_handlers[name]
return cls._base_handlers[name]
##@brief Return the module name to import in order to use the datahandler
# @param data_handler_name str : Data handler name

View file

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

View file

@ -23,6 +23,12 @@ PYTHON_SYS_LIB_PATH = '/usr/local/lib/python{major}.{minor}/'.format(
major = sys.version_info.major,
minor = sys.version_info.minor)
class MetaSettings(type):
@property
def s(self):
self.singleton_assert(True)
return self.instance.settings
##@brief Handles configuration load etc.
#
# 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
#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)
_conf_preload = {
'lib_path': ( PYTHON_SYS_LIB_PATH+'/lodel2/',
SettingValidator('directory')),
'plugins_path': ( PYTHON_SYS_LIB_PATH+'lodel2/plugins/',
SettingValidator('directory_list')),
}
## @brief Stores the singleton instance
instance = None
##@brief Should be called only by the boostrap classmethod
# @param conf_file str : Path to the global lodel2 configuration file
# @param conf_dir str : Path to the conf directory
def __init__(self, conf_file, conf_dir):
self.__confs = dict()
## @brief Instanciate the Settings singleton
# @param conf_dir str : The configuration directory
def __init__(self, conf_dir):
self.singleton_assert() # check that it is the only instance
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.__load_bootstrap_conf(conf_file)
# now we should have the self.__confs['lodel2']['plugins_paths']
# and self.__confs['lodel2']['lib_path'] set
self.__bootstrap()
##@brief Stores as class attribute a Settings instance
@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
## @brief Get the named tuple representing configuration
@property
def confs(self):
return copy.copy(self.__confs)
def settings(self):
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
def __bootstrap(self):
@ -109,15 +129,24 @@ class Settings(object):
if kname.lower() != kname:
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_path_opt_specs = lodel2_specs['lodel2']['plugins_path']
# Init the settings loader
loader = SettingsLoader(self.__conf_dir)
# 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
Plugins.bootstrap(self.__confs['lodel2']['plugins_path'])
Plugins.bootstrap(plugins_path)
# Fetching conf specs from plugins
specs = [lodel2_specs]
errors = list()
@ -128,8 +157,8 @@ class Settings(object):
errors.append(e)
if len(errors) > 0: #Raise all plugins import errors
raise SettingsErrors(errors)
specs = self.__merge_specs(specs)
self.__populate_from_specs(specs, loader)
self.__conf_specs = self.__merge_specs(specs)
self.__populate_from_specs(self.__conf_specs, loader)
##@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 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)
# Construct final specs dict replacing variable sections
# by the actual existing sections
@ -242,39 +272,11 @@ class Settings(object):
ResNamedTuple = namedtuple(name, conftree.keys())
return ResNamedTuple(**conftree)
##@brief Load base global configurations keys
#
# Base configurations keys are :
# - 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")
class MetaSettingsRO(type):
def __getattr__(self, name):
return getattr(Settings.s, name)
#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:
optionstr=sec[keyname]
option=validator(sec[keyname])
try:
del self.__conf_sv[section + ':' + keyname]
except KeyError: #allready fetched
pass
return option
elif mandatory:
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")
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
#
@ -182,6 +187,11 @@ SettingValidator.register_validator(
loglevel_val,
'Loglevel validator')
SettingValidator.register_validator(
'path',
path_val,
'path validator')
SettingValidator.create_list_validator(
'list',
SettingValidator('strip'),
@ -208,10 +218,14 @@ LODEL2_CONF_SPECS = {
'lodel2': {
'debug': ( True,
SettingValidator('bool')),
'plugins_path': ( None,
SettingValidator('list')),
'plugins': ( "",
SettingValidator('list')),
'sitename': ( 'noname',
SettingValidator('strip')),
'lib_path': ( None,
SettingValidator('path')),
},
'lodel2.logging.*' : {
'level': ( 'ERROR',

View file

@ -4,9 +4,7 @@
# This file should be imported in every tests files
#
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)
if not Settings.started():
Settings('globconf.d')

View file

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

View file

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

View file

@ -58,6 +58,29 @@ class SettingsLoaderTestCase(unittest.TestCase):
self.assertEqual(value, "42")
value = loader.getoption('lodel2.foo.bar', 'foobar', dummy_validator)
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):
""" Testing behavior of getoption with less simple files & confs """