Skip to content

Commit 6100544

Browse files
authored
Merge pull request #4512 from Liam-DeVoe/next
Update base CI version to 3.14 (from 3.10)
2 parents 174c1f1 + 1cab11e commit 6100544

36 files changed

Lines changed: 255 additions & 256 deletions

.github/workflows/fuzz.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ jobs:
2424
- uses: actions/checkout@v3
2525
with:
2626
fetch-depth: 0
27-
- name: Set up Python 3.10
27+
- name: Set up Python 3.14
2828
uses: actions/setup-python@v4
2929
with:
30-
python-version: "3.10.9"
30+
python-version: "3.14.2"
3131
- name: Restore cache
3232
uses: actions/cache@v3
3333
with:
@@ -79,7 +79,7 @@ jobs:
7979
with:
8080
name: explicit-example-patches
8181
path: .hypothesis/patches/latest_hypofuzz_*.patch
82-
82+
8383
# Upload the database so it'll be persisted between runs.
8484
# Note that we can also pull it down to use locally via
8585
# https://hypothesis.readthedocs.io/en/latest/database.html#hypothesis.database.GitHubArtifactDatabase

.github/workflows/main.yml

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
- check-format
3434
fail-fast: false
3535
env:
36-
PYTHON_VERSION: "3.10"
36+
PYTHON_VERSION: "3.14"
3737
steps:
3838
- uses: actions/checkout@v3
3939
with:
@@ -88,22 +88,24 @@ jobs:
8888
- check-py313t-nocover
8989
- check-py313t-niche
9090
- check-quality
91-
- check-pytest62
92-
- check-pytest62
91+
- check-pytest9
92+
- check-pytest84
93+
- check-pytest74
94+
- check-py311-pytest62
9395
- check-django60
9496
- check-django42
95-
- check-pandas22
96-
- check-pandas21
97-
- check-pandas20
98-
- check-pandas15
99-
- check-pandas14
100-
- check-pandas13
97+
- check-py313-pandas22
98+
- check-py312-pandas21
99+
- check-py311-pandas20
100+
- check-py311-pandas15
101+
- check-py310-pandas14
102+
- check-py310-pandas13
101103
## FIXME: actions update means Python builds without eg _bz2, which was required
102104
# - check-py310-pandas12
103105
# - check-py310-pandas11
104106
fail-fast: false
105107
env:
106-
PYTHON_VERSION: "3.10"
108+
PYTHON_VERSION: "3.14"
107109
steps:
108110
- uses: actions/checkout@v3
109111
with:
@@ -172,7 +174,7 @@ jobs:
172174
- check-numpy-nightly
173175
fail-fast: false
174176
env:
175-
PYTHON_VERSION: "3.10"
177+
PYTHON_VERSION: "3.14"
176178
steps:
177179
- uses: actions/checkout@v3
178180
with:
@@ -201,7 +203,7 @@ jobs:
201203
- macos-latest
202204
python-version:
203205
- "3.11"
204-
- "3.13"
206+
- "3.14"
205207
python-architecture:
206208
- null
207209
- "x86"
@@ -213,11 +215,11 @@ jobs:
213215
- alt-rest
214216
exclude:
215217
- { os: macos-latest, python-architecture: "x86" }
216-
- { python-version: "3.13", python-architecture: "x86" }
218+
- { python-version: "3.14", python-architecture: "x86" }
217219
- { python-version: "3.11", task: nocover }
218220
- { python-version: "3.11", task: rest }
219-
- { python-version: "3.13", task: alt-nocover }
220-
- { python-version: "3.13", task: alt-rest }
221+
- { python-version: "3.14", task: alt-nocover }
222+
- { python-version: "3.14", task: alt-rest }
221223
fail-fast: false
222224
runs-on: ${{ matrix.os }}
223225
env:
@@ -253,6 +255,7 @@ jobs:
253255
# The versions of pyodide-build and the Pyodide runtime may differ.
254256
PYODIDE_VERSION: 0.29.1
255257
PYODIDE_BUILD_VERSION: 0.31.1
258+
# pyodide 0.29.0 (latest at time of writing) doesn't yet support 3.14
256259
PYTHON_VERSION: 3.13.2
257260
steps:
258261
- uses: actions/checkout@v3
@@ -316,7 +319,7 @@ jobs:
316319
token: ${{ secrets.GH_TOKEN }}
317320
- uses: ./.github/actions/install-base
318321
with:
319-
python-version: "3.10"
322+
python-version: "3.14"
320323
- name: Deploy package
321324
env:
322325
GH_TOKEN: ${{ secrets.GH_TOKEN }}

.github/workflows/update-deps.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ jobs:
1111
runs-on: ubuntu-latest
1212
steps:
1313
- uses: actions/checkout@v3
14-
- name: Set up Python 3.10
14+
- name: Set up Python 3.14
1515
uses: actions/setup-python@v4
1616
with:
17-
python-version: '3.10'
17+
python-version: '3.14'
1818
- name: Update pinned dependencies
1919
run: ./build.sh upgrade-requirements
2020
- name: Open pull request

.readthedocs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ formats:
1414
build:
1515
os: ubuntu-22.04
1616
tools:
17-
python: "3.10"
17+
python: "3.14"
1818
python:
1919
install:
2020
- requirements: requirements/tools.txt

build.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,16 @@ SCRIPTS="$ROOT/tooling/scripts"
1919
# shellcheck source=tooling/scripts/common.sh
2020
source "$SCRIPTS/common.sh"
2121

22+
PYTHON_VERSION="3.14.2"
23+
2224
if [ -n "${GITHUB_ACTIONS-}" ] || [ -n "${CODESPACES-}" ] || [ -n "${CLAUDECODE-}" ] ; then
2325
# We're on GitHub Actions, Codespaces, or Claude Code and already have a suitable Python
2426
PYTHON=$(command -v python3 || command -v python)
2527
else
2628
# Otherwise, we install it from scratch
2729
# NOTE: tooling keeps this version in sync with ci_version in tooling
28-
"$SCRIPTS/ensure-python.sh" 3.10.19
29-
PYTHON=$(pythonloc 3.10.19)/bin/python
30+
"$SCRIPTS/ensure-python.sh" "$PYTHON_VERSION"
31+
PYTHON=$(pythonloc "$PYTHON_VERSION")/bin/python
3032
fi
3133

3234
TOOL_REQUIREMENTS="$ROOT/requirements/tools.txt"

hypothesis-python/RELEASE.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
RELEASE_TYPE: patch
2+
3+
Update some internal type hints.

hypothesis-python/src/hypothesis/database.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ def _usable_dir(path: StrPathT) -> bool:
7676
# Loop terminates because the root dir ('/' on unix) always exists.
7777
path = path.parent
7878
return path.is_dir() and os.access(path, os.R_OK | os.W_OK | os.X_OK)
79-
except PermissionError:
79+
except PermissionError: # pragma: no cover
80+
# path.exists() returns False on 3.14+ instead of raising. See
81+
# https://docs.python.org/3.14/library/pathlib.html#querying-file-type-and-status
8082
return False
8183

8284

@@ -132,18 +134,36 @@ def __call__(self, *args: Any, **kwargs: Any) -> "ExampleDatabase":
132134
# This code only runs if Sphinx has already been imported; and it would live in our
133135
# docs/conf.py except that we would also like it to work for anyone documenting
134136
# downstream ExampleDatabase subclasses too.
135-
if "sphinx" in sys.modules:
137+
#
138+
# We avoid type-checking this block due to this combination facts:
139+
# * our check-types-api CI job runs under 3.14
140+
# * tools.txt therefore pins to a newer version of sphinx which uses 3.12+ `type`
141+
# syntax
142+
# * in test_mypy.py, mypy sees this block, sees sphinx is installed, tries parsing
143+
# sphinx code, and errors
144+
#
145+
# Putting `and not TYPE_CHECKING` here is just a convenience for our testing setup
146+
# (because we don't split mypy tests by running CI version, eg), not for runtime
147+
# behavior.
148+
if "sphinx" in sys.modules and not TYPE_CHECKING: # pragma: no cover
136149
try:
137150
import sphinx.ext.autodoc
138151

139152
signature = "hypothesis.database._EDMeta.__call__"
153+
154+
# _METACLASS_CALL_BLACKLIST moved in newer sphinx versions
155+
try:
156+
import sphinx.ext.autodoc._dynamic._signatures as _module
157+
except ImportError:
158+
_module = sphinx.ext.autodoc
159+
140160
# _METACLASS_CALL_BLACKLIST is a frozenset in later sphinx versions
141-
if isinstance(sphinx.ext.autodoc._METACLASS_CALL_BLACKLIST, frozenset):
142-
sphinx.ext.autodoc._METACLASS_CALL_BLACKLIST = (
143-
sphinx.ext.autodoc._METACLASS_CALL_BLACKLIST | {signature}
144-
)
161+
if isinstance(_module._METACLASS_CALL_BLACKLIST, frozenset):
162+
_module._METACLASS_CALL_BLACKLIST = _module._METACLASS_CALL_BLACKLIST | {
163+
signature
164+
}
145165
else:
146-
sphinx.ext.autodoc._METACLASS_CALL_BLACKLIST.append(signature)
166+
_module._METACLASS_CALL_BLACKLIST.append(signature)
147167
except Exception:
148168
pass
149169

hypothesis-python/src/hypothesis/entry_points.py

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,15 @@
1717

1818
import importlib.metadata
1919
import os
20-
from collections.abc import Generator, Sequence
21-
from importlib.metadata import EntryPoint
22-
23-
24-
def get_entry_points() -> Generator[EntryPoint, None, None]:
25-
try:
26-
eps: Sequence[EntryPoint] = importlib.metadata.entry_points(group="hypothesis")
27-
except TypeError: # pragma: no cover
28-
# Load-time selection requires Python >= 3.10. See also
29-
# https://importlib-metadata.readthedocs.io/en/latest/using.html
30-
eps = importlib.metadata.entry_points().get("hypothesis", [])
31-
yield from eps
3220

3321

3422
def run() -> None:
35-
if not os.environ.get("HYPOTHESIS_NO_PLUGINS"):
36-
for entry in get_entry_points(): # pragma: no cover
37-
hook = entry.load()
38-
if callable(hook):
39-
hook()
23+
if os.environ.get("HYPOTHESIS_NO_PLUGINS"):
24+
return
25+
26+
for entry in importlib.metadata.entry_points(
27+
group="hypothesis"
28+
): # pragma: no cover
29+
hook = entry.load()
30+
if callable(hook):
31+
hook()

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,8 @@ def valid_tuple_axes(
134134
min_size: int = 0,
135135
max_size: int | None = None,
136136
) -> st.SearchStrategy[tuple[int, ...]]:
137-
"""All tuples will have a length >= ``min_size`` and <= ``max_size``. The default
137+
"""
138+
All tuples will have a length >= ``min_size`` and <= ``max_size``. The default
138139
value for ``max_size`` is ``ndim``.
139140
140141
Examples from this strategy shrink towards an empty tuple, which render most
@@ -153,7 +154,6 @@ def valid_tuple_axes(
153154
.. code-block:: python
154155
155156
any_axis_strategy = none() | integers(-ndim, ndim - 1) | valid_tuple_axes(ndim)
156-
157157
"""
158158
check_type(int, ndim, "ndim")
159159
check_type(int, min_size, "min_size")
@@ -364,7 +364,8 @@ def mutually_broadcastable_shapes(
364364
min_side: int = 1,
365365
max_side: int | None = None,
366366
) -> st.SearchStrategy[BroadcastableShapes]:
367-
"""Return a strategy for a specified number of shapes N that are
367+
"""
368+
Return a strategy for a specified number of shapes N that are
368369
mutually-broadcastable with one another and with the provided base shape.
369370
370371
* ``num_shapes`` is the number of mutually broadcast-compatible shapes to generate.
@@ -397,7 +398,6 @@ def mutually_broadcastable_shapes(
397398
BroadcastableShapes(input_shapes=((), (), ()), result_shape=())
398399
BroadcastableShapes(input_shapes=((3,), (), (3,)), result_shape=(3,))
399400
BroadcastableShapes(input_shapes=((1, 2, 3), (3,), ()), result_shape=(1, 2, 3))
400-
401401
"""
402402
arg_msg = "Pass either the `num_shapes` or the `signature` argument, but not both."
403403
if num_shapes is not not_set:

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

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727
get_args,
2828
)
2929

30-
try:
31-
BaseExceptionGroup = BaseExceptionGroup
32-
ExceptionGroup = ExceptionGroup # pragma: no cover
33-
except NameError:
30+
if sys.version_info >= (3, 11):
31+
BaseExceptionGroup = BaseExceptionGroup # noqa: F821
32+
ExceptionGroup = ExceptionGroup # noqa: F821
33+
else: # pragma: no cover
3434
from exceptiongroup import (
3535
BaseExceptionGroup as BaseExceptionGroup,
3636
ExceptionGroup as ExceptionGroup,
@@ -47,7 +47,7 @@
4747
# In order to use NotRequired, we need the version of TypedDict included in Python 3.11+.
4848
if sys.version_info[:2] >= (3, 11):
4949
from typing import NotRequired as NotRequired, TypedDict as TypedDict
50-
else:
50+
else: # pragma: no cover
5151
try:
5252
from typing_extensions import (
5353
NotRequired as NotRequired,
@@ -65,7 +65,7 @@ def __class_getitem__(cls, item):
6565
from typing import (
6666
override as override,
6767
)
68-
except ImportError:
68+
except ImportError: # pragma: no cover
6969
try:
7070
from typing_extensions import (
7171
override as override,
@@ -83,7 +83,7 @@ def __class_getitem__(cls, item):
8383
def add_note(exc, note):
8484
try:
8585
exc.add_note(note)
86-
except AttributeError:
86+
except AttributeError: # pragma: no cover
8787
if not hasattr(exc, "__notes__"):
8888
try:
8989
exc.__notes__ = []
@@ -251,6 +251,31 @@ def bad_django_TestCase(runner: Optional["ConjectureRunner"]) -> bool:
251251
# see issue #3812
252252
if sys.version_info[:2] < (3, 12):
253253

254+
def _asdict_inner(obj, dict_factory):
255+
if dataclasses._is_dataclass_instance(obj):
256+
return dict_factory(
257+
(f.name, _asdict_inner(getattr(obj, f.name), dict_factory))
258+
for f in dataclasses.fields(obj)
259+
)
260+
elif isinstance(obj, tuple) and hasattr(obj, "_fields"):
261+
return type(obj)(*[_asdict_inner(v, dict_factory) for v in obj])
262+
elif isinstance(obj, (list, tuple)):
263+
return type(obj)(_asdict_inner(v, dict_factory) for v in obj)
264+
elif isinstance(obj, dict):
265+
if hasattr(type(obj), "default_factory"):
266+
result = type(obj)(obj.default_factory)
267+
for k, v in obj.items():
268+
result[_asdict_inner(k, dict_factory)] = _asdict_inner(
269+
v, dict_factory
270+
)
271+
return result
272+
return type(obj)(
273+
(_asdict_inner(k, dict_factory), _asdict_inner(v, dict_factory))
274+
for k, v in obj.items()
275+
)
276+
else:
277+
return copy.deepcopy(obj)
278+
254279
def dataclass_asdict(obj, *, dict_factory=dict):
255280
"""
256281
A vendored variant of dataclasses.asdict. Includes the bugfix for
@@ -267,30 +292,6 @@ def dataclass_asdict(obj, *, dict_factory=dict):
267292
dataclass_asdict = dataclasses.asdict
268293

269294

270-
def _asdict_inner(obj, dict_factory):
271-
if dataclasses._is_dataclass_instance(obj):
272-
return dict_factory(
273-
(f.name, _asdict_inner(getattr(obj, f.name), dict_factory))
274-
for f in dataclasses.fields(obj)
275-
)
276-
elif isinstance(obj, tuple) and hasattr(obj, "_fields"):
277-
return type(obj)(*[_asdict_inner(v, dict_factory) for v in obj])
278-
elif isinstance(obj, (list, tuple)):
279-
return type(obj)(_asdict_inner(v, dict_factory) for v in obj)
280-
elif isinstance(obj, dict):
281-
if hasattr(type(obj), "default_factory"):
282-
result = type(obj)(obj.default_factory)
283-
for k, v in obj.items():
284-
result[_asdict_inner(k, dict_factory)] = _asdict_inner(v, dict_factory)
285-
return result
286-
return type(obj)(
287-
(_asdict_inner(k, dict_factory), _asdict_inner(v, dict_factory))
288-
for k, v in obj.items()
289-
)
290-
else:
291-
return copy.deepcopy(obj)
292-
293-
294295
if sys.version_info[:2] < (3, 13):
295296
# batched was added in 3.12, strict flag in 3.13
296297
# copied from 3.13 docs reference implementation

0 commit comments

Comments
 (0)