Skip to content

Commit 915a8d5

Browse files
committed
Merge branch 'master' into provider-conformance-realization
2 parents 057abf7 + 549d8cd commit 915a8d5

38 files changed

+297
-156
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# mypyc
2+
3+
*.so
4+
15
# misc (editors, file systems, etc)
26

37
*.swo

hypothesis-python/docs/changelog.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,31 @@ Hypothesis 6.x
1818

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

21+
.. _v6.140.1:
22+
23+
--------------------
24+
6.140.1 - 2025-09-22
25+
--------------------
26+
27+
This patch re-enables the warning for incompatible :func:`~hypothesis.strategies.shared`
28+
strategies that was first enabled in :v:`6.133.0` but disabled in :v:`6.135.15`.
29+
30+
.. _v6.140.0:
31+
32+
--------------------
33+
6.140.0 - 2025-09-22
34+
--------------------
35+
36+
|st.characters| now validates that the elements of the ``exclude_characters`` and ``include_characters`` arguments are single characters, which was always assumed internally. For example, ``exclude_characters=["a", "b"]`` is valid while ``exclude_characters=["ab"]`` will now raise an error up-front.
37+
38+
.. _v6.139.3:
39+
40+
--------------------
41+
6.139.3 - 2025-09-22
42+
--------------------
43+
44+
Add ``phase`` to the :ref:`hypothesis-specific metadata <observability-hypothesis-metadata>` in :ref:`observability <observability>`.
45+
2146
.. _v6.139.2:
2247

2348
--------------------

hypothesis-python/docs/reference/schema_metadata.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@
4747
"type": "number",
4848
"description": "The unix timestamp when Hypothesis was imported."
4949
},
50+
"phase": {
51+
"type": "string",
52+
"description": "The Hypothesis |Phase| this test case was generated in."
53+
},
5054
"data_status": {
5155
"type": "number",
5256
"enum": [0, 1, 2, 3],

hypothesis-python/src/hypothesis/core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -942,7 +942,7 @@ def __init__(
942942
self._timing_features = {}
943943

944944
@property
945-
def test_identifier(self):
945+
def test_identifier(self) -> str:
946946
return getattr(
947947
current_pytest_item.value, "nodeid", None
948948
) or get_pretty_function_description(self.wrapped_test)

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import sys
1616
import tempfile
1717
import unicodedata
18-
from collections.abc import Iterable
18+
from collections.abc import Collection, Iterable
1919
from functools import cache
2020
from pathlib import Path
2121
from typing import TYPE_CHECKING, Literal, Optional
@@ -78,7 +78,7 @@ def charmap_file(fname: str = "charmap") -> Path:
7878
)
7979

8080

81-
_charmap = None
81+
_charmap: Optional[dict[CategoryName, IntervalsT]] = None
8282

8383

8484
def charmap() -> dict[CategoryName, IntervalsT]:
@@ -293,8 +293,8 @@ def query(
293293
categories: Optional[Categories] = None,
294294
min_codepoint: Optional[int] = None,
295295
max_codepoint: Optional[int] = None,
296-
include_characters: str = "",
297-
exclude_characters: str = "",
296+
include_characters: Collection[str] = "",
297+
exclude_characters: Collection[str] = "",
298298
) -> IntervalSet:
299299
"""Return a tuple of intervals covering the codepoints for all characters
300300
that meet the criteria.
@@ -314,8 +314,8 @@ def query(
314314
if max_codepoint is None:
315315
max_codepoint = sys.maxunicode
316316
catkey = _category_key(categories)
317-
character_intervals = IntervalSet.from_string(include_characters or "")
318-
exclude_intervals = IntervalSet.from_string(exclude_characters or "")
317+
character_intervals = IntervalSet.from_string("".join(include_characters))
318+
exclude_intervals = IntervalSet.from_string("".join(exclude_characters))
319319
qkey = (
320320
catkey,
321321
min_codepoint,

hypothesis-python/src/hypothesis/internal/conjecture/data.py

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -115,19 +115,6 @@ def __getattr__(name: str) -> Any:
115115
threadlocal = ThreadLocal(global_test_counter=int)
116116

117117

118-
class ExtraInformation:
119-
"""A class for holding shared state on a ``ConjectureData`` that should
120-
be added to the final ``ConjectureResult``."""
121-
122-
def __repr__(self) -> str:
123-
return "ExtraInformation({})".format(
124-
", ".join(f"{k}={v!r}" for k, v in self.__dict__.items()),
125-
)
126-
127-
def has_information(self) -> bool:
128-
return bool(self.__dict__)
129-
130-
131118
class Status(IntEnum):
132119
OVERRUN = 0
133120
INVALID = 1
@@ -597,7 +584,6 @@ class ConjectureResult:
597584
nodes: tuple[ChoiceNode, ...] = attr.ib(eq=False, repr=False)
598585
length: int = attr.ib()
599586
output: str = attr.ib()
600-
extra_information: Optional[ExtraInformation] = attr.ib()
601587
expected_exception: Optional[BaseException] = attr.ib()
602588
expected_traceback: Optional[str] = attr.ib()
603589
has_discards: bool = attr.ib()
@@ -720,16 +706,15 @@ def __init__(
720706
self._sampled_from_all_strategies_elements_message: Optional[
721707
tuple[str, object]
722708
] = None
723-
self._shared_strategy_draws: dict[Hashable, tuple[int, Any]] = {}
709+
self._shared_strategy_draws: dict[Hashable, tuple[Any, "SearchStrategy"]] = {}
724710
self._shared_data_strategy: Optional[DataObject] = None
725711
self._stateful_repr_parts: Optional[list[Any]] = None
726712
self.states_for_ids: Optional[dict[int, RandomState]] = None
727713
self.seeds_to_states: Optional[dict[Any, RandomState]] = None
728-
self.hypothesis_runner = not_set
714+
self.hypothesis_runner: Any = not_set
729715

730716
self.expected_exception: Optional[BaseException] = None
731717
self.expected_traceback: Optional[str] = None
732-
self.extra_information = ExtraInformation()
733718

734719
self.prefix = prefix
735720
self.nodes: tuple[ChoiceNode, ...] = ()
@@ -1165,11 +1150,6 @@ def as_result(self) -> Union[ConjectureResult, _Overrun]:
11651150
output=self.output,
11661151
expected_traceback=self.expected_traceback,
11671152
expected_exception=self.expected_exception,
1168-
extra_information=(
1169-
self.extra_information
1170-
if self.extra_information.has_information()
1171-
else None
1172-
),
11731153
has_discards=self.has_discards,
11741154
target_observations=self.target_observations,
11751155
tags=frozenset(self.tags),

hypothesis-python/src/hypothesis/internal/conjecture/datatree.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ def compute_max_children(
220220
# or downwards with our full 128 bit generation, but only half of these
221221
# (plus one for the case of generating zero) result in a probe in the
222222
# direction we want. ((2**128 - 1) // 2) + 1 == 2 ** 127
223-
assert (min_value is None) ^ (max_value is None)
223+
assert (min_value is None) != (max_value is None)
224224
return 2**127
225225
elif choice_type == "boolean":
226226
constraints = cast(BooleanConstraints, constraints)

hypothesis-python/src/hypothesis/internal/conjecture/engine.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from datetime import timedelta
2121
from enum import Enum
2222
from random import Random, getrandbits
23-
from typing import Callable, Final, Literal, NoReturn, Optional, Union, cast
23+
from typing import Callable, Literal, NoReturn, Optional, Union, cast
2424

2525
from hypothesis import HealthCheck, Phase, Verbosity, settings as Settings
2626
from hypothesis._settings import local_settings, note_deprecation
@@ -71,11 +71,15 @@
7171
from hypothesis.internal.observability import Observation, with_observability_callback
7272
from hypothesis.reporting import base_report, report
7373

74+
# In most cases, the following constants are all Final. However, we do allow users
75+
# to monkeypatch all of these variables, which means we cannot annotate them as
76+
# Final or mypyc will inline them and render monkeypatching useless.
77+
7478
#: The maximum number of times the shrinker will reduce the complexity of a failing
7579
#: input before giving up. This avoids falling down a trap of exponential (or worse)
7680
#: complexity, where the shrinker appears to be making progress but will take a
7781
#: substantially long time to finish completely.
78-
MAX_SHRINKS: Final[int] = 500
82+
MAX_SHRINKS: int = 500
7983

8084
# If the shrinking phase takes more than five minutes, abort it early and print
8185
# a warning. Many CI systems will kill a build after around ten minutes with
@@ -87,17 +91,17 @@
8791
#: for before giving up. This is across all shrinks for the same failure, so even
8892
#: if the shrinker successfully reduces the complexity of a single failure several
8993
#: times, it will stop when it hits |MAX_SHRINKING_SECONDS| of total time taken.
90-
MAX_SHRINKING_SECONDS: Final[int] = 300
94+
MAX_SHRINKING_SECONDS: int = 300
9195

9296
#: The maximum amount of entropy a single test case can use before giving up
9397
#: while making random choices during input generation.
9498
#:
9599
#: The "unit" of one |BUFFER_SIZE| does not have any defined semantics, and you
96100
#: should not rely on it, except that a linear increase |BUFFER_SIZE| will linearly
97101
#: increase the amount of entropy a test case can use during generation.
98-
BUFFER_SIZE: Final[int] = 8 * 1024
99-
CACHE_SIZE: Final[int] = 10000
100-
MIN_TEST_CALLS: Final[int] = 10
102+
BUFFER_SIZE: int = 8 * 1024
103+
CACHE_SIZE: int = 10000
104+
MIN_TEST_CALLS: int = 10
101105

102106

103107
def shortlex(s):
@@ -168,8 +172,8 @@ class RunIsComplete(Exception):
168172
def _get_provider(backend: str) -> Union[type, PrimitiveProvider]:
169173
provider_cls = AVAILABLE_PROVIDERS[backend]
170174
if isinstance(provider_cls, str):
171-
mname, cname = provider_cls.rsplit(".", 1)
172-
provider_cls = getattr(importlib.import_module(mname), cname)
175+
module_name, class_name = provider_cls.rsplit(".", 1)
176+
provider_cls = getattr(importlib.import_module(module_name), class_name)
173177

174178
if provider_cls.lifetime == "test_function":
175179
return provider_cls(None)
@@ -309,13 +313,9 @@ def __init__(
309313
self.first_bug_found_at: Optional[int] = None
310314
self.last_bug_found_at: Optional[int] = None
311315

312-
# At runtime, the keys are only ever type `InterestingOrigin`, but can be `None` during tests.
313-
self.shrunk_examples: set[Optional[InterestingOrigin]] = set()
314-
316+
self.shrunk_examples: set[InterestingOrigin] = set()
315317
self.health_check_state: Optional[HealthCheckState] = None
316-
317318
self.tree: DataTree = DataTree()
318-
319319
self.provider: Union[type, PrimitiveProvider] = _get_provider(
320320
self.settings.backend
321321
)
@@ -386,8 +386,6 @@ def _log_phase_statistics(
386386
self._current_phase = phase
387387
yield
388388
finally:
389-
# We ignore the mypy type error here. Because `phase` is a string literal and "-phase" is a string literal
390-
# as well, the concatenation will always be valid key in the dictionary.
391389
self.statistics[phase + "-phase"] = { # type: ignore
392390
"duration-seconds": time.perf_counter() - start_time,
393391
"test-cases": list(self.stats_per_test_case),

hypothesis-python/src/hypothesis/internal/conjecture/shrinking/integer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def try_mask(k):
6161
return self.consider(mask & base)
6262

6363
@property
64-
def size(self):
64+
def size(self) -> int:
6565
return self.current.bit_length()
6666

6767
def shrink_by_multiples(self, k):

hypothesis-python/src/hypothesis/internal/conjecture/utils.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
from collections import OrderedDict, abc
1717
from collections.abc import Sequence
1818
from functools import lru_cache
19-
from typing import TYPE_CHECKING, Optional, TypeVar, Union
19+
from types import FunctionType
20+
from typing import TYPE_CHECKING, Callable, Optional, TypeVar, Union
2021

2122
from hypothesis.errors import InvalidArgument
2223
from hypothesis.internal.compat import int_from_bytes
2324
from hypothesis.internal.floats import next_up
25+
from hypothesis.internal.lambda_sources import _function_key
2426

2527
if TYPE_CHECKING:
2628
from hypothesis.internal.conjecture.data import ConjectureData
@@ -34,6 +36,20 @@ def calc_label_from_name(name: str) -> int:
3436
return int_from_bytes(hashed[:8])
3537

3638

39+
def calc_label_from_callable(f: Callable) -> int:
40+
if isinstance(f, FunctionType):
41+
return calc_label_from_hash(_function_key(f, ignore_name=True))
42+
elif isinstance(f, type):
43+
return calc_label_from_cls(f)
44+
else:
45+
# probably an instance defining __call__
46+
try:
47+
return calc_label_from_hash(f)
48+
except Exception:
49+
# not hashable
50+
return calc_label_from_cls(type(f))
51+
52+
3753
def calc_label_from_cls(cls: type) -> int:
3854
return calc_label_from_name(cls.__qualname__)
3955

0 commit comments

Comments
 (0)