From e613aee46bdaaa766b3004f961c1d940ed1a086d Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 15 Aug 2025 02:09:42 -0400 Subject: [PATCH 01/30] fix changelog --- hypothesis-python/docs/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hypothesis-python/docs/changelog.rst b/hypothesis-python/docs/changelog.rst index 71fe042fcb..40ef7756ee 100644 --- a/hypothesis-python/docs/changelog.rst +++ b/hypothesis-python/docs/changelog.rst @@ -24,7 +24,7 @@ Hypothesis 6.x 6.138.1 - 2025-08-15 -------------------- -Internal refactoring and cleanup. As a result, ``hypothesis[black]`` now requires ``black>=20.8b0`` instead of the previous ``black>=19.10b0``. +Internal refactoring and cleanup. As a result, ``hypothesis[cli]`` and ``hypothesis[ghostwriter]`` now require ``black>=20.8b0`` instead of the previous ``black>=19.10b0``. .. _v6.138.0: From 2606b56c9f4d5cc13dd9e4ac295c4043fa8c4683 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 15 Aug 2025 22:51:37 -0400 Subject: [PATCH 02/30] bump base ci version to 3.13 --- .github/workflows/fuzz.yml | 6 +- .github/workflows/main.yml | 18 +- .github/workflows/update-deps.yml | 4 +- .readthedocs.yml | 2 +- build.sh | 9 +- hypothesis-python/RELEASE.rst | 3 + hypothesis-python/pyproject.toml | 4 +- hypothesis-python/src/hypothesis/_settings.py | 2 +- .../src/hypothesis/entry_points.py | 6 +- .../conjecture/provider_conformance.py | 2 +- .../hypothesis/strategies/_internal/types.py | 10 +- hypothesis-python/tox.ini | 4 +- pyproject.toml | 2 +- requirements/coverage.txt | 54 +++--- requirements/crosshair.txt | 36 ++-- requirements/fuzzing.txt | 84 ++++----- requirements/test.txt | 16 +- requirements/tools.in | 1 + requirements/tools.txt | 161 ++++++++---------- tooling/src/hypothesistooling/__main__.py | 10 +- 20 files changed, 186 insertions(+), 248 deletions(-) create mode 100644 hypothesis-python/RELEASE.rst diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 1e7c096d2b..4aac401905 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -24,10 +24,10 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Set up Python 3.10 + - name: Set up Python 3.13 uses: actions/setup-python@v4 with: - python-version: "3.10.9" + python-version: "3.13.5" - name: Restore cache uses: actions/cache@v3 with: @@ -79,7 +79,7 @@ jobs: with: name: explicit-example-patches path: .hypothesis/patches/latest_hypofuzz_*.patch - + # Upload the database so it'll be persisted between runs. # Note that we can also pull it down to use locally via # https://hypothesis.readthedocs.io/en/latest/database.html#hypothesis.database.GitHubArtifactDatabase diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 35f464bf23..f1e8084703 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -111,10 +111,10 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Set up Python 3.10 + - name: Set up Python 3.13 uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: "3.13" - name: Restore cache uses: actions/cache@v3 with: @@ -221,10 +221,10 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Set up Python 3.10 + - name: Set up Python 3.13 uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: "3.13" - name: Restore cache uses: actions/cache@v3 with: @@ -244,9 +244,9 @@ jobs: NODE_VERSION: 22 # Note that the versions below are updated by `update_pyodide_versions()` in our weekly cronjob. # The versions of pyodide-build and the Pyodide runtime may differ. - PYODIDE_VERSION: 0.27.7 - PYODIDE_BUILD_VERSION: 0.30.5 - PYTHON_VERSION: 3.12.7 + PYODIDE_VERSION: 0.28.1 + PYODIDE_BUILD_VERSION: 0.30.6 + PYTHON_VERSION: 3.13.2 steps: - uses: actions/checkout@v3 with: @@ -298,10 +298,10 @@ jobs: with: fetch-depth: 0 token: ${{ secrets.GH_TOKEN }} - - name: Set up Python 3.10 + - name: Set up Python 3.13 uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: "3.13" - name: Restore cache uses: actions/cache@v3 with: diff --git a/.github/workflows/update-deps.yml b/.github/workflows/update-deps.yml index 685cdf63db..c644781ec8 100644 --- a/.github/workflows/update-deps.yml +++ b/.github/workflows/update-deps.yml @@ -11,10 +11,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Set up Python 3.10 + - name: Set up Python 3.13 uses: actions/setup-python@v4 with: - python-version: '3.10' + python-version: '3.13' - name: Update pinned dependencies run: ./build.sh upgrade-requirements - name: Open pull request diff --git a/.readthedocs.yml b/.readthedocs.yml index 0ca2e8060c..e90297f953 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -14,7 +14,7 @@ formats: build: os: ubuntu-22.04 tools: - python: "3.10" + python: "3.13" python: install: - requirements: requirements/tools.txt diff --git a/build.sh b/build.sh index 9bd11953e4..41402281b5 100755 --- a/build.sh +++ b/build.sh @@ -19,19 +19,22 @@ SCRIPTS="$ROOT/tooling/scripts" # shellcheck source=tooling/scripts/common.sh source "$SCRIPTS/common.sh" +PYTHON_VERSION="3.13.5" + if [ -n "${GITHUB_ACTIONS-}" ] || [ -n "${CODESPACES-}" ] ; then # We're on GitHub Actions or Codespaces and already set up a suitable Python PYTHON=$(command -v python) else # Otherwise, we install it from scratch # NOTE: tooling keeps this version in sync with ci_version in tooling - "$SCRIPTS/ensure-python.sh" 3.10.18 - PYTHON=$(pythonloc 3.10.18)/bin/python + "$SCRIPTS/ensure-python.sh" "$PYTHON_VERSION" + PYTHON=$(pythonloc "$PYTHON_VERSION")/bin/python fi TOOL_REQUIREMENTS="$ROOT/requirements/tools.txt" -TOOL_HASH=$("$PYTHON" "$SCRIPTS/tool-hash.py" < "$TOOL_REQUIREMENTS") +# append PYTHON_VERSION to bust caches when we upgrade versions +TOOL_HASH=$((cat "$TOOL_REQUIREMENTS" && echo "$PYTHON_VERSION") | "$PYTHON" "$SCRIPTS/tool-hash.py") TOOL_VIRTUALENV="$VIRTUALENVS/build-$TOOL_HASH" TOOL_PYTHON="$TOOL_VIRTUALENV/bin/python" diff --git a/hypothesis-python/RELEASE.rst b/hypothesis-python/RELEASE.rst new file mode 100644 index 0000000000..2364c92ace --- /dev/null +++ b/hypothesis-python/RELEASE.rst @@ -0,0 +1,3 @@ +RELEASE_TYPE: patch + +Removes some internal ``# type: ignore`` comments as a result of bumping CI packages. diff --git a/hypothesis-python/pyproject.toml b/hypothesis-python/pyproject.toml index 325ba195cb..86a9183a2e 100644 --- a/hypothesis-python/pyproject.toml +++ b/hypothesis-python/pyproject.toml @@ -111,7 +111,7 @@ pandas = ["pandas>=1.1"] pytest = ["pytest>=4.6"] dpcontracts = ["dpcontracts>=0.4"] redis = ["redis>=3.0.0"] -crosshair = ["hypothesis-crosshair>=0.0.24", "crosshair-tool>=0.0.93"] +crosshair = ["hypothesis-crosshair>=0.0.25", "crosshair-tool>=0.0.95"] # zoneinfo is an odd one: every dependency is platform-conditional. zoneinfo = ["tzdata>=2025.2; sys_platform == 'win32' or sys_platform == 'emscripten'"] # We only support Django versions with upstream support - see @@ -121,7 +121,7 @@ zoneinfo = ["tzdata>=2025.2; sys_platform == 'win32' or sys_platform == 'emscrip django = ["django>=4.2"] watchdog = ["watchdog>=4.0.0"] # Avoid changing this by hand. This is automatically updated by update_changelog_and_version -all = ["black>=20.8b0", "click>=7.0", "crosshair-tool>=0.0.93", "django>=4.2", "dpcontracts>=0.4", "hypothesis-crosshair>=0.0.24", "lark>=0.10.1", "libcst>=0.3.16", "numpy>=1.19.3", "pandas>=1.1", "pytest>=4.6", "python-dateutil>=1.4", "pytz>=2014.1", "redis>=3.0.0", "rich>=9.0.0", "tzdata>=2025.2; sys_platform == 'win32' or sys_platform == 'emscripten'", "watchdog>=4.0.0"] +all = ["black>=20.8b0", "click>=7.0", "crosshair-tool>=0.0.95", "django>=4.2", "dpcontracts>=0.4", "hypothesis-crosshair>=0.0.25", "lark>=0.10.1", "libcst>=0.3.16", "numpy>=1.19.3", "pandas>=1.1", "pytest>=4.6", "python-dateutil>=1.4", "pytz>=2014.1", "redis>=3.0.0", "rich>=9.0.0", "tzdata>=2025.2; sys_platform == 'win32' or sys_platform == 'emscripten'", "watchdog>=4.0.0"] [tool.setuptools.dynamic] version = {attr = "hypothesis.version.__version__"} diff --git a/hypothesis-python/src/hypothesis/_settings.py b/hypothesis-python/src/hypothesis/_settings.py index 18442267a9..20facf163a 100644 --- a/hypothesis-python/src/hypothesis/_settings.py +++ b/hypothesis-python/src/hypothesis/_settings.py @@ -910,7 +910,7 @@ def __call__(self, test: T) -> T: ) setattr(test, attr_name, True) _test.TestCase.settings = self - return test # type: ignore + return test else: raise InvalidArgument( "@settings(...) can only be used as a decorator on " diff --git a/hypothesis-python/src/hypothesis/entry_points.py b/hypothesis-python/src/hypothesis/entry_points.py index 4a68af7c43..a29cbe89f1 100644 --- a/hypothesis-python/src/hypothesis/entry_points.py +++ b/hypothesis-python/src/hypothesis/entry_points.py @@ -25,9 +25,9 @@ def get_entry_points() -> Generator[EntryPoint, None, None]: try: eps: Sequence[EntryPoint] = importlib.metadata.entry_points(group="hypothesis") except TypeError: # pragma: no cover - # Load-time selection requires Python >= 3.10. See also - # https://importlib-metadata.readthedocs.io/en/latest/using.html - eps = importlib.metadata.entry_points().get("hypothesis", []) + # Load-time selection requires Python >= 3.10. + # type ignore since interface differs pre-3.10 + eps = importlib.metadata.entry_points().get("hypothesis", []) # type: ignore yield from eps diff --git a/hypothesis-python/src/hypothesis/internal/conjecture/provider_conformance.py b/hypothesis-python/src/hypothesis/internal/conjecture/provider_conformance.py index f4fdd57c8f..ef5d168fe2 100644 --- a/hypothesis-python/src/hypothesis/internal/conjecture/provider_conformance.py +++ b/hypothesis-python/src/hypothesis/internal/conjecture/provider_conformance.py @@ -42,7 +42,7 @@ def build_intervals(intervals: list[int]) -> list[tuple[int, int]]: if len(intervals) % 2: intervals = intervals[:-1] intervals.sort() - return list(batched(intervals, 2, strict=True)) + return list(batched(intervals, 2, strict=True)) # type: ignore def interval_lists( diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/types.py b/hypothesis-python/src/hypothesis/strategies/_internal/types.py index eb842178ed..34cbd95f72 100644 --- a/hypothesis-python/src/hypothesis/strategies/_internal/types.py +++ b/hypothesis-python/src/hypothesis/strategies/_internal/types.py @@ -99,7 +99,7 @@ except AttributeError: # pragma: no cover pass # Is missing for `python<3.10` try: - TypeGuardTypes += (typing.TypeIs,) # type: ignore + TypeGuardTypes += (typing.TypeIs,) except AttributeError: # pragma: no cover pass # Is missing for `python<3.13` try: @@ -110,7 +110,7 @@ RequiredTypes: tuple = () try: - RequiredTypes += (typing.Required,) # type: ignore + RequiredTypes += (typing.Required,) except AttributeError: # pragma: no cover pass # Is missing for `python<3.11` try: @@ -121,7 +121,7 @@ NotRequiredTypes: tuple = () try: - NotRequiredTypes += (typing.NotRequired,) # type: ignore + NotRequiredTypes += (typing.NotRequired,) except AttributeError: # pragma: no cover pass # Is missing for `python<3.11` try: @@ -132,7 +132,7 @@ ReadOnlyTypes: tuple = () try: - ReadOnlyTypes += (typing.ReadOnly,) # type: ignore + ReadOnlyTypes += (typing.ReadOnly,) except AttributeError: # pragma: no cover pass # Is missing for `python<3.13` try: @@ -143,7 +143,7 @@ LiteralStringTypes: tuple = () try: - LiteralStringTypes += (typing.LiteralString,) # type: ignore + LiteralStringTypes += (typing.LiteralString,) except AttributeError: # pragma: no cover pass # Is missing for `python<3.11` try: diff --git a/hypothesis-python/tox.ini b/hypothesis-python/tox.ini index 49f459f8ce..6a22b2ec09 100644 --- a/hypothesis-python/tox.ini +++ b/hypothesis-python/tox.ini @@ -177,8 +177,8 @@ deps= -r../requirements/test.txt django42: django==4.2.23 django51: django==5.1.11 - django52: django==5.2.3 - django-nocontrib: django==5.2.3 + django52: django==5.2.5 + django-nocontrib: django==5.2.5 commands = python -bb -X dev -m tests.django.manage test tests.django {posargs} diff --git a/pyproject.toml b/pyproject.toml index 1d146b5c7f..5eca9ddb96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,7 +89,7 @@ ignore = [ "hypothesis-python/tests/conjecture/test_data_tree.py" = ["B023"] [tool.mypy] -python_version = "3.10" +python_version = "3.13" platform = "linux" allow_redefinition = true diff --git a/requirements/coverage.txt b/requirements/coverage.txt index bb8650a64d..1bba012f9a 100644 --- a/requirements/coverage.txt +++ b/requirements/coverage.txt @@ -1,42 +1,36 @@ # -# This file is autogenerated by pip-compile with Python 3.10 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # # ./build.sh upgrade-requirements # annotated-types==0.7.0 # via -r requirements/coverage.in -async-timeout==5.0.1 - # via redis attrs==25.3.0 # via hypothesis (hypothesis-python/pyproject.toml) black==25.1.0 # via -r requirements/coverage.in -click==8.1.8 +click==8.2.1 # via # -r requirements/coverage.in # black -coverage[toml]==7.8.0 +coverage[toml]==7.10.3 # via pytest-cov dpcontracts==0.6.0 # via -r requirements/coverage.in -exceptiongroup==1.2.2 ; python_version < "3.11" - # via - # hypothesis (hypothesis-python/pyproject.toml) - # pytest execnet==2.1.1 # via pytest-xdist -fakeredis==2.28.1 +fakeredis==2.31.0 # via -r requirements/coverage.in iniconfig==2.1.0 # via pytest lark==1.2.2 # via -r requirements/coverage.in -libcst==1.7.0 +libcst==1.8.2 # via -r requirements/coverage.in mypy-extensions==1.1.0 # via black -numpy==2.2.5 +numpy==2.3.2 # via # -r requirements/coverage.in # pandas @@ -44,28 +38,32 @@ packaging==25.0 # via # black # pytest -pandas==2.2.3 +pandas==2.3.1 # via -r requirements/coverage.in pathspec==0.12.1 # via black pexpect==4.9.0 # via -r requirements/test.in -platformdirs==4.3.7 +platformdirs==4.3.8 # via black -pluggy==1.5.0 - # via pytest +pluggy==1.6.0 + # via + # pytest + # pytest-cov ptyprocess==0.7.0 # via pexpect -pyarrow==19.0.1 +pyarrow==21.0.0 # via -r requirements/coverage.in -pytest==8.4.0 +pygments==2.19.2 + # via pytest +pytest==8.4.1 # via # -r requirements/test.in # pytest-cov # pytest-xdist -pytest-cov==6.1.1 +pytest-cov==6.2.1 # via -r requirements/coverage.in -pytest-xdist==3.6.1 +pytest-xdist==3.8.0 # via -r requirements/test.in python-dateutil==2.9.0.post0 # via @@ -75,9 +73,9 @@ pytz==2025.2 # via # -r requirements/coverage.in # pandas -pyyaml==6.0.2 +pyyaml-ft==8.0.0 # via libcst -redis==5.2.1 +redis==6.4.0 # via fakeredis six==1.17.0 # via python-dateutil @@ -85,16 +83,8 @@ sortedcontainers==2.4.0 # via # fakeredis # hypothesis (hypothesis-python/pyproject.toml) -tomli==2.2.1 - # via - # black - # coverage - # pytest -typing-extensions==4.14.0 - # via - # -r requirements/coverage.in - # black - # fakeredis +typing-extensions==4.14.1 + # via -r requirements/coverage.in tzdata==2025.2 # via pandas watchdog==6.0.0 diff --git a/requirements/crosshair.txt b/requirements/crosshair.txt index 20fa743348..2c09fe48c7 100644 --- a/requirements/crosshair.txt +++ b/requirements/crosshair.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.10 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # # ./build.sh upgrade-requirements @@ -10,27 +10,21 @@ attrs==25.3.0 # hypothesis # hypothesis (hypothesis-python/pyproject.toml) # lsprotocol -cattrs==24.1.3 +cattrs==25.1.1 # via # lsprotocol # pygls -crosshair-tool==0.0.93 +crosshair-tool==0.0.95 # via # -r requirements/crosshair.in # hypothesis-crosshair -exceptiongroup==1.2.2 ; python_version < "3.11" - # via - # cattrs - # hypothesis - # hypothesis (hypothesis-python/pyproject.toml) - # pytest execnet==2.1.1 # via pytest-xdist -hypothesis==6.135.14 +hypothesis==6.138.2 # via hypothesis-crosshair -hypothesis-crosshair==0.0.24 +hypothesis-crosshair==0.0.25 # via -r requirements/crosshair.in -importlib-metadata==8.6.1 +importlib-metadata==8.7.0 # via crosshair-tool importlib-resources==6.5.2 # via typeshed-client @@ -46,27 +40,27 @@ packaging==25.0 # pytest pexpect==4.9.0 # via -r requirements/test.in -pluggy==1.5.0 +pluggy==1.6.0 # via pytest ptyprocess==0.7.0 # via pexpect pygls==1.3.1 # via crosshair-tool -pytest==8.4.0 +pygments==2.19.2 + # via pytest +pytest==8.4.1 # via # -r requirements/test.in # pytest-xdist -pytest-xdist==3.6.1 +pytest-xdist==3.8.0 # via -r requirements/test.in sortedcontainers==2.4.0 # via # hypothesis # hypothesis (hypothesis-python/pyproject.toml) -tomli==2.2.1 - # via pytest -typeshed-client==2.7.0 +typeshed-client==2.8.2 # via crosshair-tool -typing-extensions==4.13.2 +typing-extensions==4.14.1 # via # cattrs # crosshair-tool @@ -74,7 +68,7 @@ typing-extensions==4.13.2 # typing-inspect typing-inspect==0.9.0 # via crosshair-tool -z3-solver==4.15.0.0 +z3-solver==4.15.3.0 # via crosshair-tool -zipp==3.21.0 +zipp==3.23.0 # via importlib-metadata diff --git a/requirements/fuzzing.txt b/requirements/fuzzing.txt index db94515dd6..98b152ac94 100644 --- a/requirements/fuzzing.txt +++ b/requirements/fuzzing.txt @@ -1,15 +1,13 @@ # -# This file is autogenerated by pip-compile with Python 3.10 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # # ./build.sh upgrade-requirements # annotated-types==0.7.0 # via -r requirements/coverage.in -anyio==4.9.0 +anyio==4.10.0 # via starlette -async-timeout==5.0.1 - # via redis attrs==25.3.0 # via # hypothesis @@ -21,31 +19,22 @@ black==25.1.0 # -r requirements/coverage.in # hypofuzz # hypothesis -click==8.1.8 +click==8.2.1 # via # -r requirements/coverage.in # black # hypothesis -coverage[toml]==7.8.0 +coverage[toml]==7.10.3 # via # hypofuzz # pytest-cov dpcontracts==0.6.0 # via -r requirements/coverage.in -exceptiongroup==1.2.2 ; python_version < "3.11" - # via - # anyio - # hypercorn - # hypothesis - # hypothesis (hypothesis-python/pyproject.toml) - # pytest - # taskgroup - # trio execnet==2.1.1 # via pytest-xdist -fakeredis==2.28.1 +fakeredis==2.31.0 # via -r requirements/coverage.in -h11==0.14.0 +h11==0.16.0 # via # hypercorn # wsproto @@ -57,9 +46,9 @@ hypercorn==0.17.3 # via hypofuzz hyperframe==6.1.0 # via h2 -hypofuzz==25.4.5 +hypofuzz==25.8.1 # via -r requirements/fuzzing.in -hypothesis[cli,watchdog]==6.131.7 +hypothesis[cli,watchdog]==6.138.2 # via hypofuzz idna==3.10 # via @@ -69,17 +58,17 @@ iniconfig==2.1.0 # via pytest lark==1.2.2 # via -r requirements/coverage.in -libcst==1.7.0 +libcst==1.8.2 # via # -r requirements/coverage.in # hypofuzz -markdown-it-py==3.0.0 +markdown-it-py==4.0.0 # via rich mdurl==0.1.2 # via markdown-it-py mypy-extensions==1.1.0 # via black -numpy==2.2.5 +numpy==2.3.2 # via # -r requirements/coverage.in # pandas @@ -89,35 +78,39 @@ packaging==25.0 # via # black # pytest -pandas==2.2.3 +pandas==2.3.1 # via -r requirements/coverage.in pathspec==0.12.1 # via black pexpect==4.9.0 # via -r requirements/test.in -platformdirs==4.3.7 +platformdirs==4.3.8 # via black -pluggy==1.5.0 - # via pytest +pluggy==1.6.0 + # via + # pytest + # pytest-cov priority==2.0.0 # via hypercorn psutil==7.0.0 # via hypofuzz ptyprocess==0.7.0 # via pexpect -pyarrow==19.0.1 +pyarrow==21.0.0 # via -r requirements/coverage.in -pygments==2.19.1 - # via rich -pytest==8.4.0 +pygments==2.19.2 + # via + # pytest + # rich +pytest==8.4.1 # via # -r requirements/test.in # hypofuzz # pytest-cov # pytest-xdist -pytest-cov==6.1.1 +pytest-cov==6.2.1 # via -r requirements/coverage.in -pytest-xdist==3.6.1 +pytest-xdist==3.8.0 # via -r requirements/test.in python-dateutil==2.9.0.post0 # via @@ -127,11 +120,11 @@ pytz==2025.2 # via # -r requirements/coverage.in # pandas -pyyaml==6.0.2 +pyyaml-ft==8.0.0 # via libcst -redis==5.2.1 +redis==6.4.0 # via fakeredis -rich==14.0.0 +rich==14.1.0 # via hypothesis six==1.17.0 # via python-dateutil @@ -145,27 +138,12 @@ sortedcontainers==2.4.0 # hypothesis # hypothesis (hypothesis-python/pyproject.toml) # trio -starlette==0.46.2 +starlette==0.47.2 # via hypofuzz -taskgroup==0.2.2 - # via hypercorn -tomli==2.2.1 - # via - # black - # coverage - # hypercorn - # pytest trio==0.30.0 # via hypofuzz -typing-extensions==4.13.2 - # via - # -r requirements/coverage.in - # anyio - # black - # fakeredis - # hypercorn - # rich - # taskgroup +typing-extensions==4.14.1 + # via -r requirements/coverage.in tzdata==2025.2 # via pandas watchdog==6.0.0 diff --git a/requirements/test.txt b/requirements/test.txt index ff46a1df33..a90eee0d99 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,15 +1,11 @@ # -# This file is autogenerated by pip-compile with Python 3.10 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # # ./build.sh upgrade-requirements # attrs==25.3.0 # via hypothesis (hypothesis-python/pyproject.toml) -exceptiongroup==1.2.2 ; python_version < "3.11" - # via - # hypothesis (hypothesis-python/pyproject.toml) - # pytest execnet==2.1.1 # via pytest-xdist iniconfig==2.1.0 @@ -18,17 +14,17 @@ packaging==25.0 # via pytest pexpect==4.9.0 # via -r requirements/test.in -pluggy==1.5.0 +pluggy==1.6.0 # via pytest ptyprocess==0.7.0 # via pexpect -pytest==8.4.0 +pygments==2.19.2 + # via pytest +pytest==8.4.1 # via # -r requirements/test.in # pytest-xdist -pytest-xdist==3.6.1 +pytest-xdist==3.8.0 # via -r requirements/test.in sortedcontainers==2.4.0 # via hypothesis (hypothesis-python/pyproject.toml) -tomli==2.2.1 - # via pytest diff --git a/requirements/tools.in b/requirements/tools.in index 8dba6aac5e..2f107c40b7 100644 --- a/requirements/tools.in +++ b/requirements/tools.in @@ -29,4 +29,5 @@ typing-extensions watchdog # for typing build sortedcontainers-stubs # for typing +tomli # for update_pyproject_toml -r test.in diff --git a/requirements/tools.txt b/requirements/tools.txt index d6bd6bc71e..214c7cc2dd 100644 --- a/requirements/tools.txt +++ b/requirements/tools.txt @@ -1,14 +1,16 @@ # -# This file is autogenerated by pip-compile with Python 3.10 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # # ./build.sh upgrade-requirements # +accessible-pygments==0.0.5 + # via furo alabaster==1.0.0 # via sphinx -anyio==4.9.0 +anyio==4.10.0 # via watchfiles -asgiref==3.8.1 +asgiref==3.9.1 # via django asttokens==3.0.0 # via stack-data @@ -18,8 +20,6 @@ autoflake==2.3.1 # via shed babel==2.17.0 # via sphinx -backports-tarfile==1.2.0 - # via jaraco-context beautifulsoup4==4.13.4 # via # furo @@ -28,21 +28,21 @@ black==25.1.0 # via shed blinker==1.9.0 # via pelican -build==1.2.2.post1 +build==1.3.0 # via # -r requirements/tools.in # pip-tools -cachetools==5.5.2 +cachetools==6.1.0 # via tox -certifi==2025.1.31 +certifi==2025.8.3 # via requests cffi==1.17.1 # via cryptography chardet==5.2.0 # via tox -charset-normalizer==3.4.1 +charset-normalizer==3.4.3 # via requests -click==8.1.8 +click==8.2.1 # via # black # pip-tools @@ -52,18 +52,17 @@ colorama==0.4.6 # via tox com2ann==0.3.0 # via shed -coverage==7.8.0 +coverage==7.10.3 # via -r requirements/tools.in -cryptography==44.0.2 +cryptography==45.0.6 # via - # secretstorage # types-pyopenssl # types-redis decorator==5.2.1 # via ipython -distlib==0.3.9 +distlib==0.4.0 # via virtualenv -django==5.2 +django==5.2.5 # via -r requirements/tools.in docutils==0.21.2 # via @@ -74,23 +73,17 @@ docutils==0.21.2 # sphinx-jsonschema dpcontracts==0.6.0 # via -r requirements/tools.in -exceptiongroup==1.2.2 ; python_version < "3.11" - # via - # anyio - # hypothesis (hypothesis-python/pyproject.toml) - # ipython - # pytest execnet==2.1.1 # via pytest-xdist executing==2.2.0 # via stack-data -feedgenerator==2.1.0 +feedgenerator==2.2.0 # via pelican -filelock==3.18.0 +filelock==3.19.1 # via # tox # virtualenv -furo==2024.8.6 +furo==2025.7.19 # via -r requirements/tools.in id==1.5.0 # via twine @@ -100,26 +93,22 @@ idna==3.10 # requests imagesize==1.4.1 # via sphinx -importlib-metadata==8.6.1 - # via keyring iniconfig==2.1.0 # via pytest -ipython==8.35.0 +ipython==9.4.0 # via -r requirements/tools.in +ipython-pygments-lexers==1.1.1 + # via ipython isort==6.0.1 # via shed jaraco-classes==3.4.0 # via keyring jaraco-context==6.0.1 # via keyring -jaraco-functools==4.1.0 +jaraco-functools==4.2.1 # via keyring jedi==0.19.2 # via ipython -jeepney==0.9.0 - # via - # keyring - # secretstorage jinja2==3.1.6 # via # pelican @@ -130,13 +119,13 @@ keyring==25.6.0 # via twine lark==1.2.2 # via -r requirements/tools.in -libcst==1.7.0 +libcst==1.8.2 # via # -r requirements/tools.in # shed -markdown==3.8 +markdown==3.8.2 # via pelican -markdown-it-py==3.0.0 +markdown-it-py==4.0.0 # via rich markupsafe==3.0.2 # via jinja2 @@ -148,17 +137,17 @@ more-itertools==10.7.0 # via # jaraco-classes # jaraco-functools -mypy==1.16.1 +mypy==1.17.1 # via -r requirements/tools.in mypy-extensions==1.1.0 # via # black # mypy -nh3==0.2.21 +nh3==0.3.0 # via readme-renderer nodeenv==1.9.1 # via pyright -numpy==2.2.5 +numpy==2.3.2 # via -r requirements/tools.in ordered-set==4.1.0 # via pelican @@ -174,21 +163,23 @@ packaging==25.0 parso==0.8.4 # via jedi pathspec==0.12.1 - # via black + # via + # black + # mypy pelican[markdown]==4.11.0 # via -r requirements/tools.in pexpect==4.9.0 # via # -r requirements/test.in # ipython -pip-tools==7.4.1 +pip-tools==7.5.0 # via -r requirements/tools.in -platformdirs==4.3.7 +platformdirs==4.3.8 # via # black # tox # virtualenv -pluggy==1.5.0 +pluggy==1.6.0 # via # pytest # tox @@ -200,45 +191,46 @@ pure-eval==0.2.3 # via stack-data pycparser==2.22 # via cffi -pyflakes==3.3.2 +pyflakes==3.4.0 # via autoflake pygments==2.18.0 # via + # accessible-pygments # furo # ipython + # ipython-pygments-lexers # pelican + # pytest # readme-renderer # rich # sphinx -pyproject-api==1.9.0 +pyproject-api==1.9.1 # via tox pyproject-hooks==1.2.0 # via # build # pip-tools -pyright==1.1.399 +pyright==1.1.403 # via -r requirements/tools.in -pytest==8.4.0 +pytest==8.4.1 # via # -r requirements/test.in # pytest-xdist -pytest-xdist==3.6.1 +pytest-xdist==3.8.0 # via -r requirements/test.in python-dateutil==2.9.0.post0 # via # -r requirements/tools.in # pelican -pytz==2025.2 - # via feedgenerator -pyupgrade==3.19.1 +pyupgrade==3.20.0 # via shed pyyaml==6.0.2 - # via - # libcst - # sphinx-jsonschema + # via sphinx-jsonschema +pyyaml-ft==8.0.0 + # via libcst readme-renderer==44.0 # via twine -requests==2.32.3 +requests==2.32.4 # via # -r requirements/tools.in # id @@ -252,21 +244,21 @@ restructuredtext-lint==1.4.0 # via -r requirements/tools.in rfc3986==2.0.0 # via twine -rich==14.0.0 +rich==14.1.0 # via # pelican # twine -ruff==0.12.5 +roman-numerals-py==3.1.0 + # via sphinx +ruff==0.12.9 # via -r requirements/tools.in -secretstorage==3.3.3 - # via keyring shed==2024.1.1 # via -r requirements/tools.in six==1.17.0 # via python-dateutil sniffio==1.3.1 # via anyio -snowballstemmer==2.2.0 +snowballstemmer==3.0.1 # via sphinx sortedcontainers==2.4.0 # via @@ -276,7 +268,7 @@ sortedcontainers-stubs==2.4.3 # via -r requirements/tools.in soupsieve==2.7 # via beautifulsoup4 -sphinx==8.1.3 +sphinx==8.2.3 # via # -r requirements/tools.in # furo @@ -284,7 +276,7 @@ sphinx==8.1.3 # sphinx-codeautolink sphinx-basic-ng==1.0.0b2 # via furo -sphinx-codeautolink==0.17.4 +sphinx-codeautolink==0.17.5 # via -r requirements/tools.in sphinx-jsonschema==1.19.1 # via -r requirements/tools.in @@ -306,20 +298,11 @@ sqlparse==0.5.3 # via django stack-data==0.6.3 # via ipython -tokenize-rt==6.1.0 +tokenize-rt==6.2.0 # via pyupgrade tomli==2.2.1 - # via - # autoflake - # black - # build - # mypy - # pip-tools - # pyproject-api - # pytest - # sphinx - # tox -tox==4.25.0 + # via -r requirements/tools.in +tox==4.28.4 # via -r requirements/tools.in traitlets==5.14.3 # via @@ -327,54 +310,44 @@ traitlets==5.14.3 # matplotlib-inline twine==6.1.0 # via -r requirements/tools.in -types-cffi==1.17.0.20250326 +types-cffi==1.17.0.20250809 # via types-pyopenssl types-click==7.1.8 # via -r requirements/tools.in types-pyopenssl==24.1.0.20240722 # via types-redis -types-pytz==2025.2.0.20250326 +types-pytz==2025.2.0.20250809 # via -r requirements/tools.in types-redis==4.6.0.20241004 # via -r requirements/tools.in -types-setuptools==79.0.0.20250422 +types-setuptools==80.9.0.20250809 # via types-cffi -typing-extensions==4.13.2 +typing-extensions==4.14.1 # via # -r requirements/tools.in - # anyio - # asgiref # beautifulsoup4 - # black - # ipython # mypy # pyright - # rich # sortedcontainers-stubs - # tox -unidecode==1.3.8 +unidecode==1.4.0 # via pelican -urllib3==2.4.0 +urllib3==2.5.0 # via # requests # twine -virtualenv==20.30.0 +virtualenv==20.34.0 # via tox watchdog==6.0.0 # via -r requirements/tools.in -watchfiles==1.0.5 +watchfiles==1.1.0 # via pelican wcwidth==0.2.13 # via prompt-toolkit wheel==0.45.1 # via pip-tools -zipp==3.21.0 - # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: -pip==25.0.1 +pip==25.2 + # via pip-tools +setuptools==80.9.0 # via pip-tools -setuptools==79.0.0 - # via - # pip-tools - # types-setuptools diff --git a/tooling/src/hypothesistooling/__main__.py b/tooling/src/hypothesistooling/__main__.py index e0c1b2b8c8..69a813ce2b 100644 --- a/tooling/src/hypothesistooling/__main__.py +++ b/tooling/src/hypothesistooling/__main__.py @@ -362,7 +362,7 @@ def update_python_versions(): DJANGO_VERSIONS = { "4.2": "4.2.23", "5.1": "5.1.11", - "5.2": "5.2.3", + "5.2": "5.2.5", } @@ -574,17 +574,17 @@ def run_tox(task, version, *args): "3.10": "3.10.18", "3.11": "3.11.13", "3.12": "3.12.11", - "3.13": "3.13.5", + "3.13": "3.13.7", "3.13t": "3.13t-dev", - "3.14": "3.14.0rc1", + "3.14": "3.14.0rc2", "3.14t": "3.14t-dev", "3.15": "3.15-dev", "3.15t": "3.15t-dev", "pypy3.9": "pypy3.9-7.3.16", "pypy3.10": "pypy3.10-7.3.19", - "pypy3.11": "pypy3.11-7.3.19", + "pypy3.11": "pypy3.11-7.3.20", } -ci_version = "3.10" # Keep this in sync with GH Actions main.yml and .readthedocs.yml +ci_version = "3.13" # Keep this in sync with GH Actions main.yml and .readthedocs.yml python_tests = task( if_changed=( From d15669e9c672fd79227959095bbf61164f8d6c6f Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sat, 16 Aug 2025 01:33:42 -0400 Subject: [PATCH 03/30] sphinx errors unless we adjust the newlines here?? "Block quote ends without a blank line; unexpected unindent" --- hypothesis-python/src/hypothesis/extra/_array_helpers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hypothesis-python/src/hypothesis/extra/_array_helpers.py b/hypothesis-python/src/hypothesis/extra/_array_helpers.py index 910c64695a..5c3aa48504 100644 --- a/hypothesis-python/src/hypothesis/extra/_array_helpers.py +++ b/hypothesis-python/src/hypothesis/extra/_array_helpers.py @@ -122,7 +122,8 @@ def valid_tuple_axes( min_size: int = 0, max_size: Optional[int] = None, ) -> st.SearchStrategy[tuple[int, ...]]: - """All tuples will have a length >= ``min_size`` and <= ``max_size``. The default + """ + All tuples will have a length >= ``min_size`` and <= ``max_size``. The default value for ``max_size`` is ``ndim``. Examples from this strategy shrink towards an empty tuple, which render most @@ -141,7 +142,6 @@ def valid_tuple_axes( .. code-block:: python any_axis_strategy = none() | integers(-ndim, ndim - 1) | valid_tuple_axes(ndim) - """ check_type(int, ndim, "ndim") check_type(int, min_size, "min_size") @@ -352,7 +352,8 @@ def mutually_broadcastable_shapes( min_side: int = 1, max_side: Optional[int] = None, ) -> st.SearchStrategy[BroadcastableShapes]: - """Return a strategy for a specified number of shapes N that are + """ + Return a strategy for a specified number of shapes N that are mutually-broadcastable with one another and with the provided base shape. * ``num_shapes`` is the number of mutually broadcast-compatible shapes to generate. @@ -385,7 +386,6 @@ def mutually_broadcastable_shapes( BroadcastableShapes(input_shapes=((), (), ()), result_shape=()) BroadcastableShapes(input_shapes=((3,), (), (3,)), result_shape=(3,)) BroadcastableShapes(input_shapes=((1, 2, 3), (3,), ()), result_shape=(1, 2, 3)) - """ arg_msg = "Pass either the `num_shapes` or the `signature` argument, but not both." if num_shapes is not not_set: From 4052c28f06a014b839e9a94699a34dc487a42174 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sat, 16 Aug 2025 01:34:01 -0400 Subject: [PATCH 04/30] ignore EncodingWarning with tzdata --- hypothesis-python/tests/conjecture/test_provider.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/hypothesis-python/tests/conjecture/test_provider.py b/hypothesis-python/tests/conjecture/test_provider.py index c8798d2f56..de7993659f 100644 --- a/hypothesis-python/tests/conjecture/test_provider.py +++ b/hypothesis-python/tests/conjecture/test_provider.py @@ -13,6 +13,7 @@ import math import sys import time +import warnings from contextlib import contextmanager, nullcontext from random import Random from threading import RLock @@ -798,9 +799,15 @@ def f(n): @pytest.mark.parametrize("provider", [HypothesisProvider, PrngProvider]) def test_provider_conformance(provider): - run_conformance_test( - provider, settings=settings(max_examples=20, stateful_step_count=20) - ) + with warnings.catch_warnings(): + # emitted by available_timezones() from st.timezone_keys() on 3.11+ + # with tzdata installed. see https://github.com/python/cpython/issues/137841. + # Once cpython fixes this, we can remove this. + if sys.version_info >= (3, 10): + warnings.simplefilter("ignore", EncodingWarning) # noqa: F821 + run_conformance_test( + provider, settings=settings(max_examples=20, stateful_step_count=20) + ) # see https://github.com/HypothesisWorks/hypothesis/issues/4462 and discussion From 8e1829f32339f938ffcd05ecb7ecdd9c726cad53 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Wed, 5 Nov 2025 10:52:16 -0800 Subject: [PATCH 05/30] fix issue ref --- hypothesis-python/src/hypothesis/internal/entropy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hypothesis-python/src/hypothesis/internal/entropy.py b/hypothesis-python/src/hypothesis/internal/entropy.py index 4987d2b9a0..6aea67a54b 100644 --- a/hypothesis-python/src/hypothesis/internal/entropy.py +++ b/hypothesis-python/src/hypothesis/internal/entropy.py @@ -244,7 +244,7 @@ def restore_all() -> None: def deterministic_PRNG(seed: int = 0) -> Generator[None, None, None]: """Context manager that handles random.seed without polluting global state. - See issue #1255 and PR #1295 for details and motivation - in short, + See issue #1266 and PR #1295 for details and motivation - in short, leaving the global pseudo-random number generator (PRNG) seeded is a very bad idea in principle, and breaks all kinds of independence assumptions in practice. From ac86f4112173b1eeb37ac0b54ddf5ef38a518c5f Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Wed, 7 Jan 2026 00:48:46 -0500 Subject: [PATCH 06/30] fix merge --- .github/workflows/main.yml | 79 ++---------------- requirements/coverage.txt | 55 +++++------- requirements/crosshair.txt | 38 ++++----- requirements/fuzzing.txt | 85 ++++++++----------- requirements/test.txt | 16 +--- requirements/tools.txt | 166 ++++++++++++++++--------------------- 6 files changed, 151 insertions(+), 288 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 29dafce270..4304147a73 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -177,33 +177,11 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Set up Python 3.13 - uses: actions/setup-python@v4 - with: - python-version: "3.13" - - name: Restore cache - uses: actions/cache@v3 + - name: "Install base for Python ${{ env.PYTHON_VERSION }}" + uses: ./.github/actions/install-base with: - path: | - ~/.cache - ~/wheelhouse - ~/.local - vendor/bundle - .tox/ - key: deps-${{ runner.os }}-${{ hashFiles('requirements/*.txt') }}-${{ matrix.task }} - restore-keys: | - deps-${{ runner.os }}-${{ hashFiles('requirements/*.txt') }} - deps-${{ runner.os }} - - name: Install dotnet6 for Pyjion - if: ${{ endsWith(matrix.task, '-pyjion') }} - run: | - wget https://packages.microsoft.com/config/ubuntu/21.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb - sudo dpkg -i packages-microsoft-prod.deb - rm packages-microsoft-prod.deb - sudo apt-get update - sudo apt-get install -y apt-transport-https && \ - sudo apt-get update && \ - sudo apt-get install -y dotnet-sdk-6.0 + python-version: ${{ env.PYTHON_VERSION }} + task: ${{ matrix.task }} - name: Run tests run: | export TASK=${{ matrix.task }} @@ -260,37 +238,9 @@ jobs: pip install --upgrade setuptools pip wheel pip install tox - name: Run tests - # remove this ignore when https://github.com/pschanely/hypothesis-crosshair/issues/40 is fixed - run: python -m pytest -W "ignore:hypothesis.internal.observability.TESTCASE_CALLBACKS is deprecated" --numprocesses auto ${{ matrix.whichtests == 'nocover' && 'hypothesis-python/tests/nocover' || 'hypothesis-python/tests/ --ignore=hypothesis-python/tests/nocover/ --ignore=hypothesis-python/tests/quality/ --ignore=hypothesis-python/tests/ghostwriter/ --ignore=hypothesis-python/tests/patching/' }} - - test-osx: - runs-on: macos-latest - strategy: - matrix: - task: - - check-py310-cover - - check-py310-nocover - - check-py310-niche - - check-py311-cover - fail-fast: false - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Set up Python 3.13 - uses: actions/setup-python@v4 - with: - python-version: "3.13" - - name: Restore cache - uses: actions/cache@v3 - with: - path: | - ~/.cache - ~/Library/Caches/pip - .tox/ - key: deps-${{ runner.os }}-${{ hashFiles('requirements/*.txt') }}-${{ matrix.task }} - - name: Run tests - run: TASK=${{ matrix.task }} ./build.sh + working-directory: ./hypothesis-python + run: | + tox -e ${{ matrix.task }} # See https://pyodide.org/en/stable/usage/building-and-testing-packages.html # and https://github.com/numpy/numpy/blob/9a650391651c8486d8cb8b27b0e75aed5d36033e/.github/workflows/emscripten.yml @@ -364,22 +314,9 @@ jobs: with: fetch-depth: 0 token: ${{ secrets.GH_TOKEN }} - - name: Set up Python 3.13 - uses: actions/setup-python@v4 + - uses: ./.github/actions/install-base with: python-version: "3.13" - - name: Restore cache - uses: actions/cache@v3 - with: - path: | - ~/.cache - ~/wheelhouse - ~/.local - vendor/bundle - key: deps-${{ runner.os }}-${{ hashFiles('requirements/*.txt') }}-${{ matrix.task }} - restore-keys: | - deps-${{ runner.os }}-${{ hashFiles('requirements/*.txt') }} - deps-${{ runner.os }} - name: Deploy package env: GH_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/requirements/coverage.txt b/requirements/coverage.txt index cd0ef48187..600bdb0182 100644 --- a/requirements/coverage.txt +++ b/requirements/coverage.txt @@ -6,35 +6,29 @@ # annotated-types==0.7.0 # via -r requirements/coverage.in -attrs==25.3.0 - # via hypothesis (hypothesis-python/pyproject.toml) -black==25.1.0 +black==25.12.0 # via -r requirements/coverage.in -click==8.2.1 +click==8.3.1 # via # -r requirements/coverage.in # black -coverage[toml]==7.10.5 +coverage[toml]==7.13.1 # via pytest-cov dpcontracts==0.6.0 # via -r requirements/coverage.in -exceptiongroup==1.3.0 ; python_version < "3.11" - # via - # hypothesis (hypothesis-python/pyproject.toml) - # pytest -execnet==2.1.1 +execnet==2.1.2 # via pytest-xdist -fakeredis==2.31.0 +fakeredis==2.33.0 # via -r requirements/coverage.in -iniconfig==2.1.0 +iniconfig==2.3.0 # via pytest -lark==1.2.2 +lark==1.3.1 # via -r requirements/coverage.in -libcst==1.8.2 +libcst==1.8.6 # via -r requirements/coverage.in mypy-extensions==1.1.0 # via black -numpy==2.2.6 +numpy==2.4.0 # via # -r requirements/coverage.in # pandas @@ -42,13 +36,13 @@ packaging==25.0 # via # black # pytest -pandas==2.3.2 +pandas==2.3.3 # via -r requirements/coverage.in -pathspec==0.12.1 +pathspec==1.0.1 # via black pexpect==4.9.0 # via -r requirements/test.in -platformdirs==4.3.8 +platformdirs==4.5.1 # via black pluggy==1.6.0 # via @@ -56,16 +50,16 @@ pluggy==1.6.0 # pytest-cov ptyprocess==0.7.0 # via pexpect -pyarrow==21.0.0 +pyarrow==22.0.0 # via -r requirements/coverage.in pygments==2.19.2 # via pytest -pytest==8.4.1 +pytest==9.0.2 # via # -r requirements/test.in # pytest-cov # pytest-xdist -pytest-cov==6.2.1 +pytest-cov==7.0.0 # via -r requirements/coverage.in pytest-xdist==3.8.0 # via -r requirements/test.in @@ -73,13 +67,15 @@ python-dateutil==2.9.0.post0 # via # -r requirements/coverage.in # pandas +pytokens==0.3.0 + # via black pytz==2025.2 # via # -r requirements/coverage.in # pandas pyyaml-ft==8.0.0 # via libcst -redis==6.4.0 +redis==7.1.0 # via fakeredis six==1.17.0 # via python-dateutil @@ -87,18 +83,9 @@ sortedcontainers==2.4.0 # via # fakeredis # hypothesis (hypothesis-python/pyproject.toml) -tomli==2.2.1 - # via - # black - # coverage - # pytest -typing-extensions==4.14.1 - # via - # -r requirements/coverage.in - # black - # exceptiongroup - # fakeredis -tzdata==2025.2 +typing-extensions==4.15.0 + # via -r requirements/coverage.in +tzdata==2025.3 # via pandas watchdog==6.0.0 # via -r requirements/coverage.in diff --git a/requirements/crosshair.txt b/requirements/crosshair.txt index 943977fdc1..198c86b13a 100644 --- a/requirements/crosshair.txt +++ b/requirements/crosshair.txt @@ -4,39 +4,32 @@ # # ./build.sh upgrade-requirements # -attrs==25.3.0 +attrs==25.4.0 # via # cattrs - # hypothesis - # hypothesis (hypothesis-python/pyproject.toml) # lsprotocol -cattrs==25.1.1 + # pygls +cattrs==25.3.0 # via # lsprotocol # pygls -crosshair-tool==0.0.95 +crosshair-tool==0.0.101 # via # -r requirements/crosshair.in # hypothesis-crosshair -exceptiongroup==1.3.0 ; python_version < "3.11" - # via - # cattrs - # hypothesis - # hypothesis (hypothesis-python/pyproject.toml) - # pytest -execnet==2.1.1 +execnet==2.1.2 # via pytest-xdist -hypothesis==6.138.2 +hypothesis==6.150.0 # via hypothesis-crosshair -hypothesis-crosshair==0.0.25 +hypothesis-crosshair==0.0.27 # via -r requirements/crosshair.in -importlib-metadata==8.7.0 +importlib-metadata==8.7.1 # via crosshair-tool importlib-resources==6.5.2 # via typeshed-client -iniconfig==2.1.0 +iniconfig==2.3.0 # via pytest -lsprotocol==2023.0.1 +lsprotocol==2025.0.0 # via pygls mypy-extensions==1.1.0 # via typing-inspect @@ -50,11 +43,11 @@ pluggy==1.6.0 # via pytest ptyprocess==0.7.0 # via pexpect -pygls==1.3.1 +pygls==2.0.0 # via crosshair-tool pygments==2.19.2 # via pytest -pytest==8.4.1 +pytest==9.0.2 # via # -r requirements/test.in # pytest-xdist @@ -64,20 +57,17 @@ sortedcontainers==2.4.0 # via # hypothesis # hypothesis (hypothesis-python/pyproject.toml) -tomli==2.2.1 - # via pytest typeshed-client==2.8.2 # via crosshair-tool -typing-extensions==4.14.1 +typing-extensions==4.15.0 # via # cattrs # crosshair-tool - # exceptiongroup # typeshed-client # typing-inspect typing-inspect==0.9.0 # via crosshair-tool -z3-solver==4.15.3.0 +z3-solver==4.15.4.0 # via crosshair-tool zipp==3.23.0 # via importlib-metadata diff --git a/requirements/fuzzing.txt b/requirements/fuzzing.txt index 1d50a90bbc..d57dd474d8 100644 --- a/requirements/fuzzing.txt +++ b/requirements/fuzzing.txt @@ -6,42 +6,31 @@ # annotated-types==0.7.0 # via -r requirements/coverage.in -anyio==4.10.0 +anyio==4.12.1 # via starlette -attrs==25.3.0 +attrs==25.4.0 # via - # hypothesis - # hypothesis (hypothesis-python/pyproject.toml) # outcome # trio -black==25.1.0 +black==25.12.0 # via # -r requirements/coverage.in # hypofuzz # hypothesis -click==8.2.1 +click==8.3.1 # via # -r requirements/coverage.in # black # hypothesis -coverage[toml]==7.10.5 +coverage[toml]==7.13.1 # via # hypofuzz # pytest-cov dpcontracts==0.6.0 # via -r requirements/coverage.in -exceptiongroup==1.3.0 ; python_version < "3.11" - # via - # anyio - # hypercorn - # hypothesis - # hypothesis (hypothesis-python/pyproject.toml) - # pytest - # taskgroup - # trio -execnet==2.1.1 +execnet==2.1.2 # via pytest-xdist -fakeredis==2.31.0 +fakeredis==2.33.0 # via -r requirements/coverage.in h11==0.16.0 # via @@ -51,23 +40,23 @@ h2==4.3.0 # via hypercorn hpack==4.1.0 # via h2 -hypercorn==0.17.3 +hypercorn==0.18.0 # via hypofuzz hyperframe==6.1.0 # via h2 -hypofuzz==25.8.2 +hypofuzz==25.11.1 # via -r requirements/fuzzing.in -hypothesis[cli,watchdog]==6.138.2 +hypothesis[cli,watchdog]==6.150.0 # via hypofuzz -idna==3.10 +idna==3.11 # via # anyio # trio -iniconfig==2.1.0 +iniconfig==2.3.0 # via pytest -lark==1.2.2 +lark==1.3.1 # via -r requirements/coverage.in -libcst==1.8.2 +libcst==1.8.6 # via # -r requirements/coverage.in # hypofuzz @@ -77,7 +66,7 @@ mdurl==0.1.2 # via markdown-it-py mypy-extensions==1.1.0 # via black -numpy==2.2.6 +numpy==2.4.0 # via # -r requirements/coverage.in # pandas @@ -87,13 +76,13 @@ packaging==25.0 # via # black # pytest -pandas==2.3.2 +pandas==2.3.3 # via -r requirements/coverage.in -pathspec==0.12.1 +pathspec==1.0.1 # via black pexpect==4.9.0 # via -r requirements/test.in -platformdirs==4.3.8 +platformdirs==4.5.1 # via black pluggy==1.6.0 # via @@ -101,23 +90,23 @@ pluggy==1.6.0 # pytest-cov priority==2.0.0 # via hypercorn -psutil==7.0.0 +psutil==7.2.1 # via hypofuzz ptyprocess==0.7.0 # via pexpect -pyarrow==21.0.0 +pyarrow==22.0.0 # via -r requirements/coverage.in pygments==2.19.2 # via # pytest # rich -pytest==8.4.1 +pytest==9.0.2 # via # -r requirements/test.in # hypofuzz # pytest-cov # pytest-xdist -pytest-cov==6.2.1 +pytest-cov==7.0.0 # via -r requirements/coverage.in pytest-xdist==3.8.0 # via -r requirements/test.in @@ -125,47 +114,39 @@ python-dateutil==2.9.0.post0 # via # -r requirements/coverage.in # pandas +pytokens==0.3.0 + # via black pytz==2025.2 # via # -r requirements/coverage.in # pandas pyyaml-ft==8.0.0 # via libcst -redis==6.4.0 +redis==7.1.0 # via fakeredis -rich==14.1.0 +rich==14.2.0 # via hypothesis six==1.17.0 # via python-dateutil sniffio==1.3.1 - # via - # anyio - # trio + # via trio sortedcontainers==2.4.0 # via # fakeredis # hypothesis # hypothesis (hypothesis-python/pyproject.toml) # trio -starlette==0.47.2 +starlette==0.50.0 # via hypofuzz -trio==0.30.0 +trio==0.32.0 # via hypofuzz -typing-extensions==4.14.1 - # via - # -r requirements/coverage.in - # anyio - # black - # exceptiongroup - # fakeredis - # hypercorn - # starlette - # taskgroup -tzdata==2025.2 +typing-extensions==4.15.0 + # via -r requirements/coverage.in +tzdata==2025.3 # via pandas watchdog==6.0.0 # via # -r requirements/coverage.in # hypothesis -wsproto==1.2.0 +wsproto==1.3.2 # via hypercorn diff --git a/requirements/test.txt b/requirements/test.txt index 522fe8da29..8ce2b0b6bb 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -4,15 +4,9 @@ # # ./build.sh upgrade-requirements # -attrs==25.3.0 - # via hypothesis (hypothesis-python/pyproject.toml) -exceptiongroup==1.3.0 ; python_version < "3.11" - # via - # hypothesis (hypothesis-python/pyproject.toml) - # pytest -execnet==2.1.1 +execnet==2.1.2 # via pytest-xdist -iniconfig==2.1.0 +iniconfig==2.3.0 # via pytest packaging==25.0 # via pytest @@ -24,7 +18,7 @@ ptyprocess==0.7.0 # via pexpect pygments==2.19.2 # via pytest -pytest==8.4.1 +pytest==9.0.2 # via # -r requirements/test.in # pytest-xdist @@ -32,7 +26,3 @@ pytest-xdist==3.8.0 # via -r requirements/test.in sortedcontainers==2.4.0 # via hypothesis (hypothesis-python/pyproject.toml) -tomli==2.2.1 - # via pytest -typing-extensions==4.14.1 - # via exceptiongroup diff --git a/requirements/tools.txt b/requirements/tools.txt index 6aaefecd44..b2f41b9509 100644 --- a/requirements/tools.txt +++ b/requirements/tools.txt @@ -8,23 +8,21 @@ accessible-pygments==0.0.5 # via furo alabaster==1.0.0 # via sphinx -anyio==4.10.0 +anyio==4.12.1 # via watchfiles -asgiref==3.9.1 +asgiref==3.11.0 # via django -asttokens==3.0.0 +asttokens==3.0.1 # via stack-data -attrs==25.3.0 - # via hypothesis (hypothesis-python/pyproject.toml) autoflake==2.3.1 # via shed babel==2.17.0 # via sphinx -beautifulsoup4==4.13.4 +beautifulsoup4==4.14.3 # via # furo # sphinx-codeautolink -black==25.1.0 +black==25.12.0 # via shed blinker==1.9.0 # via pelican @@ -32,17 +30,17 @@ build==1.3.0 # via # -r requirements/tools.in # pip-tools -cachetools==6.1.0 +cachetools==6.2.4 # via tox -certifi==2025.8.3 +certifi==2026.1.4 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography chardet==5.2.0 # via tox -charset-normalizer==3.4.3 +charset-normalizer==3.4.4 # via requests -click==8.2.1 +click==8.3.1 # via # black # pip-tools @@ -52,9 +50,9 @@ colorama==0.4.6 # via tox com2ann==0.3.0 # via shed -coverage==7.10.5 +coverage==7.13.1 # via -r requirements/tools.in -cryptography==45.0.6 +cryptography==46.0.3 # via # types-pyopenssl # types-redis @@ -62,9 +60,9 @@ decorator==5.2.1 # via ipython distlib==0.4.0 # via virtualenv -django==5.2.5 +django==6.0.1 # via -r requirements/tools.in -docutils==0.21.2 +docutils==0.22.4 # via # pelican # readme-renderer @@ -73,47 +71,39 @@ docutils==0.21.2 # sphinx-jsonschema dpcontracts==0.6.0 # via -r requirements/tools.in -exceptiongroup==1.3.0 ; python_version < "3.11" - # via - # anyio - # hypothesis (hypothesis-python/pyproject.toml) - # ipython - # pytest -execnet==2.1.1 +execnet==2.1.2 # via pytest-xdist -executing==2.2.0 +executing==2.2.1 # via stack-data feedgenerator==2.2.1 # via pelican -filelock==3.19.1 +filelock==3.20.2 # via # tox # virtualenv -furo==2025.7.19 +furo==2025.12.19 # via -r requirements/tools.in id==1.5.0 # via twine -idna==3.10 +idna==3.11 # via # anyio # requests imagesize==1.4.1 # via sphinx -importlib-metadata==8.7.0 - # via keyring -iniconfig==2.1.0 +iniconfig==2.3.0 # via pytest -ipython==8.37.0 +ipython==9.9.0 # via -r requirements/tools.in ipython-pygments-lexers==1.1.1 # via ipython -isort==6.0.1 +isort==7.0.0 # via shed jaraco-classes==3.4.0 # via keyring -jaraco-context==6.0.1 +jaraco-context==6.0.2 # via keyring -jaraco-functools==4.3.0 +jaraco-functools==4.4.0 # via keyring jedi==0.19.2 # via ipython @@ -123,39 +113,41 @@ jinja2==3.1.6 # sphinx jsonpointer==3.0.0 # via sphinx-jsonschema -keyring==25.6.0 +keyring==25.7.0 # via twine -lark==1.2.2 +lark==1.3.1 # via -r requirements/tools.in -libcst==1.8.2 +libcst==1.8.6 # via # -r requirements/tools.in # shed -markdown==3.8.2 +librt==0.7.7 + # via mypy +markdown==3.10 # via pelican markdown-it-py==4.0.0 # via rich -markupsafe==3.0.2 +markupsafe==3.0.3 # via jinja2 -matplotlib-inline==0.1.7 +matplotlib-inline==0.2.1 # via ipython mdurl==0.1.2 # via markdown-it-py -more-itertools==10.7.0 +more-itertools==10.8.0 # via # jaraco-classes # jaraco-functools -mypy==1.17.1 +mypy==1.19.1 # via -r requirements/tools.in mypy-extensions==1.1.0 # via # black # mypy -nh3==0.3.0 +nh3==0.3.2 # via readme-renderer -nodeenv==1.9.1 +nodeenv==1.10.0 # via pyright -numpy==2.2.6 +numpy==2.4.0 # via -r requirements/tools.in ordered-set==4.1.0 # via pelican @@ -170,19 +162,19 @@ packaging==25.0 # twine parso==0.8.5 # via jedi -pathspec==0.12.1 +pathspec==1.0.1 # via # black # mypy -pelican[markdown]==4.11.0 +pelican[markdown]==4.11.0.post0 # via -r requirements/tools.in pexpect==4.9.0 # via # -r requirements/test.in # ipython -pip-tools==7.5.0 +pip-tools==7.5.2 # via -r requirements/tools.in -platformdirs==4.3.8 +platformdirs==4.5.1 # via # black # tox @@ -191,13 +183,13 @@ pluggy==1.6.0 # via # pytest # tox -prompt-toolkit==3.0.51 +prompt-toolkit==3.0.52 # via ipython ptyprocess==0.7.0 # via pexpect pure-eval==0.2.3 # via stack-data -pycparser==2.22 +pycparser==2.23 # via cffi pyflakes==3.4.0 # via autoflake @@ -212,15 +204,15 @@ pygments==2.18.0 # readme-renderer # rich # sphinx -pyproject-api==1.9.1 +pyproject-api==1.10.0 # via tox pyproject-hooks==1.2.0 # via # build # pip-tools -pyright==1.1.404 +pyright==1.1.407 # via -r requirements/tools.in -pytest==8.4.1 +pytest==9.0.2 # via # -r requirements/test.in # pytest-xdist @@ -230,9 +222,11 @@ python-dateutil==2.9.0.post0 # via # -r requirements/tools.in # pelican -pyupgrade==3.20.0 +pytokens==0.3.0 + # via black +pyupgrade==3.21.2 # via shed -pyyaml==6.0.2 +pyyaml==6.0.3 # via sphinx-jsonschema pyyaml-ft==8.0.0 # via libcst @@ -248,22 +242,22 @@ requests==2.32.5 # twine requests-toolbelt==1.0.0 # via twine -restructuredtext-lint==1.4.0 +restructuredtext-lint==2.0.2 # via -r requirements/tools.in rfc3986==2.0.0 # via twine -rich==14.1.0 +rich==14.2.0 # via # pelican # twine -ruff==0.12.10 +roman-numerals==4.1.0 + # via sphinx +ruff==0.14.10 # via -r requirements/tools.in shed==2024.1.1 # via -r requirements/tools.in six==1.17.0 # via python-dateutil -sniffio==1.3.1 - # via anyio snowballstemmer==3.0.1 # via sphinx sortedcontainers==2.4.0 @@ -272,9 +266,9 @@ sortedcontainers==2.4.0 # sortedcontainers-stubs sortedcontainers-stubs==2.4.3 # via -r requirements/tools.in -soupsieve==2.7 +soupsieve==2.8.1 # via beautifulsoup4 -sphinx==8.2.3 +sphinx==9.1.0 # via # -r requirements/tools.in # furo @@ -284,7 +278,7 @@ sphinx-basic-ng==1.0.0b2 # via furo sphinx-codeautolink==0.17.5 # via -r requirements/tools.in -sphinx-jsonschema==1.19.1 +sphinx-jsonschema==1.19.2 # via -r requirements/tools.in sphinx-selective-exclude==1.0.3 # via -r requirements/tools.in @@ -300,76 +294,60 @@ sphinxcontrib-qthelp==2.0.0 # via sphinx sphinxcontrib-serializinghtml==2.0.0 # via sphinx -sqlparse==0.5.3 +sqlparse==0.5.5 # via django stack-data==0.6.3 # via ipython tokenize-rt==6.2.0 # via pyupgrade -tomli==2.2.1 - # via - # autoflake - # black - # build - # mypy - # pip-tools - # pyproject-api - # pytest - # sphinx - # tox -tox==4.28.4 +tomli==2.3.0 + # via -r requirements/tools.in +tox==4.33.0 # via -r requirements/tools.in traitlets==5.14.3 # via # ipython # matplotlib-inline -twine==6.1.0 +twine==6.2.0 # via -r requirements/tools.in -types-cffi==1.17.0.20250822 +types-cffi==1.17.0.20250915 # via types-pyopenssl types-click==7.1.8 # via -r requirements/tools.in types-pyopenssl==24.1.0.20240722 # via types-redis -types-pytz==2025.2.0.20250809 +types-pytz==2025.2.0.20251108 # via -r requirements/tools.in types-redis==4.6.0.20241004 # via -r requirements/tools.in -types-setuptools==80.9.0.20250822 +types-setuptools==80.9.0.20251223 # via types-cffi -typing-extensions==4.14.1 +typing-extensions==4.15.0 # via # -r requirements/tools.in # beautifulsoup4 - # black - # exceptiongroup - # ipython # mypy # pyright # sortedcontainers-stubs - # tox - # virtualenv unidecode==1.4.0 # via pelican -urllib3==2.5.0 +urllib3==2.6.2 # via # requests # twine -virtualenv==20.34.0 +virtualenv==20.35.4 # via tox watchdog==6.0.0 # via -r requirements/tools.in -watchfiles==1.1.0 +watchfiles==1.1.1 # via pelican -wcwidth==0.2.13 +wcwidth==0.2.14 # via prompt-toolkit wheel==0.45.1 # via pip-tools -zipp==3.23.0 - # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: -pip==25.2 +pip==25.3 # via pip-tools setuptools==80.9.0 # via pip-tools From 729ad705652e4d9c09c71a2626f516cff3b71645 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Wed, 7 Jan 2026 01:53:41 -0500 Subject: [PATCH 07/30] some fixes? --- .github/workflows/main.yml | 2 +- build.sh | 2 +- hypothesis-python/RELEASE.rst | 2 +- hypothesis-python/src/hypothesis/database.py | 18 ++++++++++++++---- .../src/hypothesis/internal/compat.py | 8 ++++---- .../hypothesis/strategies/_internal/core.py | 1 + .../hypothesis/strategies/_internal/types.py | 7 ++++++- .../tests/conjecture/test_provider.py | 4 ++-- requirements/tools.in | 1 + requirements/tools.txt | 6 ++++-- tooling/{README.rst => README.md} | 4 +--- 11 files changed, 36 insertions(+), 19 deletions(-) rename tooling/{README.rst => README.md} (67%) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4304147a73..5c3b6ecee9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -252,7 +252,7 @@ jobs: # Note that the versions below are updated by `update_pyodide_versions()` in our weekly cronjob. # The versions of pyodide-build and the Pyodide runtime may differ. PYODIDE_VERSION: 0.29.0 - PYODIDE_BUILD_VERSION: 0.30.9 + PYODIDE_BUILD_VERSION: 0.31.1 PYTHON_VERSION: 3.13.2 steps: - uses: actions/checkout@v3 diff --git a/build.sh b/build.sh index f71f4c9d16..e0af5105d5 100755 --- a/build.sh +++ b/build.sh @@ -34,7 +34,7 @@ fi TOOL_REQUIREMENTS="$ROOT/requirements/tools.txt" # append PYTHON_VERSION to bust caches when we upgrade versions -TOOL_HASH=$((cat "$TOOL_REQUIREMENTS" && echo "$PYTHON_VERSION") | "$PYTHON" "$SCRIPTS/tool-hash.py") +TOOL_HASH=$( (cat "$TOOL_REQUIREMENTS" && echo "$PYTHON_VERSION") | "$PYTHON" "$SCRIPTS/tool-hash.py") TOOL_VIRTUALENV="$VIRTUALENVS/build-$TOOL_HASH" TOOL_PYTHON="$TOOL_VIRTUALENV/bin/python" diff --git a/hypothesis-python/RELEASE.rst b/hypothesis-python/RELEASE.rst index 2364c92ace..3d24d900f6 100644 --- a/hypothesis-python/RELEASE.rst +++ b/hypothesis-python/RELEASE.rst @@ -1,3 +1,3 @@ RELEASE_TYPE: patch -Removes some internal ``# type: ignore`` comments as a result of bumping CI packages. +Update some internal type hints. diff --git a/hypothesis-python/src/hypothesis/database.py b/hypothesis-python/src/hypothesis/database.py index 68568675f3..4ff64072e2 100644 --- a/hypothesis-python/src/hypothesis/database.py +++ b/hypothesis-python/src/hypothesis/database.py @@ -134,16 +134,26 @@ def __call__(self, *args: Any, **kwargs: Any) -> "ExampleDatabase": # downstream ExampleDatabase subclasses too. if "sphinx" in sys.modules: try: + from types import ModuleType + import sphinx.ext.autodoc signature = "hypothesis.database._EDMeta.__call__" + _module: ModuleType # make mypy happy + + # _METACLASS_CALL_BLACKLIST moved in newer sphinx versions + try: + import sphinx.ext.autodoc._dynamic._signatures as _module + except ImportError: + _module = sphinx.ext.autodoc + # _METACLASS_CALL_BLACKLIST is a frozenset in later sphinx versions - if isinstance(sphinx.ext.autodoc._METACLASS_CALL_BLACKLIST, frozenset): - sphinx.ext.autodoc._METACLASS_CALL_BLACKLIST = ( - sphinx.ext.autodoc._METACLASS_CALL_BLACKLIST | {signature} + if isinstance(_module._METACLASS_CALL_BLACKLIST, frozenset): + _module._METACLASS_CALL_BLACKLIST = ( # type: ignore + _module._METACLASS_CALL_BLACKLIST | {signature} ) else: - sphinx.ext.autodoc._METACLASS_CALL_BLACKLIST.append(signature) + _module._METACLASS_CALL_BLACKLIST.append(signature) except Exception: pass diff --git a/hypothesis-python/src/hypothesis/internal/compat.py b/hypothesis-python/src/hypothesis/internal/compat.py index 0c82a53e30..7807672774 100644 --- a/hypothesis-python/src/hypothesis/internal/compat.py +++ b/hypothesis-python/src/hypothesis/internal/compat.py @@ -27,10 +27,10 @@ get_args, ) -try: - BaseExceptionGroup = BaseExceptionGroup - ExceptionGroup = ExceptionGroup # pragma: no cover -except NameError: +if sys.version_info >= (3, 11): + BaseExceptionGroup = BaseExceptionGroup # noqa: F821 + ExceptionGroup = ExceptionGroup # noqa: F821 +else: from exceptiongroup import ( BaseExceptionGroup as BaseExceptionGroup, ExceptionGroup as ExceptionGroup, diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/core.py b/hypothesis-python/src/hypothesis/strategies/_internal/core.py index 1006fc792d..1192de1a91 100644 --- a/hypothesis-python/src/hypothesis/strategies/_internal/core.py +++ b/hypothesis-python/src/hypothesis/strategies/_internal/core.py @@ -2499,6 +2499,7 @@ def register_type_strategy( ) if ( "pydantic.generics" in sys.modules + and isinstance(custom_type, type) and issubclass(custom_type, sys.modules["pydantic.generics"].GenericModel) and not re.search(r"[A-Za-z_]+\[.+\]", repr(custom_type)) and callable(strategy) diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/types.py b/hypothesis-python/src/hypothesis/strategies/_internal/types.py index 21c69de05c..b5ddd18a16 100644 --- a/hypothesis-python/src/hypothesis/strategies/_internal/types.py +++ b/hypothesis-python/src/hypothesis/strategies/_internal/types.py @@ -717,8 +717,13 @@ def _networks(bits): # exposed for it, and NotImplemented itself is typed as Any so that it can be # returned without being listed in a function signature: # https://github.com/python/mypy/issues/6710#issuecomment-485580032 +if sys.version_info < (3, 12): + _RegistryKeyT: typing.TypeAlias = type +else: # pragma: no cover + _RegistryKeyT: typing.TypeAlias = type | typing.TypeAliasType + _global_type_lookup: dict[ - type, st.SearchStrategy | typing.Callable[[type], st.SearchStrategy] + _RegistryKeyT, st.SearchStrategy | typing.Callable[[type], st.SearchStrategy] ] = { type(None): st.none(), bool: st.booleans(), diff --git a/hypothesis-python/tests/conjecture/test_provider.py b/hypothesis-python/tests/conjecture/test_provider.py index 80aca0526c..0349119b30 100644 --- a/hypothesis-python/tests/conjecture/test_provider.py +++ b/hypothesis-python/tests/conjecture/test_provider.py @@ -795,8 +795,8 @@ def test_provider_conformance(provider): # emitted by available_timezones() from st.timezone_keys() on 3.11+ # with tzdata installed. see https://github.com/python/cpython/issues/137841. # Once cpython fixes this, we can remove this. - if sys.version_info >= (3, 10): - warnings.simplefilter("ignore", EncodingWarning) # noqa: F821 + if sys.version_info >= (3, 11): + warnings.simplefilter("ignore", EncodingWarning) run_conformance_test( provider, settings=settings(max_examples=20, stateful_step_count=20) ) diff --git a/requirements/tools.in b/requirements/tools.in index 2f107c40b7..fca70d91a6 100644 --- a/requirements/tools.in +++ b/requirements/tools.in @@ -29,5 +29,6 @@ typing-extensions watchdog # for typing build sortedcontainers-stubs # for typing +attrs # for typing tomli # for update_pyproject_toml -r test.in diff --git a/requirements/tools.txt b/requirements/tools.txt index b2f41b9509..04e6ac6475 100644 --- a/requirements/tools.txt +++ b/requirements/tools.txt @@ -14,6 +14,8 @@ asgiref==3.11.0 # via django asttokens==3.0.1 # via stack-data +attrs==25.4.0 + # via -r requirements/tools.in autoflake==2.3.1 # via shed babel==2.17.0 @@ -60,7 +62,7 @@ decorator==5.2.1 # via ipython distlib==0.4.0 # via virtualenv -django==6.0.1 +django==5.2.10 # via -r requirements/tools.in docutils==0.22.4 # via @@ -268,7 +270,7 @@ sortedcontainers-stubs==2.4.3 # via -r requirements/tools.in soupsieve==2.8.1 # via beautifulsoup4 -sphinx==9.1.0 +sphinx==9.0.4 # via # -r requirements/tools.in # furo diff --git a/tooling/README.rst b/tooling/README.md similarity index 67% rename from tooling/README.rst rename to tooling/README.md index 7d42af830f..b2c2276d7c 100644 --- a/tooling/README.rst +++ b/tooling/README.md @@ -1,6 +1,4 @@ -======================== -Hypothesis Build Tooling -======================== +# Hypothesis Build Tooling This is a piece of software for managing Hypothesis's build tasks, releases, etc. It's very Hypothesis specific, though it may become less so in the future. From 34958cd74e30425e5838b2f9c22b0de3c1a071fb Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 8 Jan 2026 23:59:11 -0500 Subject: [PATCH 08/30] coverage --- .../src/hypothesis/internal/compat.py | 57 ++++++++++--------- .../hypothesis/strategies/_internal/core.py | 1 + .../hypothesis/strategies/_internal/types.py | 2 +- hypothesis-python/tests/cover/test_lookup.py | 14 ++--- 4 files changed, 38 insertions(+), 36 deletions(-) diff --git a/hypothesis-python/src/hypothesis/internal/compat.py b/hypothesis-python/src/hypothesis/internal/compat.py index 7807672774..23ccc42815 100644 --- a/hypothesis-python/src/hypothesis/internal/compat.py +++ b/hypothesis-python/src/hypothesis/internal/compat.py @@ -30,7 +30,7 @@ if sys.version_info >= (3, 11): BaseExceptionGroup = BaseExceptionGroup # noqa: F821 ExceptionGroup = ExceptionGroup # noqa: F821 -else: +else: # pragma: no cover from exceptiongroup import ( BaseExceptionGroup as BaseExceptionGroup, ExceptionGroup as ExceptionGroup, @@ -47,7 +47,7 @@ # In order to use NotRequired, we need the version of TypedDict included in Python 3.11+. if sys.version_info[:2] >= (3, 11): from typing import NotRequired as NotRequired, TypedDict as TypedDict - else: + else: # pragma: no cover try: from typing_extensions import ( NotRequired as NotRequired, @@ -65,7 +65,7 @@ def __class_getitem__(cls, item): from typing import ( override as override, ) - except ImportError: + except ImportError: # pragma: no cover try: from typing_extensions import ( override as override, @@ -83,7 +83,7 @@ def __class_getitem__(cls, item): def add_note(exc, note): try: exc.add_note(note) - except AttributeError: + except AttributeError: # pragma: no cover if not hasattr(exc, "__notes__"): try: exc.__notes__ = [] @@ -251,6 +251,31 @@ def bad_django_TestCase(runner: Optional["ConjectureRunner"]) -> bool: # see issue #3812 if sys.version_info[:2] < (3, 12): + def _asdict_inner(obj, dict_factory): + if dataclasses._is_dataclass_instance(obj): + return dict_factory( + (f.name, _asdict_inner(getattr(obj, f.name), dict_factory)) + for f in dataclasses.fields(obj) + ) + elif isinstance(obj, tuple) and hasattr(obj, "_fields"): + return type(obj)(*[_asdict_inner(v, dict_factory) for v in obj]) + elif isinstance(obj, (list, tuple)): + return type(obj)(_asdict_inner(v, dict_factory) for v in obj) + elif isinstance(obj, dict): + if hasattr(type(obj), "default_factory"): + result = type(obj)(obj.default_factory) + for k, v in obj.items(): + result[_asdict_inner(k, dict_factory)] = _asdict_inner( + v, dict_factory + ) + return result + return type(obj)( + (_asdict_inner(k, dict_factory), _asdict_inner(v, dict_factory)) + for k, v in obj.items() + ) + else: + return copy.deepcopy(obj) + def dataclass_asdict(obj, *, dict_factory=dict): """ A vendored variant of dataclasses.asdict. Includes the bugfix for @@ -267,30 +292,6 @@ def dataclass_asdict(obj, *, dict_factory=dict): dataclass_asdict = dataclasses.asdict -def _asdict_inner(obj, dict_factory): - if dataclasses._is_dataclass_instance(obj): - return dict_factory( - (f.name, _asdict_inner(getattr(obj, f.name), dict_factory)) - for f in dataclasses.fields(obj) - ) - elif isinstance(obj, tuple) and hasattr(obj, "_fields"): - return type(obj)(*[_asdict_inner(v, dict_factory) for v in obj]) - elif isinstance(obj, (list, tuple)): - return type(obj)(_asdict_inner(v, dict_factory) for v in obj) - elif isinstance(obj, dict): - if hasattr(type(obj), "default_factory"): - result = type(obj)(obj.default_factory) - for k, v in obj.items(): - result[_asdict_inner(k, dict_factory)] = _asdict_inner(v, dict_factory) - return result - return type(obj)( - (_asdict_inner(k, dict_factory), _asdict_inner(v, dict_factory)) - for k, v in obj.items() - ) - else: - return copy.deepcopy(obj) - - if sys.version_info[:2] < (3, 13): # batched was added in 3.12, strict flag in 3.13 # copied from 3.13 docs reference implementation diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/core.py b/hypothesis-python/src/hypothesis/strategies/_internal/core.py index 1192de1a91..cfcb1f58bc 100644 --- a/hypothesis-python/src/hypothesis/strategies/_internal/core.py +++ b/hypothesis-python/src/hypothesis/strategies/_internal/core.py @@ -1067,6 +1067,7 @@ def do_draw(self, data: ConjectureData) -> Ex: with context.track_arg_label(k) as arg_label: kwargs[k] = data.draw(v) arg_labels |= arg_label + try: obj = self.target(*args, **kwargs) except TypeError as err: diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/types.py b/hypothesis-python/src/hypothesis/strategies/_internal/types.py index b5ddd18a16..407f1e39b0 100644 --- a/hypothesis-python/src/hypothesis/strategies/_internal/types.py +++ b/hypothesis-python/src/hypothesis/strategies/_internal/types.py @@ -202,7 +202,7 @@ ): try: NON_RUNTIME_TYPES += (getattr(typing, name),) - except AttributeError: + except AttributeError: # pragma: no cover pass try: NON_RUNTIME_TYPES += (getattr(typing_extensions, name),) diff --git a/hypothesis-python/tests/cover/test_lookup.py b/hypothesis-python/tests/cover/test_lookup.py index 05f999a5ca..535734fb59 100644 --- a/hypothesis-python/tests/cover/test_lookup.py +++ b/hypothesis-python/tests/cover/test_lookup.py @@ -665,27 +665,27 @@ def test_recursive_type_with_defaults_minimizes_to_defaults(): assert minimal(from_type(MyList), lambda ex: True) == MyList() -class A: - def __init__(self, nxt: typing.Optional["B"]): +class MutualA: + def __init__(self, nxt: typing.Optional["MutualB"]): self.nxt = nxt def __repr__(self): return f"A({self.nxt})" -class B: - def __init__(self, nxt: typing.Optional["A"]): +class MutualB: + def __init__(self, nxt: typing.Optional["MutualA"]): self.nxt = nxt def __repr__(self): return f"B({self.nxt})" -@given(nxt=st.from_type(A)) +@given(nxt=st.from_type(MutualA)) def test_resolving_mutually_recursive_types(nxt): i = 0 while nxt: - assert isinstance(nxt, [A, B][i % 2]) + assert isinstance(nxt, [MutualA, MutualB][i % 2]) nxt = nxt.nxt i += 1 @@ -696,7 +696,7 @@ def test_resolving_mutually_recursive_types_with_limited_stack(): sys.setrecursionlimit(current_stack_depth + 100) try: - @given(nxt=st.from_type(A)) + @given(nxt=st.from_type(MutualA)) def test(nxt): pass From 5a95a14b89eaf1a19482fedf62dfac83ce6ad3a8 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 9 Jan 2026 00:16:18 -0500 Subject: [PATCH 09/30] pin pandas versions --- .github/workflows/main.yml | 12 ++++++------ hypothesis-python/tox.ini | 12 ++++++------ tooling/src/hypothesistooling/__main__.py | 8 ++++++-- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5c3b6ecee9..ce4ee71e10 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -92,12 +92,12 @@ jobs: - check-pytest62 - check-django60 - check-django42 - - check-pandas22 - - check-pandas21 - - check-pandas20 - - check-pandas15 - - check-pandas14 - - check-pandas13 + - check-py313-pandas22 + - check-py312-pandas21 + - check-py311-pandas20 + - check-py311-pandas15 + - check-py310-pandas14 + - check-py310-pandas13 ## FIXME: actions update means Python builds without eg _bz2, which was required # - check-py310-pandas12 # - check-py310-pandas11 diff --git a/hypothesis-python/tox.ini b/hypothesis-python/tox.ini index bbb6027310..57eb41dbef 100644 --- a/hypothesis-python/tox.ini +++ b/hypothesis-python/tox.ini @@ -97,7 +97,7 @@ deps = commands = python -bb -X dev -m pytest tests/pandas -n auto -[testenv:pandas13] +[testenv:py310-pandas13] deps = -r../requirements/test.txt numpy~=1.26.4 @@ -105,7 +105,7 @@ deps = commands = python -bb -X dev -m pytest tests/pandas -n auto -[testenv:pandas14] +[testenv:py310-pandas14] deps = -r../requirements/test.txt numpy~=1.26.4 @@ -113,7 +113,7 @@ deps = commands = python -bb -X dev -m pytest tests/pandas -n auto -[testenv:pandas15] +[testenv:py311-pandas15] deps = -r../requirements/test.txt numpy~=1.26.4 @@ -121,7 +121,7 @@ deps = commands = python -bb -X dev -m pytest tests/pandas -n auto -[testenv:pandas20] +[testenv:py311-pandas20] deps = -r../requirements/test.txt numpy~=1.26.4 @@ -129,7 +129,7 @@ deps = commands = python -bb -X dev -m pytest tests/pandas -n auto -[testenv:pandas21] +[testenv:py312-pandas21] deps = -r../requirements/test.txt numpy~=1.26.4 @@ -139,7 +139,7 @@ setenv= commands = python -bb -X dev -m pytest tests/pandas -n auto -[testenv:pandas22] +[testenv:py313-pandas22] deps = -r../requirements/test.txt pandas~=2.2.2 diff --git a/tooling/src/hypothesistooling/__main__.py b/tooling/src/hypothesistooling/__main__.py index 633ac62d54..e88371fcf2 100644 --- a/tooling/src/hypothesistooling/__main__.py +++ b/tooling/src/hypothesistooling/__main__.py @@ -698,10 +698,14 @@ def standard_tox_task(name, py=ci_version): # we also test no-contrib on the latest django version standard_tox_task("django-nocontrib", py=dj_version) -for n in [13, 14, 15, 20, 21, 22]: - standard_tox_task(f"pandas{n}") standard_tox_task("py310-pandas11", py="3.10") standard_tox_task("py310-pandas12", py="3.10") +standard_tox_task("py310-pandas13", py="3.10") +standard_tox_task("py310-pandas14", py="3.10") +standard_tox_task("py311-pandas15", py="3.11") +standard_tox_task("py311-pandas20", py="3.11") +standard_tox_task("py312-pandas21", py="3.12") +standard_tox_task("py313-pandas22", py="3.13") for kind in ("cover", "nocover", "niche", "custom"): standard_tox_task(f"crosshair-{kind}") From 93352ae5583ddc4a6075efe8c17f9dd747b94c63 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 9 Jan 2026 13:58:55 -0500 Subject: [PATCH 10/30] nocover, tweak+enable more pytest ci jobs --- .github/workflows/main.yml | 6 ++++-- .../src/hypothesis/internal/lambda_sources.py | 2 +- .../src/hypothesis/internal/reflection.py | 2 +- hypothesis-python/tox.ini | 12 ++++++++++-- tooling/src/hypothesistooling/__main__.py | 7 +++++-- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ce4ee71e10..fece5d5622 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -88,8 +88,10 @@ jobs: - check-py313t-nocover - check-py313t-niche - check-quality - - check-pytest62 - - check-pytest62 + - check-pytest9 + - check-pytest84 + - check-pytest74 + - check-py311-pytest62 - check-django60 - check-django42 - check-py313-pandas22 diff --git a/hypothesis-python/src/hypothesis/internal/lambda_sources.py b/hypothesis-python/src/hypothesis/internal/lambda_sources.py index 596b4c8810..0803c3010f 100644 --- a/hypothesis-python/src/hypothesis/internal/lambda_sources.py +++ b/hypothesis-python/src/hypothesis/internal/lambda_sources.py @@ -301,7 +301,7 @@ def _lambda_code_matches_node(f, node): return _function_key(f) == _function_key(compiled) -def _check_unknown_perfectly_aligned_lambda(candidate): +def _check_unknown_perfectly_aligned_lambda(candidate): # pragma: no cover # This is a monkeypatch point for our self-tests, to make unknown # lambdas raise. pass diff --git a/hypothesis-python/src/hypothesis/internal/reflection.py b/hypothesis-python/src/hypothesis/internal/reflection.py index 87bf67a3e7..f462b10fcf 100644 --- a/hypothesis-python/src/hypothesis/internal/reflection.py +++ b/hypothesis-python/src/hypothesis/internal/reflection.py @@ -116,7 +116,7 @@ def function_digest(function: Any) -> bytes: return hasher.digest() -def check_signature(sig: Signature) -> None: +def check_signature(sig: Signature) -> None: # pragma: no cover # 3.10 only # Backport from Python 3.11; see https://github.com/python/cpython/pull/92065 for p in sig.parameters.values(): if iskeyword(p.name) and p.kind is not p.POSITIONAL_ONLY: diff --git a/hypothesis-python/tox.ini b/hypothesis-python/tox.ini index 57eb41dbef..2ea14c9db8 100644 --- a/hypothesis-python/tox.ini +++ b/hypothesis-python/tox.ini @@ -223,11 +223,19 @@ commands_pre = commands = python -bb -X dev -m pytest tests/pytest tests/cover/test_testdecorators.py -[testenv:pytest8] +[testenv:pytest84] deps = -r../requirements/test.txt commands_pre = - pip install pytest==8.* pytest-xdist + pip install pytest==8.4.2 pytest-xdist +commands = + python -bb -X dev -m pytest tests/pytest tests/cover/test_testdecorators.py + +[testenv:pytest9] +deps = + -r../requirements/test.txt +commands_pre = + pip install pytest==9.* pytest-xdist commands = python -bb -X dev -m pytest tests/pytest tests/cover/test_testdecorators.py diff --git a/tooling/src/hypothesistooling/__main__.py b/tooling/src/hypothesistooling/__main__.py index e88371fcf2..c87fe2c284 100644 --- a/tooling/src/hypothesistooling/__main__.py +++ b/tooling/src/hypothesistooling/__main__.py @@ -689,8 +689,10 @@ def standard_tox_task(name, py=ci_version): ) -# standard_tox_task("py310-pytest46", py="3.10") -standard_tox_task("pytest62") +standard_tox_task("py311-pytest62", py="3.11") # hits "ast.Str is deprecated" in 3.12+ +standard_tox_task("pytest74") +standard_tox_task("pytest84") +standard_tox_task("pytest9") dj_version = max(ci_version, "3.12") for n in DJANGO_VERSIONS: @@ -698,6 +700,7 @@ def standard_tox_task(name, py=ci_version): # we also test no-contrib on the latest django version standard_tox_task("django-nocontrib", py=dj_version) +# test each pandas version with the latest python version they support standard_tox_task("py310-pandas11", py="3.10") standard_tox_task("py310-pandas12", py="3.10") standard_tox_task("py310-pandas13", py="3.10") From 1dc1baebab81d802009df672582d83b0d99d1825 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 9 Jan 2026 14:44:50 -0500 Subject: [PATCH 11/30] more fixes --- hypothesis-python/tests/cover/test_lookup.py | 12 ++++++++++++ hypothesis-python/tests/test_annotated_types.py | 7 ++++++- tooling/src/hypothesistooling/installers.py | 3 +++ whole_repo_tests/types/revealed_types.py | 8 ++++---- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/hypothesis-python/tests/cover/test_lookup.py b/hypothesis-python/tests/cover/test_lookup.py index 535734fb59..11ed26386c 100644 --- a/hypothesis-python/tests/cover/test_lookup.py +++ b/hypothesis-python/tests/cover/test_lookup.py @@ -1002,6 +1002,18 @@ def test_hashable_type_unhashable_value(): ) +def test_unhashable_type(): + class UnhashableMeta(type): + __hash__ = None + + class UnhashableType(metaclass=UnhashableMeta): + pass + + assert_simple_property( + st.from_type(UnhashableType), lambda x: isinstance(x, UnhashableType) + ) + + class _EmptyClass: def __init__(self, value=-1) -> None: pass diff --git a/hypothesis-python/tests/test_annotated_types.py b/hypothesis-python/tests/test_annotated_types.py index 796070a854..1aa3b68907 100644 --- a/hypothesis-python/tests/test_annotated_types.py +++ b/hypothesis-python/tests/test_annotated_types.py @@ -20,7 +20,7 @@ from hypothesis.strategies._internal.strategies import FilteredStrategy from hypothesis.strategies._internal.types import _get_constraints -from tests.common.debug import check_can_generate_examples +from tests.common.debug import assert_simple_property, check_can_generate_examples try: import annotated_types as at @@ -118,6 +118,11 @@ def test_collection_size_from_slice(data): assert 1 <= len(value) <= 10 +def test_unhashable_annotated_metadata(): + t = Annotated[int, {"key": "value"}] + assert_simple_property(st.from_type(t), lambda x: isinstance(x, int)) + + class GroupedStuff: __is_annotated_types_grouped_metadata__ = True diff --git a/tooling/src/hypothesistooling/installers.py b/tooling/src/hypothesistooling/installers.py index 2519cfa09f..f533579050 100644 --- a/tooling/src/hypothesistooling/installers.py +++ b/tooling/src/hypothesistooling/installers.py @@ -56,6 +56,9 @@ def ensure_stack(): if os.path.exists(STACK): return subprocess.check_call("mkdir -p ~/.local/bin", shell=True) + # if you're on macos, this will error with "--wildcards is not supported" + # or similar. You should put shellcheck on your PATH with your package + # manager of choice; eg `brew install shellcheck`. subprocess.check_call( "curl -L https://www.stackage.org/stack/linux-x86_64 " "| tar xz --wildcards --strip-components=1 -C $HOME" diff --git a/whole_repo_tests/types/revealed_types.py b/whole_repo_tests/types/revealed_types.py index 2cc7c22398..fd211c511f 100644 --- a/whole_repo_tests/types/revealed_types.py +++ b/whole_repo_tests/types/revealed_types.py @@ -94,7 +94,7 @@ class DifferingRevealedTypes(NamedTuple): NUMPY_REVEALED_TYPES = [ ( 'arrays(dtype=np.dtype("int32"), shape=1)', - "ndarray[tuple[int, ...], dtype[signedinteger[_32Bit]]]", + "ndarray[tuple[Any, ...], dtype[signedinteger[_32Bit]]]", ), # ( # "arrays(dtype=np.dtype(int), shape=1)", @@ -199,15 +199,15 @@ class DifferingRevealedTypes(NamedTuple): ), ( "integer_array_indices(shape=(2, 3))", - "tuple[ndarray[tuple[int, ...], dtype[signedinteger[Any]]], ...]", + "tuple[ndarray[tuple[Any, ...], dtype[signedinteger[Any]]], ...]", ), ( 'integer_array_indices(shape=(2, 3), dtype=np.dtype("int32"))', - "tuple[ndarray[tuple[int, ...], dtype[signedinteger[_32Bit]]], ...]", + "tuple[ndarray[tuple[Any, ...], dtype[signedinteger[_32Bit]]], ...]", ), ( 'integer_array_indices(shape=(2, 3), dtype=np.dtype("uint8"))', - "tuple[ndarray[tuple[int, ...], dtype[unsignedinteger[_8Bit]]], ...]", + "tuple[ndarray[tuple[Any, ...], dtype[unsignedinteger[_8Bit]]], ...]", ), # basic_indices with allow_ellipsis=False (no EllipsisType differences) ( From b320717c47d038180a8cadb8822b672be3a1ef52 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 9 Jan 2026 15:09:15 -0500 Subject: [PATCH 12/30] more nocover --- .../src/hypothesis/strategies/_internal/types.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/types.py b/hypothesis-python/src/hypothesis/strategies/_internal/types.py index 407f1e39b0..1bd025823d 100644 --- a/hypothesis-python/src/hypothesis/strategies/_internal/types.py +++ b/hypothesis-python/src/hypothesis/strategies/_internal/types.py @@ -981,8 +981,11 @@ def resolve_Tuple(thing): elem_types = getattr(thing, "__args__", None) or () if len(elem_types) == 2 and elem_types[-1] is Ellipsis: return st.lists(st.from_type(elem_types[0])).map(tuple) - elif len(elem_types) == 1 and elem_types[0] == (): - return st.tuples() # Empty tuple; see issue #1583 + elif len(elem_types) == 1 and elem_types[0] == (): # pragma: no cover + # Empty tuple; see issue #1583. + # Only possible on 3.10. `from typing import Tuple; Tuple[()].__args__`. + # is ((),) on 3.10, and () on 3.11+. + return st.tuples() return st.tuples(*map(st.from_type, elem_types)) From a8105cfcecb624e22c7f8426fd8d51f2644a730f Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 9 Jan 2026 15:50:31 -0500 Subject: [PATCH 13/30] try python 3.14 --- .github/workflows/fuzz.yml | 4 ++-- .github/workflows/main.yml | 18 +++++++++--------- .github/workflows/update-deps.yml | 4 ++-- .readthedocs.yml | 2 +- build.sh | 2 +- pyproject.toml | 2 +- requirements/coverage.txt | 4 ++-- requirements/crosshair.txt | 2 +- requirements/fuzzing.txt | 4 ++-- requirements/test.txt | 2 +- requirements/tools.txt | 8 ++++---- 11 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 4aac401905..e348ecb774 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -24,10 +24,10 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Set up Python 3.13 + - name: Set up Python 3.14 uses: actions/setup-python@v4 with: - python-version: "3.13.5" + python-version: "3.14.2" - name: Restore cache uses: actions/cache@v3 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fece5d5622..c91f93c6e6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,7 +33,7 @@ jobs: - check-format fail-fast: false env: - PYTHON_VERSION: "3.13" + PYTHON_VERSION: "3.14" steps: - uses: actions/checkout@v3 with: @@ -105,7 +105,7 @@ jobs: # - check-py310-pandas11 fail-fast: false env: - PYTHON_VERSION: "3.13" + PYTHON_VERSION: "3.14" steps: - uses: actions/checkout@v3 with: @@ -174,7 +174,7 @@ jobs: - check-numpy-nightly fail-fast: false env: - PYTHON_VERSION: "3.13" + PYTHON_VERSION: "3.14" steps: - uses: actions/checkout@v3 with: @@ -203,7 +203,7 @@ jobs: - macos-latest python-version: - "3.11" - - "3.13" + - "3.14" python-architecture: - null - "x86" @@ -215,11 +215,11 @@ jobs: - alt-rest exclude: - { os: macos-latest, python-architecture: "x86" } - - { python-version: "3.13", python-architecture: "x86" } + - { python-version: "3.14", python-architecture: "x86" } - { python-version: "3.11", task: nocover } - { python-version: "3.11", task: rest } - - { python-version: "3.13", task: alt-nocover } - - { python-version: "3.13", task: alt-rest } + - { python-version: "3.14", task: alt-nocover } + - { python-version: "3.14", task: alt-rest } fail-fast: false runs-on: ${{ matrix.os }} env: @@ -255,7 +255,7 @@ jobs: # The versions of pyodide-build and the Pyodide runtime may differ. PYODIDE_VERSION: 0.29.0 PYODIDE_BUILD_VERSION: 0.31.1 - PYTHON_VERSION: 3.13.2 + PYTHON_VERSION: 3.14.2 steps: - uses: actions/checkout@v3 with: @@ -318,7 +318,7 @@ jobs: token: ${{ secrets.GH_TOKEN }} - uses: ./.github/actions/install-base with: - python-version: "3.13" + python-version: "3.14" - name: Deploy package env: GH_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/update-deps.yml b/.github/workflows/update-deps.yml index c644781ec8..674f5f6b89 100644 --- a/.github/workflows/update-deps.yml +++ b/.github/workflows/update-deps.yml @@ -11,10 +11,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Set up Python 3.13 + - name: Set up Python 3.14 uses: actions/setup-python@v4 with: - python-version: '3.13' + python-version: '3.14' - name: Update pinned dependencies run: ./build.sh upgrade-requirements - name: Open pull request diff --git a/.readthedocs.yml b/.readthedocs.yml index e90297f953..68422c0ead 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -14,7 +14,7 @@ formats: build: os: ubuntu-22.04 tools: - python: "3.13" + python: "3.14" python: install: - requirements: requirements/tools.txt diff --git a/build.sh b/build.sh index e0af5105d5..9d2a129745 100755 --- a/build.sh +++ b/build.sh @@ -19,7 +19,7 @@ SCRIPTS="$ROOT/tooling/scripts" # shellcheck source=tooling/scripts/common.sh source "$SCRIPTS/common.sh" -PYTHON_VERSION="3.13.5" +PYTHON_VERSION="3.14.2" if [ -n "${GITHUB_ACTIONS-}" ] || [ -n "${CODESPACES-}" ] || [ -n "${CLAUDECODE-}" ] ; then # We're on GitHub Actions, Codespaces, or Claude Code and already have a suitable Python diff --git a/pyproject.toml b/pyproject.toml index bb4441a24a..4e634a5a2c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,7 +91,7 @@ ignore = [ "hypothesis-python/tests/conjecture/test_data_tree.py" = ["B023"] [tool.mypy] -python_version = "3.13" +python_version = "3.14" platform = "linux" allow_redefinition = true diff --git a/requirements/coverage.txt b/requirements/coverage.txt index 600bdb0182..a56c95b0ed 100644 --- a/requirements/coverage.txt +++ b/requirements/coverage.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.13 +# This file is autogenerated by pip-compile with Python 3.14 # by the following command: # # ./build.sh upgrade-requirements @@ -73,7 +73,7 @@ pytz==2025.2 # via # -r requirements/coverage.in # pandas -pyyaml-ft==8.0.0 +pyyaml==6.0.3 # via libcst redis==7.1.0 # via fakeredis diff --git a/requirements/crosshair.txt b/requirements/crosshair.txt index 198c86b13a..baa890184c 100644 --- a/requirements/crosshair.txt +++ b/requirements/crosshair.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.13 +# This file is autogenerated by pip-compile with Python 3.14 # by the following command: # # ./build.sh upgrade-requirements diff --git a/requirements/fuzzing.txt b/requirements/fuzzing.txt index d57dd474d8..158e04e49c 100644 --- a/requirements/fuzzing.txt +++ b/requirements/fuzzing.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.13 +# This file is autogenerated by pip-compile with Python 3.14 # by the following command: # # ./build.sh upgrade-requirements @@ -120,7 +120,7 @@ pytz==2025.2 # via # -r requirements/coverage.in # pandas -pyyaml-ft==8.0.0 +pyyaml==6.0.3 # via libcst redis==7.1.0 # via fakeredis diff --git a/requirements/test.txt b/requirements/test.txt index 8ce2b0b6bb..a6cda8a2fe 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.13 +# This file is autogenerated by pip-compile with Python 3.14 # by the following command: # # ./build.sh upgrade-requirements diff --git a/requirements/tools.txt b/requirements/tools.txt index 04e6ac6475..57b948b44f 100644 --- a/requirements/tools.txt +++ b/requirements/tools.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.13 +# This file is autogenerated by pip-compile with Python 3.14 # by the following command: # # ./build.sh upgrade-requirements @@ -229,9 +229,9 @@ pytokens==0.3.0 pyupgrade==3.21.2 # via shed pyyaml==6.0.3 - # via sphinx-jsonschema -pyyaml-ft==8.0.0 - # via libcst + # via + # libcst + # sphinx-jsonschema readme-renderer==44.0 # via twine requests==2.32.5 From 49358e81fa2236041469036e4e1e94a54c8e7758 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 9 Jan 2026 15:57:45 -0500 Subject: [PATCH 14/30] coverage --- hypothesis-python/src/hypothesis/internal/filtering.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hypothesis-python/src/hypothesis/internal/filtering.py b/hypothesis-python/src/hypothesis/internal/filtering.py index 155e67e74a..41dca9ca55 100644 --- a/hypothesis-python/src/hypothesis/internal/filtering.py +++ b/hypothesis-python/src/hypothesis/internal/filtering.py @@ -31,16 +31,16 @@ from fractions import Fraction from functools import partial from typing import Any, NamedTuple, TypeVar +import sys from hypothesis.internal.compat import ceil, floor from hypothesis.internal.floats import next_down, next_up from hypothesis.internal.lambda_sources import lambda_description from hypothesis.internal.reflection import get_pretty_function_description -try: - # new in 3.14 - from functools import Placeholder # type: ignore -except ImportError: +if sys.version_info[:2] >= (3, 14): + from functools import Placeholder +else: # pragma: no cover Placeholder = object() Ex = TypeVar("Ex") From bf9a5293fc8a36e843439418c7f4c6500e1bc76f Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 9 Jan 2026 16:03:09 -0500 Subject: [PATCH 15/30] format --- hypothesis-python/src/hypothesis/internal/filtering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hypothesis-python/src/hypothesis/internal/filtering.py b/hypothesis-python/src/hypothesis/internal/filtering.py index 41dca9ca55..cf1f2f4f5d 100644 --- a/hypothesis-python/src/hypothesis/internal/filtering.py +++ b/hypothesis-python/src/hypothesis/internal/filtering.py @@ -26,12 +26,12 @@ import inspect import math import operator +import sys from collections.abc import Callable, Collection from decimal import Decimal from fractions import Fraction from functools import partial from typing import Any, NamedTuple, TypeVar -import sys from hypothesis.internal.compat import ceil, floor from hypothesis.internal.floats import next_down, next_up From fa5ca371fb064924852844ee63ca78cee4c01193 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 9 Jan 2026 23:43:19 -0500 Subject: [PATCH 16/30] bump pyodide python version back down --- .github/workflows/main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c91f93c6e6..8986e3b5d9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -255,7 +255,8 @@ jobs: # The versions of pyodide-build and the Pyodide runtime may differ. PYODIDE_VERSION: 0.29.0 PYODIDE_BUILD_VERSION: 0.31.1 - PYTHON_VERSION: 3.14.2 + # pyodide 0.29.0 (latest at time of writing) doesn't yet support 3.14 + PYTHON_VERSION: 3.13.2 steps: - uses: actions/checkout@v3 with: From 30956afc87c2859d92a14a2ee58904d815a3778f Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sat, 10 Jan 2026 00:30:03 -0500 Subject: [PATCH 17/30] update a missed 3.13 reference --- tooling/src/hypothesistooling/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/src/hypothesistooling/__main__.py b/tooling/src/hypothesistooling/__main__.py index c87fe2c284..ada1a676c1 100644 --- a/tooling/src/hypothesistooling/__main__.py +++ b/tooling/src/hypothesistooling/__main__.py @@ -631,7 +631,7 @@ def run_tox(task, version, *args): "pypy3.10": "pypy3.10-7.3.19", "pypy3.11": "pypy3.11-7.3.20", } -ci_version = "3.13" # Keep this in sync with GH Actions main.yml and .readthedocs.yml +ci_version = "3.14" # Keep this in sync with GH Actions main.yml and .readthedocs.yml python_tests = task( if_changed=( From bcc2dfe8da1dc1db8d4337130dc0b0ce4f189b54 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sat, 10 Jan 2026 00:40:03 -0500 Subject: [PATCH 18/30] xfail more crosshair tests --- hypothesis-python/tests/common/utils.py | 14 +++++++------- .../tests/cover/test_cache_implementation.py | 2 +- hypothesis-python/tests/cover/test_core.py | 5 +++++ hypothesis-python/tests/cover/test_lookup.py | 14 +++++++++++++- hypothesis-python/tests/cover/test_regex.py | 4 ++++ .../tests/nocover/test_drypython_returns.py | 5 ++++- hypothesis-python/tests/nocover/test_regex.py | 14 ++++++++++++++ 7 files changed, 48 insertions(+), 10 deletions(-) diff --git a/hypothesis-python/tests/common/utils.py b/hypothesis-python/tests/common/utils.py index d84c1946d8..dcb3f73be9 100644 --- a/hypothesis-python/tests/common/utils.py +++ b/hypothesis-python/tests/common/utils.py @@ -258,14 +258,14 @@ class Why(enum.Enum): def xfail_on_crosshair(why: Why, /, *, strict=True, as_marks=False): # run `pytest -m xf_crosshair` to select these tests! - kw = { - "strict": strict and why != Why.undiscovered, - "reason": f"Expected failure due to: {why.value}", - "condition": settings().backend == "crosshair", - } + mark = pytest.mark.xfail( + strict=strict and why != Why.undiscovered, + reason=f"Expected failure due to: {why.value}", + condition=settings().backend == "crosshair", + ) if as_marks: # for use with pytest.param(..., marks=xfail_on_crosshair()) - return (pytest.mark.xf_crosshair, pytest.mark.xfail(**kw)) - return lambda fn: pytest.mark.xf_crosshair(pytest.mark.xfail(**kw)(fn)) + return (pytest.mark.xf_crosshair, mark) + return lambda fn: pytest.mark.xf_crosshair(mark(fn)) def skipif_threading(f): diff --git a/hypothesis-python/tests/cover/test_cache_implementation.py b/hypothesis-python/tests/cover/test_cache_implementation.py index a664555dc7..bf9732037e 100644 --- a/hypothesis-python/tests/cover/test_cache_implementation.py +++ b/hypothesis-python/tests/cover/test_cache_implementation.py @@ -116,7 +116,7 @@ def test_behaves_like_a_dict_with_losses(implementation, writes, size): assert len(target) <= min(len(model), size) -@xfail_on_crosshair(Why.symbolic_outside_context) +@xfail_on_crosshair(Why.symbolic_outside_context, strict=False) @settings( suppress_health_check={HealthCheck.too_slow} | set(settings().suppress_health_check), diff --git a/hypothesis-python/tests/cover/test_core.py b/hypothesis-python/tests/cover/test_core.py index 2112134bc0..127a341820 100644 --- a/hypothesis-python/tests/cover/test_core.py +++ b/hypothesis-python/tests/cover/test_core.py @@ -17,6 +17,8 @@ from hypothesis.database import InMemoryExampleDatabase from hypothesis.errors import InvalidArgument, NoSuchExample, Unsatisfiable +from tests.common.utils import Why, xfail_on_crosshair + def test_stops_after_max_examples_if_satisfying(): count = 0 @@ -162,6 +164,9 @@ def test_non_executed_tests_raise_skipped(test_fn): ], ) @given(st.data()) +@xfail_on_crosshair( + Why.other +) # https://github.com/pschanely/hypothesis-crosshair/issues/48 def test_characters_codec(codec, max_codepoint, exclude_categories, categories, data): strategy = st.characters( codec=codec, diff --git a/hypothesis-python/tests/cover/test_lookup.py b/hypothesis-python/tests/cover/test_lookup.py index 11ed26386c..83b37dd451 100644 --- a/hypothesis-python/tests/cover/test_lookup.py +++ b/hypothesis-python/tests/cover/test_lookup.py @@ -52,7 +52,7 @@ find_any, minimal, ) -from tests.common.utils import fails_with, temp_registered +from tests.common.utils import Why, fails_with, temp_registered, xfail_on_crosshair # we'll continue testing the typing variants until their removal from the stdlib # ruff: noqa: UP006, UP035, UP045, UP007 @@ -682,6 +682,9 @@ def __repr__(self): @given(nxt=st.from_type(MutualA)) +@xfail_on_crosshair( + Why.other +) # https://github.com/pschanely/hypothesis-crosshair/issues/49 def test_resolving_mutually_recursive_types(nxt): i = 0 while nxt: @@ -690,6 +693,9 @@ def test_resolving_mutually_recursive_types(nxt): i += 1 +@xfail_on_crosshair( + Why.other +) # https://github.com/pschanely/hypothesis-crosshair/issues/49 def test_resolving_mutually_recursive_types_with_limited_stack(): orig_recursionlimit = sys.getrecursionlimit() current_stack_depth = stack_depth_of_caller() @@ -979,6 +985,9 @@ def test_timezone_lookup(type_): ) @settings(suppress_health_check=[HealthCheck.data_too_large]) @given(data=st.data()) +@xfail_on_crosshair( + Why.other +) # https://github.com/pschanely/hypothesis-crosshair/issues/49 def test_generic_collections_only_use_hashable_elements(typ, data): data.draw(from_type(typ)) @@ -1101,6 +1110,9 @@ def __init__(self, value: int) -> None: @given(st.data()) +@xfail_on_crosshair( + Why.other +) # https://github.com/pschanely/hypothesis-crosshair/issues/49 def test_constructor_is_more_important(data): """Constructor types should take precedence over all other annotations.""" data.draw(st.builds(AnnotatedConstructor)) diff --git a/hypothesis-python/tests/cover/test_regex.py b/hypothesis-python/tests/cover/test_regex.py index 3e26d97d2a..ade39911c6 100644 --- a/hypothesis-python/tests/cover/test_regex.py +++ b/hypothesis-python/tests/cover/test_regex.py @@ -33,6 +33,7 @@ check_can_generate_examples, find_any, ) +from tests.common.utils import Why, xfail_on_crosshair def is_ascii(s): @@ -409,6 +410,9 @@ def test_shared_union(): @given(st.data()) +@xfail_on_crosshair( + Why.other +) # https://github.com/pschanely/hypothesis-crosshair/issues/48 def test_issue_992_regression(data): strat = st.from_regex( re.compile( diff --git a/hypothesis-python/tests/nocover/test_drypython_returns.py b/hypothesis-python/tests/nocover/test_drypython_returns.py index e3767bfcd2..c67f75aff2 100644 --- a/hypothesis-python/tests/nocover/test_drypython_returns.py +++ b/hypothesis-python/tests/nocover/test_drypython_returns.py @@ -16,7 +16,7 @@ from hypothesis.errors import ResolutionFailed from tests.common.debug import check_can_generate_examples, find_any -from tests.common.utils import temp_registered +from tests.common.utils import Why, temp_registered, xfail_on_crosshair # Primitives: # =========== @@ -59,6 +59,9 @@ def target_func(mappable: "MappableN[_FirstType]") -> bool: @given(st.data()) +@xfail_on_crosshair( + Why.other +) # https://github.com/pschanely/hypothesis-crosshair/issues/49 def test_my_mappable(source: st.DataObject) -> None: """ Checks that complex types with multiple inheritance levels and strings are fine. diff --git a/hypothesis-python/tests/nocover/test_regex.py b/hypothesis-python/tests/nocover/test_regex.py index b4f49165de..f30582ee8b 100644 --- a/hypothesis-python/tests/nocover/test_regex.py +++ b/hypothesis-python/tests/nocover/test_regex.py @@ -21,6 +21,8 @@ base_regex_strategy, ) +from tests.common.utils import Why, xfail_on_crosshair + @st.composite def charset(draw): @@ -73,6 +75,9 @@ def test_conservative_regex_are_correct_by_construction(data): assert pattern.search(result) is not None +@xfail_on_crosshair( + Why.other +) # https://github.com/pschanely/hypothesis-crosshair/issues/48 @given(st.data()) def test_fuzz_stuff(data): pattern = data.draw( @@ -102,6 +107,9 @@ def test_fuzz_stuff(data): assert regex.search(ex) +@xfail_on_crosshair( + Why.other +) # https://github.com/pschanely/hypothesis-crosshair/issues/48 @pytest.mark.skipif(sys.version_info[:2] < (3, 11), reason="new syntax") @given(st.data()) def test_regex_atomic_group(data): @@ -110,6 +118,9 @@ def test_regex_atomic_group(data): assert re.search(pattern, ex) +@xfail_on_crosshair( + Why.other +) # https://github.com/pschanely/hypothesis-crosshair/issues/48 @pytest.mark.skipif(sys.version_info[:2] < (3, 11), reason="new syntax") @given(st.data()) def test_regex_possessive(data): @@ -124,6 +135,9 @@ def test_regex_possessive(data): assert re.compile(I_WITH_DOT, flags=re.IGNORECASE).match(I_WITH_DOT.swapcase()) +@xfail_on_crosshair( + Why.other +) # https://github.com/pschanely/hypothesis-crosshair/issues/48 @given(st.data()) def test_case_insensitive_not_literal_never_constructs_multichar_match(data): # So our goal is to confirm that we can never accidentally create a non-matching From 52982b6bf0388176728183a839ec639548429384 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sat, 10 Jan 2026 01:03:47 -0500 Subject: [PATCH 19/30] strict fixes --- hypothesis-python/tests/cover/test_core.py | 2 +- hypothesis-python/tests/nocover/test_drypython_returns.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hypothesis-python/tests/cover/test_core.py b/hypothesis-python/tests/cover/test_core.py index 127a341820..ac4cb0d89b 100644 --- a/hypothesis-python/tests/cover/test_core.py +++ b/hypothesis-python/tests/cover/test_core.py @@ -165,7 +165,7 @@ def test_non_executed_tests_raise_skipped(test_fn): ) @given(st.data()) @xfail_on_crosshair( - Why.other + Why.other, strict=False ) # https://github.com/pschanely/hypothesis-crosshair/issues/48 def test_characters_codec(codec, max_codepoint, exclude_categories, categories, data): strategy = st.characters( diff --git a/hypothesis-python/tests/nocover/test_drypython_returns.py b/hypothesis-python/tests/nocover/test_drypython_returns.py index c67f75aff2..635a6e6b63 100644 --- a/hypothesis-python/tests/nocover/test_drypython_returns.py +++ b/hypothesis-python/tests/nocover/test_drypython_returns.py @@ -60,7 +60,7 @@ def target_func(mappable: "MappableN[_FirstType]") -> bool: @given(st.data()) @xfail_on_crosshair( - Why.other + Why.other, strict=False ) # https://github.com/pschanely/hypothesis-crosshair/issues/49 def test_my_mappable(source: st.DataObject) -> None: """ From 8e95b8a3e3cc977cf066fe55ae0302eb1e74111a Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 11 Jan 2026 17:06:42 -0500 Subject: [PATCH 20/30] more explicit random.triangular covering test --- hypothesis-python/tests/cover/test_randoms.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hypothesis-python/tests/cover/test_randoms.py b/hypothesis-python/tests/cover/test_randoms.py index ff7af60c8d..0ee768e39a 100644 --- a/hypothesis-python/tests/cover/test_randoms.py +++ b/hypothesis-python/tests/cover/test_randoms.py @@ -236,6 +236,12 @@ def test_handles_singleton_regions_of_triangular_correctly(rnd): assert rnd.triangular(0.0, -0.0) == 0.0 +@given(st.randoms(use_true_random=False)) +def test_triangular_with_mode(rnd): + x = rnd.triangular(0.0, 1.0, mode=0.5) + assert 0.0 <= x <= 1.0 + + @pytest.mark.parametrize("use_true_random", [False, True]) def test_outputs_random_calls(use_true_random): @given(st.randoms(use_true_random=use_true_random, note_method_calls=True)) From 13499802363da6c13af628f7046f93a9831b19c8 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 11 Jan 2026 17:07:33 -0500 Subject: [PATCH 21/30] update random tests for 3.14 --- .../tests/cover/test_random_module.py | 74 ++++++++++--------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/hypothesis-python/tests/cover/test_random_module.py b/hypothesis-python/tests/cover/test_random_module.py index 53401bbee9..92b75a1de0 100644 --- a/hypothesis-python/tests/cover/test_random_module.py +++ b/hypothesis-python/tests/cover/test_random_module.py @@ -28,7 +28,7 @@ from hypothesis.internal.compat import GRAALPY, PYPY from hypothesis.internal.entropy import deterministic_PRNG -from tests.common.utils import skipif_threading, xfail_if_gil_disabled +from tests.common.utils import skipif_threading def gc_collect(): @@ -75,20 +75,22 @@ def test_cannot_register_non_Random(): @skipif_threading -@pytest.mark.filterwarnings( - "ignore:It looks like `register_random` was passed an object that could be garbage collected" -) -@pytest.mark.xfail( - sys.version_info[:2] == (3, 14), - reason="TODO_314: is this intentional semantics of the new gc?", -) def test_registering_a_Random_is_idempotent(): gc_collect() n_registered = len(entropy.RANDOMS_TO_MANAGE) - r = random.Random() + # on 3.14+, python introduced the LOAD_FAST_BORROW opcode, which does + # not increment the refcount. Passing a bare r to register_random here on 3.14+ + # would use LOAD_FAST_BORROW and entropy.py would see a non-increasing refcount + # and hard-error. On 3.13 and earlier, this is a warning instead. + # + # For compatibility with both versions, this test forces a refcount increment + # with a redundant container. + container = [random.Random()] + r = container[0] register_random(r) register_random(r) assert len(entropy.RANDOMS_TO_MANAGE) == n_registered + 1 + del container del r gc_collect() assert len(entropy.RANDOMS_TO_MANAGE) == n_registered @@ -174,13 +176,6 @@ def test_find_does_not_pollute_state(): assert state_a2 != state_b2 -@pytest.mark.filterwarnings( - "ignore:It looks like `register_random` was passed an object that could be garbage collected" -) -@pytest.mark.skipif( - sys.version_info[:2] == (3, 14), - reason="TODO_314: is this intentional semantics of the new gc?", -) @skipif_threading # we assume we're the only writer to entropy.RANDOMS_TO_MANAGE def test_evil_prng_registration_nonsense(): # my guess is that other tests may register randoms that are then marked for @@ -195,7 +190,10 @@ def test_evil_prng_registration_nonsense(): pass n_registered = len(entropy.RANDOMS_TO_MANAGE) - r1, r2, r3 = random.Random(1), random.Random(2), random.Random(3) + # put inside a list to increment ref count and avoid our warning/error about no + # referrers + c1, c2, c3 = [random.Random(1)], [random.Random(2)], [random.Random(3)] + r1, r2, r3 = c1[0], c2[0], c3[0] s2 = r2.getstate() # We're going to be totally evil here: register two randoms, then @@ -208,6 +206,7 @@ def test_evil_prng_registration_nonsense(): with deterministic_PRNG(): del r1 + del c1 gc_collect() assert k not in entropy.RANDOMS_TO_MANAGE, "r1 has been garbage-collected" assert len(entropy.RANDOMS_TO_MANAGE) == n_registered + 1 @@ -240,18 +239,12 @@ def f(): with pytest.raises(ReferenceError): f() - -@xfail_if_gil_disabled -@pytest.mark.skipif( - PYPY, reason="We can't guard against bad no-reference patterns in pypy." -) -@pytest.mark.skipif( - sys.version_info[:2] == (3, 14), - reason="TODO_314: is this intentional semantics of the new gc?", -) -def test_passing_referenced_instance_within_function_scope_warns(): + # we have two error paths for register_random: one which warns and one which + # errors. We use an alias to bump the refcount while not adding a gc referrer, + # which covers the warning path. def f(): r = random.Random(0) + _r2 = r register_random(r) with pytest.warns( @@ -262,22 +255,37 @@ def f(): f() -@pytest.mark.filterwarnings( - "ignore:It looks like `register_random` was passed an object that could be garbage collected" -) @pytest.mark.skipif( PYPY, reason="We can't guard against bad no-reference patterns in pypy." ) @pytest.mark.skipif( - sys.version_info[:2] == (3, 14), - reason="TODO_314: is this intentional semantics of the new gc?", + sys.version_info[:2] < (3, 14), + reason="warns instead of raises on 3.13 or earlier due to gc changes", +) +def test_passing_referenced_instance_within_function_scope_raises(): + def f(): + r = random.Random(0) + register_random(r) + + with pytest.raises( + ReferenceError, + match=r"`register_random` was passed .* which will be garbage collected", + ): + f() + + +@pytest.mark.skipif( + PYPY, reason="We can't guard against bad no-reference patterns in pypy." ) @skipif_threading # we assume we're the only writer to entropy.RANDOMS_TO_MANAGE def test_register_random_within_nested_function_scope(): n_registered = len(entropy.RANDOMS_TO_MANAGE) def f(): - r = random.Random() + # put inside a list to increment ref count and avoid our warning/error about no + # referrers + container = [random.Random()] + r = container[0] register_random(r) assert len(entropy.RANDOMS_TO_MANAGE) == n_registered + 1 From 5f9e2f6e3233336d406c49479c6a7ac155697909 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 11 Jan 2026 17:33:06 -0500 Subject: [PATCH 22/30] fix docs reference --- hypothesis-python/docs/changelog.rst | 12 ++++++------ .../src/hypothesis/strategies/_internal/types.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/hypothesis-python/docs/changelog.rst b/hypothesis-python/docs/changelog.rst index 6835d6b5cc..353015c7f8 100644 --- a/hypothesis-python/docs/changelog.rst +++ b/hypothesis-python/docs/changelog.rst @@ -2427,7 +2427,7 @@ This patch makes progress towards adding type hints to our internal conjecture e This release allows using :obj:`~python:typing.Annotated` and :obj:`!ReadOnly` types -for :class:`~python:typing.TypedDict` value types +for :obj:`~python:typing.TypedDict` value types with :func:`~hypothesis.strategies.from_type`. .. _v6.108.10: @@ -5075,7 +5075,7 @@ scans. ------------------- This patch by Cheuk Ting Ho adds support for :pep:`655` ``Required`` and ``NotRequired`` as attributes of -:class:`~python:typing.TypedDict` in :func:`~hypothesis.strategies.from_type` (:issue:`3339`). +:obj:`~python:typing.TypedDict` in :func:`~hypothesis.strategies.from_type` (:issue:`3339`). .. _v6.46.5: @@ -5248,7 +5248,7 @@ already been decorated with :func:`@given() `. Previously, 6.42.3 - 2022-04-10 ------------------- -This patch fixes :func:`~hypothesis.strategies.from_type` on a :class:`~python:typing.TypedDict` +This patch fixes :func:`~hypothesis.strategies.from_type` on a :obj:`~python:typing.TypedDict` with complex annotations, defined in a file using ``from __future__ import annotations``. Thanks to Katelyn Gigante for identifying and fixing this bug! @@ -6760,7 +6760,7 @@ creating import cycles. There is no user-visible change. ------------------ This patch enables :func:`~hypothesis.strategies.register_type_strategy` for subclasses of -:class:`python:typing.TypedDict`. Previously, :func:`~hypothesis.strategies.from_type` +:obj:`python:typing.TypedDict`. Previously, :func:`~hypothesis.strategies.from_type` would ignore the registered strategy (:issue:`2872`). Thanks to Ilya Lebedev for identifying and fixing this bug! @@ -8074,7 +8074,7 @@ deprecated - you can explicitly pass ``min_magnitude=0``, or omit the argument e ------------------- This patch fixes an internal error in :func:`~hypothesis.strategies.from_type` -for :class:`python:typing.NamedTuple` in Python 3.9. Thanks to Michel Salim +for :obj:`python:typing.NamedTuple` in Python 3.9. Thanks to Michel Salim for reporting and fixing :issue:`2427`! .. _v5.13.0: @@ -12603,7 +12603,7 @@ not matter. ------------------- This patch fixes inference in the :func:`~hypothesis.strategies.builds` -strategy with subtypes of :class:`python:typing.NamedTuple`, where the +strategy with subtypes of :obj:`python:typing.NamedTuple`, where the ``__init__`` method is not useful for introspection. We now use the field types instead - thanks to James Uther for identifying this bug. diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/types.py b/hypothesis-python/src/hypothesis/strategies/_internal/types.py index 1bd025823d..84d73a2d20 100644 --- a/hypothesis-python/src/hypothesis/strategies/_internal/types.py +++ b/hypothesis-python/src/hypothesis/strategies/_internal/types.py @@ -983,7 +983,7 @@ def resolve_Tuple(thing): return st.lists(st.from_type(elem_types[0])).map(tuple) elif len(elem_types) == 1 and elem_types[0] == (): # pragma: no cover # Empty tuple; see issue #1583. - # Only possible on 3.10. `from typing import Tuple; Tuple[()].__args__`. + # Only possible on 3.10. `from typing import Tuple; Tuple[()].__args__` # is ((),) on 3.10, and () on 3.11+. return st.tuples() return st.tuples(*map(st.from_type, elem_types)) From b3c3c30fdb855139d1254c1593eb54decd255e37 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 11 Jan 2026 17:43:32 -0500 Subject: [PATCH 23/30] add xfails --- hypothesis-python/tests/cover/test_random_module.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hypothesis-python/tests/cover/test_random_module.py b/hypothesis-python/tests/cover/test_random_module.py index 92b75a1de0..1c3c6a29fc 100644 --- a/hypothesis-python/tests/cover/test_random_module.py +++ b/hypothesis-python/tests/cover/test_random_module.py @@ -28,7 +28,7 @@ from hypothesis.internal.compat import GRAALPY, PYPY from hypothesis.internal.entropy import deterministic_PRNG -from tests.common.utils import skipif_threading +from tests.common.utils import skipif_threading, xfail_if_gil_disabled def gc_collect(): @@ -229,6 +229,7 @@ def test_passing_unreferenced_instance_raises(): register_random(random.Random(0)) +@xfail_if_gil_disabled @pytest.mark.skipif( PYPY, reason="We can't guard against bad no-reference patterns in pypy." ) @@ -255,6 +256,7 @@ def f(): f() +@xfail_if_gil_disabled @pytest.mark.skipif( PYPY, reason="We can't guard against bad no-reference patterns in pypy." ) From eda5d654c81a6b7e4b54658ff554e1680491d5e1 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 11 Jan 2026 23:05:31 -0500 Subject: [PATCH 24/30] use removesuffix --- whole_repo_tests/types/test_mypy.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/whole_repo_tests/types/test_mypy.py b/whole_repo_tests/types/test_mypy.py index eb2a330582..f5b1201f39 100644 --- a/whole_repo_tests/types/test_mypy.py +++ b/whole_repo_tests/types/test_mypy.py @@ -60,10 +60,11 @@ def get_mypy_output(fname, *extra_args): def get_mypy_analysed_type(fname): attempts = 0 while True: - out = get_mypy_output(fname).rstrip() - msg = "Success: no issues found in 1 source file" - if out.endswith(msg): - out = out[: -len(msg)] + out = ( + get_mypy_output(fname) + .rstrip() + .removesuffix("Success: no issues found in 1 source file") + ) # we've noticed some flakiness in getting an empty output here. Give it # a couple tries. if len(out.splitlines()) == 0: From 04440db4cf17e26d640f83c3c856e9cba829e24c Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 11 Jan 2026 23:05:40 -0500 Subject: [PATCH 25/30] 3.14 does not throw permissionerror --- hypothesis-python/src/hypothesis/database.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hypothesis-python/src/hypothesis/database.py b/hypothesis-python/src/hypothesis/database.py index 4ff64072e2..1fb64213ca 100644 --- a/hypothesis-python/src/hypothesis/database.py +++ b/hypothesis-python/src/hypothesis/database.py @@ -76,7 +76,9 @@ def _usable_dir(path: StrPathT) -> bool: # Loop terminates because the root dir ('/' on unix) always exists. path = path.parent return path.is_dir() and os.access(path, os.R_OK | os.W_OK | os.X_OK) - except PermissionError: + except PermissionError: # pragma: no cover + # path.exists() returns False on 3.14+ instead of raising. See + # https://docs.python.org/3.14/library/pathlib.html#querying-file-type-and-status return False From 75131903e7c91241e34f18d19a292a471a515614 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 11 Jan 2026 23:05:51 -0500 Subject: [PATCH 26/30] don't type check sphinx hack --- hypothesis-python/src/hypothesis/database.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/hypothesis-python/src/hypothesis/database.py b/hypothesis-python/src/hypothesis/database.py index 1fb64213ca..5545f4e7e1 100644 --- a/hypothesis-python/src/hypothesis/database.py +++ b/hypothesis-python/src/hypothesis/database.py @@ -134,14 +134,22 @@ def __call__(self, *args: Any, **kwargs: Any) -> "ExampleDatabase": # This code only runs if Sphinx has already been imported; and it would live in our # docs/conf.py except that we would also like it to work for anyone documenting # downstream ExampleDatabase subclasses too. -if "sphinx" in sys.modules: +# +# We avoid type-checking this block due to this combination facts: +# * our check-types-api CI job runs under 3.14 +# * tools.txt therefore pins to a newer version of sphinx which uses 3.12+ `type` +# syntax +# * in test_mypy.py, mypy sees this block, sees sphinx is installed, tries parsing +# sphinx code, and errors +# +# Putting `and not TYPE_CHECKING` here is just a convenience for our testing setup +# (because we don't split mypy tests by running CI version, eg), not for runtime +# behavior. +if "sphinx" in sys.modules and not TYPE_CHECKING: try: - from types import ModuleType - import sphinx.ext.autodoc signature = "hypothesis.database._EDMeta.__call__" - _module: ModuleType # make mypy happy # _METACLASS_CALL_BLACKLIST moved in newer sphinx versions try: @@ -151,7 +159,7 @@ def __call__(self, *args: Any, **kwargs: Any) -> "ExampleDatabase": # _METACLASS_CALL_BLACKLIST is a frozenset in later sphinx versions if isinstance(_module._METACLASS_CALL_BLACKLIST, frozenset): - _module._METACLASS_CALL_BLACKLIST = ( # type: ignore + _module._METACLASS_CALL_BLACKLIST = ( _module._METACLASS_CALL_BLACKLIST | {signature} ) else: From 432fba12fce1fd3723150c17393277f0dd39bd22 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 11 Jan 2026 23:07:41 -0500 Subject: [PATCH 27/30] format --- hypothesis-python/src/hypothesis/database.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hypothesis-python/src/hypothesis/database.py b/hypothesis-python/src/hypothesis/database.py index 5545f4e7e1..5a23ab8b8b 100644 --- a/hypothesis-python/src/hypothesis/database.py +++ b/hypothesis-python/src/hypothesis/database.py @@ -159,9 +159,9 @@ def __call__(self, *args: Any, **kwargs: Any) -> "ExampleDatabase": # _METACLASS_CALL_BLACKLIST is a frozenset in later sphinx versions if isinstance(_module._METACLASS_CALL_BLACKLIST, frozenset): - _module._METACLASS_CALL_BLACKLIST = ( - _module._METACLASS_CALL_BLACKLIST | {signature} - ) + _module._METACLASS_CALL_BLACKLIST = _module._METACLASS_CALL_BLACKLIST | { + signature + } else: _module._METACLASS_CALL_BLACKLIST.append(signature) except Exception: From d5cc3aabf048c6cfbc4a712b88861cfa172d19e4 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 11 Jan 2026 23:10:23 -0500 Subject: [PATCH 28/30] add back accidentally-removed sphinx-autobuild? --- requirements/tools.in | 1 + requirements/tools.txt | 24 +++++++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/requirements/tools.in b/requirements/tools.in index fca70d91a6..4bf4e87e7c 100644 --- a/requirements/tools.in +++ b/requirements/tools.in @@ -16,6 +16,7 @@ restructuredtext-lint ruff shed==2024.1.1 sphinx +sphinx-autobuild sphinx-codeautolink sphinx-jsonschema furo diff --git a/requirements/tools.txt b/requirements/tools.txt index 38761b5d5e..72d0f6bb3a 100644 --- a/requirements/tools.txt +++ b/requirements/tools.txt @@ -9,7 +9,9 @@ accessible-pygments==0.0.5 alabaster==1.0.0 # via sphinx anyio==4.12.1 - # via watchfiles + # via + # starlette + # watchfiles asgiref==3.11.0 # via django asttokens==3.0.1 @@ -46,10 +48,13 @@ click==8.3.1 # via # black # pip-tools + # uvicorn codespell==2.4.1 # via -r requirements/tools.in colorama==0.4.6 - # via tox + # via + # sphinx-autobuild + # tox com2ann==0.3.0 # via shed coverage==7.13.1 @@ -85,6 +90,8 @@ filelock==3.20.3 # virtualenv furo==2025.12.19 # via -r requirements/tools.in +h11==0.16.0 + # via uvicorn id==1.5.0 # via twine idna==3.11 @@ -274,8 +281,11 @@ sphinx==9.1.0 # via # -r requirements/tools.in # furo + # sphinx-autobuild # sphinx-basic-ng # sphinx-codeautolink +sphinx-autobuild==2025.8.25 + # via -r requirements/tools.in sphinx-basic-ng==1.0.0b2 # via furo sphinx-codeautolink==0.17.5 @@ -300,6 +310,8 @@ sqlparse==0.5.5 # via django stack-data==0.6.3 # via ipython +starlette==0.51.0 + # via sphinx-autobuild tokenize-rt==6.2.0 # via pyupgrade tomli==2.4.0 @@ -337,14 +349,20 @@ urllib3==2.6.3 # via # requests # twine +uvicorn==0.40.0 + # via sphinx-autobuild virtualenv==20.36.1 # via tox watchdog==6.0.0 # via -r requirements/tools.in watchfiles==1.1.1 - # via pelican + # via + # pelican + # sphinx-autobuild wcwidth==0.2.14 # via prompt-toolkit +websockets==16.0 + # via sphinx-autobuild wheel==0.45.1 # via pip-tools From 09cdd474773d0b924ed682bcf4b92a3af4908f26 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 11 Jan 2026 23:39:12 -0500 Subject: [PATCH 29/30] work on coverage --- hypothesis-python/src/hypothesis/database.py | 2 +- .../tests/redis/test_redis_exampledatabase.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/hypothesis-python/src/hypothesis/database.py b/hypothesis-python/src/hypothesis/database.py index 5a23ab8b8b..4fbddd2902 100644 --- a/hypothesis-python/src/hypothesis/database.py +++ b/hypothesis-python/src/hypothesis/database.py @@ -145,7 +145,7 @@ def __call__(self, *args: Any, **kwargs: Any) -> "ExampleDatabase": # Putting `and not TYPE_CHECKING` here is just a convenience for our testing setup # (because we don't split mypy tests by running CI version, eg), not for runtime # behavior. -if "sphinx" in sys.modules and not TYPE_CHECKING: +if "sphinx" in sys.modules and not TYPE_CHECKING: # pragma: no cover try: import sphinx.ext.autodoc diff --git a/hypothesis-python/tests/redis/test_redis_exampledatabase.py b/hypothesis-python/tests/redis/test_redis_exampledatabase.py index 94987e3d70..031c97c8bc 100644 --- a/hypothesis-python/tests/redis/test_redis_exampledatabase.py +++ b/hypothesis-python/tests/redis/test_redis_exampledatabase.py @@ -164,6 +164,15 @@ def test_redis_move_into_key_with_value(): db.move(b"a", b"b", b"x") +def test_redis_move_to_same_key(): + # explicit covering test for: + # * moving a value where src == dest + redis = FakeRedis() + db = RedisExampleDatabase(redis) + db.move(b"a", b"a", b"x") + assert list(db.fetch(b"a")) == [b"x"] + + def test_redis_equality(): redis = FakeRedis() assert RedisExampleDatabase(redis) == RedisExampleDatabase(redis) From 1cab11ec24f48490ae47fa9e54f3f827b8b64cd2 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Tue, 13 Jan 2026 11:04:20 -0500 Subject: [PATCH 30/30] simplify entrypoints --- .../src/hypothesis/entry_points.py | 26 +++++++------------ .../conjecture/provider_conformance.py | 1 + 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/hypothesis-python/src/hypothesis/entry_points.py b/hypothesis-python/src/hypothesis/entry_points.py index a29cbe89f1..98933265c2 100644 --- a/hypothesis-python/src/hypothesis/entry_points.py +++ b/hypothesis-python/src/hypothesis/entry_points.py @@ -17,23 +17,15 @@ import importlib.metadata import os -from collections.abc import Generator, Sequence -from importlib.metadata import EntryPoint - - -def get_entry_points() -> Generator[EntryPoint, None, None]: - try: - eps: Sequence[EntryPoint] = importlib.metadata.entry_points(group="hypothesis") - except TypeError: # pragma: no cover - # Load-time selection requires Python >= 3.10. - # type ignore since interface differs pre-3.10 - eps = importlib.metadata.entry_points().get("hypothesis", []) # type: ignore - yield from eps def run() -> None: - if not os.environ.get("HYPOTHESIS_NO_PLUGINS"): - for entry in get_entry_points(): # pragma: no cover - hook = entry.load() - if callable(hook): - hook() + if os.environ.get("HYPOTHESIS_NO_PLUGINS"): + return + + for entry in importlib.metadata.entry_points( + group="hypothesis" + ): # pragma: no cover + hook = entry.load() + if callable(hook): + hook() diff --git a/hypothesis-python/src/hypothesis/internal/conjecture/provider_conformance.py b/hypothesis-python/src/hypothesis/internal/conjecture/provider_conformance.py index f41f58ba04..55ced851b5 100644 --- a/hypothesis-python/src/hypothesis/internal/conjecture/provider_conformance.py +++ b/hypothesis-python/src/hypothesis/internal/conjecture/provider_conformance.py @@ -44,6 +44,7 @@ def build_intervals(intervals: list[int]) -> list[tuple[int, int]]: if len(intervals) % 2: intervals = intervals[:-1] intervals.sort() + # help mypy infer tuple[int, ...] -> tuple[int, int] return list(batched(intervals, 2, strict=True)) # type: ignore