1
0
Fork 0
mirror of https://github.com/yweber/lodel2.git synced 2026-01-14 10:42:14 +01:00

[1 test fail] Updated the settings to prepare them to be writable + more tests

This commit is contained in:
Yann 2016-04-21 09:39:37 +02:00
commit d30f3e189f
11 changed files with 135 additions and 104 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

@ -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()
@ -131,15 +131,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)
@ -148,10 +148,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):
##@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')),
}
class Settings(object, metaclass=MetaSettings):
## @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):
@ -108,16 +128,25 @@ class Settings(object):
for kname in lodel2_specs[section]:
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])
del self.__conf_sv[section + ':' + 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 """