<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.opensourceecology.org/index.php?action=history&amp;feed=atom&amp;title=Wall_Syntactic_Validator</id>
	<title>Wall Syntactic Validator - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.opensourceecology.org/index.php?action=history&amp;feed=atom&amp;title=Wall_Syntactic_Validator"/>
	<link rel="alternate" type="text/html" href="https://wiki.opensourceecology.org/index.php?title=Wall_Syntactic_Validator&amp;action=history"/>
	<updated>2026-04-28T16:32:59Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.39.13</generator>
	<entry>
		<id>https://wiki.opensourceecology.org/index.php?title=Wall_Syntactic_Validator&amp;diff=321151&amp;oldid=prev</id>
		<title>Marcin: Redirected page to Validate syntax.py</title>
		<link rel="alternate" type="text/html" href="https://wiki.opensourceecology.org/index.php?title=Wall_Syntactic_Validator&amp;diff=321151&amp;oldid=prev"/>
		<updated>2026-03-11T02:25:59Z</updated>

		<summary type="html">&lt;p&gt;Redirected page to &lt;a href=&quot;/wiki/Validate_syntax.py&quot; title=&quot;Validate syntax.py&quot;&gt;Validate syntax.py&lt;/a&gt;&lt;/p&gt;
&lt;a href=&quot;https://wiki.opensourceecology.org/index.php?title=Wall_Syntactic_Validator&amp;amp;diff=321151&amp;amp;oldid=321123&quot;&gt;Show changes&lt;/a&gt;</summary>
		<author><name>Marcin</name></author>
	</entry>
	<entry>
		<id>https://wiki.opensourceecology.org/index.php?title=Wall_Syntactic_Validator&amp;diff=321123&amp;oldid=prev</id>
		<title>Marcin: Created page with &quot;#!/usr/bin/env python3  import sys from pathlib import Path from typing import Any  import yaml   def load_yaml(path: Path) -&gt; Any:     with path.open(&quot;r&quot;, encoding=&quot;utf-8&quot;) as f:         return yaml.safe_load(f)   def is_number(value: Any) -&gt; bool:     return isinstance(value, (int, float)) and not isinstance(value, bool)   def validate_enum(instance_name: str, field_name: str, value: Any, allowed: list[Any], errors: list[str]) -&gt; None:     if value not in allowed:...&quot;</title>
		<link rel="alternate" type="text/html" href="https://wiki.opensourceecology.org/index.php?title=Wall_Syntactic_Validator&amp;diff=321123&amp;oldid=prev"/>
		<updated>2026-03-11T01:02:32Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;#!/usr/bin/env python3  import sys from pathlib import Path from typing import Any  import yaml   def load_yaml(path: Path) -&amp;gt; Any:     with path.open(&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;) as f:         return yaml.safe_load(f)   def is_number(value: Any) -&amp;gt; bool:     return isinstance(value, (int, float)) and not isinstance(value, bool)   def validate_enum(instance_name: str, field_name: str, value: Any, allowed: list[Any], errors: list[str]) -&amp;gt; None:     if value not in allowed:...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;#!/usr/bin/env python3&lt;br /&gt;
&lt;br /&gt;
import sys&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
from typing import Any&lt;br /&gt;
&lt;br /&gt;
import yaml&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def load_yaml(path: Path) -&amp;gt; Any:&lt;br /&gt;
    with path.open(&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;) as f:&lt;br /&gt;
        return yaml.safe_load(f)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def is_number(value: Any) -&amp;gt; bool:&lt;br /&gt;
    return isinstance(value, (int, float)) and not isinstance(value, bool)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def validate_enum(instance_name: str, field_name: str, value: Any, allowed: list[Any], errors: list[str]) -&amp;gt; None:&lt;br /&gt;
    if value not in allowed:&lt;br /&gt;
        errors.append(&lt;br /&gt;
            f&amp;quot;{instance_name}: &amp;#039;{field_name}&amp;#039; = {value!r} is invalid; allowed values are {allowed}&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def validate_numeric_range(&lt;br /&gt;
    instance_name: str,&lt;br /&gt;
    field_name: str,&lt;br /&gt;
    value: Any,&lt;br /&gt;
    min_value: float | None,&lt;br /&gt;
    max_value: float | None,&lt;br /&gt;
    errors: list[str],&lt;br /&gt;
) -&amp;gt; None:&lt;br /&gt;
    if not is_number(value):&lt;br /&gt;
        errors.append(f&amp;quot;{instance_name}: &amp;#039;{field_name}&amp;#039; must be a number, got {type(value).__name__}&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
&lt;br /&gt;
    if min_value is not None and value &amp;lt; min_value:&lt;br /&gt;
        errors.append(&lt;br /&gt;
            f&amp;quot;{instance_name}: &amp;#039;{field_name}&amp;#039; = {value} is below minimum {min_value}&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
    if max_value is not None and value &amp;gt; max_value:&lt;br /&gt;
        errors.append(&lt;br /&gt;
            f&amp;quot;{instance_name}: &amp;#039;{field_name}&amp;#039; = {value} is above maximum {max_value}&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def validate_required_keys(&lt;br /&gt;
    obj: dict[str, Any],&lt;br /&gt;
    required_keys: list[str],&lt;br /&gt;
    context: str,&lt;br /&gt;
    errors: list[str],&lt;br /&gt;
) -&amp;gt; None:&lt;br /&gt;
    for key in required_keys:&lt;br /&gt;
        if key not in obj:&lt;br /&gt;
            errors.append(f&amp;quot;{context}: missing required key &amp;#039;{key}&amp;#039;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def build_parameter_rules(schema: dict[str, Any]) -&amp;gt; dict[str, dict[str, Any]]:&lt;br /&gt;
    if &amp;quot;parameters&amp;quot; not in schema or not isinstance(schema[&amp;quot;parameters&amp;quot;], dict):&lt;br /&gt;
        raise ValueError(&amp;quot;Schema must contain a top-level &amp;#039;parameters&amp;#039; mapping&amp;quot;)&lt;br /&gt;
    return schema[&amp;quot;parameters&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def validate_instance(&lt;br /&gt;
    instance: dict[str, Any],&lt;br /&gt;
    schema: dict[str, Any],&lt;br /&gt;
    parameter_rules: dict[str, dict[str, Any]],&lt;br /&gt;
) -&amp;gt; list[str]:&lt;br /&gt;
    errors: list[str] = []&lt;br /&gt;
&lt;br /&gt;
    validate_required_keys(instance, [&amp;quot;id&amp;quot;, &amp;quot;family&amp;quot;, &amp;quot;parameters&amp;quot;], &amp;quot;instance&amp;quot;, errors)&lt;br /&gt;
    if errors:&lt;br /&gt;
        return errors&lt;br /&gt;
&lt;br /&gt;
    instance_name = instance[&amp;quot;id&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
    if not isinstance(instance[&amp;quot;id&amp;quot;], str):&lt;br /&gt;
        errors.append(f&amp;quot;{instance_name}: &amp;#039;id&amp;#039; must be a string&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    if not isinstance(instance[&amp;quot;family&amp;quot;], str):&lt;br /&gt;
        errors.append(f&amp;quot;{instance_name}: &amp;#039;family&amp;#039; must be a string&amp;quot;)&lt;br /&gt;
    elif instance[&amp;quot;family&amp;quot;] != schema.get(&amp;quot;family&amp;quot;):&lt;br /&gt;
        errors.append(&lt;br /&gt;
            f&amp;quot;{instance_name}: family {instance[&amp;#039;family&amp;#039;]!r} does not match schema family {schema.get(&amp;#039;family&amp;#039;)!r}&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
&lt;br /&gt;
    params = instance[&amp;quot;parameters&amp;quot;]&lt;br /&gt;
    if not isinstance(params, dict):&lt;br /&gt;
        errors.append(f&amp;quot;{instance_name}: &amp;#039;parameters&amp;#039; must be a mapping&amp;quot;)&lt;br /&gt;
        return errors&lt;br /&gt;
&lt;br /&gt;
    # Check required parameters from schema&lt;br /&gt;
    for param_name in parameter_rules.keys():&lt;br /&gt;
        if param_name not in params:&lt;br /&gt;
            errors.append(f&amp;quot;{instance_name}: missing required parameter &amp;#039;{param_name}&amp;#039;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    # Check for unknown parameters&lt;br /&gt;
    for param_name in params.keys():&lt;br /&gt;
        if param_name not in parameter_rules:&lt;br /&gt;
            errors.append(f&amp;quot;{instance_name}: unknown parameter &amp;#039;{param_name}&amp;#039;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    # Validate parameter values against schema&lt;br /&gt;
    for param_name, rule in parameter_rules.items():&lt;br /&gt;
        if param_name not in params:&lt;br /&gt;
            continue&lt;br /&gt;
&lt;br /&gt;
        value = params[param_name]&lt;br /&gt;
        rule_type = rule.get(&amp;quot;type&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
        if rule_type == &amp;quot;float&amp;quot;:&lt;br /&gt;
            validate_numeric_range(&lt;br /&gt;
                instance_name,&lt;br /&gt;
                param_name,&lt;br /&gt;
                value,&lt;br /&gt;
                rule.get(&amp;quot;min&amp;quot;),&lt;br /&gt;
                rule.get(&amp;quot;max&amp;quot;),&lt;br /&gt;
                errors,&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        elif rule_type == &amp;quot;enum&amp;quot;:&lt;br /&gt;
            allowed = rule.get(&amp;quot;allowed&amp;quot;)&lt;br /&gt;
            if not isinstance(allowed, list):&lt;br /&gt;
                errors.append(&lt;br /&gt;
                    f&amp;quot;{instance_name}: schema for &amp;#039;{param_name}&amp;#039; must define &amp;#039;allowed&amp;#039; as a list&amp;quot;&lt;br /&gt;
                )&lt;br /&gt;
            else:&lt;br /&gt;
                validate_enum(instance_name, param_name, value, allowed, errors)&lt;br /&gt;
&lt;br /&gt;
        else:&lt;br /&gt;
            errors.append(&lt;br /&gt;
                f&amp;quot;{instance_name}: unsupported schema type {rule_type!r} for parameter &amp;#039;{param_name}&amp;#039;&amp;quot;&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
    return errors&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def validate_schema_structure(schema: dict[str, Any]) -&amp;gt; list[str]:&lt;br /&gt;
    errors: list[str] = []&lt;br /&gt;
&lt;br /&gt;
    if not isinstance(schema, dict):&lt;br /&gt;
        return [&amp;quot;Schema root must be a mapping&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
    if &amp;quot;family&amp;quot; not in schema:&lt;br /&gt;
        errors.append(&amp;quot;Schema missing top-level &amp;#039;family&amp;#039;&amp;quot;)&lt;br /&gt;
    elif not isinstance(schema[&amp;quot;family&amp;quot;], str):&lt;br /&gt;
        errors.append(&amp;quot;Schema &amp;#039;family&amp;#039; must be a string&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    if &amp;quot;parameters&amp;quot; not in schema:&lt;br /&gt;
        errors.append(&amp;quot;Schema missing top-level &amp;#039;parameters&amp;#039;&amp;quot;)&lt;br /&gt;
    elif not isinstance(schema[&amp;quot;parameters&amp;quot;], dict):&lt;br /&gt;
        errors.append(&amp;quot;Schema &amp;#039;parameters&amp;#039; must be a mapping&amp;quot;)&lt;br /&gt;
    else:&lt;br /&gt;
        for param_name, rule in schema[&amp;quot;parameters&amp;quot;].items():&lt;br /&gt;
            if not isinstance(rule, dict):&lt;br /&gt;
                errors.append(f&amp;quot;Schema parameter &amp;#039;{param_name}&amp;#039; must be a mapping&amp;quot;)&lt;br /&gt;
                continue&lt;br /&gt;
&lt;br /&gt;
            if &amp;quot;type&amp;quot; not in rule:&lt;br /&gt;
                errors.append(f&amp;quot;Schema parameter &amp;#039;{param_name}&amp;#039; missing &amp;#039;type&amp;#039;&amp;quot;)&lt;br /&gt;
                continue&lt;br /&gt;
&lt;br /&gt;
            rule_type = rule[&amp;quot;type&amp;quot;]&lt;br /&gt;
            if rule_type == &amp;quot;float&amp;quot;:&lt;br /&gt;
                if &amp;quot;min&amp;quot; in rule and not is_number(rule[&amp;quot;min&amp;quot;]):&lt;br /&gt;
                    errors.append(f&amp;quot;Schema parameter &amp;#039;{param_name}&amp;#039; has non-numeric &amp;#039;min&amp;#039;&amp;quot;)&lt;br /&gt;
                if &amp;quot;max&amp;quot; in rule and not is_number(rule[&amp;quot;max&amp;quot;]):&lt;br /&gt;
                    errors.append(f&amp;quot;Schema parameter &amp;#039;{param_name}&amp;#039; has non-numeric &amp;#039;max&amp;#039;&amp;quot;)&lt;br /&gt;
            elif rule_type == &amp;quot;enum&amp;quot;:&lt;br /&gt;
                if &amp;quot;allowed&amp;quot; not in rule:&lt;br /&gt;
                    errors.append(f&amp;quot;Schema parameter &amp;#039;{param_name}&amp;#039; missing &amp;#039;allowed&amp;#039;&amp;quot;)&lt;br /&gt;
                elif not isinstance(rule[&amp;quot;allowed&amp;quot;], list):&lt;br /&gt;
                    errors.append(f&amp;quot;Schema parameter &amp;#039;{param_name}&amp;#039; &amp;#039;allowed&amp;#039; must be a list&amp;quot;)&lt;br /&gt;
            else:&lt;br /&gt;
                errors.append(&lt;br /&gt;
                    f&amp;quot;Schema parameter &amp;#039;{param_name}&amp;#039; has unsupported type {rule_type!r}&amp;quot;&lt;br /&gt;
                )&lt;br /&gt;
&lt;br /&gt;
    return errors&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main() -&amp;gt; int:&lt;br /&gt;
    if len(sys.argv) != 3:&lt;br /&gt;
        print(&amp;quot;Usage: python validate_syntax.py &amp;lt;schema.yaml&amp;gt; &amp;lt;instances.yaml&amp;gt;&amp;quot;)&lt;br /&gt;
        return 2&lt;br /&gt;
&lt;br /&gt;
    schema_path = Path(sys.argv[1])&lt;br /&gt;
    instances_path = Path(sys.argv[2])&lt;br /&gt;
&lt;br /&gt;
    if not schema_path.exists():&lt;br /&gt;
        print(f&amp;quot;Error: schema file not found: {schema_path}&amp;quot;)&lt;br /&gt;
        return 2&lt;br /&gt;
&lt;br /&gt;
    if not instances_path.exists():&lt;br /&gt;
        print(f&amp;quot;Error: instances file not found: {instances_path}&amp;quot;)&lt;br /&gt;
        return 2&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        schema = load_yaml(schema_path)&lt;br /&gt;
        instances_doc = load_yaml(instances_path)&lt;br /&gt;
    except yaml.YAMLError as e:&lt;br /&gt;
        print(f&amp;quot;YAML parse error: {e}&amp;quot;)&lt;br /&gt;
        return 2&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        print(f&amp;quot;File load error: {e}&amp;quot;)&lt;br /&gt;
        return 2&lt;br /&gt;
&lt;br /&gt;
    schema_errors = validate_schema_structure(schema)&lt;br /&gt;
    if schema_errors:&lt;br /&gt;
        print(&amp;quot;Schema syntax validation failed:&amp;quot;)&lt;br /&gt;
        for err in schema_errors:&lt;br /&gt;
            print(f&amp;quot;  - {err}&amp;quot;)&lt;br /&gt;
        return 1&lt;br /&gt;
&lt;br /&gt;
    if not isinstance(instances_doc, dict):&lt;br /&gt;
        print(&amp;quot;Instances file root must be a mapping&amp;quot;)&lt;br /&gt;
        return 1&lt;br /&gt;
&lt;br /&gt;
    if &amp;quot;instances&amp;quot; not in instances_doc:&lt;br /&gt;
        print(&amp;quot;Instances file must contain top-level key &amp;#039;instances&amp;#039;&amp;quot;)&lt;br /&gt;
        return 1&lt;br /&gt;
&lt;br /&gt;
    instances = instances_doc[&amp;quot;instances&amp;quot;]&lt;br /&gt;
    if not isinstance(instances, list):&lt;br /&gt;
        print(&amp;quot;Instances file &amp;#039;instances&amp;#039; must be a list&amp;quot;)&lt;br /&gt;
        return 1&lt;br /&gt;
&lt;br /&gt;
    parameter_rules = build_parameter_rules(schema)&lt;br /&gt;
&lt;br /&gt;
    all_errors: list[str] = []&lt;br /&gt;
    seen_ids: set[str] = set()&lt;br /&gt;
&lt;br /&gt;
    for index, instance in enumerate(instances):&lt;br /&gt;
        if not isinstance(instance, dict):&lt;br /&gt;
            all_errors.append(f&amp;quot;instances[{index}] must be a mapping&amp;quot;)&lt;br /&gt;
            continue&lt;br /&gt;
&lt;br /&gt;
        instance_id = instance.get(&amp;quot;id&amp;quot;)&lt;br /&gt;
        if isinstance(instance_id, str):&lt;br /&gt;
            if instance_id in seen_ids:&lt;br /&gt;
                all_errors.append(f&amp;quot;{instance_id}: duplicate instance id&amp;quot;)&lt;br /&gt;
            else:&lt;br /&gt;
                seen_ids.add(instance_id)&lt;br /&gt;
&lt;br /&gt;
        all_errors.extend(validate_instance(instance, schema, parameter_rules))&lt;br /&gt;
&lt;br /&gt;
    if all_errors:&lt;br /&gt;
        print(&amp;quot;Instance syntax validation failed:&amp;quot;)&lt;br /&gt;
        for err in all_errors:&lt;br /&gt;
            print(f&amp;quot;  - {err}&amp;quot;)&lt;br /&gt;
        return 1&lt;br /&gt;
&lt;br /&gt;
    print(f&amp;quot;Syntax validation passed: {len(instances)} instance(s) validated successfully.&amp;quot;)&lt;br /&gt;
    return 0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    raise SystemExit(main())&lt;/div&gt;</summary>
		<author><name>Marcin</name></author>
	</entry>
</feed>