Skip to content

Commit f00ef0d

Browse files
authored
Merge pull request #3382 from HypothesisWorks/fix-InferType
2 parents 047487f + d653df2 commit f00ef0d

File tree

8 files changed

+149
-39
lines changed

8 files changed

+149
-39
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 fixes type annotations that had caused the signature of
4+
:func:`@given <hypothesis.given>` to be partially-unknown to type-checkers for Python
5+
versions before 3.10.

hypothesis-python/src/hypothesis/core.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from itertools import chain
2626
from random import Random
2727
from typing import (
28+
TYPE_CHECKING,
2829
Any,
2930
BinaryIO,
3031
Callable,
@@ -114,7 +115,8 @@
114115

115116
if sys.version_info >= (3, 10): # pragma: no cover
116117
from types import EllipsisType as InferType
117-
118+
elif TYPE_CHECKING:
119+
from builtins import ellipsis as InferType
118120
else:
119121
InferType = type(Ellipsis)
120122

hypothesis-python/src/hypothesis/extra/django/_impl.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import unittest
1313
from functools import partial
1414
from inspect import Parameter, signature
15-
from typing import Optional, Type, Union
15+
from typing import TYPE_CHECKING, Optional, Type, Union
1616

1717
from django import forms as df, test as dt
1818
from django.contrib.staticfiles import testing as dst
@@ -28,7 +28,8 @@
2828

2929
if sys.version_info >= (3, 10): # pragma: no cover
3030
from types import EllipsisType as InferType
31-
31+
elif TYPE_CHECKING:
32+
from builtins import ellipsis as InferType
3233
else:
3334
InferType = type(Ellipsis)
3435

hypothesis-python/src/hypothesis/extra/ghostwriter.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
from string import ascii_lowercase
8484
from textwrap import dedent, indent
8585
from typing import (
86+
TYPE_CHECKING,
8687
Any,
8788
Callable,
8889
Dict,
@@ -119,7 +120,8 @@
119120

120121
if sys.version_info >= (3, 10): # pragma: no cover
121122
from types import EllipsisType as InferType
122-
123+
elif TYPE_CHECKING:
124+
from builtins import ellipsis as InferType
123125
else:
124126
InferType = type(Ellipsis)
125127

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@
109109

110110
if sys.version_info >= (3, 10): # pragma: no cover
111111
from types import EllipsisType as InferType
112-
112+
elif typing.TYPE_CHECKING: # pragma: no cover
113+
from builtins import ellipsis as InferType
113114
else:
114115
InferType = type(Ellipsis)
115116

requirements/tools.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ pygments==2.12.0
236236
# sphinx
237237
pyparsing==3.0.9
238238
# via packaging
239-
pyright==1.1.249
239+
pyright==1.1.255
240240
# via -r requirements/tools.in
241241
pytest==7.1.2
242242
# via -r requirements/tools.in

whole-repo-tests/test_mypy.py

Lines changed: 75 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
from hypothesistooling.projects.hypothesispython import PYTHON_SRC
1717
from hypothesistooling.scripts import pip_tool, tool_path
1818

19+
PYTHON_VERSIONS = ["3.7", "3.8", "3.9", "3.10", "3.11"]
20+
1921

2022
def test_mypy_passes_on_hypothesis():
2123
pip_tool("mypy", PYTHON_SRC)
@@ -56,14 +58,20 @@ def get_mypy_analysed_type(fname, val):
5658
)
5759

5860

59-
def assert_mypy_errors(fname, expected):
60-
out = get_mypy_output(fname, "--no-error-summary", "--show-error-codes")
61+
def assert_mypy_errors(fname, expected, python_version=None):
62+
_args = ["--no-error-summary", "--show-error-codes"]
63+
64+
if python_version:
65+
_args.append(f"--python-version={python_version}")
66+
67+
out = get_mypy_output(fname, *_args)
68+
del _args
6169
# Shell output looks like:
6270
# file.py:2: error: Incompatible types in assignment ... [assignment]
6371

6472
def convert_lines():
6573
for error_line in out.splitlines():
66-
col, category = error_line.split(":")[1:3]
74+
col, category = error_line.split(":")[-3:-1]
6775
if category.strip() != "error":
6876
# mypy outputs "note" messages for overload problems, even with
6977
# --hide-error-context. Don't include these
@@ -343,23 +351,6 @@ def test_stateful_consumed_bundle_cannot_be_target(tmpdir):
343351
assert_mypy_errors(str(f.realpath()), [(3, "call-overload")])
344352

345353

346-
def test_raises_for_mixed_pos_kwargs_in_given(tmpdir):
347-
f = tmpdir.join("raises_for_mixed_pos_kwargs_in_given.py")
348-
f.write(
349-
textwrap.dedent(
350-
"""
351-
from hypothesis import given
352-
from hypothesis.strategies import text
353-
354-
@given(text(), x=text())
355-
def test_bar(x):
356-
...
357-
"""
358-
)
359-
)
360-
assert_mypy_errors(str(f.realpath()), [(5, "call-overload")])
361-
362-
363354
@pytest.mark.parametrize(
364355
"return_val,errors",
365356
[
@@ -445,3 +436,67 @@ def test_pos_only_args(tmpdir):
445436
(8, "call-overload"),
446437
],
447438
)
439+
440+
441+
@pytest.mark.parametrize("python_version", PYTHON_VERSIONS)
442+
def test_mypy_passes_on_basic_test(tmpdir, python_version):
443+
f = tmpdir.join("check_mypy_on_basic_tests.py")
444+
f.write(
445+
textwrap.dedent(
446+
"""
447+
import hypothesis
448+
import hypothesis.strategies as st
449+
450+
@hypothesis.given(x=st.text())
451+
def test_foo(x: str) -> None:
452+
assert x == x
453+
454+
from hypothesis import given
455+
from hypothesis.strategies import text
456+
457+
@given(x=text())
458+
def test_bar(x: str) -> None:
459+
assert x == x
460+
"""
461+
)
462+
)
463+
assert_mypy_errors(str(f.realpath()), [], python_version=python_version)
464+
465+
466+
@pytest.mark.parametrize("python_version", PYTHON_VERSIONS)
467+
def test_given_only_allows_strategies(tmpdir, python_version):
468+
f = tmpdir.join("check_mypy_given_expects_strategies.py")
469+
f.write(
470+
textwrap.dedent(
471+
"""
472+
from hypothesis import given
473+
474+
@given(1)
475+
def f():
476+
pass
477+
"""
478+
)
479+
)
480+
assert_mypy_errors(
481+
str(f.realpath()), [(4, "call-overload")], python_version=python_version
482+
)
483+
484+
485+
@pytest.mark.parametrize("python_version", PYTHON_VERSIONS)
486+
def test_raises_for_mixed_pos_kwargs_in_given(tmpdir, python_version):
487+
f = tmpdir.join("raises_for_mixed_pos_kwargs_in_given.py")
488+
f.write(
489+
textwrap.dedent(
490+
"""
491+
from hypothesis import given
492+
from hypothesis.strategies import text
493+
494+
@given(text(), x=text())
495+
def test_bar(x):
496+
...
497+
"""
498+
)
499+
)
500+
assert_mypy_errors(
501+
str(f.realpath()), [(5, "call-overload")], python_version=python_version
502+
)

whole-repo-tests/test_pyright.py

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
from hypothesistooling.projects.hypothesispython import HYPOTHESIS_PYTHON, PYTHON_SRC
2222
from hypothesistooling.scripts import pip_tool, tool_path
2323

24+
PYTHON_VERSIONS = ["3.7", "3.8", "3.9", "3.10", "3.11"]
25+
2426

2527
@pytest.mark.skip(
2628
reason="Hypothesis type-annotates the public API as a convenience for users, "
@@ -30,7 +32,8 @@ def test_pyright_passes_on_hypothesis():
3032
pip_tool("pyright", "--project", HYPOTHESIS_PYTHON)
3133

3234

33-
def test_pyright_passes_on_basic_test(tmp_path: Path):
35+
@pytest.mark.parametrize("python_version", PYTHON_VERSIONS)
36+
def test_pyright_passes_on_basic_test(tmp_path: Path, python_version: str):
3437
file = tmp_path / "test.py"
3538
file.write_text(
3639
textwrap.dedent(
@@ -51,10 +54,40 @@ def test_bar(x: str):
5154
"""
5255
)
5356
)
54-
_write_config(tmp_path, {"typeCheckingMode": "strict"})
57+
_write_config(
58+
tmp_path, {"typeCheckingMode": "strict", "pythonVersion": python_version}
59+
)
5560
assert _get_pyright_errors(file) == []
5661

5762

63+
@pytest.mark.parametrize("python_version", PYTHON_VERSIONS)
64+
def test_given_only_allows_strategies(tmp_path: Path, python_version: str):
65+
file = tmp_path / "test.py"
66+
file.write_text(
67+
textwrap.dedent(
68+
"""
69+
from hypothesis import given
70+
71+
@given(1)
72+
def f():
73+
pass
74+
"""
75+
)
76+
)
77+
_write_config(
78+
tmp_path, {"typeCheckingMode": "strict", "pythonVersion": python_version}
79+
)
80+
assert (
81+
sum(
82+
e["message"].startswith(
83+
'Argument of type "Literal[1]" cannot be assigned to parameter "_given_arguments"'
84+
)
85+
for e in _get_pyright_errors(file)
86+
)
87+
== 1
88+
)
89+
90+
5891
def test_pyright_issue_3296(tmp_path: Path):
5992
file = tmp_path / "test.py"
6093
file.write_text(
@@ -85,9 +118,14 @@ def test_bar(x: str):
85118
)
86119
)
87120
_write_config(tmp_path, {"typeCheckingMode": "strict"})
88-
assert any(
89-
e["message"].startswith('No overloads for "given" match the provided arguments')
90-
for e in _get_pyright_errors(file)
121+
assert (
122+
sum(
123+
e["message"].startswith(
124+
'No overloads for "given" match the provided arguments'
125+
)
126+
for e in _get_pyright_errors(file)
127+
)
128+
== 1
91129
)
92130

93131

@@ -122,11 +160,14 @@ def test_pyright_tuples_pos_args_only(tmp_path: Path):
122160
)
123161
)
124162
_write_config(tmp_path, {"typeCheckingMode": "strict"})
125-
assert any(
126-
e["message"].startswith(
127-
'No overloads for "tuples" match the provided arguments'
163+
assert (
164+
sum(
165+
e["message"].startswith(
166+
'No overloads for "tuples" match the provided arguments'
167+
)
168+
for e in _get_pyright_errors(file)
128169
)
129-
for e in _get_pyright_errors(file)
170+
== 2
130171
)
131172

132173

@@ -143,11 +184,14 @@ def test_pyright_one_of_pos_args_only(tmp_path: Path):
143184
)
144185
)
145186
_write_config(tmp_path, {"typeCheckingMode": "strict"})
146-
assert any(
147-
e["message"].startswith(
148-
'No overloads for "one_of" match the provided arguments'
187+
assert (
188+
sum(
189+
e["message"].startswith(
190+
'No overloads for "one_of" match the provided arguments'
191+
)
192+
for e in _get_pyright_errors(file)
149193
)
150-
for e in _get_pyright_errors(file)
194+
== 2
151195
)
152196

153197

0 commit comments

Comments
 (0)