Package libxyz :: Package core :: Package plugins :: Module manager

[hide private]
[frames] | no frames]

# Source Code for Module libxyz.core.plugins.manager

      1  #-*- coding: utf8 -*
      2  #
      3  # Max E. Kuznecov ~syhpoon <syhpoon@syhpoon.name> 2008
      4  #
      5  # This file is part of XYZCommander.
      6  # XYZCommander is free software: you can redistribute it and/or modify
      7  # it under the terms of the GNU Lesser Public License as published by
      8  # the Free Software Foundation, either version 3 of the License, or
      9  # (at your option) any later version.
     10  # XYZCommander is distributed in the hope that it will be useful,
     11  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     13  # GNU Lesser Public License for more details.
     14  # You should have received a copy of the GNU Lesser Public License
     15  # along with XYZCommander. If not, see <http://www.gnu.org/licenses/>.
     16
     17  import sys
     18
     19  from libxyz.exceptions import PluginError
     20  from libxyz.exceptions import XYZValueError
     21  from libxyz.core.plugins import BasePlugin
     22  from libxyz.core.plugins import Namespace
     23  from libxyz.core.utils import ustring
     24
     25 -def ns_transform(func):
     26      """
     27      Transform passed ns plugin path to libxyz.core.plugins.Namespace instance
     28      """
     29
     30      def _trans(instance, *args, **kwargs):
     31          _path, _rest = args[0], args[1:]
     32
     33          if not isinstance(_path, Namespace):
     34              _path = Namespace(_path)
     35
     36          return func(instance, _path, *_rest, **kwargs)
     37
     38      return _trans
     39
     40  #++++++++++++++++++++++++++++++++++++++++++++++++
     41
     42 -class PluginManager(object):
     43      """
     44      Plugin manager class
     45      It is supposed to provide easy access to plugin data
     46      """
     47
     48      PLUGIN_CLASS = u"XYZPlugin"
     49      PLUGIN_FILE = u"main"
     50      VIRTUAL_NAMESPACE = u"sys"
     51      EVENT_INIT = u"event:plugin_init"
     52      EVENT_FROM_LOAD = u"event:plugin_from_load"
     53      EVENT_FROM_LOAD_DATA = u"event:plugin_from_load_data"
     54      EVENT_PREPARE = u"event:plugin_prepare"
     55      EVENT_FIN = u"event:plugin_fin"
     56
     57 -    def __init__(self, xyz, dirs):
     58          """
     59          @param xyz: XYZ data
     60          @param dirs: Plugin directories list
     61          @type dirs: list
     62          """
     63
     64          if not isinstance(dirs, list):
     65              raise XYZValueError(_(u"Invalid argument type %s. List expected" %
     66                                    type(dirs)))
     67          else:
     68              sys.path = dirs + sys.path
     69
     70          self.xyz = xyz
     71
     72          self.enabled = self._enabled_list()
     73          # Do not load all the enabled plugin at once
     74          # Do it on demand
     75          self._loaded = {}
     76          self._waiting = {}
     77
     78      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     79
     80      @ns_transform
     81 -    def load(self, plugin, *initargs, **initkwargs):
     82          """
     83          Load and initiate required plugin
     84          @param plugin: Plugin namespace path
     85          @param initargs: Necessary arguments to initiate plugin
     86          @param initkwargs: Necessary kw arguments to initiate plugin
     87          """
     88
     89          virtual = self.is_virtual(plugin)
     90
     91          if plugin.pfull not in self.enabled:
     92              raise PluginError(_(u"Plugin %s is disabled or does not exist" %
     93                                  plugin))
     94
     95          if self.is_loaded(plugin):
     96              return self.get_loaded(plugin)
     97
     98          if virtual:
     99              # Do not raise any error here, because virtual plugins may be
    100              # initiated at runtime after keys conf is parsed.
    101              return None
    102
    103          plugin.set_method(self.PLUGIN_FILE)
    104
    105          self.xyz.hm.dispatch(self.EVENT_INIT, plugin)
    106
    107          # Import plugin
    108          # Plugin entry-point is XYZPlugin class in a main.py file
    109          try:
    110              _loaded = __import__(plugin.internal, globals(), locals(),
    111                                  [self.PLUGIN_CLASS])
    112          except ImportError, e:
    113              raise PluginError(_(u"Unable to load plugin %s: %s" % (plugin, e)))
    114
    115          try:
    116              _loaded = getattr(_loaded, self.PLUGIN_CLASS)
    117          except AttributeError, e:
    118              raise PluginError(_(u"Unable to find required %s class" % \
    119                                self.PLUGIN_CLASS))
    120
    121          # Initiate plugin
    122          _obj = _loaded(self.xyz, *initargs, **initkwargs)
    123
    124          # Run prepare (constructor)
    125          self.xyz.hm.dispatch(self.EVENT_PREPARE, _obj)
    126          _obj.prepare()
    127
    128          self.set_loaded(plugin, _obj)
    129
    130          return _obj
    131
    132      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    133
    134      @ns_transform
    135 -    def reload(self, plugin, *initargs, **initkwargs):
    136          """
    137          Force load plugin if it's already in cache.
    138          """
    139
    140          if self.is_virtual(plugin):
    141              # Virtual plugins do not support reloading
    142              return None
    143
    144          if self.is_loaded(plugin):
    145              self.del_loaded(plugin)
    146
    147          return self.load(plugin, *initargs, **initkwargs)
    148
    149      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    150
    151      @ns_transform
    152 -    def from_load(self, plugin, method):
    153          """
    154          Load method from plugin.
    155          If plugin was not loaded before, load and initiate it first.
    156
    157          @param plugin: Plugin namespace path
    158          @param method: Public method name
    159          """
    160
    161          self.xyz.hm.dispatch(self.EVENT_FROM_LOAD, plugin, method)
    162
    163          if not self.is_loaded(plugin):
    164              _obj = self.load(plugin)
    165          else:
    166              _obj = self.get_loaded(plugin)
    167
    168          # Possible case for virtual plugins
    169          if _obj is None:
    170              return None
    171
    172          if method not in _obj.public:
    173              raise PluginError(_(u"%s plugin instance does not export "\
    174                                  u"method %s" % (plugin, method)))
    175          else:
    176              return _obj.public[method]
    177
    178      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    179
    180      @ns_transform
    181 -    def from_load_data(self, plugin, obj):
    182          """
    183          Load data object from plugin.
    184          If plugin was not loaded before, load and initiate it first.
    185
    186          @param plugin: Plugin namespace path
    187          @param obj: Public data object name
    188          """
    189
    190          self.xyz.hm.dispatch(self.EVENT_FROM_LOAD_DATA, plugin, obj)
    191
    192          if not self.is_loaded(plugin):
    193              _obj = self.load(plugin)
    194          else:
    195              _obj = self.get_loaded(plugin)
    196
    197          if obj not in _obj.public_data:
    198              raise PluginError(_(u"%s plugin instance does not export "\
    199                                  u"data object %s" % (plugin, obj)))
    200          else:
    201              return _obj.public_data[obj]
    202
    203      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    204
    205      @ns_transform
    206 -    def is_loaded(self, plugin):
    207          """
    208          Check if plugin already loaded
    209          @param plugin: Plugin namespace path
    210          """
    211
    212          return plugin.full in self._loaded
    213
    214      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    215
    216      @ns_transform
    217 -    def get_loaded(self, plugin=None):
    218          """
    219          Return loaded and initiated inistance of plugin
    220          @param plugin: Plugin namespace path
    221          """
    222
    223          return self._loaded[plugin.pfull]
    224
    225      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    226
    227 -    def get_all_loaded(self):
    228          """
    229          Return all currenty loaded plugins as dictionary with plugins ns path
    230          as keys and instances as values
    231          """
    232
    233          return self._loaded
    234
    235      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    236
    237      @ns_transform
    238 -    def set_loaded(self, plugin, inst):
    239          """
    240          Set loaded and initiated inistance of plugin
    241          @param plugin: Plugin namespace path
    242          @param inst: Plugin instance
    243          """
    244
    245          # Check for pending waiting plugins
    246          if plugin.pfull in self._waiting:
    247              # Try to run callback
    248              for _cb, _args in self._waiting[plugin.pfull]:
    249                  try:
    250                      _cb(inst, *_args)
    251                  except Exception, e:
    252                      xyzlog.warning(_(u"Error in wait_for() callback: %s" %
    253                                 ustring(str(e))))
    254              del(self._waiting[plugin.pfull])
    255
    256          self._loaded[plugin.pfull] = inst
    257
    258      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    259
    260      @ns_transform
    261 -    def del_loaded(self, plugin):
    262          """
    263          Delete loaded instance from cache
    264          @param plugin: Plugin namespace path
    265          """
    266
    267          try:
    268              self.shutdown(plugin.pfull)
    269              del(self._loaded[plugin.pfull])
    270          except KeyError:
    271              pass
    272
    273      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    274
    275      @ns_transform
    276 -    def wait_for(self, plugin, callback, *args):
    277          """
    278          Some virtual plugins are not available at the parsing time.
    279          This method is used to wait while plugin is loaded and then run
    280          callback.
    281          Arguments to callback: loaded plugin obj, and all optional *args passed
    282          """
    283
    284          # WTF? already loaded? No need to wait
    285          if self.is_loaded(plugin):
    286              return callback(self.get_loaded(plugin), *args)
    287
    288          # Initiate storage
    289          if plugin.pfull not in self._waiting:
    290              self._waiting[plugin.pfull] = []
    291
    292          # Register for waiting
    293          self._waiting[plugin.pfull].append((callback, args))
    294
    295      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    296
    297 -    def shutdown(self, plugin=None):
    298          """
    299          Run destructors on specified or all loaded plugins
    300          """
    301
    302          def _fin(p):
    303              try:
    304                  self._loaded[p].finalize()
    305              except Exception:
    306                  pass
    307
    308          #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    309
    310          if self._loaded:
    311              _plugins = self._loaded
    312          else:
    313              _plugins = [plugin]
    314
    315          for plugin_name in self._loaded:
    316              self.xyz.hm.dispatch(self.EVENT_FIN, self._loaded[plugin_name])
    317              _fin(plugin_name)
    318
    319      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    320
    321 -    def register(self, obj):
    322          """
    323          Register new plugin.
    324          @param obj: libxyz.core.BasePlugin inherited instance
    325          """
    326
    327          if not isinstance(obj, BasePlugin):
    328              raise XYZValueError(_(u"BasePlugin instance expected, got: %s" %
    329                                    type(obj)))
    330
    331          self.set_loaded(obj.ns, obj)
    332
    333      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    334
    335 -    def is_virtual(self, plugin):
    336          return plugin.ns == self.VIRTUAL_NAMESPACE
    337
    338      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    339
    340 -    def _enabled_list(self):
    341          """
    342          Make list of enabled plugins
    343          """
    344
    345          _data = self.xyz.conf[u"xyz"][u"plugins"]
    346          return [_pname for _pname in _data if _data[_pname]]
    347