Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e613aee
fix changelog
Liam-DeVoe Aug 15, 2025
2606b56
bump base ci version to 3.13
Liam-DeVoe Aug 16, 2025
d15669e
sphinx errors unless we adjust the newlines here?? "Block quote ends …
Liam-DeVoe Aug 16, 2025
4052c28
ignore EncodingWarning with tzdata
Liam-DeVoe Aug 16, 2025
e9d6049
Merge branch 'master' into next
Liam-DeVoe Aug 29, 2025
8e1829f
fix issue ref
Liam-DeVoe Nov 5, 2025
1de4784
Merge branch 'master' into next
Liam-DeVoe Jan 7, 2026
ac86f41
fix merge
Liam-DeVoe Jan 7, 2026
729ad70
some fixes?
Liam-DeVoe Jan 7, 2026
34958cd
coverage
Liam-DeVoe Jan 9, 2026
5a95a14
pin pandas versions
Liam-DeVoe Jan 9, 2026
93352ae
nocover, tweak+enable more pytest ci jobs
Liam-DeVoe Jan 9, 2026
1dc1bae
more fixes
Liam-DeVoe Jan 9, 2026
b320717
more nocover
Liam-DeVoe Jan 9, 2026
a8105cf
try python 3.14
Liam-DeVoe Jan 9, 2026
49358e8
coverage
Liam-DeVoe Jan 9, 2026
bf9a529
format
Liam-DeVoe Jan 9, 2026
fa5ca37
bump pyodide python version back down
Liam-DeVoe Jan 10, 2026
30956af
update a missed 3.13 reference
Liam-DeVoe Jan 10, 2026
bcc2dfe
xfail more crosshair tests
Liam-DeVoe Jan 10, 2026
52982b6
strict fixes
Liam-DeVoe Jan 10, 2026
0aea687
Merge branch 'master' into next
Liam-DeVoe Jan 11, 2026
8e95b8a
more explicit random.triangular covering test
Liam-DeVoe Jan 11, 2026
1349980
update random tests for 3.14
Liam-DeVoe Jan 11, 2026
5f9e2f6
fix docs reference
Liam-DeVoe Jan 11, 2026
b3c3c30
add xfails
Liam-DeVoe Jan 11, 2026
eda5d65
use removesuffix
Liam-DeVoe Jan 12, 2026
04440db
3.14 does not throw permissionerror
Liam-DeVoe Jan 12, 2026
7513190
don't type check sphinx hack
Liam-DeVoe Jan 12, 2026
432fba1
format
Liam-DeVoe Jan 12, 2026
d5cc3aa
add back accidentally-removed sphinx-autobuild?
Liam-DeVoe Jan 12, 2026
09cdd47
work on coverage
Liam-DeVoe Jan 12, 2026
c62b34a
Merge branch 'master' into next
Liam-DeVoe Jan 12, 2026
1cab11e
simplify entrypoints
Liam-DeVoe Jan 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ jobs:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python 3.10
- name: Set up Python 3.14
uses: actions/setup-python@v4
with:
python-version: "3.10.9"
python-version: "3.14.2"
- name: Restore cache
uses: actions/cache@v3
with:
Expand Down Expand Up @@ -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
Expand Down
35 changes: 19 additions & 16 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
- check-format
fail-fast: false
env:
PYTHON_VERSION: "3.10"
PYTHON_VERSION: "3.14"
steps:
- uses: actions/checkout@v3
with:
Expand Down Expand Up @@ -88,22 +88,24 @@ 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-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
fail-fast: false
env:
PYTHON_VERSION: "3.10"
PYTHON_VERSION: "3.14"
steps:
- uses: actions/checkout@v3
with:
Expand Down Expand Up @@ -172,7 +174,7 @@ jobs:
- check-numpy-nightly
fail-fast: false
env:
PYTHON_VERSION: "3.10"
PYTHON_VERSION: "3.14"
steps:
- uses: actions/checkout@v3
with:
Expand Down Expand Up @@ -201,7 +203,7 @@ jobs:
- macos-latest
python-version:
- "3.11"
- "3.13"
- "3.14"
python-architecture:
- null
- "x86"
Expand All @@ -213,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:
Expand Down Expand Up @@ -253,6 +255,7 @@ jobs:
# The versions of pyodide-build and the Pyodide runtime may differ.
PYODIDE_VERSION: 0.29.1
PYODIDE_BUILD_VERSION: 0.31.1
# 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
Expand Down Expand Up @@ -316,7 +319,7 @@ jobs:
token: ${{ secrets.GH_TOKEN }}
- uses: ./.github/actions/install-base
with:
python-version: "3.10"
python-version: "3.14"
- name: Deploy package
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/update-deps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.14
uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.14'
- name: Update pinned dependencies
run: ./build.sh upgrade-requirements
- name: Open pull request
Expand Down
2 changes: 1 addition & 1 deletion .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ formats:
build:
os: ubuntu-22.04
tools:
python: "3.10"
python: "3.14"
python:
install:
- requirements: requirements/tools.txt
Expand Down
6 changes: 4 additions & 2 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ SCRIPTS="$ROOT/tooling/scripts"
# shellcheck source=tooling/scripts/common.sh
source "$SCRIPTS/common.sh"

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
PYTHON=$(command -v python3 || 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.19
PYTHON=$(pythonloc 3.10.19)/bin/python
"$SCRIPTS/ensure-python.sh" "$PYTHON_VERSION"
PYTHON=$(pythonloc "$PYTHON_VERSION")/bin/python
fi

TOOL_REQUIREMENTS="$ROOT/requirements/tools.txt"
Expand Down
3 changes: 3 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
RELEASE_TYPE: patch

Update some internal type hints.
34 changes: 27 additions & 7 deletions hypothesis-python/src/hypothesis/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -132,18 +134,36 @@ 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: # pragma: no cover
try:
import sphinx.ext.autodoc

signature = "hypothesis.database._EDMeta.__call__"

# _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 = _module._METACLASS_CALL_BLACKLIST | {
signature
}
else:
sphinx.ext.autodoc._METACLASS_CALL_BLACKLIST.append(signature)
_module._METACLASS_CALL_BLACKLIST.append(signature)
except Exception:
pass

Expand Down
26 changes: 9 additions & 17 deletions hypothesis-python/src/hypothesis/entry_points.py
Original file line number Diff line number Diff line change
Expand Up @@ -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. See also
# https://importlib-metadata.readthedocs.io/en/latest/using.html
eps = importlib.metadata.entry_points().get("hypothesis", [])
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()
8 changes: 4 additions & 4 deletions hypothesis-python/src/hypothesis/extra/_array_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ def valid_tuple_axes(
min_size: int = 0,
max_size: int | None = 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
Expand All @@ -153,7 +154,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")
Expand Down Expand Up @@ -364,7 +364,8 @@ def mutually_broadcastable_shapes(
min_side: int = 1,
max_side: int | None = 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.
Expand Down Expand Up @@ -397,7 +398,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:
Expand Down
63 changes: 32 additions & 31 deletions hypothesis-python/src/hypothesis/internal/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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: # pragma: no cover
from exceptiongroup import (
BaseExceptionGroup as BaseExceptionGroup,
ExceptionGroup as ExceptionGroup,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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__ = []
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ 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))
# help mypy infer tuple[int, ...] -> tuple[int, int]
return list(batched(intervals, 2, strict=True)) # type: ignore


def interval_lists(
Expand Down
Loading
Loading