Skip to content

Commit

Permalink
Merge pull request #2117 from Zac-HD/stricter-types
Browse files Browse the repository at this point in the history
More precise strategy type-hints
  • Loading branch information
Zac-HD committed Oct 9, 2019
2 parents 4a70818 + b183b9c commit 017ca45
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 18 deletions.
9 changes: 9 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
RELEASE_TYPE: patch

This patch improves our type hints on the :func:`~hypothesis.strategies.emails`,
:func:`~hypothesis.strategies.functions`, :func:`~hypothesis.strategies.integers`,
:func:`~hypothesis.strategies.iterables`, and :func:`~hypothesis.strategies.slices`
strategies, as well as the ``.filter()`` method.

There is no runtime change, but if you use :pypi:`mypy` or a similar
type-checker on your tests the results will be a bit more precise.
30 changes: 20 additions & 10 deletions hypothesis-python/src/hypothesis/_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@
import random # noqa
from types import ModuleType # noqa
from typing import Any, Dict, Union, Sequence, Callable, Pattern # noqa
from typing import TypeVar, Tuple, List, Set, FrozenSet, overload # noqa
from typing import TypeVar, Tuple, Iterable, List, Set, FrozenSet, overload # noqa
from typing import Type, Text, AnyStr, Optional # noqa

from hypothesis.utils.conventions import InferType # noqa
Expand Down Expand Up @@ -341,7 +341,7 @@ def one_of(*args):
@cacheable
@defines_strategy_with_reusable_values
def integers(min_value=None, max_value=None):
# type: (Real, Real) -> SearchStrategy[int]
# type: (int, int) -> SearchStrategy[int]
"""Returns a strategy which generates integers; in Python 2 these may be
ints or longs.
Expand Down Expand Up @@ -850,7 +850,14 @@ def __repr__(self):


@defines_strategy
def iterables(elements, min_size=0, max_size=None, unique_by=None, unique=False):
def iterables(
elements, # type: SearchStrategy[Ex]
min_size=0, # type: int
max_size=None, # type: int
unique_by=None, # type: Union[Callable, Tuple[Callable, ...]]
unique=False, # type: bool
):
# type: (...) -> SearchStrategy[Iterable[Ex]]
"""This has the same behaviour as lists, but returns iterables instead.
Some iterables cannot be indexed (e.g. sets) and some do not have a
Expand Down Expand Up @@ -2081,6 +2088,7 @@ def do_draw(self, data):

@defines_strategy_with_reusable_values
def runner(default=not_set):
# type: (Any) -> SearchStrategy[Any]
"""A strategy for getting "the current test runner", whatever that may be.
The exact meaning depends on the entry point, but it will usually be the
associated 'self' value for it.
Expand Down Expand Up @@ -2239,6 +2247,7 @@ def deferred(definition):

@defines_strategy_with_reusable_values
def emails():
# type: () -> SearchStrategy[Text]
"""A strategy for generating email addresses as unicode strings. The
address format is specified in :rfc:`5322#section-3.4.1`. Values shrink
towards shorter local-parts and host domains.
Expand All @@ -2256,14 +2265,14 @@ def emails():
)


# Mypy can't yet handle default values with generic types or typevars, but the
# @overload workaround from https://github.com/python/mypy/issues/3737 doesn't
# work with @composite functions - Mypy can't see that the function implements
# `(Any, Callable[..., T], SearchStrategy[T]) -> Callable[..., T]`


@defines_strategy
def functions(like=lambda: None, returns=none()):
def functions(
like=lambda: None, # type: Callable[..., Any]
returns=none(), # type: SearchStrategy[Any]
):
# type: (...) -> SearchStrategy[Callable[..., Any]]
# The proper type signature of `functions()` would have T instead of Any, but mypy
# disallows default args for generics: https://github.com/python/mypy/issues/3737
"""A strategy for functions, which can be used in callbacks.
The generated functions will mimic the interface of ``like``, which must
Expand All @@ -2288,6 +2297,7 @@ def functions(like=lambda: None, returns=none()):

@composite
def slices(draw, size):
# type: (Any, int) -> slice
"""Generates slices that will select indices up to the supplied size
Generated slices will have start and stop indices that range from 0 to size - 1
Expand Down
6 changes: 3 additions & 3 deletions hypothesis-python/src/hypothesis/extra/django/_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@

if False:
from datetime import tzinfo # noqa
from typing import Any, Type, Optional, List, Text, Callable, Union # noqa
from typing import Any, Dict, Type, Optional, List, Text, Callable, Union # noqa


# Mapping of field types, to strategy objects or functions of (type) -> strategy
Expand All @@ -66,7 +66,7 @@
df.NullBooleanField: st.one_of(st.none(), st.booleans()),
df.URLField: urls(),
df.UUIDField: st.uuids(),
}
} # type: Dict[Any, Union[st.SearchStrategy, Callable[[Any], st.SearchStrategy]]]


def register_for(field_type):
Expand Down Expand Up @@ -265,7 +265,7 @@ def from_field(field):
if getattr(field, "null", False):
return st.none()
raise InvalidArgument("Could not infer a strategy for %r", (field,))
strategy = _global_field_lookup[type(field)]
strategy = _global_field_lookup[type(field)] # type: ignore
if not isinstance(strategy, st.SearchStrategy):
strategy = strategy(field)
assert isinstance(strategy, st.SearchStrategy)
Expand Down
3 changes: 2 additions & 1 deletion hypothesis-python/src/hypothesis/extra/pandas/impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def is_categorical_dtype(dt):


if False:
from typing import Any, Union, Sequence, Set # noqa
from typing import Any, List, Union, Sequence, Set # noqa
from hypothesis.searchstrategy.strategies import Ex # noqa


Expand Down Expand Up @@ -341,6 +341,7 @@ def columns(
fill=None, # type: st.SearchStrategy[Ex]
unique=False, # type: bool
):
# type: (...) -> List[column]
"""A convenience function for producing a list of :class:`column` objects
of the same general shape.
Expand Down
4 changes: 2 additions & 2 deletions hypothesis-python/src/hypothesis/searchstrategy/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
from hypothesis.utils.conventions import UniqueIdentifier

try:
from typing import List, Callable, TypeVar, Generic, Optional # noqa
from typing import Any, List, Callable, TypeVar, Generic, Optional # noqa

Ex = TypeVar("Ex", covariant=True)
T = TypeVar("T")
Expand Down Expand Up @@ -348,7 +348,7 @@ def flatmap(self, expand):
return FlatMapStrategy(expand=expand, strategy=self)

def filter(self, condition):
# type: (Callable[[Ex], bool]) -> SearchStrategy[Ex]
# type: (Callable[[Ex], Any]) -> SearchStrategy[Ex]
"""Returns a new strategy that generates values from this strategy
which satisfy the provided condition. Note that if the condition is too
hard to satisfy this might result in your tests failing with
Expand Down
4 changes: 2 additions & 2 deletions whole-repo-tests/test_type_hints.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def get_mypy_analysed_type(fname, val):
encoding="utf-8",
universal_newlines=True,
# We set the MYPYPATH explicitly, because PEP561 discovery wasn't
# working in CI as of mypy==0.600 - hopefully a temporary workaround.
# working in CI as of mypy==0.730 - hopefully a temporary workaround.
env=dict(os.environ, MYPYPATH=PYTHON_SRC),
).stdout.read()
assert len(out.splitlines()) == 1
Expand Down Expand Up @@ -83,7 +83,7 @@ def test_revealed_types(tmpdir, val, expect):


def test_data_object_type_tracing(tmpdir):
f = tmpdir.join("chech_mypy_on_st_data.py")
f = tmpdir.join("check_mypy_on_st_data.py")
f.write(
"from hypothesis.strategies import data, integers\n"
"d = data().example()\n"
Expand Down

0 comments on commit 017ca45

Please sign in to comment.