diff --git a/hypothesis-python/RELEASE.rst b/hypothesis-python/RELEASE.rst new file mode 100644 index 0000000000..5908aedd89 --- /dev/null +++ b/hypothesis-python/RELEASE.rst @@ -0,0 +1,8 @@ +RELEASE_TYPE: patch + +This patch improves :func:`~hypothesis.strategies.builds` and +:func:`~hypothesis.strategies.from_type` support for explicitly defined ``__signature__`` +attributes, from :ref:`version 5.8.3 `, to support generic types from the +:mod:`python:typing` module. + +Thanks to Rónán Carrigan for identifying and fixing this problem! diff --git a/hypothesis-python/src/hypothesis/internal/compat.py b/hypothesis-python/src/hypothesis/internal/compat.py index 4d968ff51f..c1b5e6be03 100644 --- a/hypothesis-python/src/hypothesis/internal/compat.py +++ b/hypothesis-python/src/hypothesis/internal/compat.py @@ -130,12 +130,14 @@ def get_type_hints(thing): # comprehensive type information from get_type_hints # See https://github.com/HypothesisWorks/hypothesis/pull/2580 # for more details. + from hypothesis.strategies._internal.types import is_a_type + spec = inspect.getfullargspec(thing) hints.update( { k: v for k, v in spec.annotations.items() - if k in (spec.args + spec.kwonlyargs) and isinstance(v, type) + if k in (spec.args + spec.kwonlyargs) and is_a_type(v) } ) except (AttributeError, TypeError, NameError): diff --git a/hypothesis-python/tests/nocover/test_build_signature.py b/hypothesis-python/tests/nocover/test_build_signature.py index dc57f984f5..d55bd4c24d 100644 --- a/hypothesis-python/tests/nocover/test_build_signature.py +++ b/hypothesis-python/tests/nocover/test_build_signature.py @@ -14,7 +14,7 @@ # END HEADER from inspect import signature -from typing import get_type_hints +from typing import List, get_type_hints from hypothesis import given, strategies as st @@ -53,7 +53,9 @@ def use_annotations( pass -def use_signature(self, testA: int, testB: str = None, *, testX: float, testY: str): +def use_signature( + self, testA: int, testB: str = None, *, testX: float, testY: List[str] +): pass @@ -66,9 +68,28 @@ def __init__(self, **kwargs): assert set(kwargs) == {"testA", "testX", "testY"} assert isinstance(kwargs["testA"], int) assert isinstance(kwargs["testX"], float) - assert isinstance(kwargs["testY"], str) + assert isinstance(kwargs["testY"], list) + assert all(isinstance(elem, str) for elem in kwargs["testY"]) @given(st.builds(ModelWithAlias)) def test_build_using_different_signature_and_annotations(val): assert isinstance(val, ModelWithAlias) + + +def use_bad_signature(self, testA: 1, *, testX: float): + pass + + +class ModelWithBadAliasSignature: + __annotations__ = get_type_hints(use_annotations) + __signature__ = signature(use_bad_signature) + + def __init__(self, **kwargs): + assert set(kwargs) == {"testX"} + assert isinstance(kwargs["testX"], float) + + +@given(st.builds(ModelWithBadAliasSignature)) +def test_build_with_non_types_in_signature(val): + assert isinstance(val, ModelWithBadAliasSignature)