Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

12. Steps in Python and Rust

Everything from chapter 4 on is about the sequence, and the sequence does not know what language a step is written in. It names the step and the executor. To show that, this chapter writes board/measure_rail twice more — in Python and in Rust — and runs it with a sequence that differs from sequences/first.yseq only in its executors: line.

Writing the step is much the same in all three languages: the function’s signature is the catalog, a returned number is a measurement, and the limit lives in the sequence. What differs is how the executor is started, and that is what this chapter is about.

C#PythonRust
the executor isyour own programa server from the repository, pointed at a folderanvil-exec-wasm, next to your compiled modules
who starts ityouyouAnvil, when a sequence uses it
executor typegrpcgrpcwasm
a step isa method with [Step]a function with @stepa function with #[step]

Python

The Python executor is not in the release archive: it is in the repository you cloned in chapter 2. It needs grpcio, and the gRPC code it imports has to be generated once after cloning. From ~/anvil-book:

$ python3 -m venv .venv
$ .venv/bin/pip install grpcio grpcio-tools
$ cd anvil/executors/python
$ ../../../.venv/bin/python -m grpc_tools.protoc -I ../../crates/modelo --python_out=. --grpc_python_out=. ../../crates/modelo/paso.proto
$ cd ~/anvil-book

If python3 -m venv fails, your distribution may ship it as a separate package — python3-venv on Debian and Ubuntu. That case is not verified in this book.

A step is a decorated function. Create python_steps/board.py:

from anvil_step import step


@step
def measure_rail() -> float:
    """Measures the supply rail, in volts."""
    return 4.98

The file name is the module, so this is board/measure_rail again. Returning a float is a measurement, a bool is pass or fail, and Result.error(...) reports what could not be judged, as in C#. An exception becomes error.

Start the executor in its own terminal and leave it running:

$ .venv/bin/python anvil/executors/python/anvil-exec-python --steps python_steps
module 'board' (/home/you/anvil-book/python_steps/board.py) sha256:831270a42ae7bb6a050f189a2dcf434cf8cc44ca68122871a74e84f3808009e6
python executor listening on 127.0.0.1:9101 — 1 step(s) from 1 module(s): board/measure_rail

It serves every module in the folder given to --steps, on port 9101 by default. You never edit the server; to add steps, add files to the folder and restart it.

name: python

executors:
  - { name: py, type: grpc, host: 127.0.0.1, port: 9101 }

main:
  - name: board/measure_rail
    executor: py
    limit: { type: range, min: 4.75, max: 5.25 }
$ anvil sequences/python.yseq 2>/dev/null
=== python: pass ===
  [pass] board/measure_rail: 

The Python executor’s own README, executors/python/README.md, covers the rest: named outputs, references and options. It is still in Spanish.

Rust

Rust steps are compiled to WebAssembly modules, and the executor is a program that ships in the release archive, anvil-exec-wasm. It serves every .wasm file in the folder it sits in. You need a Rust toolchain and one extra target:

$ rustup target add wasm32-wasip2

Create board-wasm/Cargo.toml — the package name is the module name:

[package]
name = "board"
version = "0.1.0"
edition = "2021"

[dependencies]
anvil-step = { path = "../anvil/executors/rust/anvil-step" }

[lib]
crate-type = ["cdylib"]

and board-wasm/src/lib.rs:

#![allow(unused)]
fn main() {
use anvil_step::{step, Outcome};

/// Measures the supply rail, in volts.
#[step]
fn measure_rail() -> Outcome {
    Outcome::measured(4.98)
}

anvil_step::export!();
}

Build it, and put the module next to a copy of the executor. That folder is the executor, and you can copy it to another machine as it is:

$ cargo build --target wasm32-wasip2 --manifest-path board-wasm/Cargo.toml
$ mkdir wasm-dept
$ cp anvil-v0.5.0-x86_64-linux-musl/anvil-exec-wasm board-wasm/target/wasm32-wasip2/debug/board.wasm wasm-dept/

The first build takes about a minute. Then:

name: wasm

executors:
  - { name: rs, type: wasm, path: ../wasm-dept/anvil-exec-wasm }

main:
  - name: board/measure_rail
    executor: rs
    limit: { type: range, min: 4.75, max: 5.25 }
$ anvil sequences/wasm.yseq 2>/dev/null
=== wasm: pass ===
  [pass] board/measure_rail: 
$ ./wasm-dept/anvil-exec-wasm --list
board  sha256:2087caa515293f3a0cda2b14c78a09d6d9b2bfb98a5e2c63916c872cb1d807d5
    /home/you/anvil-book/wasm-dept/board.wasm
    board/measure_rail()
        Measures the supply rail, in volts.

There was no executor to start: with type: wasm, Anvil starts anvil-exec-wasm itself from path. That path is relative to the sequence file, not to the folder you run anvil from — hence the ../. anvil-exec-wasm --list shows each module with the SHA-256 of the file that serves it.

The step-by-step Rust quick start goes further, with two modules and optional inputs, and executors/rust/README.md covers the SDK.