Property and Parameter Access

Every supported way to read and write resource properties and step parameters.

Resource properties and step parameters are stored the same way and are read the same way. ParameterSchema deliberately mirrors PropertySchema, so once you know one you know the other.

Writing them differs, and that difference is the main thing this page exists to record.

The shape

Both follow the same four-level chain:

resource.properties . <group> . <attribute> . value
step.parameters     . <group> . <attribute> . value

The third level is not a bare number or string. It is a small wrapper that carries the value together with its unit and its template definition. The .value at the end is what unwraps it.

Reading

print(plate.properties.dimensions.rows.value)      # a resource property
print(step.parameters.acquisition.exposure.value)  # a step parameter
16
0.25

.values is optional

The wrapper models live under a values attribute, but both properties and parameters forward attribute access through it, so you can leave it out. These are equivalent:

print(plate.properties.dimensions.rows.value)
print(plate.properties.dimensions.values.rows.value)
16
16

Prefer the short form. The long form is only useful when you want the values model itself, for example to call .keys() on it.

.value is not optional

Omitting .value gives you the wrapper, not the value. The wrapper renders as its value when printed, so this mistake survives a print() and fails at the first comparison:

rows = plate.properties.dimensions.rows

print("printed:       ", rows)
print("rows == 16:    ", rows == 16)
print("rows.value:    ", rows.value)
print("rows.value==16:", rows.value == 16)
printed:        16
rows == 16:     False
rows.value:     16
rows.value==16: True
WarningThe wrapper prints like the value but is not equal to it

If a comparison, arithmetic operation, or function call behaves oddly, check whether a .value is missing.

The wrapper carries the unit

This is why the wrapper exists:

print(plate.properties.dimensions.temperature.unit)
print(step.parameters.acquisition.exposure.unit)
degC
s

Dotted access and bracket access

Form Accepts Missing attribute
properties.dimensions.rows slug only AttributeError
properties["dimensions"]["rows"] slug or original name KeyError
properties["dimensions"].get("rows") slug or original name returns None

Dotted access needs the slug, so a group named "Sample Temperature" is properties.sample_temperature. Bracket access accepts either, which makes it the right choice when names are not valid Python identifiers or are built at runtime.

group = plate.properties["dimensions"]

print(group["rows"].value)
print(group.get("rows").value)
print(group.get("not_a_real_attribute"))
16
16
None

Writing properties

Assign the raw value to the attribute, inside a resource builder block. There is no .value on the left-hand side:

with namespace.build_resource(resource_id=draft_id) as builder:
    builder.resource.properties.dimensions.rows = 24

Reading it back confirms the change was saved. Note that a mutable resource is not returned by queries, so it is read through a builder rather than query_maker():

with namespace.build_resource(resource_id=draft_id) as check:
    print(check.get_model(update=True).properties.dimensions.rows.value)
24

Bracket forms work identically and accept original names:

builder.resource.properties["dimensions"]["rows"] = 24
builder.resource.properties["dimensions"].values["rows"] = 24

The builder validates against the template and saves once, on clean exit. If the block raises, nothing is written.

ImportantWrites to an active resource are silently ignored

A resource is frozen once it becomes ACTIVE, and an attempted edit does not raise. It simply has no effect:

print("before:", plate.status)

with namespace.build_resource(resource_id=plate.id) as builder:
    builder.resource.properties.dimensions.rows = 9999

check = namespace.query_maker().resources().filter(name="Plate 001").first()
print("after: ", check.properties.dimensions.rows.value, "(9999 discarded)")
before: LifecycleStatus.ACTIVE
after:  16 (9999 discarded)

Since queries only return active resources, any resource you found by querying is already frozen. To change one, copy it first, as described in Manage Resource Lifecycle.

Writing parameters

Parameters are not written by assigning through a loaded process run. Use get_params() and set_params() inside the build_process_run block:

with namespace.build_process_run(...) as run:
    params = run.get_params("Scan")
    params.acquisition.exposure = 0.25
    run.set_params(params)

The model returned by get_params() holds the same wrappers, so you assign raw values and read them back with .value:

print(step.parameters.acquisition.exposure.value)
0.25
ImportantIn-place parameter edits do not persist

Assigning through a queried process run mutates only the in-memory copy. The statement below succeeds and changes nothing in the database:

scan.steps["Scan"].parameters.acquisition.exposure = 5.0   # no effect

set_params() is the only supported way to record parameter values.

Summary

Properties Parameters
Read resource.properties.<g>.<a>.value step.parameters.<g>.<a>.value
Unit ....<a>.unit ....<a>.unit
.values level optional optional
.value suffix required required
Write assign on builder.resource.properties get_params() / set_params()
Write window while MUTABLE only inside build_process_run

Related: Edit Resource Properties, Build Process Runs, Manage Resource Lifecycle.