CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pykwalify

Python lib/cli for JSON/YAML schema validation

Pending
Overview
Eval results
Files

error-handling.mddocs/

Error Handling

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.

Capabilities

Base Exception Class

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"""

Validation Error Classes

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."""

Error Code Constants

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,
}
"""

Usage Examples

Basic Error Handling

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}")

Comprehensive Error Handling

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}")

Accessing Detailed Error Information

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}")

File-Based Error Handling

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}")

Custom Error Handling with Context

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)

Error Recovery Patterns

Validation with Fallbacks

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

docs

cli.md

compatibility.md

core-validation.md

error-handling.md

index.md

schema-rules.md

type-system.md

tile.json