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:
commit
c8a6d5f99a
12 changed files with 192 additions and 119 deletions
|
|
@ -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)
|
||||
|
|
@ -1 +1,3 @@
|
|||
__author__ = 'roland'
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
from .utils.starter import init_lodel
|
||||
|
|
|
|||
|
|
@ -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" % (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
[lodel2.foo.bar]
|
||||
foo = 42
|
||||
foobar = hello world
|
||||
foo_bar = foobar
|
||||
foo.bar = barfoo
|
||||
|
|
|
|||
|
|
@ -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
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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 """
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue