Skip to main content

Puzzle Plugin System

This repo helps implementing a plugin system for Python applications. It uses setuptools entry points for plugin discovery and blinker signals to propagate events between loosely connected components. Both aspects are strictly separated so you can use the setuptools part without blinker signalling (or the other way around).

$ pip install PuzzlePluginSystem

Events and Signals (blinker)

Plugins can "connect" (register) to specific blinker signals. When the main application triggers a signal a subscribed plugin handler is called.

Example application:

# "myapp.plugins" module
from schwarz.puzzle_plugins import SignalRegistry

registry = SignalRegistry()

class AppSignal:
    foo = 'myapp:foo'

# need to trigger plugin registration here (see "Plugin Discovery" section below)

# trigger a signal to call some plugin code and get the returned value
result = registry.call_plugin(AppSignal.foo, signal_kwargs={'a': 137})

This is plugin code (e.g. myplugin.py):

from schwarz.puzzle_plugins import connect_signals, disconnect_signals
from myapp.plugins import AppSignal

class MyPlugin:
    def __init__(self, registry):
        self._connected_signals = None
        self._registry = registry

    def signal_map(self):
        return {
            AppSignal.foo: handle_foo,
        }


# The main application must call this function on startup.
# See "Plugin Discovery" section on how to do this.
def initialize(context, registry):
    plugin = MyPlugin(registry)
    plugin._connected_signals = connect_signals(plugin.signal_map(), registry)
    context['plugin'] = plugin

def terminate(context):
    plugin = context['plugin']
    disconnect_signals(plugin._connected_signals, plugin._registry)
    plugin._registry = None
    plugin._connected_signals = None


# --- actual plugin functionality -----------------------------------------
def handle_foo(sender, a=21):
    return a * 2

SignalRegistry: triggering plugin functionality

registry.call_plugins(signal_name, signal_kwargs={…}, expect_single_result=False) is convenience method to get return values from all plugins registered for the specified signal.

If you pass expect_single_result=True this means you still get only a single (scalar) result value. If multiple plugins return a non-None value ValueError is raised.

Plugin Discovery (setuptools)

The blinker signalling above requires that plugins subscribe to specific signals before the main application triggers a signal. When all plugins are known while writing the main application you can just insert the right calls in the startup routine and everything will be fine.

However I believe the more common scenario (and usually main motivation to introduce a plugin system) is to separate plugins from the main application. For example several customers could use the same base software but different plugins which add customer-specific functionality. In this situation the main application must be able to discover and activate available plugins. This is done with the help of setuptools' entry points.

Example:

Create a separate setuptools-project for your plugin. Add your code (for example as shown in the "Events and Signals" section above).

# file: setup.cfg
[options.entry_points]
myapp.plugins =
    MyPlugin = my_plugin

The my_plugin module must contain two functions which are called by the main application:

  • initialize(context, registry)
  • terminate(context)

context is just a dict where the plugin can store arbitrary state. The main application will keep the context and pass the same instance to terminate().

The main application needs to initialize the plugins at startup. If you use blinker-based signalling you must keep the plugin_loader instance during the whole lifetime of the application. When the instance is garbage collected all blinker signal connections will be lost.

from schwarz.puzzle_plugins import parse_list_str, PluginLoader
from myapp.plugins import registry

def initialize_plugins():
    # This string is usually stored in the application config.
    # Use "*" to enable all plugins.
    plugin_config_str = 'MyPlugin, OtherPlugin'
    enabled_plugins = parse_list_str(plugin_config_str)
    plugin_loader = PluginLoader('myapp.plugins', enabled_plugins=enabled_plugins)
    plugin_loader.initialize_plugins(registry)
    return plugin_loader

Metadata

Release files for PuzzlePluginSystem 0.8.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for PuzzlePluginSystem 0.8.0
File Size Uploaded
puzzlepluginsystem-0.8.0.tar.gz 8.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for PuzzlePluginSystem 0.8.0
File Interpreter ABI Platform
puzzlepluginsystem-0.8.0-py3-none-any.whl Python 3 none any Details

Total release size: 15.0 kB

Release files / puzzlepluginsystem-0.8.0.tar.gz

Download URL puzzlepluginsystem-0.8.0.tar.gz
Size 8.1 kB
Tags Source
SHA-256 checksum
How to use checksums
07d0d3abcafb9eb7f0bed0b4cdbfd8917171f880671cef1640b36e0a01a3825e
BLAKE2b-256 checksum
How to use checksums
ffc6c63aa1e85c10eeca611b5c9985fd9f39b2153871d3c98a9b6b0f6253ed84
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.3

Release files / puzzlepluginsystem-0.8.0-py3-none-any.whl

Download URL puzzlepluginsystem-0.8.0-py3-none-any.whl
Size 6.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f0ab6a92cbb9b3d5ab48e98a4e4aafa1d989ec8255e1e8040b440e3a20a256b4
BLAKE2b-256 checksum
How to use checksums
1c3b49b0aed13e0cb961294aee53e652e72a24edf7bf53ba20a529087d1b62bf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.3

Release history Release notifications | RSS feed

This release

0.8.0 This release

2 release files

0.7.0

2 release files

0.6.1

1 release file

0.6.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page