Collect Multiple Validation Errors in Python Before Raising

A chainable Validator class that accumulates all validation errors and raises them together in a single exception.

Medium Python 3.9+ Aug 9, 2026 Errors & debugging 13 views 0 copies

Python code

53 lines
Python 3.9+
class ValidationError(Exception):
    pass

class Validator:
    def __init__(self):
        self.errors = []
    
    def validate_required(self, value, field_name):
        if not value:
            self.errors.append(f"{field_name} is required")
        return self
    
    def validate_email(self, email):
        if "@" not in email or "." not in email.split("@")[-1]:
            self.errors.append(f"{email} is not a valid email address")
        return self
    
    def validate_length(self, value, field_name, max_length):
        if len(value) > max_length:
            self.errors.append(f"{field_name} exceeds max length of {max_length}")
        return self
    
    def validate_range(self, value, field_name, min_val, max_val):
        if not min_val <= value <= max_val:
            self.errors.append(f"{field_name} must be between {min_val} and {max_val}")
        return self
    
    def validate_all(self):
        if self.errors:
            raise ValidationError("\n".join(self.errors))
        return True


class UserValidator(Validator):
    def validate_user(self, user):
        (self.validate_required(user.get("name"), "Name")
             .validate_length(user.get("name", ""), "Name", 50)
             .validate_required(user.get("email"), "Email")
             .validate_email(user.get("email", ""))
             .validate_required(user.get("age"), "Age")
             .validate_range(user.get("age", 0), "Age", 18, 120))
        return self.validate_all()


if __name__ == "__main__":
    validuser = UserValidator().validate_user({"name": "John Doe", "email": "john@example.com", "age": 30})
    print(f"User valid: {validuser}")
    
    try:
        UserValidator().validate_user({"name": "", "email": "invalid-email", "age": 5})
        print("Validator should have raised an error")
    except ValidationError as e:
        print(F"Errors:\n{e}")

Output

stdout
User valid: True
Errors:
Name is required
invalid-email is not a valid email address
Age must be between 18 and 120

How it works

This validator pattern collects all violations before raising, unlike typical fail-fast validation. The chaining methods return self, which enables readable fluent syntax with .validate_required().validate_email(). Errors accumulate in a list until validate_all() is called, which raises a single ValidationError if any issues were collected. Using ".\n".join(self.errors)creates a readable multi-line error message instead of showing only the first problem. The baseValidator` class stays reusable for different entities through subclassing.

Common mistakes

  • Forgetting to call validate_all(), which means errors never get raised
  • Not returning self from validation methods, breaking the chaining pattern
  • Using early stochastic return instead of accumulating all errors
  • Assuming validate_all returns False; it actually returns True when valid

Variations

  1. Use a dataclass or Pydantic model for declarative field validation instead
  2. Return a list of errors directly instead of raising; caller decides how to handle

Real-world use cases

  • API request payload validation, where users want all field problems in one 400 response
  • Form validation in web frameworks to show every invalid input at once on the page
  • Batch data quality checks in ETL pipelines that report all row issues before write

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Errors & debugging

Related tutorials and quizzes for this topic.