From 76e386e3211de0fd3cae3704041ff3d1fb1d6bdb Mon Sep 17 00:00:00 2001 From: Yann Date: Fri, 19 Aug 2016 16:21:29 +0200 Subject: [PATCH] Implements SessionHandlerPlugin ExtensionsPlugin + settings preload Implements both Plugin child classes handling extensions and session handling. Modified the settings plugin preload piece of code. It is now adapted to the new plugins organisation. Each Plugin child classes provides a confspec indicating where the plugin list is stored in configuration. --- lodel/plugin/__init__.py | 2 + lodel/plugin/datasource_plugin.py | 8 +-- lodel/plugin/extensions.py | 12 +++++ lodel/plugin/plugins.py | 74 ++++++++------------------ lodel/plugin/scripts.py | 1 - lodel/plugin/sessionhandler.py | 28 ++++++++++ lodel/settings/settings.py | 43 ++++++++------- lodel/settings/settings_loader.py | 1 - lodel/settings/validator.py | 15 ++---- plugins/filesystem_session/__init__.py | 2 +- plugins/filesystem_session/confspec.py | 4 +- plugins/filesystem_session/main.py | 2 + tests/settings/test_settings_loader.py | 3 +- tests/tests_conf.d/lodel2.ini | 3 ++ 14 files changed, 104 insertions(+), 94 deletions(-) create mode 100644 lodel/plugin/extensions.py create mode 100644 lodel/plugin/sessionhandler.py diff --git a/lodel/plugin/__init__.py b/lodel/plugin/__init__.py index 74030b9..b67330f 100644 --- a/lodel/plugin/__init__.py +++ b/lodel/plugin/__init__.py @@ -42,3 +42,5 @@ from .hooks import LodelHook from .plugins import Plugin, CustomMethod from .datasource_plugin import DatasourcePlugin +from .sessionhandler import SessionHandlerPlugin +from .extensions import Extension diff --git a/lodel/plugin/datasource_plugin.py b/lodel/plugin/datasource_plugin.py index 97eb8e2..93d2b83 100644 --- a/lodel/plugin/datasource_plugin.py +++ b/lodel/plugin/datasource_plugin.py @@ -14,9 +14,9 @@ class DatasourcePlugin(Plugin): ##@brief Stores confspecs indicating where DatasourcePlugin list is stored _plist_confspecs = { 'section': 'lodel2', - 'key': 'datasources', + 'key': 'datasource_connectors', 'default': None, - 'validator': SettingValidator('list', none_is_valid = False) } + 'validator': SettingValidator('strip', none_is_valid = False) } def __init__(self, name): super().__init__(name) @@ -30,10 +30,6 @@ class DatasourcePlugin(Plugin): def migration_handler(self): return self.loader_module().migration_handler_class() - @classmethod - def plist_confspec(cls): - return copy.copy(cls._plist_confspecs) - ##@brief Return an initialized Datasource instance #@param ds_name str : The name of the datasource to instanciate #@param ro bool diff --git a/lodel/plugin/extensions.py b/lodel/plugin/extensions.py new file mode 100644 index 0000000..a354df0 --- /dev/null +++ b/lodel/plugin/extensions.py @@ -0,0 +1,12 @@ +from .plugins import Plugin +from .exceptions import * +from lodel.settings.validator import SettingValidator + +class Extension(Plugin): + + _plist_confspecs = { + 'section': 'lodel2', + 'key': 'extensions', + 'default': [], + 'validator': SettingValidator('list', none_is_valid = False)} + diff --git a/lodel/plugin/plugins.py b/lodel/plugin/plugins.py index 882fc44..cb78b1e 100644 --- a/lodel/plugin/plugins.py +++ b/lodel/plugin/plugins.py @@ -11,6 +11,7 @@ import plugins from lodel import logger from lodel.settings.utils import SettingsError from .exceptions import * +from lodel.exceptions import * ## @package lodel.plugins Lodel2 plugins management # @@ -161,7 +162,6 @@ class MetaPlugType(type): #Here we can store all child classes of Plugin super().__init__(name, bases, attrs) if len(bases) == 1 and bases[0] == object: - print("Dropped : ", name, bases) return self.__register_types() #list_name= [cls.__name__ for cls in __all_ptypes] @@ -177,6 +177,9 @@ def plug_type_register(cls): __all_ptypes.append(cls) logger.info("New child class registered : %s" % cls.__name__) +def all_types(): + return copy.copy(__all_ptypes) + ##@brief Handle plugins # @@ -204,6 +207,10 @@ class Plugin(object, metaclass=MetaPlugType): ##@brief Store dict representation of discover cache content _discover_cache = None + + #@brief Designed to store, in child classes, the confspec indicating \ + #where plugin list is stored + _plist_confspecs = None ##@brief Plugin class constructor # @@ -213,7 +220,7 @@ class Plugin(object, metaclass=MetaPlugType): # @param plugin_name str : plugin name # @throw PluginError def __init__(self, plugin_name): - self.started() + self.name = plugin_name self.path = self.plugin_path(plugin_name) @@ -272,9 +279,9 @@ class Plugin(object, metaclass=MetaPlugType): #PLUGIN_VERSION_VARNAME in init file is mandatory self.__version = getattr(self.module, PLUGIN_VERSION_VARNAME) except AttributeError: - msg = "Error that should not append : no %s found in plugin \ -init file. Malformed plugin" - msg %= PLUGIN_VERSION_VARNAME + msg = "Error that should not append while loading plugin '%s': no \ +%s found in plugin init file. Malformed plugin" + msg %= (plugin_name, PLUGIN_VERSION_VARNAME) raise LodelFatalError(msg) # Load plugin type @@ -459,6 +466,13 @@ name differ from the one found in plugin's init file" def confspecs(self): return copy.copy(self.__confspecs) + @classmethod + def plist_confspecs(cls): + if cls._plist_confspecs is None: + raise LodelFatalError('Unitialized _plist_confspecs attribute for \ +%s' % cls.__name__) + return copy.copy(cls._plist_confspecs) + ##@brief Retrieves plugin list confspecs # #This method ask for each Plugin child class the confspecs specifying where @@ -525,7 +539,6 @@ file : '%s'. Running discover again..." % DISCOVER_CACHE_FILENAME) pcls = DatasourcePlugin else: pcls = cls - print(plugin_name, ptype, pcls) plugin = pcls(plugin_name) cls._plugin_instances[plugin_name] = plugin logger.debug("Plugin %s available." % plugin) @@ -550,7 +563,7 @@ file : '%s'. Running discover again..." % DISCOVER_CACHE_FILENAME) # @return the plugin directory path @classmethod def plugin_path(cls, plugin_name): - cls.started() + plist = cls.plugin_list() if plugin_name not in plist: raise PluginError("No plugin named '%s' found" % plugin_name) @@ -572,25 +585,10 @@ file : '%s'. Running discover again..." % DISCOVER_CACHE_FILENAME) # # This method load path and preload plugins @classmethod - def start(cls, plugins_directories, plugins): - if cls._plugin_directories is not None: - return - import inspect - self_path = inspect.getsourcefile(Plugin) - default_plugin_path = os.path.abspath(self_path + '../../../../plugins') - if plugins_directories is None: - plugins_directories = list() - plugins_directories += [ default_plugin_path ] - cls._plugin_directories = list(set(plugins_directories)) + def start(cls, plugins): for plugin_name in plugins: cls.register(plugin_name) - @classmethod - def started(cls, raise_if_not = True): - res = cls._plugin_directories is not None - if raise_if_not and not res: - raise RuntimeError("Class Plugins is not initialized") - @classmethod def clear(cls): if cls._plugin_directories is not None: @@ -657,7 +655,7 @@ file : '%s'. Running discover again..." % DISCOVER_CACHE_FILENAME) ##@brief Return a list of child Class Plugin @classmethod def plugin_types(cls): - return cls.__all_ptypes + return all_types() ##@brief Attempt to open and load plugin discover cache #@return discover cache @@ -880,31 +878,3 @@ with %s" % (custom_method._method_name, custom_method)) custom_method.__get_method()) logger.debug( "Custom method %s added to target" % custom_method) - - -##@page lodel2_plugins Lodel2 plugins system -# -# @par Plugin structure -#A plugin is a package (a folder containing, at least, an __init__.py file. -#This file should expose multiple things : -# - a CONFSPEC variable containing configuration specifications -# - an _activate() method that returns True if the plugin can be activated ( -# optionnal) -# - - -class SessionHandler(Plugin): - __instance = None - - def __init__(self, plugin_name): - if self.__instance is None: - super(Plugin, self).__init__(plugin_name) - self.__instance = True - else: - raise RuntimeError("A SessionHandler Plugin is already plug") - -class InterfacePlugin(Plugin): - def __init__(self, plugin_name): - super(Plugin, self).__init__(plugin_name) - - diff --git a/lodel/plugin/scripts.py b/lodel/plugin/scripts.py index eec3f53..6dba005 100644 --- a/lodel/plugin/scripts.py +++ b/lodel/plugin/scripts.py @@ -17,7 +17,6 @@ class MetaLodelScript(type): #Here we can store all child classes of LodelScript super().__init__(name, bases, attrs) if len(bases) == 1 and bases[0] == object: - print("Dropped : ", name, bases) return self.__register_script(name) diff --git a/lodel/plugin/sessionhandler.py b/lodel/plugin/sessionhandler.py new file mode 100644 index 0000000..0c57e9a --- /dev/null +++ b/lodel/plugin/sessionhandler.py @@ -0,0 +1,28 @@ +from .plugins import Plugin +from .exceptions import * +from lodel.settings.validator import SettingValidator + +##@page lodel2_plugins Lodel2 plugins system +# +# @par Plugin structure +#A plugin is a package (a folder containing, at least, an __init__.py file. +#This file should expose multiple things : +# - a CONFSPEC variable containing configuration specifications +# - an _activate() method that returns True if the plugin can be activated ( +# optionnal) +# +class SessionHandlerPlugin(Plugin): + __instance = None + _plist_confspecs = { + 'section': 'lodel2', + 'key': 'session_handler', + 'default': None, + 'validator': SettingValidator('string', none_is_valid=False)} + + def __init__(self, plugin_name): + if self.__instance is None: + super(Plugin, self).__init__(plugin_name) + self.__instance = True + else: + raise RuntimeError("A SessionHandler Plugin is already plug") + diff --git a/lodel/settings/settings.py b/lodel/settings/settings.py index 75e617a..1604374 100644 --- a/lodel/settings/settings.py +++ b/lodel/settings/settings.py @@ -11,7 +11,8 @@ from collections import namedtuple from lodel import logger from lodel.plugin.plugins import Plugin, PluginError from lodel.settings.utils import SettingsError, SettingsErrors -from lodel.settings.validator import SettingValidator, LODEL2_CONF_SPECS +from lodel.settings.validator import SettingValidator, LODEL2_CONF_SPECS, \ + confspec_append from lodel.settings.settings_loader import SettingsLoader ## @package lodel.settings.settings Lodel2 settings module @@ -132,37 +133,39 @@ class Settings(object, metaclass=MetaSettings): def __bootstrap(self): logger.debug("Settings bootstraping") lodel2_specs = LODEL2_CONF_SPECS + loader = SettingsLoader(self.__conf_dir) + plugin_list = [] + for ptype in Plugin.plugin_types(): + pls = ptype.plist_confspecs() + lodel2_specs = confspec_append(lodel2_specs, **pls) + cur_list = loader.getoption( + pls['section'], + pls['key'], + pls['validator'], + pls['default']) + if cur_list is None: + continue + try: + if isinstance(cur_list, str): + cur_list = [cur_list] + plugin_list += cur_list + except TypeError: + plugin_list += [cur_list] + #Checking confspecs for section in lodel2_specs: if section.lower() != section: 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...)") - - # 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_path = loader.getoption( 'lodel2', - 'plugins_path', - plugins_path_opt_specs[1], - plugins_path_opt_specs[0], - False) # Starting the Plugins class logger.debug("Starting lodel.plugin.Plugin class") - Plugin.start(plugins_path, plugins_list) + Plugin.start(plugin_list) # Fetching conf specs from plugins specs = [lodel2_specs] errors = list() - for plugin_name in plugins_list: + for plugin_name in plugin_list: try: specs.append(Plugin.get(plugin_name).confspecs) except PluginError as e: diff --git a/lodel/settings/settings_loader.py b/lodel/settings/settings_loader.py index 6b0c801..3620dc6 100644 --- a/lodel/settings/settings_loader.py +++ b/lodel/settings/settings_loader.py @@ -66,7 +66,6 @@ class SettingsLoader(object): # @return the option def getoption(self,section,keyname,validator,default_value=None,mandatory=False): conf=self.__conf - if section not in conf: conf[section] = dict() diff --git a/lodel/settings/validator.py b/lodel/settings/validator.py index a5142f2..bf3caf5 100644 --- a/lodel/settings/validator.py +++ b/lodel/settings/validator.py @@ -357,17 +357,12 @@ SettingValidator.create_re_validator( #@param orig dict : the confspec to update #@param upd dict : the confspec to add #@return new confspec -def confspec_append(orig, upd): - for section in orig: - if section in upd: - orig[section].update(upd[section]) - else: - orig[section] = upd[section] - return orig - -def confspec_add(orig, section, key, default, validator): +def confspec_append(orig, section, key, validator, default): if section not in orig: - section[orig] = dict() + orig[section] = dict() + if key not in orig[section]: + orig[section][key] = (default, validator) + return orig ##@brief Global specifications for lodel2 settings LODEL2_CONF_SPECS = { diff --git a/plugins/filesystem_session/__init__.py b/plugins/filesystem_session/__init__.py index 3cb2bac..dd64582 100644 --- a/plugins/filesystem_session/__init__.py +++ b/plugins/filesystem_session/__init__.py @@ -1,7 +1,7 @@ from lodel.settings.validator import SettingValidator __plugin_name__ = 'filesystem_session' -__version = [0,0,1] +__version__ = [0,0,1] __type__ = 'session_handler' __loader__ = 'main.py' __confspec__ = "confspec.py" diff --git a/plugins/filesystem_session/confspec.py b/plugins/filesystem_session/confspec.py index 10098b2..a3d5940 100644 --- a/plugins/filesystem_session/confspec.py +++ b/plugins/filesystem_session/confspec.py @@ -4,8 +4,8 @@ from lodel.settings.validator import SettingValidator CONFSPEC = { 'lodel2.sessions':{ - 'directory': ('/tmp/lodel2_session', SettingValidator('path')), + 'directory': ('/tmp/', SettingValidator('path')), 'expiration': (900, SettingValidator('int')), 'file_template': ('lodel2_%s.sess', SettingValidator('dummy')) } -} \ No newline at end of file +} diff --git a/plugins/filesystem_session/main.py b/plugins/filesystem_session/main.py index 6d9a8e9..c08655a 100644 --- a/plugins/filesystem_session/main.py +++ b/plugins/filesystem_session/main.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +""" from lodel.auth.exceptions import AuthenticationError from lodel.plugin import LodelHook @@ -38,3 +39,4 @@ def read_session(caller, sid): def clean_sessions(caller): FileSystemSession.clean() ''' +""" diff --git a/tests/settings/test_settings_loader.py b/tests/settings/test_settings_loader.py index dd20563..980c18f 100644 --- a/tests/settings/test_settings_loader.py +++ b/tests/settings/test_settings_loader.py @@ -4,6 +4,7 @@ import unittest import os.path from lodel.settings.utils import * +from lodel.plugin.exceptions import * from lodel.settings.settings_loader import SettingsLoader @@ -255,5 +256,5 @@ class SettingsLoaderTestCase(unittest.TestCase): def test_invalid_conf(self): from lodel.settings.settings import Settings Settings.stop() - with self.assertRaises(SettingsErrors): + with self.assertRaises((SettingsErrors, PluginError)): Settings('tests/settings/settings_examples/bad_conf.d') diff --git a/tests/tests_conf.d/lodel2.ini b/tests/tests_conf.d/lodel2.ini index 99718ee..84739d0 100644 --- a/tests/tests_conf.d/lodel2.ini +++ b/tests/tests_conf.d/lodel2.ini @@ -4,6 +4,9 @@ sitename = noname plugins_path = /foo/plugins plugins = dummy, dummy_datasource runtest=True +extensions = dummy +datasource_connectors = dummy_datasource +session_handler = filesystem_session [lodel2.logging.stderr] level = Error