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.
Python code
53 linesclass 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
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
- Use a dataclass or Pydantic model for declarative field validation instead
- 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
More from Errors & debugging
- Catch RecursionError and Fail Gracefully in Python easy
- Catch ValueError and print friendly message in Python easy
- Handle ValueError and ZeroDivisionError in Python with try except easy
- How to Add a Correlation ID to Logging Records in Python medium
- How to Assert Preconditions with Descriptive Messages in Python easy
- How to Assert an Invariant After a Complex Transformation in Python easy
Keep learning
Related tutorials and quizzes for this topic.