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:
- If multiple plugins with the same type and name are loaded, the plugin with the highest version is used when
PluginLoader.plugins
is accessed.- Blacklists can filter plugins based on their version number.
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