Skip to content

Commit

Permalink
fix: provide correct type bounds on sequence matchers
Browse files Browse the repository at this point in the history
fixes hamcrest#207

`Matcher[object]` is too tight of a bound on the output type; what we're
asserting is that a matcher for `Any` type is what `has_properties` will
do
  • Loading branch information
offbyone committed Aug 6, 2022
1 parent 5622f04 commit 7209bca
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 11 deletions.
1 change: 1 addition & 0 deletions changelog.d/207.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
``has_properties`` now returns ``Matcher[Any]`` type, which addresses type checking errors when nested as a matcher.
14 changes: 7 additions & 7 deletions src/hamcrest/core/assert_that.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@


@overload
def assert_that(actual: T, matcher: Matcher[T], reason: str = "") -> None:
def assert_that(actual_or_assertion: T, matcher: Matcher[T], reason: str = "", /) -> None:
...


@overload
def assert_that(assertion: bool, reason: str = "") -> None:
def assert_that(actual_or_assertion: bool, reason: str = "", /) -> None:
...


def assert_that(actual, matcher=None, reason=""):
def assert_that(actual_or_assertion, matcher=None, reason=""):
"""Asserts that actual value satisfies matcher. (Can also assert plain
boolean condition.)
Expand Down Expand Up @@ -55,11 +55,11 @@ def assert_that(actual, matcher=None, reason=""):
"""
if isinstance(matcher, Matcher):
_assert_match(actual=actual, matcher=matcher, reason=reason)
_assert_match(actual=actual_or_assertion, matcher=matcher, reason=reason)
else:
if isinstance(actual, Matcher):
warnings.warn("arg1 should be boolean, but was {}".format(type(actual)))
_assert_bool(assertion=cast(bool, actual), reason=cast(str, matcher))
if isinstance(actual_or_assertion, Matcher):
warnings.warn("arg1 should be boolean, but was {}".format(type(actual_or_assertion)))
_assert_bool(assertion=cast(bool, actual_or_assertion), reason=cast(str, matcher))


def _assert_match(actual: T, matcher: Matcher[T], reason: str) -> None:
Expand Down
6 changes: 3 additions & 3 deletions src/hamcrest/library/object/hasproperty.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,19 +94,19 @@ def has_property(name: str, match: Union[None, Matcher[V], V] = None) -> Matcher

# Keyword argument form
@overload
def has_properties(**keys_valuematchers: Union[Matcher[V], V]) -> Matcher[object]:
def has_properties(**keys_valuematchers: Union[Matcher[V], V]) -> Matcher[Any]:
...


# Name to matcher dict form
@overload
def has_properties(keys_valuematchers: Mapping[str, Union[Matcher[V], V]]) -> Matcher[object]:
def has_properties(keys_valuematchers: Mapping[str, Union[Matcher[V], V]]) -> Matcher[Any]:
...


# Alternating name/matcher form
@overload
def has_properties(*keys_valuematchers: Any) -> Matcher[object]:
def has_properties(*keys_valuematchers: Any) -> Matcher[Any]:
...


Expand Down
2 changes: 1 addition & 1 deletion tests/type-hinting/library/collection/test_empty.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
from hamcrest import assert_that, is_, empty
assert_that([], empty())
assert_that(99, empty()) # E: Cannot infer type argument 1 of "assert_that"
assert_that(99, empty()) # E: Cannot infer type argument 1 of "assert_that"
27 changes: 27 additions & 0 deletions tests/type-hinting/library/collection/test_generics.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
- case: valid_has_items_has_properties
skip: platform.python_implementation() == "PyPy"
main: |
from dataclasses import dataclass
from hamcrest import assert_that, has_items, has_properties
@dataclass
class Example:
name: str
items = [Example("dave"), Example("wave")]
a = assert_that(items, has_items(has_properties(name="dave")))
- case: valid_has_item_has_properties
skip: platform.python_implementation() == "PyPy"
main: |
from dataclasses import dataclass
from hamcrest import assert_that, has_item, has_properties
@dataclass
class Example:
name: str
items = [Example("dave"), Example("wave")]
matcher = has_item(has_properties(name="dave"))
a = assert_that(items, matcher)

0 comments on commit 7209bca

Please sign in to comment.