Concepts

Abstract Methods

Methods required in child plugins should be labeled as abstract methods. Plugins without these methods or with parameters that don’t match, will not be loaded.

Regular methods, static methods, class methods, properties, and attributes can all be designated as abstract. The process is slightly different depending on the type and what version of Python you are running.

Regular methods always use the @abstractmethod decorator.

@pluginlib.Parent('parser')
class Parser(object):

    @pluginlib.abstractmethod
    def parse(self, string):
        pass

For Python 3.3 and above, static methods, class methods, and properties also use the @abstractmethod decorator. Just place @abstractmethod as the innermost decorator.

@pluginlib.Parent('parser')
class Parser(object):

    @staticmethod
    @pluginlib.abstractmethod
    def abstract_staticmethod():
        return 'foo'

    @classmethod
    @pluginlib.abstractmethod
    def abstract_classmethod(cls):
        return cls.foo

    @property
    @pluginlib.abstractmethod
    def abstract_property(self):
        return self.foo

For code that must be compatible with older versions of Python, use the @abstractstaticmethod, @abstractclassmethod, and @abstractproperty decorators.

@pluginlib.Parent('parser')
class Parser(object):

    @pluginlib.abstractstaticmethod
    def abstract_staticmethod():
        return 'foo'

    @pluginlib.abstractclassmethod
    def abstract_classmethod(cls):
        return cls.foo

    @pluginlib.abstractproperty
    def abstract_property(self):
        return self.foo

Abstract attributes call also be defined, but no guarantee is made as to what kind of attribute the child plugin will have, just that the attribute is present. Abstract attributes are defined using abstractattribute.

@pluginlib.Parent('parser')
class Parser(object):
    abstract_attribute = pluginlib.abstractattribute

Versions

Plugin versions have two uses in Pluginlib:

  1. If multiple plugins with the same type and name are loaded, the plugin with the highest version is used when PluginLoader.plugins is accessed.
  2. Blacklists can filter plugins based on their version number.
  3. PluginLoader.plugins_all returns all unfiltered versions of plugins

Versions must be strings and should adhere to PEP 440. Version strings are evaluated using pkg_resources.parse_version().

By default, all plugins will have a version of None, which is treated as '0' when compared against other versions.

A plugin version can be set explicitly with the _version_ class attribute.

class NullParser(ParserParent):

    _version _ = '1.0.1'

    def parse(self, string):
        return string

If a plugin version is not explicitly set and the module it’s found in has a __version__ variable, the module version is used.

__version__ = '1.0.1'

class NullParser(ParserParent):

    def parse(self, string):
        return string

Conditional Loading

Sometimes a plugin child class is created that should not be loaded as a plugin. Examples include plugins only intended for specific environments and plugins inherited by additional plugins.

The _skipload_ attribute can be configured to prevent a plugin from loading. _skipload_ can be a Boolean, static method, or class method. If _skipload_ is a method, it will be called with no arguments.

Note

_skipload_ can not be inherited and must be declared directly in the plugin class it applies to.

_skipload_ as an attribute:

class ParserPlugin(ParserParent):

    _skipload_ = True

_skipload_ as a static method:

import platform

class ParserPlugin(ParserParent):

    @staticmethod
    def _skipload_():

        if platform.system() != 'Linux':
            return True, "Only supported on Linux"
        return False

_skipload_ as a class method:

import sys

class ParserPlugin(ParserParent):

    minimum_python = (3,4)

    @classmethod
    def _skipload_(cls):
        if sys.version_info[:2] < cls.minimum_python
            return True, "Not supported on this version of Python"
        return False

Blacklists

PluginLoader allows blacklisting plugins based on the plugin type, name, or version. Blacklists are implemented with the blacklist argument.

The blacklist argument to PluginLoader must an iterable containing either BlacklistEntry instances or tuples of arguments for creating BlacklistEntry instances.

The following are equivalent:

PluginLoader(blacklist=[BlacklistEntry('parser', 'json')])
PluginLoader(blacklist=[('parser', 'json')])

For information about blacklist entries, see BlacklistEntry in the API Reference.

Plugin Groups

By default, Pluginlib places all plugins in a single group. This may not be desired in all cases, such as when created libraries and frameworks. For these use cases, a group should be specified for the @Parent decorator and when creating a PluginLoader instance. Only plugins with a matching group will be available from the PluginLoader instance.

@pluginlib.Parent('parser', group='my_framework')
class Parser(object):

    @pluginlib.abstractmethod
    def parse(self, string):
        pass
loader = pluginlib.PluginLoader(modules=['sample_plugins'], group='my_framework')

Type Filters

By default, PluginLoader will provide plugins for all parent plugins in the same plugin group. To limit plugins to specific types, use the type_filter keyword.

loader = PluginLoader(library='myapp.lib')
print(loader.plugins.keys())
# ['parser', 'engine', 'hook', 'action']

loader = PluginLoader(library='myapp.lib', type_filter=('parser', 'engine'))
print(loader.plugins.keys())
# ['parser', 'engine']

Accessing Plugins

Plugins are accessed through PluginLoader properties and methods. In all cases, plugins that are filtered out through blacklists or type filters will not be returned.

Plugins are filtered each time these methods are called, so it is recommended to save the result to a variable.

PluginLoader.plugins
This property returns the newest version of each available plugin.
PluginLoader.plugins_all
This property returns all versions of each available plugin.
PluginLoader.get_plugin()
This method returns a specific plugin or None if unavailable.
loader = PluginLoader(library='myapp.lib')

plugins = loader.plugins
# {'parser': {'json': <class 'myapp.lib.JSONv2'>}}

plugins_all = loader.plugins_all
# {'parser': {'json': {'1.0': <class 'myapp.lib.JSONv1'>,
#                      '2.0': <class 'myapp.lib.JSONv2'>}}}

json_parser = loader.get_plugin('parser', 'json')
# <class 'myapp.lib.JSONv2'>

json_parser = loader.get_plugin('parser', 'json', '1.0')
# <class 'myapp.lib.JSONv1'>

json_parser = loader.get_plugin('parser', 'json', '4.0')
# None