diff options
| author | robot-piglet <[email protected]> | 2025-11-19 10:16:42 +0300 |
|---|---|---|
| committer | robot-piglet <[email protected]> | 2025-11-19 10:29:21 +0300 |
| commit | ccd5394bd239df01603ee6fa4adb04ea8389cf85 (patch) | |
| tree | 1fb10dba55fbd344904aaf26bd22abcbba1d6faa /contrib/python/hypothesis | |
| parent | 8c1573d7b800bff882ae5a8bb66e8c85dabc1777 (diff) | |
Intermediate changes
commit_hash:a0c75506f0d1cf5a6805d32237dc5b93c517de28
Diffstat (limited to 'contrib/python/hypothesis')
22 files changed, 181 insertions, 166 deletions
diff --git a/contrib/python/hypothesis/py3/.dist-info/METADATA b/contrib/python/hypothesis/py3/.dist-info/METADATA index fa535de452c..be7f9497fd0 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.144.0 +Version: 6.145.1 Summary: A library for property-based testing Author-email: "David R. MacIver and Zac Hatfield-Dodds" <[email protected]> License-Expression: MPL-2.0 @@ -33,7 +33,6 @@ Classifier: Typing :: Typed Requires-Python: >=3.10 Description-Content-Type: text/markdown License-File: LICENSE.txt -Requires-Dist: attrs>=22.2.0 Requires-Dist: exceptiongroup>=1.0.0; python_version < "3.11" Requires-Dist: sortedcontainers<3.0.0,>=2.1.0 Provides-Extra: cli diff --git a/contrib/python/hypothesis/py3/hypothesis/core.py b/contrib/python/hypothesis/py3/hypothesis/core.py index 9315b89a8a4..3cc6a4e85ed 100644 --- a/contrib/python/hypothesis/py3/hypothesis/core.py +++ b/contrib/python/hypothesis/py3/hypothesis/core.py @@ -157,7 +157,7 @@ global_force_seed = None threadlocal = ThreadLocal(_hypothesis_global_random=lambda: None) -@dataclass +@dataclass(slots=True, frozen=False) class Example: args: Any kwargs: Any @@ -718,7 +718,7 @@ def get_random_for_wrapped_test(test, wrapped_test): return Random(seed) -@dataclass +@dataclass(slots=True, frozen=False) class Stuff: selfy: Any args: tuple @@ -1622,7 +1622,7 @@ def fake_subTest(self, msg=None, **__): yield -@dataclass +@dataclass(slots=False, frozen=False) class HypothesisHandle: """This object is provided as the .hypothesis attribute on @given tests. diff --git a/contrib/python/hypothesis/py3/hypothesis/extra/pandas/impl.py b/contrib/python/hypothesis/py3/hypothesis/extra/pandas/impl.py index 7160566f62e..4d4c00f1277 100644 --- a/contrib/python/hypothesis/py3/hypothesis/extra/pandas/impl.py +++ b/contrib/python/hypothesis/py3/hypothesis/extra/pandas/impl.py @@ -11,10 +11,10 @@ from collections import OrderedDict, abc from collections.abc import Sequence from copy import copy +from dataclasses import dataclass from datetime import datetime, timedelta from typing import Any, Generic, Union -import attr import numpy as np import pandas @@ -25,7 +25,6 @@ from hypothesis.errors import InvalidArgument from hypothesis.extra import numpy as npst from hypothesis.internal.conjecture import utils as cu from hypothesis.internal.coverage import check, check_function -from hypothesis.internal.reflection import get_pretty_function_description from hypothesis.internal.validation import ( check_type, check_valid_interval, @@ -364,7 +363,7 @@ def series( return result() [email protected](slots=True) +@dataclass(slots=True, frozen=False) class column(Generic[Ex]): """Data object for describing a column in a DataFrame. @@ -382,11 +381,11 @@ class column(Generic[Ex]): * unique: If all values in this column should be distinct. """ - name: str | int | None = attr.ib(default=None) - elements: st.SearchStrategy[Ex] | None = attr.ib(default=None) - dtype: Any = attr.ib(default=None, repr=get_pretty_function_description) - fill: st.SearchStrategy[Ex] | None = attr.ib(default=None) - unique: bool = attr.ib(default=False) + name: str | int | None = None + elements: st.SearchStrategy[Ex] | None = None + dtype: Any = None + fill: st.SearchStrategy[Ex] | None = None + unique: bool = False def columns( diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/cache.py b/contrib/python/hypothesis/py3/hypothesis/internal/cache.py index 73b9ed76d2c..899576f228d 100644 --- a/contrib/python/hypothesis/py3/hypothesis/internal/cache.py +++ b/contrib/python/hypothesis/py3/hypothesis/internal/cache.py @@ -10,22 +10,21 @@ import threading from collections import OrderedDict +from dataclasses import dataclass from typing import Any, Generic, TypeVar -import attr - from hypothesis.errors import InvalidArgument K = TypeVar("K") V = TypeVar("V") [email protected](slots=True) +@dataclass(slots=True, frozen=False) class Entry(Generic[K, V]): - key: K = attr.ib() - value: V = attr.ib() - score: int = attr.ib() - pins: int = attr.ib(default=0) + key: K + value: V + score: int + pins: int = 0 @property def sort_key(self) -> tuple[int, ...]: diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/choice.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/choice.py index ffc08246fa6..b66f7e27537 100644 --- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/choice.py +++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/choice.py @@ -10,6 +10,7 @@ import math from collections.abc import Callable, Hashable, Iterable, Sequence +from dataclasses import dataclass from typing import ( Literal, TypeAlias, @@ -18,8 +19,6 @@ from typing import ( cast, ) -import attr - from hypothesis.errors import ChoiceTooLarge from hypothesis.internal.conjecture.floats import float_to_lex, lex_to_float from hypothesis.internal.conjecture.utils import identity @@ -72,23 +71,23 @@ ChoiceKeyT: TypeAlias = ( ) [email protected](slots=True) +@dataclass(slots=True, frozen=False) class ChoiceTemplate: - type: Literal["simplest"] = attr.ib() - count: int | None = attr.ib() + type: Literal["simplest"] + count: int | None - def __attrs_post_init__(self) -> None: + def __post_init__(self) -> None: if self.count is not None: assert self.count > 0 [email protected](slots=True, repr=False, eq=False) +@dataclass(slots=True, frozen=False) class ChoiceNode: - type: ChoiceTypeT = attr.ib() - value: ChoiceT = attr.ib() - constraints: ChoiceConstraintsT = attr.ib() - was_forced: bool = attr.ib() - index: int | None = attr.ib(default=None) + type: ChoiceTypeT + value: ChoiceT + constraints: ChoiceConstraintsT + was_forced: bool + index: int | None = None def copy( self, diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py index 6f9d07591bb..4ac53e9dd92 100644 --- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py +++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py @@ -12,6 +12,7 @@ import math import time from collections import defaultdict from collections.abc import Hashable, Iterable, Iterator, Sequence +from dataclasses import dataclass, field from enum import IntEnum from functools import cached_property from random import Random @@ -26,8 +27,6 @@ from typing import ( overload, ) -import attr - from hypothesis.errors import ( CannotProceedScopeT, ChoiceTooLarge, @@ -120,9 +119,9 @@ class Status(IntEnum): return f"Status.{self.name}" [email protected](slots=True, frozen=True) +@dataclass(slots=True, frozen=True) class StructuralCoverageTag: - label: int = attr.ib() + label: int STRUCTURAL_COVERAGE_CACHE: dict[int, StructuralCoverageTag] = {} @@ -568,27 +567,27 @@ class DataObserver: pass [email protected](slots=True) +@dataclass(slots=True, frozen=True) class ConjectureResult: """Result class storing the parts of ConjectureData that we will care about after the original ConjectureData has outlived its usefulness.""" - status: Status = attr.ib() - interesting_origin: InterestingOrigin | None = attr.ib() - nodes: tuple[ChoiceNode, ...] = attr.ib(eq=False, repr=False) - length: int = attr.ib() - output: str = attr.ib() - expected_exception: BaseException | None = attr.ib() - expected_traceback: str | None = attr.ib() - has_discards: bool = attr.ib() - target_observations: TargetObservations = attr.ib() - tags: frozenset[StructuralCoverageTag] = attr.ib() - spans: Spans = attr.ib(repr=False, eq=False) - arg_slices: set[tuple[int, int]] = attr.ib(repr=False) - slice_comments: dict[tuple[int, int], str] = attr.ib(repr=False) - misaligned_at: MisalignedAt | None = attr.ib(repr=False) - cannot_proceed_scope: CannotProceedScopeT | None = attr.ib(repr=False) + status: Status + interesting_origin: InterestingOrigin | None + nodes: tuple[ChoiceNode, ...] = field(repr=False, compare=False) + length: int + output: str + expected_exception: BaseException | None + expected_traceback: str | None + has_discards: bool + target_observations: TargetObservations + tags: frozenset[StructuralCoverageTag] + spans: Spans = field(repr=False, compare=False) + arg_slices: set[tuple[int, int]] = field(repr=False) + slice_comments: dict[tuple[int, int], str] = field(repr=False) + misaligned_at: MisalignedAt | None = field(repr=False) + cannot_proceed_scope: CannotProceedScopeT | None = field(repr=False) def as_result(self) -> "ConjectureResult": return self diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/datatree.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/datatree.py index 54e9b2664db..df02449a917 100644 --- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/datatree.py +++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/datatree.py @@ -10,11 +10,10 @@ import math from collections.abc import Generator, Set +from dataclasses import dataclass, field from random import Random from typing import TYPE_CHECKING, Final, TypeAlias, cast -import attr - from hypothesis.errors import ( FlakyReplay, FlakyStrategyDefinition, @@ -64,14 +63,14 @@ _FLAKY_STRAT_MSG = ( EMPTY: frozenset[int] = frozenset() [email protected](slots=True) +@dataclass(slots=True, frozen=True) class Killed: """Represents a transition to part of the tree which has been marked as "killed", meaning we want to treat it as not worth exploring, so it will be treated as if it were completely explored for the purposes of exhaustion.""" - next_node: "TreeNode" = attr.ib() + next_node: "TreeNode" def _repr_pretty_(self, p: "RepresentationPrinter", cycle: bool) -> None: assert cycle is False @@ -89,14 +88,14 @@ def _node_pretty( return f"{choice_type} {value!r}{forced_marker} {constraints}" [email protected](slots=True) +@dataclass(slots=True, frozen=False) class Branch: """Represents a transition where multiple choices can be made as to what to drawn.""" - constraints: ChoiceConstraintsT = attr.ib() - choice_type: ChoiceTypeT = attr.ib() - children: dict[ChoiceT, "TreeNode"] = attr.ib(repr=False) + constraints: ChoiceConstraintsT + choice_type: ChoiceTypeT + children: dict[ChoiceT, "TreeNode"] = field(repr=False) @property def max_children(self) -> int: @@ -117,12 +116,12 @@ class Branch: p.pretty(child) [email protected](slots=True, frozen=True) +@dataclass(slots=True, frozen=True) class Conclusion: """Represents a transition to a finished state.""" - status: Status = attr.ib() - interesting_origin: InterestingOrigin | None = attr.ib() + status: Status + interesting_origin: InterestingOrigin | None def _repr_pretty_(self, p: "RepresentationPrinter", cycle: bool) -> None: assert cycle is False @@ -334,7 +333,7 @@ def all_children( yield from _floats_between(min_point, max_value) [email protected](slots=True) +@dataclass(slots=True, frozen=False) class TreeNode: """ A node, or collection of directly descended nodes, in a DataTree. @@ -400,15 +399,15 @@ class TreeNode: # The constraints, value, and choice_types of the nodes stored here. These always # have the same length. The values at index i belong to node i. - constraints: list[ChoiceConstraintsT] = attr.ib(factory=list) - values: list[ChoiceT] = attr.ib(factory=list) - choice_types: list[ChoiceTypeT] = attr.ib(factory=list) + constraints: list[ChoiceConstraintsT] = field(default_factory=list) + values: list[ChoiceT] = field(default_factory=list) + choice_types: list[ChoiceTypeT] = field(default_factory=list) # The indices of nodes which had forced values. # # Stored as None if no indices have been forced, purely for space saving # reasons (we force quite rarely). - __forced: set[int] | None = attr.ib(default=None, init=False) + __forced: set[int] | None = field(default=None, init=False) # What happens next after drawing these nodes. (conceptually, "what is the # child/children of the last node stored here"). @@ -419,14 +418,14 @@ class TreeNode: # - Conclusion (ConjectureData.conclude_test was called here) # - Killed (this branch is valid and may even have children, but should not # be explored when generating novel prefixes) - transition: None | Branch | Conclusion | Killed = attr.ib(default=None) + transition: None | Branch | Conclusion | Killed = None # A tree node is exhausted if every possible sequence of draws below it has # been explored. We only update this when performing operations that could # change the answer. # # See also TreeNode.check_exhausted. - is_exhausted: bool = attr.ib(default=False, init=False) + is_exhausted: bool = field(default=False, init=False) @property def forced(self) -> Set[int]: diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/dfa/lstar.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/dfa/lstar.py index 061cb038253..25e638637b2 100644 --- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/dfa/lstar.py +++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/dfa/lstar.py @@ -10,8 +10,7 @@ from bisect import bisect_right, insort from collections import Counter - -import attr +from dataclasses import dataclass, field from hypothesis.errors import InvalidState from hypothesis.internal.conjecture.dfa import DFA, cached @@ -81,31 +80,31 @@ learning languages offline that we can record for later use. """ [email protected](slots=True) +@dataclass(slots=True, frozen=False) class DistinguishedState: """Relevant information for a state that we have witnessed as definitely distinct from ones we have previously seen so far.""" # Index of this state in the learner's list of states - index: int = attr.ib() + index: int # A string that witnesses this state (i.e. when starting from the origin # and following this string you will end up in this state). - label: str = attr.ib() + label: str # A boolean as to whether this is an accepting state. - accepting: bool = attr.ib() + accepting: bool # A list of experiments that it is necessary to run to determine whether # a string is in this state. This is stored as a dict mapping experiments # to their expected result. A string is only considered to lead to this # state if ``all(learner.member(s + experiment) == result for experiment, # result in self.experiments.items())``. - experiments: dict = attr.ib() + experiments: dict # A cache of transitions out of this state, mapping bytes to the states # that they lead to. - transitions: dict = attr.ib(factory=dict) + transitions: dict = field(default_factory=dict) class LStar: diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py index a363cdd047a..7af61ac6045 100644 --- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py +++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py @@ -113,7 +113,7 @@ def shortlex(s): return (len(s), s) -@dataclass +@dataclass(slots=True, frozen=False) class HealthCheckState: valid_examples: int = field(default=0) invalid_examples: int = field(default=0) diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py index 9aad89c6e44..204b794345c 100644 --- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py +++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py @@ -89,7 +89,7 @@ def sort_key(nodes: Sequence[ChoiceNode]) -> tuple[int, tuple[int, ...]]: ) -@dataclass +@dataclass(slots=True, frozen=False) class ShrinkPass: function: Any name: str | None = None diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/escalation.py b/contrib/python/hypothesis/py3/hypothesis/internal/escalation.py index c29c846b770..031a99004c0 100644 --- a/contrib/python/hypothesis/py3/hypothesis/internal/escalation.py +++ b/contrib/python/hypothesis/py3/hypothesis/internal/escalation.py @@ -16,7 +16,7 @@ import traceback from collections.abc import Callable from dataclasses import dataclass from functools import partial -from inspect import getframeinfo +from inspect import getfile, getsourcefile from pathlib import Path from types import ModuleType, TracebackType @@ -82,7 +82,7 @@ def get_trimmed_traceback( return tb while tb.tb_next is not None and ( # If the frame is from one of our files, it's been added by Hypothesis. - is_hypothesis_file(getframeinfo(tb.tb_frame).filename) + is_hypothesis_file(getsourcefile(tb.tb_frame) or getfile(tb.tb_frame)) # But our `@proxies` decorator overrides the source location, # so we check for an attribute it injects into the frame too. or tb.tb_frame.f_globals.get("__hypothesistracebackhide__") is True @@ -91,7 +91,7 @@ def get_trimmed_traceback( return tb -@dataclass(frozen=True) +@dataclass(slots=True, frozen=True) class InterestingOrigin: # The `interesting_origin` is how Hypothesis distinguishes between multiple # failures, for reporting and also to replay from the example database (even diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/observability.py b/contrib/python/hypothesis/py3/hypothesis/internal/observability.py index 0310967132a..84d5b51bf49 100644 --- a/contrib/python/hypothesis/py3/hypothesis/internal/observability.py +++ b/contrib/python/hypothesis/py3/hypothesis/internal/observability.py @@ -68,7 +68,7 @@ _callbacks: dict[int | None, list[CallbackThreadT]] = {} _callbacks_all_threads: list[CallbackAllThreadsT] = [] -@dataclass +@dataclass(slots=True, frozen=False) class PredicateCounts: satisfied: int = 0 unsatisfied: int = 0 @@ -167,7 +167,7 @@ def nodes_to_json(nodes: tuple[ChoiceNode, ...]) -> list[dict[str, Any]]: ] -@dataclass +@dataclass(slots=True, frozen=True) class ObservationMetadata: traceback: str | None reproduction_decorator: str | None @@ -221,7 +221,7 @@ class ObservationMetadata: return data -@dataclass +@dataclass(slots=True, frozen=True) class BaseObservation: type: Literal["test_case", "info", "alert", "error"] property: str @@ -232,14 +232,14 @@ InfoObservationType = Literal["info", "alert", "error"] TestCaseStatus = Literal["gave_up", "passed", "failed"] -@dataclass +@dataclass(slots=True, frozen=True) class InfoObservation(BaseObservation): type: InfoObservationType title: str content: str | dict -@dataclass +@dataclass(slots=True, frozen=True) class TestCaseObservation(BaseObservation): __test__ = False # no! bad pytest! diff --git a/contrib/python/hypothesis/py3/hypothesis/stateful.py b/contrib/python/hypothesis/py3/hypothesis/stateful.py index 7c2ef1ccb40..22596158397 100644 --- a/contrib/python/hypothesis/py3/hypothesis/stateful.py +++ b/contrib/python/hypothesis/py3/hypothesis/stateful.py @@ -269,7 +269,7 @@ class StateMachineMeta(type): return super().__setattr__(name, value) -@dataclass +@dataclass(slots=True, frozen=True) class _SetupState: rules: list["Rule"] invariants: list["Invariant"] @@ -492,7 +492,7 @@ class RuleBasedStateMachine(metaclass=StateMachineMeta): return StateMachineTestCase -@dataclass +@dataclass(slots=True, frozen=False) class Rule: targets: Any function: Any @@ -501,9 +501,9 @@ class Rule: bundles: tuple["Bundle", ...] = field(init=False) _cached_hash: int | None = field(init=False, default=None) _cached_repr: str | None = field(init=False, default=None) + arguments_strategies: dict[Any, Any] = field(init=False, default_factory=dict) def __post_init__(self): - self.arguments_strategies = {} bundles = [] for k, v in sorted(self.arguments.items()): assert not isinstance(v, BundleReferenceStrategy) @@ -658,7 +658,7 @@ def consumes(bundle: Bundle[Ex]) -> SearchStrategy[Ex]: return BundleConsumer(bundle) -@dataclass +@dataclass(slots=True, frozen=True) class MultipleResults(Iterable[Ex]): values: tuple[Ex, ...] @@ -941,7 +941,7 @@ def initialize( return accept -@dataclass +@dataclass(slots=True, frozen=True) class VarReference: name: str @@ -1010,7 +1010,7 @@ def precondition(precond: Callable[[Any], bool]) -> Callable[[TestFunc], TestFun return decorator -@dataclass +@dataclass(slots=True, frozen=True) class Invariant: function: Any preconditions: Any diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/attrs.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/attrs.py index 555b7ca09b6..4e057df545c 100644 --- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/attrs.py +++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/attrs.py @@ -8,6 +8,9 @@ # 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/. +# Since Hypothesis doesn't have a hard dependency on attrs, be careful to only import +# this file when attrs is in sys.modules. + from collections.abc import Collection, Generator, Iterable, Sequence from functools import reduce from itertools import chain diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py index 187be5c78f1..bd3be431a29 100644 --- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py +++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py @@ -45,8 +45,6 @@ from typing import ( ) from uuid import UUID -import attr - from hypothesis._settings import note_deprecation from hypothesis.control import ( cleanup, @@ -172,7 +170,7 @@ def sampled_from( ... -@defines_strategy(try_non_lazy=True) +@defines_strategy(eager="try") def sampled_from( elements: type[enum.Enum] | Sequence[Any], ) -> SearchStrategy[Any]: @@ -1154,7 +1152,11 @@ def builds( required = required_args(target, args, kwargs) to_infer = {k for k, v in kwargs.items() if v is ...} if required or to_infer: - if isinstance(target, type) and attr.has(target): + if ( + isinstance(target, type) + and (attr := sys.modules.get("attr")) is not None + and attr.has(target) + ): # pragma: no cover # covered by our attrs tests in check-niche # Use our custom introspection for attrs classes from hypothesis.strategies._internal.attrs import from_attrs @@ -1188,7 +1190,7 @@ def builds( @cacheable -@defines_strategy(never_lazy=True) +@defines_strategy(eager=True) def from_type(thing: type[T]) -> SearchStrategy[T]: """Looks up the appropriate search strategy for the given type. @@ -1536,7 +1538,7 @@ def _from_type(thing: type[Ex]) -> SearchStrategy[Ex]: required = required_args(thing) if required and not ( required.issubset(get_type_hints(thing)) - or attr.has(thing) + or ((attr := sys.modules.get("attr")) is not None and attr.has(thing)) or is_typed_named_tuple(thing) # weird enough that we have a specific check ): raise ResolutionFailed( @@ -1826,7 +1828,7 @@ def decimals( return strat | (sampled_from(special) if special else nothing()) -@defines_strategy(never_lazy=True) +@defines_strategy(eager=True) def recursive( base: SearchStrategy[Ex], extend: Callable[[SearchStrategy[Any]], SearchStrategy[T]], @@ -2171,7 +2173,7 @@ def complex_numbers( return constrained_complex() -@defines_strategy(never_lazy=True) +@defines_strategy(eager=True) def shared( base: SearchStrategy[Ex], *, @@ -2338,7 +2340,7 @@ class DataStrategy(SearchStrategy): @cacheable -@defines_strategy(never_lazy=True) +@defines_strategy(eager=True) def data() -> SearchStrategy[DataObject]: """ Provides an object ``data`` with a ``data.draw`` function which acts like @@ -2486,7 +2488,7 @@ def register_type_strategy( @cacheable -@defines_strategy(never_lazy=True) +@defines_strategy(eager=True) def deferred(definition: Callable[[], SearchStrategy[Ex]]) -> SearchStrategy[Ex]: """A deferred strategy allows you to write a strategy that references other strategies that have not yet been defined. This allows for the easy diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/misc.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/misc.py index 6e453040e42..cbcfa324455 100644 --- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/misc.py +++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/misc.py @@ -64,7 +64,7 @@ class JustStrategy(SampledFromStrategy[Ex]): return self._transform(self.value) -@defines_strategy(never_lazy=True) +@defines_strategy(eager=True) def just(value: T) -> SearchStrategy[T]: """Return a strategy which only generates ``value``. @@ -120,7 +120,7 @@ NOTHING = Nothing() @cacheable -@defines_strategy(never_lazy=True) +@defines_strategy(eager=True) def nothing() -> SearchStrategy["Never"]: """This strategy never successfully draws a value and will always reject on an attempt to draw. diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/random.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/random.py index 8ff1d077877..523991c6e72 100644 --- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/random.py +++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/random.py @@ -11,11 +11,10 @@ import abc import inspect import math +from dataclasses import dataclass, field from random import Random from typing import Any -import attr - from hypothesis.control import should_note from hypothesis.internal.conjecture.data import ConjectureData from hypothesis.internal.reflection import define_function_signature @@ -147,10 +146,10 @@ for r in RANDOM_METHODS: define_copy_method(r) [email protected](slots=True) +@dataclass(slots=True, frozen=False) class RandomState: - next_states: dict = attr.ib(factory=dict) - state_id: Any = attr.ib(default=None) + next_states: dict = field(default_factory=dict) + state_id: Any = None def state_for_seed(data, seed): diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/strategies.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/strategies.py index e5e03145d61..0fd21ea108a 100644 --- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/strategies.py +++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/strategies.py @@ -271,9 +271,12 @@ class SearchStrategy(Generic[Ex]): def has_reusable_values(self) -> Any: return recursive_property(self, "has_reusable_values", True) - # Whether this strategy is suitable for holding onto in a cache. @property def is_cacheable(self) -> Any: + """ + Whether it is safe to hold on to instances of this strategy in a cache. + See _STRATEGY_CACHE. + """ return recursive_property(self, "is_cacheable", True) def calc_is_cacheable(self, recur: RecurT) -> bool: @@ -918,7 +921,7 @@ def one_of(*args: SearchStrategy[Any]) -> SearchStrategy[Any]: # pragma: no cov ... -@defines_strategy(never_lazy=True) +@defines_strategy(eager=True) def one_of( *args: Sequence[SearchStrategy[Any]] | SearchStrategy[Any], ) -> SearchStrategy[Any]: diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/utils.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/utils.py index 6c5ec635c6a..c9656436c58 100644 --- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/utils.py +++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/utils.py @@ -12,11 +12,10 @@ import dataclasses import sys from collections.abc import Callable from functools import partial -from typing import TypeAlias, TypeVar +from typing import Literal, TypeAlias, TypeVar from weakref import WeakValueDictionary -import attr - +from hypothesis.errors import InvalidArgument from hypothesis.internal.cache import LRUReusedCache from hypothesis.internal.floats import clamp, float_to_int from hypothesis.internal.reflection import proxies @@ -34,13 +33,13 @@ _all_strategies: WeakValueDictionary[str, Callable] = WeakValueDictionary() _STRATEGY_CACHE = LRUReusedCache[StrategyCacheKey, object](1024) -def convert_value(v: object) -> ValueKey: - if isinstance(v, float): - return (float, float_to_int(v)) - return (type(v), v) +def _value_key(value: object) -> ValueKey: + if isinstance(value, float): + return (float, float_to_int(value)) + return (type(value), value) -def clear_cache() -> None: +def clear_strategy_cache() -> None: _STRATEGY_CACHE.clear() @@ -55,64 +54,85 @@ def cacheable(fn: T) -> T: return fn(*args, **kwargs) try: - kwargs_cache_key = {(k, convert_value(v)) for k, v in kwargs.items()} + kwargs_cache_key = {(k, _value_key(v)) for k, v in kwargs.items()} except TypeError: return fn(*args, **kwargs) - cache_key = (fn, tuple(map(convert_value, args)), frozenset(kwargs_cache_key)) + + cache_key = ( + fn, + tuple(_value_key(v) for v in args), + frozenset(kwargs_cache_key), + ) try: - if cache_key in _STRATEGY_CACHE: - return _STRATEGY_CACHE[cache_key] + return _STRATEGY_CACHE[cache_key] + except KeyError: + pass except TypeError: return fn(*args, **kwargs) - else: - result = fn(*args, **kwargs) - if not isinstance(result, SearchStrategy) or result.is_cacheable: - _STRATEGY_CACHE[cache_key] = result - return result - cached_strategy.__clear_cache = clear_cache # type: ignore + result = fn(*args, **kwargs) + if not isinstance(result, SearchStrategy) or result.is_cacheable: + _STRATEGY_CACHE[cache_key] = result + return result + + # note that calling this clears the full _STRATEGY_CACHE for all strategies, + # not just the cache for this strategy. + cached_strategy.__clear_cache = clear_strategy_cache # type: ignore return cached_strategy def defines_strategy( *, force_reusable_values: bool = False, - try_non_lazy: bool = False, - never_lazy: bool = False, + eager: bool | Literal["try"] = False, ) -> Callable[[T], T]: - """Returns a decorator for strategy functions. + """ + Each standard strategy function provided to users by Hypothesis should be + decorated with @defines_strategy. This registers the strategy with _all_strategies, + which is used in our own test suite to check that e.g. we document all strategies + in sphinx. - If ``force_reusable_values`` is True, the returned strategy will be marked - with ``.has_reusable_values == True`` even if it uses maps/filters or - non-reusable strategies internally. This tells our numpy/pandas strategies - that they can implicitly use such strategies as background values. + If you're reading this and are the author of a third-party strategy library: + don't worry, third-party strategies don't need to be decorated with + @defines_strategy. This function is internal to Hypothesis and not intended + for outside use. - If ``try_non_lazy`` is True, attempt to execute the strategy definition - function immediately, so that a LazyStrategy is only returned if this - raises an exception. + Parameters + ---------- + force_reusable_values : bool + If ``True``, strategies returned from the strategy function will have + ``.has_reusable_values == True`` set, even if it uses maps/filters or + non-reusable strategies internally. This tells our numpy/pandas strategies + that they can implicitly use such strategies as background values. + eager : bool | "try" + If ``True``, strategies returned by the strategy function are returned + as-is, and not wrapped in LazyStrategy. - If ``never_lazy`` is True, the decorator performs no lazy-wrapping at all, - and instead returns the original function. + If "try", we first attempt to call the strategy function and return the + resulting strategy. If this throws an exception, we treat it the same as + ``eager = False``, by returning the strategy function wrapped in a + LazyStrategy. """ + if eager is not False and force_reusable_values: # pragma: no cover + # We could support eager + force_reusable_values with a suitable wrapper, + # but there are currently no callers that request this combination. + raise InvalidArgument( + f"Passing both eager={eager} and force_reusable_values=True is " + "currently not supported" + ) + def decorator(strategy_definition): - """A decorator that registers the function as a strategy and makes it - lazily evaluated.""" _all_strategies[strategy_definition.__name__] = strategy_definition - if never_lazy: - assert not try_non_lazy - # We could potentially support never_lazy + force_reusable_values - # with a suitable wrapper, but currently there are no callers that - # request this combination. - assert not force_reusable_values + if eager is True: return strategy_definition - from hypothesis.strategies._internal.lazy import LazyStrategy - @proxies(strategy_definition) def accept(*args, **kwargs): - if try_non_lazy: + from hypothesis.strategies._internal.lazy import LazyStrategy + + if eager == "try": # Why not try this unconditionally? Because we'd end up with very # deep nesting of recursive strategies - better to be lazy unless we # *know* that eager evaluation is the right choice. @@ -178,19 +198,15 @@ def _to_jsonable(obj: object, *, avoid_realization: bool, seen: set[int]) -> obj pass # Special handling for dataclasses, attrs, and pydantic classes - if ( - (dcs := sys.modules.get("dataclasses")) - and dcs.is_dataclass(obj) - and not isinstance(obj, type) - ): + if dataclasses.is_dataclass(obj) and not isinstance(obj, type): # Avoid dataclasses.asdict here to ensure that inner to_json overrides # can get called as well return { field.name: recur(getattr(obj, field.name)) - for field in dataclasses.fields(obj) # type: ignore + for field in dataclasses.fields(obj) } - if attr.has(type(obj)): - return recur(attr.asdict(obj, recurse=False)) # type: ignore + if (attr := sys.modules.get("attr")) is not None and attr.has(type(obj)): + return recur(attr.asdict(obj, recurse=False)) if (pyd := sys.modules.get("pydantic")) and isinstance(obj, pyd.BaseModel): return recur(obj.model_dump()) diff --git a/contrib/python/hypothesis/py3/hypothesis/vendor/pretty.py b/contrib/python/hypothesis/py3/hypothesis/vendor/pretty.py index 83fab2406d7..af790bbab2c 100644 --- a/contrib/python/hypothesis/py3/hypothesis/vendor/pretty.py +++ b/contrib/python/hypothesis/py3/hypothesis/vendor/pretty.py @@ -232,7 +232,7 @@ class RepresentationPrinter: self.type_pprinters[cls] = printer return printer(obj, self, cycle) else: - if hasattr(cls, "__attrs_attrs__"): + if hasattr(cls, "__attrs_attrs__"): # pragma: no cover return pprint_fields( obj, self, diff --git a/contrib/python/hypothesis/py3/hypothesis/version.py b/contrib/python/hypothesis/py3/hypothesis/version.py index f9daf2ead6b..cee78be632c 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, 144, 0) +__version_info__ = (6, 145, 1) __version__ = ".".join(map(str, __version_info__)) diff --git a/contrib/python/hypothesis/py3/ya.make b/contrib/python/hypothesis/py3/ya.make index c392b732384..e0998729d12 100644 --- a/contrib/python/hypothesis/py3/ya.make +++ b/contrib/python/hypothesis/py3/ya.make @@ -2,12 +2,11 @@ PY3_LIBRARY() -VERSION(6.144.0) +VERSION(6.145.1) LICENSE(MPL-2.0) PEERDIR( - contrib/python/attrs contrib/python/sortedcontainers ) |
