Example Usage#

Initializing and Basic Operations#

Firstly, dml_util expects both DML_S3_BUCKET and DML_S3_PREFIX to be set. This is where we’ll store data. For this example, we set it to random nonsense (we won’t be using s3 here).

from contextlib import redirect_stderr, redirect_stdout

from daggerml import  Dml, Error
from dml_util import funkify

# Create a dml instance
dml = Dml()
# Create a new DAG
dag = dml.new('my_dag', 'example dag')
# Put simple values
node1 = dag._put(list(range(5)))
# you can always get the value of a node
node1.value()
[0, 1, 2, 3, 4]

Because our node is a list, we can do list stuff with it…

# Indexing and slicing
print(f"{node1[0] = } -- {node1[0].value()}")

# Create complex structures
complex_node = dag._put({'x': node1, 'y': 'z'})

# Iterate and access items
for k, v in complex_node.items():
    print(f"{k = !s}, {v = } -- {v.value() = !r}")

# Commit the DAG
dag.result = complex_node
node1[0] = Node(node/231a9420336d1a186ff152eb2dfffb9c) -- 0
k = x, v = ListNode(node/dc7b6be9cf9f17f40ea73447470f8ef9) -- v.value() = [0, 1, 2, 3, 4]
k = y, v = Node(node/a57049d7237acea0c1914c4d80d7dfa4) -- v.value() = 'z'

Calling functions#

Let’s define a function that takes \(n\ge 2\) arguments, sums up all but the last, and then divides by the last.

@funkify
def my_funk(dag):
    numer = sum(dag.argv[1:-1].value())
    return numer / dag.argv[-1].value()

dag = dml.new('funk-dag', 'dag to build a funk')
dag.my_funk = my_funk
with redirect_stdout(None), redirect_stderr(None):
    result = dag.my_funk(*range(5))
print(f"{result = } -- {result.value() = }")
result = Node(node/8a6aa0c8ab4c821b2afb2d6321832327) -- result.value() = 1.5

And we can save this as our dag’s “result” and then import it later without having to consider the infrastructure it’s running on, or anything else.

dag.result = dag.my_funk

try:
    with dml.new("new-dag", "my cool new dag that will unforunately fail") as dag:
        dag.funk = dml.load("funk-dag").result
        with redirect_stdout(None), redirect_stderr(None):
            dag.funk(1, 2, 3, 4, 0, name="bad-guy")  # note we're dividing by zero!
except Error as e:
    print("ruh roh! We divided by zero...")
    print(e)
ruh roh! We divided by zero...
Traceback (most recent call last):
  File "/opt/hostedtoolcache/Python/3.13.5/x64/lib/python3.13/site-packages/dml_util/funk.py", line 207, in aws_fndag
    yield dag
  File "/tmp/dml.41eg9jt1/script.py", line 13, in <module>
    res = my_funk(dag)
  File "/tmp/dml.41eg9jt1/script.py", line 6, in my_funk
    return numer / dag.argv[-1].value()
           ~~~~~~^~~~~~~~~~~~~~~~~~~~~~
ZeroDivisionError: division by zero

And because we used the dag as a context manager, that error is stored as the dag’s result, and we can query it later.

print(dml("dag", "describe", "new-dag")["error"])
Traceback (most recent call last):
  File "/opt/hostedtoolcache/Python/3.13.5/x64/lib/python3.13/site-packages/dml_util/funk.py", line 207, in aws_fndag
    yield dag
  File "/tmp/dml.41eg9jt1/script.py", line 13, in <module>
    res = my_funk(dag)
  File "/tmp/dml.41eg9jt1/script.py", line 6, in my_funk
    return numer / dag.argv[-1].value()
           ~~~~~~^~~~~~~~~~~~~~~~~~~~~~
ZeroDivisionError: division by zero

Or in another dag:

dag = dml.new("newest")
try:
    dml.load("new-dag")["bad-guy"].value()
except Error as e:
    print("reraised?", "ZeroDivisionError" in str(e))
reraised? True

This funkify decorator can be used to send your code into the cloud, or have a function run in a different environment, or do whatever you want.

For more, read the examples on the daggerml and dml-util repos.