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.

Original source

Related problems