Skip to main content

Plugin Framework

Project description

libkplug

libkplug is a simple framework for creating modular, configuration driven python applications.

Q: How is this different from just standard module development.

A: libkplug provides mechanisms to import and instantiate instances of classes by string identifiers, allowing for development of applications that can have behaviors altered or extended through config change alone.

Q: What are some example use-cases?

A1: Say I have developed a automation suite, which uses YAML files as a input source, with libkplug, I could abstract the YAML file loader into a named "ConfigReaderPlugin", and ship my automation suite with ConfigReaderPlugin set to the YAML file loader class 'YAMLFileLoader'. A user could then implement their own "ConfigReaderPlugin" which integrates with GIT, with the same contract as 'YAMLFileReader', and by allowing the user to set a variable 'ConfigPlugin=GitConfigLoader', the user has the means to integrate the application with ease.

Q: How do I instantiate plugins without having the type imported before-hand?

A: Code

# read the class of a plugin
clazz = libkplug.plugin_registry('SomePluginName')
# instantiate with args and kwargs as usual
myinst = clazz(key1=1, keyN='N')

Installation

source /my/venv/bin/activate
python setup.py install

Usage

Simple Example

This is a simple example, which will load the module plugins.plugin_helloworld, which contains a plugin HelloWorldPlugin. The program then instantiates a instance via the registry, using the plugin name as an argument.

# import libkplug
import libkplug
from libksettings import KSettings

# initialize settings with some defaults, suppressing config.yaml file loading
settings = KSettings(MY_HELLO_WORLD_CLASS='HelloWorldPlugin', PLUGINS=['plugins.plugin_helloworld'], load_yaml=False)

# get the class of the plugin
clazz = libkplug.plugin_registry(settings.MY_HELLO_WORLD_CLASS)

# instantiate it passing in args and kwargs if needed
inst = clazz(True, "Foo", kwarg1=1, kwargN="N")

# keep the program in a loop
while True:
    pass

The output

$> python run.py 
__init__.py:157 INFO Initializing plugin architecture
__init__.py:67 INFO Plugin Init: (), {}
__init__.py:55 INFO Loading yaml file: config.yaml
__init__.py:61 INFO Setting default MY_HELLO_WORLD_CLASS = HelloWorldPlugin
__init__.py:61 INFO Setting default PLUGINS = ['plugins.plugin_helloworld']
__init__.py:71 INFO Setting config KEY1 = VAL1
__init__.py:71 INFO Setting config PLUGINS = ['plugins.plugin_helloworld']
__init__.py:71 INFO Setting config MY_HELLO_WORLD_CLASS = HelloWorldPlugin
__init__.py:77 INFO Yaml loaded successful
__init__.py:83 INFO Plugins to load: ['plugins.plugin_helloworld']
__init__.py:86 INFO Attempting to load plugin: plugins.plugin_helloworld
__init__.py:80 INFO Registering Plugin id: HelloWorldPlugin from class: <class 'plugins.plugin_helloworld.HelloWorldPlugin'> 
run.py:22 INFO Starting up
__init__.py:117 INFO OrderedDict([('HelloWorldPlugin', <class 'plugins.plugin_helloworld.HelloWorldPlugin'>)])
__init__.py:118 INFO Module Unknown->run.py:25->HelloWorldPlugin
__init__.py:119 INFO Plugin Request: args: ('HelloWorldPlugin',), kwargs: {}
__init__.py:121 INFO Class: <class 'plugins.plugin_helloworld.HelloWorldPlugin'>
plugin_helloworld.py:16 INFO Instantiating instance of: HelloWorldPlugin args: () and kwargs: {'settings': <libksettings.KSettings object at 0x1094f1358>}
plugin_helloworld.py:38 INFO Thread-1: Sat Jan 19 09:35:24 2019
plugin_helloworld.py:38 INFO Thread-2: Sat Jan 19 09:35:26 2019
plugin_helloworld.py:38 INFO Thread-1: Sat Jan 19 09:35:26 2019

Sample Application

See example which loads config.yaml file, and starts a plugin which has two threads.

libkplug

libkplug provides the KPlugin base class, plugin_registry, and plugin registration decorators.

Simple example plugin:

import logging

# initializes the plugin system
import libkplug

# a inline plugin registration
@libkplug.plugin_registry.register
class HelloWorldPlugin(libkplug.KPlugin):
    # this is the identifier for when calling the plugin, should almost always be the class name
    plugin_name = 'HelloWorldPlugin'

    def __init__(self, *args, **kwargs):
        logging.info('Instantiating instance of: %s args: %s and kwargs: %s' % (self.plugin_name, args, kwargs))

    def hello(self, name="default"):
        logging.info("Hello %s from %s instance method" % (name, self))
        return name

    @staticmethod
    def hello_world():
        logging.info("Hello World")
        return "Hello World"

Initialize the Plugin System

>>> # init libkplug
>>> import libkplug
>>> # check current plugins
>>> print(libkplug.plugin_registry.plugins_dict)
OrderedDict()
>>> import plugins.plugin_helloworkd
>>> print(libkplug.plugin_registry.plugins_dict)
OrderedDict([('HelloWorldPlugin', <class 'plugins.plugin_helloworld.HelloWorldPlugin'>)])

Register a Plugin with KSettings

>>> import libksettings
>>> import libkplug
__init__.py:157 INFO Initializing plugin architecture
__init__.py:67 INFO Plugin Init: (), {}
>>> settings = libksettings.KSettings(PLUGINS=['plugins.plugin_helloworld'], load_yaml=False)
__init__.py:56 INFO Setting default PLUGINS = ['plugins.plugin_helloworld']
__init__.py:87 INFO YAML loading disabled
__init__.py:96 INFO Plugins to load: ['plugins.plugin_helloworld']
__init__.py:99 INFO Attempting to load plugin: plugins.plugin_helloworld
__init__.py:80 INFO Registering Plugin id: HelloWorldPlugin from class: <class 'plugins.plugin_helloworld.HelloWorldPlugin'> 
__init__.py:111 INFO Config: {'PLUGINS': ['plugins.plugin_helloworld']}
>>> 

Register a Plugin Manually

Importing any file containing classes decorated with @libkplug.plugin_registry.register will register the plugins. Just be sure to import libkplug before.

>>> import libkplug
__init__.py:157 INFO Initializing plugin architecture
__init__.py:67 INFO Plugin Init: (), {}
>>> import plugins.plugin_helloworld
__init__.py:80 INFO Registering Plugin id: HelloWorldPlugin from class: <class 'plugins.plugin_helloworld.HelloWorldPlugin'> 
>>> 

Plugin Static Methods

Static methods work as expected, and do not require instantiation of the plugin, it only needs to be loaded to work.

>>> import libkplug
>>> import plugins.plugin_helloworld
>>> libkplug.plugin_registry('HelloWorldPlugin').hello_world()
'Hello World'

Plugin Instantiation

Instantiation is done by determining the class, and then passing in kwargs to the constructor as usual.

>>> import libkplug
>>> import plugins.plugin_helloworld
>>> # read the class
>>> clazz = libkplug.plugin_registry('HelloWorldPlugin')
>>> # instantiate with kwargs
>>> myinst = clazz(key1=1, keyN='N')

libksettings

KSettings is designed to load yaml files, set defaults, and initialize plugins listed in PLUGINS.

IMPORTANT: KSettings is "global", so once instantiated with "defaults" and or a config, any other part of the application can import KSettings and access previously set values without reloading the config or redefining defaults. That said you might want to use namespaced defaults if plugins need to declare defaults.

Example of separate instances "sharing" config

>>> import libksettings
>>> settings1 = libksettings.KSettings(A='a', b=1, c=True, PLUGINS=[], load_yaml=False)
>>> settings2 = libksettings.KSettings(load_yaml=False)
>>> settings1.A == settings2.A
True

Example if separate instances declaring new defaults

>>> import libksettings
>>> foo_settings = libksettings.KSettings(A='a', b=1, c=True, PLUGINS=[], load_yaml=False, FOO_DEFAULT="foo")
>>> bar_settings = libksettings.KSettings(A='a', b=1, c=True, PLUGINS=[], load_yaml=False, BAR_DEFAULT="bar")
>>> foo_settings.BAR_DEFAULT
'bar'

When KSettings is instantiated, it will use all kwargs as 'default' initial values, then load the yaml file, overriding any defaults, finally it loads the Plugins, and returns a instance of itself.

Example initializing without config file, setting some defaults, and not loading plugins,

>>> from libksettings import KSettings
>>> settings = KSettings(A='a', b=1, c=True, PLUGINS=[], load_yaml=False)
>>> settings.A
'boo'
>>> settings.B
1

Example initializing with DEFAULTS and named config file, and default plugins

>>> from libksettings import KSettings
>>> settings = KSettings(config_filename='/tmp/config.yaml' A='a', b=1, c=True, PLUGINS=['plugins.plugin_helloworld'])
>>> settings.A
'boo'
>>> settings.B
1
>>> settings.SOME_YAML_KEY
'bar'

Example initializing with DEFAULTS and named config file from env var

>>> import os
>>> from libksettings import KSettings
>>> os.environ.setdefault('MY_CONF', '/tmp/other.yaml')
>>> settings = KSettings(config_filename_envvar='MY_CONF' A='a', b=1, c=True, PLUGINS=['plugins.plugin_helloworld'])
>>> settings.A
'boo'
>>> settings.B
1

Example initializing with config search locations

>>> import os
>>> from libksettings import KSettings
>>> os.environ.setdefault('MY_CONF', '/tmp/other.yaml')
>>> settings = KSettings(config_filename='myconf.ysml', config_load_locations=["/opt/mysvc", "/etc/mysvc"], A='a', b=1, c=True, PLUGINS=['plugins.plugin_helloworld'])
>>> settings.A
'boo'
>>> settings.B
1

License

    libkplug, a plugin system for python applications

    Copyright (C) 2013 Kegan Holtzhausen

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

Project details


Download files

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

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

libkplug-0.3.0-py2.py3-none-any.whl (20.0 kB view details)

Uploaded Python 2 Python 3

File details

Details for the file libkplug-0.3.0-py2.py3-none-any.whl.

File metadata

  • Download URL: libkplug-0.3.0-py2.py3-none-any.whl
  • Upload date:
  • Size: 20.0 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.3.0 pkginfo/1.7.0 requests/2.25.1 setuptools/54.0.0 requests-toolbelt/0.9.1 tqdm/4.58.0 CPython/3.9.1

File hashes

Hashes for libkplug-0.3.0-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 8d43b77541d1fda3de3983fd155bbe475940e4c0db081d588bd374f52af8fa1f
MD5 a0a6ab3a0cb244cf588f15989c8709b6
BLAKE2b-256 36c1fd44d1af24fb66427046d05f9ceb54f74a8cfc2ceab19a5aa394405bbf45

See more details on using hashes here.

Supported by

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