Skip to content

Commit

Permalink
Resolves strategies from Annotated type
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn committed Sep 3, 2021
1 parent c4fb7c2 commit 591031c
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 1 deletion.
4 changes: 4 additions & 0 deletions hypothesis-python/RELEASE.rst
@@ -0,0 +1,4 @@
RELEASE_TYPE: minor

Adds ability to specify hypothesis strategies in :obj:`python:typing.Annotated`.
Example: ``PositiveInt = Annotated[int, st.integer(min_value=1)]``
18 changes: 18 additions & 0 deletions hypothesis-python/src/hypothesis/strategies/_internal/types.py
Expand Up @@ -171,6 +171,21 @@ def is_annotated_type(thing):
)


def find_annotated_strategy(annotated_type): # pragma: no cover
flattened_meta = []

all_args = (
*getattr(annotated_type, "__args__", ()),
*getattr(annotated_type, "__metadata__", ()),
)
for arg in all_args:
if is_annotated_type(arg):
flattened_meta.append(find_annotated_strategy(arg))
if isinstance(arg, st.SearchStrategy):
flattened_meta.append(arg)
return flattened_meta[-1] if flattened_meta else None


def has_type_arguments(type_):
"""Decides whethere or not this type has applied type arguments."""
args = getattr(type_, "__args__", None)
Expand Down Expand Up @@ -258,6 +273,9 @@ def from_typing_type(thing):
return st.sampled_from(literals)
if is_annotated_type(thing): # pragma: no cover
# This requires Python 3.9+ or the typing_extensions package
annotated_strategy = find_annotated_strategy(thing)
if annotated_strategy is not None:
return annotated_strategy
args = thing.__args__
assert args, "it's impossible to make an annotated type with no args"
annotated_type = args[0]
Expand Down
36 changes: 35 additions & 1 deletion hypothesis-python/tests/cover/test_lookup_py39.py
Expand Up @@ -18,7 +18,7 @@

import pytest

from hypothesis import strategies as st
from hypothesis import given, strategies as st
from hypothesis.errors import InvalidArgument


Expand All @@ -40,6 +40,40 @@ def test_typing_Annotated(annotated_type, expected_strategy_repr):
assert repr(st.from_type(annotated_type)) == expected_strategy_repr


PositiveInt = typing.Annotated[int, st.integers(min_value=1)]
MoreThenTenInt = typing.Annotated[PositiveInt, st.integers(min_value=10 + 1)]
WithTwoStrategies = typing.Annotated[int, st.integers(), st.none()]
ExtraAnnotationNoStrategy = typing.Annotated[PositiveInt, "metadata"]


def arg_positive(x: PositiveInt):
assert x > 0


def arg_more_than_ten(x: MoreThenTenInt):
assert x > 10


@given(st.data())
def test_annotated_positive_int(data):
data.draw(st.builds(arg_positive))


@given(st.data())
def test_annotated_more_than_ten(data):
data.draw(st.builds(arg_more_than_ten))


@given(st.data())
def test_annotated_with_two_strategies(data):
assert data.draw(st.from_type(WithTwoStrategies)) is None


@given(st.data())
def test_annotated_extra_metadata(data):
assert data.draw(st.from_type(ExtraAnnotationNoStrategy)) > 0


@dataclasses.dataclass
class User:
id: int
Expand Down
34 changes: 34 additions & 0 deletions hypothesis-python/tests/typing_extensions/test_backported_types.py
Expand Up @@ -97,3 +97,37 @@ def test_defaultdict(ex):
)
def test_typing_extensions_Annotated(annotated_type, expected_strategy_repr):
assert repr(st.from_type(annotated_type)) == expected_strategy_repr


PositiveInt = Annotated[int, st.integers(min_value=1)]
MoreThenTenInt = Annotated[PositiveInt, st.integers(min_value=10 + 1)]
WithTwoStrategies = Annotated[int, st.integers(), st.none()]
ExtraAnnotationNoStrategy = Annotated[PositiveInt, "metadata"]


def arg_positive(x: PositiveInt):
assert x > 0


def arg_more_than_ten(x: MoreThenTenInt):
assert x > 10


@given(st.data())
def test_annotated_positive_int(data):
data.draw(st.builds(arg_positive))


@given(st.data())
def test_annotated_more_than_ten(data):
data.draw(st.builds(arg_more_than_ten))


@given(st.data())
def test_annotated_with_two_strategies(data):
assert data.draw(st.from_type(WithTwoStrategies)) is None


@given(st.data())
def test_annotated_extra_metadata(data):
assert data.draw(st.from_type(ExtraAnnotationNoStrategy)) > 0

0 comments on commit 591031c

Please sign in to comment.