From 4627ae17e57422050ec2bfcb3a6ca689668081e6 Mon Sep 17 00:00:00 2001 From: Yann Date: Fri, 19 Aug 2016 09:09:29 +0200 Subject: [PATCH] DatasourcePlugin class implementation - DatasourcePlugin implmentation as child class of lodel.plugin.plugins.Plugin - moved methods about datasource initialisation from LeObject to DatasourcePlugin ( _init_datasource(), plugin_name(), _get_ds_connection_conf() ...) --- lodel/leapi/leobject.py | 100 +------------------------ lodel/plugin/__init__.py | 1 + lodel/plugin/datasource_plugin.py | 119 ++++++++++++++++++++++++++++++ lodel/plugin/exceptions.py | 6 ++ lodel/plugin/plugins.py | 3 +- 5 files changed, 132 insertions(+), 97 deletions(-) create mode 100644 lodel/plugin/datasource_plugin.py diff --git a/lodel/leapi/leobject.py b/lodel/leapi/leobject.py index 9a6b154..004be89 100644 --- a/lodel/leapi/leobject.py +++ b/lodel/leapi/leobject.py @@ -4,13 +4,14 @@ import importlib import warnings import copy -from lodel.plugin import Plugin from lodel import logger from lodel.settings import Settings from lodel.settings.utils import SettingsError from .query import LeInsertQuery, LeUpdateQuery, LeDeleteQuery, LeGetQuery from .exceptions import * +from lodel.plugin.exceptions import * from lodel.plugin.hooks import LodelHook +from lodel.plugin import Plugin, DatasourcePlugin from lodel.leapi.datahandlers.base_classes import DatasConstructor ##@brief Stores the name of the field present in each LeObject that indicates @@ -244,7 +245,7 @@ class LeObject(object): else: ro_ds, rw_ds = cls._datasource_name #Read only datasource initialisation - cls._ro_datasource = cls._init_datasource(ro_ds, True) + cls._ro_datasource = DatasourcePlugin.init_datasource(ro_ds, True) if cls._ro_datasource is None: log_msg = "No read only datasource set for LeObject %s" log_msg %= cls.__name__ @@ -254,7 +255,7 @@ class LeObject(object): log_msg %= (ro_ds, cls.__name__) logger.debug(log_msg) #Read write datasource initialisation - cls._rw_datasource = cls._init_datasource(rw_ds, False) + cls._rw_datasource = DatasourcePlugin.init_datasource(rw_ds, False) if cls._ro_datasource is None: log_msg = "No read/write datasource set for LeObject %s" log_msg %= cls.__name__ @@ -264,99 +265,6 @@ class LeObject(object): log_msg %= (ro_ds, cls.__name__) logger.debug(log_msg) - - ##@brief Replace the _datasource attribute value by a datasource instance - # - #This method is used once at dyncode load to replace the datasource string - #by a datasource instance to avoid doing this operation for each query - #@param ds_name str : The name of the datasource to instanciate - #@param ro bool : if true initialise the _ro_datasource attribute else - #initialise _rw_datasource attribute - #@throw SettingsError if an error occurs - @classmethod - def _init_datasource(cls, ds_name, ro): - expt_msg = "In LeAPI class '%s' " % cls.__name__ - if ds_name not in Settings.datasources._fields: - #Checking that datasource exists - expt_msg += "Unknown or unconfigured datasource %s for class %s" - expt_msg %= (ds_name, cls.__name__) - raise SettingsError(expt_msg) - try: - #fetching plugin name - ds_plugin_name, ds_identifier = cls._get_ds_plugin_name(ds_name, ro) - except NameError: - expt_msg += "Datasource %s is missconfigured, missing identifier." - expt_msg %= ds_name - raise SettingsError(expt_msg) - except RuntimeError: - expt_msg += "Error in datasource %s configuration. Trying to use \ -a read only as a read&write datasource" - expt_msg %= ds_name - raise SettingsError(expt_msg) - except ValueError as e: - expt_msg += str(e) - raise SettingsError(expt_msg) - - try: - ds_conf = cls._get_ds_connection_conf(ds_identifier, ds_plugin_name) - except NameError as e: - expt_msg += str(e) - raise SettingsError(expt_msg) - #Checks that the datasource plugin exists - ds_plugin_module = Plugin.get(ds_plugin_name).loader_module() - try: - datasource_class = getattr(ds_plugin_module, "Datasource") - except AttributeError as e: - expt_msg += "The datasource plugin %s seems to be invalid. Error \ -raised when trying to import Datasource" - expt_msg %= ds_identifier - raise SettingsError(expt_msg) - - return datasource_class(**ds_conf) - - ##@brief Try to fetch a datasource configuration - #@param ds_identifier str : datasource name - #@param ds_plugin_name : datasource plugin name - #@return a dict containing datasource initialisation options - #@throw NameError if a datasource plugin or instance cannot be found - @staticmethod - def _get_ds_connection_conf(ds_identifier,ds_plugin_name): - if ds_plugin_name not in Settings.datasource._fields: - msg = "Unknown or unconfigured datasource plugin %s" - msg %= ds_plugin - raise NameError(msg) - ds_conf = getattr(Settings.datasource, ds_plugin_name) - if ds_identifier not in ds_conf._fields: - msg = "Unknown or unconfigured datasource instance %s" - msg %= ds_identifier - raise NameError(msg) - ds_conf = getattr(ds_conf, ds_identifier) - return {k: getattr(ds_conf,k) for k in ds_conf._fields } - - ##@brief fetch datasource plugin name - #@param ds_name str : datasource name - #@param ro bool : if true consider the datasource as read only - #@return a tuple(DATASOURCE_PLUGIN_NAME, DATASOURCE_CONNECTION_NAME) - #@throw NameError if datasource identifier not found - #@throw RuntimeError if datasource is read_only but ro flag was false - @staticmethod - def _get_ds_plugin_name(ds_name, ro): - datasource_orig_name = ds_name - # fetching connection identifier given datasource name - ds_identifier = getattr(Settings.datasources, ds_name) - read_only = getattr(ds_identifier, 'read_only') - try: - ds_identifier = getattr(ds_identifier, 'identifier') - except NameError as e: - raise e - if read_only and not ro: - raise RuntimeError() - res = ds_identifier.split('.') - if len(res) != 2: - raise ValueError("expected value for identifier is like \ -DS_PLUGIN_NAME.DS_INSTANCE_NAME. But got %s" % ds_identifier) - return res - ##@brief Return the uid of the current LeObject instance #@return the uid value #@warning Broke multiple uid capabilities diff --git a/lodel/plugin/__init__.py b/lodel/plugin/__init__.py index 33c1eb4..74030b9 100644 --- a/lodel/plugin/__init__.py +++ b/lodel/plugin/__init__.py @@ -41,3 +41,4 @@ from .hooks import LodelHook from .plugins import Plugin, CustomMethod +from .datasource_plugin import DatasourcePlugin diff --git a/lodel/plugin/datasource_plugin.py b/lodel/plugin/datasource_plugin.py new file mode 100644 index 0000000..666e1a7 --- /dev/null +++ b/lodel/plugin/datasource_plugin.py @@ -0,0 +1,119 @@ +from .plugins import Plugin +from .exceptions import * + +##@brief Designed to handles datasources plugins +# +#A datasource provide data access to LeAPI typically a connector on a DB +#or an API +#@note For the moment implementation is done with a retro-compatibilities +#priority and not with a convenience priority. +#@todo Refactor and rewrite lodel2 datasource handling +class DatasourcePlugin(Plugin): + + def __init__(self, name): + super().__init__(name) + self.__datasource_cls = self.loader_module().Datasource + + def datasource(self): + return self.__datasource + + def migration_handler(self): + return self.loader_module().migration_handler_class() + + ##@brief Return an initialized Datasource instance + #@param ds_name str : The name of the datasource to instanciate + #@param ro bool + #@return A properly initialized Datasource instance + #@throw SettingsError if an error occurs in settings + #@throw DatasourcePluginError for various errors + @classmethod + def init_datasource(cls, ds_name, ro): + plugin_name, ds_identifier = cls.plugin_name(ds_name, ro) + ds_conf = cls._get_ds_connection_conf(ds_identifier, plugin_name) + ds_cls = cls.get_datasource(plugin_name) + return ds_cls(**ds_conf) + + ##@brief Given a datasource name returns a DatasourcePlugin name + #@param ds_name str : datasource name + #@param ro bool : if true consider the datasource as readonly + #@return a DatasourcePlugin name + #@throw PluginError if datasource name not found + #@throw DatasourcePermError if datasource is read_only but ro flag arg is + #false + @staticmethod + def plugin_name(ds_name, ro): + from lodel.settings import Settings + # fetching connection identifier given datasource name + try: + ds_identifier = getattr(Settings.datasources, ds_name) + except (NameError, AttributeError): + raise DatasourcePluginError("Unknown or unconfigured datasource \ +'%s'" % ds_name) + # fetching read_only flag + try: + read_only = getattr(ds_identifier, 'read_only') + except (NameError, AttributeError): + raise SettingsError("Malformed datasource configuration for '%s' \ +: missing read_only key" % ds_name) + # fetching datasource identifier + try: + ds_identifier = getattr(ds_identifier, 'identifier') + except (NameError,AttributeError) as e: + raise SettingsError("Malformed datasource configuration for '%s' \ +: missing identifier key" % ds_name) + # settings and ro arg consistency check + if read_only and not ro: + raise DatasourcePluginError("ro argument was set to False but \ +True found in settings for datasource '%s'" % ds_name) + res = ds_identifier.split('.') + if len(res) != 2: + raise SettingsError("expected value for identifier is like \ +DS_PLUGIN_NAME.DS_INSTANCE_NAME. But got %s" % ds_identifier) + return res + + ##@brief Try to fetch a datasource configuration + #@param ds_identifier str : datasource name + #@param ds_plugin_name : datasource plugin name + #@return a dict containing datasource initialisation options + #@throw NameError if a datasource plugin or instance cannot be found + @staticmethod + def _get_ds_connection_conf(ds_identifier,ds_plugin_name): + from lodel.settings import Settings + if ds_plugin_name not in Settings.datasource._fields: + msg = "Unknown or unconfigured datasource plugin %s" + msg %= ds_plugin + raise DatasourcePluginError(msg) + ds_conf = getattr(Settings.datasource, ds_plugin_name) + if ds_identifier not in ds_conf._fields: + msg = "Unknown or unconfigured datasource instance %s" + msg %= ds_identifier + raise DatasourcePluginError(msg) + ds_conf = getattr(ds_conf, ds_identifier) + return {k: getattr(ds_conf,k) for k in ds_conf._fields } + + ##@brief DatasourcePlugin instance accessor + #@param ds_name str : plugin name + #@return a DatasourcePlugin instance + #@throw PluginError if no plugin named ds_name found + #@throw PluginTypeError if ds_name ref to a plugin that is not a + #DatasourcePlugin + @classmethod + def get(cls, ds_name): + pinstance = super().get(ds_name) #Will raise PluginError if bad name + if not isinstance(pinstance, DatasourcePlugin): + raise PluginTypeErrror("A name of a DatasourcePlugin was excepted \ +but %s is a %s" % (ds_name, pinstance.__class__.__name__)) + return pinstance + + ##@brief Return a datasource class given a datasource name + #@param ds_name str : datasource plugin name + #@throw PluginError if ds_name is not an existing plugin name + #@throw PluginTypeError if ds_name is not the name of a DatasourcePlugin + @classmethod + def get_datasource(cls, ds_plugin_name): + return cls.get(ds_plugin_name).datasource() + + @classmethod + def get_migration_handler(cls, ds_plugin_name): + return cls.get(ds_plugin_name).migration_handler_class() + diff --git a/lodel/plugin/exceptions.py b/lodel/plugin/exceptions.py index b72000c..bbf4242 100644 --- a/lodel/plugin/exceptions.py +++ b/lodel/plugin/exceptions.py @@ -1,5 +1,11 @@ class PluginError(Exception): pass +class PluginTypeErrror(PluginError): + pass + class LodelScriptError(Exception): pass + +class DatasourcePluginError(PluginError): + pass diff --git a/lodel/plugin/plugins.py b/lodel/plugin/plugins.py index 3fd8a08..cf6b392 100644 --- a/lodel/plugin/plugins.py +++ b/lodel/plugin/plugins.py @@ -8,8 +8,9 @@ import json from importlib.machinery import SourceFileLoader, SourcelessFileLoader import plugins -from .exceptions import * from lodel import logger +from lodel.settings.utils import SettingsError +from .exceptions import * ## @package lodel.plugins Lodel2 plugins management #