Validate Dataclass Fields in __post_init__

Enforce types and ranges on dataclass fields at construction time, normalize values, and raise one error listing every invalid field.

PythonData Integrity

Code

snippet
from dataclasses import dataclass


class ValidationError(ValueError):
    def __init__(self, errors: dict[str, str]) -> None:
        self.errors = errors
        message = "; ".join(f"{field}: {text}" for field, text in errors.items())
        super().__init__(message)


@dataclass
class Signup:
    email: str
    age: int
    username: str

    def __post_init__(self) -> None:
        errors: dict[str, str] = {}

        email = self.email.strip().lower()
        if email.count("@") != 1 or email.startswith("@") or email.endswith("@"):
            errors["email"] = "must contain one @ with local and domain parts"
        else:
            self.email = email

        if isinstance(self.age, bool) or not isinstance(self.age, int):
            errors["age"] = "must be an integer"
        elif not 13 <= self.age <= 120:
            errors["age"] = "must be between 13 and 120"

        username = self.username.strip()
        if not 3 <= len(username) <= 20:
            errors["username"] = "must be 3-20 characters"
        elif not username.replace("_", "").isalnum():
            errors["username"] = "may contain only letters, digits, and underscores"
        else:
            self.username = username

        if errors:
            raise ValidationError(errors)


def main() -> None:
    print(Signup(email="  [email protected] ", age=36, username=" ada_1 "))
    try:
        Signup(email="nope", age=True, username="x")
    except ValidationError as error:
        print(error)
        print(error.errors)


if __name__ == "__main__":
    main()