Browse Source

Now plugins can define dependencies.

Yann Weber 8 years ago
parent
commit
75c2a81225
3 changed files with 104 additions and 14 deletions
  1. 103
    14
      lodel/plugin/plugins.py
  2. 0
    0
      lodel/plugins/__init__.py
  3. 1
    0
      plugins/dummy_datasource/__init__.py

+ 103
- 14
lodel/plugin/plugins.py View File

@@ -22,6 +22,8 @@ INIT_FILENAME = '__init__.py' # Loaded with settings
22 22
 CONFSPEC_FILENAME_VARNAME = '__confspec__'
23 23
 CONFSPEC_VARNAME = 'CONFSPEC'
24 24
 LOADER_FILENAME_VARNAME = '__loader__'
25
+PLUGIN_DEPS_VARNAME = '__plugin_deps__'
26
+ACTIVATE_METHOD_NAME = '_activate'
25 27
 
26 28
 class PluginError(Exception):
27 29
     pass
@@ -39,8 +41,13 @@ class Plugin(object):
39 41
     
40 42
     ##@brief Stores plugin directories paths
41 43
     _plugin_directories = None
42
-
44
+    
45
+    ##@brief Stores Plugin instances indexed by name
43 46
     _plugin_instances = dict()
47
+    
48
+    ##@brief Attribute used by load_all and load methods to detect circular
49
+    #dependencies
50
+    _load_called = []
44 51
 
45 52
     ##@brief Plugin class constructor
46 53
     #
@@ -56,6 +63,7 @@ class Plugin(object):
56 63
         self.path = self.plugin_path(plugin_name)
57 64
         self.module = None
58 65
         self.__confspecs = dict()
66
+        self.loaded = False
59 67
         
60 68
         # Importing __init__.py
61 69
         plugin_module = '%s.%s' % ( VIRTUAL_PACKAGE_NAME,
@@ -120,21 +128,86 @@ class Plugin(object):
120 128
         filename = self.path + filename
121 129
         loader = SourceFileLoader(module_name, filename)
122 130
         return loader.load_module()
123
-
124
-    ##@brief Register hooks etc
125
-    def load(self):
131
+    
132
+    ##@brief Check dependencies of plugin
133
+    #@return A list of plugin name to be loaded before
134
+    def check_deps(self):
135
+        try:
136
+            res = getattr(self.module, PLUGIN_DEPS_VARNAME)
137
+        except AttributeError:
138
+            return list()
139
+        result = list()
140
+        errors = list()
141
+        for plugin_name in res:
142
+            try:
143
+                result.append(self.get(plugin_name))
144
+            except PluginError:
145
+                errors.append(plugin_name)
146
+        if len(errors) > 0:
147
+            raise PluginError(  "Bad dependencie for '%s' :"%self.name,
148
+                                ', '.join(errors))
149
+        return result
150
+    
151
+    ##@brief Check if the plugin should be activated
152
+    #
153
+    #Try to fetch a function called @ref ACTIVATE_METHOD_NAME in __init__.py
154
+    #of a plugin. If none found assert that the plugin can be loaded, else
155
+    #the method is called. If it returns anything else that True, the plugin
156
+    #is noted as not activable
157
+    #
158
+    # @note Maybe we have to exit everything if a plugin cannot be loaded...
159
+    def activable(self):
126 160
         from lodel import logger
127 161
         try:
128
-            test_fun = self.module._activate
162
+            test_fun = getattr(self.module, ACTIVATE_METHOD_NAME)
129 163
         except AttributeError:
130
-            logger.debug("No _activate method found for plugin %s. Assuming plugin is ready to be loaded" % self.name)
164
+            msg = "No %s method found for plugin %s. Assuming plugin is ready to be loaded"
165
+            msg %= (ACTIVATE_METHOD_NAME, self.name)
166
+            logger.debug(msg)
131 167
             test_fun = lambda:True
132
-        res = test_fun()
133
-        if not(res is True):
134
-            raise PluginError(res)
168
+        return test_fun()
169
+        
170
+    ##@brief Load a plugin
171
+    #
172
+    #Loading a plugin mean importing a file. The filename is defined in the 
173
+    #plugin's __init__.py file in a LOADER_FILENAME_VARNAME variable.
174
+    #
175
+    #The loading process has to take care of other things :
176
+    #- loading dependencies (other plugins)
177
+    #- check that the plugin can be activated using Plugin.activate() method
178
+    #- avoid circular dependencies infinite loop
179
+    def _load(self):
180
+        if self.loaded:
181
+            return
182
+        from lodel import logger
183
+        #Test that plugin "wants" to be activated
184
+        activable = self.activable()
185
+        if not(activable is True):
186
+            msg = "Plugin %s is not activable : %s"
187
+            msg %= (self.name, activable)
188
+            raise PluginError(activable)
135 189
 
190
+        #Circular dependencie detection
191
+        if self.name in self._load_called:
192
+            raise PluginError("Circular dependencie in Plugin detected. Abording")
193
+        else:
194
+            self._load_called.append(self.name)
195
+        
196
+        #Dependencie load
197
+        for dependencie in self.check_deps():
198
+            activable = dependencie.activable()
199
+            if activable is True:
200
+                dependencie._load()
201
+            else:
202
+                msg = "Plugin {plugin_name} not activable because it depends on plugin {dep_name} that is not activable : {reason}"
203
+                msg = msg.format(
204
+                    plugin_name = self.name,
205
+                    dep_name = dependencie.name,
206
+                    reason = activable)
207
+        
208
+        #Loading the plugin
136 209
         try:
137
-            return self._import_from_init_var(LOADER_FILENAME_VARNAME)
210
+            self._import_from_init_var(LOADER_FILENAME_VARNAME)
138 211
         except AttributeError:
139 212
             msg = "Malformed plugin {plugin}. No {varname} found in __init__.py"
140 213
             msg = msg.format(
@@ -148,13 +221,21 @@ class Plugin(object):
148 221
                 expt = str(e))
149 222
             raise PluginError(msg)
150 223
         logger.debug("Plugin '%s' loaded" % self.name)
151
-                
224
+        self.loaded = True
225
+             
226
+    ##@brief Call load method on every pre-loaded plugins
227
+    #
228
+    # Called by loader to trigger hooks registration.
229
+    # This method have to avoid circular dependencies infinite loops. For this
230
+    # purpose a class attribute _load_called exists.
231
+    # @throw PluginError
152 232
     @classmethod
153 233
     def load_all(cls):
154 234
         errors = dict()
235
+        cls._load_called = []
155 236
         for name, plugin in cls._plugin_instances.items():
156 237
             try:
157
-                plugin.load()
238
+                plugin._load()
158 239
             except PluginError as e:
159 240
                 errors[name] = e
160 241
         if len(errors) > 0:
@@ -163,14 +244,17 @@ class Plugin(object):
163 244
                 msg += "\n\t%20s : %s" % (name,e)
164 245
             msg += "\n"
165 246
             raise PluginError(msg)
166
-
247
+    
248
+    ##@return a copy of __confspecs attr
167 249
     @property
168 250
     def confspecs(self):
169 251
         return copy.copy(self.__confspecs)
170 252
 
171 253
     ##@brief Register a new plugin
172 254
     # 
173
-    # preload
255
+    #@param plugin_name str : The plugin name
256
+    #@return a Plugin instance
257
+    #@throw PluginError
174 258
     @classmethod
175 259
     def register(cls, plugin_name):
176 260
         if plugin_name in cls._plugin_instances:
@@ -181,6 +265,11 @@ class Plugin(object):
181 265
         cls._plugin_instances[plugin_name] = plugin
182 266
         return plugin
183 267
 
268
+    ##@brief Plugins instances accessor
269
+    #
270
+    #@param plugin_name str: The plugin name
271
+    #@return a Plugin instance
272
+    #@throw PluginError if plugin not found
184 273
     @classmethod
185 274
     def get(cls, plugin_name):
186 275
         try:

+ 0
- 0
lodel/plugins/__init__.py View File


+ 1
- 0
plugins/dummy_datasource/__init__.py View File

@@ -2,6 +2,7 @@ from lodel.settings.validator import SettingValidator
2 2
 from .main import DummyDatasource as Datasource
3 3
 
4 4
 __loader__ = 'main.py'
5
+__plugin_deps__ = ['datasources']
5 6
 
6 7
 CONFSPEC = {
7 8
     'lodel2.datasource.dummy_datasource.*' : {

Loading…
Cancel
Save