mistral/doc/source/developer/extensions/extending_yaql.rst
Renat Akhmerov 9fb213c315 The first iteration of restructuring Mistral doc
* Grouped documentation articles into several main parts: user docs,
  admin docs, developer docs.
* Changed the index page so that it contains links to the index pages
  of the main documentation parts like user docs, admin docs etc.
* Fixed all the broken links
* Removed index generation since it's not informative at all in its
  current form and it exposes code internals (REST controller class
  names)
* Removed obsolete properties that are no longer used by the
  "openstackdocstheme" project
* Removed obsolete static html files
* Moved part of the images to the folders where they are used

Further work:

* Refactor main chapters (user, admin, developer) one by one and
  make them look consistent. For example, there are several pages
  that provide similar kind of information: overview, quick start,
  main features etc. It is a mess that's been accumulated throughout
  the last 4-5 years.
* Fill the gaps. Add all missing pages like: event notifications,
  workflow environment, etc.
* Move cookbooks from Wiki to this doc.

Partially implements: blueprint mistral-restructure-docs
Change-Id: Ia722a6885ad2fb97d63a34285b0a5b1a23da79e8
2020-01-22 14:06:51 +07:00

5.2 KiB

How to write a custom YAQL function

Tutorial

1. Create a new Python project, an empty folder, containing a basic setup.py file.

$ mkdir my_project
$ cd my_project
$ vim setup.py
try:
    from setuptools import setup, find_packages
except ImportError:
    from distutils.core import setup, find_packages

setup(
    name="project_name",
    version="0.1.0",
    packages=find_packages(),
    install_requires=["mistral", "yaql"],
    entry_points={
        "mistral.expression.functions": [
            "random_uuid = my_package.sub_package.yaql:random_uuid_"
        ]
    }
)

Publish the random_uuid_ function in the entry_points section, in the mistral.expression.functions namespace in setup.py. This function will be defined later.

Note that the package name will be used in Pip and must not overlap with other packages installed. project_name may be replaced by something else. The package name (my_package here) may overlap with other packages, but module paths (.py files) may not.

For example, it is possible to have a mistral package (though not recommended), but there must not be a mistral/version.py file, which would overlap with the file existing in the original mistral package.

yaql and mistral are the required packages. mistral is necessary in this example only because calls to the Mistral Python DB API are made.

For each entry point, the syntax is:

"<name_of_YAQL_expression> = <path.to.module>:<function_name>"

stevedore will detect all the entry points and make them available to all Python applications needing them. Using this feature, there is no need to modify Mistral's core code.

  1. Create a package folder.

A package folder is directory with a __init__.py file. Create a file that will contain the custom YAQL functions. There are no restrictions on the paths or file names used.

$ mkdir -p my_package/sub_package
$ touch my_package/__init__.py
$ touch my_package/sub_package/__init__.py
  1. Write a function in yaql.py.

That function might have context as first argument to have the current YAQL context available inside the function.

$ cd my_package/sub_package
$ vim yaql.py
from uuid import uuid5, UUID
from time import time


def random_uuid_(context):
    """generate a UUID using the execution ID and the clock"""

    # fetch the current workflow execution ID found in the context
    execution_id = context['__execution']['id']

    time_str = str(time())
    execution_uuid = UUID(execution_id)
    return uuid5(execution_uuid, time_str)

This function returns a random UUID using the current workflow execution ID as a namespace.

The context argument will be passed by Mistral YAQL engine to the function. It is invisible to the user. It contains variables from the current task execution scope, such as __execution which is a dictionary with information about the current workflow execution such as its id.

Note that errors can be raised and will be displayed in the task execution state information in case they are raised. Any valid Python primitives may be returned.

The context argument is optional. There can be as many arguments as wanted, even list arguments such as *args or dictionary arguments such as **kwargs can be used as function arguments.

For more information about YAQL, read the official YAQL documentation.

  1. Install pip and setuptools.
$ curl https://bootstrap.pypa.io/3.2/get-pip.py | python
$ pip install --upgrade setuptools
$ cd -
  1. Install the package (note that there is a dot . at the end of the line).
$ pip install .
  1. The YAQL function can be called in Mistral using its name random_uuid.

The function name in Python random_uuid_ does not matter, only the entry point name random_uuid does.

my_workflow:
  tasks:
    my_action_task:
      action: std.echo
      publish:
        random_id: <% random_uuid() %>
      input:
        output: "hello world"

Updating changes

After any new created functions or any modification in the code, re-run pip install . and restart Mistral.

Development

While developing, it is sufficient to add the root source folder (the parent folder of my_package) to the PYTHONPATH environment variable and the line random_uuid = my_package.sub_package.yaql:random_uuid_ in the Mistral entry points in the mistral.expression.functions namespace. If the path to the parent folder of my_package is /path/to/my_project.

$ export PYTHONPATH=$PYTHONPATH:/path/to/my_project
$ vim $(find / -name "mistral.*egg-info*")/entry_points.txt
[entry_points]
mistral.expression.functions =
    random_uuid = my_package.sub_package.yaql:random_uuid_