Source code for tiptop_ipy.validation

"""Config validation against the defaults.yaml schema."""
import os.path as p
import yaml


def _load_defaults():
    """Load defaults.yaml and return the parsed dict."""
    with open(p.join(p.dirname(__file__), "defaults.yaml")) as f:
        return yaml.full_load(f.read())


[docs] def validate_config(config, defaults=None): """Validate a config dict against the defaults.yaml schema. Parameters ---------- config : dict[str, dict[str, Any]] The configuration to validate (from ini_parser.parse_ini). defaults : dict, optional The defaults schema. If None, loads from defaults.yaml. Returns ------- issues : list[str] List of warning/error strings. Empty list means valid. """ if defaults is None: defaults = _load_defaults() issues = [] # Check that known required sections exist known_sections = set(defaults.keys()) config_sections = set(config.keys()) # Core sections that must always be present core_sections = { "atmosphere", "telescope", "sources_science", "sources_HO", "sensor_science", "sensor_HO", "DM", "RTC", } # Sections in defaults that have required_keywords for section_name, section_defaults in defaults.items(): if not isinstance(section_defaults, dict): continue required_keys = section_defaults.get("required_keywords", []) if not required_keys: continue if section_name not in config: if section_name in core_sections: issues.append( f"ERROR: Missing required section [{section_name}]" ) continue # Check required keywords within the section (if section is present) for key in required_keys: if key not in config[section_name]: issues.append( f"ERROR: Missing required key '{key}' in [{section_name}]" ) # Warn on unknown sections (might be newer TIPTOP params like sources_Focus) extra_sections = config_sections - known_sections for section_name in sorted(extra_sections): issues.append( f"WARNING: Unknown section [{section_name}] " f"(may be a newer TIPTOP parameter)" ) # Check for unknown keys within known sections for section_name in config_sections & known_sections: section_defaults = defaults[section_name] if not isinstance(section_defaults, dict): continue known_keys = { k for k in section_defaults if k not in ("description", "required_keywords") } config_keys = set(config[section_name].keys()) extra_keys = config_keys - known_keys for key in sorted(extra_keys): issues.append( f"WARNING: Unknown key '{key}' in [{section_name}] " f"(may be a newer TIPTOP parameter)" ) # Type checking for key parameters _check_types(config, issues) return issues
def _check_types(config, issues): """Basic type checks for common parameters.""" # Numeric checks numeric_params = [ ("atmosphere", "Seeing", (int, float)), ("atmosphere", "L0", (int, float)), ("telescope", "TelescopeDiameter", (int, float)), ("telescope", "ObscurationRatio", (int, float)), ("telescope", "Resolution", (int, float)), ] for section, key, expected_types in numeric_params: if section in config and key in config[section]: val = config[section][key] if val is not None and not isinstance(val, expected_types): issues.append( f"ERROR: '{key}' in [{section}] should be numeric, " f"got {type(val).__name__}: {val!r}" ) # List checks list_params = [ ("sources_science", "Wavelength"), ("sources_science", "Zenith"), ("sources_science", "Azimuth"), ("DM", "NumberActuators"), ("DM", "DmPitchs"), ] for section, key in list_params: if section in config and key in config[section]: val = config[section][key] if val is not None and not isinstance(val, list): issues.append( f"WARNING: '{key}' in [{section}] is typically a list, " f"got {type(val).__name__}: {val!r}" )