From e646bd917e38eaab5882170aa563700d910dee03 Mon Sep 17 00:00:00 2001 From: Finite State Machine <38001514+finite-state-machine@users.noreply.github.com> Date: Sat, 3 Jan 2026 18:50:56 -0500 Subject: [PATCH 1/8] Fix `optional` validator to accept tuples of len > 1 --- src/attr/validators.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/attr/validators.pyi b/src/attr/validators.pyi index 36a7e800c..18fb112c8 100644 --- a/src/attr/validators.pyi +++ b/src/attr/validators.pyi @@ -54,7 +54,7 @@ def optional( validator: ( _ValidatorType[_T] | list[_ValidatorType[_T]] - | tuple[_ValidatorType[_T]] + | tuple[_ValidatorType[_T], ...] ), ) -> _ValidatorType[_T | None]: ... def in_(options: Container[_T]) -> _ValidatorType[_T]: ... From 52cd1d7bc3120ba0e53d9c8119c1f345f2c57f7a Mon Sep 17 00:00:00 2001 From: Finite State Machine Date: Sat, 3 Jan 2026 19:19:24 -0500 Subject: [PATCH 2/8] Add changelog --- changelog.d/1496.change.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1496.change.md diff --git a/changelog.d/1496.change.md b/changelog.d/1496.change.md new file mode 100644 index 000000000..a3dd5a305 --- /dev/null +++ b/changelog.d/1496.change.md @@ -0,0 +1 @@ +Fix type annotations for `optional` validator so it no longer rejects tuples of len > 1 From 110c6d26b140880d5377e49d8a60a4a2e3867a9c Mon Sep 17 00:00:00 2001 From: Finite State Machine Date: Sat, 3 Jan 2026 19:19:35 -0500 Subject: [PATCH 3/8] Add `optional` validator over 2-tuple test case to `baseline.py` --- typing-examples/baseline.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/typing-examples/baseline.py b/typing-examples/baseline.py index 195b3262b..a34e20d97 100644 --- a/typing-examples/baseline.py +++ b/typing-examples/baseline.py @@ -94,6 +94,15 @@ class Validated: num: int = attrs.field(validator=attrs.validators.ge(0)) +@attrs.define +class ValidatedOptionalOverTuple: + num: int | None = attrs.field( + validator=attrs.validators.optional( + (attrs.validators.instance_of(int), attrs.validators.ge(0)) + ) + ) + + @attrs.define class ValidatedInconsistentOr: num: int | str = attrs.field( From 61a1dd0ee474e9c8cfbe055ebb463d8168c65bbe Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 4 Jan 2026 00:20:34 +0000 Subject: [PATCH 4/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- typing-examples/baseline.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/typing-examples/baseline.py b/typing-examples/baseline.py index a34e20d97..15542cab7 100644 --- a/typing-examples/baseline.py +++ b/typing-examples/baseline.py @@ -97,10 +97,10 @@ class Validated: @attrs.define class ValidatedOptionalOverTuple: num: int | None = attrs.field( - validator=attrs.validators.optional( - (attrs.validators.instance_of(int), attrs.validators.ge(0)) - ) - ) + validator=attrs.validators.optional( + (attrs.validators.instance_of(int), attrs.validators.ge(0)) + ) + ) @attrs.define From c1129cc578d0742c57bca719189e95384d5313a6 Mon Sep 17 00:00:00 2001 From: Finite State Machine Date: Sat, 3 Jan 2026 19:38:37 -0500 Subject: [PATCH 5/8] Work around `ty` false positive `invalid-argument-type` --- typing-examples/baseline.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/typing-examples/baseline.py b/typing-examples/baseline.py index 15542cab7..3f712c763 100644 --- a/typing-examples/baseline.py +++ b/typing-examples/baseline.py @@ -98,7 +98,11 @@ class Validated: class ValidatedOptionalOverTuple: num: int | None = attrs.field( validator=attrs.validators.optional( - (attrs.validators.instance_of(int), attrs.validators.ge(0)) + (attrs.validators.instance_of(int), attrs.validators.ge(int(0))) + # `int(0)` needed to avoid false positive + # `invalid-argument-type` when checking with + # ty 0.0.8 (aa7559db8 2025-12-29): ty assumes `ge`'s + # specialization is `Literal[0]` rather than `int` ) ) From 5b413827365559bac9cb7b72e0b75a0ce4328772 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 4 Jan 2026 00:42:54 +0000 Subject: [PATCH 6/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- typing-examples/baseline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typing-examples/baseline.py b/typing-examples/baseline.py index 3f712c763..4a666dd8e 100644 --- a/typing-examples/baseline.py +++ b/typing-examples/baseline.py @@ -98,7 +98,7 @@ class Validated: class ValidatedOptionalOverTuple: num: int | None = attrs.field( validator=attrs.validators.optional( - (attrs.validators.instance_of(int), attrs.validators.ge(int(0))) + (attrs.validators.instance_of(int), attrs.validators.ge(0)) # `int(0)` needed to avoid false positive # `invalid-argument-type` when checking with # ty 0.0.8 (aa7559db8 2025-12-29): ty assumes `ge`'s From 849dbeecfa38c22dd774bf8642edfcff37e6e25f Mon Sep 17 00:00:00 2001 From: Finite State Machine Date: Sat, 3 Jan 2026 20:03:49 -0500 Subject: [PATCH 7/8] Revert "fixes" from pre-commit.com hooks [skip pre-commit.ci] This reverts commit 5b413827365559bac9cb7b72e0b75a0ce4328772. --- typing-examples/baseline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typing-examples/baseline.py b/typing-examples/baseline.py index 4a666dd8e..3f712c763 100644 --- a/typing-examples/baseline.py +++ b/typing-examples/baseline.py @@ -98,7 +98,7 @@ class Validated: class ValidatedOptionalOverTuple: num: int | None = attrs.field( validator=attrs.validators.optional( - (attrs.validators.instance_of(int), attrs.validators.ge(0)) + (attrs.validators.instance_of(int), attrs.validators.ge(int(0))) # `int(0)` needed to avoid false positive # `invalid-argument-type` when checking with # ty 0.0.8 (aa7559db8 2025-12-29): ty assumes `ge`'s From 3e55d900046edb09dce22cd93d1ef6bb73cdbdfa Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 4 Jan 2026 01:04:43 +0000 Subject: [PATCH 8/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- typing-examples/baseline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typing-examples/baseline.py b/typing-examples/baseline.py index 3f712c763..4a666dd8e 100644 --- a/typing-examples/baseline.py +++ b/typing-examples/baseline.py @@ -98,7 +98,7 @@ class Validated: class ValidatedOptionalOverTuple: num: int | None = attrs.field( validator=attrs.validators.optional( - (attrs.validators.instance_of(int), attrs.validators.ge(int(0))) + (attrs.validators.instance_of(int), attrs.validators.ge(0)) # `int(0)` needed to avoid false positive # `invalid-argument-type` when checking with # ty 0.0.8 (aa7559db8 2025-12-29): ty assumes `ge`'s