Decorators

Learn how to use OpenHTF decorators to enhance your test phases with measurements, hardware plugins, and execution configuration.

Last updated · Verified with OpenHTF 1.6.1

Learn how to use OpenHTF decorators to enhance your test phases.

Decorators stack on a phase function. PhaseOptions configures execution, measures declares the measurements the phase records, and plug injects the hardware plug the phase receives as an argument.

  • @htf.PhaseOptions(timeout_s=30)execution: 30 s timeout
  • @htf.measures(Measurement("voltage"))
  • @htf.plug(dmm=MultimeterPlug)
  • def phase_voltage(test, dmm)

Python decorators modify function behavior without changing their code. In OpenHTF, decorators add functionality to test phases like declaring measurements, injecting hardware plugins, and configuring execution. They're applied using the @ symbol before function definitions:

@decorator_function
def my_function():
    pass

Key decorators

OpenHTF ships these decorators:

1. Declaring measurements

The @htf.measures decorator is used to declare what measurements a phase will capture and validate:

@htf.measures(
    htf.Measurement("temperature")
    .in_range(0, 100)
    .with_units(units.DEGREE_CELSIUS)
    .with_precision(1)
)
def measure_temperature(test):
    test.measurements.temperature = 25.5

2. Hardware plugin injection

The @htf.plug decorator injects hardware or software plugins into your test phases:

@htf.plug(multimeter=MultimeterPlug)
def test_voltage(test, multimeter):
    voltage = multimeter.measure_voltage()
    test.measurements.voltage = voltage

3. Phase configuration

The @htf.PhaseOptions decorator configures how a phase should execute:

@htf.PhaseOptions(timeout_s=10, repeat_limit=3)
def retry_phase(test):
    # Phase will timeout after 10 seconds and can retry up to 3 times
    return htf.PhaseResult.CONTINUE

Combining

You can stack decorators on one phase:

@htf.measures(
    htf.Measurement("firmware_version").equals("1.2.4"),
    htf.Measurement("connection_status").equals(True)
)
@htf.plug(device=DeviceConnection)
@htf.PhaseOptions(timeout_s=30)
def verify_device_connection(test, device):
    test.measurements.connection_status = device.is_connected()
    test.measurements.firmware_version = device.get_firmware_version()

Enhanced example

Let's enhance our first test to demonstrate decorator usage:

main.py
import openhtf as htf

@htf.measures(
    htf.Measurement("greeting_message").with_validator(
        lambda msg: "Hello" in msg
    )
)
@htf.PhaseOptions(timeout_s=5)
def hello_world_with_measurement(test):
    message = "Hello world!"
    print(message)
    test.measurements.greeting_message = message

def main():
    test = htf.Test(hello_world_with_measurement)
    test.execute(lambda: "SN1234")

if __name__ == '__main__':
    main()

This version adds:

  • Measurement validation using @htf.measures
  • Phase timeout using @htf.PhaseOptions
  • Custom validation with lambda functions

Dynamic configuration

Python decorators are callables, which means you can apply them at runtime instead of with the @ syntax: htf.measures(m)(phase) returns the decorated phase. This is how you build phases whose limits come from a config file, a product database or operator input.

main.py
import openhtf as htf

def phase_resistance_test(test):
    test.measurements.resistance = 10.5

def main():
    limits = {"min": 5, "max": 17}   # e.g. loaded from YAML per part number

    measurement = htf.Measurement("resistance").in_range(limits["min"], limits["max"])
    configured_phase = htf.PhaseOptions(timeout_s=10)(
        htf.measures(measurement)(phase_resistance_test)
    )

    test = htf.Test(configured_phase)
    test.execute(lambda: "SN1234")

if __name__ == "__main__":
    main()

The same works for @htf.plug. A worked measurement example is under Callable decorators.

Other decorators

Beyond the three above, OpenHTF exposes:

@htf.diagnose(*diagnosers)

Attach phase diagnosers that classify the phase's results.

@htf.monitors(name, func, units=, poll_interval_ms=)

Sample a value in a background thread while the phase runs — see Monitors.

@CONF.save_and_restore(**overrides)

Temporarily override configuration values for one phase.

@CONF.inject_positional_args

Fill a plug's __init__ arguments from configuration keys of the same name.

phase.with_args(**kwargs) / phase.with_plugs(**plugs)

Not decorators but the same idea: return a copy of a phase with template arguments or plug placeholders filled in. Used in the with_plugs example.

On this page

Run it on your stations

$ tofupilot run .
✓ operator UI ready
$ git push
✓ stations updated
✓ runs uploaded

Same OpenHTF script, no code changes.

How it works