From 0cfb09371c10e9755aa660359aa3b20665a5b36d Mon Sep 17 00:00:00 2001 From: Nathan Edwards Date: Tue, 28 Oct 2025 14:39:08 +1100 Subject: [PATCH 1/7] feat: add schema validator --- .pre-commit-config.yaml | 1 + .pre-commit-config.yaml.example | 10 ++ .pre-commit-hooks.yaml | 8 ++ schema_validator/README.md | 80 ++++++++++++++ schema_validator/parameter_schema.json | 57 ++++++++++ schema_validator/setup.py | 20 ++++ schema_validator/validate_schema.py | 145 +++++++++++++++++++++++++ 7 files changed, 321 insertions(+) create mode 100644 .pre-commit-config.yaml.example create mode 100644 .pre-commit-hooks.yaml create mode 100644 schema_validator/README.md create mode 100644 schema_validator/parameter_schema.json create mode 100644 schema_validator/setup.py create mode 100755 schema_validator/validate_schema.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 79dc471..d9cf3c0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,6 +28,7 @@ repos: - id: check-json - id: check-executables-have-shebangs - id: pretty-format-json + args: ['--autofix'] - id: check-merge-conflict - id: check-symlinks - id: check-toml diff --git a/.pre-commit-config.yaml.example b/.pre-commit-config.yaml.example new file mode 100644 index 0000000..2c933d7 --- /dev/null +++ b/.pre-commit-config.yaml.example @@ -0,0 +1,10 @@ +# Example pre-commit configuration for generate_parameter_library projects +# Copy this to .pre-commit-config.yaml in your project root + +repos: + # Validate parameter YAML files against JSON schema + - repo: https://github.com/Greenroom-Robotics/generate_parameter_library + rev: main + hooks: + - id: validate-parameter-schema + files: .*parameters\.yaml$ diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml new file mode 100644 index 0000000..8597002 --- /dev/null +++ b/.pre-commit-hooks.yaml @@ -0,0 +1,8 @@ +- id: validate-parameter-schema + name: Validate ROS2 PickNikRobotics Generate Parameter Library Schema + description: Validates generate_parameter_library YAML files against JSON schema + entry: schema_validator/validate_schema.py + language: python + types: [yaml] + files: .*parameters\.yaml$ + additional_dependencies: [pyyaml, jsonschema] diff --git a/schema_validator/README.md b/schema_validator/README.md new file mode 100644 index 0000000..41faab9 --- /dev/null +++ b/schema_validator/README.md @@ -0,0 +1,80 @@ +# Parameter Schema Validator + +A pre-commit hook to validate `generate_parameter_library` YAML files against the JSON schema. + +## Installation + +### As a pre-commit hook + +Add this to your `.pre-commit-config.yaml`: + +```yaml +repos: + - repo: https://github.com/Greenroom-Robotics/generate_parameter_library + rev: main # Use a specific tag/version in production + hooks: + - id: validate-parameter-schema +``` + +Then install the hook: + +```bash +pre-commit install +``` + +### Standalone usage + +Install dependencies: + +```bash +pip install pyyaml jsonschema +``` + +Run the validator: + +```bash +python schema_validator/validate_schema.py path/to/your/parameters.yaml +``` + +Or with a custom schema: + +```bash +python schema_validator/validate_schema.py --schema path/to/schema.json path/to/parameters.yaml +``` + +## What it validates + +The validator checks: + +1. **YAML syntax**: Ensures the file is valid YAML +2. **Single root element**: The YAML must have exactly one root element (the namespace) +3. **Required fields**: Each parameter must have a `type` field +4. **Valid types**: Type must be one of the supported parameter types +5. **Valid field names**: Only recognized fields are allowed (type, default_value, description, read_only, validation, additional_constraints) +6. **Nested structure**: Validates nested parameter groups and mapped parameters +7. **Validation syntax**: Ensures validation rules follow the correct structure + +## Example output + +Successful validation: +``` +✓ config/parameters.yaml is valid +``` + +Failed validation: +``` +❌ Validation failed for config/parameters.yaml: + Path: my_controller -> pid -> p + Error: 'type' is a required property + Value: {'default_value': 1.0, 'description': 'Proportional gain'} +``` + +## Limitations + +Note that this validator checks the **structure** of the YAML file, but does not perform: + +- Type checking of default values against declared types (use generate_parameter_library for this) +- Custom validator verification +- Deep semantic validation + +For complete validation, always run `generate_parameter_library` during your build process. diff --git a/schema_validator/parameter_schema.json b/schema_validator/parameter_schema.json new file mode 100644 index 0000000..52c9030 --- /dev/null +++ b/schema_validator/parameter_schema.json @@ -0,0 +1,57 @@ +{ + "$defs": { + "node": { + "patternProperties": { + "^(?!type$|default_value$|description$|read_only$|validation$|additional_constraints$)[a-zA-Z_][a-zA-Z0-9_]*$": { + "$ref": "#/$defs/node", + "description": "Nested parameter or parameter group" + }, + "^__map_[a-zA-Z_][a-zA-Z0-9_]*$": { + "$ref": "#/$defs/node", + "description": "Mapped parameter group using array parameter as keys" + } + }, + "properties": { + "additional_constraints": { + "description": "Additional constraints for ParameterDescriptor (not used for validation)", + "type": "string" + }, + "default_value": { + "description": "Default value for the parameter. Type must match the declared type." + }, + "description": { + "description": "Human-readable description shown by 'ros2 param describe'", + "type": "string" + }, + "read_only": { + "default": false, + "description": "If true, parameter can only be set at launch time, not dynamically updated", + "type": "boolean" + }, + "type": { + "description": "Parameter data type. Use _fixed_XX suffix for fixed-size types (e.g., string_fixed_25, double_array_fixed_10)", + "pattern": "^(bool|int|double|string|bool_array|int_array|double_array|string_array|none|string_fixed_[0-9]+|int_array_fixed_[0-9]+|double_array_fixed_[0-9]+|string_array_fixed_[0-9]+)$", + "type": "string" + }, + "validation": { + "description": "Validation functions and their arguments. Use <> suffix for templated validators (e.g., bounds<>, gt<>, one_of<>).", + "type": "object" + } + }, + "type": "object" + } + }, + "$id": "https://raw.githubusercontent.com/Greenroom-Robotics/generate_parameter_library/main/parameter_schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "description": "Schema for YAML parameter definition files used by generate_parameter_library. This schema validates the structure of parameter definitions but does not perform deep type checking - use generate_parameter_library for complete validation.", + "maxProperties": 1, + "minProperties": 1, + "patternProperties": { + "^[a-zA-Z_][a-zA-Z0-9_]*$": { + "$ref": "#/$defs/node" + } + }, + "title": "ROS2 Generate Parameter Library Schema", + "type": "object" +} diff --git a/schema_validator/setup.py b/schema_validator/setup.py new file mode 100644 index 0000000..0a61a09 --- /dev/null +++ b/schema_validator/setup.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from setuptools import setup, find_packages + +setup( + name='generate-parameter-library-schema-validator', + version='0.1.0', + description='Schema validator for generate_parameter_library YAML files', + author='Greenroom Robotics', + py_modules=['validate_schema'], + install_requires=[ + 'pyyaml>=5.0', + 'jsonschema>=3.0', + ], + entry_points={ + 'console_scripts': [ + 'validate-parameter-schema=validate_schema:main', + ], + }, + python_requires='>=3.6', +) diff --git a/schema_validator/validate_schema.py b/schema_validator/validate_schema.py new file mode 100755 index 0000000..3a4a362 --- /dev/null +++ b/schema_validator/validate_schema.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Pre-commit hook to validate generate_parameter_library YAML files against JSON schema. +""" + +import argparse +import json +import sys +from pathlib import Path +from typing import List, Optional + +try: + import yaml + from jsonschema import Draft7Validator + from jsonschema.exceptions import SchemaError +except ImportError as e: + print(f"Error: Required package not installed: {e}", file=sys.stderr) + print('Install with: pip install pyyaml jsonschema', file=sys.stderr) + sys.exit(1) + + +def load_schema(schema_path: Path) -> dict: + """Load the JSON schema from file.""" + try: + with open(schema_path, 'r') as f: + return json.load(f) + except FileNotFoundError: + print(f"Error: Schema file not found: {schema_path}", file=sys.stderr) + sys.exit(1) + except json.JSONDecodeError as e: + print(f"Error: Invalid JSON schema: {e}", file=sys.stderr) + sys.exit(1) + + +def load_yaml(yaml_path: Path) -> Optional[dict]: + """Load YAML file.""" + try: + with open(yaml_path, 'r') as f: + return yaml.safe_load(f) + except yaml.YAMLError as e: + print(f"Error parsing {yaml_path}: {e}", file=sys.stderr) + return None + except FileNotFoundError: + print(f"Error: File not found: {yaml_path}", file=sys.stderr) + return None + + +def validate_yaml_against_schema( + yaml_data: dict, schema: dict, yaml_path: Path +) -> bool: + """Validate YAML data against JSON schema.""" + try: + validator = Draft7Validator(schema) + + # Check if there are validation errors + errors = list(validator.iter_errors(yaml_data)) + + if errors: + print(f"\n❌ Validation failed for {yaml_path}:", file=sys.stderr) + for error in errors: + # Build path to the error + path = ' -> '.join(str(p) for p in error.path) if error.path else 'root' + print(f" Path: {path}", file=sys.stderr) + print(f" Error: {error.message}", file=sys.stderr) + + # Show the failing instance if it's not too large + if error.instance and len(str(error.instance)) < 200: + print(f" Value: {error.instance}", file=sys.stderr) + print('', file=sys.stderr) + return False + + return True + + except SchemaError as e: + print(f"Error: Invalid schema: {e}", file=sys.stderr) + sys.exit(1) + + +def validate_parameter_structure(yaml_data: dict, yaml_path: Path) -> bool: + """ + Additional validation beyond JSON schema: + - Ensure only one root element + - Check type/default_value consistency + """ + if not isinstance(yaml_data, dict): + print(f"Error in {yaml_path}: Root must be a dictionary", file=sys.stderr) + return False + + # Check for single root element + if len(yaml_data) != 1: + print( + f"Error in {yaml_path}: YAML must have exactly one root element (namespace), " + f"found {len(yaml_data)}: {list(yaml_data.keys())}", + file=sys.stderr, + ) + return False + + return True + + +def main(argv: Optional[List[str]] = None) -> int: + """Main entry point for the validator.""" + parser = argparse.ArgumentParser( + description='Validate generate_parameter_library YAML files against JSON schema' + ) + parser.add_argument('files', nargs='+', type=Path, help='YAML files to validate') + parser.add_argument( + '--schema', + type=Path, + default=Path(__file__).parent.parent / 'parameter_schema.json', + help='Path to JSON schema file (default: parameter_schema.json)', + ) + + args = parser.parse_args(argv) + + # Load schema once + schema = load_schema(args.schema) + + all_valid = True + + for yaml_file in args.files: + # Load YAML + yaml_data = load_yaml(yaml_file) + if yaml_data is None: + all_valid = False + continue + + # Validate structure (single root, etc.) + if not validate_parameter_structure(yaml_data, yaml_file): + all_valid = False + continue + + # Validate against schema + if not validate_yaml_against_schema(yaml_data, schema, yaml_file): + all_valid = False + continue + + print(f"✓ {yaml_file} is valid") + + return 0 if all_valid else 1 + + +if __name__ == '__main__': + sys.exit(main()) From 07a58bc50d1c5326bf7ef6430cedad86495acefc Mon Sep 17 00:00:00 2001 From: Nathan Edwards Date: Tue, 28 Oct 2025 14:49:35 +1100 Subject: [PATCH 2/7] feat: add alpha_ui schema based on react json schema form --- schema_validator/README.md | 40 ++++ schema_validator/alpha_ui_schema.json | 257 ++++++++++++++++++++++++++ schema_validator/validate_schema.py | 94 +++++++++- 3 files changed, 390 insertions(+), 1 deletion(-) create mode 100644 schema_validator/alpha_ui_schema.json diff --git a/schema_validator/README.md b/schema_validator/README.md index 41faab9..6243c8c 100644 --- a/schema_validator/README.md +++ b/schema_validator/README.md @@ -42,6 +42,12 @@ Or with a custom schema: python schema_validator/validate_schema.py --schema path/to/schema.json path/to/parameters.yaml ``` +To also validate `additional_constraints` JSON content against the UI schema: + +```bash +python schema_validator/validate_schema.py --ui-schema path/to/parameters.yaml +``` + ## What it validates The validator checks: @@ -53,6 +59,7 @@ The validator checks: 5. **Valid field names**: Only recognized fields are allowed (type, default_value, description, read_only, validation, additional_constraints) 6. **Nested structure**: Validates nested parameter groups and mapped parameters 7. **Validation syntax**: Ensures validation rules follow the correct structure +8. **Additional constraints (optional)**: When `--ui-schema` is provided, validates that the `additional_constraints` field contains valid JSON that conforms to the UI schema ## Example output @@ -69,6 +76,39 @@ Failed validation: Value: {'default_value': 1.0, 'description': 'Proportional gain'} ``` +## Alpha UI Schema for additional_constraints + +The `alpha_ui_schema.json` defines the expected format for JSON content embedded in the `additional_constraints` field. This is useful when `additional_constraints` contains UI metadata for form generation. + +### Format + +The `additional_constraints` field should contain a JSON-encoded string like: + +```yaml +my_parameter: + type: double + default_value: 100.0 + additional_constraints: '{"type": "number", "units": "hertz"}' +``` + +The JSON content can include: +- `type` (required): JSON Schema type (number, string, integer, boolean, array, object) +- `units` (optional): Physical units (seconds, hertz, metersPerSecond, meters, radians, degrees, etc.) +- Any React JSON Schema Form properties: `minimum`, `maximum`, `enum`, `pattern`, `title`, etc. + +### Validation + +To validate `additional_constraints` content: + +```bash +python schema_validator/validate_schema.py --ui-schema path/to/parameters.yaml +``` + +This will: +1. Parse the JSON string in each `additional_constraints` field +2. Validate it conforms to the UI schema structure +3. Report any malformed JSON or schema violations + ## Limitations Note that this validator checks the **structure** of the YAML file, but does not perform: diff --git a/schema_validator/alpha_ui_schema.json b/schema_validator/alpha_ui_schema.json new file mode 100644 index 0000000..5d15dc9 --- /dev/null +++ b/schema_validator/alpha_ui_schema.json @@ -0,0 +1,257 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://raw.githubusercontent.com/Greenroom-Robotics/generate_parameter_library/main/alpha_ui_schema.json", + "title": "Alpha UI Schema for additional_constraints", + "description": "Validates the JSON structure embedded in the additional_constraints string field for UI generation. Compatible with React JSON Schema Form with additional 'units' field for physical units.", + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "The JSON Schema type for the parameter", + "enum": ["number", "string", "integer", "boolean", "array", "object", "null"] + }, + "units": { + "type": "string", + "description": "Physical units for the parameter value. Custom extension for unit conversion support. Based on unitConversion.ts from alpha schema-form.", + "enum": [ + "degrees", + "radians", + "degreesPerSecond", + "radiansPerSecond", + "revolutionsPerMinute", + "degreesPerSecondSquared", + "radiansPerSecondSquared", + "meters", + "kilometers", + "nauticalMiles", + "miles", + "feet", + "metersPerSecond", + "knots", + "kilometersPerHour", + "milesPerHour", + "metersPerSecondSquared", + "celsius", + "fahrenheit", + "kelvin", + "pascal", + "bar", + "psi", + "atmosphere", + "milliseconds", + "seconds", + "minutes", + "hours", + "ratio", + "percent", + "hertz", + "secondsPeriod", + "millisecondsPeriod", + "milliSecondsPeriod" + ] + }, + "title": { + "type": "string", + "description": "A title for the field (react-jsonschema-form)" + }, + "description": { + "type": "string", + "description": "A description for the field (react-jsonschema-form)" + }, + "default": { + "description": "Default value for the field (react-jsonschema-form)" + }, + "enum": { + "type": "array", + "description": "Enumeration of allowed values (react-jsonschema-form)", + "items": {} + }, + "enumNames": { + "type": "array", + "description": "Human-readable labels for enum values (react-jsonschema-form)", + "items": { + "type": "string" + } + }, + "const": { + "description": "Constant value (react-jsonschema-form)" + }, + "min": { + "type": "number", + "description": "Min value for numbers (react-jsonschema-form)" + }, + "max": { + "type": "number", + "description": "Max value for numbers (react-jsonschema-form)" + }, + "minimum": { + "type": "number", + "description": "Minimum value for numbers (react-jsonschema-form)" + }, + "maximum": { + "type": "number", + "description": "Maximum value for numbers (react-jsonschema-form)" + }, + "exclusiveMinimum": { + "type": "number", + "description": "Exclusive minimum value for numbers (react-jsonschema-form)" + }, + "exclusiveMaximum": { + "type": "number", + "description": "Exclusive maximum value for numbers (react-jsonschema-form)" + }, + "multipleOf": { + "type": "number", + "description": "Value must be a multiple of this number (react-jsonschema-form)" + }, + "minLength": { + "type": "integer", + "description": "Minimum string length (react-jsonschema-form)", + "minimum": 0 + }, + "maxLength": { + "type": "integer", + "description": "Maximum string length (react-jsonschema-form)", + "minimum": 0 + }, + "pattern": { + "type": "string", + "description": "Regular expression pattern for string validation (react-jsonschema-form)" + }, + "format": { + "type": "string", + "description": "String format (date, time, email, uri, etc.) (react-jsonschema-form)", + "enum": [ + "date", + "time", + "date-time", + "email", + "hostname", + "ipv4", + "ipv6", + "uri", + "uri-reference", + "uri-template", + "json-pointer", + "relative-json-pointer", + "regex", + "data-url", + "color" + ] + }, + "items": { + "description": "Schema for array items (react-jsonschema-form)", + "oneOf": [ + {"type": "object"}, + { + "type": "array", + "items": {"type": "object"} + } + ] + }, + "minItems": { + "type": "integer", + "description": "Minimum array length (react-jsonschema-form)", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "description": "Maximum array length (react-jsonschema-form)", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean", + "description": "Whether array items must be unique (react-jsonschema-form)" + }, + "properties": { + "type": "object", + "description": "Object property schemas (react-jsonschema-form)", + "additionalProperties": true + }, + "required": { + "type": "array", + "description": "List of required property names (react-jsonschema-form)", + "items": { + "type": "string" + } + }, + "additionalProperties": { + "description": "Schema for additional properties or boolean (react-jsonschema-form)", + "oneOf": [ + {"type": "boolean"}, + {"type": "object"} + ] + }, + "oneOf": { + "type": "array", + "description": "One of these schemas must match (react-jsonschema-form)", + "items": { + "type": "object" + } + }, + "anyOf": { + "type": "array", + "description": "Any of these schemas can match (react-jsonschema-form)", + "items": { + "type": "object" + } + }, + "allOf": { + "type": "array", + "description": "All of these schemas must match (react-jsonschema-form)", + "items": { + "type": "object" + } + }, + "not": { + "type": "object", + "description": "Schema must not match (react-jsonschema-form)" + }, + "dependencies": { + "type": "object", + "description": "Property dependencies (react-jsonschema-form)", + "additionalProperties": true + }, + "readOnly": { + "type": "boolean", + "description": "Field is read-only in the UI (react-jsonschema-form)" + }, + "writeOnly": { + "type": "boolean", + "description": "Field is write-only (not displayed after submission) (react-jsonschema-form)" + } + }, + "required": ["type"], + "additionalProperties": true, + "examples": [ + { + "type": "number", + "units": "hertz", + "title": "Frequency", + "description": "Control loop frequency", + "minimum": 1, + "maximum": 1000 + }, + { + "type": "number", + "units": "metersPerSecond", + "minimum": 0, + "maximum": 10 + }, + { + "type": "string", + "enum": ["standby", "navigation", "take_over_the_world"], + "enumNames": ["Standby Mode", "Navigation Mode", "World Domination Mode"] + }, + { + "type": "integer", + "multipleOf": 5, + "minimum": 0, + "maximum": 100 + }, + { + "type": "string", + "format": "date-time" + } + ] +} diff --git a/schema_validator/validate_schema.py b/schema_validator/validate_schema.py index 3a4a362..b0186d6 100755 --- a/schema_validator/validate_schema.py +++ b/schema_validator/validate_schema.py @@ -77,6 +77,77 @@ def validate_yaml_against_schema( sys.exit(1) +def validate_additional_constraints( + node: dict, path: str, yaml_path: Path, ui_schema: Optional[dict] +) -> bool: + """ + Recursively validate additional_constraints JSON content against UI schema. + Returns True if valid or if ui_schema is None. + """ + if ui_schema is None: + return True + + all_valid = True + + # Check if this node has additional_constraints + if 'additional_constraints' in node: + constraints_str = node['additional_constraints'] + try: + # Parse the JSON string + constraints_data = json.loads(constraints_str) + + # Validate against UI schema + from jsonschema import Draft7Validator + + validator = Draft7Validator(ui_schema) + errors = list(validator.iter_errors(constraints_data)) + + if errors: + print( + f"\n❌ additional_constraints validation failed at {path} in {yaml_path}:", + file=sys.stderr, + ) + for error in errors: + error_path = ( + ' -> '.join(str(p) for p in error.path) + if error.path + else 'root' + ) + print(f" Path: {error_path}", file=sys.stderr) + print(f" Error: {error.message}", file=sys.stderr) + print(f" Constraints value: {constraints_str}", file=sys.stderr) + print('', file=sys.stderr) + all_valid = False + + except json.JSONDecodeError as e: + print( + f"\n❌ additional_constraints is not valid JSON at {path} in {yaml_path}:", + file=sys.stderr, + ) + print(f" Error: {e}", file=sys.stderr) + print(f" Value: {constraints_str}", file=sys.stderr) + print('', file=sys.stderr) + all_valid = False + + # Recursively check nested parameters + for key, value in node.items(): + if isinstance(value, dict) and key not in [ + 'type', + 'default_value', + 'description', + 'read_only', + 'validation', + 'additional_constraints', + ]: + nested_path = f"{path} -> {key}" if path else key + if not validate_additional_constraints( + value, nested_path, yaml_path, ui_schema + ): + all_valid = False + + return all_valid + + def validate_parameter_structure(yaml_data: dict, yaml_path: Path) -> bool: """ Additional validation beyond JSON schema: @@ -108,15 +179,26 @@ def main(argv: Optional[List[str]] = None) -> int: parser.add_argument( '--schema', type=Path, - default=Path(__file__).parent.parent / 'parameter_schema.json', + default=Path(__file__).parent / 'parameter_schema.json', help='Path to JSON schema file (default: parameter_schema.json)', ) + parser.add_argument( + '--ui-schema', + action='store_true', + help='Validate additional_constraints JSON content against alpha_ui_schema.json', + ) args = parser.parse_args(argv) # Load schema once schema = load_schema(args.schema) + # Load UI schema if flag is set + ui_schema = None + if args.ui_schema: + ui_schema_path = Path(__file__).parent / 'alpha_ui_schema.json' + ui_schema = load_schema(ui_schema_path) + all_valid = True for yaml_file in args.files: @@ -136,6 +218,16 @@ def main(argv: Optional[List[str]] = None) -> int: all_valid = False continue + # Validate additional_constraints if UI schema provided + if ui_schema: + # Get the root namespace + root_namespace = list(yaml_data.keys())[0] + if not validate_additional_constraints( + yaml_data[root_namespace], root_namespace, yaml_file, ui_schema + ): + all_valid = False + continue + print(f"✓ {yaml_file} is valid") return 0 if all_valid else 1 From 8a2add7084108a527a53c27acb9da012bc7b6d26 Mon Sep 17 00:00:00 2001 From: Nathan Edwards Date: Tue, 28 Oct 2025 14:57:36 +1100 Subject: [PATCH 3/7] fix: pre-commit hook setup --- .pre-commit-hooks.yaml | 4 +- schema_validator/alpha_ui_schema.json | 396 ++++++++++++++------------ schema_validator/pyproject.toml | 23 ++ schema_validator/setup.py | 20 -- 4 files changed, 236 insertions(+), 207 deletions(-) create mode 100644 schema_validator/pyproject.toml delete mode 100644 schema_validator/setup.py diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 8597002..f1c24fc 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -1,8 +1,8 @@ - id: validate-parameter-schema name: Validate ROS2 PickNikRobotics Generate Parameter Library Schema description: Validates generate_parameter_library YAML files against JSON schema - entry: schema_validator/validate_schema.py + entry: validate_schema.py language: python types: [yaml] files: .*parameters\.yaml$ - additional_dependencies: [pyyaml, jsonschema] + additional_dependencies: ['./schema_validator'] diff --git a/schema_validator/alpha_ui_schema.json b/schema_validator/alpha_ui_schema.json index 5d15dc9..05b920d 100644 --- a/schema_validator/alpha_ui_schema.json +++ b/schema_validator/alpha_ui_schema.json @@ -1,125 +1,109 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://raw.githubusercontent.com/Greenroom-Robotics/generate_parameter_library/main/alpha_ui_schema.json", - "title": "Alpha UI Schema for additional_constraints", + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": true, "description": "Validates the JSON structure embedded in the additional_constraints string field for UI generation. Compatible with React JSON Schema Form with additional 'units' field for physical units.", - "type": "object", - "properties": { - "type": { - "type": "string", - "description": "The JSON Schema type for the parameter", - "enum": ["number", "string", "integer", "boolean", "array", "object", "null"] + "examples": [ + { + "description": "Control loop frequency", + "maximum": 1000, + "minimum": 1, + "title": "Frequency", + "type": "number", + "units": "hertz" }, - "units": { - "type": "string", - "description": "Physical units for the parameter value. Custom extension for unit conversion support. Based on unitConversion.ts from alpha schema-form.", + { + "maximum": 10, + "minimum": 0, + "type": "number", + "units": "metersPerSecond" + }, + { "enum": [ - "degrees", - "radians", - "degreesPerSecond", - "radiansPerSecond", - "revolutionsPerMinute", - "degreesPerSecondSquared", - "radiansPerSecondSquared", - "meters", - "kilometers", - "nauticalMiles", - "miles", - "feet", - "metersPerSecond", - "knots", - "kilometersPerHour", - "milesPerHour", - "metersPerSecondSquared", - "celsius", - "fahrenheit", - "kelvin", - "pascal", - "bar", - "psi", - "atmosphere", - "milliseconds", - "seconds", - "minutes", - "hours", - "ratio", - "percent", - "hertz", - "secondsPeriod", - "millisecondsPeriod", - "milliSecondsPeriod" + "standby", + "navigation", + "take_over_the_world" + ], + "enumNames": [ + "Standby Mode", + "Navigation Mode", + "World Domination Mode" + ], + "type": "string" + }, + { + "maximum": 100, + "minimum": 0, + "multipleOf": 5, + "type": "integer" + }, + { + "format": "date-time", + "type": "string" + } + ], + "properties": { + "additionalProperties": { + "description": "Schema for additional properties or boolean (react-jsonschema-form)", + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "object" + } ] }, - "title": { - "type": "string", - "description": "A title for the field (react-jsonschema-form)" + "allOf": { + "description": "All of these schemas must match (react-jsonschema-form)", + "items": { + "type": "object" + }, + "type": "array" }, - "description": { - "type": "string", - "description": "A description for the field (react-jsonschema-form)" + "anyOf": { + "description": "Any of these schemas can match (react-jsonschema-form)", + "items": { + "type": "object" + }, + "type": "array" + }, + "const": { + "description": "Constant value (react-jsonschema-form)" }, "default": { "description": "Default value for the field (react-jsonschema-form)" }, + "dependencies": { + "additionalProperties": true, + "description": "Property dependencies (react-jsonschema-form)", + "type": "object" + }, + "description": { + "description": "A description for the field (react-jsonschema-form)", + "type": "string" + }, "enum": { - "type": "array", "description": "Enumeration of allowed values (react-jsonschema-form)", - "items": {} + "items": {}, + "type": "array" }, "enumNames": { - "type": "array", "description": "Human-readable labels for enum values (react-jsonschema-form)", "items": { "type": "string" - } - }, - "const": { - "description": "Constant value (react-jsonschema-form)" - }, - "min": { - "type": "number", - "description": "Min value for numbers (react-jsonschema-form)" - }, - "max": { - "type": "number", - "description": "Max value for numbers (react-jsonschema-form)" - }, - "minimum": { - "type": "number", - "description": "Minimum value for numbers (react-jsonschema-form)" - }, - "maximum": { - "type": "number", - "description": "Maximum value for numbers (react-jsonschema-form)" - }, - "exclusiveMinimum": { - "type": "number", - "description": "Exclusive minimum value for numbers (react-jsonschema-form)" + }, + "type": "array" }, "exclusiveMaximum": { - "type": "number", - "description": "Exclusive maximum value for numbers (react-jsonschema-form)" - }, - "multipleOf": { - "type": "number", - "description": "Value must be a multiple of this number (react-jsonschema-form)" - }, - "minLength": { - "type": "integer", - "description": "Minimum string length (react-jsonschema-form)", - "minimum": 0 + "description": "Exclusive maximum value for numbers (react-jsonschema-form)", + "type": "number" }, - "maxLength": { - "type": "integer", - "description": "Maximum string length (react-jsonschema-form)", - "minimum": 0 - }, - "pattern": { - "type": "string", - "description": "Regular expression pattern for string validation (react-jsonschema-form)" + "exclusiveMinimum": { + "description": "Exclusive minimum value for numbers (react-jsonschema-form)", + "type": "number" }, "format": { - "type": "string", "description": "String format (date, time, email, uri, etc.) (react-jsonschema-form)", "enum": [ "date", @@ -137,121 +121,163 @@ "regex", "data-url", "color" - ] + ], + "type": "string" }, "items": { "description": "Schema for array items (react-jsonschema-form)", "oneOf": [ - {"type": "object"}, { - "type": "array", - "items": {"type": "object"} + "type": "object" + }, + { + "items": { + "type": "object" + }, + "type": "array" } ] }, - "minItems": { - "type": "integer", - "description": "Minimum array length (react-jsonschema-form)", - "minimum": 0 + "max": { + "description": "Max value for numbers (react-jsonschema-form)", + "type": "number" }, "maxItems": { - "type": "integer", "description": "Maximum array length (react-jsonschema-form)", - "minimum": 0 + "minimum": 0, + "type": "integer" }, - "uniqueItems": { - "type": "boolean", - "description": "Whether array items must be unique (react-jsonschema-form)" + "maxLength": { + "description": "Maximum string length (react-jsonschema-form)", + "minimum": 0, + "type": "integer" }, - "properties": { - "type": "object", - "description": "Object property schemas (react-jsonschema-form)", - "additionalProperties": true + "maximum": { + "description": "Maximum value for numbers (react-jsonschema-form)", + "type": "number" }, - "required": { - "type": "array", - "description": "List of required property names (react-jsonschema-form)", - "items": { - "type": "string" - } + "min": { + "description": "Min value for numbers (react-jsonschema-form)", + "type": "number" }, - "additionalProperties": { - "description": "Schema for additional properties or boolean (react-jsonschema-form)", - "oneOf": [ - {"type": "boolean"}, - {"type": "object"} - ] + "minItems": { + "description": "Minimum array length (react-jsonschema-form)", + "minimum": 0, + "type": "integer" }, - "oneOf": { - "type": "array", - "description": "One of these schemas must match (react-jsonschema-form)", - "items": { - "type": "object" - } + "minLength": { + "description": "Minimum string length (react-jsonschema-form)", + "minimum": 0, + "type": "integer" }, - "anyOf": { - "type": "array", - "description": "Any of these schemas can match (react-jsonschema-form)", - "items": { - "type": "object" - } + "minimum": { + "description": "Minimum value for numbers (react-jsonschema-form)", + "type": "number" }, - "allOf": { - "type": "array", - "description": "All of these schemas must match (react-jsonschema-form)", + "multipleOf": { + "description": "Value must be a multiple of this number (react-jsonschema-form)", + "type": "number" + }, + "not": { + "description": "Schema must not match (react-jsonschema-form)", + "type": "object" + }, + "oneOf": { + "description": "One of these schemas must match (react-jsonschema-form)", "items": { "type": "object" - } + }, + "type": "array" }, - "not": { - "type": "object", - "description": "Schema must not match (react-jsonschema-form)" + "pattern": { + "description": "Regular expression pattern for string validation (react-jsonschema-form)", + "type": "string" }, - "dependencies": { - "type": "object", - "description": "Property dependencies (react-jsonschema-form)", - "additionalProperties": true + "properties": { + "additionalProperties": true, + "description": "Object property schemas (react-jsonschema-form)", + "type": "object" }, "readOnly": { - "type": "boolean", - "description": "Field is read-only in the UI (react-jsonschema-form)" + "description": "Field is read-only in the UI (react-jsonschema-form)", + "type": "boolean" }, - "writeOnly": { - "type": "boolean", - "description": "Field is write-only (not displayed after submission) (react-jsonschema-form)" - } - }, - "required": ["type"], - "additionalProperties": true, - "examples": [ - { - "type": "number", - "units": "hertz", - "title": "Frequency", - "description": "Control loop frequency", - "minimum": 1, - "maximum": 1000 + "required": { + "description": "List of required property names (react-jsonschema-form)", + "items": { + "type": "string" + }, + "type": "array" }, - { - "type": "number", - "units": "metersPerSecond", - "minimum": 0, - "maximum": 10 + "title": { + "description": "A title for the field (react-jsonschema-form)", + "type": "string" }, - { - "type": "string", - "enum": ["standby", "navigation", "take_over_the_world"], - "enumNames": ["Standby Mode", "Navigation Mode", "World Domination Mode"] + "type": { + "description": "The JSON Schema type for the parameter", + "enum": [ + "number", + "string", + "integer", + "boolean", + "array", + "object", + "null" + ], + "type": "string" }, - { - "type": "integer", - "multipleOf": 5, - "minimum": 0, - "maximum": 100 + "uniqueItems": { + "description": "Whether array items must be unique (react-jsonschema-form)", + "type": "boolean" }, - { - "type": "string", - "format": "date-time" + "units": { + "description": "Physical units for the parameter value. Custom extension for unit conversion support. Based on unitConversion.ts from alpha schema-form.", + "enum": [ + "degrees", + "radians", + "degreesPerSecond", + "radiansPerSecond", + "revolutionsPerMinute", + "degreesPerSecondSquared", + "radiansPerSecondSquared", + "meters", + "kilometers", + "nauticalMiles", + "miles", + "feet", + "metersPerSecond", + "knots", + "kilometersPerHour", + "milesPerHour", + "metersPerSecondSquared", + "celsius", + "fahrenheit", + "kelvin", + "pascal", + "bar", + "psi", + "atmosphere", + "milliseconds", + "seconds", + "minutes", + "hours", + "ratio", + "percent", + "hertz", + "secondsPeriod", + "millisecondsPeriod", + "milliSecondsPeriod" + ], + "type": "string" + }, + "writeOnly": { + "description": "Field is write-only (not displayed after submission) (react-jsonschema-form)", + "type": "boolean" } - ] + }, + "required": [ + "type" + ], + "title": "Alpha UI Schema for additional_constraints", + "type": "object" } diff --git a/schema_validator/pyproject.toml b/schema_validator/pyproject.toml new file mode 100644 index 0000000..99bc732 --- /dev/null +++ b/schema_validator/pyproject.toml @@ -0,0 +1,23 @@ +[build-system] +requires = ["setuptools>=45", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "generate-parameter-library-schema-validator" +version = "0.1.0" +description = "Schema validator for generate_parameter_library YAML files" +authors = [ + {name = "Greenroom Robotics"} +] +readme = "README.md" +requires-python = ">=3.6" +dependencies = [ + "pyyaml>=5.0", + "jsonschema>=3.0", +] + +[project.scripts] +validate-parameter-schema = "validate_schema:main" + +[tool.setuptools] +py-modules = ["validate_schema"] diff --git a/schema_validator/setup.py b/schema_validator/setup.py deleted file mode 100644 index 0a61a09..0000000 --- a/schema_validator/setup.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -from setuptools import setup, find_packages - -setup( - name='generate-parameter-library-schema-validator', - version='0.1.0', - description='Schema validator for generate_parameter_library YAML files', - author='Greenroom Robotics', - py_modules=['validate_schema'], - install_requires=[ - 'pyyaml>=5.0', - 'jsonschema>=3.0', - ], - entry_points={ - 'console_scripts': [ - 'validate-parameter-schema=validate_schema:main', - ], - }, - python_requires='>=3.6', -) From 7cf727bb77f774877dcde0f66fe5caa0e7a0c6aa Mon Sep 17 00:00:00 2001 From: Nathan Edwards Date: Tue, 28 Oct 2025 15:01:40 +1100 Subject: [PATCH 4/7] fix: attempt number 2 --- .pre-commit-hooks.yaml | 3 +-- pyproject.toml | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 pyproject.toml diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index f1c24fc..12aca50 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -1,8 +1,7 @@ - id: validate-parameter-schema name: Validate ROS2 PickNikRobotics Generate Parameter Library Schema description: Validates generate_parameter_library YAML files against JSON schema - entry: validate_schema.py + entry: schema_validator/validate_schema.py language: python types: [yaml] files: .*parameters\.yaml$ - additional_dependencies: ['./schema_validator'] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..43e76af --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[build-system] +requires = ["setuptools>=45", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "generate-parameter-library-hooks" +version = "0.1.0" +description = "Pre-commit hooks for generate_parameter_library" +authors = [ + {name = "Greenroom Robotics"} +] +requires-python = ">=3.6" +dependencies = [ + "pyyaml>=5.0", + "jsonschema>=3.0", +] + +[tool.setuptools] +packages = [] From fd4dcb20307d01bc4e91fbb2c697768fe8351861 Mon Sep 17 00:00:00 2001 From: Nathan Edwards Date: Tue, 28 Oct 2025 15:03:16 +1100 Subject: [PATCH 5/7] fix: path problems --- .pre-commit-hooks.yaml | 3 ++- schema_validator/__main__.py | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 schema_validator/__main__.py diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 12aca50..9009b01 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -1,7 +1,8 @@ - id: validate-parameter-schema name: Validate ROS2 PickNikRobotics Generate Parameter Library Schema description: Validates generate_parameter_library YAML files against JSON schema - entry: schema_validator/validate_schema.py + entry: python -m validate_schema language: python types: [yaml] files: .*parameters\.yaml$ + additional_dependencies: ['./schema_validator'] diff --git a/schema_validator/__main__.py b/schema_validator/__main__.py new file mode 100644 index 0000000..7b7205a --- /dev/null +++ b/schema_validator/__main__.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 +"""Allow running validate_schema as a module: python -m validate_schema""" +import sys +from validate_schema import main + +if __name__ == '__main__': + sys.exit(main()) From 6ae45ea3c899825880401c4653a8e2c921a2586a Mon Sep 17 00:00:00 2001 From: Nathan Edwards Date: Tue, 28 Oct 2025 15:04:36 +1100 Subject: [PATCH 6/7] fix: add json files to installed package --- schema_validator/MANIFEST.in | 2 ++ schema_validator/pyproject.toml | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 schema_validator/MANIFEST.in diff --git a/schema_validator/MANIFEST.in b/schema_validator/MANIFEST.in new file mode 100644 index 0000000..2919c22 --- /dev/null +++ b/schema_validator/MANIFEST.in @@ -0,0 +1,2 @@ +include *.json +include README.md diff --git a/schema_validator/pyproject.toml b/schema_validator/pyproject.toml index 99bc732..5c73f1f 100644 --- a/schema_validator/pyproject.toml +++ b/schema_validator/pyproject.toml @@ -20,4 +20,8 @@ dependencies = [ validate-parameter-schema = "validate_schema:main" [tool.setuptools] -py-modules = ["validate_schema"] +py-modules = ["validate_schema", "__main__"] +include-package-data = true + +[tool.setuptools.package-data] +"." = ["*.json"] From 236c21d61a18c6723aa70a1f6798b2cdf790ce26 Mon Sep 17 00:00:00 2001 From: Nathan Edwards Date: Tue, 28 Oct 2025 15:05:23 +1100 Subject: [PATCH 7/7] fix: minor --- schema_validator/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema_validator/pyproject.toml b/schema_validator/pyproject.toml index 5c73f1f..62e52e2 100644 --- a/schema_validator/pyproject.toml +++ b/schema_validator/pyproject.toml @@ -24,4 +24,4 @@ py-modules = ["validate_schema", "__main__"] include-package-data = true [tool.setuptools.package-data] -"." = ["*.json"] +"*" = ["*.json"]