A schema and validator for YAML with comprehensive data type validation and constraint support.
Error handling and testing utilities for comprehensive validation workflows and error reporting. Yamale provides specialized exception classes and testing utilities for robust error handling in validation scenarios.
Main exception class raised when YAML validation fails, containing detailed error information for all failed validations.
class YamaleError(ValueError):
"""
Exception raised when YAML validation fails.
Inherits from ValueError for standard exception handling.
"""
def __init__(self, results):
"""
Initialize YamaleError with validation results.
Parameters:
- results (list): List of ValidationResult objects containing errors
Attributes:
- message (str): Combined error message from all failed validations
- results (list): List of ValidationResult objects
"""
message: str # Formatted error message string
results: list # List of ValidationResult objects with detailed errorsUsage examples:
import yamale
try:
yamale.validate(schema, data)
print("Validation successful!")
except yamale.YamaleError as e:
# Access the combined error message
print(f"Validation failed: {e}")
print(f"Error message: {e.message}")
# Process individual validation results
print(f"Failed validations: {len([r for r in e.results if not r.isValid()])}")
for result in e.results:
if not result.isValid():
print(f"\nFile: {result.data}")
print(f"Schema: {result.schema}")
for error in result.errors:
print(f" - {error}")
# Handle as standard ValueError
except ValueError as e:
print(f"General validation error: {e}")YamaleError provides structured access to validation failures:
try:
yamale.validate(schema, data)
except yamale.YamaleError as e:
# Get summary statistics
total_results = len(e.results)
failed_results = [r for r in e.results if not r.isValid()]
success_rate = (total_results - len(failed_results)) / total_results * 100
print(f"Validation Summary:")
print(f" Total files: {total_results}")
print(f" Failed: {len(failed_results)}")
print(f" Success rate: {success_rate:.1f}%")
# Group errors by type
error_types = {}
for result in failed_results:
for error in result.errors:
error_type = error.split(':')[0] if ':' in error else 'General'
error_types[error_type] = error_types.get(error_type, 0) + 1
print(f"\nError breakdown:")
for error_type, count in error_types.items():
print(f" {error_type}: {count}")Unittest.TestCase subclass for easy integration of YAML validation into unit testing frameworks.
class YamaleTestCase(TestCase):
"""
TestCase subclass for validating YAML files in unit tests.
Inherits from unittest.TestCase.
"""
schema = None # String path to schema file (required)
yaml = None # String path or list of paths to YAML files (required)
base_dir = None # String path to prepend to all other paths (optional)
def validate(self, validators=None):
"""
Validate configured YAML files against schema.
Parameters:
- validators (dict, optional): Custom validator dictionary
Returns:
bool: True if all validations pass
Raises:
ValueError: If validation fails or configuration is invalid
"""Usage examples:
import os
import yamale
from yamale import YamaleTestCase
class TestUserConfigs(YamaleTestCase):
# Required configuration
schema = 'schemas/user.yaml'
yaml = 'data/users/*.yaml' # Supports glob patterns
base_dir = os.path.dirname(os.path.realpath(__file__))
def runTest(self):
# Validate all matching files
self.assertTrue(self.validate())
class TestMultipleFiles(YamaleTestCase):
schema = 'config-schema.yaml'
yaml = ['config.yaml', 'config-prod.yaml', 'config-dev.yaml']
def runTest(self):
self.assertTrue(self.validate())
class TestWithCustomValidators(YamaleTestCase):
schema = 'custom-schema.yaml'
yaml = 'custom-data.yaml'
def runTest(self):
# Use custom validators
custom_validators = yamale.validators.DefaultValidators.copy()
custom_validators['email'] = EmailValidator
self.assertTrue(self.validate(validators=custom_validators))
# Run tests
if __name__ == '__main__':
import unittest
unittest.main()import unittest
import os
from yamale import YamaleTestCase
class BaseYamaleTest(YamaleTestCase):
"""Base class with common configuration."""
base_dir = os.path.join(os.path.dirname(__file__), 'fixtures')
class TestAPISchemas(BaseYamaleTest):
schema = 'api-schema.yaml'
yaml = 'api-examples/*.yaml'
def runTest(self):
self.assertTrue(self.validate())
class TestConfigSchemas(BaseYamaleTest):
schema = 'config-schema.yaml'
yaml = ['config-*.yaml']
def runTest(self):
self.assertTrue(self.validate())
# Create test suite
def create_yamale_suite():
suite = unittest.TestSuite()
suite.addTest(TestAPISchemas())
suite.addTest(TestConfigSchemas())
return suiteclass TestWithErrorHandling(YamaleTestCase):
schema = 'strict-schema.yaml'
yaml = 'test-data.yaml'
def runTest(self):
try:
result = self.validate()
self.assertTrue(result)
except ValueError as e:
# Log detailed error information for debugging
self.fail(f"Validation failed: {e}")
def test_expected_failure(self):
"""Test that intentionally invalid data fails validation."""
# Temporarily change to invalid data
original_yaml = self.yaml
self.yaml = 'invalid-data.yaml'
with self.assertRaises(ValueError):
self.validate()
# Restore original configuration
self.yaml = original_yamlimport pytest
import yamale
def test_yaml_validation():
"""Test YAML validation using pytest."""
schema = yamale.make_schema('./schema.yaml')
data = yamale.make_data('./data.yaml')
try:
yamale.validate(schema, data)
except yamale.YamaleError as e:
pytest.fail(f"YAML validation failed: {e}")
@pytest.mark.parametrize("data_file", [
"config-dev.yaml",
"config-prod.yaml",
"config-test.yaml"
])
def test_multiple_configs(data_file):
"""Test multiple configuration files."""
schema = yamale.make_schema('./config-schema.yaml')
data = yamale.make_data(f'./configs/{data_file}')
yamale.validate(schema, data) # Will raise on failure
def test_validation_with_custom_message():
"""Test with custom error handling."""
schema = yamale.make_schema('./user-schema.yaml')
data = yamale.make_data('./invalid-user.yaml')
with pytest.raises(yamale.YamaleError) as exc_info:
yamale.validate(schema, data)
# Assert specific error conditions
error_msg = str(exc_info.value)
assert "age" in error_msg
assert "required" in error_msgimport logging
import yamale
def validate_config_file(config_path, schema_path):
"""
Validate configuration file with comprehensive error handling.
"""
logger = logging.getLogger(__name__)
try:
# Create schema
schema = yamale.make_schema(schema_path)
logger.info(f"Loaded schema from {schema_path}")
# Load data
data = yamale.make_data(config_path)
logger.info(f"Loaded data from {config_path}")
# Validate
yamale.validate(schema, data)
logger.info("Validation successful")
return True
except FileNotFoundError as e:
logger.error(f"File not found: {e}")
return False
except yaml.YAMLError as e:
logger.error(f"YAML parsing error: {e}")
return False
except yamale.YamaleError as e:
logger.error(f"Validation failed: {e}")
# Log detailed errors for debugging
for result in e.results:
if not result.isValid():
logger.debug(f"Failed file: {result.data}")
for error in result.errors:
logger.debug(f" Error: {error}")
return False
except Exception as e:
logger.error(f"Unexpected error: {e}")
return False
# Usage
if validate_config_file('config.yaml', 'schema.yaml'):
print("Configuration is valid")
else:
print("Configuration validation failed")
sys.exit(1)def validate_multiple_files(file_paths, schema_path):
"""
Validate multiple files and aggregate errors.
"""
schema = yamale.make_schema(schema_path)
all_results = []
for file_path in file_paths:
try:
data = yamale.make_data(file_path)
results = yamale.validate(schema, data, _raise_error=False)
all_results.extend(results)
except Exception as e:
# Create error result for files that couldn't be loaded
error_result = yamale.schema.validationresults.Result([str(e)])
error_result.data = file_path
error_result.schema = schema_path
all_results.append(error_result)
# Analyze results
valid_count = sum(1 for r in all_results if r.isValid())
total_count = len(all_results)
print(f"Validation Summary: {valid_count}/{total_count} files valid")
# Report failures
for result in all_results:
if not result.isValid():
print(f"FAILED: {result.data}")
for error in result.errors:
print(f" {error}")
return valid_count == total_countInstall with Tessl CLI
npx tessl i tessl/pypi-yamale