From 8f4a5db16f7ae4d233bb487f400ac980c43a92b1 Mon Sep 17 00:00:00 2001 From: Buck Evan Date: Wed, 25 Jun 2025 13:54:28 -0500 Subject: [PATCH] improved typehinting for described_as --- src/hamcrest/core/core/described_as.py | 14 ++++++++------ tests/type-hinting/core/core/test_described_as.yml | 7 +++++++ 2 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 tests/type-hinting/core/core/test_described_as.yml diff --git a/src/hamcrest/core/core/described_as.py b/src/hamcrest/core/core/described_as.py index dba2b5cc..4135494a 100644 --- a/src/hamcrest/core/core/described_as.py +++ b/src/hamcrest/core/core/described_as.py @@ -1,5 +1,5 @@ import re -from typing import Any, Optional, Tuple +from typing import Optional, Tuple, TypeVar from hamcrest.core.base_matcher import BaseMatcher from hamcrest.core.description import Description @@ -11,19 +11,21 @@ ARG_PATTERN = re.compile("%([0-9]+)") +T = TypeVar("T") -class DescribedAs(BaseMatcher[Any]): + +class DescribedAs(BaseMatcher[T]): def __init__( - self, description_template: str, matcher: Matcher[Any], *values: Tuple[Any, ...] + self, description_template: str, matcher: Matcher[T], *values: Tuple[T, ...] ) -> None: self.template = description_template self.matcher = matcher self.values = values - def matches(self, item: Any, mismatch_description: Optional[Description] = None) -> bool: + def matches(self, item: T, mismatch_description: Optional[Description] = None) -> bool: return self.matcher.matches(item, mismatch_description) - def describe_mismatch(self, item: Any, mismatch_description: Description) -> None: + def describe_mismatch(self, item: T, mismatch_description: Description) -> None: self.matcher.describe_mismatch(item, mismatch_description) def describe_to(self, description: Description) -> None: @@ -38,7 +40,7 @@ def describe_to(self, description: Description) -> None: description.append_text(self.template[text_start:]) -def described_as(description: str, matcher: Matcher[Any], *values) -> Matcher[Any]: +def described_as(description: str, matcher: Matcher[T], *values) -> Matcher[T]: """Adds custom failure description to a given matcher. :param description: Overrides the matcher's description. diff --git a/tests/type-hinting/core/core/test_described_as.yml b/tests/type-hinting/core/core/test_described_as.yml new file mode 100644 index 00000000..a3ce6c55 --- /dev/null +++ b/tests/type-hinting/core/core/test_described_as.yml @@ -0,0 +1,7 @@ +- case: described_as + # pypy + mypy doesn't work. See https://foss.heptapod.net/pypy/pypy/-/issues/3526 + skip: platform.python_implementation() == "PyPy" + main: | + from hamcrest import is_, described_as + + reveal_type(described_as("oh, hi", is_(98))) # N: Revealed type is "hamcrest.core.matcher.Matcher[builtins.int]"