save_spec

save_spec(spec, path, *, defaults=False)

Write a specification back out as YAML - what was set, not what it holds.

from_yaml reads a configuration and PlatypusSpec builds one in code; this is the way back, so the two interfaces are one pipeline rather than one and a half. A worked example can write the YAML of the run it has just done beside the results, and a specification assembled in R or Python can become the file somebody else runs.

It writes the fields that were set. Not the fields the object holds, which on a two-field model is 29 of them - and the difference is not verbosity. A model given weights and no architecture adopts the architecture from the weights sidecar, and that adoption fires on architecture not being in model_fields_set. Dump the defaults into the file and the reloaded specification has all 29 set, so the adoption can never fire again: the file would look like the original and quietly stop doing what it did. R paid for this once already in the other direction (JOURNAL.md §4aa) and dropped its own defaults for the same reason.

defaults=True is the opposite want, which is legitimate and is a second mode rather than the default: freeze this run exactly as it resolved, so it reproduces in five years. The cost is the one above - the file stops adopting anything, by design.

Every discriminator is always written, whatever was set. task is the obvious one - it is what from_yaml dispatches on, and a configuration that does not say what it is asking for means whatever the version reading it decides. The same is true one level down and is easier to miss: DiceLoss(smooth=0.5) sets smooth and not name, because name is a Literal with a default, so a dump of what was set alone writes loss: {smooth: 0.5} - a block with no tag, which comes back as Unable to extract tag using discriminator ‘name’. A field annotated Literal is a tag here, and tags survive.

Parameters

Name Type Description Default
spec PlatypusSpec The specification to write. Either task. required
path str | Path Where to write it. The parent directory must exist. required
defaults bool Write every field at its resolved value, including the ones nobody set. False

Returns

Name Type Description
Path The path written, so a caller can log or return it.

import tempfile, pathlib from pyplatypus.spec import SegmentationSpec, SegmentationData, SegmentationModel spec = SegmentationSpec( … data=SegmentationData( … train_path=“train/”, validation_path=“valid/”, … colormap=[[0, 0, 0], [255, 255, 255]], … ), … models=[SegmentationModel(name=“m”, input_shape=(32, 32))], … ) path = pathlib.Path(tempfile.mkdtemp()) / “spec.yaml” _ = save_spec(spec, path) sorted(yaml.safe_load(path.read_text())[“models”][0]) [‘input_shape’, ‘name’] frozen = save_spec(spec, path.with_name(“frozen.yaml”), defaults=True) len(yaml.safe_load(frozen.read_text())[“models”][0]) 29