Validation
The iterable.validate module provides a framework for validating data against rules and schemas.
Overview
Validation operations help ensure data quality:
- Built-in validation rules (email, URL, Russian identifiers)
- Custom validator registration for domain-specific validation
- Multiple validation modes (stats, invalid, valid)
- Streaming validation for large datasets
- Detailed error reporting with context
Functions
iterable()
Validate rows in an iterable dataset against validation rules.
from iterable import validate
# Basic validation
rules = {"email": ["common.email"], "url": ["common.url"]}
for record, errors in validate.iterable("data.csv", rules):
if errors:
print(f"Errors: {errors}")
# Validation with stats
stats = validate.iterable("data.csv", rules, mode="stats")
print(f"Valid: {stats['valid_rows']}, Invalid: {stats['invalid_rows']}")
Parameters:
iterable: An iterable of row dictionaries, or a file path/streamrules: Dictionary mapping field names to lists of rule names- Example:
{"email": ["common.email", "required"]}
- Example:
mode: Validation mode (default: "default")"default": Returns (record, errors) tuples for all rows"stats": Returns validation statistics dictionary"invalid": Returns only rows that failed validation"valid": Returns only rows that passed validation
max_errors: Maximum number of errors to collect before stopping (None for no limit)
Returns:
- If
mode="stats": Dictionary with validation statistics - Otherwise: Iterator of (record, errors) tuples where errors is a list of error messages
Validation Hooks
Validation hooks allow you to automatically validate data during read and write operations, providing fail-fast validation and data quality assurance.
Basic Usage
from iterable import open_iterable
from iterable.helpers.validation import rules_validator
# Create validation hook
rules = {"email": ["common.email"], "url": ["common.url"]}
hook = rules_validator(rules)
# Use validation hook during iteration
with open_iterable(
"data.csv",
iterableargs={"validation_hook": hook, "on_validation_error": "skip"}
) as source:
for row in source: # Invalid rows automatically skipped
process(row)
Error Handling Policies
Validation hooks support four error handling policies:
'raise'(default): Raise exception on validation failure'skip': Skip invalid rows, continue processing'log': Log errors, continue processing'warn': Issue warning, continue processing
# Fail fast on invalid data
with open_iterable("data.csv", iterableargs={"validation_hook": hook, "on_validation_error": "raise"}) as source:
for row in source: # Raises on first invalid row
process(row)
# Skip invalid rows
with open_iterable("data.csv", iterableargs={"validation_hook": hook, "on_validation_error": "skip"}) as source:
for row in source: # Invalid rows skipped
process(row)
Schema Validation Hooks
from iterable.ops import schema
from iterable.helpers.validation import schema_validator
# Infer schema
sch = schema.infer("data.csv")
# Create schema validation hook
hook = schema_validator(sch)
# Use during iteration
with open_iterable("data.csv", iterableargs={"validation_hook": hook}) as source:
for row in source: # Automatically validated against schema
process(row)
Rules Validation Hooks
from iterable.helpers.validation import rules_validator
# Create rules validation hook
rules = {"email": ["common.email"], "age": ["required"]}
hook = rules_validator(rules)
# Use during iteration
with open_iterable("data.csv", iterableargs={"validation_hook": hook}) as source:
for row in source: # Automatically validated against rules
process(row)
Multiple Validation Hooks
You can chain multiple validation hooks:
def validate_schema(row):
return schema_validator(sch)(row)
def validate_business_rules(row):
if row["status"] not in ["active", "inactive"]:
raise ValueError("Invalid status")
return row
# Chain hooks
with open_iterable(
"data.csv",
iterableargs={"validation_hook": [validate_schema, validate_business_rules]}
) as source:
for row in source: # Both hooks applied in sequence
process(row)
Validation During Write
Validation hooks also work during write operations:
def validate_before_write(row):
if "id" not in row:
raise ValueError("Row missing required 'id' field")
return row
with open_iterable(
"output.jsonl",
mode="w",
iterableargs={"validation_hook": validate_before_write, "on_validation_error": "skip"}
) as dest:
dest.write({"id": 1, "name": "test"}) # Validated before write
dest.write({"name": "no_id"}) # Skipped (missing id)
Built-in Validation Rules
Email Validation
from iterable import validate
rules = {"email": ["common.email"]}
for record, errors in validate.iterable("users.csv", rules):
if errors:
print(f"Invalid email: {record['email']}")
Validates email address format using standard email regex pattern.
URL Validation
from iterable import validate
rules = {"url": ["common.url"]}
for record, errors in validate.iterable("links.csv", rules):
if errors:
print(f"Invalid URL: {record['url']}")
Validates URL format (http:// or https://).
Required Field Validation
from iterable import validate
rules = {"name": ["required"]}
for record, errors in validate.iterable("data.csv", rules):
if errors:
print(f"Missing required field: {errors}")
Validates that a field is not None or empty string.
Russian INN Validation
from iterable import validate
rules = {"inn": ["ru.org.inn"]}
for record, errors in validate.iterable("companies.csv", rules):
if errors:
print(f"Invalid INN: {record['inn']}")
Validates Russian tax identification number (INN) format (10 or 12 digits).
Russian OGRN Validation
from iterable import validate
rules = {"ogrn": ["ru.org.ogrn"]}
for record, errors in validate.iterable("companies.csv", rules):
if errors:
print(f"Invalid OGRN: {record['ogrn']}")
Validates Russian state registration number (OGRN) format (13 or 15 digits).
Custom Validators
Registering Custom Validators
from iterable.validate import register
from iterable import validate
# Define custom validator
def validate_positive(value):
"""Validate that value is positive."""
return isinstance(value, (int, float)) and value > 0
# Register the validator
register("positive", validate_positive)
# Use in validation rules
rules = {"price": ["positive"]}
for record, errors in validate.iterable("products.csv", rules):
if errors:
print(f"Invalid price: {record['price']}")
Validator with Parameters
from iterable.validate import register
def validate_range(min_val, max_val):
"""Create a validator for a value range."""
def validator(value):
return isinstance(value, (int, float)) and min_val <= value <= max_val
return validator
# Register with specific parameters
register("age_range", validate_range(18, 120))
rules = {"age": ["age_range"]}
Examples
Basic Validation
from iterable import validate
# Validate email addresses
rules = {"email": ["common.email"]}
for record, errors in validate.iterable("users.csv", rules):
if errors:
print(f"User {record.get('id')}: {errors}")
Multiple Rules per Field
from iterable import validate
# Validate email is both required and valid format
rules = {"email": ["required", "common.email"]}
for record, errors in validate.iterable("users.csv", rules):
if errors:
print(f"Validation failed: {errors}")
Validation Statistics
from iterable import validate
rules = {"email": ["common.email"], "url": ["common.url"]}
stats = validate.iterable("data.csv", rules, mode="stats")
print(f"Total rows: {stats['total_rows']}")
print(f"Valid rows: {stats['valid_rows']}")
print(f"Invalid rows: {stats['invalid_rows']}")
print(f"Error counts: {stats['error_counts']}")
Filter Invalid Rows
from iterable import validate
rules = {"email": ["common.email"]}
invalid_rows = list(validate.iterable("users.csv", rules, mode="invalid"))
print(f"Found {len(invalid_rows)} invalid rows")
for record, errors in invalid_rows:
print(f" {record.get('id')}: {errors}")
Filter Valid Rows
from iterable import validate
rules = {"email": ["common.email"]}
valid_rows = list(validate.iterable("users.csv", rules, mode="valid"))
print(f"Found {len(valid_rows)} valid rows")
# Process only valid rows
for record, errors in valid_rows:
process(record)
Early Termination
from iterable import validate
# Stop after finding 100 errors
rules = {"email": ["common.email"]}
results = list(validate.iterable("large_file.csv", rules, max_errors=100))
print(f"Checked {len(results)} rows before hitting error limit")
Custom Validation Rules
from iterable.validate import register
from iterable import validate
# Register custom validators
def validate_phone(value):
"""Validate US phone number format."""
import re
pattern = r"^\d{3}-\d{3}-\d{4}$"
return isinstance(value, str) and bool(re.match(pattern, value))
register("us.phone", validate_phone)
# Use custom validator
rules = {"phone": ["us.phone"]}
for record, errors in validate.iterable("contacts.csv", rules):
if errors:
print(f"Invalid phone: {record['phone']}")
Combining with Other Operations
from iterable import validate, filter as f, transform
# Filter, validate, then process
filtered = f.filter_expr("data.csv", "`status` == 'active'")
validated = validate.iterable(filtered, {"email": ["common.email"]}, mode="valid")
deduped = transform.deduplicate(validated, keys=["email"])
for record, errors in deduped:
process(record)
Validation Modes
Default Mode
Returns all rows with their validation errors:
for record, errors in validate.iterable("data.csv", rules):
if errors:
print(f"Row has errors: {errors}")
else:
print("Row is valid")
Stats Mode
Returns only statistics, no individual row results:
stats = validate.iterable("data.csv", rules, mode="stats")
print(f"Validation complete: {stats['valid_rows']}/{stats['total_rows']} valid")
Invalid Mode
Returns only rows that failed validation:
invalid = list(validate.iterable("data.csv", rules, mode="invalid"))
for record, errors in invalid:
print(f"Invalid: {errors}")
Valid Mode
Returns only rows that passed validation:
valid = list(validate.iterable("data.csv", rules, mode="valid"))
for record, errors in valid:
process(record) # All records here are valid
Error Reporting
Error messages include:
- Field name: Which field failed validation
- Rule name: Which rule failed
- Error message: Human-readable description
Example error messages:
"email: Value failed validation rule 'common.email'"
"name: Value failed validation rule 'required'"
Performance Notes
- Streaming validation: Memory usage remains constant regardless of dataset size
- Early termination: Use
max_errorsto stop validation early when many errors are found - Rule order: Rules are evaluated in order; first failure stops evaluation for that field (unless all rules need checking)
Best Practices
- Validate early: Validate data as soon as possible in your pipeline
- Use specific rules: Use the most specific validation rules for your data
- Combine rules: Use multiple rules per field for comprehensive validation
- Monitor statistics: Use stats mode to understand data quality at scale
- Custom validators: Register domain-specific validators for reusable validation logic