Custom Validators¶
Extend PyCharter's validation with custom coercion and validation rules.
Custom Coercion¶
Coercions transform data before validation:
from pycharter.shared.coercions import register_coercion
def coerce_phone_number(value):
"""Extract digits from phone number."""
if value is None:
return None
if isinstance(value, str):
return ''.join(c for c in value if c.isdigit())
return str(value)
register_coercion("coerce_phone_number", coerce_phone_number)
Use in schema:
Custom Validation¶
Validations check data after schema validation:
from pycharter.shared.validations import register_validation
def is_valid_phone(min_digits=10, max_digits=15):
"""Validate phone number length."""
def _validate(value, info):
if value is None:
return value
digits = ''.join(c for c in str(value) if c.isdigit())
if len(digits) < min_digits:
raise ValueError(f"Phone must have at least {min_digits} digits")
if len(digits) > max_digits:
raise ValueError(f"Phone must have at most {max_digits} digits")
return value
return _validate
register_validation("is_valid_phone", is_valid_phone)
Use in schema:
Validation Factory Pattern¶
Custom validations must follow the factory pattern:
def my_validation(param1, param2=default):
"""Factory that returns the actual validator."""
def _validate(value, info):
# Validation logic here
if not is_valid(value, param1, param2):
raise ValueError("Validation failed")
return value
return _validate
Complex Example: Credit Card Validation¶
from pycharter.shared.coercions import register_coercion
from pycharter.shared.validations import register_validation
# Coercion: extract digits
def coerce_credit_card(value):
if value is None:
return None
return ''.join(c for c in str(value) if c.isdigit())
register_coercion("coerce_credit_card", coerce_credit_card)
# Validation: Luhn algorithm
def is_valid_credit_card():
def _validate(value, info):
if value is None:
return value
digits = [int(d) for d in str(value)]
checksum = 0
for i, digit in enumerate(reversed(digits)):
if i % 2 == 1:
digit *= 2
if digit > 9:
digit -= 9
checksum += digit
if checksum % 10 != 0:
raise ValueError("Invalid credit card number")
return value
return _validate
register_validation("is_valid_credit_card", is_valid_credit_card)
Best Practices¶
- Handle None values - Always check for None early
- Return the value - Validators must return the (possibly modified) value
- Use clear error messages - Help users understand what went wrong
- Keep coercions idempotent - Running twice should give same result
- Document your rules - Add docstrings explaining behavior