require_schema(config, schema) is a generic recursive declarative
checker (nested-dict schema, leaves name required type(s), each
intermediate object node gets its own dict-type check so a wrong-typed
section is named directly instead of surfacing as a confusing "missing
child" error). validate_temp_ctrl() is now just require_schema(config,
TEMPCTRL_SCHEMA) against a declarative schema dict, replacing the
hand-rolled nested loops.
utils/sud_validate.py's static structural check ('steps' must be a
list) now goes through the same require_schema()/SUD_SCHEMA - one
shared mechanism for both server config.json and sud.json documents.
The genuinely dynamic per-step check (ramp.rate required only when a
step sets its own temperature) stays hand-written, since it depends on
the document's content, not just its shape.
Added TestRequireSchema, exercising the generic engine directly
against a schema unrelated to TempCtrl.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4oEE99rkKVU8L8tkzXWdG
46 lines
2.0 KiB
Python
46 lines
2.0 KiB
Python
from utils.config_validate import ConfigError, require, require_schema
|
|
from components.sud import Sud
|
|
|
|
# The only part of a sud.json document's shape that's fixed regardless of
|
|
# content - same declarative mechanism validate_temp_ctrl() uses for
|
|
# config.json's TempCtrl section (see config_validate.py's require_schema()).
|
|
# Everything else about a schedule (how many steps, which fields each one
|
|
# overrides vs. inherits from default.step, whether a step needs ramp.rate
|
|
# at all) depends on the document's own content, not just its shape, so it
|
|
# can't be expressed as a static schema - handled by the step loop below
|
|
# instead, still built on the same require()/ConfigError primitives.
|
|
SUD_SCHEMA = {
|
|
'steps': list,
|
|
}
|
|
|
|
|
|
def validate_sud(data):
|
|
"""Fail-fast structural check for a sud.json document - same rationale
|
|
as config_validate.py's validate_temp_ctrl(): a malformed field here
|
|
doesn't surface until deep inside a run, or worse, during
|
|
SudForecastEstimator's very first Load-time simulation (tasks/sud.py,
|
|
components/sud_forecast.py and scripts/demos/sud/demo_sud.py all read
|
|
ramp['rate'] directly, no .get() fallback, the moment any step actually
|
|
sets its own 'temperature') - as a bare KeyError with no indication of
|
|
which schedule step or field is wrong.
|
|
|
|
Reuses Sud._parse_data() itself (rather than re-implementing default-
|
|
merging/step-building) so what's checked here can never drift from what
|
|
actually gets parsed at runtime."""
|
|
require_schema(data, SUD_SCHEMA)
|
|
if not data['steps']:
|
|
raise ConfigError("'steps' must be a non-empty list")
|
|
|
|
try:
|
|
schedule = Sud._parse_data(data)[2]
|
|
except (KeyError, TypeError, AttributeError) as e:
|
|
raise ConfigError("malformed sud document: {}".format(e))
|
|
|
|
for step in schedule:
|
|
if step['temperature'] is None:
|
|
continue
|
|
try:
|
|
require(step, 'ramp.rate', type=(int, float))
|
|
except ConfigError as e:
|
|
raise ConfigError("step {} ({}): {}".format(step['number'], step.get('descr'), e))
|