Refactored plugin docs + improved docs on sortables

This commit is contained in:
Gina Häußge 2016-03-24 16:42:41 +01:00
parent 6d95a5849b
commit ee185230a4
11 changed files with 658 additions and 471 deletions

View file

@ -1,444 +1,39 @@
.. _sec-plugin-concepts:
Concepts
========
.. contents::
:local:
General Concepts
================
OctoPrint's plugins are `Python Packages <https://docs.python.org/2/tutorial/modules.html#packages>`_ which in their
top-level module define a bunch of :ref:`control properties <sec-plugin-concepts-controlproperties>` defining
top-level module define a bunch of :ref:`control properties <sec-plugins-controlproperties>` defining
metadata (like name, version etc of the plugin) as well as information on how to initialize the plugin and into what
parts of the system the plugin will actually plug in to perform its job.
There are three types of ways a plugin might attach itself to the system, through so called
:ref:`mixin <sec-plugin-concepts-mixins>` implementations, by attaching itself to specified
:ref:`hook <sec-plugin-concepts-hooks>` or by offering :ref:`helper <sec-plugin-concepts-helpers>` functionality to be
:ref:`mixin <sec-plugins-mixins>` implementations, by attaching itself to specified
:ref:`hook <sec-plugins-hooks>` or by offering :ref:`helper <sec-plugins-helpers>` functionality to be
used by other plugins.
Plugin mixin implementations will get a bunch of :ref:`properties injected <sec-plugins-concepts-injectedproperties>`
Plugin mixin implementations will get a bunch of :ref:`properties injected <sec-plugins-mixins-injectedproperties>`
by OctoPrint plugin system to help them work.
.. _sec-plugin-concepts-controlproperties:
Control Properties
------------------
As already mentioned above, plugins are Python packages which provide certain pieces of metadata to tell OctoPrint's
plugin subsystem about themselves. These are simple package attributes defined in the top most package file, e.g.:
.. code-block:: python
import octoprint.plugin
# ...
__plugin_name__ = "My Plugin"
def __plugin_load__():
# whatever you need to do to load your plugin, if anything at all
pass
The following properties are recognized:
``__plugin_name__``
Name of your plugin, optional, overrides the name specified in ``setup.py`` if provided. If neither this property nor
a name from ``setup.py`` is available to the plugin subsystem, the plugin's identifier (= package name) will be
used instead.
``__plugin_version__``
Version of your plugin, optional, overrides the version specified in ``setup.py`` if provided.
``__plugin_description__``
Description of your plugin, optional, overrides the description specified in ``setup.py`` if provided.
``__plugin_author__``
Author of your plugin, optional, overrides the author specified in ``setup.py`` if provided.
``__plugin_url__``
URL of the webpage of your plugin, e.g. the Github repository, optional, overrides the URL specified in ``setup.py`` if
provided.
``__plugin_license__``
License of your plugin, optional, overrides the license specified in ``setup.py`` if provided.
``__plugin_implementation__``
Instance of an implementation of one or more :ref:`plugin mixins <sec-plugins-mixins>`.
``__plugin_hooks__``
Handlers for one or more of the various :ref:`plugin hooks <sec-plugins-hooks>`.
``__plugin_check__``
Method called upon discovery of the plugin by the plugin subsystem, should return ``True`` if the
plugin can be instantiated later on, ``False`` if there are reasons why not, e.g. if dependencies
are missing.
``__plugin_load__``
Method called upon loading of the plugin by the plugin subsystem, can be used to instantiate
plugin implementations, connecting them to hooks etc.
``__plugin_unload__``
Method called upon unloading of the plugin by the plugin subsystem, can be used to do any final clean ups.
``__plugin_enable__``
Method called upon enabling of the plugin by the plugin subsystem. Also see :func:`~octoprint.plugin.core.Plugin.on_plugin_enabled``.
``__plugin_disable__``
Method called upon disabling of the plugin by the plugin subsystem. Also see :func:`~octoprint.plugin.core.Plugin.on_plugin_disabled``.
.. _sec-plugin-concepts-mixins:
Mixins
------
Plugin mixins are the heart of OctoPrint's plugin system. They are :ref:`special base classes <sec-plugins-mixins>`
which are to be subclassed and extended to add functionality to OctoPrint. Plugins declare their instances that
implement one or multiple mixins using the ``__plugin_implementation__`` control property. OctoPrint's plugin core
collects those from the plugins and offers methods to access them based on the mixin type, which get used at multiple
locations within OctoPrint.
Using mixins always follows the pattern of retrieving the matching implementations from the plugin subsystem, then
calling the specific mixin's methods as defined and necessary.
The following snippet taken from OctoPrint's code for example shows how all :class:`~octoprint.plugin.AssetPlugin`
implementations are collected and then all assets they return via their ``get_assets`` methods are retrieved and
merged into one big asset map (differing between javascripts and stylesheets of various types) for use during
rendition of the UI.
.. code-block:: python
:linenos:
asset_plugins = pluginManager.get_implementations(octoprint.plugin.AssetPlugin)
for name, implementation in asset_plugins.items():
all_assets = implementation.get_assets()
if "js" in all_assets:
for asset in all_assets["js"]:
assets["js"].append(url_for('plugin_assets', name=name, filename=asset))
if preferred_stylesheet in all_assets:
for asset in all_assets[preferred_stylesheet]:
assets["stylesheets"].append((preferred_stylesheet, url_for('plugin_assets', name=name, filename=asset)))
else:
for stylesheet in supported_stylesheets:
if not stylesheet in all_assets:
continue
for asset in all_assets[stylesheet]:
assets["stylesheets"].append((stylesheet, url_for('plugin_assets', name=name, filename=asset)))
break
.. seealso::
:ref:`Available Mixins <sec-plugins-mixins>`
An overview of all mixin types available for extending OctoPrint.
:ref:`The Getting Started Guide <sec-plugins-gettingstarted>`
Tutorial on how to write a simple OctoPrint module utilizing mixins for various types of extension.
.. _sec-plugin-concepts-hooks:
Hooks
-----
Hooks are the smaller siblings of 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 :ref:`GCODE scripts <sec-features-gcode_scripts>`, either as ``prefix`` (before the existing lines)
or as ``postfix`` (after the existing lines).
.. code-block:: python
:linenos:
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 <https://github.com/OctoPrint/OctoPrint-SoftwareUpdate>`_
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.
.. onlineinclude:: https://raw.githubusercontent.com/OctoPrint/Plugin-Examples/master/custom_action_command.py
:linenos:
:tab-width: 4
:caption: `custom_action_command.py <https://github.com/OctoPrint/Plugin-Examples/blob/master/custom_action_command.py>`_
:name: sec-plugin-concepts-hooks-example
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``:
.. code-block:: python
:linenos:
:name: `ordertest.py`
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:
.. code-block:: python
:linenos:
:name: `oneorderedhook.py`
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)
}
.. code-block:: python
:linenos:
:name: `anotherorderedhook.py`
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)
}
.. code-block:: python
:linenos:
:name: `yetanotherhook.py`
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:
.. code-block:: plain
[...]
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
[...]
.. seealso::
:ref:`Available Hooks <sec-plugins-hooks>`
An overview of all hooks defined in OctoPrint itself.
.. _sec-plugin-concepts-helpers:
Helpers
-------
Helpers are methods that plugin can exposed to other plugins in order to make common functionality available on the
system. They are registered with the OctoPrint plugin system through the use of the control property ``__plugin_helpers__``.
An example for providing a couple of helper functions to the system can be found in the
`Discovery Plugin <https://github.com/foosel/OctoPrint/wiki/Plugin:-Discovery>`_,
which provides it's SSDP browsing and Zeroconf browsing and publishing functions as helper methods.
.. code-block:: python
:linenos:
:emphasize-lines: 11-20
:caption: Excerpt from the Discovery Plugin showing the declaration of its exported helpers.
:name: sec-plugin-concepts-helpers-example-export
def __plugin_load__():
if not pybonjour:
# no pybonjour available, we can't use that
logging.getLogger("octoprint.plugins." + __name__).info("pybonjour is not installed, Zeroconf Discovery won't be available")
plugin = DiscoveryPlugin()
global __plugin_implementation__
__plugin_implementation__ = plugin
global __plugin_helpers__
__plugin_helpers__ = dict(
ssdp_browse=plugin.ssdp_browse
)
if pybonjour:
__plugin_helpers__.update(dict(
zeroconf_browse=plugin.zeroconf_browse,
zeroconf_register=plugin.zeroconf_register,
zeroconf_unregister=plugin.zeroconf_unregister
))
An example of how to use helpers can be found in the `Growl Plugin <https://github.com/OctoPrint/OctoPrint-Growl>`_.
Using :meth:`~octoprint.plugin.code.PluginManager.get_helpers` plugins can retrieve exported helper methods and call
them as (hopefully) documented.
.. code-block:: python
:linenos:
:emphasize-lines: 6-8,20
:caption: Excerpt from the Growl Plugin showing utilization of the helpers published by the Discovery Plugin.
:name: sec-plugin-concepts-helpers-example-usage
def on_after_startup(self):
host = self._settings.get(["hostname"])
port = self._settings.getInt(["port"])
password = self._settings.get(["password"])
helpers = self._plugin_manager.get_helpers("discovery", "zeroconf_browse")
if helpers and "zeroconf_browse" in helpers:
self.zeroconf_browse = helpers["zeroconf_browse"]
self.growl, _ = self._register_growl(host, port, password=password)
# ...
def on_api_get(self, request):
if not self.zeroconf_browse:
return flask.jsonify(dict(
browsing_enabled=False
))
browse_results = self.zeroconf_browse("_gntp._tcp", block=True)
growl_instances = [dict(name=v["name"], host=v["host"], port=v["port"]) for v in browse_results]
return flask.jsonify(dict(
browsing_enabled=True,
growl_instances=growl_instances
))
.. _sec-plugins-concepts-injectedproperties:
Injected Properties
-------------------
OctoPrint's plugin subsystem will inject a bunch of properties into each :ref:`mixin implementation <sec-plugin-concepts-mixins>`.
An overview of these properties follows.
``self._identifier``
The plugin's identifier.
``self._plugin_name``
The plugin's name, as taken from either the ``__plugin_name__`` control property or the package info.
``self._plugin_version``
The plugin's version, as taken from either the ``__plugin_version__`` control property or the package info.
``self._basefolder``
The plugin's base folder where it's installed. Can be used to refer to files relative to the plugin's installation
location, e.g. included scripts, templates or assets.
``self._datafolder``
The plugin's additional data folder path. Can be used to store additional files needed for the plugin's operation (cache,
data files etc). Plugins should not access this property directly but instead utilize :func:`~octoprint.plugin.types.OctoPrintPlugin.get_plugin_data_folder`
which will make sure the path actually does exist and if not create it before returning it.
``self._logger``
A `python logger instance <https://docs.python.org/2/library/logging.html>`_ logging to the log target
``octoprint.plugin.<plugin identifier>``.
``self._settings``
The plugin's personalized settings manager, injected only into plugins that include the :class:`~octoprint.plugin.SettingsPlugin` mixin.
An instance of :class:`octoprint.plugin.PluginSettings`.
``self._plugin_manager``
OctoPrint's plugin manager object, an instance of :class:`octoprint.plugin.core.PluginManager`.
``self._printer_profile_manager``
OctoPrint's printer profile manager, an instance of :class:`octoprint.printer.profile.PrinterProfileManager`.
``self._event_bus``
OctoPrint's event bus, an instance of :class:`octoprint.events.EventManager`.
``self._analysis_queue``
OctoPrint's analysis queue for analyzing GCODEs or other files, an instance of :class:`octoprint.filemanager.analysis.AnalysisQueue`.
``self._slicing_manager``
OctoPrint's slicing manager, an instance of :class:`octoprint.slicing.SlicingManager`.
``self._file_manager``
OctoPrint's file manager, an instance of :class:`octoprint.filemanager.FileManager`.
``self._printer``
OctoPrint's printer management object, an instance of :class:`octoprint.printer.PrinterInterface`.
``self._app_session_manager``
OctoPrint's application session manager, an instance of :class:`octoprint.server.util.flask.AppSessionManager`.
``self._user_manager``
OctoPrint's user manager, an instance of :class:`octoprint.users.UserManager`.
.. seealso::
:class:`~octoprint.plugin.core.Plugin` and :class:`~octoprint.plugin.types.OctoPrintPlugin`
Class documentation also containing the properties shared among all mixing implementations.
:ref:`Available Mixins <sec-plugins-mixins>`
Some mixin types trigger the injection of additional properties.
.. _sec-plugins-concept-lifecycle:
Lifecycle
---------
There are three sources of installed plugins that OctoPrint will check during start up:
* it's own ``octoprint/plugins`` folder (this is where the bundled plugins reside),
* the ``plugins`` folder in its configuration directory (e.g. ``~/.octoprint/plugins`` on Linux),
* any python packages registered for the entry point ``octoprint.plugin``.
Each plugin that OctoPrint finds it will first load, then enable. On enabling a plugin, OctoPrint will
register its declared :ref:`hook handlers <sec-plugins-hooks>` and :ref:`helpers <sec-plugins-helpers>`,
:ref:`inject the required properties <sec-plugins-mixins-injectedproperties>` into its declared
:ref:`mixin implementation <sec-plugins-mixins>` and register those as well.
On disabling a plugin, its hook handlers, helpers and mixin implementations will be de-registered again.
.. image:: ../images/plugins_lifecycle.png
:align: center
:alt: The lifecycle of OctoPrint plugins.

View file

@ -0,0 +1,54 @@
.. _sec-plugins-controlproperties:
Control Properties
==================
As already mentioned earlier, plugins are Python packages which provide certain pieces of metadata to tell OctoPrint's
plugin subsystem about themselves. These are simple package attributes defined in the top most package file, e.g.:
.. code-block:: python
import octoprint.plugin
# ...
__plugin_name__ = "My Plugin"
def __plugin_load__():
# whatever you need to do to load your plugin, if anything at all
pass
The following properties are recognized:
``__plugin_name__``
Name of your plugin, optional, overrides the name specified in ``setup.py`` if provided. If neither this property nor
a name from ``setup.py`` is available to the plugin subsystem, the plugin's identifier (= package name) will be
used instead.
``__plugin_version__``
Version of your plugin, optional, overrides the version specified in ``setup.py`` if provided.
``__plugin_description__``
Description of your plugin, optional, overrides the description specified in ``setup.py`` if provided.
``__plugin_author__``
Author of your plugin, optional, overrides the author specified in ``setup.py`` if provided.
``__plugin_url__``
URL of the webpage of your plugin, e.g. the Github repository, optional, overrides the URL specified in ``setup.py`` if
provided.
``__plugin_license__``
License of your plugin, optional, overrides the license specified in ``setup.py`` if provided.
``__plugin_implementation__``
Instance of an implementation of one or more :ref:`plugin mixins <sec-plugins-mixins>`.
``__plugin_hooks__``
Handlers for one or more of the various :ref:`plugin hooks <sec-plugins-hooks>`.
``__plugin_check__``
Method called upon discovery of the plugin by the plugin subsystem, should return ``True`` if the
plugin can be instantiated later on, ``False`` if there are reasons why not, e.g. if dependencies
are missing.
``__plugin_load__``
Method called upon loading of the plugin by the plugin subsystem, can be used to instantiate
plugin implementations, connecting them to hooks etc.
``__plugin_unload__``
Method called upon unloading of the plugin by the plugin subsystem, can be used to do any final clean ups.
``__plugin_enable__``
Method called upon enabling of the plugin by the plugin subsystem. Also see :func:`~octoprint.plugin.core.Plugin.on_plugin_enabled``.
``__plugin_disable__``
Method called upon disabling of the plugin by the plugin subsystem. Also see :func:`~octoprint.plugin.core.Plugin.on_plugin_disabled``.

View file

@ -14,7 +14,7 @@ Manual file distribution
------------------------
You can have your users copy it to OctoPrint's plugin folder (normally located at ``~/.octoprint/plugins`` under Linux,
``%APPDATA%\OctoPrint\plugins`` on Windows and ... on Mac). In this case your plugin will be distributed directly
``%APPDATA%\OctoPrint\plugins`` on Windows and ``~/Library/Application Support/OctoPrint`` on Mac). In this case your plugin will be distributed directly
as a Python module (a single ``.py`` file containing all of your plugin's code directly and named
like your plugin) or a package (a folder named like your plugin + ``__init.py__`` contained within).
@ -35,10 +35,11 @@ requirements management and pretty much any thing else that Python's setuptools
.. seealso::
`OctoPrint Plugin Skeleton <https://github.com/OctoPrint/OctoPrint-PluginSkeleton>`_
A basic plugin skeleton providing you with all you need to get started with distributing your plugin as a proper
package. See the :ref:`Getting Started Guide <sec-plugins-gettingstarted>` for an
:ref:`example <sec-plugins-gettingstarted-growingup>` on how to use this.
`OctoPrint Plugin Cookiecutter Template <https://github.com/OctoPrint/cookiecutter-octoprint-plugin>`_
A `Cookiecutter Template <https://github.com/audreyr/cookiecutter>`_ providing
you with all you need to get started with writing a properly packaged OctoPrint plugin. See the
:ref:`Plugin Tutorial <sec-plugins-gettingstarted>` for an :ref:`example <sec-plugins-gettingstarted-growingup>`
on how to use this.
.. rubric:: Footnotes
@ -47,3 +48,15 @@ requirements management and pretty much any thing else that Python's setuptools
the plugin that they also used for installing & running OctoPrint. For OctoPi this means using
``~/oprint/bin/pip`` for installing plugins instead of just ``pip``.
.. _sec-plugins-distribution-pluginrepo:
Registering with the official plugin repository
-----------------------------------------------
Once it is ready for general consumption, you might want to register your plugin with the
`official OctoPrint Plugin Repository <http://plugins.octoprint.org>`_. You can find instructions on how to do
that in the `Plugin Repository's help pages <http://plugins.octoprint.org/help/registering/>`_.
If you used the `OctoPrint Plugin Cookiecutter Template <https://github.com/OctoPrint/cookiecutter-octoprint-plugin>`_
when creating your plugin, you can find a prepared registration entry ``.md`` file in the ``extras`` folder of your
plugin.

View file

@ -1,6 +1,6 @@
.. _sec-plugins-gettingstarted:
Getting Started
Plugin Tutorial
===============
.. contents::
@ -108,7 +108,7 @@ used :func:`~octoprint.plugin.StartupPlugin.on_startup` instead, in which case o
up and ready to serve requests.
You'll also note that we are using ``self._logger`` for logging. Where did that one come from? OctoPrint's plugin system
injects :ref:`a couple of useful objects <sec-plugins-concepts-injectedproperties>` into our plugin implementation classes,
injects :ref:`a couple of useful objects <sec-plugins-mixins-injectedproperties>` into our plugin implementation classes,
one of those being a fully instantiated `python logger <https://docs.python.org/2/library/logging.html>`_ ready to be
used by your plugin. As you can see in the log output above, that logger uses the namespace ``octoprint.plugins.helloworld``
for our little plugin here, or more generally ``octoprint.plugins.<plugin identifier>``.
@ -326,7 +326,7 @@ Restart OctoPrint again::
[...]
Much better! You can override pretty much all of the metadata defined within ``setup.py`` from within your Plugin itself --
take a look at :ref:`the available control properties <sec-plugin-concepts-controlproperties>` for all available
take a look at :ref:`the available control properties <sec-plugins-controlproperties>` for all available
overrides.
Following the README of the `Plugin Skeleton <https://github.com/OctoPrint/OctoPrint-PluginSkeleton>`_ you could now

76
docs/plugins/helpers.rst Normal file
View file

@ -0,0 +1,76 @@
.. _sec-plugins-helpers:
Helpers
=======
Helpers are methods that plugin can exposed to other plugins in order to make common functionality available on the
system. They are registered with the OctoPrint plugin system through the use of the control property ``__plugin_helpers__``.
An example for providing a couple of helper functions to the system can be found in the
`Discovery Plugin <https://github.com/foosel/OctoPrint/wiki/Plugin:-Discovery>`_,
which provides it's SSDP browsing and Zeroconf browsing and publishing functions as helper methods.
.. code-block:: python
:linenos:
:emphasize-lines: 11-20
:caption: Excerpt from the Discovery Plugin showing the declaration of its exported helpers.
:name: sec-plugin-concepts-helpers-example-export
def __plugin_load__():
if not pybonjour:
# no pybonjour available, we can't use that
logging.getLogger("octoprint.plugins." + __name__).info("pybonjour is not installed, Zeroconf Discovery won't be available")
plugin = DiscoveryPlugin()
global __plugin_implementation__
__plugin_implementation__ = plugin
global __plugin_helpers__
__plugin_helpers__ = dict(
ssdp_browse=plugin.ssdp_browse
)
if pybonjour:
__plugin_helpers__.update(dict(
zeroconf_browse=plugin.zeroconf_browse,
zeroconf_register=plugin.zeroconf_register,
zeroconf_unregister=plugin.zeroconf_unregister
))
An example of how to use helpers can be found in the `Growl Plugin <https://github.com/OctoPrint/OctoPrint-Growl>`_.
Using :meth:`~octoprint.plugin.code.PluginManager.get_helpers` plugins can retrieve exported helper methods and call
them as (hopefully) documented.
.. code-block:: python
:linenos:
:emphasize-lines: 6-8,20
:caption: Excerpt from the Growl Plugin showing utilization of the helpers published by the Discovery Plugin.
:name: sec-plugin-concepts-helpers-example-usage
def on_after_startup(self):
host = self._settings.get(["hostname"])
port = self._settings.getInt(["port"])
password = self._settings.get(["password"])
helpers = self._plugin_manager.get_helpers("discovery", "zeroconf_browse")
if helpers and "zeroconf_browse" in helpers:
self.zeroconf_browse = helpers["zeroconf_browse"]
self.growl, _ = self._register_growl(host, port, password=password)
# ...
def on_api_get(self, request):
if not self.zeroconf_browse:
return flask.jsonify(dict(
browsing_enabled=False
))
browse_results = self.zeroconf_browse("_gntp._tcp", block=True)
growl_instances = [dict(name=v["name"], host=v["host"], port=v["port"]) for v in browse_results]
return flask.jsonify(dict(
browsing_enabled=True,
growl_instances=growl_instances
))

View file

@ -1,7 +1,195 @@
.. _sec-plugins-hooks:
Hooks
=====
.. contents::
:local:
.. _sec-plugins-hooks-general:
General Concepts
----------------
Hooks are the smaller siblings of :ref:`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 :ref:`GCODE scripts <sec-features-gcode_scripts>`, either as ``prefix`` (before the existing lines)
or as ``postfix`` (after the existing lines).
.. code-block:: python
:linenos:
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 <https://github.com/OctoPrint/OctoPrint-SoftwareUpdate>`_
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.
.. onlineinclude:: https://raw.githubusercontent.com/OctoPrint/Plugin-Examples/master/custom_action_command.py
:linenos:
:tab-width: 4
:caption: `custom_action_command.py <https://github.com/OctoPrint/Plugin-Examples/blob/master/custom_action_command.py>`_
:name: sec-plugin-concepts-hooks-example
.. _sec-plugins-hooks-ordering:
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``:
.. code-block:: python
:linenos:
:caption: ordertest.py
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:
.. code-block:: python
:linenos:
:caption: oneorderedhook.py
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)
}
.. code-block:: python
:linenos:
:caption: anotherorderedhook.py
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)
}
.. code-block:: python
:linenos:
:caption: yetanotherhook.py
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:
.. code-block:: none
[...]
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
[...]
.. _sec-plugins-hooks-available:
Available plugin hooks
======================
----------------------
.. note::
@ -15,7 +203,7 @@ Available plugin hooks
.. _sec-plugins-hook-accesscontrol-appkey:
octoprint.accesscontrol.appkey
------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. py:function:: hook(*args, **kwargs)
@ -40,7 +228,7 @@ octoprint.accesscontrol.appkey
.. _sec-plugins-hook-cli-commands:
octoprint.cli.commands
----------------------
~~~~~~~~~~~~~~~~~~~~~~
.. py:function:: hook(cli_group, pass_octoprint_ctx, *args, **kwargs)
@ -119,7 +307,7 @@ octoprint.cli.commands
If your hook handler is an instance method of a plugin mixin implementation, be aware that the hook will be
called without OctoPrint initializing your implementation instance. That means that **none** of the
:ref:`injected properties <sec-plugins-concepts-injectedproperties>` will be available and also the
:ref:`injected properties <sec-plugins-mixins-injectedproperties>` will be available and also the
:meth:`~octoprint.plugin.Plugin.initialize` method will not be called.
Your hook handler will have access to the plugin manager as ``cli_group.plugin_manager`` and to the
@ -163,7 +351,7 @@ octoprint.cli.commands
.. _sec-plugins-hook-comm-protocol-action:
octoprint.comm.protocol.action
------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. py:function:: hook(comm_instance, line, action, *args, **kwargs)
@ -191,7 +379,7 @@ octoprint.comm.protocol.action
.. _sec-plugins-hook-comm-protocol-gcode-phase:
octoprint.comm.protocol.gcode.<phase>
-------------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This describes actually four hooks:
@ -285,7 +473,7 @@ This describes actually four hooks:
.. _sec-plugins-hook-comm-protocol-gcode-received:
octoprint.comm.protocol.gcode.received
--------------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. py:function:: hook(comm_instance, line, *args, **kwargs)
@ -296,7 +484,7 @@ octoprint.comm.protocol.gcode.received
**Example:**
Looks for the response of a M115, which contains information about the MACHINE_TYPE, among other things.
Looks for the response of an ``M115``, which contains information about the ``MACHINE_TYPE``, among other things.
.. onlineinclude:: https://raw.githubusercontent.com/OctoPrint/Plugin-Examples/master/read_m115_response.py
:linenos:
@ -311,7 +499,7 @@ octoprint.comm.protocol.gcode.received
.. _sec-plugins-hook-comm-protocol-scripts:
octoprint.comm.protocol.scripts
-------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. py:function:: hook(comm_instance, script_type, script_name, *args, **kwargs)
@ -345,7 +533,7 @@ octoprint.comm.protocol.scripts
.. _sec-plugins-hook-comm-transport-serial-factory:
octoprint.comm.transport.serial.factory
---------------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. py:function:: hook(comm_instance, port, baudrate, read_timeout, *args, **kwargs)
@ -421,7 +609,7 @@ octoprint.comm.transport.serial.factory
.. _sec-plugins-hook-filemanager-extensiontree:
octoprint.filemanager.extension_tree
------------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. py:function:: hook(*args, **kwargs)
@ -457,7 +645,7 @@ octoprint.filemanager.extension_tree
.. _sec-plugins-hook-filemanager-preprocessor:
octoprint.filemanager.preprocessor
----------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. py:function:: hook(path, file_object, links=None, printer_profile=None, allow_overwrite=False, *args, **kwargs)
@ -491,7 +679,7 @@ octoprint.filemanager.preprocessor
.. _sec-plugins-hook-server-http-bodysize:
octoprint.server.http.bodysize
------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. py:function:: hook(current_max_body_sizes, *args, **kwargs)
@ -529,7 +717,7 @@ octoprint.server.http.bodysize
.. _sec-plugins-hook-server-http-routes:
octoprint.server.http.routes
----------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. py:function:: hook(server_routes, *args, **kwargs)
@ -584,7 +772,7 @@ octoprint.server.http.routes
.. _sec-plugins-hook-ui-web-templatetypes:
octoprint.ui.web.templatetypes
------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. py:function:: hook(template_sorting, template_rules, *args, **kwargs)

View file

@ -7,9 +7,12 @@ Developing Plugins
.. toctree::
:maxdepth: 3
gettingstarted.rst
concepts.rst
distributing.rst
controlproperties.rst
mixins.rst
hooks.rst
helpers.rst
injectedproperties.rst
viewmodels.rst
gettingstarted.rst
distributing.rst

View file

@ -0,0 +1,54 @@
.. _sec-plugins-injectedproperties:
Injected Properties
===================
OctoPrint's plugin subsystem will inject a bunch of properties into each :ref:`mixin implementation <sec-plugins-mixins>`.
An overview of these properties follows.
``self._identifier``
The plugin's identifier.
``self._plugin_name``
The plugin's name, as taken from either the ``__plugin_name__`` control property or the package info.
``self._plugin_version``
The plugin's version, as taken from either the ``__plugin_version__`` control property or the package info.
``self._basefolder``
The plugin's base folder where it's installed. Can be used to refer to files relative to the plugin's installation
location, e.g. included scripts, templates or assets.
``self._datafolder``
The plugin's additional data folder path. Can be used to store additional files needed for the plugin's operation (cache,
data files etc). Plugins should not access this property directly but instead utilize :func:`~octoprint.plugin.types.OctoPrintPlugin.get_plugin_data_folder`
which will make sure the path actually does exist and if not create it before returning it.
``self._logger``
A `python logger instance <https://docs.python.org/2/library/logging.html>`_ logging to the log target
``octoprint.plugin.<plugin identifier>``.
``self._settings``
The plugin's personalized settings manager, injected only into plugins that include the :class:`~octoprint.plugin.SettingsPlugin` mixin.
An instance of :class:`octoprint.plugin.PluginSettings`.
``self._plugin_manager``
OctoPrint's plugin manager object, an instance of :class:`octoprint.plugin.core.PluginManager`.
``self._printer_profile_manager``
OctoPrint's printer profile manager, an instance of :class:`octoprint.printer.profile.PrinterProfileManager`.
``self._event_bus``
OctoPrint's event bus, an instance of :class:`octoprint.events.EventManager`.
``self._analysis_queue``
OctoPrint's analysis queue for analyzing GCODEs or other files, an instance of :class:`octoprint.filemanager.analysis.AnalysisQueue`.
``self._slicing_manager``
OctoPrint's slicing manager, an instance of :class:`octoprint.slicing.SlicingManager`.
``self._file_manager``
OctoPrint's file manager, an instance of :class:`octoprint.filemanager.FileManager`.
``self._printer``
OctoPrint's printer management object, an instance of :class:`octoprint.printer.PrinterInterface`.
``self._app_session_manager``
OctoPrint's application session manager, an instance of :class:`octoprint.server.util.flask.AppSessionManager`.
``self._user_manager``
OctoPrint's user manager, an instance of :class:`octoprint.users.UserManager`.
.. seealso::
:class:`~octoprint.plugin.core.Plugin` and :class:`~octoprint.plugin.types.OctoPrintPlugin`
Class documentation also containing the properties shared among all mixing implementations.
:ref:`Available Mixins <sec-plugins-mixins-available>`
Some mixin types trigger the injection of additional properties.

View file

@ -1,7 +1,202 @@
.. _sec-plugins-mixins:
Mixins
======
.. contents::
:local:
.. _sec-plugins-mixins-general:
General Concepts
----------------
Plugin mixins are the heart of OctoPrint's plugin system. They are :ref:`special base classes <sec-plugins-mixins>`
which are to be subclassed and extended to add functionality to OctoPrint. Plugins declare their instances that
implement one or multiple mixins using the ``__plugin_implementation__`` control property. OctoPrint's plugin core
collects those from the plugins and offers methods to access them based on the mixin type, which get used at multiple
locations within OctoPrint.
Using mixins always follows the pattern of retrieving the matching implementations from the plugin subsystem, then
calling the specific mixin's methods as defined and necessary.
The following snippet taken from OctoPrint's code for example shows how all :class:`~octoprint.plugin.AssetPlugin`
implementations are collected and then all assets they return via their ``get_assets`` methods are retrieved and
merged into one big asset map (differing between javascripts and stylesheets of various types) for use during
rendition of the UI.
.. code-block:: python
:linenos:
asset_plugins = pluginManager.get_implementations(octoprint.plugin.AssetPlugin)
for name, implementation in asset_plugins.items():
all_assets = implementation.get_assets()
if "js" in all_assets:
for asset in all_assets["js"]:
assets["js"].append(url_for('plugin_assets', name=name, filename=asset))
if preferred_stylesheet in all_assets:
for asset in all_assets[preferred_stylesheet]:
assets["stylesheets"].append((preferred_stylesheet, url_for('plugin_assets', name=name, filename=asset)))
else:
for stylesheet in supported_stylesheets:
if not stylesheet in all_assets:
continue
for asset in all_assets[stylesheet]:
assets["stylesheets"].append((stylesheet, url_for('plugin_assets', name=name, filename=asset)))
break
.. seealso::
:ref:`The Plugin Tutorial <sec-plugins-gettingstarted>`
Tutorial on how to write a simple OctoPrint module utilizing mixins for various types of extension.
.. _sec-plugins-mixins-ordering:
Execution Order
---------------
Some mixin types, such as :class:`~octoprint.plugin.StartupPlugin`, :class:`~octoprint.plugin.ShutdownPlugin` and
:class:`~octoprint.plugin.UiPlugin`, support influencing the execution order for various execution contexts by also
implementing the :class:`~octoprint.plugin.core.SortablePlugin` mixin.
If a method is to be called on a plugin implementation for which a sorting context is defined (see the mixin
documentation for information on this), OctoPrint's plugin subsystem will ensure that the order in which the implementation
calls are done is as follows:
* Plugins with a return value that is not ``None`` for :meth:`~octoprint.plugin.core.SortablePlugin.get_sorting_key`
for the provided sorting context will be ordered among each other first. If the returned order number is equal for
two or more implementations, the plugin's identifier will be the next sorting criteria.
* After that follow plugins which returned ``None`` (the default). They are sorted by their identifier.
Example: Consider three plugin implementations implementing the :class:`~octoprint.plugin.StartupPlugin` mixin, called
``plugin_a``, ``plugin_b`` and ``plugin_c``. ``plugin_a`` doesn't override :meth:`~octoprint.plugin.core.SortablePlugin.get_sorting_key`.
``plugin_b`` and ``plugin_c`` both return ``1`` for the sorting context ``StartupPlugin.on_startup``, ``None`` otherwise:
.. code-block:: python
:linenos:
:caption: plugin_a.py
import octoprint.plugin
class PluginA(octoprint.plugin.StartupPlugin):
def on_startup(self, *args, **kwargs):
self._logger.info("PluginA starting up")
def on_after_startup(self, *args, **kwargs):
self._logger.info("PluginA started up")
__plugin_implementation__ = PluginA()
.. code-block:: python
:linenos:
:caption: plugin_b.py
import octoprint.plugin
class PluginB(octoprint.plugin.StartupPlugin):
def get_sorting_key(context):
if context == "StartupPlugin.on_startup":
return 1
return None
def on_startup(self, *args, **kwargs):
self._logger.info("PluginB starting up")
def on_after_startup(self, *args, **kwargs):
self._logger.info("PluginB started up")
__plugin_implementation__ = PluginB()
.. code-block:: python
:linenos:
:caption: plugin_c.py
import octoprint.plugin
class PluginC(octoprint.plugin.StartupPlugin):
def get_sorting_key(context):
if context == "StartupPlugin.on_startup":
return 1
return None
def on_startup(self, *args, **kwargs):
self._logger.info("PluginC starting up")
def on_after_startup(self, *args, **kwargs):
self._logger.info("PluginC started up")
__plugin_implementation__ = PluginC()
OctoPrint will detect that ``plugin_b`` and ``plugin_c`` define a order number, and since it's identical for both (``1``)
will order both plugins based on their plugin identifier. ``plugin_a`` doesn't define a sort key and hence will be
put after the other two. The execution order of the ``on_startup`` method will hence be ``plugin_b``, ``plugin_c``, ``plugin_a``.
Now, the execution order of the ``on_after_startup`` method will be determined based on another sorting context,
``StartupPlugin.on_after_startup`` for which all of the plugins return ``None``. Hence, the execution order of the
``on_after_startup`` method will be purely ordered by plugin identifier, ``plugin_a``, ``plugin_b``, ``plugin_c``.
.. _sec-plugins-mixins-injectedproperties:
Injected Properties
-------------------
OctoPrint's plugin subsystem will inject a bunch of properties into each :ref:`mixin implementation <sec-plugins-mixins>`.
An overview of these properties follows.
``self._identifier``
The plugin's identifier.
``self._plugin_name``
The plugin's name, as taken from either the ``__plugin_name__`` control property or the package info.
``self._plugin_version``
The plugin's version, as taken from either the ``__plugin_version__`` control property or the package info.
``self._basefolder``
The plugin's base folder where it's installed. Can be used to refer to files relative to the plugin's installation
location, e.g. included scripts, templates or assets.
``self._datafolder``
The plugin's additional data folder path. Can be used to store additional files needed for the plugin's operation (cache,
data files etc). Plugins should not access this property directly but instead utilize :func:`~octoprint.plugin.types.OctoPrintPlugin.get_plugin_data_folder`
which will make sure the path actually does exist and if not create it before returning it.
``self._logger``
A `python logger instance <https://docs.python.org/2/library/logging.html>`_ logging to the log target
``octoprint.plugin.<plugin identifier>``.
``self._settings``
The plugin's personalized settings manager, injected only into plugins that include the :class:`~octoprint.plugin.SettingsPlugin` mixin.
An instance of :class:`octoprint.plugin.PluginSettings`.
``self._plugin_manager``
OctoPrint's plugin manager object, an instance of :class:`octoprint.plugin.core.PluginManager`.
``self._printer_profile_manager``
OctoPrint's printer profile manager, an instance of :class:`octoprint.printer.profile.PrinterProfileManager`.
``self._event_bus``
OctoPrint's event bus, an instance of :class:`octoprint.events.EventManager`.
``self._analysis_queue``
OctoPrint's analysis queue for analyzing GCODEs or other files, an instance of :class:`octoprint.filemanager.analysis.AnalysisQueue`.
``self._slicing_manager``
OctoPrint's slicing manager, an instance of :class:`octoprint.slicing.SlicingManager`.
``self._file_manager``
OctoPrint's file manager, an instance of :class:`octoprint.filemanager.FileManager`.
``self._printer``
OctoPrint's printer management object, an instance of :class:`octoprint.printer.PrinterInterface`.
``self._app_session_manager``
OctoPrint's application session manager, an instance of :class:`octoprint.server.util.flask.AppSessionManager`.
``self._user_manager``
OctoPrint's user manager, an instance of :class:`octoprint.users.UserManager`.
.. seealso::
:class:`~octoprint.plugin.core.Plugin` and :class:`~octoprint.plugin.types.OctoPrintPlugin`
Class documentation also containing the properties shared among all mixing implementations.
.. _sec-plugins-mixins-available:
Available plugin mixins
=======================
-----------------------
The following plugin mixins are currently available:
@ -14,7 +209,7 @@ Please note that all plugin mixins inherit from :class:`~octoprint.plugin.core.P
.. _sec-plugins-mixins-startupplugin:
StartupPlugin
-------------
~~~~~~~~~~~~~
.. autoclass:: octoprint.plugin.StartupPlugin
:members:
@ -23,7 +218,7 @@ StartupPlugin
.. _sec-plugins-mixins-shutdownplugin:
ShutdownPlugin
--------------
~~~~~~~~~~~~~~
.. autoclass:: octoprint.plugin.ShutdownPlugin
:members:
@ -32,7 +227,7 @@ ShutdownPlugin
.. _sec-plugins-mixins-settingsplugin:
SettingsPlugin
--------------
~~~~~~~~~~~~~~
.. autoclass:: octoprint.plugin.SettingsPlugin
:members:
@ -41,7 +236,7 @@ SettingsPlugin
.. _sec-plugins-mixins-assetplugin:
AssetPlugin
-----------
~~~~~~~~~~~
.. autoclass:: octoprint.plugin.AssetPlugin
:members:
@ -50,7 +245,7 @@ AssetPlugin
.. _sec-plugins-mixins-templateplugin:
TemplatePlugin
--------------
~~~~~~~~~~~~~~
.. autoclass:: octoprint.plugin.TemplatePlugin
:members:
@ -59,7 +254,7 @@ TemplatePlugin
.. _sec-plugins-mixins-wizardplugin:
WizardPlugin
------------
~~~~~~~~~~~~
.. autoclass:: octoprint.plugin.WizardPlugin
:members:
@ -68,7 +263,7 @@ WizardPlugin
.. _sec-plugins-mixins-uiplugin:
UiPlugin
--------
~~~~~~~~
.. autoclass:: octoprint.plugin.UiPlugin
:members:
@ -77,7 +272,7 @@ UiPlugin
.. _sec-plugins-mixins-simpleapiplugin:
SimpleApiPlugin
---------------
~~~~~~~~~~~~~~~
.. autoclass:: octoprint.plugin.SimpleApiPlugin
:members:
@ -86,7 +281,7 @@ SimpleApiPlugin
.. _sec-plugins-mixins-blueprintplugin:
BlueprintPlugin
---------------
~~~~~~~~~~~~~~~
.. autoclass:: octoprint.plugin.BlueprintPlugin
:members:
@ -95,7 +290,7 @@ BlueprintPlugin
.. _sec-plugins-mixins-eventhandlerplugin:
EventHandlerPlugin
------------------
~~~~~~~~~~~~~~~~~~
.. autoclass:: octoprint.plugin.EventHandlerPlugin
:members:
@ -104,7 +299,7 @@ EventHandlerPlugin
.. _sec-plugins-mixins-progressplugin:
ProgressPlugin
--------------
~~~~~~~~~~~~~~
.. autoclass:: octoprint.plugin.ProgressPlugin
:members:
@ -113,7 +308,7 @@ ProgressPlugin
.. _sec-plugins-mixins-slicerplugin:
SlicerPlugin
------------
~~~~~~~~~~~~
.. autoclass:: octoprint.plugin.SlicerPlugin
:members:

View file

@ -46,7 +46,7 @@ class PluginInfo(object):
implementations, hooks and helpers.
It works on Python module objects and extracts the relevant data from those via accessing the
:ref:`control properties <sec-plugin-concepts-controlproperties>`.
:ref:`control properties <sec-plugins-controlproperties>`.
Arguments:
key (str): Identifier of the plugin

View file

@ -109,9 +109,9 @@ class StartupPlugin(OctoPrintPlugin, SortablePlugin):
The ``StartupPlugin`` allows hooking into the startup of OctoPrint. It can be used to start up additional services
on or just after the startup of the server.
``StartupPlugin`` is a :class:`~octoprint.plugin.core.SortablePlugin`. The
relevant sorting context for :meth:`on_startup` is ``StartupPlugin.on_startup``,
the one for :meth:`on_after_startup` will be ``StartupPlugin.on_after_startup``.
``StartupPlugin`` is a :class:`~octoprint.plugin.core.SortablePlugin` and provides
sorting contexts for :meth:`~octoprint.plugin.StartupPlugin.on_startup` as well as
:meth:`~octoprint.plugin.StartupPlugin.on_after_startup`.
"""
def on_startup(self, host, port):
@ -122,6 +122,8 @@ class StartupPlugin(OctoPrintPlugin, SortablePlugin):
is not actually up yet and none of your plugin's APIs or blueprints will be reachable yet. If you need to be
externally reachable, use :func:`on_after_startup` instead or additionally.
The relevant sorting context is ``StartupPlugin.on_startup``.
:param string host: the host the server will listen on, may be ``0.0.0.0``
:param int port: the port the server will listen on
"""
@ -131,6 +133,8 @@ class StartupPlugin(OctoPrintPlugin, SortablePlugin):
def on_after_startup(self):
"""
Called just after launch of the server, so when the listen loop is actually running already.
The relevant sorting context is ``StartupPlugin.on_after_startup``.
"""
pass
@ -142,13 +146,15 @@ class ShutdownPlugin(OctoPrintPlugin, SortablePlugin):
:class:`StartupPlugin` mixin, to cleanly shut down additional services again that where started by the :class:`StartupPlugin`
part of the plugin.
``ShutdownPlugin`` is a :class:`~octoprint.plugin.core.SortablePlugin`.
The relevant sorting context will be ``ShutdownPlugin.on_shutdown``.
``ShutdownPlugin`` is a :class:`~octoprint.plugin.core.SortablePlugin` and provides a sorting context for
:meth:`~octoprint.plugin.ShutdownPlugin.on_shutdown`.
"""
def on_shutdown(self):
"""
Called upon the imminent shutdown of OctoPrint.
The relevant sorting context is ``ShutdownPlugin.on_shutdown``.
"""
pass
@ -523,9 +529,9 @@ class UiPlugin(OctoPrintPlugin, SortablePlugin):
response altogether, a plugin may set no-cache headers on the returned
response as well.
``UiPlugin`` is a :class:`~octoprint.plugin.core.SortablePlugin`. The
relevant sorting context when acting as a UiPlugin is ``UiPlugin.will_handle_ui``.
The first plugin to return ``True`` will be the one whose ui will be used,
``UiPlugin`` is a :class:`~octoprint.plugin.core.SortablePlugin` with a sorting context
for :meth:`~octoprint.plugin.UiPlugin.will_handle_ui`. The first plugin to return ``True``
for :meth:`~octoprint.plugin.UiPlugin.will_handle_ui` will be the one whose ui will be used,
no further calls to :meth:`~octoprint.plugin.UiPlugin.on_ui_render` will be performed.
If implementations want to serve custom templates in the :meth:`~octoprint.plugin.UiPlugin.on_ui_render`
@ -565,6 +571,9 @@ class UiPlugin(OctoPrintPlugin, SortablePlugin):
the request and that the result of its :meth:`~octoprint.plugin.UiPlugin.on_ui_render` method
is what should be served to the user.
The execution order of calls to this method can be influenced via the sorting context
``UiPlugin.will_handle_ui``.
Arguments:
request (flask.Request): A Flask `Request <http://flask.pocoo.org/docs/0.10/api/#flask.Request>`_
object.