Python3 actions and triggers

Ryax supports actions and triggers written in python 3.

Here, we will describe what a Ryax python action is, what it consists of, as well as some technical functionalities made available.

You can find examples of python actions and triggers available by default in Ryax by clicking this link.

General

An action or a trigger requires at least 2 files: the python code in a .py file, and the Ryax metadata file: ryax_metadata.yaml. If the action has some external dependencies such as pypi packages, or anything outside the python standard library (ex: pandas, tensorflow), it needs a standard requirements.txt file with those dependencies listed on separate lines in plain text. You can also add a logo to your action, to easily identify it in the webui. This logo must be at most 250x250px, and be in either jpg or png format.

Dependencies

Python dependencies

In a typical python3 program, you might need to install some dependencies for your program to work. The common approach is to have a file requirements.txt with a list of all packages needed. Ryax will look at this file in the root of the action to know the external python libraries that are required by your code. If your code does not have any external dependencies, you can omit this file.

Windows users must be careful because windows sometimes adds a .txt resulting in a requirements.txt.txt.

A requirements.txt file consists of a list of python dependencies, one per line. You can add version constraints to be sure that you use the right version of your dependency:

pandas
tensorflow==1.3.0
pillow>=7.0.0

You can look for all available python dependencies on Pypi.

Binary dependencies

If you have non-python dependencies (called “binary dependencies”), you can specify them in the dependencies field of the ryax_metadata.yaml file. It is possible that some python actions require a binary dependency to work, like opencv. You can look for all supported dependencies, and their version, here. Some binaries might not work as Ryax run your code in a containerized and secured environement.

Metadata file

To describe your action, you need a ryax_metadata.yaml file that contains a high-level description of your action and the inputs/outputs. The file ryax_metadata.yaml follows the YAML standard that is equivalent to json in many aspects.

Here is an example:

apiVersion: "ryax.tech/v2.0"
kind: Processor
spec:
  id: tfdetection
  human_name: Tag objects
  description: "Tag detected objects on images using Tensorflow"
  type: python3
  version: "1.0"
  logo: mylogo.png
  dynamic_outputs: false
  inputs:
  - help: Model name
    human_name: Model name
    name: model
    type: enum
    enum_values:
     - ssdlite_mobilenet_v2_coco_2018_05_09
     - mask_rcnn_inception_v2_coco_2018_01_28
    default_value: ssdlite_mobilenet_v2_coco_2018_05_09
  - help: An image to be tagged; in any format accepted by OpenCV
    human_name: Image
    name: image
    type: file
  outputs:
  - help: Path of the tagged image
    human_name: Tagged image
    name: tagged_image
    type: file

Some explanation on the fileds:

  • kind to tell the kind of the action: a trigger, or an action?

  • id unique name of the action

  • version unique version of the action

  • human_name a short name, readable by a human

  • description a description of the action

  • type the programming language

  • logo optional, a relative path to a logo file.

Note the field dynamic_outputs as well. This is an optional field and will default to false. Only triggers may have dynamic outputs. This is useful in some cases such as when using an online form which can be filled out, to trigger workflows. This feature allows for the reuse of all the code for that trigger, while also allowing users to re-define the fields on that form in different workflows.

inputs and outputs contain the list of all the inputs and outputs of this action. Here, the name is the name in your python program, while the human_name is to display in the webUI.

The complete list of all fields is available here as a JSON Schema.

TODO: add json schema. {.error}

The code

Ryax knows how to call your code by importing a python code and starting a specific function in it.

Depending on the kind of the action, we use 2 different filenames:

  • ryax_run.py for triggers

  • ryax_handler.py for all other actions

When creating a action, Ryax will copy all the files that are in the action directory. Thus, if you split your code in multiple files and use some resource files, they will be copied into the action.

Actions

The ryax_handler.py file should contain a handle method that takes a dict as first parameter and returns a dict or a list of dict.

def handle(request: dict)->dict:
    a = request["an_integer_input"]
    a = a + 42
    return {
        "output1": "value",
        "output2": a,
        }

By looking at this code, we can guess that this action has at least an input called an_integer_input (an integer), and has 2 outputs: output1 and output2. Ryax should know about these by describing them in the ryax_metadata.yaml file.

The handle function must return a dict, it should contain an entry for all outputs of the action, with a valid value, as defined and typed in the ryax_metadata.yaml file.

Triggers

ryax_run.py is the file to write your python code for a trigger. This script must contain a run function that takes 2 paramaters: a dict containing the inputs and an instance of the RyaxSourceProtocol class.

import asyncio
from ryax_wrapper import RyaxSourceProtocol  #optional import

async def run(service: RyaxSourceProtocol, input_values: dict) -> None:
    while True:
        text = source.inputs_values["input1"]
        source.create_run({"output1": "hello "+text})
        await asyncio.sleep(1)

In this particular case, we are creating an execution every second. We can guess that this action has 1 input called input1 and 1 output called output1. Ryax should know about these by describing them in the ryax_metadata.yaml file.

Here is the interface of RyaxSourceProtocol:

class RyaxRunStatus(Enum):
    DONE = 1
    ERROR = 2


class RyaxSourceProtocol(metaclass=abc.ABCMeta):
    async def create_run_error(self) -> None:
        await self.create_run({}, status=RyaxRunStatus.ERROR)

    @abc.abstractmethod
    async def create_run(
        self,
        data: dict,
        running_time: float = 0.001,
        status: RyaxRunStatus = RyaxRunStatus.DONE,
    ) -> None:
        ...

    def get_output_definitions(self) -> Dict[str, Any]:
        ...

Migrating from v1 to v2.0

Most of the specifications stay the same, except the following.

ryax_metadata.yaml changes:

  • apiVersion: "ryax.tech/v2.0"

  • kind changes: Gateways is now Source, Functions is now Processor, Publishers is now Publisher

  • spec.detail becomes spec.description

The requirements.txt is now completly standard.

handler.py has to be renamed ryax_handler.py.

run.py has to be renamed ryax_run.py. Also, the content has to be changed, we do not use a class anymore. You have to use a run function has explained in the documentation above.

If the ryax_metadata.yaml file contains unknown fields, an error is thrown.