diff --git a/changelog.d/207.bugfix.rst b/changelog.d/207.bugfix.rst new file mode 100644 index 00000000..ecc2dac0 --- /dev/null +++ b/changelog.d/207.bugfix.rst @@ -0,0 +1 @@ +``has_properties`` now returns ``Matcher[Any]`` type, which addresses type checking errors when nested as a matcher. diff --git a/setup.py b/setup.py index b3ea6541..97057a9c 100755 --- a/setup.py +++ b/setup.py @@ -42,6 +42,8 @@ def read(fname): # Can't use 0.940: https://github.com/python/mypy/issues/12339 "mypy!=0.940; platform_python_implementation != 'PyPy'", "types-mock", + "dataclasses; python_version<'3.7'", + "types-dataclasses; python_version<'3.7'", ] TESTS_NUMPY = ["numpy"] DEV_TOOLS = [ diff --git a/src/hamcrest/__init__.py b/src/hamcrest/__init__.py index b307591c..90573fe3 100644 --- a/src/hamcrest/__init__.py +++ b/src/hamcrest/__init__.py @@ -2,7 +2,7 @@ from hamcrest.library import * from hamcrest import core, library -__version__ = "2.0.3" +__version__ = "2.0.4" __author__ = "Chris Rose" __copyright__ = "Copyright 2020 hamcrest.org" __license__ = "BSD, see License.txt" diff --git a/src/hamcrest/core/assert_that.py b/src/hamcrest/core/assert_that.py index c8882bb5..a31cf3a1 100644 --- a/src/hamcrest/core/assert_that.py +++ b/src/hamcrest/core/assert_that.py @@ -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.) @@ -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: diff --git a/src/hamcrest/library/object/hasproperty.py b/src/hamcrest/library/object/hasproperty.py index b27036dc..ea2519f7 100644 --- a/src/hamcrest/library/object/hasproperty.py +++ b/src/hamcrest/library/object/hasproperty.py @@ -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]: ... diff --git a/tests/type-hinting/library/collection/test_empty.yml b/tests/type-hinting/library/collection/test_empty.yml index 5264802c..cf815c72 100644 --- a/tests/type-hinting/library/collection/test_empty.yml +++ b/tests/type-hinting/library/collection/test_empty.yml @@ -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" \ No newline at end of file + assert_that(99, empty()) # E: Cannot infer type argument 1 of "assert_that" diff --git a/tests/type-hinting/library/collection/test_generics.yml b/tests/type-hinting/library/collection/test_generics.yml new file mode 100644 index 00000000..4666d5f1 --- /dev/null +++ b/tests/type-hinting/library/collection/test_generics.yml @@ -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)