Refactored plugin docs + improved docs on sortables
This commit is contained in:
parent
6d95a5849b
commit
ee185230a4
11 changed files with 658 additions and 471 deletions
|
|
@ -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.
|
||||
|
|
|
|||
54
docs/plugins/controlproperties.rst
Normal file
54
docs/plugins/controlproperties.rst
Normal 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``.
|
||||
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
76
docs/plugins/helpers.rst
Normal 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
|
||||
))
|
||||
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
54
docs/plugins/injectedproperties.rst
Normal file
54
docs/plugins/injectedproperties.rst
Normal 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.
|
||||
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue