Python lib/cli for JSON/YAML schema validation
—
PyKwalify provides a structured exception hierarchy with specific error types for different validation failures, providing detailed error messages and validation paths for debugging and error reporting.
class PyKwalifyException(RuntimeError):
"""Base exception class for all PyKwalify errors."""
def __init__(self, msg=None, error_key=None, retcode=None, path=None):
"""
Initialize PyKwalify exception.
Args:
msg (str, optional): Error message
error_key (str, optional): Unique error identifier
retcode (int, optional): Error return code
path (str, optional): Path where error occurred (default: "/")
"""
@property
def msg(self):
"""Error message string"""
@property
def retcode(self):
"""Error return code integer"""
@property
def retname(self):
"""Error return code name string"""class SchemaError(PyKwalifyException):
"""Raised when schema validation fails."""
class SchemaErrorEntry(object):
"""Individual schema error entry."""
def __init__(self, msg, path, value, **kwargs):
"""
Args:
msg (str): Error message template
path (str): Path where error occurred
value: Value that caused the error
**kwargs: Additional error context
"""
class CoreError(PyKwalifyException):
"""Raised when core processing encounters an error."""
class RuleError(PyKwalifyException):
"""Raised when rule processing encounters an error."""
class NotMappingError(PyKwalifyException):
"""Raised when a value is not a mapping when expected."""
class NotSequenceError(PyKwalifyException):
"""Raised when a value is not a sequence when expected."""
class SchemaConflict(PyKwalifyException):
"""Raised when schema definitions conflict."""
class UnknownError(PyKwalifyException):
"""Raised for unknown or unexpected errors."""retcodes: dict # Mapping of error codes to names
"""
{
0: 'noerror',
1: 'unknownerror',
2: 'schemaerror',
3: 'coreerror',
4: 'ruleerror',
5: 'schemaconflict',
6: 'notmaperror',
7: 'notsequenceerror',
}
"""
retnames: dict # Mapping of error names to codes
"""
{
'noerror': 0,
'unknownerror': 1,
'schemaerror': 2,
'coreerror': 3,
'ruleerror': 4,
'schemaconflict': 5,
'notmaperror': 6,
'notsequenceerror': 7,
}
"""from pykwalify.core import Core
from pykwalify.errors import SchemaError, CoreError
# Invalid data that will fail validation
data = {
"name": 123, # Should be string
"age": "thirty" # Should be integer
}
schema = {
"type": "map",
"mapping": {
"name": {"type": "str", "required": True},
"age": {"type": "int", "required": True}
}
}
try:
c = Core(source_data=data, schema_data=schema)
c.validate(raise_exception=True)
except SchemaError as e:
print(f"Schema validation failed!")
print(f"Error message: {e.msg}")
print(f"Error path: {e.path}")
print(f"Error code: {e.retcode}")
print(f"Error name: {e.retname}")
except CoreError as e:
print(f"Core processing error: {e.msg}")from pykwalify.core import Core
from pykwalify.errors import (
PyKwalifyException, SchemaError, CoreError, RuleError,
NotMappingError, NotSequenceError, SchemaConflict, UnknownError
)
def validate_with_comprehensive_error_handling(data, schema):
try:
c = Core(source_data=data, schema_data=schema)
c.validate(raise_exception=True)
return True, "Validation successful"
except SchemaError as e:
return False, f"Schema validation failed: {e.msg} (Path: {e.path})"
except NotMappingError as e:
return False, f"Expected mapping but got different type: {e.msg} (Path: {e.path})"
except NotSequenceError as e:
return False, f"Expected sequence but got different type: {e.msg} (Path: {e.path})"
except RuleError as e:
return False, f"Rule processing error: {e.msg} (Path: {e.path})"
except SchemaConflict as e:
return False, f"Schema conflict: {e.msg} (Path: {e.path})"
except CoreError as e:
return False, f"Core processing error: {e.msg}"
except UnknownError as e:
return False, f"Unknown error: {e.msg}"
except PyKwalifyException as e:
return False, f"PyKwalify error: {e.msg} (Code: {e.retcode})"
except Exception as e:
return False, f"Unexpected error: {str(e)}"
# Test with various error scenarios
test_cases = [
# Valid data
({"name": "John", "age": 30}, {"type": "map", "mapping": {"name": {"type": "str"}, "age": {"type": "int"}}}),
# Schema error - wrong type
({"name": 123}, {"type": "map", "mapping": {"name": {"type": "str"}}}),
# Not mapping error - expecting dict but got list
([1, 2, 3], {"type": "map", "mapping": {"key": {"type": "str"}}}),
# Not sequence error - expecting list but got dict
({"key": "value"}, {"type": "seq", "sequence": [{"type": "str"}]}),
]
for i, (test_data, test_schema) in enumerate(test_cases):
success, message = validate_with_comprehensive_error_handling(test_data, test_schema)
print(f"Test {i+1}: {'PASS' if success else 'FAIL'} - {message}")from pykwalify.core import Core
from pykwalify.errors import SchemaError
# Complex data with multiple validation errors
data = {
"user": {
"name": "", # Too short
"age": -5, # Below minimum
"email": "invalid-email" # Invalid format
},
"items": [
{"id": "abc", "count": "five"}, # id should be int, count should be int
{"id": 123} # missing required count
]
}
schema = {
"type": "map",
"mapping": {
"user": {
"type": "map",
"mapping": {
"name": {"type": "str", "length": {"min": 1}},
"age": {"type": "int", "range": {"min": 0}},
"email": {"type": "email"}
}
},
"items": {
"type": "seq",
"sequence": [{
"type": "map",
"mapping": {
"id": {"type": "int", "required": True},
"count": {"type": "int", "required": True}
}
}]
}
}
}
c = Core(source_data=data, schema_data=schema)
# Validate without raising exceptions to collect all errors
is_valid = c.validate(raise_exception=False)
if not is_valid:
print("Validation failed with the following errors:")
# Access general error list
if c.errors:
print("\nGeneral errors:")
for error in c.errors:
print(f" - {error}")
# Access validation errors
if c.validation_errors:
print("\nValidation errors:")
for error in c.validation_errors:
print(f" - {error}")
# Access validation error exceptions for detailed info
if c.validation_errors_exceptions:
print("\nDetailed validation errors:")
for exc in c.validation_errors_exceptions:
print(f" - {exc.msg}")
print(f" Path: {exc.path}")
print(f" Code: {exc.retcode} ({exc.retname})")
if hasattr(exc, 'error_key') and exc.error_key:
print(f" Key: {exc.error_key}")from pykwalify.core import Core
from pykwalify.errors import CoreError, SchemaError
import tempfile
import os
def validate_files_with_error_handling(data_content, schema_content):
# Create temporary files
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as data_file:
data_file.write(data_content)
data_file_path = data_file.name
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as schema_file:
schema_file.write(schema_content)
schema_file_path = schema_file.name
try:
c = Core(source_file=data_file_path, schema_files=[schema_file_path])
c.validate(raise_exception=True)
return True, "File validation successful"
except CoreError as e:
if "do not exists on disk" in str(e.msg):
return False, f"File not found: {e.msg}"
elif "Unable to load" in str(e.msg):
return False, f"File loading error: {e.msg}"
else:
return False, f"Core error: {e.msg}"
except SchemaError as e:
return False, f"Schema validation error: {e.msg} at {e.path}"
finally:
# Clean up temporary files
try:
os.unlink(data_file_path)
os.unlink(schema_file_path)
except:
pass
# Test file validation
data_yaml = """
name: John Doe
age: 30
email: john@example.com
"""
schema_yaml = """
type: map
mapping:
name: {type: str, required: true}
age: {type: int, range: {min: 0, max: 120}}
email: {type: email}
"""
success, message = validate_files_with_error_handling(data_yaml, schema_yaml)
print(f"File validation: {'SUCCESS' if success else 'FAILED'} - {message}")from pykwalify.core import Core
from pykwalify.errors import SchemaError, PyKwalifyException
class ValidationResult:
def __init__(self, success=True, errors=None, warnings=None):
self.success = success
self.errors = errors or []
self.warnings = warnings or []
def add_error(self, error_type, message, path=None):
self.errors.append({
'type': error_type,
'message': message,
'path': path
})
self.success = False
def __str__(self):
if self.success:
return "Validation successful"
result = f"Validation failed with {len(self.errors)} error(s):"
for error in self.errors:
path_info = f" at {error['path']}" if error['path'] else ""
result += f"\n - {error['type']}: {error['message']}{path_info}"
return result
def advanced_validate(data, schema, context=None):
"""Enhanced validation with custom error handling and context."""
result = ValidationResult()
context = context or {}
try:
c = Core(source_data=data, schema_data=schema)
# Add context to error messages if validation fails
try:
c.validate(raise_exception=True)
except PyKwalifyException as e:
error_context = context.get('error_context', 'validation')
result.add_error(
error_type=e.__class__.__name__,
message=f"[{error_context}] {e.msg}",
path=getattr(e, 'path', None)
)
# Add suggestions based on error type
if isinstance(e, SchemaError):
if 'type' in str(e.msg).lower():
result.add_error(
'Suggestion',
'Check that data types match schema requirements',
path=getattr(e, 'path', None)
)
except Exception as e:
result.add_error('UnexpectedError', str(e))
return result
# Test advanced validation
test_data = {"name": 123, "age": "thirty"}
test_schema = {
"type": "map",
"mapping": {
"name": {"type": "str"},
"age": {"type": "int"}
}
}
validation_result = advanced_validate(
test_data,
test_schema,
context={'error_context': 'user_profile_validation'}
)
print(validation_result)from pykwalify.core import Core
from pykwalify.errors import SchemaError
def validate_with_fallback(data, primary_schema, fallback_schema=None):
"""Try primary schema, fall back to secondary if validation fails."""
# Try primary schema
try:
c = Core(source_data=data, schema_data=primary_schema)
c.validate(raise_exception=True)
return True, "Primary schema validation successful", None
except SchemaError as primary_error:
if fallback_schema:
try:
c = Core(source_data=data, schema_data=fallback_schema)
c.validate(raise_exception=True)
return True, "Fallback schema validation successful", primary_error
except SchemaError as fallback_error:
return False, "Both schemas failed validation", [primary_error, fallback_error]
else:
return False, "Primary schema validation failed", primary_error
# Example usage
data = {"version": "1.0", "name": "test"}
# Strict schema (v2 format)
strict_schema = {
"type": "map",
"mapping": {
"version": {"type": "str", "enum": ["2.0"]},
"name": {"type": "str"},
"description": {"type": "str", "required": True}
}
}
# Lenient schema (v1 format)
lenient_schema = {
"type": "map",
"mapping": {
"version": {"type": "str"},
"name": {"type": "str", "required": True}
}
}
success, message, errors = validate_with_fallback(data, strict_schema, lenient_schema)
print(f"Result: {message}")
if errors:
print("Original error:", errors.msg if hasattr(errors, 'msg') else str(errors))Install with Tessl CLI
npx tessl i tessl/pypi-pykwalify