Skip to content

Commit 68bd50f

Browse files
authored
Merge pull request #3297 from HypothesisWorks/given-overloads
Update annotations for `@given`
2 parents ab8276d + 2b05c6f commit 68bd50f

File tree

18 files changed

+136
-26
lines changed

18 files changed

+136
-26
lines changed

hypothesis-python/RELEASE.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
RELEASE_TYPE: patch
2+
3+
This patch updates the type annotations for :func:`@given <hypothesis.given>`
4+
so that type-checkers will warn on mixed positional and keyword arguments,
5+
as well as fixing :issue:`3296`.

hypothesis-python/src/hypothesis/core.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
Optional,
3535
TypeVar,
3636
Union,
37+
overload,
3738
)
3839
from unittest import TestCase
3940

@@ -970,9 +971,27 @@ def fuzz_one_input(
970971
return self.__cached_target
971972

972973

974+
@overload
973975
def given(
974-
*_given_arguments: Union[SearchStrategy[Ex], InferType],
975-
**_given_kwargs: Union[SearchStrategy[Ex], InferType],
976+
*_given_arguments: Union[SearchStrategy[Any], InferType],
977+
) -> Callable[
978+
[Callable[..., Union[None, Coroutine[Any, Any, None]]]], Callable[..., None]
979+
]: # pragma: no cover
980+
...
981+
982+
983+
@overload
984+
def given(
985+
**_given_kwargs: Union[SearchStrategy[Any], InferType],
986+
) -> Callable[
987+
[Callable[..., Union[None, Coroutine[Any, Any, None]]]], Callable[..., None]
988+
]: # pragma: no cover
989+
...
990+
991+
992+
def given(
993+
*_given_arguments: Union[SearchStrategy[Any], InferType],
994+
**_given_kwargs: Union[SearchStrategy[Any], InferType],
976995
) -> Callable[
977996
[Callable[..., Union[None, Coroutine[Any, Any, None]]]], Callable[..., None]
978997
]:

hypothesis-python/src/hypothesis/errors.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
99
# obtain one at https://mozilla.org/MPL/2.0/.
1010

11+
1112
class HypothesisException(Exception):
1213
"""Generic parent class for exceptions thrown by Hypothesis."""
1314

hypothesis-python/src/hypothesis/executors.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
99
# obtain one at https://mozilla.org/MPL/2.0/.
1010

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

hypothesis-python/src/hypothesis/internal/escalation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def get_trimmed_traceback(exception=None):
9494
return tb
9595
while tb.tb_next is not None and (
9696
# If the frame is from one of our files, it's been added by Hypothesis.
97-
is_hypothesis_file(getframeinfo(tb.tb_frame)[0])
97+
is_hypothesis_file(getframeinfo(tb.tb_frame).filename)
9898
# But our `@proxies` decorator overrides the source location,
9999
# so we check for an attribute it injects into the frame too.
100100
or tb.tb_frame.f_globals.get("__hypothesistracebackhide__") is True

hypothesis-python/src/hypothesis/internal/intervalsets.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
99
# obtain one at https://mozilla.org/MPL/2.0/.
1010

11+
1112
class IntervalSet:
1213
def __init__(self, intervals):
1314
self.intervals = tuple(intervals)

hypothesis-python/src/hypothesis/internal/lazyformat.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
99
# obtain one at https://mozilla.org/MPL/2.0/.
1010

11+
1112
class lazyformat:
1213
"""A format string that isn't evaluated until it's needed."""
1314

hypothesis-python/src/hypothesis/strategies/_internal/core.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,6 @@
118118
except ImportError: # < py3.8
119119
Protocol = object # type: ignore[assignment]
120120

121-
UniqueBy = Union[Callable[[Ex], Hashable], Tuple[Callable[[Ex], Hashable], ...]]
122-
123121

124122
@cacheable
125123
@defines_strategy()
@@ -205,7 +203,11 @@ def lists(
205203
*,
206204
min_size: int = 0,
207205
max_size: Optional[int] = None,
208-
unique_by: Optional[UniqueBy] = None,
206+
unique_by: Union[
207+
None,
208+
Callable[[Ex], Hashable],
209+
Tuple[Callable[[Ex], Hashable], ...],
210+
] = None,
209211
unique: bool = False,
210212
) -> SearchStrategy[List[Ex]]:
211213
"""Returns a list containing values drawn from elements with length in the
@@ -389,7 +391,11 @@ def iterables(
389391
*,
390392
min_size: int = 0,
391393
max_size: Optional[int] = None,
392-
unique_by: Optional[UniqueBy] = None,
394+
unique_by: Union[
395+
None,
396+
Callable[[Ex], Hashable],
397+
Tuple[Callable[[Ex], Hashable], ...],
398+
] = None,
393399
unique: bool = False,
394400
) -> SearchStrategy[Iterable[Ex]]:
395401
"""This has the same behaviour as lists, but returns iterables instead.

hypothesis-python/src/hypothesis/strategies/_internal/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
# that this value can't be reassigned.
101101
NON_RUNTIME_TYPES = frozenset(
102102
(
103+
typing.Any,
103104
*ClassVarTypes,
104105
*TypeAliasTypes,
105106
*FinalTypes,

hypothesis-python/src/hypothesis/utils/conventions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
99
# obtain one at https://mozilla.org/MPL/2.0/.
1010

11+
1112
class UniqueIdentifier:
1213
"""A factory for sentinel objects with nice reprs."""
1314

hypothesis-python/tests/conftest.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,9 @@
3838
collect_ignore_glob.append("cover/test_asyncio.py") # @asyncio.coroutine removed
3939

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

4845

4946
def pytest_configure(config):

hypothesis-python/tests/cover/test_lookup.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import io
1818
import re
1919
import string
20+
import sys
2021
import typing
2122
from inspect import signature
2223
from numbers import Real
@@ -770,7 +771,11 @@ def test_compat_get_type_hints_aware_of_None_default():
770771
find_any(strategy, lambda x: x.a is None)
771772
find_any(strategy, lambda x: x.a is not None)
772773

773-
assert typing.get_type_hints(constructor)["a"] == typing.Optional[str]
774+
if sys.version_info[:2] >= (3, 11):
775+
# https://docs.python.org/3.11/library/typing.html#typing.get_type_hints
776+
assert typing.get_type_hints(constructor)["a"] == str
777+
else:
778+
assert typing.get_type_hints(constructor)["a"] == typing.Optional[str]
774779
assert inspect.signature(constructor).parameters["a"].annotation == str
775780

776781

hypothesis-python/tests/cover/test_lookup_py37.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ def test_resolving_standard_callable_ellipsis(x: collections.abc.Callable[..., E
163163
assert isinstance(x(1, 2, 3, a=4, b=5, c=6), Elem)
164164

165165

166+
@pytest.mark.skipif(
167+
sys.version_info[:3] == (3, 11, 0),
168+
reason="https://github.com/python/cpython/issues/91621",
169+
)
166170
@given(...)
167171
def test_resolving_standard_callable_no_args(x: collections.abc.Callable[[], Elem]):
168172
assert isinstance(x, collections.abc.Callable)

requirements/coverage.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ py==1.11.0
6161
# via
6262
# pytest
6363
# pytest-forked
64-
pyparsing==3.0.7
64+
pyparsing==3.0.8
6565
# via packaging
6666
pytest==7.1.1
6767
# via

requirements/test.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ py==1.11.0
2424
# via
2525
# pytest
2626
# pytest-forked
27-
pyparsing==3.0.7
27+
pyparsing==3.0.8
2828
# via packaging
2929
pytest==7.1.1
3030
# via

requirements/tools.txt

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
#
77
alabaster==0.7.12
88
# via sphinx
9-
appnope==0.1.3
10-
# via ipython
119
asgiref==3.5.0
1210
# via django
1311
astor==0.8.1
@@ -37,6 +35,8 @@ bleach==5.0.0
3735
# via readme-renderer
3836
certifi==2021.10.8
3937
# via requests
38+
cffi==1.15.0
39+
# via cryptography
4040
charset-normalizer==2.0.12
4141
# via requests
4242
click==8.1.2
@@ -51,11 +51,13 @@ commonmark==0.9.1
5151
# via rich
5252
coverage==6.3.2
5353
# via -r requirements/tools.in
54+
cryptography==36.0.2
55+
# via secretstorage
5456
decorator==5.1.1
5557
# via ipython
5658
distlib==0.3.4
5759
# via virtualenv
58-
django==4.0.3
60+
django==4.0.4
5961
# via -r requirements/tools.in
6062
docutils==0.17.1
6163
# via
@@ -144,6 +146,10 @@ isort==5.10.1
144146
# via shed
145147
jedi==0.18.1
146148
# via ipython
149+
jeepney==0.8.0
150+
# via
151+
# keyring
152+
# secretstorage
147153
jinja2==3.1.1
148154
# via sphinx
149155
keyring==23.5.0
@@ -212,6 +218,8 @@ pycodestyle==2.8.0
212218
# via
213219
# flake8
214220
# flake8-bandit
221+
pycparser==2.21
222+
# via cffi
215223
pydocstyle==6.1.1
216224
# via flake8-docstrings
217225
pyflakes==2.4.0
@@ -224,9 +232,9 @@ pygments==2.11.2
224232
# readme-renderer
225233
# rich
226234
# sphinx
227-
pyparsing==3.0.7
235+
pyparsing==3.0.8
228236
# via packaging
229-
pyright==1.1.235
237+
pyright==1.1.238
230238
# via -r requirements/tools.in
231239
pytest==7.1.1
232240
# via -r requirements/tools.in
@@ -256,7 +264,9 @@ rfc3986==2.0.0
256264
# via twine
257265
rich==12.2.0
258266
# via twine
259-
shed==0.9.4
267+
secretstorage==3.3.1
268+
# via keyring
269+
shed==0.9.5
260270
# via -r requirements/tools.in
261271
six==1.16.0
262272
# via
@@ -273,7 +283,7 @@ snowballstemmer==2.2.0
273283
# sphinx
274284
sortedcontainers==2.4.0
275285
# via hypothesis (hypothesis-python/setup.py)
276-
soupsieve==2.3.2
286+
soupsieve==2.3.2.post1
277287
# via beautifulsoup4
278288
sphinx==4.5.0
279289
# via
@@ -318,7 +328,7 @@ tomli==2.0.1
318328
# mypy
319329
# pep517
320330
# pytest
321-
tox==3.24.5
331+
tox==3.25.0
322332
# via -r requirements/tools.in
323333
traitlets==5.1.1
324334
# via
@@ -332,7 +342,7 @@ types-pkg-resources==0.1.3
332342
# via -r requirements/tools.in
333343
types-pytz==2021.3.6
334344
# via -r requirements/tools.in
335-
types-redis==4.1.19
345+
types-redis==4.1.21
336346
# via -r requirements/tools.in
337347
typing-extensions==4.1.1
338348
# via
@@ -349,7 +359,7 @@ urllib3==1.26.9
349359
# via
350360
# requests
351361
# twine
352-
virtualenv==20.14.0
362+
virtualenv==20.14.1
353363
# via tox
354364
wcwidth==0.2.5
355365
# via prompt-toolkit

whole-repo-tests/test_mypy.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import os
1212
import subprocess
13+
import textwrap
1314

1415
import pytest
1516

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

345346

347+
def test_raises_for_mixed_pos_kwargs_in_given(tmpdir):
348+
f = tmpdir.join("raises_for_mixed_pos_kwargs_in_given")
349+
f.write(
350+
textwrap.dedent(
351+
"""
352+
from hypothesis import given
353+
from hypothesis.strategies import text
354+
355+
@given(text(), x=text())
356+
def test_bar(x):
357+
...
358+
"""
359+
)
360+
)
361+
assert_mypy_errors(str(f.realpath()), [(5, "call-overload")])
362+
363+
346364
@pytest.mark.parametrize(
347365
"return_val,errors",
348366
[

0 commit comments

Comments
 (0)