How validation works#
Func diagnostics#
Model-function names in the pipeline are checked against the schema itself
(the single source of truth), not a separate list, so every stage is covered.
When a func is wrong, validate_config now:
collapses the schema’s downstream argument explosion (one bad func could otherwise surface hundreds of misleading “X is a required property” errors from the branches the validator tried) down to a single diagnostic;
distinguishes a typo from a valid model in the wrong stage, and points the latter at the stage it belongs to;
offers a “did you mean” suggestion (Levenshtein over the leaf name) and a short list of valid models for that stage, carried as structured
suggestionandvalid_optionsfields on theDiagnostic.
This is the Python port of the browser GUI’s modelDiscriminator.js, so both
consumers reason about func values the same way.
What validation covers#
The core validates what the schema encodes, plus a short list of run rules for what Pyxel refuses beyond it; both are shared with the GUI, so the two reach the same verdict. The schema already includes a fair amount of value checking, not just structure:
types and required arguments per model;
enums — a bad choice string is rejected against the allowed set (e.g.
single/exponential/binomial/sbx);numeric bounds (
minimum/maximum/exclusiveMinimum) and array-length bounds (minItems/maxItems) wherever the schema declares them;func discriminators, with the friendly typo / wrong-stage diagnostics described above;
the root shape: which running mode and which detector the config uses. The schema’s root is an anyOf over twelve (mode, detector) branches, and a bare validator answers a missing or misspelled detector with the same “required property” line four times over. The core reads the branch structure out of the schema (the Python port of the browser app’s
configShape.js) and reports one sentence instead: which section is missing, which two conflict, or thatccd_detecorshould beccd_detector.run rules (
run_rules.py, mirrored by the browser’srunRules.js): what Pyxel 3.0.2 refuses at load or stops on at run time although the schema accepts it. A readout with bothtimesandtimes_from_file, a calibration with bothweightsandweights_from_file, an observation with no parameters,simple_measurementwith neither againnor a detectorcharge_to_volt,simple_amplifierwithoutpre_amplification, andsimple_adcwithout its ADC settings, a whole number written as a decimal where Pyxel refuses one (a detector’srow: 128.0), a number in exponent notation that Pyxel’s YAML reader takes as text (1e-6), and a model whose own code refuses the config’s detector class (cdmon a CMOS detector). They report under thepyxel-rulecategory, and CI runs a fixture for each one through the real Pyxel so a rule cannot drift from what Pyxel does.duplicate keys: Pyxel keeps the last of two equal keys in a mapping without a word, so the core refuses a key written twice and names both lines.
focal plane arrays (
fpa.py): the schema has nofocal_plane_arraybranch, so a plane is checked against what Pyxel’s focal-plane loader requires (one running mode, exposure only; the physical geometry and one scale; tile IDs 1..N; positions; a plane-wide scene; nothing beside the plane), and each tile is validated with the schema and the run rules as the flat config Pyxel builds it from: the plane’s running mode, the tile’s detector and the tile’s pipeline. Findings name the tile and keep their path inside the plane, so the line in a SARIF or Code Quality report is the line in the file.
Reports are grouped by path: one field’s alternatives collapse into one line
naming every accepted type, at most eight paths are listed, and a closing line
counts what was left out so nothing is dropped silently. A pipeline step whose
func is known is checked against that model’s own definition, so a mistyped
argument reports as itself and not as the required arguments of every other
model in the stage.
Deliberately out of scope#
Beyond the run rules above, checks that live above the schema are not attempted here, so there is never a second rulebook that could drift from the GUI or from Pyxel:
Other super-schema constraints. Cross-field consistency (e.g. one argument that must stay
<=another), physical plausibility, and any other bound Pyxel enforces in code but that isn’t written into the JSON schema. These pass the schema check. The--deepswitch exists for exactly this gap: it hands the config topyxel.load(), which applies every rule Pyxel has at load time.