summaryrefslogtreecommitdiffstats
path: root/contrib/python/hypothesis
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2026-06-03 11:48:18 +0300
committerrobot-piglet <[email protected]>2026-06-03 13:01:08 +0300
commit8f985d25514540edfb1474af9eeb9f0d5c420d55 (patch)
tree6d0af8185f26c264f661316b63b389373257cde3 /contrib/python/hypothesis
parentb62b275069b9c743d3da207efbc6e03206212f47 (diff)
Intermediate changes
commit_hash:d2da6ed53d161b48478eb9496a3b244ce234b972
Diffstat (limited to 'contrib/python/hypothesis')
-rw-r--r--contrib/python/hypothesis/py3/.dist-info/METADATA18
-rw-r--r--contrib/python/hypothesis/py3/_hypothesis_ftz_detector.py4
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/extra/_patching.py8
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/extra/dateutil.py2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/extra/django/_fields.py1
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/extra/ghostwriter.py4
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/extra/lark.py12
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/junkdrawer.py2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/providers.py143
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/utils.py4
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/coverage.py2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/filtering.py2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/floats.py6
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/observability.py3
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/statistics.py260
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/strategies/_internal/attrs.py8
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/strategies/_internal/collections.py2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py15
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/strategies/_internal/strategies.py6
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/strategies/_internal/types.py2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/vendor/tlds-alpha-by-domain.txt3
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/version.py2
-rw-r--r--contrib/python/hypothesis/py3/ya.make3
23 files changed, 401 insertions, 111 deletions
diff --git a/contrib/python/hypothesis/py3/.dist-info/METADATA b/contrib/python/hypothesis/py3/.dist-info/METADATA
index 21754a6e162..053754adaf7 100644
--- a/contrib/python/hypothesis/py3/.dist-info/METADATA
+++ b/contrib/python/hypothesis/py3/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: hypothesis
-Version: 6.152.7
+Version: 6.152.9
Summary: The property-based testing library for Python
Author-email: "David R. MacIver and Zac Hatfield-Dodds" <[email protected]>
License-Expression: MPL-2.0
@@ -60,21 +60,21 @@ Requires-Dist: dpcontracts>=0.4; extra == "dpcontracts"
Provides-Extra: redis
Requires-Dist: redis>=3.0.0; extra == "redis"
Provides-Extra: crosshair
-Requires-Dist: hypothesis-crosshair>=0.0.27; extra == "crosshair"
-Requires-Dist: crosshair-tool>=0.0.102; extra == "crosshair"
+Requires-Dist: hypothesis-crosshair>=0.0.28; extra == "crosshair"
+Requires-Dist: crosshair-tool>=0.0.104; extra == "crosshair"
Provides-Extra: zoneinfo
-Requires-Dist: tzdata>=2026.1; (sys_platform == "win32" or sys_platform == "emscripten") and extra == "zoneinfo"
+Requires-Dist: tzdata>=2026.2; (sys_platform == "win32" or sys_platform == "emscripten") and extra == "zoneinfo"
Provides-Extra: django
-Requires-Dist: django>=4.2; extra == "django"
+Requires-Dist: django>=5.2; extra == "django"
Provides-Extra: watchdog
Requires-Dist: watchdog>=4.0.0; extra == "watchdog"
Provides-Extra: all
Requires-Dist: black>=20.8b0; extra == "all"
Requires-Dist: click>=7.0; extra == "all"
-Requires-Dist: crosshair-tool>=0.0.102; extra == "all"
-Requires-Dist: django>=4.2; extra == "all"
+Requires-Dist: crosshair-tool>=0.0.104; extra == "all"
+Requires-Dist: django>=5.2; extra == "all"
Requires-Dist: dpcontracts>=0.4; extra == "all"
-Requires-Dist: hypothesis-crosshair>=0.0.27; extra == "all"
+Requires-Dist: hypothesis-crosshair>=0.0.28; extra == "all"
Requires-Dist: lark>=0.10.1; extra == "all"
Requires-Dist: libcst>=0.3.16; extra == "all"
Requires-Dist: numpy>=1.21.6; extra == "all"
@@ -84,7 +84,7 @@ Requires-Dist: python-dateutil>=1.4; extra == "all"
Requires-Dist: pytz>=2014.1; extra == "all"
Requires-Dist: redis>=3.0.0; extra == "all"
Requires-Dist: rich>=9.0.0; extra == "all"
-Requires-Dist: tzdata>=2026.1; (sys_platform == "win32" or sys_platform == "emscripten") and extra == "all"
+Requires-Dist: tzdata>=2026.2; (sys_platform == "win32" or sys_platform == "emscripten") and extra == "all"
Requires-Dist: watchdog>=4.0.0; extra == "all"
Dynamic: license-file
diff --git a/contrib/python/hypothesis/py3/_hypothesis_ftz_detector.py b/contrib/python/hypothesis/py3/_hypothesis_ftz_detector.py
index 5eaadadd512..2a5ae3915a4 100644
--- a/contrib/python/hypothesis/py3/_hypothesis_ftz_detector.py
+++ b/contrib/python/hypothesis/py3/_hypothesis_ftz_detector.py
@@ -83,8 +83,8 @@ def modules_imported_by(mod: str) -> FTZCulprits:
# We don't want to redo all the expensive process-spawning checks when we've already
# done them, so we cache known-good packages and a known-FTZ result if we have one.
-KNOWN_FTZ = None
-CHECKED_CACHE = set()
+KNOWN_FTZ: str | None = None
+CHECKED_CACHE: set[str] = set()
def identify_ftz_culprits() -> str:
diff --git a/contrib/python/hypothesis/py3/hypothesis/extra/_patching.py b/contrib/python/hypothesis/py3/hypothesis/extra/_patching.py
index ca8048ee876..0417703c213 100644
--- a/contrib/python/hypothesis/py3/hypothesis/extra/_patching.py
+++ b/contrib/python/hypothesis/py3/hypothesis/extra/_patching.py
@@ -129,20 +129,20 @@ class AddExamplesCodemod(VisitorBasedCodemodCommand):
else node.args
),
)
- via: cst.BaseExpression = cst.Call(
+ expr: cst.BaseExpression = cst.Call(
func=cst.Attribute(node, cst.Name("via")),
args=[cst.Arg(cst.SimpleString(repr(via)))],
)
if black: # pragma: no branch
try:
pretty = black.format_str(
- cst.Module([]).code_for_node(via),
+ cst.Module([]).code_for_node(expr),
mode=black.Mode(line_length=self.line_length),
)
except (ImportError, AttributeError): # pragma: no cover
return None # See https://github.com/psf/black/pull/4224
- via = cst.parse_expression(pretty.strip())
- return cst.Decorator(via)
+ expr = cst.parse_expression(pretty.strip())
+ return cst.Decorator(expr)
def leave_FunctionDef(
self, _original_node: cst.FunctionDef, updated_node: cst.FunctionDef
diff --git a/contrib/python/hypothesis/py3/hypothesis/extra/dateutil.py b/contrib/python/hypothesis/py3/hypothesis/extra/dateutil.py
index a41f7f48241..debc7af7750 100644
--- a/contrib/python/hypothesis/py3/hypothesis/extra/dateutil.py
+++ b/contrib/python/hypothesis/py3/hypothesis/extra/dateutil.py
@@ -21,7 +21,7 @@ and :func:`~hypothesis.strategies.times` produce timezone-aware values.
import datetime as dt
-from dateutil import tz, zoneinfo # type: ignore
+from dateutil import tz, zoneinfo
from hypothesis import strategies as st
from hypothesis.strategies._internal.utils import cacheable, defines_strategy
diff --git a/contrib/python/hypothesis/py3/hypothesis/extra/django/_fields.py b/contrib/python/hypothesis/py3/hypothesis/extra/django/_fields.py
index 960b52be75f..2dece58af29 100644
--- a/contrib/python/hypothesis/py3/hypothesis/extra/django/_fields.py
+++ b/contrib/python/hypothesis/py3/hypothesis/extra/django/_fields.py
@@ -368,6 +368,7 @@ def from_field(field: F) -> st.SearchStrategy[F | None]:
"""
check_type((dm.Field, df.Field), field, "field")
+ strategy: st.SearchStrategy[Any]
# The following isinstance check must occur *before* the getattr
# check. In the case of ModelChoicesField, evaluating
# field.choices causes database access, which we want to avoid if
diff --git a/contrib/python/hypothesis/py3/hypothesis/extra/ghostwriter.py b/contrib/python/hypothesis/py3/hypothesis/extra/ghostwriter.py
index ef793634e9a..73216946ce3 100644
--- a/contrib/python/hypothesis/py3/hypothesis/extra/ghostwriter.py
+++ b/contrib/python/hypothesis/py3/hypothesis/extra/ghostwriter.py
@@ -1806,7 +1806,7 @@ def _make_binop_body(
try:
identity = find(operands, lambda x: True, settings=_quietly_settings)
except Exception:
- identity = "identity element here" # type: ignore
+ identity = "identity element here"
# If the repr of this element is invalid Python, stringify it - this
# can't be executed as-is, but at least makes it clear what should
# happen. E.g. type(None) -> <class 'NoneType'> -> quoted.
@@ -1817,7 +1817,7 @@ def _make_binop_body(
# executed; so you still shouldn't ghostwrite for hostile code.
compile(repr(identity), "<string>", "exec")
except SyntaxError:
- identity = repr(identity) # type: ignore
+ identity = repr(identity)
identity_parts = [
f"{identity = }",
_assert_eq(
diff --git a/contrib/python/hypothesis/py3/hypothesis/extra/lark.py b/contrib/python/hypothesis/py3/hypothesis/extra/lark.py
index ad554e222ee..75702b4abc8 100644
--- a/contrib/python/hypothesis/py3/hypothesis/extra/lark.py
+++ b/contrib/python/hypothesis/py3/hypothesis/extra/lark.py
@@ -69,17 +69,17 @@ class LarkStrategy(st.SearchStrategy):
) -> None:
super().__init__()
assert isinstance(grammar, lark.lark.Lark)
- start: list[str] = grammar.options.start if start is None else [start]
+ starts = grammar.options.start if start is None else [start]
# This is a total hack, but working around the changes is a nicer user
# experience than breaking for anyone who doesn't instantly update their
# installation of Lark alongside Hypothesis.
compile_args = signature(grammar.grammar.compile).parameters
if "terminals_to_keep" in compile_args:
- terminals, rules, ignore_names = grammar.grammar.compile(start, ())
+ terminals, rules, ignore_names = grammar.grammar.compile(starts, ())
elif "start" in compile_args: # pragma: no cover
# Support lark <= 0.10.0, without the terminals_to_keep argument.
- terminals, rules, ignore_names = grammar.grammar.compile(start) # type: ignore
+ terminals, rules, ignore_names = grammar.grammar.compile(starts) # type: ignore
else: # pragma: no cover
# This branch is to support lark <= 0.7.1, without the start argument.
terminals, rules, ignore_names = grammar.grammar.compile() # type: ignore
@@ -140,12 +140,12 @@ class LarkStrategy(st.SearchStrategy):
):
allowed_rules = {*self.terminal_strategies, *nonterminals}
- if set(start).isdisjoint(allowed_rules):
+ if set(starts).isdisjoint(allowed_rules):
raise InvalidArgument(
- f"No start rule {tuple(start)} is allowed by {alphabet=}"
+ f"No start rule {tuple(starts)} is allowed by {alphabet=}"
)
self.start = st.sampled_from(
- [self.names_to_symbols[s] for s in start if s in allowed_rules]
+ [self.names_to_symbols[s] for s in starts if s in allowed_rules]
)
self.nonterminal_strategies = {
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/junkdrawer.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/junkdrawer.py
index 6f356706def..0463b9b9b09 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/junkdrawer.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/junkdrawer.py
@@ -287,7 +287,7 @@ def stack_depth_of_caller() -> int:
frame = sys._getframe(2)
size = 1
while frame:
- frame = frame.f_back # type: ignore[assignment]
+ frame = frame.f_back
size += 1
return size
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/providers.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/providers.py
index be8bbeebf89..a8483a3650a 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/providers.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/providers.py
@@ -45,8 +45,6 @@ from hypothesis.internal.conjecture.choice import (
from hypothesis.internal.conjecture.floats import lex_to_float
from hypothesis.internal.conjecture.junkdrawer import bits_to_bytes
from hypothesis.internal.conjecture.utils import (
- INT_SIZES,
- INT_SIZES_SAMPLER,
Sampler,
many,
)
@@ -57,6 +55,7 @@ from hypothesis.internal.constants_ast import (
)
from hypothesis.internal.floats import (
SIGNALING_NAN,
+ clamp,
float_to_int,
make_float_clamper,
next_down,
@@ -64,6 +63,11 @@ from hypothesis.internal.floats import (
)
from hypothesis.internal.intervalsets import IntervalSet
from hypothesis.internal.observability import InfoObservationType, TestCaseObservation
+from hypothesis.internal.statistics import (
+ LogStudentTDistribution,
+ PiecewiseDistribution,
+ UniformDistribution,
+)
if TYPE_CHECKING:
from hypothesis.internal.conjecture.data import ConjectureData
@@ -73,6 +77,13 @@ T = TypeVar("T")
LifetimeT: TypeAlias = Literal["test_case", "test_function"]
COLLECTION_DEFAULT_MAX_SIZE = 10**10 # "arbitrarily large"
+# see https://github.com/HypothesisWorks/hypothesis/pull/4728 for details
+INTEGERS_DISTRIBUTION = PiecewiseDistribution(
+ inner=UniformDistribution(half_width=256),
+ outer=LogStudentTDistribution(scale_bits=13, df=2),
+ switchover=256,
+)
+
#: Registered Hypothesis backends. This is a dictionary where keys are the name
#: to be used in |settings.backend|. The value of a key can be either:
@@ -845,12 +856,6 @@ class HypothesisProvider(PrimitiveProvider):
assert isinstance(constant, int)
return constant
- center = 0
- if min_value is not None:
- center = max(min_value, center)
- if max_value is not None:
- center = min(max_value, center)
-
if weights is not None:
assert min_value is not None
assert max_value is not None
@@ -871,28 +876,81 @@ class HypothesisProvider(PrimitiveProvider):
idx = sampler.sample(self._cd)
if idx == 0:
- return self._draw_bounded_integer(min_value, max_value)
+ return self._draw_integer_from_distribution(min_value, max_value)
# implicit reliance on dicts being sorted for determinism
return list(weights)[idx - 1]
- if min_value is None and max_value is None:
- return self._draw_unbounded_integer()
+ return self._draw_integer_from_distribution(min_value, max_value)
+
+ def _draw_integer_from_distribution(
+ self, min_value: int | None, max_value: int | None
+ ) -> int:
+ assert self._random is not None
+ dist = INTEGERS_DISTRIBUTION
- if min_value is None:
+ # Our integers distribution is defined over the full float64 range. If the user
+ # has not placed a lower and/or upper bound, we don't actually want to sample
+ # from this full range, because we might otherwise generate integers
+ # that are so large as to cause problems. For example python errors when passing
+ # a too-large integer to c APIs (common and implicit in some python stdlib APIs).
+ #
+ # * If the user asks for an unbounded range, we bound at [-2**128, 2**128].
+ # * If the user asks for a half-bounded range starting at n, we bound at
+ # [n, 2**128] for n < 2**127, and [n, 2n] otherwise, so we always support
+ # generating large integers if a user explicitly asks for integers around that
+ # magnitude. The choice of a factor of two here is arbitrary.
+ if min_value is None and max_value is None:
+ min_value = -(2**128)
+ max_value = 2**128
+ elif min_value is None:
assert max_value is not None
- probe = max_value + 1
- while max_value < probe:
- probe = center + self._draw_unbounded_integer()
- return probe
+ min_value = -max(2**128, 2 * abs(max_value))
+ elif max_value is None:
+ max_value = max(2**128, 2 * abs(min_value))
- if max_value is None:
- assert min_value is not None
- probe = min_value - 1
- while probe < min_value:
- probe = center + self._draw_unbounded_integer()
- return probe
+ lo = 0.0
+ hi = 1.0
+ safe_bounds = True
+ try:
+ cdf_min = min_value - 0.5
+ cdf_max = max_value + 0.5
+ except OverflowError:
+ safe_bounds = False
+
+ if safe_bounds:
+ lo = dist.cdf(cdf_min)
+ hi = dist.cdf(cdf_max)
+
+ # if the bounds in CDF-space are too close together, there isn't enough room
+ # to stably sample between hi and lo. For example, in the extreme case of
+ # hi = lo (which can happen even if min_value != max_value due to either float
+ # imprecision or numerical instability), our sampling algorithm would collapse
+ # to always return the sample value.
+ #
+ # The choice of 1e-13 here is somewhat arbitrary. At 1.0, epsilon in float64
+ # is 2^-53 ~= 1e-16. We want some breathing room from epsilon, because we need
+ # some variation to actually sample in. I haven't put much thought into where
+ # the correct tradeoff lies. The downside risk to choosing a too-aggressive
+ # bound is some integers may literally not be representable in the `hi - lo`
+ # sampling space. The downside risk to a too-conservative bound is switching
+ # off our good distribution too early.
+ if hi - lo < 1e-13:
+ safe_bounds = False
- return self._draw_bounded_integer(min_value, max_value)
+ if safe_bounds:
+ # inverse_cdf requires strictly 0 < p < 1. Resample until we get it.
+ while (p := lo + self._random.random() * (hi - lo)) in {0, 1}:
+ pass # pragma: no cover
+ n = round(dist.inverse_cdf(p))
+ # As an extra measure of safety, clamp our generated value to the requested
+ # range. It would of course be nice if we provably satisfied this by
+ # construction - I'm just not 100% confident that we do.
+ return clamp(min_value, n, max_value)
+
+ # We have determined the bounds [min_value, max_value] are not numerically safe
+ # to use. Fall back to a simpler and safer distribution: a uniform distribution
+ # on [min_value, max_value].
+ return self._random.randint(min_value, max_value)
def draw_float(
self,
@@ -1048,45 +1106,6 @@ class HypothesisProvider(PrimitiveProvider):
sign = 1 if self._random.getrandbits(1) else -1
return sign * f
- def _draw_unbounded_integer(self) -> int:
- assert self._cd is not None
- assert self._random is not None
-
- size = INT_SIZES[INT_SIZES_SAMPLER.sample(self._cd)]
-
- r = self._random.getrandbits(size)
- sign = r & 1
- r >>= 1
- if sign:
- r = -r
- return r
-
- def _draw_bounded_integer(
- self,
- lower: int,
- upper: int,
- *,
- vary_size: bool = True,
- ) -> int:
- assert lower <= upper
- assert self._cd is not None
- assert self._random is not None
-
- if lower == upper:
- return lower
-
- bits = (upper - lower).bit_length()
- if bits > 24 and vary_size and self._random.random() < 7 / 8:
- # For large ranges, we combine the uniform random distribution
- # with a weighting scheme with moderate chance. Cutoff at 2 ** 24 so that our
- # choice of unicode characters is uniform but the 32bit distribution is not.
- idx = INT_SIZES_SAMPLER.sample(self._cd)
- cap_bits = min(bits, INT_SIZES[idx])
- upper = min(upper, lower + 2**cap_bits - 1)
- return self._random.randint(lower, upper)
-
- return self._random.randint(lower, upper)
-
# Masks for masking off the first byte of an n-bit buffer.
# The appropriate mask is stored at position n % 8.
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/utils.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/utils.py
index d27df88c83d..e916fe2e3f1 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/utils.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/utils.py
@@ -245,10 +245,6 @@ class Sampler:
return base
-INT_SIZES = (8, 16, 32, 64, 128)
-INT_SIZES_SAMPLER = Sampler((4.0, 8.0, 1.0, 1.0, 0.5), observe=False)
-
-
class many:
"""Utility class for collections. Bundles up the logic we use for "should I
keep drawing more values?" and handles starting and stopping examples in
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/coverage.py b/contrib/python/hypothesis/py3/hypothesis/internal/coverage.py
index 98cffed7b4a..b4d148b5f74 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/coverage.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/coverage.py
@@ -49,7 +49,7 @@ def pretty_file_name(f):
IN_COVERAGE_TESTS = os.getenv("HYPOTHESIS_INTERNAL_COVERAGE") == "true"
-description_stack = []
+description_stack: list[str] = []
if IN_COVERAGE_TESTS:
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/filtering.py b/contrib/python/hypothesis/py3/hypothesis/internal/filtering.py
index cf1f2f4f5d4..10414ed096a 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/filtering.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/filtering.py
@@ -272,7 +272,7 @@ def get_numeric_predicate_bounds(predicate: Predicate) -> ConstructivePredicate:
# parsing it to an abstract syntax tree; if this fails for any reason we bail out
# and fall back to standard rejection sampling (a running theme).
try:
- if predicate.__name__ == "<lambda>":
+ if predicate.__name__ == "<lambda>": # type: ignore
source = lambda_description(predicate)
else:
source = inspect.getsource(predicate)
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/floats.py b/contrib/python/hypothesis/py3/hypothesis/internal/floats.py
index 93f09de185e..e5449199911 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/floats.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/floats.py
@@ -12,7 +12,7 @@ import math
import struct
from collections.abc import Callable
from sys import float_info
-from typing import Literal, SupportsFloat, TypeAlias
+from typing import Literal, SupportsFloat, TypeAlias, overload
SignedIntFormat: TypeAlias = Literal["!h", "!i", "!q"]
UnsignedIntFormat: TypeAlias = Literal["!H", "!I", "!Q"]
@@ -191,6 +191,10 @@ def sign_aware_lte(x: float | int, y: float | int) -> bool:
return x <= y
+@overload
+def clamp(lower: int, value: int, upper: int) -> int: ...
+@overload
+def clamp(lower: float, value: float, upper: float) -> float: ...
def clamp(lower: float | int, value: float | int, upper: float | int) -> float | int:
"""Given a value and lower/upper bounds, 'clamp' the value so that
it satisfies lower <= value <= upper. NaN is mapped to lower."""
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/observability.py b/contrib/python/hypothesis/py3/hypothesis/internal/observability.py
index 1c29eff7636..06c6de18941 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/observability.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/observability.py
@@ -24,6 +24,7 @@ from contextlib import contextmanager
from dataclasses import dataclass
from datetime import date, timedelta
from functools import lru_cache
+from pathlib import Path
from threading import Lock
from typing import (
TYPE_CHECKING,
@@ -477,7 +478,7 @@ def make_testcase(
)
-_WROTE_TO = set()
+_WROTE_TO: set[Path] = set()
_deliver_to_file_lock = Lock()
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/statistics.py b/contrib/python/hypothesis/py3/hypothesis/internal/statistics.py
new file mode 100644
index 00000000000..43c7159b820
--- /dev/null
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/statistics.py
@@ -0,0 +1,260 @@
+# This file is part of Hypothesis, which may be found at
+# https://github.com/HypothesisWorks/hypothesis/
+#
+# Copyright the Hypothesis Authors.
+# Individual contributors are listed in AUTHORS.rst and the git log.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public License,
+# v. 2.0. If a copy of the MPL was not distributed with this file, You can
+# obtain one at https://mozilla.org/MPL/2.0/.
+
+import abc
+import math
+
+from hypothesis.internal.floats import clamp
+
+# Liam: please be aware that an LLM wrote the stdtr and stdtrit functions, with some
+# guidance from me on comparing to scipy's existing implementation.
+#
+# This code is strongly in-distribution for AI, and is tested against a strong
+# oracle (scipy's implementation), which are the only reasons I am willing to
+# accept this code without deeply understanding it.
+
+
+def stdtr(df: int, t: float) -> float: # pragma: no cover # covered by tests/scipy
+ """Student's t CDF for integer df >= 1, evaluated at t.
+
+ Closed-form finite sum from Abramowitz & Stegun 26.7.7-8.
+ """
+ assert isinstance(df, int)
+ if df < 1:
+ raise ValueError(f"stdtr requires integer df >= 1, got {df}")
+ if t == 0.0:
+ return 0.5
+ abs_t = abs(t)
+ z = 1.0 + abs_t * abs_t / df
+ if df % 2 == 1:
+ # odd: includes an arctan term
+ u = abs_t / math.sqrt(df)
+ p = math.atan(u)
+ if df > 1:
+ f = 1.0
+ tz = 1.0
+ j = 3
+ while j <= df - 2:
+ tz *= (j - 1) / (z * j)
+ f += tz
+ j += 2
+ p += f * u / z
+ p *= 2.0 / math.pi
+ else:
+ # even: simple finite sum, no arctan
+ f = 1.0
+ tz = 1.0
+ j = 2
+ while j <= df - 2:
+ tz *= (j - 1) / (z * j)
+ f += tz
+ j += 2
+ p = f * abs_t / math.sqrt(z * df)
+ return 0.5 - 0.5 * p if t < 0 else 0.5 + 0.5 * p
+
+
+def stdtrit(
+ df: int, p: float, *, eps: float = 1e-10, max_iter: int = 50
+) -> float: # pragma: no cover # covered by tests/scipy
+ """Inverse Student's t CDF (quantile) for integer df >= 1.
+
+ df ∈ {1, 2}: closed-form analytic quantile (Shaw 2006, eq 35-36).
+
+ df >= 3: bracketed Newton iteration on stdtr — doubling search from t=1
+ to bracket the target, then Newton steps with bisection fallback. Always
+ converges; ~5-10 iterations typical.
+ """
+ if df < 1:
+ raise ValueError(f"stdtrit requires integer df >= 1, got {df}")
+ if p == 0.5:
+ return 0.0
+ if not 0.0 < p < 1.0:
+ raise ValueError(f"stdtrit requires 0 < p < 1, got {p}")
+ if df == 1:
+ # Cauchy: F^{-1}(p) = -cot(pi p). Reflect via 1-p when p > 0.5 because
+ # sin(pi p) near pi suffers cancellation; sin(pi (1-p)) near 0 is exact.
+ if p > 0.5:
+ return math.cos(math.pi * (1 - p)) / math.sin(math.pi * (1 - p))
+ return -math.cos(math.pi * p) / math.sin(math.pi * p)
+ if df == 2:
+ return (2 * p - 1) / math.sqrt(2 * p * (1 - p))
+
+ sign = 1.0 if p > 0.5 else -1.0
+ q = p if p > 0.5 else 1 - p
+
+ lo, hi = 0.0, 1.0
+ while stdtr(df, hi) < q:
+ hi *= 2
+
+ log_norm = (
+ math.lgamma(0.5 * (df + 1))
+ - 0.5 * math.log(df * math.pi)
+ - math.lgamma(0.5 * df)
+ )
+ t = 0.5 * (lo + hi)
+ for _ in range(max_iter):
+ F = stdtr(df, t)
+ if F < q:
+ lo = t
+ else:
+ hi = t
+ log_f = log_norm - 0.5 * (df + 1) * math.log1p(t * t / df)
+ f = math.exp(log_f)
+ if f == 0.0:
+ t = 0.5 * (lo + hi)
+ else:
+ t_newton = t - (F - q) / f
+ t = t_newton if lo <= t_newton <= hi else 0.5 * (lo + hi)
+ if hi - lo < eps * (1 + abs(t)):
+ break
+ return sign * t
+
+
+# Liam: I'm not convinced this abstraction pays for itself. If it's a hindrance in the
+# future, feel free to refactor. This class is mainly here to concretize the minimal set
+# of abstractions our integer sampling code expects a distribution to define, and to
+# provide stronger guardrails around composition.
+#
+# Note that our code makes the explicit assumption that any _Distribution
+# implementation is symmetric around 0. This implies inverse_cdf(0.5) = 0, for example.
+# Do not break this assumption without verifying carefully against callers.
+class _Distribution(abc.ABC):
+ @abc.abstractmethod
+ def cdf(self, x: float) -> float:
+ raise NotImplementedError
+
+ @abc.abstractmethod
+ def inverse_cdf(self, u: float) -> float:
+ raise NotImplementedError
+
+ @abc.abstractmethod
+ def pdf(self, x: float) -> float:
+ raise NotImplementedError
+
+
+class UniformDistribution(_Distribution):
+ """Uniform distribution on [-half_width, half_width]."""
+
+ def __init__(self, *, half_width: float) -> None:
+ self.half_width = half_width
+
+ def cdf(self, x: float) -> float:
+ if x < -self.half_width:
+ return 0.0 # pragma: no cover
+ if x > self.half_width:
+ return 1.0 # pragma: no cover
+ return (x + self.half_width) / (2 * self.half_width)
+
+ def inverse_cdf(self, u: float) -> float:
+ return -self.half_width + 2 * self.half_width * u
+
+ def pdf(self, x: float) -> float:
+ if -self.half_width <= x <= self.half_width:
+ return 1 / (2 * self.half_width)
+ return 0.0 # pragma: no cover
+
+
+class LogStudentTDistribution(_Distribution):
+ """Student's t distribution, in the transformed domain of log_2(x).
+
+ Y = sign(x) * log_2(1 + |x|) ~ scale_bits * t(df).
+
+ Note that we only support integer df. This is to reduce surface area in the vendored
+ mathematical functions, which are simpler when df is an integer. There is no
+ fundamental difficulty to supporting float df, other than the necessary due diligence
+ for the vendored code.
+ """
+
+ LN2 = math.log(2)
+
+ def __init__(self, *, scale_bits: float, df: int) -> None:
+ self.scale_bits = scale_bits
+ self.df = df
+ self._t_coef = math.gamma((df + 1) / 2) / (
+ math.sqrt(df * math.pi) * math.gamma(df / 2)
+ )
+
+ def cdf(self, x: float) -> float:
+ y = math.copysign(math.log2(1 + abs(x)), x) / self.scale_bits
+ return float(stdtr(self.df, y))
+
+ def inverse_cdf(self, u: float) -> float:
+ y = self.scale_bits * float(stdtrit(self.df, u))
+ # 2^1023 is the largest power of 2 below sys.float_info.max. This makes y=1023
+ # the largest value we can turn into a float to return here without overflowing.
+ #
+ # In the rare case that we draw such an extreme value, clamp to the bounds.
+ y = clamp(-1023, y, 1023)
+ return math.copysign(math.expm1(abs(y) * self.LN2), y)
+
+ def pdf(self, x: float) -> float:
+ y = math.copysign(math.log2(1 + abs(x)), x) / self.scale_bits
+ f_t = self._t_coef * (1 + y * y / self.df) ** (-(self.df + 1) / 2)
+ return f_t / (self.scale_bits * (1 + abs(x)) * self.LN2)
+
+
+class PiecewiseDistribution:
+ """Two-region splice: `inner` on (-switchover, switchover),
+ `outer` on |x| >= switchover.
+
+ Each region is normalized so that the resulting density is continuous at
+ ±switchover and integrates to 1. Both inner and outer must be symmetric around 0.
+ """
+
+ def __init__(
+ self, *, inner: _Distribution, outer: _Distribution, switchover: float
+ ) -> None:
+ # Note that this code could be made simpler by taking advantage of our assumption that
+ # `inner` and `outer` are symmetric around 0. The code is currently generic enough
+ # to support asymmetric distributions, but doesn't need to be,
+ self.inner = inner
+ self.outer = outer
+ self.switchover = switchover
+ self._inner_g_neg = inner.cdf(-switchover)
+ self._inner_g_pos = inner.cdf(switchover)
+ self._outer_g_neg = outer.cdf(-switchover)
+ self._outer_g_pos = outer.cdf(switchover)
+ outer_outer_mass = 1 - (self._outer_g_pos - self._outer_g_neg)
+ inner_inner_mass = self._inner_g_pos - self._inner_g_neg
+ # density continuity: beta * inner.pdf(c) = alpha * outer.pdf(c)
+ # plus total mass 1; solve for (alpha, beta).
+ inner_pdf = inner.pdf(switchover)
+ outer_pdf = outer.pdf(switchover)
+ assert inner_pdf != 0, (
+ f"inner.pdf(switchover={switchover}) == 0; cannot density-match. inner "
+ "must have support at the boundary."
+ )
+ self._alpha = 1 / (outer_pdf * inner_inner_mass / inner_pdf + outer_outer_mass)
+ self._beta = self._alpha * outer_pdf / inner_pdf
+ self._inner_mass = self._beta * inner_inner_mass
+ self._left_mass = self._alpha * self._outer_g_neg
+
+ def cdf(self, x: float) -> float:
+ if x <= -self.switchover:
+ return self._alpha * self.outer.cdf(x)
+ if x < self.switchover:
+ return self._left_mass + self._beta * (
+ self.inner.cdf(x) - self._inner_g_neg
+ )
+ return (
+ self._left_mass
+ + self._inner_mass
+ + self._alpha * (self.outer.cdf(x) - self._outer_g_pos)
+ )
+
+ def inverse_cdf(self, u: float) -> float:
+ if u <= self._left_mass:
+ return self.outer.inverse_cdf(u / self._alpha)
+ if u < self._left_mass + self._inner_mass:
+ target = self._inner_g_neg + (u - self._left_mass) / self._beta
+ return self.inner.inverse_cdf(target)
+ return self.outer.inverse_cdf(
+ (u - self._left_mass - self._inner_mass) / self._alpha + self._outer_g_pos
+ )
diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/attrs.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/attrs.py
index 77d3b7f180e..5b8572ec6e9 100644
--- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/attrs.py
+++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/attrs.py
@@ -78,14 +78,16 @@ def from_attrs(
) -> SearchStrategy:
"""An internal version of builds(), specialised for Attrs classes."""
attributes: tuple[Attribute, ...] = attr.fields(target)
- kwargs = {k: v for k, v in kwargs.items() if v is not infer}
+ new_kwargs: dict[str, SearchStrategy[Any]] = {
+ k: v for k, v in kwargs.items() if v is not infer
+ }
for name in to_infer:
attrib = get_attribute_by_alias(attributes, name, target=target)
- kwargs[name] = from_attrs_attribute(attrib, target)
+ new_kwargs[name] = from_attrs_attribute(attrib, target)
# We might make this strategy more efficient if we added a layer here that
# retries drawing if validation fails, for improved composition.
# The treatment of timezones in datetimes() provides a precedent.
- return BuildsStrategy(target, args, kwargs)
+ return BuildsStrategy(target, args, new_kwargs)
def from_attrs_attribute(
diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/collections.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/collections.py
index 7f395e08cd6..97de24f249b 100644
--- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/collections.py
+++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/collections.py
@@ -329,7 +329,7 @@ class UniqueListStrategy(ListStrategy[Ex]):
for key, seen in zip(self.keys, seen_sets, strict=True):
seen.add(key(value))
if self.tuple_suffixes is not None:
- value = (value, *data.draw(self.tuple_suffixes)) # type: ignore
+ value = (value, *data.draw(self.tuple_suffixes))
result.append(value)
assert self.max_size >= len(result) >= self.min_size
return result
diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py
index 444417a9c60..3a8e45ad0a2 100644
--- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py
+++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py
@@ -18,7 +18,14 @@ import string
import sys
import typing
import warnings
-from collections.abc import Callable, Collection, Hashable, Iterable, Sequence
+from collections.abc import (
+ Callable,
+ Collection,
+ Hashable,
+ Iterable,
+ Mapping,
+ Sequence,
+)
from contextvars import ContextVar
from decimal import Context, Decimal, localcontext
from fractions import Fraction
@@ -364,7 +371,7 @@ def lists(
and (elements.end - elements.start) <= 255
):
elements = SampledFromStrategy(
- sorted(range(elements.start, elements.end + 1), key=abs) # type: ignore
+ sorted(range(elements.start, elements.end + 1), key=abs)
if elements.end < 0 or elements.start > 0
else (
list(range(elements.end + 1))
@@ -1580,9 +1587,9 @@ def _from_type(thing: type[Ex]) -> SearchStrategy[Ex]:
)
try:
hints = get_type_hints(thing)
- params = get_signature(thing).parameters
+ params: Mapping[str, Parameter] = get_signature(thing).parameters
except Exception:
- params = {} # type: ignore
+ params = {}
posonly_args = []
kwargs = {}
diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/strategies.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/strategies.py
index de6a9cb7ffb..cbeac0c42e5 100644
--- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/strategies.py
+++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/strategies.py
@@ -1220,11 +1220,9 @@ class FilteredStrategy(SearchStrategy[Ex]):
condition = self.flat_conditions[0]
elif len(self.flat_conditions) == 0:
# Possible, if unlikely, due to filter predicate rewriting
- condition = lambda _: True # type: ignore # covariant type param
+ condition = lambda _: True
else:
- condition = lambda x: all( # type: ignore # covariant type param
- cond(x) for cond in self.flat_conditions
- )
+ condition = lambda x: all(cond(x) for cond in self.flat_conditions)
self.__condition = condition
return condition
diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/types.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/types.py
index 84d73a2d203..b2b3eab417a 100644
--- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/types.py
+++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/types.py
@@ -164,7 +164,7 @@ try:
extended_get_origin = typing_extensions.get_origin
except AttributeError: # pragma: no cover
# `typing_extensions` might not be installed, in this case - fallback:
- extended_get_origin = get_origin # type: ignore
+ extended_get_origin = get_origin
# Used on `TypeVar` objects with no default:
diff --git a/contrib/python/hypothesis/py3/hypothesis/vendor/tlds-alpha-by-domain.txt b/contrib/python/hypothesis/py3/hypothesis/vendor/tlds-alpha-by-domain.txt
index f15ce33c96e..6ec5a70f1d6 100644
--- a/contrib/python/hypothesis/py3/hypothesis/vendor/tlds-alpha-by-domain.txt
+++ b/contrib/python/hypothesis/py3/hypothesis/vendor/tlds-alpha-by-domain.txt
@@ -1,4 +1,4 @@
-# Version 2026021400, Last Updated Sat Feb 14 07:07:01 2026 UTC
+# Version 2026051600, Last Updated Sat May 16 07:07:02 2026 UTC
AAA
AARP
ABB
@@ -741,6 +741,7 @@ MEME
MEMORIAL
MEN
MENU
+MERCK
MERCKMSD
MG
MH
diff --git a/contrib/python/hypothesis/py3/hypothesis/version.py b/contrib/python/hypothesis/py3/hypothesis/version.py
index 38877a283a1..a37af144e24 100644
--- a/contrib/python/hypothesis/py3/hypothesis/version.py
+++ b/contrib/python/hypothesis/py3/hypothesis/version.py
@@ -8,5 +8,5 @@
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at https://mozilla.org/MPL/2.0/.
-__version_info__ = (6, 152, 7)
+__version_info__ = (6, 152, 9)
__version__ = ".".join(map(str, __version_info__))
diff --git a/contrib/python/hypothesis/py3/ya.make b/contrib/python/hypothesis/py3/ya.make
index e40304836ab..6b8398b4059 100644
--- a/contrib/python/hypothesis/py3/ya.make
+++ b/contrib/python/hypothesis/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(6.152.7)
+VERSION(6.152.9)
LICENSE(MPL-2.0)
@@ -88,6 +88,7 @@ PY_SRCS(
hypothesis/internal/observability.py
hypothesis/internal/reflection.py
hypothesis/internal/scrutineer.py
+ hypothesis/internal/statistics.py
hypothesis/internal/validation.py
hypothesis/provisional.py
hypothesis/reporting.py