STRAIGHTPLUGIN(1)

πŸ“¦
version 1.4.1 Β· 5 versions
, in ubuntu resolute, ubuntu stonking

πŸ’– STRAIGHTPLUGIN πŸ’–

okay bestie, welcome to the straight.plugin docs, aka manual page straightplugin (yeah it's STRAIGHTPLUGIN(1), section 1, we're official πŸ’…). this is the whole glow-up tour of how to install it, how to write plugins, how to load them, and the full API. buckle up, it's giving ✨documentation✨.


🌸 NAME 🌸

straightplugin β€” straight.plugin Documentation. that's it, that's the name, and honestly? iconic.


πŸŽ€ GETTING STARTED πŸŽ€

Install

pip install straight.plugin

That was super easy.

no cap. one line. you're already winning 🫢

Decide on a Namespace

okay so first vibe check: you need to pick a namespace inside your package. that's the little corner where you keep your plugins and where other devs can drop in more plugins for your package to use. sharing is caring πŸ’–

for example, if you're writing a log filtering library named logfilter, you might pick logfilter.plugins as the package that holds your plugins. so you create the empty package like you would any other python package. BUT β€” and this is the twist β€” the only thing inside logfilter/plugins/__init__.py is a tiny bit of special code telling python this is a namespace package.

# This file will not be needed in Python 3.3
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

and now? every module you toss into that package is a plugin module that straight.plugin can load. periodt. πŸ’…

from straight.plugin import load

plugins = load("logfilter.plugins")

if you're gonna be using more plugins than writing, you should go read more about the loaders available and how they work. πŸ‘‘

Write a Plugin

writing a plugin is even easier than loading them, bestie, it's not even a competition. there are two important plugin types you need to learn: Module plugins and class Plugins. every module in your namespace package is a module plugin. every class they define is a class plugin.

  • when you load module plugins, you get all of them. no filtering, no gatekeeping.
  • when you load class plugins, you filter them by a common base, and you only get the class plugins that inherit it.

Module plugins are simple and usually define a few functions with names expected by whoever is loading and using the plugins.

# This is a module plugin

def add_extra(data):
    if 'x' in data and 'y' in data:
        data['z'] = x * y

# This was a fairly useless plugin

(their words, not mine 😭)

Class plugins are only a little longer, but can be a bit more controlled to work with. they depend on a common class that the plugins inherit, and that class would be defined by the project loading and using the plugins.

# This is a class plugin

class RstContentParser(ContentPlugin):
    """Parses any .rst files in a bundle."""

    extensions = ('.rst',)

    def parse(self, content_file):
        src = content_file.read()
        return self.parse_string(src)

    def parse_string(self, src):
        parts = publish_parts(source=src, writer_name='html')
        return parts['html_body']

you can fit as many class plugins inside a module plugin as you want, and to load them instead of the modules you simply pass a subclasses parameter to load().

from straight.plugin import load

plugins = load("jules.plugins", subclasses=ContentPlugin)

the resulting set of plugins is all the classes found that inherit from ContentPlugin. you can do whatever you want with these, but there are some helpful tools that make working with Class plugins easier. 🧰

you can easily create instances of all the classes, which gives you a set of Instance plugins.

instances = plugins.produce()

you can even pass initialization parameters to produce() and they'll be used when creating instances of all the classes. for the other ways you can work with groups of plugins, go look at the PluginManager API docs. πŸ“š


πŸ¦‹ WRITING PLUGINS πŸ¦‹

plugins can live inside your existing packages, or in special namespace packages that exist only to house plugins. both are valid, both are cute.

the only requirement: any package containing plugins has to be designated a "namespace package", which is currently done in Python via the pkgutil.extend_path utility, seen below. this lets the namespace be provided in multiple places on the python sys.path (where import looks), and all the contents get combined. it's giving group project that actually works. 🌈

Use a namespace package.

this allows multiple packages installed on your system to share this name, so they may come from different installed projects and all combine to provide a larger set of plugins. slay.

Example

# logfilter/__init__.py

from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

# logfilter/hide_extra.py

from logfilter import Skip

def filter(log_entry):
    level = log_entry.split(':', 1)[0]
    if level != 'EXTRA':
        return log_entry
    else:
        raise Skip()

Using the plugin

in our log tool, we might load all the modules in the logfilter namespace, and then use them all to process each entry in our logs. we don't need to know all the filters ahead of time, and other packages can be installed on a user's system providing additional modules in the namespace β€” modules we never even knew about. main character energy fr fr. ✨

from straight.plugin import load

class Skip(Exception):
    pass

plugins = load('logfilter')

def filter_entry(log_entry):
    for plugin in plugins:
        try:
            log_entry = plugin.filter(log_entry)
        except Skip:
            pass
    return log_entry

Distributing Plugins

if you're writing plugins inside your own project just to use them yourself, they get distributed like any other modules in your package. there is no extra work to do here. πŸŽ‰

BUT β€” if you want to release and distribute plugins on their own, you'll need to tell your setup.py about your namespace package.

setup(
    # ...
    namespace_packages = ['logfilter.plugins']
)

this makes sure that when your plugins are installed alongside the original project, both are importable, even though they came from their own distributions. πŸ’«

you can read more about this at the Distribute documentation on namespace packages: http://packages.python.org/distribute/setuptools.html#namespace-packages.


πŸ”₯ PLUGIN LOADERS πŸ”₯

currently, three simple loaders are provided.

  • the ModuleLoader simply loads the modules found
  • the ClassLoader loads the subclasses of a given type
  • the ObjectLoader loads arbitrary objects from the modules

ClassLoader

the recommended loader is the ClassLoader, used to load all the classes from all of the modules in the namespace given. optionally, you can pass a subclasses parameter to load(), which filters the loaded classes down to those which are a sub-class of any given type. πŸ‘‘

for example:

import os
from straight.plugin.loaders import ClassLoader
from myapp import FileHandler

plugins = ClassLoader().load('myplugins', subclasses=FileHandler)

for filename in os.listdir('.'):
    for handler_cls in plugins:
        handler = handler_cls(filename)
        if handler.valid():
            handler.process()

however, it is preferred that you use the load() helper provided.

from straight.plugin import load

plugins = load('myplugins', subclasses=FileHandler)

this will automatically use the ClassLoader when given a subclasses argument. convenient? iconic? yes and yes. πŸ’…

ModuleLoader

before anything else, straight.plugin loads modules. the ModuleLoader is used to do this.

from straight.plugin.loaders import ModuleLoader

plugins = ModuleLoader().load('myplugins')

a note about PEP-420 (http://www.python.org/dev/peps/pep-0420/):

Python 3.3 will support a new type of package, the Namespace Package. this allows language-level support for the namespaces that make straight.plugin work, and when 3.3 lands, you can create addition plugins to be found in a namespace. for now, continue to use the pkgutil boilerplate, but when 3.3 is released, straight.plugin already supports both forms of namespace package! 😭πŸ”₯

ObjectLoader

if you need to combine multiple plugins inside each module, you can load all the objects from the modules, rather than the modules themselves.

from straight.plugin.loaders import ObjectLoader

plugins = ObjectLoader().load('myplugins')

πŸ‘‘ STRAIGHT PLUGIN API πŸ‘‘

Loaders

  • straight.plugin.loaders.unified_load(namespace, subclasses=None, recurse=False) β€” provides a unified interface to both the module and class loaders, finding modules by default or classes if given a subclasses parameter.
  • class straight.plugin.loaders.Loader(*args, **kwargs) β€” base loader class. only used as a base-class for other loaders.
  • class straight.plugin.loaders.ModuleLoader(recurse=False) β€” performs the work of locating and loading straight plugins. this looks for plugins in every location in the import path.
  • class straight.plugin.loaders.ObjectLoader(recurse=False) β€” loads classes or objects out of modules in a namespace, based on a provided criteria. the load() method returns all objects exported by the module.
  • class straight.plugin.loaders.ClassLoader(recurse=False) β€” loads classes out of plugin modules which are subclasses of a single given base class.

PluginManager

  • class straight.plugin.manager.PluginManager(plugins)
    • call(methodname, *args, **kwargs) β€” call a common method on all the plugins, if it exists.
    • first(methodname, *args, **kwargs) β€” call a common method on all the plugins, if it exists. return the first result (the first non-None).
    • pipe(methodname, first_arg, *args, **kwargs) β€” call a common method on all the plugins, if it exists. the return value of each call becomes the replaces the first argument in the given argument list to pass to the next. useful to utilize plugins as sets of filters.
    • produce(*args, **kwargs) β€” produce a new set of plugins, treating the current set as plugin factories.

πŸ¦‹ GLOSSARY πŸ¦‹

ok bestie, vocab check time β€” these are the words we're throwing around all page, so let's get them locked in ✨

  • distribution β€” separately installable sets of Python modules as stored in the Python package index, and installed by distutils or setuptools.
definition taken from PEP 382 <http://www.python.org/dev/peps/pep-0382/> text
  • module β€” an importable python namespace defined in a single file.
  • namespace package β€” the mechanism for splitting a single Python package across multiple directories on disk. One or more distributions (see distribution) may provide modules which exist inside the same namespace package.
definition taken from PEP 382 <http://www.python.org/dev/peps/pep-0382/> text
  • package β€” a Python package is a module defined by a directory, containing a __init__.py file, and can contain other modules or other packages within it.
package/
    __init__.py
    subpackage/
        __init__.py
    submodule.py

see also, namespace package

  • vendor package β€” groups of files installed by an operating system's packaging mechanism (e.g. Debian or Redhat packages install on Linux systems).
definition taken from PEP 382 <http://www.python.org/dev/peps/pep-0382/> text

Straight Plugin is very easy. πŸ’…

Straight Plugin provides a type of plugin you can create from almost any existing Python modules, and an easy way for outside developers to add functionality and customization to your projects with their own plugins.

Using any available plugins is a snap. πŸ“

from straight.plugin import load

plugins = load('theproject.plugins', subclasses=FileHandler)

handlers = plugins.produce()
for line in open(filename):
    print handlers.pipe(line)

And, writing plugins is just as easy. πŸŽ€

from theproject import FileHandler

class LineNumbers(FileHandler):
    def __init__(self):
        self.lineno = 0
    def pipe(line):
        self.lineno += 1
        return "%04d %s" % (self.lineno, line)

Plugins are found from a namespace, which means the above example would find any FileHandler classes defined in modules you might import as theproject.plugins.default or theproject.plugins.extra. Through the magic of namespace packages <#term-namespace-package>, we can even split these up into separate installations, even managed by different teams. This means you can ship a project with a set of default plugins implementing its behavior, and allow other projects to hook in new functionality simply by shipping their own plugins under the same namespace. πŸͺ„ lowkey iconic, fr fr

Get started and learn more, today <>


🌈 MORE RESOURCES 🌈

the lore, if u will:


πŸ‘‘ Author

Calvin Spealman

2012, Calvin Spealman


aaaand that's a wrap bestie, you made it to the end, main character energy the whole way through 🫢 this was STRAIGHTPLUGIN(1) at version 1.4, dated Jan 29, 2026 β€” go forth and plugin, periodt πŸ’…βœ¨πŸŒΈ

πŸ“¦ where this page comes from