Hooks catalog
Tutor can be extended by making use of “hooks”. Hooks are either “actions” or “filters”. Here, we list all instances of actions and filters that are used across Tutor. Plugin developers can leverage these hooks to modify the behaviour of Tutor.
The underlying Python hook classes and API are documented here.
- class tutor.hooks.Actions
This class is a container for all actions used across Tutor (see
tutor.core.hooks.Action
). Actions are used to trigger callback functions at specific moments in the Tutor life cycle.To create a new callback for an existing action, start by importing the hooks module:
from tutor import hooks
Then create your callback function and decorate it with the
add
method of the action you’re interested in:@hooks.Actions.SOME_ACTION.add() def your_action(): # Do stuff here
Your callback function should have the same signature as the original action. For instance, to add a callback to the
COMPOSE_PROJECT_STARTED
action:@hooks.Actions.COMPOSE_PROJECT_STARTED.add(): def run_this_on_start(root, config, name): print(root, config["LMS_HOST", name])
Your callback function will then be called whenever the
COMPOSE_PROJECT_STARTED.do
method is called, i.e: whentutor local start
ortutor dev start
is run.Note that action callbacks do not return anything.
For more information about how actions work, check out the
tutor.core.hooks.Action
API.- COMPOSE_PROJECT_STARTED: tutor.core.hooks.actions.Action[(<class 'str'>, typing.Dict[str, typing.Union[str, float, NoneType, bool, typing.List[str], typing.List[typing.Any], typing.Dict[str, typing.Any], typing.Dict[typing.Any, typing.Any]]], <class 'str'>)] = Action('compose:project:started')
Triggered whenever a “docker-compose start”, “up” or “restart” command is executed.
- Parameters
root (str) – project root.
config (dict) – project configuration.
name (str) – docker-compose project name.
- CORE_READY: tutor.core.hooks.actions.Action[()] = Action('core:ready')
Called whenever the core project is ready to run. This action is called as soon as possible. This is the right time to discover plugins, for instance. In particular, we auto-discover the following plugins:
Python packages that declare a “tutor.plugin.v0” entrypoint.
Python packages that declare a “tutor.plugin.v1” entrypoint.
YAML and Python plugins stored in ~/.local/share/tutor-plugins (as indicated by
tutor plugins printroot
)When running the binary version of Tutor, official plugins that ship with the binary are automatically discovered.
Discovering a plugin is typically done by the Tutor plugin mechanism. Thus, plugin developers probably don’t have to implement this action themselves.
This action does not have any parameter.
- DO_JOB: tutor.core.hooks.actions.Action[(<class 'str'>, typing.Any)] = Action('do:job')
Called just before triggering the job tasks of any
... do <job>
command.- Parameters
job (str) – job name.
args – job positional arguments.
kwargs – job named arguments.
- PLUGINS_LOADED: tutor.core.hooks.actions.Action[()] = Action('plugins:loaded')
Triggered after all plugins have been loaded. At this point the list of loaded plugins may be obtained from the
Filters.PLUGINS_LOADED
filter.This action does not have any parameter.
- PLUGIN_LOADED: tutor.core.hooks.actions.ActionTemplate[()] = ActionTemplate('plugins:loaded:{0}')
Triggered when a single plugin needs to be loaded. Only plugins that have previously been discovered can be loaded (see
CORE_READY
).Plugins are typically loaded because they were enabled by the user; the list of plugins to enable is found in the project root (see
PROJECT_ROOT_READY
).Most plugin developers will not have to implement this action themselves, unless they want to perform a specific action at the moment the plugin is enabled.
This action does not have any parameter.
- PLUGIN_UNLOADED: tutor.core.hooks.actions.Action[(<class 'str'>, <class 'str'>, typing.Dict[str, typing.Union[str, float, NoneType, bool, typing.List[str], typing.List[typing.Any], typing.Dict[str, typing.Any], typing.Dict[typing.Any, typing.Any]]])] = Action('plugins:unloaded')
Triggered when a single plugin is unloaded. Only plugins that have previously been loaded can be unloaded (see
PLUGIN_LOADED
).Plugins are typically unloaded because they were disabled by the user.
Most plugin developers will not have to implement this action themselves, unless they want to perform a specific action at the moment the plugin is disabled.
- Parameters
plugin (str) – plugin name.
root (str) – absolute path to the project root.
config – full project configuration
- PROJECT_ROOT_READY: tutor.core.hooks.actions.Action[(<class 'str'>,)] = Action('project:root:ready')
Called as soon as we have access to the Tutor project root.
- Parameters
root (str) – absolute path to the project root.
- class tutor.hooks.Filters
Here are the names of all filters used across Tutor. (see
tutor.core.hooks.Filter
) Filters are used to modify some data at specific points during the Tutor life cycle.To add a callback to an existing filter, start by importing the hooks module:
from tutor import hooks
Then create your callback function and decorate it with
add
method of the filter instance you need:@hooks.Filters.SOME_FILTER.add() def your_filter_callback(some_data): # Do stuff here with the data ... # return the modified data return some_data
Note that your filter callback should have the same signature as the original filter. The return value should also have the same type as the first argument of the callback function.
Many filters have a list of items as the first argument. Quite often, plugin developers just want to add a new item at the end of that list. In such cases there is no need for a callback function. Instead, you can use the add_item method. For instance, you can add a “hello” to the init task of the lms container by modifying the
CLI_DO_INIT_TASKS
filter:hooks.CLI_DO_INIT_TASKS.add_item(("lms", "echo hello"))
To add multiple items at a time, use add_items:
hooks.CLI_DO_INIT_TASKS.add_items( ("lms", "echo 'hello from lms'"), ("cms", "echo 'hello from cms'"), )
The
echo
commands will then be run every time the “init” tasks are run, for instance during tutor local launch.For more information about how filters work, check out the
tutor.core.hooks.Filter
API.- CLI_COMMANDS: tutor.core.hooks.filters.Filter[list[click.core.Command], ()] = Filter('cli:commands')
List of command line interface (CLI) commands.
- Parameters
commands (list) – commands are instances of
click.Command
. They will all be added as subcommands of the maintutor
command.
- CLI_DO_COMMANDS: tutor.core.hooks.filters.Filter[list[typing.Callable[[typing.Any], typing.Iterable[tuple[str, str]]]], ()] = Filter('cli:commands:do')
List of do … commands.
- Parameters
commands (list) – see
CLI_COMMANDS
. These commands will be added as subcommands to the local/dev/k8s do commands. They must return a list of (“service name”, “service command”) tuples. Each “service command” will be executed in the “service” container, both in local, dev and k8s mode.
- CLI_DO_INIT_TASKS: tutor.core.hooks.filters.Filter[list[tuple[str, str]], ()] = Filter('cli:commands:do:init')
List of initialization tasks (scripts) to be run in the init job. This job includes all database migrations, setting up, etc. To run some tasks before or after others, they should be assigned a different priority.
- Parameters
tasks (list[tuple[str, str]]) – list of
(service, task)
tuples. Each task is essentially a bash script to be run in the “service” container. Scripts may contain Jinja markup, similar to templates.
- COMMANDS_INIT: tutor.core.hooks.filters.Filter[list[tuple[str, tuple[str, ...]]], ()] = Filter('commands:init')
DEPRECATED use
CLI_DO_INIT_TASKS
instead.List of commands to be executed during initialization. These commands typically include database migrations, setting feature flags, etc.
- Parameters
tasks (list[tuple[str, tuple[str, ...]]]) –
list of
(service, path)
tasks.service
is the name of the container in which the task will be executed.path
is a tuple that corresponds to a template relative path. Example:("myplugin", "hooks", "myservice", "pre-init")
(seeIMAGES_BUILD
). The command to execute will be read from that template, after it is rendered.
- COMMANDS_PRE_INIT: tutor.core.hooks.filters.Filter[list[tuple[str, tuple[str, ...]]], ()] = Filter('commands:pre-init')
DEPRECATED use
CLI_DO_INIT_TASKS
instead with a lower priority score.List of commands to be executed prior to initialization. These commands are run even before the mysql databases are created and the migrations are applied.
- Parameters
tasks (list[tuple[str, tuple[str, ...]]]) – list of
(service, path)
tasks. (seeCOMMANDS_INIT
).
- COMPOSE_DEV_JOBS_TMP: tutor.core.hooks.filters.Filter[Dict[str, Union[str, float, None, bool, List[str], List[Any], Dict[str, Any], Dict[Any, Any]]], ()] = Filter('compose:dev-jobs:tmp')
Same as
COMPOSE_LOCAL_JOBS_TMP
but for the development environment.
- COMPOSE_DEV_TMP: tutor.core.hooks.filters.Filter[Dict[str, Union[str, float, None, bool, List[str], List[Any], Dict[str, Any], Dict[Any, Any]]], ()] = Filter('compose:dev:tmp')
Same as
COMPOSE_LOCAL_TMP
but for the development environment.
- COMPOSE_LOCAL_JOBS_TMP: tutor.core.hooks.filters.Filter[Dict[str, Union[str, float, None, bool, List[str], List[Any], Dict[str, Any], Dict[Any, Any]]], ()] = Filter('compose:local-jobs:tmp')
Same as
COMPOSE_LOCAL_TMP
but for jobs
- COMPOSE_LOCAL_TMP: tutor.core.hooks.filters.Filter[Dict[str, Union[str, float, None, bool, List[str], List[Any], Dict[str, Any], Dict[Any, Any]]], ()] = Filter('compose:local:tmp')
Contents of the (local|dev)/docker-compose.tmp.yml files that will be generated at runtime. This is used for instance to bind-mount folders from the host (see
COMPOSE_MOUNTS
)- Parameters
docker_compose_tmp (dict[str, ...]) – values which will be serialized to local/docker-compose.tmp.yml. Keys and values will be rendered before saving, such that you may include
{{ ... }}
statements.
- COMPOSE_MOUNTS: tutor.core.hooks.filters.Filter[list[tuple[str, str]], (<class 'str'>,)] = Filter('compose:mounts')
List of folders to bind-mount in docker-compose containers, either in
tutor local
ortutor dev
.Many
tutor local
andtutor dev
commands support--mounts
options that allow plugins to define custom behaviour at runtime. For instance--mount=/path/to/edx-platform
would cause this host folder to be bind-mounted in different containers (lms, lms-worker, cms, cms-worker) at the /openedx/edx-platform location. Plugin developers may implement this filter to define custom behaviour when mounting folders that relate to their plugins. For instance, the ecommerce plugin may process the--mount=/path/to/ecommerce
option.- Parameters
mounts (list[tuple[str, str]]) – each item is a
(service, path)
tuple, whereservice
is the name of the docker-compose service andpath
is the location in the container where the folder should be bind-mounted. Note: the path must be slash-separated (“/”). Thus, do not useos.path.join
to generate thepath
because it will fail on Windows.name (str) – basename of the host-mounted folder. In the example above, this is “edx-platform”. When implementing this filter you should check this name to conditionnally add mounts.
- CONFIG_DEFAULTS: tutor.core.hooks.filters.Filter[list[tuple[str, typing.Any]], ()] = Filter('config:defaults')
Declare new default configuration settings that don’t necessarily have to be saved in the user
config.yml
file. Default settings may be overridden withtutor config save --set=...
, in which case they will automatically be added toconfig.yml
.- Parameters
items (list[tuple[str, ...]]) – list of (name, value) new settings. All new entries must be prefixed with the plugin name in all-caps.
- CONFIG_OVERRIDES: tutor.core.hooks.filters.Filter[list[tuple[str, typing.Any]], ()] = Filter('config:overrides')
Modify existing settings, either from Tutor core or from other plugins. Beware not to override any important setting, such as passwords! Overridden setting values will be printed to stdout when the plugin is disabled, such that users have a chance to back them up.
- Parameters
items (list[tuple[str, ...]]) – list of (name, value) settings.
- CONFIG_UNIQUE: tutor.core.hooks.filters.Filter[list[tuple[str, typing.Any]], ()] = Filter('config:unique')
Declare unique configuration settings that must be saved in the user
config.yml
file. This is where you should declare passwords and randomly-generated values that are different from one environment to the next.- Parameters
items (list[tuple[str, ...]]) – list of (name, value) new settings. All names must be prefixed with the plugin name in all-caps.
- DOCKER_BUILD_COMMAND: tutor.core.hooks.filters.Filter[list[str], ()] = Filter('docker:build:command')
Use this filter to modify the
docker build
command. For instance, to replace thebuild
subcommand bybuildx build
.- Parameters
command (list[str]) – the full build command, including options and arguments. Note that these arguments do not include the leading
docker
command.
- ENV_PATCH: tutor.core.hooks.filters.FilterTemplate[list[str], ()] = FilterTemplate('env:patches:{0}')
List of patches that should be inserted in a given location of the templates. The filter name must be formatted with the patch name. This filter is not so convenient and plugin developers will probably prefer
ENV_PATCHES
.
- ENV_PATCHES: tutor.core.hooks.filters.Filter[list[tuple[str, str]], ()] = Filter('env:patches')
List of patches that should be inserted in a given location of the templates. This is very similar to
ENV_PATCH
, except that the patch is added as a(name, content)
tuple.- Parameters
patches (list[tuple[str, str]]) – pairs of (name, content) tuples. Use this filter to modify the Tutor templates.
- ENV_PATTERNS_IGNORE: tutor.core.hooks.filters.Filter[list[str], ()] = Filter('env:patterns:ignore')
List of template path patterns to be ignored when rendering templates to the project root. By default, we ignore:
hidden files (
.*
)__pycache__
directories and*.pyc
files“partials” directories.
Ignored patterns are overridden by include patterns; see
ENV_PATTERNS_INCLUDE
.- Parameters
patterns (list[str]) – list of regular expression patterns. E.g:
r"(.*/)?ignored_file_name(/.*)?"
.
- ENV_PATTERNS_INCLUDE: tutor.core.hooks.filters.Filter[list[str], ()] = Filter('env:patterns:include')
List of template path patterns to be included when rendering templates to the project root. Patterns from this list will take priority over the patterns from
ENV_PATTERNS_IGNORE
.- Parameters
patterns (list[str]) – list of regular expression patterns. See
ENV_PATTERNS_IGNORE
.
- ENV_TEMPLATE_FILTERS: tutor.core.hooks.filters.Filter[list[tuple[str, typing.Callable[..., typing.Any]]], ()] = Filter('env:templates:filters')
List of Jinja2 filters that will be available in templates. Jinja2 filters are basically functions that can be used as follows within templates:
{{ "somevalue"|my_filter }}
Note that Jinja2 filters are a completely different thing than the Tutor hook filters, although they share the same name.
Out of the box, Tutor comes with the following filters:
common_domain
: Return the longest common name between two domain names. Example:{{ "studio.demo.myopenedx.com"|common_domain("lms.demo.myopenedx.com") }}
is equal to “demo.myopenedx.com”.encrypt
: Encrypt an arbitrary string. The encryption process is compatible with htpasswd verification.list_if
: In a list of(value, condition)
tuples, return the list ofvalue
for which thecondition
is true.long_to_base64
: Base-64 encode a long integer.iter_values_named
: Yield the values of the configuration settings that match a certain pattern. Example:{% for value in iter_values_named(prefix="KEY", suffix="SUFFIX")%}...{% endfor %}
. By default, only non-empty values are yielded. To iterate also on empty values, pass theallow_empty=True
argument.patch
: See patches.random_string
: Return a random string of the given length composed of ASCII letters and digits. Example:{{ 8|random_string }}
.reverse_host
: Reverse a domain name (see reference). Example:{{ "demo.myopenedx.com"|reverse_host }}
is equal to “com.myopenedx.demo”.rsa_import_key
: Import a PEM-formatted RSA key and return the corresponding object.rsa_private_key
: Export an RSA private key in PEM format.walk_templates
: Iterate recursively over the templates of the given folder. For instance:{% for file in "apps/myplugin"|walk_templates %} ... {% endfor %}
- Parameters
filters – list of (name, function) tuples. The function signature should correspond to its usage in templates.
- ENV_TEMPLATE_ROOTS: tutor.core.hooks.filters.Filter[list[str], ()] = Filter('env:templates:roots')
List of all template root folders.
- Parameters
templates_root (list[str]) – absolute paths to folders which contain templates. The templates in these folders will then be accessible by the environment renderer using paths that are relative to their template root.
- ENV_TEMPLATE_TARGETS: tutor.core.hooks.filters.Filter[list[tuple[str, str]], ()] = Filter('env:templates:targets')
List of template source/destination targets.
- Parameters
targets (list[tuple[str, str]]) – list of (source, destination) pairs. Each source is a path relative to one of the template roots, and each destination is a path relative to the environment root. For instance: adding
("c/d", "a/b")
to the filter will cause all files from “c/d” to be rendered to thea/b/c/d
subfolder.
- ENV_TEMPLATE_VARIABLES: tutor.core.hooks.filters.Filter[list[tuple[str, typing.Any]], ()] = Filter('env:templates:variables')
List of extra variables to be included in all templates.
- Parameters
filters – list of (name, value) tuples.
- IMAGES_BUILD: tutor.core.hooks.filters.Filter[list[tuple[str, tuple[str, ...], str, tuple[str, ...]]], typing.Dict[str, typing.Union[str, float, NoneType, bool, typing.List[str], typing.List[typing.Any], typing.Dict[str, typing.Any], typing.Dict[typing.Any, typing.Any]]]] = Filter('images:build')
List of images to be built when we run
tutor images build ...
.- Parameters
tasks (list[tuple[str, tuple[str, ...], str, tuple[str, ...]]]) –
list of
(name, path, tag, args)
tuples.name
is the name of the image, as intutor images build myimage
.path
is the relative path to the folder that contains the Dockerfile. For instance("myplugin", "build", "myservice")
indicates that the template will be read frommyplugin/build/myservice/Dockerfile
tag
is the Docker tag that will be applied to the image. It will be rendered at runtime with the user configuration. Thus, the image tag could be"{{ DOCKER_REGISTRY }}/myimage:{{ TUTOR_VERSION }}"
.args
is a list of arguments that will be passed todocker build ...
.
config (Config) – user configuration.
- IMAGES_PULL: tutor.core.hooks.filters.Filter[list[tuple[str, str]], typing.Dict[str, typing.Union[str, float, NoneType, bool, typing.List[str], typing.List[typing.Any], typing.Dict[str, typing.Any], typing.Dict[typing.Any, typing.Any]]]] = Filter('images:pull')
List of images to be pulled when we run
tutor images pull ...
.- Parameters
tasks (list[tuple[str, str]]) –
list of
(name, tag)
tuples.name
is the name of the image, as intutor images pull myimage
.tag
is the Docker tag that will be applied to the image. (seeIMAGES_BUILD
).
config (Config) – user configuration.
- IMAGES_PUSH: tutor.core.hooks.filters.Filter[list[tuple[str, str]], typing.Dict[str, typing.Union[str, float, NoneType, bool, typing.List[str], typing.List[typing.Any], typing.Dict[str, typing.Any], typing.Dict[typing.Any, typing.Any]]]] = Filter('images:push')
List of images to be pushed when we run
tutor images push ...
. Parameters are the same as forIMAGES_PULL
.
- PLUGINS_INFO: tutor.core.hooks.filters.Filter[list[tuple[str, str]], ()] = Filter('plugins:installed:versions')
Information about each installed plugin, including its version. Keep this information to a single line for easier parsing by 3rd-party scripts.
- Parameters
versions (list[tuple[str, str]]) – each pair is a
(plugin, info)
tuple.
- PLUGINS_INSTALLED: tutor.core.hooks.filters.Filter[list[str], ()] = Filter('plugins:installed')
List of installed plugins. In order to be added to this list, a plugin must first be discovered (see
Actions.CORE_READY
).- Parameters
plugins (list[str]) – plugin developers probably don’t have to implement this filter themselves, but they can apply it to check for the presence of other plugins.
- PLUGINS_LOADED: tutor.core.hooks.filters.Filter[list[str], ()] = Filter('plugins:loaded')
List of loaded plugins.
- Parameters
plugins (list[str]) – plugin developers probably don’t have to modify this filter themselves, but they can apply it to check whether other plugins are enabled.
- PLUGIN_INDEXES: tutor.core.hooks.filters.Filter[list[str], ()] = Filter('plugins:indexes:entries')
List of plugin indexes that are loaded when we run tutor plugins update. By default, the plugin indexes are stored in the user configuration. This filter makes it possible to extend and modify this list with plugins.
- Parameters
indexes (list[str]) – list of index URLs. Remember that entries further in the list have priority.
- PLUGIN_INDEX_ENTRY_TO_INSTALL: tutor.core.hooks.filters.Filter[dict[str, str], ()] = Filter('plugins:indexes:entries:install')
When installing an entry from a plugin index, the plugin data from the index will go through this filter before it is passed along to pip install. Thus, this is a good place to add custom authentication when you need to install from a private index.
- Parameters
plugin (dict[str, str]) – the dict entry from the plugin index. It includes an additional “index” key which contains the plugin index URL.
- PLUGIN_INDEX_URL: tutor.core.hooks.filters.Filter[str, ()] = Filter('plugins:indexes:url')
Filter to modify the url of a plugin index url. This is convenient to alias plugin indexes with a simple name, such as “main” or “contrib”.
- Parameters
url (str) – value passed to the index add/remove commands.
- class tutor.hooks.Contexts
Here we list all the
contexts
that are used across Tutor. It is not expected that plugin developers will ever need to use contexts. But if you do, this is how it should be done:from tutor import hooks with hooks.Contexts.SOME_CONTEXT.enter(): # do stuff and all created hooks will include SOME_CONTEXT ... # Apply only the hook callbacks that were created within SOME_CONTEXT hooks.Actions.MY_ACTION.do_from_context(str(hooks.Contexts.SOME_CONTEXT)) hooks.Filters.MY_FILTER.apply_from_context(hooks.Contexts.SOME_CONTEXT.name)
- APP = ContextTemplate('app:{0}')
We enter this context whenever we create hooks for a specific application or : plugin. For instance, plugin “myplugin” will be enabled within the “app:myplugin” context.
- Parameters
args (t.Any) –
kwargs (t.Any) –
- Return type
- PLUGINS = <tutor.core.hooks.contexts.Context object>
Plugins will be installed and enabled within this context.
- PLUGINS_V0_ENTRYPOINT = <tutor.core.hooks.contexts.Context object>
Python entrypoint plugins will be installed within this context.
- PLUGINS_V0_YAML = <tutor.core.hooks.contexts.Context object>
YAML-formatted v0 plugins will be installed within this context.