No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

datasource_plugin.py 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. from .plugins import Plugin
  2. from .exceptions import *
  3. from lodel.settings.validator import SettingValidator
  4. _glob_typename = 'datasource'
  5. ##@brief Designed to handles datasources plugins
  6. #
  7. #A datasource provide data access to LeAPI typically a connector on a DB
  8. #or an API
  9. #
  10. #Provide methods to initialize datasource attribute in LeAPI LeObject child
  11. #classes (see @ref leapi.leobject.LeObject._init_datasources() )
  12. #
  13. #@note For the moment implementation is done with a retro-compatibilities
  14. #priority and not with a convenience priority.
  15. #@todo Refactor and rewrite lodel2 datasource handling
  16. #@todo Write abstract classes for Datasource and MigrationHandler !!!
  17. class DatasourcePlugin(Plugin):
  18. _type_conf_name = _glob_typename
  19. ##@brief Stores confspecs indicating where DatasourcePlugin list is stored
  20. _plist_confspecs = {
  21. 'section': 'lodel2',
  22. 'key': 'datasource_connectors',
  23. 'default': None,
  24. 'validator': SettingValidator(
  25. 'plugin', none_is_valid = False,
  26. ptype = _glob_typename) }
  27. ##@brief Construct a DatasourcePlugin
  28. #@param name str : plugin name
  29. #@see plugins.Plugin
  30. def __init__(self, name):
  31. super().__init__(name)
  32. self.__datasource_cls = None
  33. ##@brief Accessor to the datasource class
  34. #@return A python datasource class
  35. def datasource_cls(self):
  36. if self.__datasource_cls is None:
  37. self.__datasource_cls = self.loader_module().Datasource
  38. return self.__datasource_cls
  39. ##@brief Accessor to migration handler class
  40. #@return A python migration handler class
  41. def migration_handler_cls(self):
  42. return self.loader_module().migration_handler_class()
  43. ##@brief Return an initialized Datasource instance
  44. #@param ds_name str : The name of the datasource to instanciate
  45. #@param ro bool
  46. #@return A properly initialized Datasource instance
  47. #@throw SettingsError if an error occurs in settings
  48. #@throw DatasourcePluginError for various errors
  49. @classmethod
  50. def init_datasource(cls, ds_name, ro):
  51. plugin_name, ds_identifier = cls.plugin_name(ds_name, ro)
  52. ds_conf = cls._get_ds_connection_conf(ds_identifier, plugin_name)
  53. ds_cls = cls.get_datasource(plugin_name)
  54. return ds_cls(**ds_conf)
  55. ##@brief Return an initialized MigrationHandler instance
  56. #@param ds_name str : The datasource name
  57. #@return A properly initialized MigrationHandler instance
  58. @classmethod
  59. def init_migration_handler(cls, ds_name):
  60. plugin_name, ds_identifier = cls.plugin_name(ds_name, False)
  61. ds_conf = cls._get_ds_connection_conf(ds_identifier, plugin_name)
  62. mh_cls = cls.get_migration_handler(plugin_name)
  63. if 'read_only' in ds_conf:
  64. if ds_conf['read_only']:
  65. raise PluginError("A read only datasource was given to \
  66. migration handler !!!")
  67. del(ds_conf['read_only'])
  68. return mh_cls(**ds_conf)
  69. ##@brief Given a datasource name returns a DatasourcePlugin name
  70. #@param ds_name str : datasource name
  71. #@param ro bool : if true consider the datasource as readonly
  72. #@return a DatasourcePlugin name
  73. #@throw PluginError if datasource name not found
  74. #@throw DatasourcePermError if datasource is read_only but ro flag arg is
  75. #false
  76. @staticmethod
  77. def plugin_name(ds_name, ro):
  78. from lodel.settings import Settings
  79. # fetching connection identifier given datasource name
  80. try:
  81. ds_identifier = getattr(Settings.datasources, ds_name)
  82. except (NameError, AttributeError):
  83. raise DatasourcePluginError("Unknown or unconfigured datasource \
  84. '%s'" % ds_name)
  85. # fetching read_only flag
  86. try:
  87. read_only = getattr(ds_identifier, 'read_only')
  88. except (NameError, AttributeError):
  89. raise SettingsError("Malformed datasource configuration for '%s' \
  90. : missing read_only key" % ds_name)
  91. # fetching datasource identifier
  92. try:
  93. ds_identifier = getattr(ds_identifier, 'identifier')
  94. except (NameError,AttributeError) as e:
  95. raise SettingsError("Malformed datasource configuration for '%s' \
  96. : missing identifier key" % ds_name)
  97. # settings and ro arg consistency check
  98. if read_only and not ro:
  99. raise DatasourcePluginError("ro argument was set to False but \
  100. True found in settings for datasource '%s'" % ds_name)
  101. res = ds_identifier.split('.')
  102. if len(res) != 2:
  103. raise SettingsError("expected value for identifier is like \
  104. DS_PLUGIN_NAME.DS_INSTANCE_NAME. But got %s" % ds_identifier)
  105. return res
  106. ##@brief Try to fetch a datasource configuration
  107. #@param ds_identifier str : datasource name
  108. #@param ds_plugin_name : datasource plugin name
  109. #@return a dict containing datasource initialisation options
  110. #@throw NameError if a datasource plugin or instance cannot be found
  111. @staticmethod
  112. def _get_ds_connection_conf(ds_identifier,ds_plugin_name):
  113. from lodel.settings import Settings
  114. if ds_plugin_name not in Settings.datasource._fields:
  115. msg = "Unknown or unconfigured datasource plugin %s"
  116. msg %= ds_plugin
  117. raise DatasourcePluginError(msg)
  118. ds_conf = getattr(Settings.datasource, ds_plugin_name)
  119. if ds_identifier not in ds_conf._fields:
  120. msg = "Unknown or unconfigured datasource instance %s"
  121. msg %= ds_identifier
  122. raise DatasourcePluginError(msg)
  123. ds_conf = getattr(ds_conf, ds_identifier)
  124. return {k: getattr(ds_conf,k) for k in ds_conf._fields }
  125. ##@brief DatasourcePlugin instance accessor
  126. #@param ds_name str : plugin name
  127. #@return a DatasourcePlugin instance
  128. #@throw PluginError if no plugin named ds_name found
  129. #@throw PluginTypeError if ds_name ref to a plugin that is not a
  130. #DatasourcePlugin
  131. @classmethod
  132. def get(cls, ds_name):
  133. pinstance = super().get(ds_name) #Will raise PluginError if bad name
  134. if not isinstance(pinstance, DatasourcePlugin):
  135. raise PluginTypeErrror("A name of a DatasourcePlugin was excepted \
  136. but %s is a %s" % (ds_name, pinstance.__class__.__name__))
  137. return pinstance
  138. ##@brief Return a datasource class given a datasource name
  139. #@param ds_name str : datasource plugin name
  140. #@throw PluginError if ds_name is not an existing plugin name
  141. #@throw PluginTypeError if ds_name is not the name of a DatasourcePlugin
  142. @classmethod
  143. def get_datasource(cls, ds_plugin_name):
  144. return cls.get(ds_plugin_name).datasource_cls()
  145. ##@brief Given a plugin name returns a migration handler class
  146. #@param ds_plugin_name str : a datasource plugin name
  147. @classmethod
  148. def get_migration_handler(cls, ds_plugin_name):
  149. return cls.get(ds_plugin_name).migration_handler_cls()
  150. ##@page lodel2_datasources Lodel2 datasources
  151. #
  152. #@par lodel2_datasources_intro Intro
  153. # A single lodel2 website can interact with multiple datasources. This page
  154. # aims to describe configuration & organisation of datasources in lodel2.
  155. # Each object is attached to a datasource. This association is done in the
  156. # editorial model, the datasource is identified by a name.
  157. #
  158. #@par Datasources declaration
  159. # To define a datasource you have to write something like this in confs file :
  160. #<pre>
  161. #[lodel2.datasources.DATASOURCE_NAME]
  162. #identifier = DATASOURCE_FAMILY.SOURCE_NAME
  163. #</pre>
  164. # See below for DATASOURCE_FAMILY & SOURCE_NAME
  165. #
  166. #@par Datasources plugins
  167. # Each datasource family is a plugin (
  168. #@ref plugin_doc "More informations on plugins" ). For example mysql or a
  169. #mongodb plugins. Here is the CONFSPEC variable templates for datasources
  170. #plugin
  171. #<pre>
  172. #CONFSPEC = {
  173. # 'lodel2.datasource.example.*' : {
  174. # 'conf1' : VALIDATOR_OPTS,
  175. # 'conf2' : VALIDATOR_OPTS,
  176. # ...
  177. # }
  178. #}
  179. #</pre>
  180. #MySQL example
  181. #<pre>
  182. #CONFSPEC = {
  183. # 'lodel2.datasource.mysql.*' : {
  184. # 'host': ( 'localhost',
  185. # SettingValidator('host')),
  186. # 'db_name': ( 'lodel',
  187. # SettingValidator('string')),
  188. # 'username': ( None,
  189. # SettingValidator('string')),
  190. # 'password': ( None,
  191. # SettingValidator('string')),
  192. # }
  193. #}
  194. #</pre>
  195. #
  196. #@par Configuration example
  197. #<pre>
  198. # [lodel2.datasources.main]
  199. # identifier = mysql.Core
  200. # [lodel2.datasources.revues_write]
  201. # identifier = mysql.Revues
  202. # [lodel2.datasources.revues_read]
  203. # identifier = mysql.Revues
  204. # [lodel2.datasources.annuaire_persons]
  205. # identifier = persons_web_api.example
  206. # ;
  207. # ; Then, in the editorial model you are able to use "main", "revues_write",
  208. # ; etc as datasource
  209. # ;
  210. # ; Here comes the datasources declarations
  211. # [lodel2.datasource.mysql.Core]
  212. # host = db.core.labocleo.org
  213. # db_name = core
  214. # username = foo
  215. # password = bar
  216. # ;
  217. # [lodel2.datasource.mysql.Revues]
  218. # host = revues.org
  219. # db_name = RO
  220. # username = foo
  221. # password = bar
  222. # ;
  223. # [lodel2.datasource.persons_web_api.example]
  224. # host = foo.bar
  225. # username = cleo
  226. #</pre>