Plugin architecture - Plugin Manager vs inspecting from plugins import *
plugins, python
Solution
The metaclass approach is useful for this issue in Python < 3.6 (see @quasoft's answer for Python 3.6+). It is very simple and acts automatically on any imported module. In addition, complex logic can be applied to plugin registration with very little effort. It requires:
The metaclass approach works like the following:
1) A custom `PluginMount` metaclass is defined which maintains a list of all plugins
2) A `Plugin` class is defined which sets `PluginMount` as its metaclass
3) When an object deriving from `Plugin` - for instance `MyPlugin` is imported, it triggers the `__init__` method on the metaclass. This registers the plugin and performs any application specific logic and event subscription.
Alternatively if you put the `PluginMount.__init__` logic in `PluginMount.__new__` it is called whenver a new instance of a `Plugin` derived class is created.
class PluginMount(type):
"""
A plugin mount point derived from:
http://martyalchin.com/2008/jan/10/simple-plugin-framework/
Acts as a metaclass which creates anything inheriting from Plugin
"""
def __init__(cls, name, bases, attrs):
"""Called when a Plugin derived class is imported"""
if not hasattr(cls, 'plugins'):
# Called when the metaclass is first instantiated
cls.plugins = []
else:
# Called when a plugin class is imported
cls.register_plugin(cls)
def register_plugin(cls, plugin):
"""Add the plugin to the plugin list and perform any registration logic"""
# create a plugin instance and store it
# optionally you could just store the plugin class and lazily instantiate
instance = plugin()
# save the plugin reference
cls.plugins.append(instance)
# apply plugin logic - in this case connect the plugin to blinker signals
# this must be defined in the derived class
instance.register_signals()
Then a base plugin class which looks like:
class Plugin(object):
"""A plugin which must provide a register_signals() method"""
__metaclass__ = PluginMount
Finally, an actual plugin class would look like the following:
class MyPlugin(Plugin):
def register_signals(self):
print "Class created and registering signals"
def other_plugin_stuff(self):
print "I can do other plugin stuff"
Plugins can be accessed from any python module that has imported `Plugin`:
for plugin in Plugin.plugins:
plugin.other_plugin_stuff()
See the full working example
Problem
I'm currently writing an application which allows the user to extend it via a 'plugin' type architecture. They can write additional python classes based on a BaseClass object I provide, and these are loaded against various application signals. The exact number and names of the classes loaded as plugins is unknown before the application is started, but are only loaded once at startup. During my research into the best way to tackle this I've come up with two common solutions. Option 1 - Roll your own using imp, pkgutil, etc. See for instance, this answer or this one. Option 2 - Use a plugin manager library Randomly picking a couple - straight.plugin - yapsy - this approach My question is - on the proviso that the application must be restarted in order to load new plugins - is there any benefit of the above methods over something inspired from this SO answer and this one such as: ``` import inspect import sys import my_plugins def predicate(c): # filter to classes return inspect.isclass(c) def load_plugins(): for name, obj in inspect.getmembers(sys.modules['my_plugins'], predicate): obj.register_signals() ``` Are there any disadvantages to this approach compared to the ones above? (other than all the plugins must be in the same file) Thanks! EDIT Comments request further information... the only additional thing I can think to add is that the plugins use the blinker library to provide signals that they subscribe to. Each plugin may subscribe to different signals of different types and hence must have its own specific "register" method.
Related problems
- How can I get a list of all classes within current module in Python?
- How do you get all classes defined in a module but not imported?
- Dynamic class loading in Python 2.6: RuntimeWarning: Parent module 'plugins' not found while handling absolute import
- How can I import a module dynamically given its name as string?