Batu Lab NotesPractical developer guides

Validate all items and raise an ExceptionGroup

By Batu · English technical notes

Also published in our primary archive.

To validate all items and raise an ExceptionGroup, collect each item-level validation exception, then raise one group after the loop. This requires Python 3.11 or later, when ExceptionGroup was added. With ['3', '-1', 'x'], a fail-fast validator raises at index 1 and never reaches the non-integer value at index 2.

The fixture first makes that limitation observable. validate_fail_fast() converts '3', then raises ValueError for the negative '-1'. In contrast, validate_all() handles only the conversion failure for each individual item, appends an exception describing it, and continues. A negative converted number contributes a separate ValueError. Once iteration ends, it raises ExceptionGroup("validation failed", errors) if the collection is nonempty. Catching that group lets the script display the two member messages in their insertion order.

The assertions establish that this particular input produces two independently described errors: one negative-value error and one not-integer error. They do not validate a schema, normalize values, or guarantee that every possible validator can safely continue after an error. If an item’s validation has side effects or later checks depend on earlier successful items, define whether continuation is valid before collecting errors. Python documents ExceptionGroup as an exception that wraps exception instances, including use cases where several failures should be reported together. Python error handling tutorial and built-in exceptions documentation.

AI-assistance disclosure: This article was drafted with AI assistance and uses a synthetic in-memory list.

values = ["3", "-1", "x"]


def validate_fail_fast(items):
    for index, text in enumerate(items):
        try:
            number = int(text)
        except ValueError:
            raise ValueError(f"not an integer at index {index}: {text!r}")
        if number < 0:
            raise ValueError(f"negative value at index {index}")


def validate_all(items):
    errors = []
    for index, text in enumerate(items):
        try:
            number = int(text)
        except ValueError:
            errors.append(ValueError(f"not an integer at index {index}: {text!r}"))
            continue
        if number < 0:
            errors.append(ValueError(f"negative value at index {index}"))
    if errors:
        raise ExceptionGroup("validation failed", errors)


try:
    validate_fail_fast(values)
except ValueError as error:
    print(f"fail-fast: {error}")

try:
    validate_all(values)
except ExceptionGroup as group:
    messages = [str(error) for error in group.exceptions]

assert messages == ["negative value at index 1", "not an integer at index 2: 'x'"]
print(f"group: {messages}")
fail-fast: negative value at index 1
group: ['negative value at index 1', "not an integer at index 2: 'x'"]