Skip to content

Commit

Permalink
Merge pull request #3297 from HypothesisWorks/given-overloads
Browse files Browse the repository at this point in the history
Update annotations for `@given`
  • Loading branch information
Zac-HD authored Apr 18, 2022
2 parents ab8276d + 2b05c6f commit 68bd50f
Show file tree
Hide file tree
Showing 18 changed files with 136 additions and 26 deletions.
5 changes: 5 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
RELEASE_TYPE: patch

This patch updates the type annotations for :func:`@given <hypothesis.given>`
so that type-checkers will warn on mixed positional and keyword arguments,
as well as fixing :issue:`3296`.
23 changes: 21 additions & 2 deletions hypothesis-python/src/hypothesis/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
Optional,
TypeVar,
Union,
overload,
)
from unittest import TestCase

Expand Down Expand Up @@ -970,9 +971,27 @@ def fuzz_one_input(
return self.__cached_target


@overload
def given(
*_given_arguments: Union[SearchStrategy[Ex], InferType],
**_given_kwargs: Union[SearchStrategy[Ex], InferType],
*_given_arguments: Union[SearchStrategy[Any], InferType],
) -> Callable[
[Callable[..., Union[None, Coroutine[Any, Any, None]]]], Callable[..., None]
]: # pragma: no cover
...


@overload
def given(
**_given_kwargs: Union[SearchStrategy[Any], InferType],
) -> Callable[
[Callable[..., Union[None, Coroutine[Any, Any, None]]]], Callable[..., None]
]: # pragma: no cover
...


def given(
*_given_arguments: Union[SearchStrategy[Any], InferType],
**_given_kwargs: Union[SearchStrategy[Any], InferType],
) -> Callable[
[Callable[..., Union[None, Coroutine[Any, Any, None]]]], Callable[..., None]
]:
Expand Down
1 change: 1 addition & 0 deletions hypothesis-python/src/hypothesis/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at https://mozilla.org/MPL/2.0/.


class HypothesisException(Exception):
"""Generic parent class for exceptions thrown by Hypothesis."""

Expand Down
1 change: 1 addition & 0 deletions hypothesis-python/src/hypothesis/executors.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at https://mozilla.org/MPL/2.0/.


def default_executor(function): # pragma: nocover
raise NotImplementedError() # We don't actually use this any more

Expand Down
2 changes: 1 addition & 1 deletion hypothesis-python/src/hypothesis/internal/escalation.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def get_trimmed_traceback(exception=None):
return tb
while tb.tb_next is not None and (
# If the frame is from one of our files, it's been added by Hypothesis.
is_hypothesis_file(getframeinfo(tb.tb_frame)[0])
is_hypothesis_file(getframeinfo(tb.tb_frame).filename)
# But our `@proxies` decorator overrides the source location,
# so we check for an attribute it injects into the frame too.
or tb.tb_frame.f_globals.get("__hypothesistracebackhide__") is True
Expand Down
1 change: 1 addition & 0 deletions hypothesis-python/src/hypothesis/internal/intervalsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at https://mozilla.org/MPL/2.0/.


class IntervalSet:
def __init__(self, intervals):
self.intervals = tuple(intervals)
Expand Down
1 change: 1 addition & 0 deletions hypothesis-python/src/hypothesis/internal/lazyformat.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at https://mozilla.org/MPL/2.0/.


class lazyformat:
"""A format string that isn't evaluated until it's needed."""

Expand Down
14 changes: 10 additions & 4 deletions hypothesis-python/src/hypothesis/strategies/_internal/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,6 @@
except ImportError: # < py3.8
Protocol = object # type: ignore[assignment]

UniqueBy = Union[Callable[[Ex], Hashable], Tuple[Callable[[Ex], Hashable], ...]]


@cacheable
@defines_strategy()
Expand Down Expand Up @@ -205,7 +203,11 @@ def lists(
*,
min_size: int = 0,
max_size: Optional[int] = None,
unique_by: Optional[UniqueBy] = None,
unique_by: Union[
None,
Callable[[Ex], Hashable],
Tuple[Callable[[Ex], Hashable], ...],
] = None,
unique: bool = False,
) -> SearchStrategy[List[Ex]]:
"""Returns a list containing values drawn from elements with length in the
Expand Down Expand Up @@ -389,7 +391,11 @@ def iterables(
*,
min_size: int = 0,
max_size: Optional[int] = None,
unique_by: Optional[UniqueBy] = None,
unique_by: Union[
None,
Callable[[Ex], Hashable],
Tuple[Callable[[Ex], Hashable], ...],
] = None,
unique: bool = False,
) -> SearchStrategy[Iterable[Ex]]:
"""This has the same behaviour as lists, but returns iterables instead.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
# that this value can't be reassigned.
NON_RUNTIME_TYPES = frozenset(
(
typing.Any,
*ClassVarTypes,
*TypeAliasTypes,
*FinalTypes,
Expand Down
1 change: 1 addition & 0 deletions hypothesis-python/src/hypothesis/utils/conventions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at https://mozilla.org/MPL/2.0/.


class UniqueIdentifier:
"""A factory for sentinel objects with nice reprs."""

Expand Down
5 changes: 1 addition & 4 deletions hypothesis-python/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,9 @@
collect_ignore_glob.append("cover/test_asyncio.py") # @asyncio.coroutine removed

assert sys.version_info.releaselevel == "alpha"
# These seem to fail due to traceback rendering failures, TODO fix the tests
# TODO: our traceback elision doesn't work with Python 3.11's nice new format yet
collect_ignore_glob.append("cover/test_traceback_elision.py")
collect_ignore_glob.append("pytest/test_capture.py")
# Changes to type-annotation inspection, TODO fix during the beta phase
collect_ignore_glob.append("cover/test_lookup.py")
collect_ignore_glob.append("cover/test_lookup_py37.py")


def pytest_configure(config):
Expand Down
7 changes: 6 additions & 1 deletion hypothesis-python/tests/cover/test_lookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import io
import re
import string
import sys
import typing
from inspect import signature
from numbers import Real
Expand Down Expand Up @@ -770,7 +771,11 @@ def test_compat_get_type_hints_aware_of_None_default():
find_any(strategy, lambda x: x.a is None)
find_any(strategy, lambda x: x.a is not None)

assert typing.get_type_hints(constructor)["a"] == typing.Optional[str]
if sys.version_info[:2] >= (3, 11):
# https://docs.python.org/3.11/library/typing.html#typing.get_type_hints
assert typing.get_type_hints(constructor)["a"] == str
else:
assert typing.get_type_hints(constructor)["a"] == typing.Optional[str]
assert inspect.signature(constructor).parameters["a"].annotation == str


Expand Down
4 changes: 4 additions & 0 deletions hypothesis-python/tests/cover/test_lookup_py37.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ def test_resolving_standard_callable_ellipsis(x: collections.abc.Callable[..., E
assert isinstance(x(1, 2, 3, a=4, b=5, c=6), Elem)


@pytest.mark.skipif(
sys.version_info[:3] == (3, 11, 0),
reason="https://github.com/python/cpython/issues/91621",
)
@given(...)
def test_resolving_standard_callable_no_args(x: collections.abc.Callable[[], Elem]):
assert isinstance(x, collections.abc.Callable)
Expand Down
2 changes: 1 addition & 1 deletion requirements/coverage.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ py==1.11.0
# via
# pytest
# pytest-forked
pyparsing==3.0.7
pyparsing==3.0.8
# via packaging
pytest==7.1.1
# via
Expand Down
2 changes: 1 addition & 1 deletion requirements/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ py==1.11.0
# via
# pytest
# pytest-forked
pyparsing==3.0.7
pyparsing==3.0.8
# via packaging
pytest==7.1.1
# via
Expand Down
30 changes: 20 additions & 10 deletions requirements/tools.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
#
alabaster==0.7.12
# via sphinx
appnope==0.1.3
# via ipython
asgiref==3.5.0
# via django
astor==0.8.1
Expand Down Expand Up @@ -37,6 +35,8 @@ bleach==5.0.0
# via readme-renderer
certifi==2021.10.8
# via requests
cffi==1.15.0
# via cryptography
charset-normalizer==2.0.12
# via requests
click==8.1.2
Expand All @@ -51,11 +51,13 @@ commonmark==0.9.1
# via rich
coverage==6.3.2
# via -r requirements/tools.in
cryptography==36.0.2
# via secretstorage
decorator==5.1.1
# via ipython
distlib==0.3.4
# via virtualenv
django==4.0.3
django==4.0.4
# via -r requirements/tools.in
docutils==0.17.1
# via
Expand Down Expand Up @@ -144,6 +146,10 @@ isort==5.10.1
# via shed
jedi==0.18.1
# via ipython
jeepney==0.8.0
# via
# keyring
# secretstorage
jinja2==3.1.1
# via sphinx
keyring==23.5.0
Expand Down Expand Up @@ -212,6 +218,8 @@ pycodestyle==2.8.0
# via
# flake8
# flake8-bandit
pycparser==2.21
# via cffi
pydocstyle==6.1.1
# via flake8-docstrings
pyflakes==2.4.0
Expand All @@ -224,9 +232,9 @@ pygments==2.11.2
# readme-renderer
# rich
# sphinx
pyparsing==3.0.7
pyparsing==3.0.8
# via packaging
pyright==1.1.235
pyright==1.1.238
# via -r requirements/tools.in
pytest==7.1.1
# via -r requirements/tools.in
Expand Down Expand Up @@ -256,7 +264,9 @@ rfc3986==2.0.0
# via twine
rich==12.2.0
# via twine
shed==0.9.4
secretstorage==3.3.1
# via keyring
shed==0.9.5
# via -r requirements/tools.in
six==1.16.0
# via
Expand All @@ -273,7 +283,7 @@ snowballstemmer==2.2.0
# sphinx
sortedcontainers==2.4.0
# via hypothesis (hypothesis-python/setup.py)
soupsieve==2.3.2
soupsieve==2.3.2.post1
# via beautifulsoup4
sphinx==4.5.0
# via
Expand Down Expand Up @@ -318,7 +328,7 @@ tomli==2.0.1
# mypy
# pep517
# pytest
tox==3.24.5
tox==3.25.0
# via -r requirements/tools.in
traitlets==5.1.1
# via
Expand All @@ -332,7 +342,7 @@ types-pkg-resources==0.1.3
# via -r requirements/tools.in
types-pytz==2021.3.6
# via -r requirements/tools.in
types-redis==4.1.19
types-redis==4.1.21
# via -r requirements/tools.in
typing-extensions==4.1.1
# via
Expand All @@ -349,7 +359,7 @@ urllib3==1.26.9
# via
# requests
# twine
virtualenv==20.14.0
virtualenv==20.14.1
# via tox
wcwidth==0.2.5
# via prompt-toolkit
Expand Down
18 changes: 18 additions & 0 deletions whole-repo-tests/test_mypy.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import os
import subprocess
import textwrap

import pytest

Expand Down Expand Up @@ -343,6 +344,23 @@ def test_stateful_consumed_bundle_cannot_be_target(tmpdir):
assert_mypy_errors(str(f.realpath()), [(3, "call-overload")])


def test_raises_for_mixed_pos_kwargs_in_given(tmpdir):
f = tmpdir.join("raises_for_mixed_pos_kwargs_in_given")
f.write(
textwrap.dedent(
"""
from hypothesis import given
from hypothesis.strategies import text
@given(text(), x=text())
def test_bar(x):
...
"""
)
)
assert_mypy_errors(str(f.realpath()), [(5, "call-overload")])


@pytest.mark.parametrize(
"return_val,errors",
[
Expand Down
Loading

0 comments on commit 68bd50f

Please sign in to comment.