CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-cerberus

Lightweight, extensible schema and data validation tool for Python dictionaries.

Overview
Eval results
Files

type-system.mddocs/

Type System

Cerberus provides an extensible type system that allows defining custom types for validation. This enables domain-specific validation logic and type constraints beyond the built-in types.

# Import types and utilities for custom type definitions
from cerberus import TypeDefinition, validator_factory
from cerberus.utils import readonly_classproperty

# Platform-specific types used in built-in definitions
from cerberus.platform import Container, Mapping, Sequence, _str_type, _int_types

Capabilities

TypeDefinition

Named tuple for defining custom types that can be used in validator type mappings.

TypeDefinition = namedtuple('TypeDefinition', 'name,included_types,excluded_types')
    """
    Defines a custom type for validation.
    
    Fields:
    - name: Descriptive type name (str)
    - included_types: Tuple of allowed Python types
    - excluded_types: Tuple of excluded Python types
    
    A value is valid for this type if it's an instance of any type in 
    included_types and not an instance of any type in excluded_types.
    """

Validator Factory

Factory function for creating custom validator classes with mixins.

def validator_factory(name, bases=None, namespace={}):
    """
    Dynamically create a Validator subclass with mixin capabilities.
    
    Parameters:
    - name: Name of the new validator class (str)
    - bases: Base/mixin classes to include (tuple, class, or None)
    - namespace: Additional class attributes (dict)
    
    Returns:
    type: New Validator subclass with combined functionality
    
    Note: Docstrings from mixin classes are automatically combined
    if __doc__ is not specified in namespace.
    """

Utility Classes

Utility classes for advanced property management.

class readonly_classproperty(property):
    """
    Creates read-only class properties that raise errors on modification attempts.
    
    Usage:
    class MyValidator(Validator):
        @readonly_classproperty
        def my_property(cls):
            return "read-only value"
    """
    
    def __get__(self, instance, owner): ...
    def __set__(self, instance, value): ...  # Raises RuntimeError
    def __delete__(self, instance): ...      # Raises RuntimeError

Built-in Type Definitions

Cerberus provides a comprehensive set of built-in type definitions that map common Python types to validation constraints.

# Built-in types_mapping in Validator class
types_mapping = {
    'binary': TypeDefinition('binary', (bytes, bytearray), ()),
    'boolean': TypeDefinition('boolean', (bool,), ()),
    'container': TypeDefinition('container', (Container,), (_str_type,)),
    'date': TypeDefinition('date', (date,), ()),
    'datetime': TypeDefinition('datetime', (datetime,), ()),
    'dict': TypeDefinition('dict', (Mapping,), ()),
    'float': TypeDefinition('float', (float, _int_types), ()),
    'integer': TypeDefinition('integer', (_int_types,), ()),
    'list': TypeDefinition('list', (Sequence,), (_str_type,)),
    'number': TypeDefinition('number', (_int_types, float), (bool,)),
    'set': TypeDefinition('set', (set,), ()),
    'string': TypeDefinition('string', (_str_type,), ()),
}

Type Details:

  • binary: Accepts bytes and bytearray objects for binary data
  • boolean: Accepts bool values (True/False)
  • container: Accepts any Container type except strings (_str_type)
  • date: Accepts datetime.date objects
  • datetime: Accepts datetime.datetime objects
  • dict: Accepts any Mapping type (dict, OrderedDict, etc.)
  • float: Accepts float and integer types (_int_types can be coerced to floats)
  • integer: Accepts integer types (_int_types) only
  • list: Accepts any Sequence type except strings (_str_type)
  • number: Accepts integer types (_int_types) and float but excludes bool (even though bool is subclass of int)
  • set: Accepts set objects
  • string: Accepts string types (_str_type)

Usage Examples

Defining Custom Types

from cerberus import Validator, TypeDefinition

# Define a custom type for positive numbers
positive_number = TypeDefinition(
    'positive_number',
    (int, float),      # Include integers and floats
    ()                 # No excluded types
)

# Add to validator's type mapping
class CustomValidator(Validator):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.types_mapping['positive_number'] = positive_number

# Define custom validation method for positive constraint
def _validate_positive(self, positive, field, value):
    """Test if value is positive"""
    if positive and value <= 0:
        self._error(field, "must be positive")

# Add the method to our custom validator
CustomValidator._validate_positive = _validate_positive

# Use the custom type and validator
schema = {
    'count': {'type': 'positive_number', 'positive': True}
}

v = CustomValidator(schema)
print(v.validate({'count': 5}))    # True
print(v.validate({'count': -1}))   # False
print(v.validate({'count': '5'}))  # False (not int or float)

Complex Type Definitions

from cerberus import TypeDefinition

# Define a type that accepts strings but excludes empty strings
non_empty_string = TypeDefinition(
    'non_empty_string',
    (str,),
    ()  # We'll handle empty string logic in custom validator
)

# Define a type for numeric types excluding booleans
numeric_no_bool = TypeDefinition(
    'numeric_no_bool',
    (int, float, complex),
    (bool,)  # Exclude booleans even though bool is subclass of int
)

# Use in schema
schema = {
    'name': {'type': 'non_empty_string'},
    'value': {'type': 'numeric_no_bool'}
}

Using validator_factory

from cerberus import validator_factory
from cerberus.validator import Validator

# Define mixin classes
class EmailValidatorMixin:
    """Adds email validation capabilities"""
    
    def _validate_email_domain(self, domain, field, value):
        """Validate email domain"""
        if '@' in value and not value.split('@')[1].endswith(domain):
            self._error(field, f"must be from {domain} domain")

class NumericValidatorMixin:
    """Adds numeric validation capabilities"""
    
    def _validate_even(self, even, field, value):
        """Validate if number is even"""
        if even and value % 2 != 0:
            self._error(field, "must be even")

# Create custom validator class with mixins
CustomValidator = validator_factory(
    'CustomValidator',
    (EmailValidatorMixin, NumericValidatorMixin),
    {
        'custom_attribute': 'custom_value'
    }
)

# Use the custom validator
schema = {
    'email': {'type': 'string', 'email_domain': '.com'},
    'count': {'type': 'integer', 'even': True}
}

v = CustomValidator(schema)
print(v.validate({
    'email': 'user@example.com',
    'count': 4
}))  # True

print(v.validate({
    'email': 'user@example.org',  # Wrong domain
    'count': 3                    # Not even
}))  # False

Custom Types with Built-in Types

from cerberus import Validator, TypeDefinition

# Create validator with custom types
class ExtendedValidator(Validator):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        # Add custom types to existing mapping
        self.types_mapping.update({
            'phone_number': TypeDefinition('phone_number', (str,), ()),
            'uuid': TypeDefinition('uuid', (str,), ()),
            'positive_int': TypeDefinition('positive_int', (int,), (bool,))
        })

# Define validation methods for custom types
def _validate_phone_format(self, phone_format, field, value):
    """Validate phone number format"""
    import re
    if phone_format and not re.match(r'^\+?1?\d{9,15}$', value):
        self._error(field, "invalid phone number format")

def _validate_uuid_format(self, uuid_format, field, value):
    """Validate UUID format"""
    import re
    if uuid_format and not re.match(r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', value.lower()):
        self._error(field, "invalid UUID format")

def _validate_positive(self, positive, field, value):
    """Validate positive number"""
    if positive and value <= 0:
        self._error(field, "must be positive")

# Add methods to validator
ExtendedValidator._validate_phone_format = _validate_phone_format
ExtendedValidator._validate_uuid_format = _validate_uuid_format
ExtendedValidator._validate_positive = _validate_positive

# Use custom types in schema
schema = {
    'id': {'type': 'uuid', 'uuid_format': True},
    'phone': {'type': 'phone_number', 'phone_format': True},
    'count': {'type': 'positive_int', 'positive': True}
}

v = ExtendedValidator(schema)

Multiple Inheritance with validator_factory

from cerberus import validator_factory

class LoggingMixin:
    """Adds logging to validation"""
    
    def validate(self, *args, **kwargs):
        print(f"Starting validation with schema: {list(self.schema.keys())}")
        result = super().validate(*args, **kwargs)
        print(f"Validation result: {result}")
        return result

class CachingMixin:
    """Adds result caching"""
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._cache = {}
    
    def validate(self, document, *args, **kwargs):
        doc_hash = hash(str(sorted(document.items())))
        if doc_hash in self._cache:
            return self._cache[doc_hash]
        
        result = super().validate(document, *args, **kwargs)
        self._cache[doc_hash] = result
        return result

# Create validator with multiple mixins
EnhancedValidator = validator_factory(
    'EnhancedValidator',
    (LoggingMixin, CachingMixin)
)

v = EnhancedValidator({'name': {'type': 'string'}})
v.validate({'name': 'test'})  # Logs and caches result
v.validate({'name': 'test'})  # Uses cached result

Read-only Class Properties

from cerberus import Validator
from cerberus.utils import readonly_classproperty

class ConfiguredValidator(Validator):
    @readonly_classproperty
    def default_schema(cls):
        return {
            'id': {'type': 'integer', 'required': True},
            'name': {'type': 'string', 'required': True}
        }
    
    @readonly_classproperty
    def version(cls):
        return "1.0.0"

# Access read-only properties
print(ConfiguredValidator.default_schema)  # Works
print(ConfiguredValidator.version)         # Works

# Attempting to modify raises RuntimeError
try:
    ConfiguredValidator.version = "2.0.0"
except RuntimeError as e:
    print(f"Error: {e}")  # "This is a readonly class property."

Type System Integration

from cerberus import Validator, TypeDefinition

class BusinessValidator(Validator):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        # Define business-specific types
        self.types_mapping.update({
            'currency': TypeDefinition('currency', (int, float), (bool,)),
            'percentage': TypeDefinition('percentage', (int, float), (bool,)),
            'business_id': TypeDefinition('business_id', (str,), ())
        })

# Add business rule validations
def _validate_currency_positive(self, currency_positive, field, value):
    if currency_positive and value < 0:
        self._error(field, "currency amounts must be positive")

def _validate_percentage_range(self, percentage_range, field, value):
    if percentage_range and not (0 <= value <= 100):
        self._error(field, "percentage must be between 0 and 100")

def _validate_business_id_format(self, business_id_format, field, value):
    if business_id_format and not value.startswith('BIZ'):
        self._error(field, "business ID must start with 'BIZ'")

BusinessValidator._validate_currency_positive = _validate_currency_positive
BusinessValidator._validate_percentage_range = _validate_percentage_range
BusinessValidator._validate_business_id_format = _validate_business_id_format

# Use in business domain schemas
product_schema = {
    'id': {'type': 'business_id', 'business_id_format': True},
    'price': {'type': 'currency', 'currency_positive': True},
    'discount': {'type': 'percentage', 'percentage_range': True}
}

v = BusinessValidator(product_schema)
result = v.validate({
    'id': 'BIZ123',
    'price': 29.99,
    'discount': 15
})
print(result)  # True

Install with Tessl CLI

npx tessl i tessl/pypi-cerberus

docs

advanced-features.md

core-validation.md

error-handling.md

index.md

schema-management.md

type-system.md

tile.json