diff --git a/docs/images/plugins_gettingstarted_helloworld_navbar.png b/docs/images/plugins_gettingstarted_helloworld_navbar.png new file mode 100644 index 00000000..2f634b0d Binary files /dev/null and b/docs/images/plugins_gettingstarted_helloworld_navbar.png differ diff --git a/docs/plugins/developing.rst b/docs/plugins/developing.rst deleted file mode 100644 index c0f1ee5c..00000000 --- a/docs/plugins/developing.rst +++ /dev/null @@ -1,102 +0,0 @@ -.. _sec-plugins-developing: - -****************** -Developing Plugins -****************** - -.. todo:: - - A section on how to develop plugins yourself, including a tutorial creating an exemplary plugin as well as - a documentation of the plugin API, will be added in the near future. - -.. note:: - - This section is still a heavy WIP, so take it with a bit of caution ;) - -.. _sec-plugins-developing-structure: - -OctoPrint Plugin Structure -========================== - -OctoPrint plugins are simple `Python modules or packages `_ providing -a couple of properties describing the module: - -``__plugin_name__`` - Name of your plugin -``__plugin_version__`` - Version of your plugin -``__plugin_description__`` - Description of your plugin -``__plugin_author__`` - Author of your plugin -``__plugin_url__`` - URL of the webpage of your plugin, e.g. the Github repository -``__plugin_implementations__`` - Instances of one or more of the various :ref:`plugin mixins ` -``__plugin_hooks__`` - Handlers for one or more of the various :ref:`plugin 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_init__`` - Method called upon initializing of the plugin by the plugin subsystem, can be used to instantiate - plugin implementations, connecting them to hooks etc. - -A very simple example plugin which only hooks into OctoPrint's startup sequence and logs "Oh hello!" would -be the following snippet: - -.. code-block:: python - - # coding=utf-8 - from __future__ import absolute_import - - import octoprint.plugin - - __plugin_name__ = "Example Plugin" - __plugin_version__ = "0.1" - __plugin_description__ = "Logs \"Oh hello!\" upon OctoPrint's startup" - - def __plugin_init__(): - global __plugin_implementations__ - __plugin_implementations__ = [ExamplePlugin()] - - class ExamplePlugin(octoprint.plugin.StartupPlugin): - def on_startup(self, host, port): - self._logger.info("Oh hello!") - -.. _sec-plugins-developing-distribution: - -Distributing your plugin -======================== - -You can distribute a plugin with OctoPrint via two ways: - - - 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 - 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). - - You can have your users install it via ``pip`` and register it for the `entry point `_ ``octoprint.plugin`` via - your plugin's ``setup.py``, this way it will be found automatically by OctoPrint upon initialization of the - plugin subsystem [#f1]_. - - For an example of how the directory structure and related files would look like in this case, please take a - look at the `helloworld example from OctoPrint's example plugins `_. - - This variant is highly recommended for pretty much any plugin besides the most basic ones since it also allows - requirements management and pretty much any thing else that Python's setuptools provide to the developer. - -.. rubric:: Footnotes - -.. [#f1] The automatic registration will only work within the same Python installation (this also includes virtual - environments), so make sure to instruct your users to use the exact same Python installation for installing - the plugin that they also used for installing & running OctoPrint. - -.. _sec-plugins-developing-mixins: - -Available plugin mixins -======================= - -.. automodule:: octoprint.plugin.types - :members: - :undoc-members: diff --git a/docs/plugins/distributing.rst b/docs/plugins/distributing.rst new file mode 100644 index 00000000..7a8d6fc0 --- /dev/null +++ b/docs/plugins/distributing.rst @@ -0,0 +1,27 @@ +.. _sec-plugins-distribution: + +Distributing your plugin +======================== + +You can distribute a plugin with OctoPrint via two ways: + + - 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 + 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). + - You can have your users install it via ``pip`` and register it for the `entry point `_ ``octoprint.plugin`` via + your plugin's ``setup.py``, this way it will be found automatically by OctoPrint upon initialization of the + plugin subsystem [#f1]_. + + For an example of how the directory structure and related files would look like in this case, please take a + look at the `helloworld example from OctoPrint's example plugins `_. + + This variant is highly recommended for pretty much any plugin besides the most basic ones since it also allows + requirements management and pretty much any thing else that Python's setuptools provide to the developer. + +.. rubric:: Footnotes + +.. [#f1] The automatic registration will only work within the same Python installation (this also includes virtual + environments), so make sure to instruct your users to use the exact same Python installation for installing + the plugin that they also used for installing & running OctoPrint. + diff --git a/docs/plugins/gettingstarted.rst b/docs/plugins/gettingstarted.rst new file mode 100644 index 00000000..a4371e92 --- /dev/null +++ b/docs/plugins/gettingstarted.rst @@ -0,0 +1,245 @@ +.. _sec-plugins-gettingstarted: + +Getting Started +=============== + +Over the course of this little tutorial we'll build a full fledged, installable OctoPrint plugin that displays "Hello World!" +at various places throughout OctoPrint. + +We'll start at the most basic form a plugin can take - just a couple of simple lines of Python code: + +.. code-block:: python + + # coding=utf-8 + from __future__ import absolute_import + + __plugin_name__ = "Hello World" + __plugin_version__ = "1.0" + __plugin_description__ = "A quick \"Hello World\" example plugin for OctoPrint" + +Saving this as ``helloworld.py`` in ``~/.octoprint/plugins`` yields you something resembling these log entries upon server startup:: + + 2015-01-27 11:14:35,124 - octoprint.server - INFO - Starting OctoPrint 1.2.0-dev-448-gd96e56e (devel branch) + 2015-01-27 11:14:35,124 - octoprint.plugin.core - INFO - Loading plugins from /home/pi/.octoprint/plugins, /home/pi/OctoPrint/src/octoprint/plugins and installed plugin packages... + 2015-01-27 11:14:36,135 - octoprint.plugin.core - INFO - Found 3 plugin(s): Hello World (1.0), CuraEngine (0.1), Discovery (0.1) + +OctoPrint found that plugin in the folder and took a look into it. The name and the version it displays in that log +entry it got from the ``__plugin_name__`` and ``__plugin_version__`` lines. It also read the description from +``__plugin_description__`` and stored it in an internal data structure, but we'll just ignore this for now. + +Saying hello: How to make the plugin actually do something +---------------------------------------------------------- + +Apart from being discovered by OctoPrint, our plugin does nothing yet. We want to change that. Let's make it print +"Hello World!" to the log upon server startup. Modify our ``helloworld.py`` like this: + +.. code-block:: python + + # coding=utf-8 + from __future__ import absolute_import + + import octoprint.plugin + + class HelloWorldPlugin(octoprint.plugin.StartupPlugin): + def on_after_startup(self): + self._logger.info("Hello World!") + + __plugin_name__ = "Hello World" + __plugin_version__ = "1.0" + __plugin_description__ = "A quick \"Hello World\" example plugin for OctoPrint" + __plugin_implementations__ = [HelloWorldPlugin()] + +and restart OctoPrint. You now get this output in the log:: + + 2015-01-27 11:17:10,792 - octoprint.plugins.helloworld - INFO - Hello World! + +Neat, isn't it? We added a custom class that subclasses one of OctoPrint's :ref:`plugin mixins ` +with :class:`StartupPlugin` and another control property, ``__plugin_implementations__``, that instantiates +our plugin class and tells OctoPrint about it. Taking a look at the documentation of :class:`StartupPlugin` we see that +this mixin offers two methods that get called by OctoPrint during startup of the server, ``on_startup`` and +``on_after_startup``. We decided to add our logging output by overriding ``on_after_startup``, but we could also have +used ``on_startup`` instead, in which case our logging statement would be executed before the server was done starting +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 ` into our plugin implementation classes, +one of those being a fully instantiated `python logger `_ 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.``. + +Growing up: How to make it distributable +---------------------------------------- + +If you now want to distribute this plugin to other OctoPrint users (since it is so awesome to be greeted upon server +startup), let's take a look at how you'd go about that now before our plugin gets more complicated. + +You basically have two options to distribute your plugin. One would be about the exact same way we are using it now, +as a simple python file following the naming convention ``.py`` that your users add to their +``~/.octoprint/plugins`` folder. You already know how that works. But let's say you have more than just a simple plugin +that can be done in one file. Distributing multiple files and getting your users to install them in the right way +so that OctoPrint will be able to actually find and load them is certainly not impossible (see :ref:`the plugin distribution +documentation ` if you want to take a closer look at that option), but we want to do it in the +best way possible, meaning we want to make our plugin a fully installable python module that your users will be able to +install directly via Python's standard package manager ``pip`` or alternatively via `OctoPrint's own plugin manager `_. + +So let's begin. First checkout the `Plugin Skeleton `_ and rename +the ``octoprint_skeleton`` folder to something better suited to our "Hello World" plugin:: + + git clone https://github.com/OctoPrint/OctoPrint-PluginSkeleton.git OctoPrint-HelloWorld + cd OctoPrint-HelloWorld + mv octoprint_skeleton octoprint_helloworld + +Then edit the configuration in the ``setup.py`` file to mirror our own "Hello World" plugin. The configuration should +look something like this: + +.. code-block:: python + + plugin_identifier = "helloworld" + plugin_name = "OctoPrint-HelloWorld" + plugin_version = "1.0" + plugin_description = "A quick \"Hello World\" example plugin for OctoPrint" + plugin_author = "You" + plugin_author_email = "you@somewhere.net" + plugin_url = "https://github.com/you/OctoPrint-HelloWorld" + +Now all that's left to do is to move our ``helloworld.py`` into the ``octoprint_helloworld`` folder and renaming it to +``__init__.py``. Make sure to delete the copy under ``~/.octoprint/plugins`` in the process, including the `.pyc` file! + +The plugin is now ready to be installed via ``python setup.py install``. However, since we are still +working on our plugin, it makes more sense to use ``python setup.py develop`` for now -- this way the plugin becomes +discoverable by OctoPrint, however we don't have to reinstall it after any changes we will still do:: + + $ python setup.py develop + running develop + running egg_info + creating OctoPrint_HelloWorld.egg-info + [...] + Finished processing dependencies for OctoPrint-HelloWorld==1.0 + +Restart OctoPrint. Your plugin should still be properly discovered and the log line should be printed:: + + 2015-01-27 13:43:34,134 - octoprint.server - INFO - Starting OctoPrint 1.2.0-dev-448-gd96e56e (devel branch) + 2015-01-27 13:43:34,134 - octoprint.plugin.core - INFO - Loading plugins from /home/pi/.octoprint/plugins, /home/pi/OctoPrint/src/octoprint/plugins and installed plugin packages... + 2015-01-27 13:43:34,818 - octoprint.plugin.core - INFO - Found 3 plugin(s): Hello World (1.0), CuraEngine (0.1), Discovery (0.1) + [...] + 2015-01-27 13:43:38,997 - octoprint.plugins.helloworld - INFO - Hello World! + +Looks like it still works! + +Something is still a bit ugly though. Take a look into ``__init__.py`` and ``setup.py``. It seems like we have a bunch +of information now defined twice: + +.. code-block:: python + + # __init__.py: + __plugin_name__ = "Hello World" + __plugin_version__ = "1.0" + __plugin_description__ = "A quick \"Hello World\" example plugin for OctoPrint" + + # setup.py + plugin_name = "OctoPrint-HelloWorld" + plugin_version = "1.0" + plugin_description = "A quick \"Hello World\" example plugin for OctoPrint" + +The nice thing about our plugin now being a proper python package is that OctoPrint can and will access the metadata defined +within ``setup.py``! So, we don't really need to define all this data twice. Remove it: + +.. code-block:: python + + # coding=utf-8 + from __future__ import absolute_import + + import octoprint.plugin + + class HelloWorldPlugin(octoprint.plugin.StartupPlugin): + def on_after_startup(self): + self._logger.info("Hello World!") + + __plugin_implementations__ = [HelloWorldPlugin()] + +and restart OctoPrint:: + + 2015-01-27 13:46:33,786 - octoprint.plugin.core - INFO - Found 3 plugin(s): OctoPrint-HelloWorld (1.0), CuraEngine (0.1), Discovery (0.1) + +Our "Hello World" Plugin still gets detected fine, but it's now listed under the same name it's installed under, +"OctoPrint-HelloWorld". That's a bit ugly, so we'll override that bit via ``__plugin_name__`` again: + +.. code-block:: python + + # coding=utf-8 + from __future__ import absolute_import + + import octoprint.plugin + + class HelloWorldPlugin(octoprint.plugin.StartupPlugin): + def on_after_startup(self): + self._logger.info("Hello World!") + + __plugin_name__ = "Hello World" + __plugin_implementations__ = [HelloWorldPlugin()] + + +Restart OctoPrint again:: + + 2015-01-27 13:48:54,122 - octoprint.plugin.core - INFO - Found 3 plugin(s): Hello World (1.0), CuraEngine (0.1), Discovery (0.1) + +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 ` for all available +overrides. + +Following the README of the `Plugin Skeleton `_ you could now +already publish your plugin on Github and it would be directly installable by others using pip:: + + pip install https://github.com/you/OctoPrint-HelloWorld/archive/master.zip + +But let's add some more features instead. + +Frontend or get out: How to add functionality to OctoPrint's web interface +-------------------------------------------------------------------------- + +Outputting a log line upon server startup is all nice and well, but we want to greet not only the administrator of +our OctoPrint instance but actually everyone that opens OctoPrint in their browser. Therefore, we need to modify +OctoPrint's web interface itself. + +We can do this using the :class:`TemplatePlugin` mixin. For now, let's start with a little "Hello World!" in OctoPrint's +navigation bar right at the top. For this we'll first add the :class:`TemplatePlugin` to our ``HelloWorldPlugin`` class: + +.. code-block:: python + + # coding=utf-8 + from __future__ import absolute_import + + import octoprint.plugin + + class HelloWorldPlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin): + def on_after_startup(self): + self._logger.info("Hello World!") + + __plugin_name__ = "Hello World" + __plugin_implementations__ = [HelloWorldPlugin()] + +Next, we'll create a sub folder ``templates`` underneath our ``octoprint_helloworld`` folder, and within that a file +``helloworld_navbar.jinja2`` like so: + +.. code-block:: html + + Hello World! + +Our plugin's directory structure should now look like this:: + + |-+ octoprint_helloworld + | |-+ templates + | | `- helloworld_navbar.jinja2 + | `- __init__.py + |- README.md + |- requirements.txt + `- setup.py + +Restart OctoPrint and open the web interface in your browser (make sure to clear your browser's cache!). + +.. _fig-plugins-gettingstarted-helloworld_navbar: +.. figure:: ../images/plugins_gettingstarted_helloworld_navbar.png + :align: center + :alt: Our "Hello World" navigation bar element in action + +Now look at that! \ No newline at end of file diff --git a/docs/plugins/hooks.rst b/docs/plugins/hooks.rst new file mode 100644 index 00000000..1b15dd4a --- /dev/null +++ b/docs/plugins/hooks.rst @@ -0,0 +1,8 @@ +.. _sec-plugins-hooks: + +Available plugin hooks +====================== + +.. todo:: + + Needs to be documented \ No newline at end of file diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index 25cff1f9..76b0621a 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -1,3 +1,5 @@ +.. automodule:: octoprint.plugin.types + .. _sec-plugins: ####### @@ -15,4 +17,8 @@ additional slicers. More plugin types are planned for the future. :maxdepth: 3 using.rst - developing.rst + gettingstarted.rst + infrastructure.rst + distributing.rst + mixins.rst + hooks.rst diff --git a/docs/plugins/infrastructure.rst b/docs/plugins/infrastructure.rst new file mode 100644 index 00000000..da6a6e5b --- /dev/null +++ b/docs/plugins/infrastructure.rst @@ -0,0 +1,68 @@ +.. _sec-plugins-infrastructure: + +Plugin Infrastructure +===================== + +.. _sec-plugins-infrastructure-controlproperties: + +Control Properties +------------------ + +``__plugin_name__`` + Name of your plugin, optional, overrides the name specified in ``setup.py`` if provided. +``__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_implementations__`` + Instances of one or more of the various :ref:`plugin mixins ` +``__plugin_hooks__`` + Handlers for one or more of the various :ref:`plugin 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_init__`` + Method called upon initializing of the plugin by the plugin subsystem, can be used to instantiate + plugin implementations, connecting them to hooks etc. + +.. _sec-plugins-infrastructure-injections: + +Injected Properties +------------------- + +``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._logger`` + A `python logger instance `_ logging to the log target + ``octoprint.plugin.``. +``self._plugin_manager`` + OctoPrint's plugin manager. +``self._printer_profile_manager`` + OctoPrint's printer profile manager. +``self._event_bus`` + OctoPrint's event bus. +``self._analysis_queue`` + OctoPrint's analysis queue for analyzing GCODEs or other files. +``self._slicing_manager`` + OctoPrint's slicing manager. +``self._file_manager`` + OctoPrint's file manager. +``self._printer`` + OctoPrint's printer management object. +``self._app_session_manager`` + OctoPrint's application session manager. diff --git a/docs/plugins/mixins.rst b/docs/plugins/mixins.rst new file mode 100644 index 00000000..b59d88c8 --- /dev/null +++ b/docs/plugins/mixins.rst @@ -0,0 +1,8 @@ +.. _sec-plugins-mixins: + +Available plugin mixins +======================= + +.. automodule:: octoprint.plugin.types + :members: + :undoc-members: diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 49f5702c..a96263e7 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -421,10 +421,9 @@ class Server(): pluginManager.initialize_implementations(dict( plugin_manager=pluginManager, printer_profile_manager=printerProfileManager, - event_manager=eventManager, + event_bus=eventManager, analysis_queue=analysisQueue, slicing_manager=slicingManager, - storage_managers=storage_managers, file_manager=fileManager, printer=printer, app_session_manager=appSessionManager,