def test_unimportable_package(self):
     self._create_pkg("_foobar").close()
     s = ModuleScanner([self.d])
     self.failIf(s.modules)
     removed, added = s.rescan()
     self.failIf(added)
     self.failIf(removed)
 def test_scanner_error(self):
     h = self._create_mod("q4.py")
     h.write(b"1syntaxerror\n")
     h.close()
     s = ModuleScanner([self.d])
     removed, added = s.rescan()
     self.failIf(added)
     self.failIf(removed)
     self.failUnlessEqual(len(s.failures), 1)
     self.failUnless("q4" in s.failures)
 def test_scanner_add(self):
     self._create_mod("q1.py").close()
     self._create_mod("q2.py").close()
     s = ModuleScanner([self.d])
     self.failIf(s.modules)
     removed, added = s.rescan()
     self.failIf(removed)
     self.failUnlessEqual(set(added), {"q1", "q2"})
     self.failUnlessEqual(len(s.modules), 2)
     self.failUnlessEqual(len(s.failures), 0)
 def test_scanner_add_package(self):
     h = self._create_pkg("somepkg")
     h2 = self._create_mod("sub.py", "somepkg")
     h2.write(b"test=123\n")
     h2.close()
     h.write(b"from .sub import *\nmain=321\n")
     h.close()
     s = ModuleScanner([self.d])
     removed, added = s.rescan()
     self.failUnlessEqual(added, ["somepkg"])
     self.failUnlessEqual(s.modules["somepkg"].module.main, 321)
     self.failUnlessEqual(s.modules["somepkg"].module.test, 123)
 def test_scanner_remove(self):
     h = self._create_mod("q3.py")
     h.close()
     s = ModuleScanner([self.d])
     s.rescan()
     os.remove(h.name)
     try:
         os.remove(h.name + "c")
     except OSError:
         pass
     removed, added = s.rescan()
     self.failIf(added)
     self.failUnlessEqual(removed, ["q3"])
     self.failUnlessEqual(len(s.modules), 0)
     self.failUnlessEqual(len(s.failures), 0)
Beispiel #6
0
    def __init__(self, folders=None):
        """folders is a list of paths that will be scanned for plugins.
        Plugins in later paths will be preferred if they share a name.
        """

        super(PluginManager, self).__init__()

        if folders is None:
            folders = []

        self.__scanner = ModuleScanner(folders)
        self.__modules = {}     # name: PluginModule
        self.__handlers = []    # handler list
        self.__enabled = set()  # (possibly) enabled plugin IDs

        self.__restore()
Beispiel #7
0
class PluginManager(object):
    """
    The manager takes care of plugin loading/reloading. Interested plugin
    handlers can register them self to get called when plugins get enabled
    or disabled.

    Plugins get exposed when at least one handler shows interest
    in them (by returning True in the handle method).

    Plugins have to be a class which defines PLUGIN_ID, PLUGIN_NAME.
    Plugins that have a true PLUGIN_INSTANCE attribute get instantiated on
    enable and the enabled/disabled methods get called.

    If plugin handlers want a plugin instance, they have to call
    Plugin.get_instance() to get a singleton.

    handlers need to implement the following methods:

        handler.plugin_handle(plugin)
            Needs to return True if the handler should be called
            whenever the plugin's enabled status changes.

        handler.plugin_enable(plugin)
            Gets called if the plugin gets enabled.

        handler.plugin_disable(plugin)
            Gets called if the plugin gets disabled.
            Should remove all references.
    """

    CONFIG_SECTION = "plugins"
    CONFIG_OPTION = "active_plugins"

    instance = None  # default instance

    def __init__(self, folders=None):
        """folders is a list of paths that will be scanned for plugins.
        Plugins in later paths will be preferred if they share a name.
        """

        super(PluginManager, self).__init__()

        if folders is None:
            folders = []

        self.__scanner = ModuleScanner(folders)
        self.__modules = {}     # name: PluginModule
        self.__handlers = []    # handler list
        self.__enabled = set()  # (possibly) enabled plugin IDs

        self.__restore()

    def rescan(self):
        """Scan for plugin changes or to initially load all plugins"""

        print_d("Rescanning..")

        removed, added = self.__scanner.rescan()

        # remember IDs of enabled plugin that get reloaded, so we can enable
        # them again
        reload_ids = []
        for name in removed:
            if name not in added:
                continue
            mod = self.__modules[name]
            for plugin in mod.plugins:
                if self.enabled(plugin):
                    reload_ids.append(plugin.id)

        for name in removed:
            # share the namespace with ModuleScanner for now
            self.__remove_module(name)

        # restore enabled state
        self.__enabled.update(reload_ids)

        for name in added:
            new_module = self.__scanner.modules[name]
            self.__add_module(name, new_module.module)

        print_d("Rescanning done.")

    @property
    def _modules(self):
        return itervalues(self.__scanner.modules)

    @property
    def _plugins(self):
        """All registered plugins"""

        plugins = []
        for module in itervalues(self.__modules):
            for plugin in module.plugins:
                plugins.append(plugin)
        return plugins

    @property
    def plugins(self):
        """Returns a list of plugins with active handlers"""

        return [p for p in self._plugins if p.handlers]

    def register_handler(self, handler):
        """
        Registers a handler, attaching it to any current plugins it
        advertises that it can handle

        `handler` should probably be a `PluginHandler`
        """
        print_d("Registering handler: %r" % type(handler).__name__)

        self.__handlers.append(handler)

        for plugin in self._plugins:
            if not handler.plugin_handle(plugin):
                continue
            if plugin.handlers:
                plugin.handlers.append(handler)
                if self.enabled(plugin):
                    handler.plugin_enable(plugin)
            else:
                plugin.handlers.append(handler)
                if self.enabled(plugin):
                    self.enable(plugin, True, force=True)

    def save(self):
        print_d("Saving plugins: %d active" % len(self.__enabled))
        config.set(self.CONFIG_SECTION,
                   self.CONFIG_OPTION,
                   "\n".join(self.__enabled))

    def enabled(self, plugin):
        """Returns if the plugin is enabled."""

        if not plugin.handlers:
            return False

        return plugin.id in self.__enabled

    def enable(self, plugin, status, force=False):
        """Enable or disable a plugin."""

        if not force and self.enabled(plugin) == bool(status):
            return

        if not status:
            print_d("Disable %r" % plugin.id)
            for handler in plugin.handlers:
                handler.plugin_disable(plugin)

            self.__enabled.discard(plugin.id)

            instance = plugin.instance
            if instance and hasattr(instance, "disabled"):
                try:
                    instance.disabled()
                except Exception:
                    util.print_exc()
        else:
            print_d("Enable %r" % plugin.id)
            obj = plugin.get_instance()
            if obj and hasattr(obj, "enabled"):
                try:
                    obj.enabled()
                except Exception:
                    util.print_exc()
            for handler in plugin.handlers:
                handler.plugin_enable(plugin)
            self.__enabled.add(plugin.id)

    @property
    def failures(self):
        """module name: list of error message text lines"""

        errors = {}
        for name, error in iteritems(self.__scanner.failures):
            exception = error.exception
            if isinstance(exception, PluginImportException):
                if not exception.should_show():
                    continue
                errors[name] = [exception.desc]
            else:
                errors[name] = error.traceback

        return errors

    def quit(self):
        """Disable plugins and tell all handlers to clean up"""

        for name in self.__modules.keys():
            self.__remove_module(name)

    def __remove_module(self, name):
        plugin_module = self.__modules.pop(name)
        for plugin in plugin_module.plugins:
            if plugin.handlers:
                self.enable(plugin, False)

    def __add_module(self, name, module):
        plugin_mod = PluginModule(name, module)
        self.__modules[name] = plugin_mod

        for plugin in plugin_mod.plugins:
            handlers = []
            for handler in self.__handlers:
                if handler.plugin_handle(plugin):
                    handlers.append(handler)
            if handlers:
                plugin.handlers = handlers
                if self.enabled(plugin):
                    self.enable(plugin, True, force=True)

    def __restore(self):
        migrate_old_config()
        active = config.get(self.CONFIG_SECTION,
                            self.CONFIG_OPTION, "").splitlines()

        self.__enabled.update(active)
        print_d("Restoring plugins: %d" % len(self.__enabled))

        for plugin in self._plugins:
            if self.enabled(plugin):
                self.enable(plugin, True, force=True)
Beispiel #8
0
# Nasty hack to allow importing of plugins...
PLUGIN_DIRS = []

root = os.path.join(get_module_dir(quodlibet), "ext")
for entry in os.listdir(root):
    if entry.startswith("_"):
        continue
    path = os.path.join(root, entry)
    if not os.path.isdir(path):
        continue
    PLUGIN_DIRS.append(path)

PLUGIN_DIRS.append(os.path.join(util.get_module_dir(), "test_plugins"))

ms = ModuleScanner(PLUGIN_DIRS)

ms.rescan()

# make sure plugins only raise expected errors
for name, err in ms.failures.items():
    exc = err.exception
    assert issubclass(type(exc), (PluginImportException, ImportError)),\
        "'%s' plugin shouldn't have raised a %s, but it did (%r)."\
        % (name, type(exc).__name__, exc)

plugins = {}
modules = {}
for name, module in iteritems(ms.modules):
    for plugin in list_plugins(module.module):
        plugins[plugin.PLUGIN_ID] = Plugin(plugin)
Beispiel #9
0
class PluginManager(object):
    """
    The manager takes care of plugin loading/reloading. Interested plugin
    handlers can register them self to get called when plugins get enabled
    or disabled.

    Plugins get exposed when at least one handler shows interest
    in them (by returning True in the handle method).

    Plugins need to define PLUGIN_ID, PLUGIN_NAME attributes to get loaded.

    Plugins that have a true PLUGIN_INSTANCE attribute get instantiated on
    enable and the enabled/disabled methods get called.

    If plugin handlers want a plugin instance, they have to call
    get_instance to get a singleton.

    handlers need to implement the following methods:

        handler.plugin_handle(plugin)
            Needs to return True if the handler should be called
            whenever the plugin's enabled status changes.

        handler.plugin_enable(plugin, instance)
            Gets called if the plugin gets enabled.
            2nd parameter is an instance of the plugin or None

        handler.plugin_disable(plugin)
            Gets called if the plugin gets disabled.
            Should remove all references.
    """

    CONFIG_SECTION = "plugins"
    CONFIG_OPTION = "active_plugins"

    instance = None # default instance

    def __init__(self, folders=None):
        """folders is a list of paths that will be scanned for plugins.
        Plugins in later paths will be preferred if they share a name.
        """

        super(PluginManager, self).__init__()

        if folders is None:
            folders = []

        self.__scanner = ModuleScanner(folders)
        self.__modules = {}     # name: module
        self.__plugins = {}     # module: [plugin, ..]
        self.__handlers = {}    # plugins: [handler, ..]
        self.__instance = {}    # plugin: instance
        self.__list = []        # handler list
        self.__enabled = set()  # (possibly) enabled plugin IDs

        self.__restore()

    def rescan(self):
        """Scan for plugin changes or to initially load all plugins"""

        print_d("Rescanning..")

        removed, added = self.__scanner.rescan()
        modules = self.__scanner.modules

        # remember IDs of enabled plugin that get reloaded, so we can enable
        # them again
        reload_ids = []
        for name in removed:
            if name not in added:
                continue
            mod = self.__modules[name]
            for plugin in self.__plugins[mod]:
                if self.enabled(plugin):
                    reload_ids.append(plugin.PLUGIN_ID)

        for name in removed:
            # share the namespace with ModuleScanner for now
            self.__remove_module(name)

        # restore enabled state
        self.__enabled.update(reload_ids)

        for name in added:
            module = modules[name]
            self.__add_module(name, module)

        print_d("Rescanning done.")

    @property
    def _modules(self):
        return self.__scanner.modules.itervalues()

    @property
    def plugins(self):
        """Returns a list of plugin classes or instances"""

        items = self.__handlers.items()
        return [self.get_instance(p) or p for (p,h) in items if h]

    def get_instance(self, plugin):
        """"Returns a possibly shared instance of the plugin class"""

        if not getattr(plugin, "PLUGIN_INSTANCE", False):
            return

        if plugin not in self.__instance:
            try:
                obj = plugin()
            except:
                util.print_exc()
                return
            self.__instance[plugin] = obj
        return self.__instance[plugin]

    def register_handler(self, handler):
        print_d("Registering handler: %r" % type(handler).__name__)
        self.__list.append(handler)
        for plugins in self.__plugins.itervalues():
            for plugin in plugins:
                if not handler.plugin_handle(plugin):
                    continue
                if self.__handlers.get(plugin):
                    self.__handlers[plugin].append(handler)
                    if self.enabled(plugin):
                        obj = self.get_instance(plugin)
                        handler.plugin_enable(plugin, obj)
                else:
                    self.__handlers[plugin] = [handler]
                    if self.enabled(plugin):
                        self.enable(plugin, True, force=True)

    def save(self):
        print_d("Saving plugins: %d active" % len(self.__enabled))
        config.set(self.CONFIG_SECTION,
                   self.CONFIG_OPTION,
                   "\n".join(self.__enabled))

    def enabled(self, plugin):
        """Returns if the plugin is enabled. Also takes an instance."""

        if type(plugin) in self.__handlers:
            plugin = type(plugin)

        return plugin.PLUGIN_ID in self.__enabled

    def enable(self, plugin, status, force=False):
        """Enable or disable a plugin. Also takes an instance."""

        if type(plugin) in self.__handlers:
            plugin = type(plugin)

        if not force and self.enabled(plugin) == bool(status):
            return

        if not status:
            print_d("Disable %r" % plugin.PLUGIN_ID)
            for handler in self.__handlers[plugin]:
                handler.plugin_disable(plugin)
            self.__enabled.discard(plugin.PLUGIN_ID)
            instance = self.__instance.get(plugin)
            if instance and hasattr(instance, "disabled"):
                try:
                    instance.disabled()
                except Exception:
                    util.print_exc()
        else:
            print_d("Enable %r" % plugin.PLUGIN_ID)
            obj = self.get_instance(plugin)
            if obj and hasattr(obj, "enabled"):
                try:
                    obj.enabled()
                except Exception:
                    util.print_exc()
            for handler in self.__handlers[plugin]:
                handler.plugin_enable(plugin, obj)
            self.__enabled.add(plugin.PLUGIN_ID)

    @property
    def failures(self):
        errors = {}
        for (name, (exc, text)) in self.__scanner.failures.iteritems():
            if isinstance(exc, PluginImportException):
                if not exc.should_show():
                    continue
                errors[name] = [exc.desc]
            else:
                errors[name] = text
        return errors

    def quit(self):
        """Disable plugins and tell all handlers to clean up"""
        for name in self.__modules.keys():
            self.__remove_module(name)

    def __remove_module(self, name):
        module = self.__modules.pop(name)
        plugins = self.__plugins.pop(module)

        for plugin in plugins:
            if self.__handlers.get(plugin):
                self.enable(plugin, False)
            self.__handlers.pop(plugin, None)

    def __add_module(self, name, module):
        self.__modules[name] = module
        plugins = list_plugins(module)
        self.__plugins[module] = plugins

        for plugin in plugins:
            handlers = []
            for handler in self.__list:
                if handler.plugin_handle(plugin):
                    handlers.append(handler)
            if handlers:
                self.__handlers[plugin] = handlers
                if self.enabled(plugin):
                    self.enable(plugin, True, force=True)

    def __restore(self):
        migrate_old_config()
        active = config.get(self.CONFIG_SECTION,
                            self.CONFIG_OPTION, "").splitlines()

        self.__enabled.update(active)
        print_d("Restoring plugins: %d" % len(self.__enabled))
        for plugin, handlers in self.__handlers.iteritems():
            if self.enabled(plugin):
                self.enable(plugin, True, force=True)