.\" Man page generated from reStructuredText. . .TH "STRAIGHTPLUGIN" "1" "September 28, 2016" "1.4" "straight.plugin" .SH NAME straightplugin \- straight.plugin Documentation . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .SH GETTING STARTED .SS Install .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C pip install straight.plugin .ft P .fi .UNINDENT .UNINDENT .sp That was super easy. .SS Decide on a Namespace .sp You\(aqll want to decide on a \fInamespace\fP within your package where you\(aqll keep your own plugins and where other developers can add more plugins for your package to use. .sp For example, if you\(aqre writing a log filtering library named \fBlogfilter\fP you may choose \fBlogfilter.plugins\fP as a package to hold your plugins, so you\(aqll create the empty package as you would any other python package. However, the only contents of \fBlogfilter/plugins/__init__.py\fP will be a little bit of special code telling python this is a \fInamespace package\fP\&. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C # This file will not be needed in Python 3.3 from pkgutil import extend_path __path__ = extend_path(__path__, __name__) .ft P .fi .UNINDENT .UNINDENT .sp Now, any modules you place in this package are plugin modules able to be loaded by \fBstraight.plugin\fP\&. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C from straight.plugin import load plugins = load("logfilter.plugins") .ft P .fi .UNINDENT .UNINDENT .sp If you\(aqll be using more plugins than writing them, you should \fBread more\fP about the loaders available and how they work. .SS Write a Plugin .sp Writing a plugin is even easier than loading them. There are two important plugin types to learn: Module plugins and class Plugins. Every module in your \fInamespace package\fP is a module plugin. Every class they define is a class plugin. .sp When you load module plugins, you get all of them. .sp When you load class plugins, you filter them by a common base and only get those class plugins which inherit it. .sp Module plugins are simple and usually define a few functions with names expected by whoever is loading and using the plugins. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C # This is a module plugin def add_extra(data): if \(aqx\(aq in data and \(aqy\(aq in data: data[\(aqz\(aq] = x * y # This was a fairly useless plugin .ft P .fi .UNINDENT .UNINDENT .sp Class plugins are only a little longer, but can be a bit more controlled to work with. They depend on a common class the plugins inherit, and this would be defined by the project loading and using the plugins. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C # This is a class plugin class RstContentParser(ContentPlugin): """Parses any .rst files in a bundle.""" extensions = (\(aq.rst\(aq,) 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=\(aqhtml\(aq) return parts[\(aqhtml_body\(aq] .ft P .fi .UNINDENT .UNINDENT .sp 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 \fBsubclasses\fP parameter to \fBload()\fP\&. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C from straight.plugin import load plugins = load("jules.plugins", subclasses=ContentPlugin) .ft P .fi .UNINDENT .UNINDENT .sp The resulting set of plugins are all the classes found which inherit from ContentPlugin. You can do whatever you want with these, but there are some helpful tools to make it easier to work with Class plugins. .sp You can easily create instances of all the classes, which gives you a set of Instance plugins. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C instances = plugins.produce() .ft P .fi .UNINDENT .UNINDENT .sp You can even pass initialization parameters to \fBproduce()\fP and they\(aqll be used when creating instances of all the classes. You can see the \fIAPI docs\fP for the \fBPluginManager\fP to see the other ways you can work with groups of plugins. .SH WRITING PLUGINS .sp Plugins can exist inside your existing packages or in special namespace packages, which exist only to house plugins. .sp The only requirement is that any package containing plugins be designated a "namespace package", which is currently performed in Python via the \fBpkgutil.extend_path\fP utility, seen below. This allows the namespace to be provided in multiple places on the python \fBsys.path\fP, where \fBimport\fP looks, and all the contents will be combined. .sp Use a \fInamespace package\fP .sp 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. .SS Example .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C # logfilter/__init__.py from pkgutil import extend_path __path__ = extend_path(__path__, __name__) .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C # logfilter/hide_extra.py from logfilter import Skip def filter(log_entry): level = log_entry.split(\(aq:\(aq, 1)[0] if level != \(aqEXTRA\(aq: return log_entry else: raise Skip() .ft P .fi .UNINDENT .UNINDENT .SS Using the plugin .sp In our log tool, we might load all the modules in the \fBlogfilter\fP namespace, and then use them all to process each entry in our logs. We don\(aqt need to know all the filters ahead of time, and other packages can be installed on a user\(aqs system providing additional modules in the namespace, which we never even knew about. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C from straight.plugin import load class Skip(Exception): pass plugins = load(\(aqlogfilter\(aq) def filter_entry(log_entry): for plugin in plugins: try: log_entry = plugin.filter(log_entry) except Skip: pass return log_entry .ft P .fi .UNINDENT .UNINDENT .SS Distributing Plugins .sp If you are writing plugins inside your own project to use, they\(aqll be distributed like any other modules in your package. There is no extra work to do here. .sp However, if you want to release and distribute plugins on their own, you\(aqll need to tell your \fIsetup.py\fP about your \fInamespace package\fP\&. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C setup( # ... namespace_packages = [\(aqlogfilter.plugins\(aq] ) .ft P .fi .UNINDENT .UNINDENT .sp This will make sure when your plugins are installed alongside the original project, both are importable, even though they came from their own distributions. .sp You can read more about this at the Distribute \fI\%documentation on namespace packages\fP\&. .SH PLUGIN LOADERS .sp Currently, three simple loaders are provided. .INDENT 0.0 .IP \(bu 2 The \fIModuleLoader\fP simply loads the modules found .IP \(bu 2 The \fIClassLoader\fP loads the subclasses of a given type .IP \(bu 2 The \fIObjectLoader\fP loads arbitrary objects from the modules .UNINDENT .SS ClassLoader .sp The recommended loader is the \fBClassLoader\fP, used to load all the classes from all of the modules in the namespace given. Optionally, you can pass a \fBsubclasses\fP parameter to \fBload()\fP, which will filter the loaded classes to those which are a sub\-class of any given type. .sp For example, .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C import os from straight.plugin.loaders import ClassLoader from myapp import FileHandler plugins = ClassLoader().load(\(aqmyplugins\(aq, subclasses=FileHandler) for filename in os.listdir(\(aq.\(aq): for handler_cls in plugins: handler = handler_cls(filename) if handler.valid(): handler.process() .ft P .fi .UNINDENT .UNINDENT .sp However, it is preferred that you use the \fBload()\fP helper provided. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C from straight.plugin import load plugins = load(\(aqmyplugins\(aq, subclasses=FileHandler) .ft P .fi .UNINDENT .UNINDENT .sp This will automatically use the \fBClassLoader\fP when given a \fBsubclasses\fP argument. .SS ModuleLoader .sp Before anything else, \fBstraight.plugin\fP loads modules. The \fBModuleLoader\fP is used to do this. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C from straight.plugin.loaders import ModuleLoader plugins = ModuleLoader().load(\(aqmyplugins\(aq) .ft P .fi .UNINDENT .UNINDENT .sp A note about \fI\%PEP\-420\fP: .sp Python 3.3 will support a new type of package, the Namespace Package. This allows language\-level support for the namespaces that make \fBstraight.plugin\fP work and when 3.3 lands, you can create addition plugins to be found in a namespace. For now, continue to use the \fBpkgutil\fP boilerplate, but when 3.3 is released, \fBstraight.plugin\fP already supports both forms of namespace package! .SS ObjectLoader .sp If you need to combine multiple plugins inside each module, you can load all the objects from the modules, rather than the modules themselves. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C from straight.plugin.loaders import ObjectLoader plugins = ObjectLoader().load(\(aqmyplugins\(aq) .ft P .fi .UNINDENT .UNINDENT .SH STRAIGHT PLUGIN API .SS Loaders .INDENT 0.0 .TP .B 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 \fBsubclasses\fP parameter. .UNINDENT .INDENT 0.0 .TP .B class straight.plugin.loaders.Loader(*args, **kwargs) Base loader class. Only used as a base\-class for other loaders. .UNINDENT .INDENT 0.0 .TP .B class straight.plugin.loaders.ModuleLoader(recurse=False) Performs the work of locating and loading straight plugins. .sp This looks for plugins in every location in the import path. .UNINDENT .INDENT 0.0 .TP .B class straight.plugin.loaders.ObjectLoader(recurse=False) Loads classes or objects out of modules in a namespace, based on a provided criteria. .sp The load() method returns all objects exported by the module. .UNINDENT .INDENT 0.0 .TP .B class straight.plugin.loaders.ClassLoader(recurse=False) Loads classes out of plugin modules which are subclasses of a single given base class. .UNINDENT .SS PluginManager .INDENT 0.0 .TP .B class straight.plugin.manager.PluginManager(plugins) .INDENT 7.0 .TP .B call(methodname, *args, **kwargs) Call a common method on all the plugins, if it exists. .UNINDENT .INDENT 7.0 .TP .B first(methodname, *args, **kwargs) Call a common method on all the plugins, if it exists. Return the first result (the first non\-None) .UNINDENT .INDENT 7.0 .TP .B 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. .sp Useful to utilize plugins as sets of filters. .UNINDENT .INDENT 7.0 .TP .B produce(*args, **kwargs) Produce a new set of plugins, treating the current set as plugin factories. .UNINDENT .UNINDENT .SH GLOSSARY .INDENT 0.0 .TP .B distribution Separately installable sets of Python modules as stored in the Python package index, and installed by distutils or setuptools. .sp \fIdefinition taken from\fP \fI\%PEP 382\fP \fItext\fP .TP .B module An importable python namespace defined in a single file. .TP .B namespace package Mechanism for splitting a single Python package across multiple directories on disk. One or more distributions (see \fIdistribution\fP) may provide modules which exist inside the same \fInamespace package\fP\&. .sp \fIdefinition taken from\fP \fI\%PEP 382\fP \fItext\fP .TP .B package A Python package is a module defined by a directory, containing a \fB__init__.py\fP file, and can contain other modules or other packages within it. .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C package/ __init__.py subpackage/ __init__.py submodule.py .ft P .fi .UNINDENT .UNINDENT .sp see also, \fInamespace package\fP .TP .B vendor package Groups of files installed by an operating system\(aqs packaging mechanism (e.g. Debian or Redhat packages install on Linux systems). .sp \fIdefinition taken from\fP \fI\%PEP 382\fP \fItext\fP .UNINDENT .sp Straight Plugin is very easy. .sp 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. .sp Using any available plugins is a snap. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C from straight.plugin import load plugins = load(\(aqtheproject.plugins\(aq, subclasses=FileHandler) handlers = plugins.produce() for line in open(filename): print handlers.pipe(line) .ft P .fi .UNINDENT .UNINDENT .sp And, writing plugins is just as easy. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C 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) .ft P .fi .UNINDENT .UNINDENT .sp Plugins are found from a \fInamespace\fP, which means the above example would find any \fBFileHandler\fP classes defined in modules you might import as \fBtheproject.plugins.default\fP or \fBtheproject.plugins.extra\fP\&. Through the magic of \fInamespace packages\fP, 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 \fInamespace\fP\&. .sp \fBGet started and learn more, today\fP .SH MORE RESOURCES .INDENT 0.0 .IP \(bu 2 Full Documentation: \fI\%http://readthedocs.org/docs/straightplugin/\fP .IP \(bu 2 Mailing List: \fI\%https://groups.google.com/forum/#!forum/straight.plugin\fP .UNINDENT .INDENT 0.0 .IP \(bu 2 \fIgenindex\fP .IP \(bu 2 \fImodindex\fP .IP \(bu 2 \fIsearch\fP .UNINDENT .SH AUTHOR Calvin Spealman .SH COPYRIGHT 2012, Calvin Spealman .\" Generated by docutils manpage writer. .