Plugins

Plugins are included using stevedore This means that any package installed with setup.py can be turned into a plugin.

Creating A Plugin

To create a simple plugin, you first need two files:

  • setup.py
  • plugin_name/__init__.py

setup.py holds the instructions on what your plugin needs to work, as well as where entrypoints live.

For our simple plugin we will use the following code

import os
from setuptools import setup, find_packages

setup(
    name="SimplePlugin",
    version='0.0.1',

    entry_points={
        'blueweather.plugins.plugin': [
            # The left side of the equals sign is the internal name of our
            # plugin, it can't contain spaces, and must be unique. It does
            # not have to be pretty.
            'simplePlugin = simplePlugin:SimplePlugin'
        ],
        'blueweather.plugins.startup': [
            # The right side of the equals sign is the location of the class.
            # The format is module.names:ClassName
            'simplePlugin = simplePlugin:SimplePlugin'
        ]
    }
)

The most import part of defining your plugin is its entrypoints. In the setup function, the parameter entry_points tells BlueWeather what type of plugins you are creating, as well as where its code lives.

For this example, we have defined two extensions:

  • blueweather.plugins.plugin
  • blueweather.plugins.startup

The Plugin extension gives usefull information about the plugin, and is required for all plugins. The Startup extension requests a message for when the server is up and running.

Next we will create the file simplePlugin/__init__.py

from blueweather.plugin import base

class SimplePlugin(base.Plugin, base.Startup):

    def get_plugin_name(self):
        return "Simple Plugin"

    def get_plugin_description(self):
        return "This is a simple plugin that prints 'Everything works!' when the server starts up"

    def on_startup(self):
        print("Everything works!")

This is where the code for the plugin lives. We have defined two required functions for the Plugin extension (get_plugin_name(), and get_plugin_description()) This simply gives information about our plugin to the user. Then from the Startup extension, we implement on_startup() where we print to the console that everything is working.

This is an exceedingly simple plugin, but it should be enough to get you started in creating your own plugins.

API Reference

Plugin The base plugin configuration.
Startup Gets a message when the server is up and running
API Allows for custom ReST APIs
Settings() Be able to interact with the settings for the plugin
Weather Plugin that Collects the weather
UnitConversion An Extension that can facilitate conversions
blueweather.plugins.hooks.get_hook(name) Create or retrieve a hook
blueweather.plugins.hooks.Hook(name, signature) Holds all the functions that are subscribed to this hook.

Plugin

class blueweather.plugins.base.Plugin

The base plugin configuration.

All Plugins must implement this extension

It gives usefull information to the user about the plugin

get_plugin_author() → list

Get the Author(s) of the plugin

Returns:authors
get_plugin_description() → str

Get the description for the plugin

Returns:plugin description
get_plugin_name() → str

Get the human readable name of the plugin

Returns:plugin name
get_plugin_url() → str

Get the plugin’s url

Returns:url

Startup

class blueweather.plugins.base.Startup

Gets a message when the server is up and running

on_startup()

Called when the server is ready

API

class blueweather.plugins.base.API

Allows for custom ReST APIs

get_api_urlpatterns() → List[Tuple[str, callable, str]]

get a list of url patterns for the api

the patterns should use django.urls patterns such as path

from . import views
return [
    ("data/", views.data, "data")
]

In each path, there are 3 parameters:

  1. endpoint (the url path) “data/” -> api/pluginName/data
  2. view (the function that can process the path)
  3. name (the name of the path) “data” -> api:pluginName:data

The namespace of the urls will be api:<pluginName>, so the name of a path will be api:<pluginName>:<pathName> (api:simplePlugin:data)

Returns:list of paths

Settings

class blueweather.plugins.base.Settings

Be able to interact with the settings for the plugin

on_settings_initialized()

Called after the settings have been initialized

After this has been called, the settings are garenteed to be available in self._settings as the deserialized object given by settings_deserialize()

settings_deserialize(data: dict) → object

Deserialize the settings from a dictionary into a custom settings object.

By default no changes are made, so the default settings object is a dict of primitives.

I recommend using Marshmallow to deserialize your settings.

Parameters:data – settings dict
Returns:deserialized settings object
settings_migrate(version: int, settings: dict) → Tuple[int, dict, bool]

Migrate the settings from an older version to the current version

Note

This function is always run, it is your job to determine if a migration is required.

Parameters:
  • version – the version of the settings
  • settings – the primative loaded settings
Returns:

version, updated settings, and whether the data has been changed

settings_serialize(obj) → dict

Serialize the settings object into a json serializeable dict.

This must undo whatever settings_deserialize does

Parameters:obj – settings object
Returns:settings primitives

Weather

class blueweather.plugins.base.Weather

Plugin that Collects the weather

on_weather_request() → Dict[str, Tuple[str, float]]

Collect the current weather

This should be returned as a dictionary of tuples. Each tuple contains the unit and value in that order:

{
    "temperature": ("c", 25.3),
    "wind_speed": ("m/s", 5.2)
}

While a unit does not need to be a standardized unit, it is important that it is a unit accessable to conversions through the UnitConversion extension. If you choose to use your own custom units, it is important that you make those units convertable by supplying your own conversion extension.

The following list contains units that are garanteed to be convertable

  • temperature: c
  • distance: m
  • mass: kg
  • time: s
  • speed: m/s
  • acceleration: m/s/s
  • force: N
  • pressure: Pa
  • energy: J
  • power: W
  • current: A
  • luminous: cd

Note

units are case-sensitive

Returns:weather data

UnitConversion

class blueweather.plugins.base.UnitConversion

An Extension that can facilitate conversions

This allows for custom conversion between units.

conversion_request(data: float, from_type: str, to_type: str) → float

Convert from one type to another.

When converting values, the first successful conversion will stop

Parameters:
  • data – value in the unit (from_type)
  • from_type – type to convert from
  • to_type – type to convert to
Returns:

converted value (if the conversion is not available, return None)

get_conversion_types() → List[Tuple[str, str]]

Get a list of available conversions in the form of tuples

Each conversion is in the form: from_type -> to_type

Returns:list of conversions

Hooks

Hooks are ways that plugins can interact with eachother. They should not be confused with stevedore’s hooks though.

A hook is a list of functions that can be called. At any point in time, anyone can create or subscribe to a hook. When the hook is called, all subscribed functions will be called.

get_hook

blueweather.plugins.hooks.get_hook(name) → blueweather.plugins.hooks.Hook

Create or retrieve a hook

With hooks, you can interact with other plugins in one of two ways

  1. Send a message to other subscribed plugins
  2. Subscribe to a hook that anyone can call.

Names should always be unique, so to prevent the possibility of clashes, the name of the plugin should be used as part of the hook name, such as “simplePlugin_hookName”

Returns:hook

Hook

class blueweather.plugins.hooks.Hook(name: str, signature: inspect.Signature)

Holds all the functions that are subscribed to this hook.

call(*args, **kwargs)

Call all the hooked functions without retreiving the return value

Parameters:
  • args – positional arguments
  • kwargs – keyword arguments
name

Name of the hook

request(*args, **kwargs) → list

Call all the hooked functions, and return a list of returned values

Parameters:
  • args – positional arguments
  • kwargs – keyword arguments
Returns:

list of return values

subscribe(name: str, func: callable)

Subscribe to the hook

When the hook is called, the function will be called

Note

The name should be unique, so to help prevent collisions, add/use the plugin name (simplePlugin)

Parameters:name – name of the subscriber
Parm func:function to subscribe
unsubscribe(name: str)

Unsubscribe from the hook

Parameters:name – name of the subscriber