Skip to content

Latest commit

 

History

History
943 lines (685 loc) · 46.9 KB

hooks.rst

File metadata and controls

943 lines (685 loc) · 46.9 KB

Hooks

General Concepts

Hooks are the smaller siblings of mixins <sec-plugins-mixins>, allowing to extend functionality or data processing where a custom mixin type would be too much overhead. Where mixins are based on classes, hooks are based on methods. Like with the mixin implementations, plugins inform OctoPrint about hook handlers using a control property, __plugin_hooks__.

This control property is a dictionary consisting of the implemented hooks' names as keys and either the hook callback or a 2-tuple of hook callback and order value as value.

Each hook defines a contract detailing the call parameters for the hook handler method and the expected return type. OctoPrint will call the hook with the define parameters and process the result depending on the hook.

An example for a hook within OctoPrint is octoprint.comm.protocol.scripts, which allows adding additional lines to OctoPrint's GCODE scripts <sec-features-gcode_scripts>, either as prefix (before the existing lines) or as postfix (after the existing lines).

self._gcode_hooks = self._pluginManager.get_hooks("octoprint.comm.protocol.scripts")

# ...

for hook in self._gcodescript_hooks:
    try:
        retval = self._gcodescript_hooks[hook](self, "gcode", scriptName)
    except:
        self._logger.exception("Error while processing gcodescript hook %s" % hook)
    else:
        if retval is None:
            continue
        if not isinstance(retval, (list, tuple)) or not len(retval) == 2:
            continue

        def to_list(data):
            if isinstance(data, str):
                data = map(str.strip, data.split("\n"))
            elif isinstance(data, unicode):
                data = map(unicode.strip, data.split("\n"))

            if isinstance(data, (list, tuple)):
                return list(data)
            else:
                return None

        prefix, suffix = map(to_list, retval)
        if prefix:
            scriptLines = list(prefix) + scriptLines
        if suffix:
            scriptLines += list(suffix)

As you can see, the hook's method signature is defined to take the current self (as in, the current comm layer instance), the general type of script for which to look for additions ("gcode") and the script name for which to look (e.g. beforePrintStarted for the GCODE script executed before the beginning of a print job). The hook is expected to return a 2-tuple of prefix and postfix if has something for either of those, otherwise None. OctoPrint will then take care to add prefix and suffix as necessary after a small round of preprocessing.

Plugins can easily add their own hooks too. For example, the Software Update Plugin declares a custom hook "octoprint.plugin.softwareupdate.check_config" which other plugins can add handlers for in order to register themselves with the Software Update Plugin by returning their own update check configuration.

If you want your hook handler to be an instance method of a mixin implementation of your plugin (for example since you need access to instance variables handed to your implementation via mixin invocations), you can get this work by using a small trick. Instead of defining it directly via __plugin_hooks__ utilize the __plugin_load__ property instead, manually instantiate your implementation instance and then add its hook handler method to the __plugin_hooks__ property and itself to the __plugin_implementation__ property. See the following example.

Execution Order

Hooks may also define an order number to allow influencing the execution order of the registered hook handlers. Instead of registering only a callback as hook handler, it is also possible to register a 2-tuple consisting of a callback and an integer value used for ordering handlers. They way this works is that OctoPrint will first sort all registered hook handlers with a order number, taking their identifier as the second sorting criteria, then after that append all hook handlers without a order number sorted only by their identifier.

An example should help clear this up. Let's assume we have the following plugin ordertest which defines a new hook called octoprint.plugin.ordertest.callback:

import octoprint.plugin

class OrderTestPlugin(octoprint.plugin.StartupPlugin):
    def get_sorting_key(self, sorting_context):
        return 10

    def on_startup(self, *args, **kwargs):
        self._logger.info("############### Order Test Plugin: StartupPlugin.on_startup called")
        hooks = self._plugin_manager.get_hooks("octoprint.plugin.ordertest.callback")
        for name, hook in hooks.items():
            hook()

    def on_after_startup(self):
        self._logger.info("############### Order Test Plugin: StartupPlugin.on_after_startup called")

__plugin_name__ = "Order Test"
__plugin_version__ = "0.1.0"
__plugin_implementation__ = OrderTestPlugin()

And these three plugins defining handlers for that hook:

import logging

 def callback(*args, **kwargs):
     logging.getLogger("octoprint.plugins." + __name__).info("Callback called in oneorderedhook")

 __plugin_name__ = "One Ordered Hook"
 __plugin_version__ = "0.1.0"
 __plugin_hooks__ = {
     "octoprint.plugin.ordertest.callback": (callback, 1)
 }
import logging

def callback(*args, **kwargs):
    logging.getLogger("octoprint.plugins." + __name__).info("Callback called in anotherorderedhook")

__plugin_name__ = "Another Ordered Hook"
__plugin_version__ = "0.1.0"
__plugin_hooks__ = {
    "octoprint.plugin.ordertest.callback": (callback, 2)
}
import logging

def callback(*args, **kwargs):
    logging.getLogger("octoprint.plugins." + __name__).info("Callback called in yetanotherhook")

__plugin_name__ = "Yet Another Hook"
__plugin_version__ = "0.1.0"
__plugin_hooks__ = {
    "octoprint.plugin.ordertest.callback": callback
}

Both orderedhook.py and anotherorderedhook.py not only define a handler callback in the hook registration, but actually a 2-tuple consisting of a callback and an order number. yetanotherhook.py only defines a callback.

OctoPrint will sort these hooks so that orderedhook will be called first, then anotherorderedhook, then yetanotherhook. Just going by the identifiers, the expected order would be anotherorderedhook, orderedhook, yetanotherhook, but since orderedhook defines a lower order number (1) than anotherorderedhook (2), it will be sorted before anotherorderedhook. If you copy those files into your ~/.octoprint/plugins folder and start up OctoPrint, you'll see output like this:

[...]
2016-03-24 09:29:21,342 - octoprint.plugins.ordertest - INFO - ############### Order Test Plugin: StartupPlugin.on_startup called
2016-03-24 09:29:21,355 - octoprint.plugins.oneorderedhook - INFO - Callback called in oneorderedhook
2016-03-24 09:29:21,357 - octoprint.plugins.anotherorderedhook - INFO - Callback called in anotherorderedhook
2016-03-24 09:29:21,358 - octoprint.plugins.yetanotherhook - INFO - Callback called in yetanotherhook
[...]
2016-03-24 09:29:21,861 - octoprint.plugins.ordertest - INFO - ############### Order Test Plugin: StartupPlugin.on_after_startup called
[...]

Available plugin hooks

Note

All of the hooks below take at least two parameters, *args and **kwargs. Make sure those are always present in your hook handler declaration. They will act as placeholders if additional parameters are added to the hooks in the future and will allow your plugin to stay compatible to OctoPrint without any necessary adjustments from you in these cases.

octoprint.accesscontrol.appkey

octoprint.cli.commands

octoprint.comm.protocol.action

octoprint.comm.protocol.gcode.<phase>

This describes actually four hooks:

  • octoprint.comm.protocol.gcode.queuing
  • octoprint.comm.protocol.gcode.queued
  • octoprint.comm.protocol.gcode.sending
  • octoprint.comm.protocol.gcode.sent

octoprint.comm.protocol.gcode.received

octoprint.comm.protocol.scripts

octoprint.comm.transport.serial.factory

octoprint.filemanager.extension_tree

octoprint.filemanager.preprocessor

octoprint.printer.factory

octoprint.server.http.bodysize

octoprint.server.http.routes

octoprint.ui.web.templatetypes