CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-apischema

JSON (de)serialization, GraphQL and JSON schema generation using Python typing.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

validation.mddocs/

Validation

Comprehensive validation framework with structured error reporting, field-level validators, custom validation functions, and detailed error location tracking.

Capabilities

ValidationError

Structured exception class that provides detailed information about validation failures with precise location tracking.

class ValidationError(Exception):
    """
    Exception raised when validation fails.
    
    Provides structured error information with location tracking
    for precise error reporting and debugging.
    """
    
    def __init__(
        self,
        messages: Optional[Union[ErrorMsg, Sequence[ErrorMsg]]] = None,
        children: Optional[Mapping[ErrorKey, "ValidationError"]] = None,
    ):
        """
        Initialize validation error.
        
        Parameters:
        - messages: Error messages for this location
        - children: Nested validation errors for child fields/elements
        """
    
    @property
    def errors(self) -> List[LocalizedError]:
        """
        Get all errors as a flat list with location information.
        
        Returns:
        List of errors with 'loc' (location path) and 'err' (error message)
        """
    
    @staticmethod
    def from_errors(errors: Sequence[LocalizedError]) -> "ValidationError":
        """
        Create ValidationError from a list of localized errors.
        
        Parameters:
        - errors: List of error dictionaries with 'loc' and 'err' keys
        
        Returns:
        ValidationError instance
        """

Validator Decorator

Decorator for registering validation functions that can be applied to types, fields, or during operations.

def validator(
    func: Optional[Callable] = None,
    *,
    field: Any = None,
    discard: Any = None,
    owner: Optional[Type] = None,
) -> Callable:
    """
    Register a validation function.
    
    Parameters:
    - func: Validation function to register
    - field: Specific field to validate (default: whole object)
    - discard: Fields to discard when validation passes
    - owner: Owner class for field validation
    
    Returns:
    Decorated validation function
    """

Validator Class

Configuration class for detailed validator setup with field targeting and error handling.

class Validator:
    """
    Validator configuration with field targeting and options.
    """
    
    def __init__(
        self,
        func: Callable,
        field: Optional[FieldOrName] = None,
        discard: Optional[Collection[FieldOrName]] = None,
    ):
        """
        Initialize validator.
        
        Parameters:
        - func: Validation function 
        - field: Target field for validation
        - discard: Fields to discard on successful validation
        """

Validation Functions

Functions for applying validators to objects and handling validation results.

def validate(
    obj: T,
    validators: Optional[Iterable[Validator]] = None,
    kwargs: Optional[Mapping[str, Any]] = None,
    *,
    aliaser: Aliaser = lambda s: s,
) -> T:
    """
    Apply validators to an object.
    
    Parameters:
    - obj: Object to validate
    - validators: List of validators to apply
    - kwargs: Additional arguments for validation functions
    - aliaser: Function to transform field names
    
    Returns:
    Validated object (may be modified if fields discarded)
    
    Raises:
    ValidationError: If validation fails
    """

Discard Exception

Exception for conditionally removing fields during validation.

class Discard(Exception):
    """
    Exception for discarding fields during validation.
    
    Allows validators to conditionally remove fields from objects
    while providing error information.
    """
    
    def __init__(self, fields: Optional[AbstractSet[str]], error: ValidationError):
        """
        Initialize discard exception.
        
        Parameters:  
        - fields: Field names to discard
        - error: Associated validation error
        """

Usage Examples

Basic Field Validation

from dataclasses import dataclass
from apischema import validator, deserialize, ValidationError

@dataclass
class User:
    name: str
    age: int
    email: str

@validator
def validate_age(age: int) -> int:
    """Validate age is reasonable."""
    if age < 0:
        raise ValueError("Age cannot be negative")
    if age > 150:
        raise ValueError("Age cannot exceed 150")
    return age

@validator  
def validate_email(email: str) -> str:
    """Validate email format."""
    if "@" not in email:
        raise ValueError("Invalid email format")
    return email

# Register validators for specific fields
validator(validate_age, field="age", owner=User)
validator(validate_email, field="email", owner=User)

# Validation during deserialization
try:
    user = deserialize(User, {"name": "John", "age": -5, "email": "invalid"})
except ValidationError as err:
    for error in err.errors:
        print(f"{error['loc']}: {error['err']}")
    # ['age']: Age cannot be negative
    # ['email']: Invalid email format

Custom Validation Functions

from typing import List

@dataclass
class Product:
    name: str
    price: float
    tags: List[str]

@validator
def validate_product(product: Product) -> Product:
    """Validate entire product object."""
    if product.price <= 0:
        raise ValueError("Price must be positive")
    
    if len(product.name.strip()) == 0:
        raise ValueError("Name cannot be empty")
    
    if len(product.tags) == 0:
        raise ValueError("Product must have at least one tag")
    
    return product

# Apply validator to the Product class
validator(validate_product, owner=Product)

try:
    product = deserialize(Product, {
        "name": "",
        "price": -10,
        "tags": []
    })
except ValidationError as err:
    print(err.errors)
    # Multiple validation errors for the product object

Conditional Field Validation

@dataclass
class Account:
    type: str  # "personal" or "business"
    name: str
    tax_id: Optional[str] = None

@validator
def validate_business_tax_id(account: Account) -> Account:
    """Business accounts must have tax ID."""
    if account.type == "business" and not account.tax_id:
        raise ValueError("Business accounts require tax ID")
    return account

validator(validate_business_tax_id, owner=Account)

# Valid personal account
personal = deserialize(Account, {"type": "personal", "name": "John Doe"})

# Invalid business account  
try:
    business = deserialize(Account, {"type": "business", "name": "ACME Corp"})
except ValidationError as err:
    print(err.errors)  # Missing tax ID error

Field Discarding

from apischema.validation import Discard

@dataclass
class UserInput:
    username: str
    password: str
    password_confirm: str

@validator
def validate_passwords(user: UserInput) -> UserInput:
    """Validate passwords match and discard confirmation field."""
    if user.password != user.password_confirm:
        raise ValidationError([{
            "loc": ["password_confirm"], 
            "err": "Passwords do not match"
        }])
    
    # Discard password_confirm field after validation
    raise Discard({"password_confirm"}, ValidationError([]))

validator(validate_passwords, owner=UserInput)

# After successful validation, password_confirm is removed
user_data = {"username": "john", "password": "secret", "password_confirm": "secret"}
user = deserialize(UserInput, user_data)
# user object won't have password_confirm field

Validation with Custom Error Messages

@validator
def validate_username(username: str) -> str:
    """Validate username with detailed error messages."""
    if len(username) < 3:
        raise ValueError("Username must be at least 3 characters long")
    
    if not username.isalnum():
        raise ValueError("Username must contain only letters and numbers")
    
    if username.lower() in ["admin", "root", "user"]:
        raise ValueError("Username is reserved and cannot be used")
    
    return username

validator(validate_username, field="username", owner=User)

Multiple Validators

@dataclass
class Order:
    items: List[Dict[str, Any]]
    total: float
    discount: float = 0.0

@validator
def validate_items_not_empty(items: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    """Order must have at least one item."""
    if not items:
        raise ValueError("Order must contain at least one item")
    return items

@validator  
def validate_total_positive(total: float) -> float:
    """Total must be positive.""" 
    if total <= 0:
        raise ValueError("Order total must be positive")
    return total

@validator
def validate_discount_range(discount: float) -> float:
    """Discount must be between 0 and 1."""
    if not 0 <= discount <= 1:
        raise ValueError("Discount must be between 0 and 1")
    return discount

# Register all validators
validator(validate_items_not_empty, field="items", owner=Order)
validator(validate_total_positive, field="total", owner=Order)
validator(validate_discount_range, field="discount", owner=Order)

Additional Validation APIs

Functions for working with validators programmatically and retrieving validation information.

def get_validators(tp: AnyType) -> Sequence[Validator]:
    """
    Get all validators registered for a given type.
    
    Parameters:
    - tp: The type to get validators for
    
    Returns:
    Sequence of Validator objects registered for the type
    """

Type Aliases

ErrorMsg = str  # Error message string
ErrorKey = Union[str, int]  # Key for error location (field name or array index)
LocalizedError = Dict[str, Any]  # Error dict with 'loc' and 'err' keys
Error = Union[ErrorMsg, Tuple[Any, ErrorMsg]]  # Basic error type
ValidatorResult = Generator[Error, None, T]  # Generator-based validator result type
FieldOrName = Union[str, Any]  # Field reference or field name
Aliaser = Callable[[str], str]  # Function for transforming field names

Install with Tessl CLI

npx tessl i tessl/pypi-apischema

docs

advanced.md

conversions.md

index.md

metadata.md

schema-generation.md

serialization.md

validation.md

tile.json