Skip to content

Commit a034db0

Browse files
committed
Merge branch 'master' into crosshair-event
2 parents ad35fc2 + bd2137f commit a034db0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+424
-259
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ jobs:
143143
export TASK=${{ matrix.task }}
144144
if [[ $TASK == check-crosshair-custom-* ]]; then
145145
GROUP="${TASK#check-crosshair-custom-}"
146-
./build.sh check-crosshair-custom -- -n auto $(cd hypothesis-python && echo tests/$GROUP | xargs -n1 echo | grep -v "_py312" | xargs)
146+
./build.sh check-crosshair-custom -- -n auto $(cd hypothesis-python && echo tests/$GROUP | xargs -n1 echo | grep -Ev "_py312|_py314" | xargs)
147147
else
148148
./build.sh
149149
fi
@@ -279,7 +279,7 @@ jobs:
279279
source .venv-pyodide/bin/activate
280280
# pyodide can't run multiple processes internally, so parallelize explicitly over
281281
# discovered test files instead (20 at a time)
282-
TEST_FILES=$(ls hypothesis-python/tests/cover/test*.py)
282+
TEST_FILES=$(ls hypothesis-python/tests/cover/test*.py | grep -v "_py314")
283283
echo "test files: $TEST_FILES"
284284
parallel --max-procs 100% --max-args 20 --keep-order --line-buffer \
285285
python -m pytest -p no:cacheprovider <<< $TEST_FILES

hypothesis-python/docs/changelog.rst

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Changelog
33
=========
44

55
This is a record of all past Hypothesis releases and what went into them,
6-
in reverse chronological order. All previous releases should still be available
6+
in reverse chronological order. All previous releases are still available
77
:pypi:`on PyPI <hypothesis>`.
88

99

@@ -18,6 +18,22 @@ Hypothesis 6.x
1818

1919
.. include:: ../RELEASE.rst
2020

21+
.. _v6.138.1:
22+
23+
--------------------
24+
6.138.1 - 2025-08-15
25+
--------------------
26+
27+
Internal refactoring and cleanup. As a result, ``hypothesis[black]`` now requires ``black>=20.8b0`` instead of the previous ``black>=19.10b0``.
28+
29+
.. _v6.138.0:
30+
31+
--------------------
32+
6.138.0 - 2025-08-13
33+
--------------------
34+
35+
On Python 3.14, |memoryview| is newly generic. This release adds the ability for |st.from_type| to resolve generic |memoryview| types on 3.14, like ``st.from_type(memoryview[CustomBufferClass])`` . ``CustomBufferClass`` must implement ``__buffer__``, as expected by |memoryview|.
36+
2137
.. _v6.137.3:
2238

2339
--------------------

hypothesis-python/docs/prolog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@
181181
.. |bytes| replace:: :obj:`python:bytes`
182182
.. |float| replace:: :obj:`python:float`
183183
.. |assert| replace:: :keyword:`python:assert`
184+
.. |memoryview| replace:: :class:`python:memoryview`
184185
.. |dataclasses| replace:: :mod:`python:dataclasses`
185186
.. |random.random| replace:: :class:`python:random.Random`
186187
.. |random.Random| replace:: :class:`python:random.Random`

hypothesis-python/pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ documentation = "https://hypothesis.readthedocs.io"
100100
issues = "https://github.com/HypothesisWorks/hypothesis/issues"
101101

102102
[project.optional-dependencies]
103-
cli = ["click>=7.0", "black>=19.10b0", "rich>=9.0.0"]
103+
cli = ["click>=7.0", "black>=20.8b0", "rich>=9.0.0"]
104104
codemods = ["libcst>=0.3.16"]
105-
ghostwriter = ["black>=19.10b0"]
105+
ghostwriter = ["black>=20.8b0"]
106106
pytz = ["pytz>=2014.1"]
107107
dateutil = ["python-dateutil>=1.4"]
108108
lark = ["lark>=0.10.1"] # probably still works with old `lark-parser` too
@@ -121,7 +121,7 @@ zoneinfo = ["tzdata>=2025.2; sys_platform == 'win32' or sys_platform == 'emscrip
121121
django = ["django>=4.2"]
122122
watchdog = ["watchdog>=4.0.0"]
123123
# Avoid changing this by hand. This is automatically updated by update_changelog_and_version
124-
all = ["black>=19.10b0", "click>=7.0", "crosshair-tool>=0.0.93", "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"]
124+
all = ["black>=20.8b0", "click>=7.0", "crosshair-tool>=0.0.93", "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"]
125125

126126
[tool.setuptools.dynamic]
127127
version = {attr = "hypothesis.version.__version__"}

hypothesis-python/src/hypothesis/core.py

Lines changed: 46 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -684,15 +684,16 @@ def execute_explicit_examples(state, wrapped_test, arguments, kwargs, original_s
684684
)
685685

686686
empty_data.freeze()
687-
tc = make_testcase(
688-
run_start=state._start_timestamp,
689-
property=state.test_identifier,
690-
data=empty_data,
691-
how_generated="explicit example",
692-
representation=state._string_repr,
693-
timing=state._timing_features,
694-
)
695-
deliver_observation(tc)
687+
if observability_enabled():
688+
tc = make_testcase(
689+
run_start=state._start_timestamp,
690+
property=state.test_identifier,
691+
data=empty_data,
692+
how_generated="explicit example",
693+
representation=state._string_repr,
694+
timing=state._timing_features,
695+
)
696+
deliver_observation(tc)
696697

697698
if fragments_reported:
698699
verbose_report(fragments_reported[0].replace("Falsifying", "Trying", 1))
@@ -846,21 +847,23 @@ def execute(data, function):
846847
return default_executor
847848

848849

850+
# This function is a crude solution, a better way of resolving it would probably
851+
# be to rewrite a bunch of exception handlers to use except*.
852+
T = TypeVar("T", bound=BaseException)
853+
854+
855+
def _flatten_group(excgroup: BaseExceptionGroup[T]) -> list[T]:
856+
found_exceptions: list[T] = []
857+
for exc in excgroup.exceptions:
858+
if isinstance(exc, BaseExceptionGroup):
859+
found_exceptions.extend(_flatten_group(exc))
860+
else:
861+
found_exceptions.append(exc)
862+
return found_exceptions
863+
864+
849865
@contextlib.contextmanager
850866
def unwrap_markers_from_group() -> Generator[None, None, None]:
851-
# This function is a crude solution, a better way of resolving it would probably
852-
# be to rewrite a bunch of exception handlers to use except*.
853-
T = TypeVar("T", bound=BaseException)
854-
855-
def _flatten_group(excgroup: BaseExceptionGroup[T]) -> list[T]:
856-
found_exceptions: list[T] = []
857-
for exc in excgroup.exceptions:
858-
if isinstance(exc, BaseExceptionGroup):
859-
found_exceptions.extend(_flatten_group(exc))
860-
else:
861-
found_exceptions.append(exc)
862-
return found_exceptions
863-
864867
try:
865868
yield
866869
except BaseExceptionGroup as excgroup:
@@ -1111,8 +1114,8 @@ def run(data: ConjectureData) -> None:
11111114
add_note(e, msg.format(format_arg))
11121115
raise
11131116
finally:
1114-
if parts := getattr(data, "_stateful_repr_parts", None):
1115-
self._string_repr = "\n".join(parts)
1117+
if data._stateful_repr_parts is not None:
1118+
self._string_repr = "\n".join(data._stateful_repr_parts)
11161119

11171120
if observability_enabled():
11181121
printer = RepresentationPrinter(context=context)
@@ -1294,7 +1297,7 @@ def _execute_once_for_engine(self, data: ConjectureData) -> None:
12941297
if trace: # pragma: no cover
12951298
# Trace collection is explicitly disabled under coverage.
12961299
self.explain_traces[interesting_origin].add(frozenset(trace))
1297-
if interesting_origin[0] == DeadlineExceeded:
1300+
if interesting_origin.exc_type == DeadlineExceeded:
12981301
self.failed_due_to_deadline = True
12991302
self.explain_traces.clear()
13001303
try:
@@ -1353,6 +1356,7 @@ def _execute_once_for_engine(self, data: ConjectureData) -> None:
13531356
backend_metadata=data.provider.observe_test_case(),
13541357
)
13551358
deliver_observation(tc)
1359+
13561360
for msg in data.provider.observe_information_messages(
13571361
lifetime="test_case"
13581362
):
@@ -1538,21 +1542,23 @@ def run_engine(self):
15381542
raise NotImplementedError("This should be unreachable")
15391543
finally:
15401544
ran_example.freeze()
1541-
# log our observability line for the final failing example
1542-
tc = make_testcase(
1543-
run_start=self._start_timestamp,
1544-
property=self.test_identifier,
1545-
data=ran_example,
1546-
how_generated="minimal failing example",
1547-
representation=self._string_repr,
1548-
arguments=ran_example._observability_args,
1549-
timing=self._timing_features,
1550-
coverage=None, # Not recorded when we're replaying the MFE
1551-
status="passed" if sys.exc_info()[0] else "failed",
1552-
status_reason=str(origin or "unexpected/flaky pass"),
1553-
metadata={"traceback": tb},
1554-
)
1555-
deliver_observation(tc)
1545+
if observability_enabled():
1546+
# log our observability line for the final failing example
1547+
tc = make_testcase(
1548+
run_start=self._start_timestamp,
1549+
property=self.test_identifier,
1550+
data=ran_example,
1551+
how_generated="minimal failing example",
1552+
representation=self._string_repr,
1553+
arguments=ran_example._observability_args,
1554+
timing=self._timing_features,
1555+
coverage=None, # Not recorded when we're replaying the MFE
1556+
status="passed" if sys.exc_info()[0] else "failed",
1557+
status_reason=str(origin or "unexpected/flaky pass"),
1558+
metadata={"traceback": tb},
1559+
)
1560+
deliver_observation(tc)
1561+
15561562
# Whether or not replay actually raised the exception again, we want
15571563
# to print the reproduce_failure decorator for the failing example.
15581564
if self.settings.print_blob:

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ def broadcastable_shapes(
241241
if s < min_side and s != 1:
242242
max_dims = n
243243
break
244-
elif not (min_side <= 1 <= max_side or s <= max_side):
244+
if not (min_side <= 1 <= max_side or s <= max_side):
245245
max_dims = n
246246
break
247247

@@ -466,7 +466,7 @@ def mutually_broadcastable_shapes(
466466
if s < min_side and s != 1:
467467
max_dims = n
468468
break
469-
elif not (min_side <= 1 <= max_side or s <= max_side):
469+
if not (min_side <= 1 <= max_side or s <= max_side):
470470
max_dims = n
471471
break
472472

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def __call_node_to_example_dec(
137137
try:
138138
pretty = black.format_str(
139139
cst.Module([]).code_for_node(via),
140-
mode=black.FileMode(line_length=self.line_length),
140+
mode=black.Mode(line_length=self.line_length),
141141
)
142142
except (ImportError, AttributeError): # pragma: no cover
143143
return None # See https://github.com/psf/black/pull/4224

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,8 +426,8 @@ def do_draw(self, data):
426426
if val in seen:
427427
elements.reject("chose an element we've already used")
428428
continue
429-
else:
430-
seen.add(val)
429+
seen.add(val)
430+
431431
result_obj[i] = val
432432
assigned.add(i)
433433
fill_mask[i] = False

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -266,10 +266,7 @@ def _strategy_for(param: inspect.Parameter, docstring: str) -> st.SearchStrategy
266266
if match is None:
267267
continue
268268
doc_type = match.group(1)
269-
if doc_type.endswith(", optional"):
270-
# Convention to describe "argument may be omitted"
271-
doc_type = doc_type[: -len(", optional")]
272-
doc_type = doc_type.strip("}{")
269+
doc_type = doc_type.removesuffix(", optional").strip("}{")
273270
elements = []
274271
types = []
275272
for token in re.split(r",? +or +| *, *", doc_type):
@@ -1083,13 +1080,21 @@ def _make_test(imports: ImportSet, body: str) -> str:
10831080
header += "# TODO: replace st.nothing() with an appropriate strategy\n\n"
10841081
elif nothings >= 1:
10851082
header += "# TODO: replace st.nothing() with appropriate strategies\n\n"
1086-
return black.format_str(header + body, mode=black.FileMode())
1083+
return black.format_str(header + body, mode=black.Mode())
10871084

10881085

10891086
def _is_probably_ufunc(obj):
10901087
# See https://numpy.org/doc/stable/reference/ufuncs.html - there doesn't seem
10911088
# to be an upstream function to detect this, so we just guess.
1092-
has_attributes = "nin nout nargs ntypes types identity signature".split()
1089+
has_attributes = [
1090+
"nin",
1091+
"nout",
1092+
"nargs",
1093+
"ntypes",
1094+
"types",
1095+
"identity",
1096+
"signature",
1097+
]
10931098
return callable(obj) and all(hasattr(obj, name) for name in has_attributes)
10941099

10951100

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def _try_import(mod_name: str, attr_name: str) -> Any:
9090
"valid_tuple_axes",
9191
]
9292

93-
TIME_RESOLUTIONS = tuple("Y M D h m s ms us ns ps fs as".split())
93+
TIME_RESOLUTIONS = ("Y", "M", "D", "h", "m", "s", "ms", "us", "ns", "ps", "fs", "as")
9494

9595
# See https://github.com/HypothesisWorks/hypothesis/pull/3394 and linked discussion.
9696
NP_FIXED_UNICODE = tuple(int(x) for x in np.__version__.split(".")[:2]) >= (1, 19)
@@ -349,8 +349,8 @@ def do_draw(self, data):
349349
if result[i] in seen:
350350
elements.reject()
351351
continue
352-
else:
353-
seen.add(result[i])
352+
seen.add(result[i])
353+
354354
needs_fill[i] = False
355355
if needs_fill.any():
356356
# We didn't fill all of the indices in the early loop, so we

0 commit comments

Comments
 (0)