Creating Plugins

Contents:

What is a Plugin?

A plugin to pyitect is simply a folder with a .json config file of the same name as the folder inside. If you have yaml support enabled the extensions .yaml and .yml are also available

/Im-A-Plugin
    Im-A-Plugin.json
    file.py
/Im-A-Plugin2
    Im-A-Plugin2.yaml
    file.py
/Im-A-Plugin3
    Im-A-Plugin3.yml
    file.py

A plugin has a name, a version, an author, a module or package, and it provides Components used to build your application. a component is simply an object which can be accessed from the imported module a plugin’s config file provides information about the plugin as well as lists components it provides and components it needs on load

Here’s an example, most fields are mandatory but the consumes and provides CAN be left as empty containers

{
    "name": "Im-A-Plugin",
    "author": "author_name",
    "version": "0.0.1",
    "file": "file.py",
    "on_enable": "on_enable_func",
    "consumes": {
        "foo" : "*"
    },
    "provides": {
        "Bar": ""
    }
}

Here is the same file in yaml

name: Im-A-Plugin
author: author_name
version: 0.0.1
file: file.py
on_enable: on_enable_func # optional, runs this function when the plugin is enabled
consumes:
    foo: '*'
provides:
    Bar: ''

Version numbers should conform to conformed to Semantic Versioning meaning that they should have a major, minor, and patch number like so: major.minor.patch-prereleace+buildifo

  • name -> the name of the plugin (No spaces)
  • author -> the author of the plugin
  • version -> a version for the plugin, a string that conformes to SemVer
  • file -> a path to a function that will be called form the imported module after the plugin is loaded
  • consumes -> a mapping of needed component names to version requierments, empty string = no requirement
  • provides -> a mapping of provided component names to paths from the imported module, empty string = path is component name

Version Requirements

A plugin can provide version requirements for the components it’s importing. they take two forms, a version string or a version mapping.

A version string is formatted like so

plugin_name:<version_requirements>

Both parts are optional and an empty string or a string containing only a ‘*’ means no requirement. If there is no requirement specified then the highest available version will be selected from the first provider in alphabetical order.

if the version requirement is not give or given as * but the plugin name is then the highest available version will be selected from the names plugin

A version requirement is a logical operator paired with a version number. Any number of requirements can be grouped with commas.

Version numbers in requirements should also follow Semantic Versioning

Version requirement support is provided by the python-semanticversion project. specifically the Spec class. More documentation can be found here.

Here are some examples of a version string

""  // no requirement
"*" // no requirement
"FooPlugin" // from this plugin and no other, but any version
"FooPlugin:*" // from this plugin and no other, but any version
"FooPlugin:==1" // from this plugin and no other, version 1.x.x
"FooPlugin:==1.0" // 1.0.x
"FooPlugin:==1.0.1" // version 1.0.1 or any post release
"FooPlugin:==1.0.1-pre123" // 1.0.1-pre123 -> this exact version
"FooPlugin:==1.2" // 1.2.x and any pre/post/dev release
"FooPlugin:>1.0" // greater than 1.0
"FooPlugin:>=1.2.3" // greater than or equal to 1.2.3
"FooPlugin:<=2.1.4" // less than or equal to 2.1.4
"FooPlugin:>1.0,<2.3" // greater than 1.0 and less than 2.3
"FooPlugin:>1.0,<=2.0,!=1.3.17" // between V1.0.x and V2.0.x but not V1.3.17

Version requirements can also be given a a mapping. The mapping must contain the keys plugin and spec but this can allow for your requirement specification to be more clear.

Here is an example:

{
    "name": "Im-A-Plugin2",
    "author": "author_name",
    "version": "0.0.1",
    "file": "file.py",
    "consumes": {
        "foo" : {
            "plugin": "special_plugin_name",
            "spec": ">1.0,<=2.0,!=1.3.17"
        }
    },
    "provides": {
        "Bar": ""
    }
}

The spec key can also be a list of version specifications

{
    "consumes": {
        "foo" : {
            "plugin": "special_plugin_name",
            "spec": [">1.0", "<=2.0,!=1.3.17"]
        }
    }
}

Letting Plugins Access Consumed Components

inside your plugin files you need to get access to your consumed components right? Here’s how you do it.

The plugin can pull it’s declared components from pyitect.imports during the import of the module or package. pyitect.imports gets cleared after the import is done. So, the component imports from pyitect.imports should be in the top level of the module, not on demand imports in the code.

if a plugin author needs access to components not declared in the config file for run time use - ie. to load component on the fly - then they will need the system author to provide access to the plugin system instance.

Writing a Plugin

Writing a plugin for pyitect is simple.

First
Create a folder to hold your plugin
Second
Create a configuration file with the same name as the folder but with an extension. .json for a JSON config or .yaml/.yml for a YAML config
Third
Create your python module or package. Your plugin folder can even be your package folder
Forth

Write up your config for the plugin

Point the file attribute to your module file or package. If it’s a package point it to the __init__.py. It doesn’t matter if your module is pure python, byte-code compiled (.pyc) or a native extension (.pyd, .so)

A working plugin looks something like the following:

Folder Structure

/Im-A-Plugin
    Im-A-Plugin.json
    file.py

Im-A-Plugin.json

{
    "name": "plugin_name",
    "author": "author_name",
    "version": "0.1.0",
    "file": "<relative_path>",
    "on_enable": "<optional_function_path>",
    "consumes": {
        "foo" : "*"
    },
    "provides": {
        "Bar": ""
    }

file.py

#file.py
from pyitect.imports import foo

class Bar(object):
    def __init__():
        foo("it's a good day to be a plugin")