1# This file is part of Hypothesis, which may be found at
2# https://github.com/HypothesisWorks/hypothesis/
3#
4# Copyright the Hypothesis Authors.
5# Individual contributors are listed in AUTHORS.rst and the git log.
6#
7# This Source Code Form is subject to the terms of the Mozilla Public License,
8# v. 2.0. If a copy of the MPL was not distributed with this file, You can
9# obtain one at https://mozilla.org/MPL/2.0/.
10
11import os
12import sys
13import threading
14import warnings
15from collections import abc, defaultdict
16from collections.abc import Callable, Sequence
17from functools import lru_cache
18from random import shuffle
19from threading import RLock
20from types import FrameType
21from typing import (
22 TYPE_CHECKING,
23 Any,
24 ClassVar,
25 Generic,
26 Literal,
27 TypeAlias,
28 TypeGuard,
29 TypeVar,
30 cast,
31 overload,
32)
33
34from hypothesis._settings import HealthCheck, Phase, Verbosity, settings
35from hypothesis.control import _current_build_context, current_build_context
36from hypothesis.errors import (
37 CannotInvert,
38 HypothesisException,
39 HypothesisWarning,
40 InvalidArgument,
41 NonInteractiveExampleWarning,
42 UnsatisfiedAssumption,
43)
44from hypothesis.internal.conjecture import utils as cu
45from hypothesis.internal.conjecture.choice import ChoiceT
46from hypothesis.internal.conjecture.data import ConjectureData
47from hypothesis.internal.conjecture.junkdrawer import equal_values
48from hypothesis.internal.conjecture.utils import (
49 calc_label_from_cls,
50 calc_label_from_hash,
51 calc_label_from_name,
52 combine_labels,
53)
54from hypothesis.internal.coverage import check_function
55from hypothesis.internal.escalation import is_hypothesis_file
56from hypothesis.internal.reflection import (
57 get_pretty_function_description,
58 is_identity_function,
59)
60from hypothesis.strategies._internal.utils import defines_strategy
61from hypothesis.utils.conventions import UniqueIdentifier, not_set
62from hypothesis.utils.dynamicvariables import DynamicVariable
63
64if TYPE_CHECKING:
65 Ex = TypeVar("Ex", covariant=True, default=Any)
66else:
67 Ex = TypeVar("Ex", covariant=True)
68
69T = TypeVar("T")
70T1 = TypeVar("T1")
71T3 = TypeVar("T3")
72T4 = TypeVar("T4")
73T5 = TypeVar("T5")
74MappedFrom = TypeVar("MappedFrom")
75MappedTo = TypeVar("MappedTo")
76RecurT: TypeAlias = Callable[["SearchStrategy"], bool]
77calculating = UniqueIdentifier("calculating")
78
79MAPPED_SEARCH_STRATEGY_DO_DRAW_LABEL = calc_label_from_name(
80 "another attempted draw in MappedStrategy"
81)
82
83FILTERED_SEARCH_STRATEGY_DO_DRAW_LABEL = calc_label_from_name(
84 "single loop iteration in FilteredStrategy"
85)
86
87label_lock = RLock()
88
89# When hypothesis re-applies a filter condition internally, eg in LazyStrategy, we override
90# stack walking with the correct original location here.
91_filter_location_override: DynamicVariable[Any] = DynamicVariable(not_set)
92
93
94def current_filter_call_site() -> str | None:
95 """The filename:lineno of the nearest enclosing non-hypothesis .filter() call.
96
97 Callers are expected to only call this from within a .filter() implementation. This
98 function simply respects _filter_location_override or walks to the nearest non-hypothesis
99 frame.
100 """
101 if (location := _filter_location_override.value) is not not_set:
102 return location
103 frame: FrameType | None = sys._getframe(1)
104 while frame is not None and is_hypothesis_file(frame.f_code.co_filename):
105 frame = frame.f_back
106 if frame is None: # pragma: no cover # ran out of frames
107 return None
108 return f"{frame.f_code.co_filename}:{frame.f_lineno}"
109
110
111def recursive_property(strategy: "SearchStrategy", name: str, default: object) -> Any:
112 """Handle properties which may be mutually recursive among a set of
113 strategies.
114
115 These are essentially lazily cached properties, with the ability to set
116 an override: If the property has not been explicitly set, we calculate
117 it on first access and memoize the result for later.
118
119 The problem is that for properties that depend on each other, a naive
120 calculation strategy may hit infinite recursion. Consider for example
121 the property is_empty. A strategy defined as x = st.deferred(lambda: x)
122 is certainly empty (in order to draw a value from x we would have to
123 draw a value from x, for which we would have to draw a value from x,
124 ...), but in order to calculate it the naive approach would end up
125 calling x.is_empty in order to calculate x.is_empty in order to etc.
126
127 The solution is one of fixed point calculation. We start with a default
128 value that is the value of the property in the absence of evidence to
129 the contrary, and then update the values of the property for all
130 dependent strategies until we reach a fixed point.
131
132 The approach taken roughly follows that in section 4.2 of Adams,
133 Michael D., Celeste Hollenbeck, and Matthew Might. "On the complexity
134 and performance of parsing with derivatives." ACM SIGPLAN Notices 51.6
135 (2016): 224-236.
136 """
137 assert name in {"is_empty", "has_reusable_values", "is_cacheable"}
138 cache_key = "cached_" + name
139 calculation = "calc_" + name
140 force_key = "force_" + name
141
142 def forced_or_cached_value(target: SearchStrategy) -> Any:
143 # Fast path: check the instance dict directly, which avoids raising and
144 # catching an AttributeError for the (usually absent) force_key.
145 d = target.__dict__
146 if force_key in d:
147 return d[force_key]
148 if cache_key in d:
149 return d[cache_key]
150 try:
151 return getattr(target, force_key)
152 except AttributeError:
153 return getattr(target, cache_key)
154
155 try:
156 return forced_or_cached_value(strategy)
157 except AttributeError:
158 pass
159
160 mapping: dict[SearchStrategy, Any] = {}
161 sentinel = object()
162 hit_recursion = False
163
164 # For a first pass we do a direct recursive calculation of the
165 # property, but we block recursively visiting a value in the
166 # computation of its property: When that happens, we simply
167 # note that it happened and return the default value.
168 def recur(strat: SearchStrategy) -> Any:
169 nonlocal hit_recursion
170 try:
171 return forced_or_cached_value(strat)
172 except AttributeError:
173 pass
174 result = mapping.get(strat, sentinel)
175 if result is calculating:
176 hit_recursion = True
177 return default
178 elif result is sentinel:
179 mapping[strat] = calculating
180 mapping[strat] = getattr(strat, calculation)(recur)
181 return mapping[strat]
182 return result
183
184 recur(strategy)
185
186 # If we hit self-recursion in the computation of any strategy
187 # value, our mapping at the end is imprecise - it may or may
188 # not have the right values in it. We now need to proceed with
189 # a more careful fixed point calculation to get the exact
190 # values. Hopefully our mapping is still pretty good and it
191 # won't take a large number of updates to reach a fixed point.
192
193 # We track which strategies use which in the course of
194 # calculating their property value. If A ever uses B in
195 # the course of calculating its value, then whenever the
196 # value of B changes we might need to update the value of
197 # A.
198 listeners: dict[SearchStrategy, set[SearchStrategy]] = defaultdict(set)
199 needs_update = set(mapping) if hit_recursion else None
200
201 def recur2(strat: SearchStrategy) -> Any:
202 def recur_inner(other: SearchStrategy) -> Any:
203 try:
204 return forced_or_cached_value(other)
205 except AttributeError:
206 pass
207 listeners[other].add(strat)
208 result = mapping.get(other, sentinel)
209 if result is sentinel:
210 assert needs_update is not None
211 needs_update.add(other)
212 mapping[other] = default
213 return default
214 return result
215
216 return recur_inner
217
218 count = 0
219 seen = set()
220 while needs_update:
221 count += 1
222 # If we seem to be taking a really long time to stabilize we
223 # start tracking seen values to attempt to detect an infinite
224 # loop. This should be impossible, and most code will never
225 # hit the count, but having an assertion for it means that
226 # testing is easier to debug and we don't just have a hung
227 # test.
228 if count > 50:
229 key = frozenset(mapping.items())
230 assert key not in seen, (key, name)
231 seen.add(key)
232 to_update = needs_update
233 needs_update = set()
234 for strat in to_update:
235 new_value = getattr(strat, calculation)(recur2(strat))
236 if new_value != mapping[strat]:
237 needs_update.update(listeners[strat])
238 mapping[strat] = new_value
239
240 # We now have a complete and accurate calculation of the
241 # property values for everything we have seen in the course of
242 # running this calculation. We simultaneously update all of
243 # them (not just the strategy we started out with).
244 for k, v in mapping.items():
245 setattr(k, cache_key, v)
246 # This used to simply be `getattr(strategy, cache_key)`. That relied on the invariant
247 # that our loop above has set `strategy.cached_* = v` on `strategy` if we've reached
248 # here. However, under threading, this is not necessarily true. If a concurrent thread
249 # sets `strategy.force_* = v` in between the two places we check for `force_*`, we
250 # will not set `strategy.cached_*`.
251 #
252 # There are several places where we might do this. unwrap_strategies sets
253 # force_has_reusable_values = True. our numpy.py's `arrays` strategy also does.
254 #
255 # We guard against this in general by checking the forced and cached values here,
256 # rather than just the cached value.
257 return forced_or_cached_value(strategy)
258
259
260class SearchStrategy(Generic[Ex]):
261 """A ``SearchStrategy`` tells Hypothesis how to generate that kind of input.
262
263 This class is only part of the public API for use in type annotations, so that
264 you can write e.g. ``-> SearchStrategy[Foo]`` for your function which returns
265 ``builds(Foo, ...)``. Do not inherit from or directly instantiate this class.
266 """
267
268 __module__: str = "hypothesis.strategies"
269 LABELS: ClassVar[dict[type, int]] = {}
270 # triggers `assert isinstance(label, int)` under threading when setting this
271 # in init instead of a classvar. I'm not sure why, init should be safe. But
272 # this works so I'm not looking into it further atm.
273 __label: int | UniqueIdentifier | None = None
274
275 def __init__(self) -> None:
276 self.validate_called: dict[int, bool] = {}
277
278 def is_currently_empty(self, data: ConjectureData) -> bool:
279 """
280 Returns whether this strategy is currently empty. Unlike ``empty``,
281 which is computed based on static information and cannot change,
282 ``is_currently_empty`` may change over time based on choices made
283 during the test case.
284
285 This is currently only used for stateful testing, where |Bundle| grows a
286 list of values to choose from over the course of a test case.
287
288 ``data`` will only be used for introspection. No values will be drawn
289 from it in a way that modifies the choice sequence.
290 """
291 return self.is_empty
292
293 @property
294 def is_empty(self) -> Any:
295 # Returns True if this strategy can never draw a value and will always
296 # result in the data being marked invalid.
297 # The fact that this returns False does not guarantee that a valid value
298 # can be drawn - this is not intended to be perfect, and is primarily
299 # intended to be an optimisation for some cases.
300 d = self.__dict__
301 if "cached_is_empty" in d and "force_is_empty" not in d:
302 return d["cached_is_empty"]
303 return recursive_property(self, "is_empty", True)
304
305 # Returns True if values from this strategy can safely be reused without
306 # this causing unexpected behaviour.
307
308 # True if values from this strategy can be implicitly reused (e.g. as
309 # background values in a numpy array) without causing surprising
310 # user-visible behaviour. Should be false for built-in strategies that
311 # produce mutable values, and for strategies that have been mapped/filtered
312 # by arbitrary user-provided functions.
313 @property
314 def has_reusable_values(self) -> Any:
315 return recursive_property(self, "has_reusable_values", True)
316
317 @property
318 def is_cacheable(self) -> Any:
319 """
320 Whether it is safe to hold on to instances of this strategy in a cache.
321 See _STRATEGY_CACHE.
322 """
323 return recursive_property(self, "is_cacheable", True)
324
325 def calc_is_cacheable(self, recur: RecurT) -> bool:
326 return True
327
328 def calc_is_empty(self, recur: RecurT) -> bool:
329 # Note: It is correct and significant that the default return value
330 # from calc_is_empty is False despite the default value for is_empty
331 # being true. The reason for this is that strategies should be treated
332 # as empty absent evidence to the contrary, but most basic strategies
333 # are trivially non-empty and it would be annoying to have to override
334 # this method to show that.
335 return False
336
337 def calc_has_reusable_values(self, recur: RecurT) -> bool:
338 return False
339
340 def example(self) -> Ex: # FIXME
341 """Provide an example of the sort of value that this strategy generates.
342
343 This method is designed for use in a REPL, and will raise an error if
344 called from inside |@given| or a strategy definition. For serious use,
345 see |@composite| or |st.data|.
346 """
347 if getattr(sys, "ps1", None) is None and (
348 # The main module's __spec__ is None when running interactively
349 # or running a source file directly.
350 # See https://docs.python.org/3/reference/import.html#main-spec.
351 sys.modules["__main__"].__spec__ is not None
352 # __spec__ is also None under pytest-xdist. To avoid an unfortunate
353 # missed alarm here, always warn under pytest.
354 or os.environ.get("PYTEST_CURRENT_TEST") is not None
355 ): # pragma: no branch
356 # The other branch *is* covered in cover/test_interactive_example.py;
357 # but as that uses `pexpect` for an interactive session `coverage`
358 # doesn't see it.
359 warnings.warn(
360 "The `.example()` method is good for exploring strategies, but should "
361 "only be used interactively. We recommend using `@given` for tests - "
362 "it performs better, saves and replays failures to avoid flakiness, "
363 f"and reports minimal failing test cases. (strategy: {self!r})",
364 NonInteractiveExampleWarning,
365 stacklevel=2,
366 )
367
368 context = _current_build_context.value
369 if context is not None:
370 if context.data is not None and context.data.depth > 0:
371 raise HypothesisException(
372 "Using example() inside a strategy definition is a bad "
373 "idea. Instead consider using hypothesis.strategies.builds() "
374 "or @hypothesis.strategies.composite to define your strategy."
375 " See https://hypothesis.readthedocs.io/en/latest/reference/"
376 "strategies.html#hypothesis.strategies.builds or "
377 "https://hypothesis.readthedocs.io/en/latest/reference/"
378 "strategies.html#hypothesis.strategies.composite for more "
379 "details."
380 )
381 else:
382 raise HypothesisException(
383 "Using example() inside a test function is a bad "
384 "idea. Instead consider using hypothesis.strategies.data() "
385 "to draw more values during testing. See "
386 "https://hypothesis.readthedocs.io/en/latest/reference/"
387 "strategies.html#hypothesis.strategies.data for more details."
388 )
389
390 try:
391 return self.__examples.pop()
392 except (AttributeError, IndexError):
393 self.__examples: list[Ex] = []
394
395 from hypothesis.core import given
396
397 # Note: this function has a weird name because it might appear in
398 # tracebacks, and we want users to know that they can ignore it.
399 @given(self)
400 @settings(
401 database=None,
402 # generate only a few examples at a time to avoid slow interactivity
403 # for large strategies. The overhead of @given is very small relative
404 # to generation, so a small batch size is fine.
405 max_examples=10,
406 deadline=None,
407 verbosity=Verbosity.quiet,
408 phases=(Phase.generate,),
409 suppress_health_check=list(HealthCheck),
410 )
411 def example_generating_inner_function(
412 ex: Ex, # type: ignore # mypy is overzealous in preventing covariant params
413 ) -> None:
414 self.__examples.append(ex)
415
416 example_generating_inner_function()
417 shuffle(self.__examples)
418 return self.__examples.pop()
419
420 def map(self, pack: Callable[[Ex], T]) -> "SearchStrategy[T]":
421 """Returns a new strategy which generates a value from this one, and
422 then returns ``pack(value)``. For example, ``integers().map(str)``
423 could generate ``str(5)`` == ``"5"``.
424 """
425 if is_identity_function(pack):
426 return self # type: ignore # Mypy has no way to know that `Ex == T`
427 return MappedStrategy(self, pack=pack)
428
429 def flatmap(
430 self, expand: Callable[[Ex], "SearchStrategy[T]"]
431 ) -> "SearchStrategy[T]": # FIXME
432 """Old syntax for a special case of |@composite|:
433
434 .. code-block:: python
435
436 @st.composite
437 def flatmap_like(draw, base_strategy, expand):
438 value = draw(base_strategy)
439 new_strategy = expand(value)
440 return draw(new_strategy)
441
442 We find that the greater readability of |@composite| usually outweighs
443 the verbosity, with a few exceptions for simple cases or recipes like
444 ``from_type(type).flatmap(from_type)`` ("pick a type, get a strategy for
445 any instance of that type, and then generate one of those").
446 """
447 from hypothesis.strategies._internal.flatmapped import FlatMapStrategy
448
449 return FlatMapStrategy(self, expand=expand)
450
451 # Note that we previously had condition extracted to a type alias as
452 # PredicateT. However, that was only useful when not specifying a relationship
453 # between the generic Ts and some other function param / return value.
454 # If we do want to - like here, where we want to say that the Ex arg to condition
455 # is of the same type as the strategy's Ex - then you need to write out the
456 # entire Callable[[Ex], Any] expression rather than use a type alias.
457 # TypeAlias is *not* simply a macro that inserts the text. TypeAlias will not
458 # reference the local TypeVar context.
459 @overload
460 def filter(
461 self, condition: Callable[[Ex], TypeGuard[T]]
462 ) -> "SearchStrategy[T]": ...
463 @overload
464 def filter(self, condition: Callable[[Ex], Any]) -> "SearchStrategy[Ex]": ...
465 def filter(self, condition):
466 """Returns a new strategy that generates values from this strategy
467 which satisfy the provided condition.
468
469 Note that if the condition is too hard to satisfy this might result
470 in your tests failing with an Unsatisfiable exception.
471 A basic version of the filtering logic would look something like:
472
473 .. code-block:: python
474
475 @st.composite
476 def filter_like(draw, strategy, condition):
477 for _ in range(3):
478 value = draw(strategy)
479 if condition(value):
480 return value
481 assume(False)
482 """
483 return FilteredStrategy(
484 self,
485 conditions=(condition,),
486 condition_locations=(current_filter_call_site(),),
487 )
488
489 @property
490 def branches(self) -> Sequence["SearchStrategy[Ex]"]:
491 return [self]
492
493 def __or__(self, other: "SearchStrategy[T]") -> "SearchStrategy[Ex | T]":
494 """Return a strategy which produces values by randomly drawing from one
495 of this strategy or the other strategy.
496
497 This method is part of the public API.
498 """
499 if not isinstance(other, SearchStrategy):
500 raise ValueError(f"Cannot | a SearchStrategy with {other!r}")
501
502 # Unwrap explicitly or'd strategies. This turns the
503 # common case of e.g. st.integers() | st.integers() | st.integers() from
504 #
505 # one_of(one_of(integers(), integers()), integers())
506 #
507 # into
508 #
509 # one_of(integers(), integers(), integers())
510 #
511 # This is purely an aesthetic unwrapping, for e.g. reprs. In practice
512 # we use .branches / .element_strategies to get the list of possible
513 # strategies, so this unwrapping is *not* necessary for correctness.
514 strategies: list[SearchStrategy] = []
515 strategies.extend(
516 self.original_strategies if isinstance(self, OneOfStrategy) else [self]
517 )
518 strategies.extend(
519 other.original_strategies if isinstance(other, OneOfStrategy) else [other]
520 )
521 return OneOfStrategy(strategies)
522
523 def __bool__(self) -> bool:
524 warnings.warn(
525 f"bool({self!r}) is always True, did you mean to draw a value?",
526 HypothesisWarning,
527 stacklevel=2,
528 )
529 return True
530
531 def validate(self) -> None:
532 """Throw an exception if the strategy is not valid.
533
534 Strategies should implement ``do_validate``, which is called by this
535 method. They should not override ``validate``.
536
537 This can happen due to invalid arguments, or lazy construction.
538 """
539 thread_id = threading.get_ident()
540 if self.validate_called.get(thread_id, False):
541 return
542 # we need to set validate_called before calling do_validate, for
543 # recursive / deferred strategies. But if a thread switches after
544 # validate_called but before do_validate, we might have a strategy
545 # which does weird things like drawing when do_validate would error but
546 # its params are technically valid (e.g. a param was passed as 1.0
547 # instead of 1) and get into weird internal states.
548 #
549 # There are two ways to fix this.
550 # (1) The first is a per-strategy lock around do_validate. Even though we
551 # expect near-zero lock contention, this still adds the lock overhead.
552 # (2) The second is allowing concurrent .validate calls. Since validation
553 # is (assumed to be) deterministic, both threads will produce the same
554 # end state, so the validation order or race conditions does not matter.
555 #
556 # In order to avoid the lock overhead of (1), we use (2) here. See also
557 # discussion in https://github.com/HypothesisWorks/hypothesis/pull/4473.
558 try:
559 self.validate_called[thread_id] = True
560 self.do_validate()
561 self.is_empty
562 self.has_reusable_values
563 except Exception:
564 self.validate_called[thread_id] = False
565 raise
566
567 @property
568 def class_label(self) -> int:
569 cls = self.__class__
570 try:
571 return cls.LABELS[cls]
572 except KeyError:
573 pass
574 result = calc_label_from_cls(cls)
575 cls.LABELS[cls] = result
576 return result
577
578 @property
579 def label(self) -> int:
580 if isinstance((label := self.__label), int):
581 # avoid locking if we've already completely computed the label.
582 return label
583
584 with label_lock:
585 if self.__label is calculating:
586 return 0
587 self.__label = calculating
588 self.__label = self.calc_label()
589 return self.__label
590
591 def calc_label(self) -> int:
592 return self.class_label
593
594 def do_validate(self) -> None:
595 pass
596
597 def do_draw(self, data: ConjectureData) -> Ex:
598 raise NotImplementedError(f"{type(self).__name__}.do_draw")
599
600 def _invert(self, value: Any) -> tuple[ChoiceT, ...]:
601 """
602 Return a choice sequence ``choices`` such that we expect
603
604 data = ConjectureData.for_choices(choices)
605 drawn = data.draw(self)
606
607 would produce ``drawn == value``. Inversion is best-effort: it should
608 satisfy this property with high probability, but callers must replay
609 the returned choices and check the outcome rather than rely on it.
610
611 Raises CannotInvert if we cannot construct such a choice sequence,
612 whether because ``value`` is not produced by this strategy or because
613 inversion isn't implemented for it.
614 """
615 raise CannotInvert(f"{type(self).__name__} does not support inversion")
616
617
618def _is_hashable(value: object) -> tuple[bool, int | None]:
619 # hashing can be expensive; return the hash value if we compute it, so that
620 # callers don't have to recompute.
621 try:
622 return (True, hash(value))
623 except TypeError:
624 return (False, None)
625
626
627def is_hashable(value: object) -> bool:
628 return _is_hashable(value)[0]
629
630
631class SampledFromStrategy(SearchStrategy[Ex]):
632 """A strategy which samples from a set of elements. This is essentially
633 equivalent to using a OneOfStrategy over Just strategies but may be more
634 efficient and convenient.
635 """
636
637 _MAX_FILTER_CALLS: ClassVar[int] = 10_000
638
639 def __init__(
640 self,
641 elements: Sequence[Ex],
642 *,
643 force_repr: str | None = None,
644 force_repr_braces: tuple[str, str] | None = None,
645 # (name, function, location of the .filter()/.map() call, if known)
646 transformations: tuple[
647 tuple[Literal["filter", "map"], Callable[[Ex], Any], str | None],
648 ...,
649 ] = (),
650 ):
651 super().__init__()
652 self.elements = cu.check_sample(elements, "sampled_from")
653 assert self.elements
654 self.force_repr = force_repr
655 self.force_repr_braces = force_repr_braces
656 self._transformations = transformations
657
658 self._cached_repr: str | None = None
659
660 def map(self, pack: Callable[[Ex], T]) -> SearchStrategy[T]:
661 s = type(self)(
662 self.elements,
663 force_repr=self.force_repr,
664 force_repr_braces=self.force_repr_braces,
665 transformations=(*self._transformations, ("map", pack, None)),
666 )
667 # guaranteed by the ("map", pack) transformation
668 return cast(SearchStrategy[T], s)
669
670 @overload
671 def filter(
672 self, condition: Callable[[Ex], TypeGuard[T]]
673 ) -> "SearchStrategy[T]": ...
674 @overload
675 def filter(self, condition: Callable[[Ex], Any]) -> "SearchStrategy[Ex]": ...
676 def filter(self, condition):
677 return type(self)(
678 self.elements,
679 force_repr=self.force_repr,
680 force_repr_braces=self.force_repr_braces,
681 transformations=(
682 *self._transformations,
683 ("filter", condition, current_filter_call_site()),
684 ),
685 )
686
687 def __repr__(self):
688 if self._cached_repr is None:
689 rep = get_pretty_function_description
690 elements_s = (
691 ", ".join(rep(v) for v in self.elements[:512]) + ", ..."
692 if len(self.elements) > 512
693 else ", ".join(rep(v) for v in self.elements)
694 )
695 braces = self.force_repr_braces or ("(", ")")
696 instance_s = (
697 self.force_repr or f"sampled_from({braces[0]}{elements_s}{braces[1]})"
698 )
699 transforms_s = "".join(
700 f".{name}({get_pretty_function_description(f)})"
701 for name, f, _ in self._transformations
702 )
703 repr_s = instance_s + transforms_s
704 self._cached_repr = repr_s
705 return self._cached_repr
706
707 def calc_label(self) -> int:
708 # strategy.label is effectively an under-approximation of structural
709 # equality (i.e., some strategies may have the same label when they are not
710 # structurally identical). More importantly for calculating the
711 # SampledFromStrategy label, we might have hash(s1) != hash(s2) even
712 # when s1 and s2 are structurally identical. For instance:
713 #
714 # s1 = st.sampled_from([st.none()])
715 # s2 = st.sampled_from([st.none()])
716 # assert hash(s1) != hash(s2)
717 #
718 # (see also test cases in test_labels.py).
719 #
720 # We therefore use the labels of any component strategies when calculating
721 # our label, and only use the hash if it is not a strategy.
722 #
723 # That's the ideal, anyway. In reality the logic is more complicated than
724 # necessary in order to be efficient in the presence of (very) large sequences:
725 # * add an unabashed special case for range, to avoid iteration over an
726 # enormous range when we know it is entirely integers.
727 # * if there is at least one strategy in self.elements, use strategy label,
728 # and the element hash otherwise.
729 # * if there are no strategies in self.elements, take the hash of the
730 # entire sequence. This prevents worst-case performance of hashing each
731 # element when a hash of the entire sequence would have sufficed.
732 #
733 # The worst case performance of this scheme is
734 # itertools.chain(range(2**100), [st.none()]), where it degrades to
735 # hashing every int in the range.
736 elements_is_hashable, hash_value = _is_hashable(self.elements)
737 if isinstance(self.elements, range) or (
738 elements_is_hashable
739 and not any(isinstance(e, SearchStrategy) for e in self.elements)
740 ):
741 return combine_labels(
742 self.class_label, calc_label_from_name(str(hash_value))
743 )
744
745 labels = [self.class_label]
746 for element in self.elements:
747 if not is_hashable(element):
748 continue
749
750 labels.append(
751 element.label
752 if isinstance(element, SearchStrategy)
753 else calc_label_from_hash(element)
754 )
755
756 return combine_labels(*labels)
757
758 def calc_has_reusable_values(self, recur: RecurT) -> bool:
759 # Because our custom .map/.filter implementations skip the normal
760 # wrapper strategies (which would automatically return False for us),
761 # we need to manually return False here if any transformations have
762 # been applied.
763 return not self._transformations
764
765 def calc_is_cacheable(self, recur: RecurT) -> bool:
766 return is_hashable(self.elements)
767
768 def _transform(
769 self,
770 # https://github.com/python/mypy/issues/7049, we're not writing `element`
771 # anywhere in the class so this is still type-safe. mypy is being more
772 # conservative than necessary
773 element: Ex, # type: ignore
774 *,
775 # None for _invert, which has no ConjectureData
776 data: ConjectureData | None,
777 ) -> Ex | UniqueIdentifier:
778 # Used in UniqueSampledListStrategy
779 for name, f, location in self._transformations:
780 if name == "map":
781 result = f(element)
782 if build_context := _current_build_context.value:
783 build_context.record_call(result, f, args=[element], kwargs={})
784 element = result
785 else:
786 assert name == "filter"
787 if not f(element):
788 if data is not None:
789 data._last_rejected_filter = (f, location)
790 return filter_not_satisfied
791 return element
792
793 def do_draw(self, data: ConjectureData) -> Ex:
794 result = self.do_filtered_draw(data)
795 if isinstance(result, SearchStrategy) and all(
796 isinstance(x, SearchStrategy) for x in self.elements
797 ):
798 data._sampled_from_all_strategies_elements_message = (
799 (
800 "sampled_from was given a collection of strategies: "
801 "{!r}. Was one_of intended?"
802 ),
803 self.elements,
804 )
805 if result is filter_not_satisfied:
806 # do_filtered_draw records data._last_rejected_filter.
807 data.mark_invalid(
808 f"Aborted test because unable to satisfy {self!r}",
809 location=data.last_rejected_filter_location(),
810 )
811 assert not isinstance(result, UniqueIdentifier)
812 return result
813
814 def get_element(self, i: int, data: ConjectureData) -> Ex | UniqueIdentifier:
815 return self._transform(self.elements[i], data=data)
816
817 def _invert(self, value: Any) -> tuple[ChoiceT, ...]:
818 # The smallest index whose (possibly transformed) element equals value.
819 # _transform might depend on external state and give us a wrong answer
820 # here; that's fine, since _invert is allowed to be fallible.
821 try:
822 for i, element in enumerate(self.elements):
823 if equal_values(self._transform(element, data=None), value):
824 return (i,)
825 except Exception:
826 # `self._transform` may include arbitrary user code (map/filter functions)
827 raise CannotInvert(f"transforming elements of {self!r} errored") from None
828 raise CannotInvert(f"{value!r} is not produced by {self!r}")
829
830 def do_filtered_draw(self, data: ConjectureData) -> Ex | UniqueIdentifier:
831 # Set of indices that have been tried so far, so that we never test
832 # the same element twice during a draw.
833 known_bad_indices: set[int] = set()
834
835 # Start with ordinary rejection sampling. It's fast if it works, and
836 # if it doesn't work then it was only a small amount of overhead.
837 for _ in range(3):
838 i = data.draw_integer(0, len(self.elements) - 1)
839 if i not in known_bad_indices:
840 element = self.get_element(i, data)
841 if element is not filter_not_satisfied:
842 return element
843 if not known_bad_indices:
844 data.events[f"Retried draw from {self!r} to satisfy filter"] = ""
845 known_bad_indices.add(i)
846
847 # If we've tried all the possible elements, give up now.
848 max_good_indices = len(self.elements) - len(known_bad_indices)
849 if not max_good_indices:
850 return filter_not_satisfied
851
852 # Impose an arbitrary cutoff to prevent us from wasting too much time
853 # on very large element lists.
854 max_good_indices = min(max_good_indices, self._MAX_FILTER_CALLS - 3)
855
856 # Before building the list of allowed indices, speculatively choose
857 # one of them. We don't yet know how many allowed indices there will be,
858 # so this choice might be out-of-bounds, but that's OK.
859 speculative_index = data.draw_integer(0, max_good_indices - 1)
860
861 # Calculate the indices of allowed values, so that we can choose one
862 # of them at random. But if we encounter the speculatively-chosen one,
863 # just use that and return immediately. Note that we also track the
864 # allowed elements, in case of .map(some_stateful_function)
865 allowed: list[tuple[int, Ex]] = []
866 for i in range(min(len(self.elements), self._MAX_FILTER_CALLS - 3)):
867 if i not in known_bad_indices:
868 element = self.get_element(i, data)
869 if element is not filter_not_satisfied:
870 assert not isinstance(element, UniqueIdentifier)
871 allowed.append((i, element))
872 if len(allowed) > speculative_index:
873 # Early-exit case: We reached the speculative index, so
874 # we just return the corresponding element.
875 data.draw_integer(0, len(self.elements) - 1, forced=i)
876 return element
877
878 # The speculative index didn't work out, but at this point we've built
879 # and can choose from the complete list of allowed indices and elements.
880 if allowed:
881 i, element = data.choice(allowed)
882 data.draw_integer(0, len(self.elements) - 1, forced=i)
883 return element
884 # If there are no allowed indices, the filter couldn't be satisfied.
885 return filter_not_satisfied
886
887
888# The ids of OneOfStrategy instances an _invert call is currently walking
889# through, per thread. See OneOfStrategy._invert.
890_inverting_one_ofs = threading.local()
891
892
893class OneOfStrategy(SearchStrategy[Ex]):
894 """Implements a union of strategies. Given a number of strategies this
895 generates values which could have come from any of them.
896
897 The conditional distribution draws uniformly at random from some
898 non-empty subset of these strategies and then draws from the
899 conditional distribution of that strategy.
900 """
901
902 def __init__(self, strategies: Sequence[SearchStrategy[Ex]]):
903 super().__init__()
904 self.original_strategies = tuple(strategies)
905 self.__element_strategies: Sequence[SearchStrategy[Ex]] | None = None
906 self.__in_branches = False
907 self.__branch_sampler: SampledFromStrategy | Literal[False] | None = None
908 self._branches_lock = RLock()
909
910 def calc_is_empty(self, recur: RecurT) -> bool:
911 return all(recur(e) for e in self.original_strategies)
912
913 def calc_has_reusable_values(self, recur: RecurT) -> bool:
914 return all(recur(e) for e in self.original_strategies)
915
916 def calc_is_cacheable(self, recur: RecurT) -> bool:
917 return all(recur(e) for e in self.original_strategies)
918
919 @property
920 def element_strategies(self) -> Sequence[SearchStrategy[Ex]]:
921 if self.__element_strategies is None:
922 # While strategies are hashable, they use object.__hash__ and are
923 # therefore distinguished only by identity.
924 #
925 # In principle we could "just" define a __hash__ method
926 # (and __eq__, but that's easy in terms of type() and hash())
927 # to make this more powerful, but this is harder than it sounds:
928 #
929 # 1. Strategies are often distinguished by non-hashable attributes,
930 # or by attributes that have the same hash value ("^.+" / b"^.+").
931 # 2. LazyStrategy: can't reify the wrapped strategy without breaking
932 # laziness, so there's a hash each for the lazy and the nonlazy.
933 #
934 # Having made several attempts, the minor benefits of making strategies
935 # hashable are simply not worth the engineering effort it would take.
936 # See also issues #2291 and #2327.
937 seen: set[SearchStrategy] = {self}
938 strategies: list[SearchStrategy] = []
939 for arg in self.original_strategies:
940 check_strategy(arg)
941 if not arg.is_empty:
942 for s in arg.branches:
943 if s not in seen and not s.is_empty:
944 seen.add(s)
945 strategies.append(s)
946 self.__element_strategies = strategies
947 return self.__element_strategies
948
949 def calc_label(self) -> int:
950 return combine_labels(
951 self.class_label, *(p.label for p in self.original_strategies)
952 )
953
954 def do_draw(self, data: ConjectureData) -> Ex:
955 strategies = self.element_strategies
956 if len(strategies) == 1:
957 # optimization: skip constructing SampledFromStrategy if we only have one
958 # strategy. This can happen for eg `st.integers() | st.nothing()`.
959 return data.draw(strategies[0])
960
961 if self.__branch_sampler is None:
962 # Only strategies which override is_currently_empty (e.g. stateful
963 # Bundles) can become empty dynamically - otherwise element_strategies
964 # has already excluded every empty branch, so we can skip the filter
965 # and reuse a single sampler rather than building one per draw.
966 if any(
967 type(s).is_currently_empty is not SearchStrategy.is_currently_empty
968 for s in strategies
969 ):
970 self.__branch_sampler = False
971 else:
972 self.__branch_sampler = SampledFromStrategy(strategies)
973 if self.__branch_sampler is not False:
974 return data.draw(data.draw(self.__branch_sampler))
975
976 strategy = data.draw(
977 SampledFromStrategy(strategies).filter(
978 lambda s: not s.is_currently_empty(data)
979 )
980 )
981 return data.draw(strategy)
982
983 def _invert(self, value: Any) -> tuple[ChoiceT, ...]:
984 # do_draw first draws a branch index, then draws from that branch.
985 # Return the simplest candidate across all branches: the shortest
986 # encoding, breaking ties by the lower branch index - matching the
987 # shrinker's ordering over choice sequences.
988 #
989 # Self-referential strategies (e.g. via st.deferred) can route a
990 # branch's inversion of the same value back to this one_of, so guard
991 # against re-entry rather than recursing forever. Unlike a structural
992 # recursion into a subvalue - which terminates because values are
993 # finite - a same-value cycle can make no progress.
994 if len(self.element_strategies) == 1:
995 return self.element_strategies[0]._invert(value)
996
997 active = getattr(_inverting_one_ofs, "ids", None)
998 if active is None:
999 active = _inverting_one_ofs.ids = set()
1000 if id(self) in active:
1001 raise CannotInvert(f"recursive inversion of {self!r}")
1002 active.add(id(self))
1003 try:
1004 best: tuple[ChoiceT, ...] | None = None
1005 for i, branch in enumerate(self.element_strategies):
1006 try:
1007 candidate = (i, *branch._invert(value))
1008 except CannotInvert:
1009 continue
1010 if best is None or len(candidate) < len(best):
1011 best = candidate
1012 if len(best) <= 2:
1013 # A selector plus at most one choice: no later branch is
1014 # worth trying. (A zero-choice just()-like branch could
1015 # still encode in one choice, but we accept two rather
1016 # than scanning - possibly recursively - for it.)
1017 break
1018 finally:
1019 active.discard(id(self))
1020 if best is None:
1021 raise CannotInvert(f"{value!r} is not produced by any branch of {self!r}")
1022 return best
1023
1024 def __repr__(self) -> str:
1025 return "one_of({})".format(", ".join(map(repr, self.original_strategies)))
1026
1027 def do_validate(self) -> None:
1028 for e in self.element_strategies:
1029 e.validate()
1030
1031 @property
1032 def branches(self) -> Sequence[SearchStrategy[Ex]]:
1033 if self.__element_strategies is not None:
1034 # common fast path which avoids the lock
1035 return self.element_strategies
1036
1037 with self._branches_lock:
1038 if not self.__in_branches:
1039 try:
1040 self.__in_branches = True
1041 return self.element_strategies
1042 finally:
1043 self.__in_branches = False
1044 else:
1045 return [self]
1046
1047 @overload
1048 def filter(
1049 self, condition: Callable[[Ex], TypeGuard[T]]
1050 ) -> "SearchStrategy[T]": ...
1051 @overload
1052 def filter(self, condition: Callable[[Ex], Any]) -> "SearchStrategy[Ex]": ...
1053 def filter(self, condition):
1054 return FilteredStrategy(
1055 OneOfStrategy([s.filter(condition) for s in self.original_strategies]),
1056 conditions=(),
1057 )
1058
1059
1060@overload
1061def one_of(
1062 __args: Sequence[SearchStrategy[Ex]],
1063) -> SearchStrategy[Ex]: ...
1064
1065
1066@overload
1067def one_of(__a1: SearchStrategy[Ex]) -> SearchStrategy[Ex]: ...
1068
1069
1070@overload
1071def one_of(
1072 __a1: SearchStrategy[T1], __a2: SearchStrategy[T]
1073) -> SearchStrategy[T1 | T]: ...
1074
1075
1076@overload
1077def one_of(
1078 __a1: SearchStrategy[T1], __a2: SearchStrategy[T], __a3: SearchStrategy[T3]
1079) -> SearchStrategy[T1 | T | T3]: ...
1080
1081
1082@overload
1083def one_of(
1084 __a1: SearchStrategy[T1],
1085 __a2: SearchStrategy[T],
1086 __a3: SearchStrategy[T3],
1087 __a4: SearchStrategy[T4],
1088) -> SearchStrategy[T1 | T | T3 | T4]: ...
1089
1090
1091@overload
1092def one_of(
1093 __a1: SearchStrategy[T1],
1094 __a2: SearchStrategy[T],
1095 __a3: SearchStrategy[T3],
1096 __a4: SearchStrategy[T4],
1097 __a5: SearchStrategy[T5],
1098) -> SearchStrategy[T1 | T | T3 | T4 | T5]: ...
1099
1100
1101@overload
1102def one_of(*args: SearchStrategy[Any]) -> SearchStrategy[Any]: ...
1103
1104
1105@defines_strategy(eager=True)
1106def one_of(
1107 *args: Sequence[SearchStrategy[Any]] | SearchStrategy[Any],
1108) -> SearchStrategy[Any]:
1109 # Mypy workaround alert: Any is too loose above; the return parameter
1110 # should be the union of the input parameters. Unfortunately, Mypy <=0.600
1111 # raises errors due to incompatible inputs instead. See #1270 for links.
1112 # v0.610 doesn't error; it gets inference wrong for 2+ arguments instead.
1113 """Return a strategy which generates values from any of the argument
1114 strategies.
1115
1116 This may be called with one iterable argument instead of multiple
1117 strategy arguments, in which case ``one_of(x)`` and ``one_of(*x)`` are
1118 equivalent.
1119
1120 Examples from this strategy will generally shrink to ones that come from
1121 strategies earlier in the list, then shrink according to behaviour of the
1122 strategy that produced them. In order to get good shrinking behaviour,
1123 try to put simpler strategies first. e.g. ``one_of(none(), text())`` is
1124 better than ``one_of(text(), none())``.
1125
1126 This is especially important when using recursive strategies. e.g.
1127 ``x = st.deferred(lambda: st.none() | st.tuples(x, x))`` will shrink well,
1128 but ``x = st.deferred(lambda: st.tuples(x, x) | st.none())`` will shrink
1129 very badly indeed.
1130 """
1131 if len(args) == 1 and not isinstance(args[0], SearchStrategy):
1132 try:
1133 args = tuple(args[0])
1134 except TypeError:
1135 pass
1136 if len(args) == 1 and isinstance(args[0], SearchStrategy):
1137 # This special-case means that we can one_of over lists of any size
1138 # without incurring any performance overhead when there is only one
1139 # strategy, and keeps our reprs simple.
1140 return args[0]
1141 if args and not any(isinstance(a, SearchStrategy) for a in args):
1142 # And this special case is to give a more-specific error message if it
1143 # seems that the user has confused `one_of()` for `sampled_from()`;
1144 # the remaining validation is left to OneOfStrategy. See PR #2627.
1145 raise InvalidArgument(
1146 f"Did you mean st.sampled_from({list(args)!r})? st.one_of() is used "
1147 "to combine strategies, but all of the arguments were of other types."
1148 )
1149 # we've handled the case where args is a one-element sequence [(s1, s2, ...)]
1150 # above, so we can assume it's an actual sequence of strategies.
1151 return OneOfStrategy(cast(Sequence[SearchStrategy], args))
1152
1153
1154class MappedStrategy(SearchStrategy[MappedTo], Generic[MappedFrom, MappedTo]):
1155 """A strategy which is defined purely by conversion to and from another
1156 strategy.
1157
1158 Its parameter and distribution come from that other strategy.
1159 """
1160
1161 def __init__(
1162 self,
1163 strategy: SearchStrategy[MappedFrom],
1164 pack: Callable[[MappedFrom], MappedTo],
1165 ) -> None:
1166 super().__init__()
1167 self.mapped_strategy = strategy
1168 self.pack = pack
1169
1170 def calc_is_empty(self, recur: RecurT) -> bool:
1171 return recur(self.mapped_strategy)
1172
1173 def calc_is_cacheable(self, recur: RecurT) -> bool:
1174 return recur(self.mapped_strategy)
1175
1176 def __repr__(self) -> str:
1177 if not hasattr(self, "_cached_repr"):
1178 self._cached_repr = f"{self.mapped_strategy!r}.map({get_pretty_function_description(self.pack)})"
1179 return self._cached_repr
1180
1181 def do_validate(self) -> None:
1182 self.mapped_strategy.validate()
1183
1184 def do_draw(self, data: ConjectureData) -> MappedTo:
1185 with warnings.catch_warnings():
1186 if isinstance(self.pack, type) and issubclass(
1187 self.pack, (abc.Mapping, abc.Set)
1188 ):
1189 warnings.simplefilter("ignore", BytesWarning)
1190 for _ in range(3):
1191 data.start_span(MAPPED_SEARCH_STRATEGY_DO_DRAW_LABEL)
1192 discard = False
1193 try:
1194 x = data.draw(self.mapped_strategy)
1195 result = self.pack(x)
1196 except UnsatisfiedAssumption as err:
1197 # we want to preserve the err.location of the actual exception here if we
1198 # re-throw it outside of the loop at the end. The alternative is a
1199 # bare `raise UnsatisfiedAssumption` outside of the loop, which would
1200 # drop the real err.location for observability.
1201 #
1202 # If we catch multiple exceptions here, we'll just report the location
1203 # of the last one, which is a reasonable tradeoff for a singleton field.
1204 failed_assumption = err
1205 discard = True
1206 continue
1207 finally:
1208 data.stop_span(discard=discard)
1209 current_build_context().record_call(
1210 result, self.pack, args=[x], kwargs={}
1211 )
1212 return result
1213 raise failed_assumption
1214
1215 def _invert(self, value: Any) -> tuple[ChoiceT, ...]:
1216 # map() is not invertible in general, but a dict-like pack - e.g. the
1217 # dict_class which st.dictionaries maps over a unique list of
1218 # (key, value) tuples - inverts as its list of items.
1219 if (
1220 isinstance(self.pack, type)
1221 and issubclass(self.pack, abc.Mapping)
1222 and isinstance(value, self.pack)
1223 ):
1224 return self.mapped_strategy._invert(list(value.items()))
1225 raise CannotInvert(f"cannot invert {self!r} (value={value!r})")
1226
1227 @property
1228 def branches(self) -> Sequence[SearchStrategy[MappedTo]]:
1229 return [
1230 MappedStrategy(strategy, pack=self.pack)
1231 for strategy in self.mapped_strategy.branches
1232 ]
1233
1234 @overload
1235 def filter(
1236 self, condition: Callable[[MappedTo], TypeGuard[T]]
1237 ) -> "SearchStrategy[T]": ...
1238 @overload
1239 def filter(
1240 self, condition: Callable[[MappedTo], Any]
1241 ) -> "SearchStrategy[MappedTo]": ...
1242 def filter(self, condition):
1243 # Includes a special case so that we can rewrite filters on collection
1244 # lengths, when most collections are `st.lists(...).map(the_type)`.
1245 ListStrategy = _list_strategy_type()
1246 if not isinstance(self.mapped_strategy, ListStrategy) or not (
1247 (isinstance(self.pack, type) and issubclass(self.pack, abc.Collection))
1248 or self.pack in _collection_ish_functions()
1249 ):
1250 return super().filter(condition)
1251
1252 # Check whether our inner list strategy can rewrite this filter condition.
1253 # If not, discard the result and _only_ apply a new outer filter.
1254 new = ListStrategy.filter(self.mapped_strategy, condition)
1255 if getattr(new, "filtered_strategy", None) is self.mapped_strategy:
1256 return super().filter(condition) # didn't rewrite
1257
1258 # Apply a new outer filter even though we rewrote the inner strategy,
1259 # because some collections can change the list length (dict, set, etc).
1260 return FilteredStrategy(
1261 type(self)(new, self.pack),
1262 conditions=(condition,),
1263 condition_locations=(current_filter_call_site(),),
1264 )
1265
1266
1267@lru_cache
1268def _list_strategy_type() -> Any:
1269 from hypothesis.strategies._internal.collections import ListStrategy
1270
1271 return ListStrategy
1272
1273
1274def _collection_ish_functions() -> Sequence[Any]:
1275 funcs = [sorted]
1276 if np := sys.modules.get("numpy"):
1277 # c.f. https://numpy.org/doc/stable/reference/routines.array-creation.html
1278 # Probably only `np.array` and `np.asarray` will be used in practice,
1279 # but why should that stop us when we've already gone this far?
1280 funcs += [
1281 np.empty_like,
1282 np.eye,
1283 np.identity,
1284 np.ones_like,
1285 np.zeros_like,
1286 np.array,
1287 np.asarray,
1288 np.asanyarray,
1289 np.ascontiguousarray,
1290 np.asmatrix,
1291 np.copy,
1292 np.rec.array,
1293 np.rec.fromarrays,
1294 np.rec.fromrecords,
1295 np.diag,
1296 # bonus undocumented functions from tab-completion:
1297 np.asarray_chkfinite,
1298 np.asfortranarray,
1299 ]
1300
1301 return funcs
1302
1303
1304filter_not_satisfied = UniqueIdentifier("filter not satisfied")
1305
1306
1307class FilteredStrategy(SearchStrategy[Ex]):
1308 def __init__(
1309 self,
1310 strategy: SearchStrategy[Ex],
1311 conditions: tuple[Callable[[Ex], Any], ...],
1312 condition_locations: tuple[str | None, ...] | None = None,
1313 ):
1314 super().__init__()
1315 # Where each condition's .filter() call was made, for observability
1316 if condition_locations is None:
1317 condition_locations = (None,) * len(conditions)
1318 assert len(condition_locations) == len(conditions)
1319 if isinstance(strategy, FilteredStrategy):
1320 # Flatten chained filters into a single filter with multiple conditions.
1321 self.flat_conditions: tuple[Callable[[Ex], Any], ...] = (
1322 strategy.flat_conditions + conditions
1323 )
1324 self.condition_locations: tuple[str | None, ...] = (
1325 strategy.condition_locations + condition_locations
1326 )
1327 self.filtered_strategy: SearchStrategy[Ex] = strategy.filtered_strategy
1328 else:
1329 self.flat_conditions = conditions
1330 self.condition_locations = condition_locations
1331 self.filtered_strategy = strategy
1332
1333 assert isinstance(self.flat_conditions, tuple)
1334 assert not isinstance(self.filtered_strategy, FilteredStrategy)
1335
1336 self.__condition: Callable[[Ex], Any] | None = None
1337
1338 def calc_is_empty(self, recur: RecurT) -> bool:
1339 return recur(self.filtered_strategy)
1340
1341 def calc_is_cacheable(self, recur: RecurT) -> bool:
1342 return recur(self.filtered_strategy)
1343
1344 def __repr__(self) -> str:
1345 if not hasattr(self, "_cached_repr"):
1346 self._cached_repr = "{!r}{}".format(
1347 self.filtered_strategy,
1348 "".join(
1349 f".filter({get_pretty_function_description(cond)})"
1350 for cond in self.flat_conditions
1351 ),
1352 )
1353 return self._cached_repr
1354
1355 def do_validate(self) -> None:
1356 # Start by validating our inner filtered_strategy. If this was a LazyStrategy,
1357 # validation also reifies it so that subsequent calls to e.g. `.filter()` will
1358 # be passed through.
1359 self.filtered_strategy.validate()
1360 # So now we have a reified inner strategy, we'll replay all our saved
1361 # predicates in case some or all of them can be rewritten. Note that this
1362 # replaces the `fresh` strategy too!
1363 fresh = self.filtered_strategy
1364 for cond, location in zip(
1365 self.flat_conditions, self.condition_locations, strict=True
1366 ):
1367 with _filter_location_override.with_value(location):
1368 fresh = fresh.filter(cond)
1369 if isinstance(fresh, FilteredStrategy):
1370 # In this case we have at least some non-rewritten filter predicates,
1371 # so we just re-initialize the strategy.
1372 FilteredStrategy.__init__(
1373 self,
1374 fresh.filtered_strategy,
1375 fresh.flat_conditions,
1376 fresh.condition_locations,
1377 )
1378 else:
1379 # But if *all* the predicates were rewritten... well, do_validate() is
1380 # an in-place method so we still just re-initialize the strategy!
1381 FilteredStrategy.__init__(self, fresh, ())
1382
1383 @overload
1384 def filter(
1385 self, condition: Callable[[Ex], TypeGuard[T]]
1386 ) -> "FilteredStrategy[T]": ...
1387 @overload
1388 def filter(self, condition: Callable[[Ex], Any]) -> "FilteredStrategy[Ex]": ...
1389 def filter(self, condition):
1390 # If we can, it's more efficient to rewrite our strategy to satisfy the
1391 # condition. We therefore exploit the fact that the order of predicates
1392 # doesn't matter (`f(x) and g(x) == g(x) and f(x)`) by attempting to apply
1393 # condition directly to our filtered strategy as the inner-most filter.
1394 out = self.filtered_strategy.filter(condition)
1395 # If it couldn't be rewritten, we'll get a new FilteredStrategy - and then
1396 # combine the conditions of each in our expected newest=last order.
1397 if isinstance(out, FilteredStrategy):
1398 return FilteredStrategy(
1399 out.filtered_strategy,
1400 self.flat_conditions + out.flat_conditions,
1401 self.condition_locations + out.condition_locations,
1402 )
1403 # But if it *could* be rewritten, we can return the more efficient form!
1404 return FilteredStrategy(out, self.flat_conditions, self.condition_locations)
1405
1406 @property
1407 def condition(self) -> Callable[[Ex], Any]:
1408 # We write this defensively to avoid any threading race conditions
1409 # with our manual FilteredStrategy.__init__ for filter-rewriting.
1410 # See https://github.com/HypothesisWorks/hypothesis/pull/4522.
1411 if (condition := self.__condition) is not None:
1412 return condition
1413
1414 if len(self.flat_conditions) == 1:
1415 # Avoid an extra indirection in the common case of only one condition.
1416 condition = self.flat_conditions[0]
1417 elif len(self.flat_conditions) == 0:
1418 # Possible, if unlikely, due to filter predicate rewriting
1419 condition = lambda _: True
1420 else:
1421 condition = lambda x: all(cond(x) for cond in self.flat_conditions)
1422 self.__condition = condition
1423 return condition
1424
1425 def do_draw(self, data: ConjectureData) -> Ex:
1426 result = self.do_filtered_draw(data)
1427 if result is not filter_not_satisfied:
1428 return cast(Ex, result)
1429
1430 # do_filtered_draw records data._last_rejected_filter.
1431 data.mark_invalid(
1432 f"Aborted test because unable to satisfy {self!r}",
1433 location=data.last_rejected_filter_location(),
1434 )
1435
1436 def do_filtered_draw(self, data: ConjectureData) -> Ex | UniqueIdentifier:
1437 for i in range(3):
1438 data.start_span(FILTERED_SEARCH_STRATEGY_DO_DRAW_LABEL)
1439 failing = None
1440 try:
1441 value = data.draw(self.filtered_strategy)
1442 # Check the conditions individually rather than via self.condition,
1443 # so that we can set data._last_rejected_filter.
1444 failing = next(
1445 (
1446 (cond, location)
1447 for cond, location in zip(
1448 self.flat_conditions, self.condition_locations, strict=True
1449 )
1450 if not cond(value)
1451 ),
1452 None,
1453 )
1454 finally:
1455 data.stop_span(discard=failing is not None)
1456 if failing is None:
1457 return value
1458 data._last_rejected_filter = failing
1459 if i == 0:
1460 data.events[f"Retried draw from {self!r} to satisfy filter"] = ""
1461
1462 return filter_not_satisfied
1463
1464 def _invert(self, value: Any) -> tuple[ChoiceT, ...]:
1465 # If the condition accepts value, do_draw would have succeeded on its
1466 # first try, drawing exactly the inner strategy's encoding.
1467 try:
1468 satisfied = self.condition(value)
1469 except Exception:
1470 # `self.condition` is user code and may raise arbitrarily.
1471 raise CannotInvert(f"{value!r} errored in filter {self!r}") from None
1472 if not satisfied:
1473 raise CannotInvert(f"{value!r} does not satisfy filter {self!r}")
1474 return self.filtered_strategy._invert(value)
1475
1476 @property
1477 def branches(self) -> Sequence[SearchStrategy[Ex]]:
1478 return [
1479 FilteredStrategy(strategy, self.flat_conditions, self.condition_locations)
1480 for strategy in self.filtered_strategy.branches
1481 ]
1482
1483
1484@check_function
1485def check_strategy(arg: object, name: str = "") -> None:
1486 assert isinstance(name, str)
1487 if not isinstance(arg, SearchStrategy):
1488 hint = ""
1489 if isinstance(arg, (list, tuple)):
1490 hint = ", such as st.sampled_from({}),".format(name or "...")
1491 if name:
1492 name += "="
1493 raise InvalidArgument(
1494 f"Expected a SearchStrategy{hint} but got {name}{arg!r} "
1495 f"(type={type(arg).__name__})"
1496 )