Quickstart

Creating a basic execution graph using alkymi is as easy as follows:

import alkymi as alk


@alk.recipe()
def long_running_task() -> int:
    # Perform expensive computation here ...
    hard_to_compute_result = 42
    return hard_to_compute_result


result = long_running_task.brew()  # == 42

If you execute the above script multiple times, alkymi will only call long_running_task the first time, and then cache the results for subsequent evaluations. Note that in alkymi lingo, a recipe is a fully-defined (all inputs known) task that can be evaluated by calling .brew(). The actual long_running_task function will be referred to as the bound function (wrapped in a recipe). Note that alkymi is declarative - the bound functions are only executed when requested through a call to .brew().

Recipes can return arbitrary data of any type, which can be cached automatically by alkymi (see Caching). If alkymi detects a change in the graph (e.g. a bound function changing between two evaluations), the affected nodes of the graph will be re-evaluated automatically on the next call to .brew().

Recipes can depend on other recipes using the ingredients argument to recipe:

import requests

@alk.recipe()
def upload_to_cloud(long_running_task: int) -> None:
    r = requests.post('http://httpbin.org/post', json={"hard_to_compute_value": result})
    if r.status_code != 200:
        raise RuntimeError("Post to cloud failed with code: {}".format(r.status_code))

upload_to_cloud.brew()  # Will only perform POST whenever input (long_running_task), or upload_to_cloud, changes

Whenever upload_to_cloud.brew() is called, alkymi will automatically traverse the entire dependency chain (in this case just long_running_task) to see if everything is up-to-date. If not, the needed steps will be evaluated to bring the graph up-to-data. Note that alkymi will automatically associate the long_running_task argument with the previously defined recipe of the same name, equivalent to how pytest resolves fixtures.

Note

Ingredients can also be passed explicitly to a recipe using the ingredients argument of the recipe decorator (instead of relying on the automatic lookup based on argument name <-> recipe name)