summaryrefslogtreecommitdiffstats
path: root/contrib/python
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2026-01-20 15:06:25 +0300
committerrobot-piglet <[email protected]>2026-01-20 15:39:58 +0300
commitb91838abbe7ab48ede4a5cff56e811aaf525a67b (patch)
treefe780697b7e0760c5099edc6fdb7db85021373ed /contrib/python
parent31211be095bdda84ca0e0e6f404561c7faff92f3 (diff)
Intermediate changes
commit_hash:bb091210500d5d8a7c7263bd9e6946718a164752
Diffstat (limited to 'contrib/python')
-rw-r--r--contrib/python/hypothesis/py3/.dist-info/METADATA2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/_settings.py19
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/control.py76
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/core.py30
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/database.py2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/errors.py2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/extra/numpy.py2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/extra/pandas/impl.py2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py3
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py16
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/observability.py3
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/scrutineer.py21
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/stateful.py8
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/strategies/_internal/collections.py62
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py24
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/utils/deprecation.py28
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/vendor/pretty.py192
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/version.py2
-rw-r--r--contrib/python/hypothesis/py3/ya.make3
20 files changed, 351 insertions, 148 deletions
diff --git a/contrib/python/hypothesis/py3/.dist-info/METADATA b/contrib/python/hypothesis/py3/.dist-info/METADATA
index e716851be16..00267a4c202 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.148.12
+Version: 6.149.1
Summary: The property-based testing library for Python
Author-email: "David R. MacIver and Zac Hatfield-Dodds" <[email protected]>
License-Expression: MPL-2.0
diff --git a/contrib/python/hypothesis/py3/hypothesis/_settings.py b/contrib/python/hypothesis/py3/hypothesis/_settings.py
index 13c90373ed0..0d66eb63368 100644
--- a/contrib/python/hypothesis/py3/hypothesis/_settings.py
+++ b/contrib/python/hypothesis/py3/hypothesis/_settings.py
@@ -18,7 +18,6 @@ import contextlib
import datetime
import inspect
import os
-import warnings
from collections.abc import Collection, Generator, Sequence
from enum import Enum, EnumMeta, unique
from functools import total_ordering
@@ -31,13 +30,13 @@ from typing import (
)
from hypothesis.errors import (
- HypothesisDeprecationWarning,
InvalidArgument,
)
from hypothesis.internal.conjecture.providers import AVAILABLE_PROVIDERS
from hypothesis.internal.reflection import get_pretty_function_description
from hypothesis.internal.validation import check_type, try_convert
from hypothesis.utils.conventions import not_set
+from hypothesis.utils.deprecation import note_deprecation
from hypothesis.utils.dynamicvariables import DynamicVariable
if TYPE_CHECKING:
@@ -622,7 +621,7 @@ class settings(metaclass=settingsMeta):
"default",
max_examples=100,
derandomize=False,
- database=not_set, # see settings.database for the default database
+ database=not_set, # see settings.database for default behavior
verbosity=Verbosity.normal,
phases=tuple(Phase),
stateful_step_count=50,
@@ -1191,20 +1190,6 @@ def local_settings(s: settings) -> Generator[settings, None, None]:
yield s
-def note_deprecation(
- message: str, *, since: str, has_codemod: bool, stacklevel: int = 0
-) -> None:
- if since != "RELEASEDAY":
- date = datetime.date.fromisoformat(since)
- assert datetime.date(2021, 1, 1) <= date
- if has_codemod:
- message += (
- "\n The `hypothesis codemod` command-line tool can automatically "
- "refactor your code to fix this warning."
- )
- warnings.warn(HypothesisDeprecationWarning(message), stacklevel=2 + stacklevel)
-
-
default = settings(
max_examples=100,
derandomize=False,
diff --git a/contrib/python/hypothesis/py3/hypothesis/control.py b/contrib/python/hypothesis/py3/hypothesis/control.py
index 11f82232311..d0aee23d2f7 100644
--- a/contrib/python/hypothesis/py3/hypothesis/control.py
+++ b/contrib/python/hypothesis/py3/hypothesis/control.py
@@ -12,13 +12,12 @@ import inspect
import math
import random
from collections import defaultdict
-from collections.abc import Callable, Sequence
+from collections.abc import Callable, Generator, Sequence
from contextlib import contextmanager
from typing import Any, Literal, NoReturn, Optional, overload
from weakref import WeakKeyDictionary
from hypothesis import Verbosity, settings
-from hypothesis._settings import note_deprecation
from hypothesis.errors import InvalidArgument, UnsatisfiedAssumption
from hypothesis.internal.compat import BaseExceptionGroup
from hypothesis.internal.conjecture.data import ConjectureData
@@ -26,8 +25,9 @@ from hypothesis.internal.observability import observability_enabled
from hypothesis.internal.reflection import get_pretty_function_description
from hypothesis.internal.validation import check_type
from hypothesis.reporting import report, verbose_report
+from hypothesis.utils.deprecation import note_deprecation
from hypothesis.utils.dynamicvariables import DynamicVariable
-from hypothesis.vendor.pretty import IDKey, PrettyPrintFunction, pretty
+from hypothesis.vendor.pretty import ArgLabelsT, IDKey, PrettyPrintFunction, pretty
def _calling_function_location(what: str, frame: Any) -> str:
@@ -157,6 +157,33 @@ class BuildContext:
defaultdict(list)
)
+ # Track nested strategy calls for explain-phase label paths
+ self._label_path: list[str] = []
+
+ @contextmanager
+ def track_arg_label(self, label: str) -> Generator[ArgLabelsT, None, None]:
+ start = len(self.data.nodes)
+ self._label_path.append(label)
+ arg_labels: ArgLabelsT = {}
+ try:
+ yield arg_labels
+ finally:
+ self._label_path.pop()
+
+ # This high up the stack, we can't see or really do much with
+ # Span / SpanRecord - not least because they're only materialized
+ # after the test case is completed.
+ #
+ # Instead, we'll stash the (start_idx, end_idx) pair on our data object
+ # for the ConjectureRunner engine to deal with, and mutate the arg_labels
+ # dict so that the pretty-printer knows where to place the
+ # which-parts-matter comments later.
+ end = len(self.data.nodes)
+ assert start <= end
+ if start != end:
+ arg_labels[label] = (start, end)
+ self.data.arg_slices.add((start, end))
+
def record_call(
self,
obj: object,
@@ -164,34 +191,33 @@ class BuildContext:
*,
args: Sequence[object],
kwargs: dict[str, object],
+ arg_labels: ArgLabelsT | None = None,
) -> None:
self.known_object_printers[IDKey(obj)].append(
- # _func=func prevents mypy from inferring lambda type. Would need
- # paramspec I think - not worth it.
- lambda obj, p, cycle, *, _func=func: p.maybe_repr_known_object_as_call( # type: ignore
- obj, cycle, get_pretty_function_description(_func), args, kwargs
+ lambda obj, p, cycle, *, _func=func, _arg_labels=arg_labels: p.maybe_repr_known_object_as_call( # type: ignore
+ obj,
+ cycle,
+ get_pretty_function_description(_func),
+ args,
+ kwargs,
+ arg_labels=_arg_labels,
)
)
- def prep_args_kwargs_from_strategies(self, kwarg_strategies):
- arg_labels = {}
- kwargs = {}
- for k, s in kwarg_strategies.items():
- start_idx = len(self.data.nodes)
- with deprecate_random_in_strategy("from {}={!r}", k, s):
- obj = self.data.draw(s, observe_as=f"generate:{k}")
- end_idx = len(self.data.nodes)
- kwargs[k] = obj
+ def prep_args_kwargs_from_strategies(
+ self,
+ kwarg_strategies: dict[str, Any],
+ ) -> tuple[dict[str, Any], ArgLabelsT]:
+ arg_labels: ArgLabelsT = {}
+ kwargs: dict[str, Any] = {}
- # This high up the stack, we can't see or really do much with the conjecture
- # Example objects - not least because they're only materialized after the
- # test case is completed. Instead, we'll stash the (start_idx, end_idx)
- # pair on our data object for the ConjectureRunner engine to deal with, and
- # pass a dict of such out so that the pretty-printer knows where to place
- # the which-parts-matter comments later.
- if start_idx != end_idx:
- arg_labels[k] = (start_idx, end_idx)
- self.data.arg_slices.add((start_idx, end_idx))
+ for k, s in kwarg_strategies.items():
+ with (
+ self.track_arg_label(k) as arg_label,
+ deprecate_random_in_strategy("from {}={!r}", k, s),
+ ):
+ kwargs[k] = self.data.draw(s, observe_as=f"generate:{k}")
+ arg_labels |= arg_label
return kwargs, arg_labels
diff --git a/contrib/python/hypothesis/py3/hypothesis/core.py b/contrib/python/hypothesis/py3/hypothesis/core.py
index ba59d9e4b63..d7d1b39e598 100644
--- a/contrib/python/hypothesis/py3/hypothesis/core.py
+++ b/contrib/python/hypothesis/py3/hypothesis/core.py
@@ -916,7 +916,14 @@ def unwrap_markers_from_group() -> Generator[None, None, None]:
class StateForActualGivenExecution:
def __init__(
- self, stuff, test, settings, random, wrapped_test, *, thread_overlap=None
+ self,
+ stuff: Stuff,
+ test: Callable[..., Any],
+ settings: Settings,
+ random: Random,
+ wrapped_test: Any,
+ *,
+ thread_overlap: dict[int, bool] | None = None,
):
self.stuff = stuff
self.test = test
@@ -933,15 +940,18 @@ class StateForActualGivenExecution:
self.last_exception = None
self.falsifying_examples = ()
self.ever_executed = False
- self.xfail_example_reprs = set()
- self.files_to_propagate = set()
+ self.xfail_example_reprs: set[str] = set()
self.failed_normally = False
self.failed_due_to_deadline = False
- self.explain_traces = defaultdict(set)
+ self.explain_traces: dict[None | InterestingOrigin, set[Trace]] = defaultdict(
+ set
+ )
self._start_timestamp = time.time()
self._string_repr = ""
- self._timing_features = {}
+ self._timing_features: dict[str, float] = {}
+
+ self._runner: ConjectureRunner | None = None
@property
def test_identifier(self) -> str:
@@ -1172,6 +1182,7 @@ class StateForActualGivenExecution:
def _flaky_replay_to_failure(
self, err: FlakyReplay, context: BaseException
) -> FlakyFailure:
+ assert self._runner is not None
# Note that in the mark_interesting case, _context_ itself
# is part of err._interesting_examples - but it's not in
# _runner.interesting_examples - this is fine, as the context
@@ -1193,7 +1204,7 @@ class StateForActualGivenExecution:
This allows the engine to assume that any exception other than
``StopTest`` must be a fatal error, and should stop the entire engine.
"""
- trace: Trace = set()
+ trace: Trace = frozenset()
try:
with Tracer(should_trace=self._should_trace()) as tracer:
try:
@@ -1203,7 +1214,7 @@ class StateForActualGivenExecution:
): # pragma: no cover
# This is in fact covered by our *non-coverage* tests, but due
# to the settrace() contention *not* by our coverage tests.
- self.explain_traces[None].add(frozenset(tracer.branches))
+ self.explain_traces[None].add(tracer.branches)
finally:
trace = tracer.branches
if result is not None:
@@ -1287,7 +1298,7 @@ class StateForActualGivenExecution:
interesting_origin = InterestingOrigin.from_exception(e)
if trace: # pragma: no cover
# Trace collection is explicitly disabled under coverage.
- self.explain_traces[interesting_origin].add(frozenset(trace))
+ self.explain_traces[interesting_origin].add(trace)
if interesting_origin.exc_type == DeadlineExceeded:
self.failed_due_to_deadline = True
self.explain_traces.clear()
@@ -1381,13 +1392,14 @@ class StateForActualGivenExecution:
else:
database_key = None
- runner = self._runner = ConjectureRunner(
+ runner = ConjectureRunner(
self._execute_once_for_engine,
settings=self.settings,
random=self.random,
database_key=database_key,
thread_overlap=self.thread_overlap,
)
+ self._runner = runner
# Use the Conjecture engine to run the test function many times
# on different inputs.
runner.run()
diff --git a/contrib/python/hypothesis/py3/hypothesis/database.py b/contrib/python/hypothesis/py3/hypothesis/database.py
index 9106a2d2193..68568675f32 100644
--- a/contrib/python/hypothesis/py3/hypothesis/database.py
+++ b/contrib/python/hypothesis/py3/hypothesis/database.py
@@ -37,11 +37,11 @@ from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
from zipfile import BadZipFile, ZipFile
-from hypothesis._settings import note_deprecation
from hypothesis.configuration import storage_directory
from hypothesis.errors import HypothesisException, HypothesisWarning
from hypothesis.internal.conjecture.choice import ChoiceT
from hypothesis.utils.conventions import UniqueIdentifier, not_set
+from hypothesis.utils.deprecation import note_deprecation
__all__ = [
"DirectoryBasedExampleDatabase",
diff --git a/contrib/python/hypothesis/py3/hypothesis/errors.py b/contrib/python/hypothesis/py3/hypothesis/errors.py
index 0e8fa889df8..daf81828c97 100644
--- a/contrib/python/hypothesis/py3/hypothesis/errors.py
+++ b/contrib/python/hypothesis/py3/hypothesis/errors.py
@@ -222,8 +222,8 @@ class Frozen(HypothesisException):
def __getattr__(name: str) -> Any:
if name == "MultipleFailures":
- from hypothesis._settings import note_deprecation
from hypothesis.internal.compat import BaseExceptionGroup
+ from hypothesis.utils.deprecation import note_deprecation
note_deprecation(
"MultipleFailures is deprecated; use the builtin `BaseExceptionGroup` type "
diff --git a/contrib/python/hypothesis/py3/hypothesis/extra/numpy.py b/contrib/python/hypothesis/py3/hypothesis/extra/numpy.py
index e78b0dd15d2..0cf3efaea3a 100644
--- a/contrib/python/hypothesis/py3/hypothesis/extra/numpy.py
+++ b/contrib/python/hypothesis/py3/hypothesis/extra/numpy.py
@@ -27,7 +27,6 @@ from typing import (
import numpy as np
from hypothesis import strategies as st
-from hypothesis._settings import note_deprecation
from hypothesis.errors import HypothesisException, InvalidArgument
from hypothesis.extra._array_helpers import (
_BIE,
@@ -60,6 +59,7 @@ from hypothesis.strategies._internal.strategies import (
check_strategy,
)
from hypothesis.strategies._internal.utils import defines_strategy
+from hypothesis.utils.deprecation import note_deprecation
def _try_import(mod_name: str, attr_name: str) -> Any:
diff --git a/contrib/python/hypothesis/py3/hypothesis/extra/pandas/impl.py b/contrib/python/hypothesis/py3/hypothesis/extra/pandas/impl.py
index 4d4c00f1277..12da7370a68 100644
--- a/contrib/python/hypothesis/py3/hypothesis/extra/pandas/impl.py
+++ b/contrib/python/hypothesis/py3/hypothesis/extra/pandas/impl.py
@@ -19,7 +19,6 @@ import numpy as np
import pandas
from hypothesis import strategies as st
-from hypothesis._settings import note_deprecation
from hypothesis.control import reject
from hypothesis.errors import InvalidArgument
from hypothesis.extra import numpy as npst
@@ -33,6 +32,7 @@ from hypothesis.internal.validation import (
)
from hypothesis.strategies._internal.strategies import Ex, check_strategy
from hypothesis.strategies._internal.utils import cacheable, defines_strategy
+from hypothesis.utils.deprecation import note_deprecation
try:
from pandas.core.arrays.integer import IntegerDtype
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py
index 4ac53e9dd92..9ac26e1cbaf 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py
@@ -70,6 +70,7 @@ from hypothesis.internal.intervalsets import IntervalSet
from hypothesis.internal.observability import PredicateCounts
from hypothesis.reporting import debug_report
from hypothesis.utils.conventions import not_set
+from hypothesis.utils.deprecation import note_deprecation
from hypothesis.utils.threading import ThreadLocal
if TYPE_CHECKING:
@@ -81,7 +82,6 @@ if TYPE_CHECKING:
def __getattr__(name: str) -> Any:
if name == "AVAILABLE_PROVIDERS":
- from hypothesis._settings import note_deprecation
from hypothesis.internal.conjecture.providers import AVAILABLE_PROVIDERS
note_deprecation(
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py
index 85b34f474fb..3a1c1944b4c 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py
@@ -23,7 +23,7 @@ from random import Random
from typing import Literal, NoReturn, cast
from hypothesis import HealthCheck, Phase, Verbosity, settings as Settings
-from hypothesis._settings import local_settings, note_deprecation
+from hypothesis._settings import local_settings
from hypothesis.database import ExampleDatabase, choices_from_bytes, choices_to_bytes
from hypothesis.errors import (
BackendCannotProceed,
@@ -70,6 +70,7 @@ from hypothesis.internal.escalation import InterestingOrigin
from hypothesis.internal.healthcheck import fail_health_check
from hypothesis.internal.observability import Observation, with_observability_callback
from hypothesis.reporting import base_report, report, verbose_report
+from hypothesis.utils.deprecation import note_deprecation
# In most cases, the following constants are all Final. However, we do allow users
# to monkeypatch all of these variables, which means we cannot annotate them as
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py
index 8b5af7f6138..50e1ff89f4c 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py
@@ -490,6 +490,17 @@ class Shrinker:
):
continue
+ # Skip slices that are subsets of already-explained slices.
+ # If a larger slice can vary freely, so can its sub-slices.
+ # Note: (0, 0) is a special marker for the "together" comment that
+ # applies to the whole test, not a specific slice, so we exclude it.
+ if any(
+ s <= start and end <= e
+ for s, e in self.shrink_target.slice_comments
+ if (s, e) != (0, 0)
+ ):
+ continue
+
# Run our experiments
n_same_failures = 0
note = "or any other generated value"
@@ -569,7 +580,10 @@ class Shrinker:
if len(self.shrink_target.slice_comments) <= 1:
return
n_same_failures_together = 0
- chunks_by_start_index = sorted(chunks.items())
+ # Only include slices that were actually added to slice_comments
+ chunks_by_start_index = sorted(
+ (k, v) for k, v in chunks.items() if k in self.shrink_target.slice_comments
+ )
for _ in range(500): # pragma: no branch
# no-branch here because we don't coverage-test the abort-at-500 logic.
new_choices: list[ChoiceT] = []
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/observability.py b/contrib/python/hypothesis/py3/hypothesis/internal/observability.py
index 84d5b51bf49..ad674f22651 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/observability.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/observability.py
@@ -51,6 +51,7 @@ from hypothesis.internal.conjecture.choice import (
from hypothesis.internal.escalation import InterestingOrigin
from hypothesis.internal.floats import float_to_int
from hypothesis.internal.intervalsets import IntervalSet
+from hypothesis.utils.deprecation import note_deprecation
if TYPE_CHECKING:
from hypothesis.internal.conjecture.data import ConjectureData, Spans, Status
@@ -355,8 +356,6 @@ class _TestcaseCallbacks:
return bool(_callbacks)
def _note_deprecation(self):
- from hypothesis._settings import note_deprecation
-
note_deprecation(
"hypothesis.internal.observability.TESTCASE_CALLBACKS is deprecated. "
"Replace TESTCASE_CALLBACKS.append with add_observability_callback, "
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/scrutineer.py b/contrib/python/hypothesis/py3/hypothesis/internal/scrutineer.py
index a4665d13752..d1429110695 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/scrutineer.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/scrutineer.py
@@ -21,15 +21,18 @@ from enum import IntEnum
from functools import lru_cache, reduce
from os import sep
from pathlib import Path
-from typing import TypeAlias
+from typing import TYPE_CHECKING, TypeAlias
from hypothesis._settings import Phase, Verbosity
from hypothesis.internal.compat import PYPY
from hypothesis.internal.escalation import is_hypothesis_file
+if TYPE_CHECKING:
+ from typing_extensions import Self
+
Location: TypeAlias = tuple[str, int]
Branch: TypeAlias = tuple[Location | None, Location]
-Trace: TypeAlias = set[Branch]
+Trace: TypeAlias = frozenset[Branch]
@functools.cache
@@ -52,14 +55,14 @@ class Tracer:
"""A super-simple branch coverage tracer."""
__slots__ = (
+ "_branches",
"_previous_location",
"_should_trace",
"_tried_and_failed_to_trace",
- "branches",
)
def __init__(self, *, should_trace: bool) -> None:
- self.branches: Trace = set()
+ self._branches: set[Branch] = set()
self._previous_location: Location | None = None
self._tried_and_failed_to_trace = False
self._should_trace = should_trace and self.can_trace()
@@ -72,6 +75,10 @@ class Tracer:
return sys.monitoring.get_tool(MONITORING_TOOL_ID) is None
return sys.gettrace() is None
+ @property
+ def branches(self) -> Trace:
+ return frozenset(self._branches)
+
def trace(self, frame, event, arg):
try:
if event == "call":
@@ -80,7 +87,7 @@ class Tracer:
fname = frame.f_code.co_filename
if should_trace_file(fname):
current_location = (fname, frame.f_lineno)
- self.branches.add((self._previous_location, current_location))
+ self._branches.add((self._previous_location, current_location))
self._previous_location = current_location
except RecursionError:
pass
@@ -93,10 +100,10 @@ class Tracer:
return sys.monitoring.DISABLE # type: ignore
current_location = (fname, line_number)
- self.branches.add((self._previous_location, current_location))
+ self._branches.add((self._previous_location, current_location))
self._previous_location = current_location
- def __enter__(self):
+ def __enter__(self) -> "Self":
self._tried_and_failed_to_trace = False
if not self._should_trace:
diff --git a/contrib/python/hypothesis/py3/hypothesis/stateful.py b/contrib/python/hypothesis/py3/hypothesis/stateful.py
index 8026d2d1672..2103fdcb2c5 100644
--- a/contrib/python/hypothesis/py3/hypothesis/stateful.py
+++ b/contrib/python/hypothesis/py3/hypothesis/stateful.py
@@ -28,12 +28,7 @@ from typing import Any, ClassVar, TypeVar, overload
from unittest import TestCase
from hypothesis import strategies as st
-from hypothesis._settings import (
- HealthCheck,
- Verbosity,
- note_deprecation,
- settings as Settings,
-)
+from hypothesis._settings import HealthCheck, Verbosity, settings as Settings
from hypothesis.control import _current_build_context, current_build_context
from hypothesis.core import TestFunc, given
from hypothesis.errors import (
@@ -62,6 +57,7 @@ from hypothesis.strategies._internal.strategies import (
SearchStrategy,
check_strategy,
)
+from hypothesis.utils.deprecation import note_deprecation
from hypothesis.vendor.pretty import RepresentationPrinter
T = TypeVar("T")
diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/collections.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/collections.py
index d585d296f71..7a604bb8833 100644
--- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/collections.py
+++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/collections.py
@@ -14,6 +14,7 @@ from collections.abc import Callable, Iterable
from typing import Any, overload
from hypothesis import strategies as st
+from hypothesis.control import current_build_context
from hypothesis.errors import InvalidArgument
from hypothesis.internal.conjecture import utils as cu
from hypothesis.internal.conjecture.data import ConjectureData
@@ -37,6 +38,12 @@ from hypothesis.strategies._internal.strategies import (
)
from hypothesis.strategies._internal.utils import cacheable, defines_strategy
from hypothesis.utils.conventions import UniqueIdentifier
+from hypothesis.vendor.pretty import (
+ ArgLabelsT,
+ IDKey,
+ _fixeddict_pprinter,
+ _tuple_pprinter,
+)
class TupleStrategy(SearchStrategy[tuple[Ex, ...]]):
@@ -64,7 +71,20 @@ class TupleStrategy(SearchStrategy[tuple[Ex, ...]]):
return all(recur(e) for e in self.element_strategies)
def do_draw(self, data: ConjectureData) -> tuple[Ex, ...]:
- return tuple(data.draw(e) for e in self.element_strategies)
+ context = current_build_context()
+ arg_labels: ArgLabelsT = {}
+ result = []
+ for i, strategy in enumerate(self.element_strategies):
+ with context.track_arg_label(f"arg[{i}]") as arg_label:
+ result.append(data.draw(strategy))
+ arg_labels |= arg_label
+
+ result = tuple(result)
+ if arg_labels:
+ context.known_object_printers[IDKey(result)].append(
+ _tuple_pprinter(arg_labels)
+ )
+ return result
def calc_is_empty(self, recur: RecurT) -> bool:
return any(recur(e) for e in self.element_strategies)
@@ -366,19 +386,35 @@ class FixedDictStrategy(SearchStrategy[dict[Any, Any]]):
self.optional = optional
def do_draw(self, data: ConjectureData) -> dict[Any, Any]:
- value = data.draw(self.fixed)
- if self.optional is None:
- return value
+ context = current_build_context()
+ arg_labels: ArgLabelsT = {}
+ value = type(self.mapping)()
- remaining = [k for k, v in self.optional.items() if not v.is_empty]
- should_draw = cu.many(
- data, min_size=0, max_size=len(remaining), average_size=len(remaining) / 2
- )
- while should_draw.more():
- j = data.draw_integer(0, len(remaining) - 1)
- remaining[-1], remaining[j] = remaining[j], remaining[-1]
- key = remaining.pop()
- value[key] = data.draw(self.optional[key])
+ for key, strategy in self.mapping.items():
+ with context.track_arg_label(str(key)) as arg_label:
+ value[key] = data.draw(strategy)
+ arg_labels |= arg_label
+
+ if self.optional is not None:
+ remaining = [k for k, v in self.optional.items() if not v.is_empty]
+ should_draw = cu.many(
+ data,
+ min_size=0,
+ max_size=len(remaining),
+ average_size=len(remaining) / 2,
+ )
+ while should_draw.more():
+ j = data.draw_integer(0, len(remaining) - 1)
+ remaining[-1], remaining[j] = remaining[j], remaining[-1]
+ key = remaining.pop()
+ with context.track_arg_label(str(key)) as arg_label:
+ value[key] = data.draw(self.optional[key])
+ arg_labels |= arg_label
+
+ if arg_labels:
+ context.known_object_printers[IDKey(value)].append(
+ _fixeddict_pprinter(arg_labels, self.mapping)
+ )
return value
def calc_is_empty(self, recur: RecurT) -> bool:
diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py
index 5aaf0fe497e..ac48b450cd3 100644
--- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py
+++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py
@@ -45,7 +45,6 @@ from typing import (
)
from uuid import UUID
-from hypothesis._settings import note_deprecation
from hypothesis.control import (
cleanup,
current_build_context,
@@ -138,7 +137,8 @@ from hypothesis.strategies._internal.strings import (
)
from hypothesis.strategies._internal.utils import cacheable, defines_strategy
from hypothesis.utils.conventions import not_set
-from hypothesis.vendor.pretty import RepresentationPrinter
+from hypothesis.utils.deprecation import note_deprecation
+from hypothesis.vendor.pretty import ArgLabelsT, RepresentationPrinter
@cacheable
@@ -1053,8 +1053,20 @@ class BuildsStrategy(SearchStrategy[Ex]):
)
def do_draw(self, data: ConjectureData) -> Ex:
- args = [data.draw(s) for s in self.args]
- kwargs = {k: data.draw(v) for k, v in self.kwargs.items()}
+ context = current_build_context()
+ arg_labels: ArgLabelsT = {}
+
+ args = []
+ for i, s in enumerate(self.args):
+ with context.track_arg_label(f"arg[{i}]") as arg_label:
+ args.append(data.draw(s))
+ arg_labels |= arg_label
+
+ kwargs = {}
+ for k, v in self.kwargs.items():
+ with context.track_arg_label(k) as arg_label:
+ kwargs[k] = data.draw(v)
+ arg_labels |= arg_label
try:
obj = self.target(*args, **kwargs)
except TypeError as err:
@@ -1086,7 +1098,9 @@ class BuildsStrategy(SearchStrategy[Ex]):
) from err
raise
- current_build_context().record_call(obj, self.target, args=args, kwargs=kwargs)
+ context.record_call(
+ obj, self.target, args=args, kwargs=kwargs, arg_labels=arg_labels
+ )
return obj
def do_validate(self) -> None:
diff --git a/contrib/python/hypothesis/py3/hypothesis/utils/deprecation.py b/contrib/python/hypothesis/py3/hypothesis/utils/deprecation.py
new file mode 100644
index 00000000000..e27e2cf9c1e
--- /dev/null
+++ b/contrib/python/hypothesis/py3/hypothesis/utils/deprecation.py
@@ -0,0 +1,28 @@
+# 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 datetime
+import warnings
+
+from hypothesis.errors import HypothesisDeprecationWarning
+
+
+def note_deprecation(
+ message: str, *, since: str, has_codemod: bool, stacklevel: int = 0
+) -> None:
+ if since != "RELEASEDAY":
+ date = datetime.date.fromisoformat(since)
+ assert datetime.date(2021, 1, 1) <= date
+ if has_codemod:
+ message += (
+ "\n The `hypothesis codemod` command-line tool can automatically "
+ "refactor your code to fix this warning."
+ )
+ warnings.warn(HypothesisDeprecationWarning(message), stacklevel=2 + stacklevel)
diff --git a/contrib/python/hypothesis/py3/hypothesis/vendor/pretty.py b/contrib/python/hypothesis/py3/hypothesis/vendor/pretty.py
index af790bbab2c..41315694d1e 100644
--- a/contrib/python/hypothesis/py3/hypothesis/vendor/pretty.py
+++ b/contrib/python/hypothesis/py3/hypothesis/vendor/pretty.py
@@ -83,10 +83,13 @@ if TYPE_CHECKING:
T = TypeVar("T")
PrettyPrintFunction: TypeAlias = Callable[[Any, "RepresentationPrinter", bool], None]
+ArgLabelsT: TypeAlias = dict[str, tuple[int, int]]
__all__ = [
"IDKey",
"RepresentationPrinter",
+ "_fixeddict_pprinter",
+ "_tuple_pprinter",
"pretty",
]
@@ -186,6 +189,9 @@ class RepresentationPrinter:
self.known_object_printers = context.known_object_printers
self.slice_comments = context.data.slice_comments
assert all(isinstance(k, IDKey) for k in self.known_object_printers)
+ # Track which slices we've already printed comments for, to avoid
+ # duplicating comments when nested objects share the same slice range.
+ self._commented_slices: set[tuple[int, int]] = set()
def pretty(self, obj: object, *, cycle: bool = False) -> None:
"""Pretty print the given object."""
@@ -212,6 +218,25 @@ class RepresentationPrinter:
if callable(pretty_method):
return pretty_method(self, cycle)
+ # Check for object-specific printers which show how this
+ # object was constructed (a Hypothesis special feature).
+ # This must come before type_pprinters so that sub-argument
+ # comments are shown for tuples/dicts/etc.
+ printers = self.known_object_printers[IDKey(obj)]
+ if len(printers) == 1:
+ return printers[0](obj, self, cycle)
+ if printers:
+ # Multiple registered functions for the same object (due to
+ # caching, small ints, etc). Use the first if all produce
+ # the same string; otherwise pretend none were registered.
+ strs = set()
+ for f in printers:
+ p = RepresentationPrinter()
+ f(obj, p, cycle)
+ strs.add(p.getvalue())
+ if len(strs) == 1:
+ return printers[0](obj, self, cycle)
+
# Next walk the mro and check for either:
# 1) a registered printer
# 2) a _repr_pretty_ method
@@ -250,26 +275,6 @@ class RepresentationPrinter:
if v.init
],
)
- # Now check for object-specific printers which show how this
- # object was constructed (a Hypothesis special feature).
- printers = self.known_object_printers[IDKey(obj)]
- if len(printers) == 1:
- return printers[0](obj, self, cycle)
- elif printers:
- # We've ended up with multiple registered functions for the same
- # object, which must have been returned from multiple calls due to
- # e.g. memoization. If they all return the same string, we'll use
- # the first; otherwise we'll pretend that *none* were registered.
- #
- # It's annoying, but still seems to be the best option for which-
- # parts-matter too, as unreportable results aren't very useful.
- strs = set()
- for f in printers:
- p = RepresentationPrinter()
- f(obj, p, cycle)
- strs.add(p.getvalue())
- if len(strs) == 1:
- return printers[0](obj, self, cycle)
# A user-provided repr. Find newlines and replace them with p.break_()
return _repr_pprint(obj, self, cycle)
@@ -411,28 +416,34 @@ class RepresentationPrinter:
name: str,
args: Sequence[object],
kwargs: dict[str, object],
+ arg_labels: ArgLabelsT | None = None,
) -> None:
# pprint this object as a call, _unless_ the call would be invalid syntax
# and the repr would be valid and there are not comments on arguments.
if cycle:
return self.text("<...>")
- # Since we don't yet track comments for sub-argument parts, we omit the
- # "if no comments" condition here for now. Add it when we revive
- # https://github.com/HypothesisWorks/hypothesis/pull/3624/
- with suppress(Exception):
- # Check whether the repr is valid syntax:
- ast.parse(repr(obj))
- # Given that the repr is valid syntax, check the call:
- p = RepresentationPrinter()
- p.stack = self.stack.copy()
- p.known_object_printers = self.known_object_printers
- p.repr_call(name, args, kwargs)
- # If the call is not valid syntax, use the repr
- try:
- ast.parse(p.getvalue())
- except Exception:
- return _repr_pprint(obj, self, cycle)
- return self.repr_call(name, args, kwargs)
+ # Look up comments from slice_comments if we have arg_labels
+ comments = {}
+ if arg_labels:
+ for key, sr in arg_labels.items():
+ if sr in self.slice_comments:
+ comments[key] = self.slice_comments[sr]
+ # If there are comments, we must use our call-style repr regardless of syntax
+ if not comments:
+ with suppress(Exception):
+ # Check whether the repr is valid syntax:
+ ast.parse(repr(obj))
+ # Given that the repr is valid syntax, check the call:
+ p = RepresentationPrinter()
+ p.stack = self.stack.copy()
+ p.known_object_printers = self.known_object_printers
+ p.repr_call(name, args, kwargs)
+ # If the call is not valid syntax, use the repr
+ try:
+ ast.parse(p.getvalue())
+ except Exception:
+ return _repr_pprint(obj, self, cycle)
+ return self.repr_call(name, args, kwargs, arg_slices=arg_labels)
def repr_call(
self,
@@ -441,7 +452,7 @@ class RepresentationPrinter:
kwargs: dict[str, object],
*,
force_split: bool | None = None,
- arg_slices: dict[str, tuple[int, int]] | None = None,
+ arg_slices: ArgLabelsT | None = None,
leading_comment: str | None = None,
avoid_realization: bool = False,
) -> None:
@@ -457,14 +468,15 @@ class RepresentationPrinter:
if func_name.startswith(("lambda:", "lambda ")):
func_name = f"({func_name})"
self.text(func_name)
- all_args = [(None, v) for v in args] + list(kwargs.items())
- # int indicates the position of a positional argument, rather than a keyword
- # argument. Currently no callers use this; see #3624.
- comments: dict[int | str, object] = {
- k: self.slice_comments[v]
- for k, v in (arg_slices or {}).items()
- if v in self.slice_comments
- }
+ # Build list of (label, value) pairs. Labels are "arg[i]" for positional
+ # args, or the keyword name. Skip slices already commented at a higher level.
+ all_args = [(f"arg[{i}]", v) for i, v in enumerate(args)]
+ all_args += list(kwargs.items())
+ arg_slices = arg_slices or {}
+ comments: dict[str, tuple[str, tuple[int, int]]] = {}
+ for label, sr in arg_slices.items():
+ if sr in self.slice_comments and sr not in self._commented_slices:
+ comments[label] = (self.slice_comments[sr], sr)
if leading_comment or any(k in comments for k, _ in all_args):
# We have to split one arg per line in order to leave comments on them.
@@ -480,7 +492,7 @@ class RepresentationPrinter:
force_split = "\n" in s
with self.group(indent=4, open="(", close=""):
- for i, (k, v) in enumerate(all_args):
+ for i, (label, v) in enumerate(all_args):
if force_split:
if i == 0 and leading_comment:
self.break_()
@@ -489,19 +501,20 @@ class RepresentationPrinter:
else:
assert leading_comment is None # only passed by top-level report
self.breakable(" " if i else "")
- if k:
- self.text(f"{k}=")
+ if not label.startswith("arg["):
+ self.text(f"{label}=")
+ # Mark slice as commented BEFORE printing value, so nested printers skip it
+ entry = comments.get(label)
+ if entry:
+ self._commented_slices.add(entry[1])
if avoid_realization:
self.text("<symbolic>")
else:
self.pretty(v)
if force_split or i + 1 < len(all_args):
self.text(",")
- comment = None
- if k is not None:
- comment = comments.get(i) or comments.get(k)
- if comment:
- self.text(f" # {comment}")
+ if entry:
+ self.text(f" # {entry[0]}")
if all_args and force_split:
self.break_()
self.text(")") # after dedent
@@ -801,6 +814,77 @@ def pprint_fields(
p.pretty(getattr(obj, field))
+def _get_slice_comment(
+ p: RepresentationPrinter,
+ arg_labels: ArgLabelsT,
+ key: Any,
+) -> tuple[str, tuple[int, int]] | None:
+ """Look up a comment for a slice, if not already printed at a higher level."""
+ if (sr := arg_labels.get(key)) and sr in p.slice_comments:
+ if sr not in p._commented_slices:
+ return (p.slice_comments[sr], sr)
+ return None
+
+
+def _tuple_pprinter(arg_labels: ArgLabelsT) -> PrettyPrintFunction:
+ """Pretty printer for tuples that shows sub-argument comments."""
+
+ def inner(obj: tuple, p: RepresentationPrinter, cycle: bool) -> None:
+ if cycle:
+ return p.text("(...)")
+
+ get = lambda i: _get_slice_comment(p, arg_labels, f"arg[{i}]")
+ has_comments = any(get(i) for i in range(len(obj)))
+
+ with p.group(indent=4, open="(", close=""):
+ for idx, x in p._enumerate(obj):
+ p.break_() if has_comments else (p.breakable() if idx else None)
+ p.pretty(x)
+ if has_comments or idx + 1 < len(obj) or len(obj) == 1:
+ p.text(",")
+ if entry := get(idx):
+ p._commented_slices.add(entry[1])
+ p.text(f" # {entry[0]}")
+ if has_comments and obj:
+ p.break_()
+ p.text(")")
+
+ return inner
+
+
+def _fixeddict_pprinter(
+ arg_labels: ArgLabelsT,
+ mapping: dict[Any, Any],
+) -> PrettyPrintFunction:
+ """Pretty printer for fixed_dictionaries that shows sub-argument comments."""
+
+ def inner(obj: dict, p: RepresentationPrinter, cycle: bool) -> None:
+ if cycle:
+ return p.text("{...}")
+
+ get = lambda k: _get_slice_comment(p, arg_labels, k)
+ # Preserve mapping key order, then any optional keys (deduped)
+ keys = list(dict.fromkeys(k for k in [*mapping, *obj] if k in obj))
+ has_comments = any(get(k) for k in keys)
+
+ with p.group(indent=4, open="{", close=""):
+ for idx, key in p._enumerate(keys):
+ p.break_() if has_comments else (p.breakable() if idx else None)
+ p.pretty(key)
+ p.text(": ")
+ p.pretty(obj[key])
+ if has_comments or idx + 1 < len(keys):
+ p.text(",")
+ if entry := get(key):
+ p._commented_slices.add(entry[1])
+ p.text(f" # {entry[0]}")
+ if has_comments and obj:
+ p.break_()
+ p.text("}")
+
+ return inner
+
+
def _function_pprint(
obj: types.FunctionType | types.BuiltinFunctionType | types.MethodType,
p: RepresentationPrinter,
diff --git a/contrib/python/hypothesis/py3/hypothesis/version.py b/contrib/python/hypothesis/py3/hypothesis/version.py
index 1b8e5a60276..41812a39d47 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, 148, 12)
+__version_info__ = (6, 149, 1)
__version__ = ".".join(map(str, __version_info__))
diff --git a/contrib/python/hypothesis/py3/ya.make b/contrib/python/hypothesis/py3/ya.make
index 9e7117c2ebf..89ea7a2df29 100644
--- a/contrib/python/hypothesis/py3/ya.make
+++ b/contrib/python/hypothesis/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(6.148.12)
+VERSION(6.149.1)
LICENSE(MPL-2.0)
@@ -119,6 +119,7 @@ PY_SRCS(
hypothesis/strategies/_internal/utils.py
hypothesis/utils/__init__.py
hypothesis/utils/conventions.py
+ hypothesis/utils/deprecation.py
hypothesis/utils/dynamicvariables.py
hypothesis/utils/terminal.py
hypothesis/utils/threading.py