Decorators

In Hypermake, a task can be decorated with some decorators, effectively modifying its behavior. This can support

  • Running with different shell;
  • Running in specific virtual environments;
  • Running through some cluster submission systems;
  • etc.

A decorator in HyperMake is just an object with a run method that takes a script as input and runs a modified version.

object decorator:
    def run(internal_script):
        ...

If a decorator admits parameters, it simply becomes a class:

class decorator(args):
    def run(internal_script):
        ...

and when applying a decorator, one could write

@decorator(args)
task taskName(...) -> out:
  ...

Example 1: A decorator that runs a task in Python

An example that let us runs a task in Python instead of shell:

object python:
  def run(internal_script):
    python $internal_script

@python
task helloWorldInPython:
  print("Hello World" + " " + "in Python!")

There is no need to define this in your pipelines: it is already available in the standard library as @std.run(interpreter="python").

Example 2: Decorates a script to run in a Conda virtual environment

In Python, a task can be run in different Conda virtual environments. This is a decorator that lets us do that.

class conda(env):
  def run(internal_conda_script):
    eval "$(command conda 'shell.bash' 'hook' 2> /dev/null)"
    conda activate $env
    . $internal_conda_script
    conda deactivate

@conda(env={Env: base myenv})
task helloWorldFromEnv:
  python -c "print('Hello World in Python from $env!')"

Note that in the task helloWorldFromEnv, the decorator conda has a parameterized argument: env={Env: base myenv}. We can invoke both cases of the task helloWorldFromEnv:

hypermake tutorial/decorators.hm run 'helloWorldFromEnv[Env: *]'

We will see both lines

Hello World in Python from base!
Hello World in Python from myenv!

output to the terminal.

Example 3: Chaining decorators

We have now created two decorators:

  • @python that executes a script using Python instead of Bash as the interpreter;
  • @conda that runs a task in a specific Conda virtual environment.

Can we compose these decorators? Yes.

@conda(env={Env: base myenv})
@python
task helloWorldInPythonFromEnv:
  import os
  print(f"Hello World in Python from {os.environ['env']}!")

One can use os.environ[var] to get the environment variable $var in Python. First, our script is wrapped by @python, then @conda(env). Recall that HyperMake passes parameters into the script as environment variables: we cannot use $env to get the HyperMake variable in Python.

Example 4: A decorator that runs a compiled language: C

We can also create a decorator that runs a task in C. Since C is a compiled language, we need to compile the script first.

object gcc:
  def run(internal_c_script):
    ln -s $internal_c_script source.c
    gcc source.c -o source.out
    ./source.out

Now we can do fun things: write C scripts in HyperMake!

@gcc
task print(input="abcde"):
  #include <stdio.h>
  #include <stdlib.h>
  int main() {
    char* input = getenv("input");
    printf("%s\n", input);
    return 0;
  }