Skip to content Skip to sidebar Skip to footer

Plugin Architecture - Plugin Manager Vs Inspecting From Plugins Import *

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 pr

Solution 1:

Since Python 3.6 a new class method __init_subclass__ is added, that is called on a base class, whenever a new subclass is created.

This method can further simplify the solution offered by will-hart above, by removing the metaclass.

The __init_subclass__ method was introduced with PEP 487: Simpler customization of class creation. The PEP comes with a minimal example for a plugin architecture:

It is now possible to customize subclass creation without using a metaclass. The new __init_subclass__ classmethod will be called on the base class whenever a new subclass is created:

classPluginBase:
    subclasses = []

    def__init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.subclasses.append(cls)

classPlugin1(PluginBase):
    passclassPlugin2(PluginBase):
    pass

The PEP example above stores references to the classes in the Plugin.plugins field.

If you want to store instances of the plugin classes, you can use a structure like this:

classPlugin:
    """Base class for all plugins. Singleton instances of subclasses are created automatically and stored in Plugin.plugins class field."""
    plugins = []

    def__init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.plugins.append(cls())

classMyPlugin1(Plugin):
    def__init__(self):
        print("MyPlugin1 instance created")

    defdo_work(self):
        print("Do something")

classMyPlugin2(Plugin):
    def__init__(self):
        print("MyPlugin2 instance created")

    defdo_work(self):
        print("Do something else")

for plugin in Plugin.plugins:
    plugin.do_work()

which outputs:

MyPlugin1 instance created
MyPlugin2 instance created
Do something
Do something else

Solution 2:

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.

classPluginMount(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"""ifnothasattr(cls, 'plugins'):
            # Called when the metaclass is first instantiated
            cls.plugins = []
        else:
            # Called when a plugin class is imported
            cls.register_plugin(cls)

    defregister_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:

classPlugin(object):
    """A plugin which must provide a register_signals() method"""
    __metaclass__ = PluginMount

Finally, an actual plugin class would look like the following:

classMyPlugin(Plugin):
    defregister_signals(self):
        print"Class created and registering signals"defother_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

Solution 3:

The approach from will-hart was the most useful one to me! For i needed more control I wrapped the Plugin Base class in a function like:

defget_plugin_base(name='Plugin',
                       cls=object,
                       metaclass=PluginMount):

    defiter_func(self):
        for mod in self._models:
            yield mod

    bases = notisinstance(cls, tuple) and (cls,) or cls

    class_dict = dict(
        _models=None,
        session=None
    )

    class_dict['__iter__'] = iter_func

    return metaclass(name, bases, class_dict)

and then:

from plugin importget_plugin_basePlugin= get_plugin_base()

This allows to add additional baseclasses or switching to another metaclass.

Post a Comment for "Plugin Architecture - Plugin Manager Vs Inspecting From Plugins Import *"