Skip to main content

a simple but powerful plugin mechanism for python

Project description

stoepsel - a simple plug-in system for python

Stoepsel (pronounce ʃtœpsl̩) is an attempt to create a simple (as in minimalistic) but flexible and powerful plugin system for python.

Stoepsel gives you the ability to develop flexible and scalable applications. It doesn't matter if you want to build a UI or console program. You just need to deploy a plugin class to register your application part.

Plugins can use other plugins. Therefore, you can register objects or functions in a model tree where other plugins can find them. To make this as safe as possible, a simple dependency resolving algorithm is implemented. Plugins are registered by their name and a version. They also can define dependencies that have to be resolved. (more on that later)

using stoepsel

installing stoepsel

you can install stoepsel by using pip

python3 -m pip install stoepsel

Or just initialize a virtualenv to install from source

python3 -m venv env
source env/bin/activate
python -m pip install .

running a stoepsel application

A simple stoepsel application can look like this:

import logging
from stoepsel import PluginManager

def main(args):
    logging.basicConfig(level=logging.DEBUG)

    # instanciate PluginManager
    pm = PluginManager()
    # find main and execute it
    pm.get_item(PluginManager.PGM_MAIN)()

    return 0

if __name__ == '__main__':
    import sys
    sys.exit(main(sys.argv))

In this case, stoepsel will look for folder named 'plugins' and read any .py- file into its registry. One file has to register (see below) the term 'main' or simply PluginManager.PGM_MAIN. This is the entry point for our application.

stoepsel configuration

It's possible to configure stoepsel and stoepsel plugins. Therefore, you can give a dictionary to PluginManager constructor which at least consists of 'plugin_path', a string which tells a directory where stoepsel looks for plugins and 'plugin_config' where plugin configuration can be stored.

config = {}
config['plugin_path'] = 'simple_plugins'
config['plugin_config'] = {}

pm = PluginManager(config)

You could also put this into a json based (or other) config file

with open('config.json') as fp:
    config = json.load(fp)
    pm = PluginManager(config)

creating a plugin

a plugin is a class which derives from stoepsel.Plugin. It needs to deploy static information about it's name, version and dependencies.

from stoepsel import Plugin, entrymethod

class MyPlugin(Plugin):
    name = 'simple_plugin'
    version = '0.0.1'
    dependencies = []

    @entrymethod(Plugin.PGM_MAIN)
    def main(self):
        print('Running around...')

It is basically possible to put several plugins into one python script. Any class which derives from Plugin will be loaded automatically.

you can also declare a method that will always run in an extra thread

from stoepsel import Plugin, entrymethod, threaded
import time

class MyThreadedPlugin(Plugin):
    name = 'threaded_plugin'
    version = '0.0.1'
    dependencies = []

    @entrymethod(Plugin.PGM_MAIN)
    @threaded
    def main(self):
        print('thread started...')
        time.sleep(1)
        print('thread stopped')

dependencies

a dependency is described by its plugin name and a version expression with ## between:

    dependencies = ['simple_plugin##0.0.1']

A version expression consists of an optional operator and a version number:

  • '>0.1.0' means higher than version 0.1.0. Versions 0.1.1 or 0.2.0 would match
  • '<0.1.0' means lower than version 0.1.0 all versions with 0.0.x would match
  • '==0.1.0' only version 0.1.0 matches (same as 0.1.0)
  • '>=0.1.0' means higher or equal
  • '<=0.1.0' means lower or equal
  • '!=0.1.0' all versions match, except 0.1.0

It is possible to combine version expressions by delimiting them with ;

  • '>=1.0;<2.0' all versions between 1.0 and lower than 2.0 match
  • '>1.0;!=1.0.3' all versions higher than 1.0 but not 1.0.3 (maybe its broken?)

If a dependency version does not match, stoepsel throws an exception.

registering objects

you can register objects by utilizing the Plugin method 'register'

def setup(self):
    self.register('myapp/plugins/plg1/sayhi', self.sayhi)

def sayhi(self):
    print('hello world')

another way is to use the entrymethod decorator. Attention: Since this is applied in the predefined Plugin_setup(), mixing this and self.register() does not work unless you add a super().setup().

@entrymethod
def sayhi(self):
    print('hello world')

using registered objects

to use registered objects you fave to find them via get_item method

def setup(self):
    sayhi = self.get_item('myapp/plugins/plg1/sayhi')
    if sayhi is not None:
        sayhi()

configuration

The configuration of the PluginManager is also set into the model tree so you can get it by utilizing get_item

self.get_item('config:plugin_config/myplugin')

this can be useful to create configuration dialogues. To make it easier there is a property config set:

print(self.config['plugin_config/mypath'])

Also a list of plugins can be read by using 'plugins:' as path or just

print(self.plugins)

Using stoepsel for CLI tools

It is possible to use stoepsel directly for cli tools by defining command line arguments for a plugin using the @argument decorator.

""" plugins/command_add.py
"""
from stoepsel import Plugin, entrymethod, argument

@argument("-a", "--summand-a", help="the first summand", type=int, default=0)
@argument("-b", "--summand-b", help="the second summand", type=int, default=0)
class MyPlugin(Plugin):
    name = 'add'
    version = '0.0.1'
    dependencies = []

    @entrymethod("add")
    def main(self, args):
        a = int(args.summand_a)
        b = int(args.summand_b)

        print(f"{a} + {b} = {a+b}")

Now, for using global arguments, it is possible to subclass PluginManager.

A possible main program can now look like this:

import sys
import logging

from stoepsel import PluginManager, argument

@argument("-v", "--verbose", help="tell me more", action="store_true")
class Program(PluginManager):
    """ this class is created to add global command line arguments
    """
    def __init__(self):
        """ initialize and run cli command
        """
        super().__init__()
        args = self.get_parser().parse_args()
        self.set_log_level(
            logging.DEBUG if args.verbose else logging.INFO
            )
        self.get_item(args.plugin_name)(args)

def main():
    logging.basicConfig(level=logging.INFO)
    pm = Program()

if __name__ == '__main__':
    sys.exit(main())

TODOs

  • find plugins recursively in plugin dir

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

stoepsel-0.4.1.tar.gz (17.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

stoepsel-0.4.1-py3-none-any.whl (15.7 kB view details)

Uploaded Python 3

File details

Details for the file stoepsel-0.4.1.tar.gz.

File metadata

  • Download URL: stoepsel-0.4.1.tar.gz
  • Upload date:
  • Size: 17.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.2

File hashes

Hashes for stoepsel-0.4.1.tar.gz
Algorithm Hash digest
SHA256 1e5eac72bbb94ff53904b7ee81606ce32324d14d120ab86cd407ff201a0395c9
MD5 be4a55550e1e3a3b192c3a7fa56afbaa
BLAKE2b-256 c3cb228e6f6c6d3c98d7050634ac1f73f7b6e06e793bab92392c523600a1318d

See more details on using hashes here.

File details

Details for the file stoepsel-0.4.1-py3-none-any.whl.

File metadata

  • Download URL: stoepsel-0.4.1-py3-none-any.whl
  • Upload date:
  • Size: 15.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.2

File hashes

Hashes for stoepsel-0.4.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d8e4cffea4bc35f80ae6bc10878f8bc4d3110c28f01b47dc140488e038254e9c
MD5 0a46df04f99156a403ad412e683485e4
BLAKE2b-256 3ccae7877384acd10c2ac438c7cd5a02b53e6f38195fa57ba1d7f5c7e9ab9f3a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page