CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-jsonschema

An implementation of JSON Schema validation for Python

Pending
Overview
Eval results
Files

validator-creation.mddocs/

Validator Creation

Advanced validator creation and extension capabilities for building custom validators, extending existing ones, and registering new schema versions. These functions provide the foundation for creating specialized validation logic.

Capabilities

Creating Custom Validators

Build completely new validator classes with custom validation logic and meta-schemas.

def create(meta_schema, 
           validators=(), 
           version=None, 
           type_checker=None, 
           format_checker=None, 
           id_of=None, 
           applicable_validators=None):
    """
    Create a new validator class.
    
    Parameters:
    - meta_schema: Schema that describes valid schemas for this validator
    - validators: Mapping of keyword names to validation functions
    - version: Version identifier for this validator
    - type_checker: TypeChecker instance for type validation
    - format_checker: FormatChecker instance for format validation  
    - id_of: Function to extract schema ID from schema
    - applicable_validators: Function to filter applicable validators
    
    Returns:
    - type[Validator]: New validator class
    """

Extending Existing Validators

Create new validator classes by extending existing ones with additional or modified validation logic.

def extend(validator, 
           validators=(), 
           version=None, 
           type_checker=None, 
           format_checker=None):
    """
    Create a new validator class by extending an existing one.
    
    Parameters:
    - validator: Existing validator class to extend
    - validators: Additional or replacement validation functions
    - version: Version identifier for the new validator
    - type_checker: TypeChecker to use (default: inherit from base)
    - format_checker: FormatChecker to use (default: inherit from base)
    
    Returns:
    - type[Validator]: Extended validator class
    """

Validator Registration

Register validators for automatic selection based on schema version.

def validates(version):
    """
    Decorator to register a validator for a schema version.
    
    Parameters:
    - version: Version identifier string
    
    Returns:
    - callable: Class decorator for validator registration
    """

Validation Function Signature

All validation functions must follow this signature:

def validation_function(validator, value, instance, schema):
    """
    Custom validation function.
    
    Parameters:
    - validator: The validator instance
    - value: The schema value for this keyword
    - instance: The instance being validated
    - schema: The complete schema being validated against
    
    Yields:
    - ValidationError: Each validation error found
    """

Usage Examples

Creating a Simple Custom Validator

from jsonschema import create, ValidationError, Draft202012Validator
from jsonschema._types import draft202012_type_checker
from jsonschema._format import draft202012_format_checker

def even_number(validator, value, instance, schema):
    """Validate that a number is even."""
    if not validator.is_type(instance, "number"):
        return
    
    if value and instance % 2 != 0:
        yield ValidationError(f"{instance} is not an even number")

def minimum_length(validator, value, instance, schema):
    """Validate minimum string length."""
    if not validator.is_type(instance, "string"):
        return
    
    if len(instance) < value:
        yield ValidationError(f"String too short: {len(instance)} < {value}")

# Create custom validator
CustomValidator = create(
    meta_schema=Draft202012Validator.META_SCHEMA,
    validators={
        "evenNumber": even_number,
        "minimumLength": minimum_length,
        # Include standard validators
        **Draft202012Validator.VALIDATORS
    },
    type_checker=draft202012_type_checker,
    format_checker=draft202012_format_checker,
    version="custom-v1"
)

# Use custom validator
schema = {
    "type": "object",
    "properties": {
        "count": {"type": "number", "evenNumber": True},
        "name": {"type": "string", "minimumLength": 3}
    }
}

validator = CustomValidator(schema)

# Test validation
valid_data = {"count": 4, "name": "Alice"}
validator.validate(valid_data)  # Passes

invalid_data = {"count": 3, "name": "Al"}  # Odd number, short name
errors = list(validator.iter_errors(invalid_data))
for error in errors:
    print(f"Custom validation error: {error.message}")

Extending Existing Validators

from jsonschema import extend, Draft202012Validator, ValidationError

def divisible_by(validator, value, instance, schema):
    """Validate that a number is divisible by the given value."""
    if not validator.is_type(instance, "number"):
        return
    
    if instance % value != 0:
        yield ValidationError(f"{instance} is not divisible by {value}")

def contains_word(validator, value, instance, schema):
    """Validate that a string contains a specific word."""
    if not validator.is_type(instance, "string"):
        return
    
    if value not in instance:
        yield ValidationError(f"String does not contain required word: {value}")

# Extend Draft 2020-12 validator
ExtendedValidator = extend(
    Draft202012Validator,
    validators={
        "divisibleBy": divisible_by,
        "containsWord": contains_word
    },
    version="extended-draft2020-12"
)

# Use extended validator
schema = {
    "type": "object", 
    "properties": {
        "score": {"type": "number", "divisibleBy": 5},
        "description": {"type": "string", "containsWord": "python"}
    }
}

validator = ExtendedValidator(schema)

valid_data = {"score": 85, "description": "I love python programming"}
validator.validate(valid_data)  # Passes

invalid_data = {"score": 87, "description": "I love javascript"}
errors = list(validator.iter_errors(invalid_data))
for error in errors:
    print(f"Extended validation error: {error.message}")

Registering Custom Validators

from jsonschema import validates, create, validator_for
from jsonschema._types import draft202012_type_checker
from jsonschema._format import draft202012_format_checker

# Custom meta-schema
CUSTOM_META_SCHEMA = {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://example.com/custom-schema",
    "type": "object",
    "properties": {
        "type": {"type": "string"},
        "customValidation": {"type": "boolean"}
    }
}

def custom_validation(validator, value, instance, schema):
    """Custom validation logic."""
    if value:
        # Perform custom validation
        if not isinstance(instance, str) or len(instance) < 5:
            yield ValidationError("Custom validation failed")

@validates("custom-v1")
class CustomValidator:
    META_SCHEMA = CUSTOM_META_SCHEMA
    VALIDATORS = {"customValidation": custom_validation}
    TYPE_CHECKER = draft202012_type_checker
    FORMAT_CHECKER = draft202012_format_checker
    
    def __init__(self, schema, **kwargs):
        # Implementation similar to standard validators
        pass

# Now validator_for will automatically select CustomValidator
schema_with_custom_version = {
    "$schema": "https://example.com/custom-schema",
    "type": "string",
    "customValidation": True
}

ValidatorClass = validator_for(schema_with_custom_version)
print(ValidatorClass.__name__)  # CustomValidator

Complex Validation Functions

from jsonschema import ValidationError

def unique_properties(validator, value, instance, schema):
    """
    Validate that all property values in an object are unique.
    """
    if not validator.is_type(instance, "object"):
        return
    
    if not value:  # Skip if not enabled
        return
    
    values = list(instance.values())
    seen = set()
    duplicates = set()
    
    for val in values:
        # Only check hashable values
        try:
            if val in seen:
                duplicates.add(val)
            else:
                seen.add(val)
        except TypeError:
            # Skip unhashable values
            continue
    
    if duplicates:
        yield ValidationError(
            f"Object has duplicate values: {duplicates}",
            validator="uniqueProperties",
            validator_value=value,
            instance=instance,
            schema=schema
        )

def conditional_required(validator, value, instance, schema):
    """
    Conditionally require properties based on other property values.
    
    Example: {"ifProperty": "type", "equals": "user", "thenRequired": ["email"]}
    """
    if not validator.is_type(instance, "object"):
        return
    
    if_prop = value.get("ifProperty")
    equals = value.get("equals") 
    then_required = value.get("thenRequired", [])
    
    if if_prop in instance and instance[if_prop] == equals:
        for required_prop in then_required:
            if required_prop not in instance:
                yield ValidationError(
                    f"Property '{required_prop}' is required when '{if_prop}' equals '{equals}'",
                    validator="conditionalRequired",
                    validator_value=value,
                    instance=instance,
                    schema=schema,
                    path=[required_prop]
                )

# Use complex validators
AdvancedValidator = extend(
    Draft202012Validator,
    validators={
        "uniqueProperties": unique_properties,
        "conditionalRequired": conditional_required
    }
)

schema = {
    "type": "object",
    "uniqueProperties": True,
    "conditionalRequired": {
        "ifProperty": "type",
        "equals": "user", 
        "thenRequired": ["email", "username"]
    },
    "properties": {
        "type": {"type": "string"},
        "email": {"type": "string"},
        "username": {"type": "string"},
        "name": {"type": "string"},
        "age": {"type": "number"}
    }
}

validator = AdvancedValidator(schema)

# Valid data
valid_data = {
    "type": "user",
    "email": "user@example.com", 
    "username": "john_doe",
    "name": "John Doe",
    "age": 30
}
validator.validate(valid_data)  # Passes

# Invalid - duplicate values
invalid_duplicate = {
    "name": "John",
    "username": "John",  # Duplicate value
    "type": "admin"
}

# Invalid - missing required when type=user
invalid_missing = {
    "type": "user",
    "name": "John"  # Missing email and username
}

for data in [invalid_duplicate, invalid_missing]:
    errors = list(validator.iter_errors(data))
    for error in errors:
        print(f"Advanced validation error: {error.message}")

Custom Meta-Schema Validation

from jsonschema import create, ValidationError, SchemaError

# Define custom meta-schema
CUSTOM_META_SCHEMA = {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://example.com/custom-meta-schema",
    "type": "object",
    "properties": {
        "type": {"type": "string"},
        "customKeyword": {"type": "string", "enum": ["strict", "loose"]}
    },
    "additionalProperties": False
}

def custom_keyword_validator(validator, value, instance, schema):
    """Custom keyword validation."""
    if value == "strict" and isinstance(instance, str) and len(instance) < 10:
        yield ValidationError("Strict mode requires strings of at least 10 characters")

CustomValidator = create(
    meta_schema=CUSTOM_META_SCHEMA,
    validators={"customKeyword": custom_keyword_validator}
)

# Valid schema according to custom meta-schema
valid_schema = {
    "type": "string",
    "customKeyword": "strict"
}

# Invalid schema - violates meta-schema
invalid_schema = {
    "type": "string", 
    "customKeyword": "invalid_value",  # Not in enum
    "additionalProperty": "not_allowed"  # Not allowed by meta-schema
}

try:
    CustomValidator.check_schema(valid_schema)
    print("Schema is valid")
except SchemaError as e:
    print(f"Schema error: {e.message}")

try:
    CustomValidator.check_schema(invalid_schema)
except SchemaError as e:
    print(f"Schema validation failed: {e.message}")

Install with Tessl CLI

npx tessl i tessl/pypi-jsonschema

docs

core-validation.md

error-handling.md

format-validation.md

index.md

type-checking.md

validator-creation.md

validators.md

tile.json