From 605cb9b38362b9cc70d5ece5ff85950b8849a5bc Mon Sep 17 00:00:00 2001 From: JasonOA888 <101583541+JasonOA888@users.noreply.github.com> Date: Fri, 20 Mar 2026 17:57:45 +0800 Subject: [PATCH 1/2] fix: improve error messages in deep_iterable and deep_mapping validators When inner validators fail, the error message now includes the index/key of the failing element instead of just the parent attribute name. Before: "Length of 'x' must be >= 1: 0" After: "Length of 'x[1]' must be >= 1: 0" This makes it clear which element in the iterable/mapping failed validation. Fixes #1245 --- src/attr/validators.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/attr/validators.py b/src/attr/validators.py index 0b1a29443..7415ad3a7 100644 --- a/src/attr/validators.py +++ b/src/attr/validators.py @@ -343,8 +343,13 @@ def __call__(self, inst, attr, value): if self.iterable_validator is not None: self.iterable_validator(inst, attr, value) - for member in value: - self.member_validator(inst, attr, member) + for idx, member in enumerate(value): + try: + self.member_validator(inst, attr, member) + except (ValueError, TypeError) as e: + # Re-raise with context about which member failed + msg = str(e).replace(f"'{attr.name}'", f"'{attr.name}[{idx}]'") + raise type(e)(msg) from None def __repr__(self): iterable_identifier = ( @@ -399,9 +404,17 @@ def __call__(self, inst, attr, value): for key in value: if self.key_validator is not None: - self.key_validator(inst, attr, key) + try: + self.key_validator(inst, attr, key) + except (ValueError, TypeError) as e: + msg = str(e).replace(f"'{attr.name}'", f"'{attr.name}[key:{key!r}]'") + raise type(e)(msg) from None if self.value_validator is not None: - self.value_validator(inst, attr, value[key]) + try: + self.value_validator(inst, attr, value[key]) + except (ValueError, TypeError) as e: + msg = str(e).replace(f"'{attr.name}'", f"'{attr.name}[{key!r}]'") + raise type(e)(msg) from None def __repr__(self): return f"" From 9764e29042c1355076183fca0ec24205ea33d87a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 09:58:26 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/attr/validators.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/attr/validators.py b/src/attr/validators.py index 7415ad3a7..b1610738f 100644 --- a/src/attr/validators.py +++ b/src/attr/validators.py @@ -407,13 +407,17 @@ def __call__(self, inst, attr, value): try: self.key_validator(inst, attr, key) except (ValueError, TypeError) as e: - msg = str(e).replace(f"'{attr.name}'", f"'{attr.name}[key:{key!r}]'") + msg = str(e).replace( + f"'{attr.name}'", f"'{attr.name}[key:{key!r}]'" + ) raise type(e)(msg) from None if self.value_validator is not None: try: self.value_validator(inst, attr, value[key]) except (ValueError, TypeError) as e: - msg = str(e).replace(f"'{attr.name}'", f"'{attr.name}[{key!r}]'") + msg = str(e).replace( + f"'{attr.name}'", f"'{attr.name}[{key!r}]'" + ) raise type(e)(msg) from None def __repr__(self):