Repository: HypothesisWorks/hypothesis

Issue:
Resolving `TypedDict` fails under Python 3.10
The relevant tests were simply skipped for #2896, but we should return to this and get them working before 3.10 is stable; and check whether this can happen with `from future import annotations` on earlier versions.

The tests could also be upgraded to have non-builtin types as members of the dicts, since I'm planning some unrelated improvements for forwardrefs to builtins.


Discussion:
Note that #2899 will make the skipped tests pass *without fixing the underlying problem*; we'll want to add new tests using TypedDicts with user-defined types for some values.

The underlying change to treating TypedDict attributes as forward-references might also be considered an upstream bug (in `typing.get_type_hints()`?); we should check that and report it if so.
Upstream is deferring [PEP 563 (annotations-as-strings)](https://www.python.org/dev/peps/pep-0563/) until at least Python 3.11, and may yet withdraw it in favor of [PEP 649 (annotations-as-closures)](https://www.python.org/dev/peps/pep-0649/).  See also: [BPO-38605](https://bugs.python.org/issue38605), [BPO-43006](https://bugs.python.org/issue43006), https://github.com/python/cpython/pull/25490, and [this Steering Council email](https://mail.python.org/archives/list/python-dev@python.org/thread/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/) announcing the deferral.

In a few weeks when we have 3.10-beta1 and a new pytest version, we'll be able to reactivate Hypothesis' 3.10 CI job and remove the version-specific logic in our tests with `git grep -E "\b3.? ?10\b" -- hypothesis-python/{src,tests}`.

Resolving patch:
diff --git a/hypothesis-python/setup.py b/hypothesis-python/setup.py
--- a/hypothesis-python/setup.py
+++ b/hypothesis-python/setup.py
@@ -88,14 +88,15 @@ def local_file(name):
 setuptools.setup(
     name="hypothesis",
     version=__version__,
-    author="David R. MacIver",
+    author="David R. MacIver and Zac Hatfield-Dodds",
     author_email="david@drmaciver.com",
     packages=setuptools.find_packages(SOURCE),
     package_dir={"": SOURCE},
     package_data={"hypothesis": ["py.typed", "vendor/tlds-alpha-by-domain.txt"]},
-    url="https://github.com/HypothesisWorks/hypothesis/tree/master/hypothesis-python",
+    url="https://hypothesis.works",
     project_urls={
-        "Website": "https://hypothesis.works",
+        "Source": "https://github.com/HypothesisWorks/hypothesis/tree/master/hypothesis-python",
+        "Changelog": "https://hypothesis.readthedocs.io/en/latest/changes.html",
         "Documentation": "https://hypothesis.readthedocs.io",
         "Issues": "https://github.com/HypothesisWorks/hypothesis/issues",
     },
diff --git a/tooling/src/hypothesistooling/__main__.py b/tooling/src/hypothesistooling/__main__.py
--- a/tooling/src/hypothesistooling/__main__.py
+++ b/tooling/src/hypothesistooling/__main__.py
@@ -280,7 +280,7 @@ def update_python_versions():
     digits = tuple("0123456789")
     best = {}
     for line in map(str.strip, result.splitlines()):
-        m = re.match(r"(?:pypy)?3\.\d+", line)
+        m = re.match(r"(?:pypy)?3\.(?:[6-9]|\d\d)", line)
         if m:
             key = m.group()
             if key.endswith(digits) or not best.get(key, key).endswith(digits):
@@ -294,6 +294,15 @@ def update_python_versions():
     if before != after:
         thisfile.write_text(after)
 
+    # Automatically sync PYMAIN with the version in build.sh
+    build_sh = pathlib.Path(tools.ROOT) / "build.sh"
+    sh_before = build_sh.read_text()
+    new_pymain = re.search(r'PYMAIN = "(3\.\d\d?\.\d\d?)"', after).group(1)
+    sh_after = re.sub(r"3\.\d\d?\.\d\d?", new_pymain, sh_before)
+    if sh_before != sh_after:
+        build_sh.unlink()  # so bash doesn't reload a modified file
+        build_sh.write_text(sh_after)
+
 
 @task()
 def upgrade_requirements():
@@ -351,8 +360,8 @@ def run_tox(task, version):
 # See update_python_versions() above
 PY36 = "3.6.13"
 PY37 = "3.7.10"
-PY38 = PYMAIN = "3.8.9"  # Note: keep this in sync with build.sh and GH Actions main.yml
-PY39 = "3.9.4"
+PY38 = PYMAIN = "3.8.10"  # Sync PYMAIN minor version with GH Actions main.yml
+PY39 = "3.9.5"
 PY310 = "3.10-dev"
 PYPY36 = "pypy3.6-7.3.3"
 PYPY37 = "pypy3.7-7.3.4"


Test patch:
diff --git a/hypothesis-python/tests/cover/test_annotations.py b/hypothesis-python/tests/cover/test_annotations.py
--- a/hypothesis-python/tests/cover/test_annotations.py
+++ b/hypothesis-python/tests/cover/test_annotations.py
@@ -13,7 +13,6 @@
 #
 # END HEADER
 
-import sys
 from inspect import getfullargspec
 
 import attr
@@ -116,8 +115,7 @@ def test_composite_edits_annotations():
 @pytest.mark.parametrize("nargs", [1, 2, 3])
 def test_given_edits_annotations(nargs):
     spec_given = getfullargspec(given(*(nargs * [st.none()]))(pointless_composite))
-    expected = None if sys.version_info[:2] < (3, 10) else type(None)
-    assert spec_given.annotations.pop("return") == expected
+    assert spec_given.annotations.pop("return") is None
     assert len(spec_given.annotations) == 3 - nargs
 
 
diff --git a/hypothesis-python/tests/cover/test_lookup.py b/hypothesis-python/tests/cover/test_lookup.py
--- a/hypothesis-python/tests/cover/test_lookup.py
+++ b/hypothesis-python/tests/cover/test_lookup.py
@@ -756,12 +756,7 @@ def test_compat_get_type_hints_aware_of_None_default():
     find_any(strategy, lambda x: x.a is not None)
 
     assert typing.get_type_hints(constructor)["a"] == typing.Optional[str]
-    annotation = inspect.signature(constructor).parameters["a"].annotation
-    assert annotation == str or (
-        # See https://bugs.python.org/issue43006
-        annotation == typing.Optional[str]
-        and sys.version_info[:2] >= (3, 10)
-    )
+    assert inspect.signature(constructor).parameters["a"].annotation == str
 
 
 _ValueType = typing.TypeVar("_ValueType")
diff --git a/hypothesis-python/tests/cover/test_lookup_py38.py b/hypothesis-python/tests/cover/test_lookup_py38.py
--- a/hypothesis-python/tests/cover/test_lookup_py38.py
+++ b/hypothesis-python/tests/cover/test_lookup_py38.py
@@ -14,7 +14,6 @@
 # END HEADER
 
 import dataclasses
-import sys
 import typing
 
 import pytest
@@ -103,7 +102,6 @@ class NestedDict(typing.TypedDict):
     inner: A
 
 
-@pytest.mark.skipif(sys.version_info[:2] >= (3, 10), reason="see issue #2897")
 @given(from_type(NestedDict))
 def test_typeddict_with_nested_value(value):
     assert type(value) == dict
diff --git a/requirements/test.txt b/requirements/test.txt
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -32,7 +32,7 @@ pytest-forked==1.3.0
     # via pytest-xdist
 pytest-xdist==2.2.1
     # via -r requirements/test.in
-pytest==6.2.3
+pytest==6.2.4
     # via
     #   -r requirements/test.in
     #   pytest-forked