|
@@ -1,3 +1,8 @@
|
|
1
|
+## @package lodel.plugin.datasource_plugin Datasource plugins management module
|
|
2
|
+#
|
|
3
|
+# It contains the base classes for all the datasource plugins that could be added to Lodel
|
|
4
|
+
|
|
5
|
+
|
1
|
6
|
from lodel.context import LodelContext
|
2
|
7
|
LodelContext.expose_modules(globals(), {
|
3
|
8
|
'lodel.plugin.plugins': ['Plugin'],
|
|
@@ -7,58 +12,61 @@ LodelContext.expose_modules(globals(), {
|
7
|
12
|
'lodel.exceptions': ['LodelException', 'LodelExceptions',
|
8
|
13
|
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
|
9
|
14
|
|
|
15
|
+## @brief The plugin type that is used in the global settings of Lodel
|
10
|
16
|
_glob_typename = 'datasource'
|
11
|
17
|
|
12
|
18
|
|
13
|
|
-##@brief Datasource class in plugins HAVE TO inherit from this abstract class
|
|
19
|
+## @brief Main abstract class from which the plugins' datasource classes must inherit.
|
14
|
20
|
class AbstractDatasource(object):
|
15
|
21
|
|
16
|
|
- ##@brief Trigger LodelFatalError when abtract method called
|
|
22
|
+ ## @brief Trigger LodelFatalError when abtract method called
|
|
23
|
+ # @throw LodelFatalError if there is an attempt to instanciate an object from this class
|
17
|
24
|
@staticmethod
|
18
|
25
|
def _abs_err():
|
19
|
26
|
raise LodelFatalError("This method is abstract and HAVE TO be \
|
20
|
27
|
reimplemented by plugin datasource child class")
|
21
|
28
|
|
22
|
|
- ##@brief The constructor
|
|
29
|
+ ##
|
|
30
|
+ # @param *conn_args
|
|
31
|
+ # @param **conn_kwargs
|
23
|
32
|
def __init__(self, *conn_args, **conn_kwargs):
|
24
|
33
|
self._abs_err()
|
25
|
34
|
|
26
|
|
- ##@brief Provide a new uniq numeric ID
|
27
|
|
- #@param emcomp LeObject subclass (not instance) : To know on wich things we
|
28
|
|
- #have to be uniq
|
29
|
|
- #@return an integer
|
|
35
|
+ ## @brief Provides a new uniq numeric ID
|
|
36
|
+ # @param emcomp LeObject subclass (not instance) : defines against which objects type the id should be unique
|
|
37
|
+ # @return int
|
30
|
38
|
def new_numeric_id(self, emcomp):
|
31
|
39
|
self._abs_err()
|
32
|
40
|
|
33
|
|
- ##@brief returns a selection of documents from the datasource
|
34
|
|
- #@param target Emclass
|
35
|
|
- #@param field_list list
|
36
|
|
- #@param filters list : List of filters
|
37
|
|
- #@param rel_filters list : List of relational filters
|
38
|
|
- #@param order list : List of column to order. ex: order = [('title', 'ASC'),]
|
39
|
|
- #@param group list : List of tupple representing the column to group together. ex: group = [('title', 'ASC'),]
|
40
|
|
- #@param limit int : Number of records to be returned
|
41
|
|
- #@param offset int: used with limit to choose the start record
|
42
|
|
- #@param instanciate bool : If true, the records are returned as instances, else they are returned as dict
|
43
|
|
- #@return list
|
|
41
|
+ ## @brief Returns a selection of documents from the datasource
|
|
42
|
+ # @param target Emclass : class of the documents
|
|
43
|
+ # @param field_list list : fields to get from the datasource
|
|
44
|
+ # @param filters list : List of filters
|
|
45
|
+ # @param rel_filters list : List of relational filters (default value : None)
|
|
46
|
+ # @param order list : List of column to order. ex: order = [('title', 'ASC'),] (default value : None)
|
|
47
|
+ # @param group list : List of tupple representing the column to group together. ex: group = [('title', 'ASC'),] (default value : None)
|
|
48
|
+ # @param limit int : Number of records to be returned (default value None)
|
|
49
|
+ # @param offset int: used with limit to choose the start record (default value : 0)
|
|
50
|
+ # @param instanciate bool : If true, the records are returned as instances, else they are returned as dict (default value : True)
|
|
51
|
+ # @return list
|
44
|
52
|
def select(self, target, field_list, filters, rel_filters=None, order=None, group=None, limit=None, offset=0,
|
45
|
53
|
instanciate=True):
|
46
|
54
|
self._abs_err()
|
47
|
55
|
|
48
|
|
- ##@brief Deletes records according to given filters
|
49
|
|
- #@param target Emclass : class of the record to delete
|
50
|
|
- #@param filters list : List of filters
|
51
|
|
- #@param relational_filters list : List of relational filters
|
52
|
|
- #@return int : number of deleted records
|
|
56
|
+ ## @brief Deletes records according to given filters
|
|
57
|
+ # @param target Emclass : class of the record to delete
|
|
58
|
+ # @param filters list : List of filters
|
|
59
|
+ # @param relational_filters list : List of relational filters
|
|
60
|
+ # @return int : number of deleted records
|
53
|
61
|
def delete(self, target, filters, relational_filters):
|
54
|
62
|
self._abs_err()
|
55
|
63
|
|
56
|
64
|
## @brief updates records according to given filters
|
57
|
|
- #@param target Emclass : class of the object to insert
|
58
|
|
- #@param filters list : List of filters
|
59
|
|
- #@param relational_filters list : List of relational filters
|
60
|
|
- #@param upd_datas dict : datas to update (new values)
|
61
|
|
- #@return int : Number of updated records
|
|
65
|
+ # @param target Emclass : class of the object to insert
|
|
66
|
+ # @param filters list : List of filters
|
|
67
|
+ # @param relational_filters list : List of relational filters
|
|
68
|
+ # @param upd_datas dict : datas to update (new values)
|
|
69
|
+ # @return int : Number of updated records
|
62
|
70
|
def update(self, target, filters, relational_filters, upd_datas):
|
63
|
71
|
self._abs_err()
|
64
|
72
|
|
|
@@ -77,22 +85,21 @@ reimplemented by plugin datasource child class")
|
77
|
85
|
self._abs_err()
|
78
|
86
|
|
79
|
87
|
|
80
|
|
-##@brief Designed to handles datasources plugins
|
|
88
|
+## @brief Represents a Datasource plugin
|
81
|
89
|
#
|
82
|
|
-#A datasource provide data access to LeAPI typically a connector on a DB
|
83
|
|
-#or an API
|
|
90
|
+# It will provide an access to a data collection to LeAPI (i.e. database connector, API ...).
|
84
|
91
|
#
|
85
|
|
-#Provide methods to initialize datasource attribute in LeAPI LeObject child
|
86
|
|
-#classes (see @ref leapi.leobject.LeObject._init_datasources() )
|
|
92
|
+# It provides the methods needed to initialize the datasource attribute in LeAPI LeObject child
|
|
93
|
+# classes (see @ref leapi.leobject.LeObject._init_datasources() )
|
87
|
94
|
#
|
88
|
|
-#@note For the moment implementation is done with a retro-compatibilities
|
89
|
|
-#priority and not with a convenience priority.
|
90
|
|
-#@todo Refactor and rewrite lodel2 datasource handling
|
91
|
|
-#@todo Write abstract classes for Datasource and MigrationHandler !!!
|
|
95
|
+# @note For the moment implementation is done with a retro-compatibilities priority and not with a convenience priority.
|
|
96
|
+# @todo Refactor and rewrite lodel2 datasource handling
|
|
97
|
+# @todo Write abstract classes for Datasource and MigrationHandler !!!
|
92
|
98
|
class DatasourcePlugin(Plugin):
|
93
|
99
|
|
94
|
100
|
_type_conf_name = _glob_typename
|
95
|
|
- ##@brief Stores confspecs indicating where DatasourcePlugin list is stored
|
|
101
|
+
|
|
102
|
+ ## @brief Stores confspecs indicating where DatasourcePlugin list is stored
|
96
|
103
|
_plist_confspecs = {
|
97
|
104
|
'section': 'lodel2',
|
98
|
105
|
'key': 'datasource_connectors',
|
|
@@ -104,15 +111,17 @@ class DatasourcePlugin(Plugin):
|
104
|
111
|
'none_is_valid': False})
|
105
|
112
|
}
|
106
|
113
|
|
107
|
|
- ##@brief Construct a DatasourcePlugin
|
108
|
|
- #@param name str : plugin name
|
109
|
|
- #@see plugins.Plugin
|
|
114
|
+ ##
|
|
115
|
+ # @param name str : plugin's name
|
|
116
|
+ # @see plugins.Plugin
|
110
|
117
|
def __init__(self, name):
|
111
|
118
|
super().__init__(name)
|
112
|
119
|
self.__datasource_cls = None
|
113
|
120
|
|
114
|
|
- ##@brief Accessor to the datasource class
|
115
|
|
- #@return A python datasource class
|
|
121
|
+ ## @brief Returns an accessor to the datasource class
|
|
122
|
+ # @return A python datasource class
|
|
123
|
+ # @throw DatasourcePluginError if the plugin's datasource class is not a child of
|
|
124
|
+ # @ref lodel.plugin.datasource_plugin.AbstractDatasource
|
116
|
125
|
def datasource_cls(self):
|
117
|
126
|
if self.__datasource_cls is None:
|
118
|
127
|
self.__datasource_cls = self.loader_module().Datasource
|
|
@@ -122,17 +131,15 @@ class DatasourcePlugin(Plugin):
|
122
|
131
|
lodel.plugin.datasource_plugin.AbstractDatasource" % (self.name))
|
123
|
132
|
return self.__datasource_cls
|
124
|
133
|
|
125
|
|
- ##@brief Accessor to migration handler class
|
126
|
|
- #@return A python migration handler class
|
|
134
|
+ ## @brief Returns an accessor to migration handler class
|
|
135
|
+ # @return A python migration handler class
|
127
|
136
|
def migration_handler_cls(self):
|
128
|
137
|
return self.loader_module().migration_handler_class()
|
129
|
138
|
|
130
|
|
- ##@brief Return an initialized Datasource instance
|
131
|
|
- #@param ds_name str : The name of the datasource to instanciate
|
132
|
|
- #@param ro bool
|
133
|
|
- #@return A properly initialized Datasource instance
|
134
|
|
- #@throw SettingsError if an error occurs in settings
|
135
|
|
- #@throw DatasourcePluginError for various errors
|
|
139
|
+ ## @brief Returns an initialized Datasource instance
|
|
140
|
+ # @param ds_name str : The name of the datasource to instanciate
|
|
141
|
+ # @param ro bool : indicates if it will be in read only mode, else it will be in write only mode
|
|
142
|
+ # @return A properly initialized Datasource instance
|
136
|
143
|
@classmethod
|
137
|
144
|
def init_datasource(cls, ds_name, ro):
|
138
|
145
|
plugin_name, ds_identifier = cls.plugin_name(ds_name, ro)
|
|
@@ -140,9 +147,10 @@ lodel.plugin.datasource_plugin.AbstractDatasource" % (self.name))
|
140
|
147
|
ds_cls = cls.get_datasource(plugin_name)
|
141
|
148
|
return ds_cls(**ds_conf)
|
142
|
149
|
|
143
|
|
- ##@brief Return an initialized MigrationHandler instance
|
144
|
|
- #@param ds_name str : The datasource name
|
145
|
|
- #@return A properly initialized MigrationHandler instance
|
|
150
|
+ ## @brief Returns an initialized MigrationHandler instance
|
|
151
|
+ # @param ds_name str : The datasource name
|
|
152
|
+ # @return A properly initialized MigrationHandler instance
|
|
153
|
+ # @throw PluginError if a read only datasource instance was given to the migration handler.
|
146
|
154
|
@classmethod
|
147
|
155
|
def init_migration_handler(cls, ds_name):
|
148
|
156
|
plugin_name, ds_identifier = cls.plugin_name(ds_name, False)
|
|
@@ -156,13 +164,12 @@ migration handler !!!")
|
156
|
164
|
return mh_cls(**ds_conf)
|
157
|
165
|
|
158
|
166
|
|
159
|
|
- ##@brief Given a datasource name returns a DatasourcePlugin name
|
160
|
|
- #@param ds_name str : datasource name
|
161
|
|
- #@param ro bool : if true consider the datasource as readonly
|
162
|
|
- #@return a DatasourcePlugin name
|
163
|
|
- #@throw PluginError if datasource name not found
|
164
|
|
- #@throw DatasourcePermError if datasource is read_only but ro flag arg is
|
165
|
|
- #false
|
|
167
|
+ ## @brief Given a datasource name returns a DatasourcePlugin name
|
|
168
|
+ # @param ds_name str : datasource name
|
|
169
|
+ # @param ro bool : if true consider the datasource as readonly
|
|
170
|
+ # @return a DatasourcePlugin name
|
|
171
|
+ # @throw DatasourcePluginError if the given datasource is unknown or not configured, or if there is a conflict in its "read-only" property (between the instance and the settings).
|
|
172
|
+ # @throw SettingsError if there are misconfigured datasource settings.
|
166
|
173
|
@staticmethod
|
167
|
174
|
def plugin_name(ds_name, ro):
|
168
|
175
|
LodelContext.expose_modules(globals(), {
|
|
@@ -195,11 +202,11 @@ True found in settings for datasource '%s'" % ds_name)
|
195
|
202
|
DS_PLUGIN_NAME.DS_INSTANCE_NAME. But got %s" % ds_identifier)
|
196
|
203
|
return res
|
197
|
204
|
|
198
|
|
- ##@brief Try to fetch a datasource configuration
|
199
|
|
- #@param ds_identifier str : datasource name
|
200
|
|
- #@param ds_plugin_name : datasource plugin name
|
201
|
|
- #@return a dict containing datasource initialisation options
|
202
|
|
- #@throw NameError if a datasource plugin or instance cannot be found
|
|
205
|
+ ## @brief Returns a datasource's configuration
|
|
206
|
+ # @param ds_identifier str : datasource name
|
|
207
|
+ # @param ds_plugin_name : datasource plugin name
|
|
208
|
+ # @return a dict containing datasource initialisation options
|
|
209
|
+ # @throw DatasourcePluginError if a datasource plugin or instance cannot be found
|
203
|
210
|
@staticmethod
|
204
|
211
|
def _get_ds_connection_conf(ds_identifier,ds_plugin_name):
|
205
|
212
|
LodelContext.expose_modules(globals(), {
|
|
@@ -216,56 +223,55 @@ DS_PLUGIN_NAME.DS_INSTANCE_NAME. But got %s" % ds_identifier)
|
216
|
223
|
ds_conf = getattr(ds_conf, ds_identifier)
|
217
|
224
|
return {k: getattr(ds_conf,k) for k in ds_conf._fields }
|
218
|
225
|
|
219
|
|
- ##@brief DatasourcePlugin instance accessor
|
220
|
|
- #@param ds_name str : plugin name
|
221
|
|
- #@return a DatasourcePlugin instance
|
222
|
|
- #@throw PluginError if no plugin named ds_name found
|
223
|
|
- #@throw PluginTypeError if ds_name ref to a plugin that is not a
|
224
|
|
- #DatasourcePlugin
|
|
226
|
+ ## @brief Returns a DatasourcePlugin instance from a plugin's name
|
|
227
|
+ # @param ds_name str : plugin name
|
|
228
|
+ # @return DatasourcePlugin
|
|
229
|
+ # @throw PluginError if no plugin named ds_name found (@see lodel.plugin.plugins.Plugin)
|
|
230
|
+ # @throw PluginTypeError if ds_name ref to a plugin that is not a DatasourcePlugin
|
225
|
231
|
@classmethod
|
226
|
232
|
def get(cls, ds_name):
|
227
|
|
- pinstance = super().get(ds_name) #Will raise PluginError if bad name
|
|
233
|
+ pinstance = super().get(ds_name) # Will raise PluginError if bad name
|
228
|
234
|
if not isinstance(pinstance, DatasourcePlugin):
|
229
|
235
|
raise PluginTypeErrror("A name of a DatasourcePlugin was excepted \
|
230
|
236
|
but %s is a %s" % (ds_name, pinstance.__class__.__name__))
|
231
|
237
|
return pinstance
|
232
|
238
|
|
233
|
|
- ##@brief Return a datasource class given a datasource name
|
234
|
|
- #@param ds_plugin_name str : datasource plugin name
|
235
|
|
- #@throw PluginError if ds_name is not an existing plugin name
|
236
|
|
- #@throw PluginTypeError if ds_name is not the name of a DatasourcePlugin
|
|
239
|
+ ## @brief Returns a datasource class given a datasource name
|
|
240
|
+ # @param ds_plugin_name str : datasource plugin name
|
|
241
|
+ # @return Datasource class
|
237
|
242
|
@classmethod
|
238
|
243
|
def get_datasource(cls, ds_plugin_name):
|
239
|
244
|
return cls.get(ds_plugin_name).datasource_cls()
|
240
|
245
|
|
241
|
|
- ##@brief Given a plugin name returns a migration handler class
|
242
|
|
- #@param ds_plugin_name str : a datasource plugin name
|
|
246
|
+ ## @brief Returns a migration handler class, given a plugin name
|
|
247
|
+ # @param ds_plugin_name str : a datasource plugin name
|
|
248
|
+ # @return MigrationHandler class
|
243
|
249
|
@classmethod
|
244
|
250
|
def get_migration_handler(cls, ds_plugin_name):
|
245
|
251
|
return cls.get(ds_plugin_name).migration_handler_cls()
|
246
|
252
|
|
247
|
253
|
|
248
|
|
-##@page lodel2_datasources Lodel2 datasources
|
|
254
|
+## @page lodel2_datasources Lodel2 datasources
|
249
|
255
|
#
|
250
|
|
-#@par lodel2_datasources_intro Intro
|
|
256
|
+# @par lodel2_datasources_intro Introduction
|
251
|
257
|
# A single lodel2 website can interact with multiple datasources. This page
|
252
|
|
-# aims to describe configuration & organisation of datasources in lodel2.
|
|
258
|
+# aims to describe configuration and organisation of datasources in lodel2.
|
253
|
259
|
# Each object is attached to a datasource. This association is done in the
|
254
|
|
-# editorial model, the datasource is identified by a name.
|
|
260
|
+# editorial model, in which the datasource is identified by its name.
|
255
|
261
|
#
|
256
|
|
-#@par Datasources declaration
|
257
|
|
-# To define a datasource you have to write something like this in confs file :
|
258
|
|
-#<pre>
|
259
|
|
-#[lodel2.datasources.DATASOURCE_NAME]
|
260
|
|
-#identifier = DATASOURCE_FAMILY.SOURCE_NAME
|
261
|
|
-#</pre>
|
262
|
|
-# See below for DATASOURCE_FAMILY & SOURCE_NAME
|
|
262
|
+# @par Datasources declaration
|
|
263
|
+# To define a datasource you have to write something like this in configuration file :
|
|
264
|
+# <pre>
|
|
265
|
+# [lodel2.datasources.DATASOURCE_NAME]
|
|
266
|
+# identifier = DATASOURCE_FAMILY.SOURCE_NAME
|
|
267
|
+# </pre>
|
|
268
|
+# See below for DATASOURCE_FAMILY & SOURCE_NAME
|
263
|
269
|
#
|
264
|
|
-#@par Datasources plugins
|
265
|
|
-# Each datasource family is a plugin (
|
266
|
|
-#@ref plugin_doc "More informations on plugins" ). For example mysql or a
|
267
|
|
-#mongodb plugins. Here is the CONFSPEC variable templates for datasources
|
268
|
|
-#plugin
|
|
270
|
+# @par Datasources plugins
|
|
271
|
+# Each datasource family is a plugin ( @ref plugin_doc "More informations on plugins" ).
|
|
272
|
+# For example mysql or a mongodb plugins. \n
|
|
273
|
+#
|
|
274
|
+# Here is the CONFSPEC variable templates for datasources plugin
|
269
|
275
|
#<pre>
|
270
|
276
|
#CONFSPEC = {
|
271
|
277
|
# 'lodel2.datasource.example.*' : {
|
|
@@ -275,7 +281,8 @@ but %s is a %s" % (ds_name, pinstance.__class__.__name__))
|
275
|
281
|
# }
|
276
|
282
|
#}
|
277
|
283
|
#</pre>
|
278
|
|
-#MySQL example
|
|
284
|
+#
|
|
285
|
+#MySQL example \n
|
279
|
286
|
#<pre>
|
280
|
287
|
#CONFSPEC = {
|
281
|
288
|
# 'lodel2.datasource.mysql.*' : {
|
|
@@ -291,8 +298,8 @@ but %s is a %s" % (ds_name, pinstance.__class__.__name__))
|
291
|
298
|
#}
|
292
|
299
|
#</pre>
|
293
|
300
|
#
|
294
|
|
-#@par Configuration example
|
295
|
|
-#<pre>
|
|
301
|
+# @par Configuration example
|
|
302
|
+# <pre>
|
296
|
303
|
# [lodel2.datasources.main]
|
297
|
304
|
# identifier = mysql.Core
|
298
|
305
|
# [lodel2.datasources.revues_write]
|