Source

moin-2.0 / MoinMoin / plugin.py

Full commit
# Copyright: 2012 MoinMoin:Miks Kalnins <mikskalnins@maikumori.com>
# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.

"""
MoinMoin plugin system.
"""

from __future__ import absolute_import, division

import os
import sys
from abc import ABCMeta, abstractproperty, abstractmethod
from inspect import getmembers, isclass

from jinja2.loaders import FileSystemLoader

from MoinMoin import log
logging = log.getLogger(__name__)

class PluginBase(object):
    """
    This is the very basic plugin interface. All plugins must implement it.
    """
    __metaclass__ = ABCMeta

    @abstractproperty
    def name(self):
        """ Name of the plugin. """
        pass

    @abstractproperty
    def author(self):
        """ Author name of the plugin. """
        pass

    @abstractproperty
    def description(self):
        """ Short description of the plugin. """
        pass

    @property
    def identifier(self):
        return self.__class__.__name__

    def make_path(self, *p):
        """ Make a path relative to the plugin directory. """
        # Little trick to work around __file__ pointing to file where the ABC declaration is located at.
        path = os.path.dirname(os.path.abspath(sys.modules[self.__class__.__module__].__file__))
        return os.path.join(path, *p)

    def __init__(self, cfg):
        pass

class ThemeBase(PluginBase):
    """
    Theme interface.

    Influenced by Flask Themes (by Matthew "LeafStorm" Frazier)
    """

    @property
    def static_path(self):
        """ The absolute path to the theme's static files directory. """
        return self.make_path('static')

    @property
    def templates_path(self):
        """
        The absolute path to the theme's templates directory.
        """
        return self.make_path('templates')

    @property
    def jinja_loader(self):
        """
        This is a Jinja2 template loader that loads templates from the theme's
        ``templates`` directory.
        """
        return FileSystemLoader(self.templates_path)


class StoreBase(PluginBase):
    """
    Store interface.
    """

    @property
    def store_name(self):
        """
        Store name used in storage URIs.
        """

    @abstractmethod
    def getByteStoreFromURI(self, uri):
        """
        :param uri: URI to be passed to ByteStore constructor
        :returns: MoinMoin.Storages.Store.ByteStore instance
        """

    @abstractmethod
    def getFileStoreFromURI(self, uri):
        """
        :param uri: URI to be passed to FileStore constructor
        :returns: MoinMoin.Storages.Store.FileStore instance
        """

class BlueprintBase(PluginBase):
    """
    Blueprint interface.

    See :file:`contrib/ExamplePlugin' for an example of how to implement Blueprint plugin.
    """

    @property
    def blueprint(self):
        """
        Flask Blueprint which will be registered with the MoinMoin application
        """
        return None

    @property
    def blueprint_options(self):
        """
        Main use case is to set url_prefix option, but can be used to setup any other options when
        the blueprint is registered.
        """
        return dict()

class AuthBase(PluginBase):
    """
    Auth interface.
    """

    @abstractmethod
    def getAuthInstance(self, **kw):
        """
        Get instance of class implementing MoinMoin.auth.BaseAuth
        :returns: MoinMoin.auth.BaseAuth instance
        """

def load(plugins, ignore_exceptions=True):
    """
    Imports plugin packages from list of names.

    :param plugins: list of plugin package names to import
    :param ignore_exceptions: (optional) true, if you want to ignore exceptions when importing

    :returns: dict of all loaded module objects
    """

    if plugins is None:
        return {}

    loaded_modules = {}
    for plugin in plugins:
        try:
            loaded_modules[plugin] = __import__(plugin)
        except:
            if ignore_exceptions:
                logging.error("Failed to import: {0}".format(plugin))
            else:
                raise

    return loaded_modules

def get_abcs():
    """
    Gets all classes defined in this file. All of those should be abstract base classes for plugins.

    :returns: list of all classes in this file
    """
    members = getmembers(sys.modules[__name__], lambda member: isclass(member) and member.__module__ == __name__)
    return [x[1] for x in members]

def init_all_plugins(plugin_config, ignore_exceptions=True):
    """
    Initializes all PluginBase subclasses.

    :param plugin_config: dict containing plugin configuration where key is plugin package name
    :param ignore_exceptions: (optional) true, if you want to ignore exceptions when initializing

    :returns: dict of all plugin instances
    """

    # Find all the subclasses of plugin abstract classes.
    plugin_classes = []
    classes = PluginBase.__subclasses__()
    abcs = get_abcs()
    # XXX: This potentialy has problems.
    # Before we only took leaf classes which means there can't be Plugin class and another class which only
    # slightly modifies it. Now we allow this situation, but we can't have ABC declared anywhere
    # else but this file because those ABCs will be interpreted as plugins and will raise exceptions.
    # (Those can be ignored and should be ignored in non-development environment).
    while classes:
        cls = classes.pop()
        subcls = cls.__subclasses__()
        # If the class is not ABC, it must be a plugin.
        if cls not in abcs:
                plugin_classes.append(cls)
        # If the class has subclasses process them as well.
        if subcls:
            classes += subcls

    # Initialize the plugin classes.
    initialized_plugins = {}
    for cls in plugin_classes:
        package_name = cls.__module__.split(".", 1)[0]
        cfg = plugin_config.get(package_name, {})
        class_id = "{0}.{1}".format(cls.__module__, cls.__name__)
        try:
            initialized_plugins[class_id] = cls(cfg)
        except:
            if ignore_exceptions:
                logging.error("Failed to initialize: {0}".format(class_id))
            else:
                raise

    return initialized_plugins

def get_by_type(plugin_type, initialized_plugins):
    """
    Get all plugins of specific type from the list of initialized plugins.


    :param plugin_type: plugin type to search for.
    :param initialized_plugins: list of initialized plugins where to search in

    :returns: list of all plugin of the given type
    """

    clsname = plugin_type.capitalize() + "Base"
    plugins = []
    for plugin in initialized_plugins.values():
        for base in plugin.__class__.__bases__:
            if clsname == base.__name__:
                plugins.append(plugin)
    return plugins