7
0
mirror of https://github.com/ChristianLight/tutor.git synced 2024-06-01 05:40:48 +00:00

docs: clarify YAML vs. Python plugins & CLI customization

I found the existing docs a bit light on the particulars
of how the YAML and Python plugin APIs relate. I was
able to figure it out (there's a nice congruence
between them) but I think these tweaks should it make
it more immediately obvious to readers how the Python
API is a essentially a superset of the YAML API that
allows for dynamic behavior.
This commit is contained in:
Kyle McCormick 2022-01-11 16:33:02 -05:00 committed by Régis Behmo
parent bb888a8af5
commit 87f8348a01
2 changed files with 54 additions and 9 deletions

View File

@ -1,7 +1,20 @@
Plugin API
==========
Plugins can affect the behaviour of Tutor at multiple levels. First, plugins can define new services with their Docker images, settings and the right initialisation commands. To do so you will have to define custom :ref:`config <plugin_config>`, :ref:`patches <plugin_patches>`, :ref:`hooks <plugin_hooks>` and :ref:`templates <plugin_templates>`. Then, plugins can also extend the CLI by defining their own :ref:`commands <plugin_command>`.
A Tutor plugin is defined either by a YAML file or a Python package.
Plugins can affect the behaviour of Tutor at multiple levels:
they can define Docker images, introduce new services, modify the settings of existing services, specify intialization processes, and more.
Plugins can achieve all this using four extension points that Tutor exposes to both YAML and Python plugins:
* :ref:`config <plugin_config>`,
* :ref:`patches <plugin_patches>`,
* :ref:`hooks <plugin_hooks>` and
* :ref:`templates <plugin_templates>`.
Additionally, Python plugins can extend Tutor's command line interface using one futher extension point:
* :ref:`command <plugin_command>`.
.. _plugin_config:
@ -60,7 +73,8 @@ Example::
This will add a Redis instance to the services run with ``tutor local`` commands.
.. note::
The ``patches`` attribute can be a callable function instead of a static dict value.
In Python plugins, remember that ``patches`` can be a callable function instead of a static dict value.
One can use this to dynamically load a list of patch files from a folder.
.. _plugin_hooks:
@ -176,7 +190,9 @@ When saving the environment, template files that are stored in a template root w
command
~~~~~~~
A plugin can provide custom command line commands. Commands are assumed to be `click.Command <https://click.palletsprojects.com/en/8.0.x/api/#commands>`__ objects, and you typically implement them using the `click.command <https://click.palletsprojects.com/en/8.0.x/api/#click.command>`__ decorator.
Python plugins can provide a custom command line interface.
The ``command`` attribute is assumed to be a `click.Command <https://click.palletsprojects.com/en/8.0.x/api/#commands>`__ object,
and you typically implement them using the `click.command <https://click.palletsprojects.com/en/8.0.x/api/#click.command>`__ decorator.
You may also use the `click.pass_obj <https://click.palletsprojects.com/en/8.0.x/api/#click.pass_obj>`__ decorator to pass the CLI `context <https://click.palletsprojects.com/en/8.0.x/api/#click.Context>`__, such as when you want to access Tutor configuration settings from your command.
@ -207,11 +223,21 @@ You can even define subcommands by creating `command groups <https://click.palle
def command():
pass
@click.command(help="I'm a plugin subcommand")
@command.command(help="I'm a plugin subcommand")
def dosomething():
click.echo("This subcommand is awesome")
This would allow any user to run::
This would allow any user to see your sub-commands::
$ tutor myplugin
Usage: tutor dev [OPTIONS] COMMAND [ARGS]...
I'm a plugin command group
Commands:
dosomething I'm a plugin subcommand
and then run them::
$ tutor myplugin dosomething
This subcommand is awesome

View File

@ -14,7 +14,9 @@ YAML files that are stored in the tutor plugins root folder will be automaticall
On Linux, this points to ``~/.local/share/tutor-plugins``. The location of the plugin root folder can be modified by setting the ``TUTOR_PLUGINS_ROOT`` environment variable.
YAML plugins need to define two extra keys: "name" and "version". Custom CLI commands are not supported by YAML plugins.
YAML plugins must define two special top-level keys: ``name`` and ``version``.
Then, YAML plugins may use four top-level keys to customize Tutor's behavior: ``config``, ``patches``, ``hooks``, and ``templates``.
Custom CLI commands are not supported by YAML plugins.
Let's create a simple plugin that adds your own `Google Analytics <https://analytics.google.com/>`__ tracking code to your Open edX platform. We need to add the ``GOOGLE_ANALYTICS_ACCOUNT`` and ``GOOGLE_ANALYTICS_TRACKING_ID`` settings to both the LMS and the CMS settings. To do so, we will only have to create the ``openedx-common-settings`` patch, which is shared by the development and the production settings both for the LMS and the CMS. First, create the plugin directory::
@ -58,17 +60,34 @@ That's it! And it's very easy to share your plugins. Just upload them to your Gi
Python package
~~~~~~~~~~~~~~
Creating a plugin as a Python package allows you to define more complex logic and to store your patches in a more structured way. Python Tutor plugins are regular Python packages that define a specific entrypoint: ``tutor.plugin.v0``.
Creating a plugin as a Python package allows you to define more complex logic and to store your patches in a more structured way. Python Tutor plugins are regular Python packages that define an entrypoint within the ``tutor.plugin.v0`` group:
Example::
from setuptools import setup
setup(
...
entry_points={"tutor.plugin.v0": ["myplugin = myplugin.plugin"]},
entry_points={
"tutor.plugin.v0": ["myplugin = myplugin.plugin"]
},
)
The ``myplugin.plugin`` python module should then declare the ``config``, ``hooks``, etc. attributes that will define its behaviour.
The ``myplugin/plugin.py`` Python module can then define the attributes ``config``, ``patches``, ``hooks``, and ``templates`` to specify the plugin's behavior. The attributes may be defined either as dictionaries or as zero-argument callables returning dictionaries; in the latter case, the callable will be evaluated upon plugin load. Finally, the ``command`` attribute can be defined as an instance of ``click.Command`` to define the plugin's command line interface.
Example::
import click
templates = pkg_resources.resource_filename(...)
config = {...}
hooks = {...}
def patches():
...
@click.command(...)
def command():
...
To get started on the right foot, it is strongly recommended to create your first plugin with the `tutor plugin cookiecutter <https://github.com/overhangio/cookiecutter-tutor-plugin>`__::