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 warnings
12from collections.abc import Hashable
13from typing import Any, Optional
14
15from hypothesis.errors import HypothesisWarning
16from hypothesis.internal.conjecture.data import ConjectureData
17from hypothesis.strategies._internal import SearchStrategy
18from hypothesis.strategies._internal.strategies import Ex
19
20
21class SharedStrategy(SearchStrategy[Ex]):
22 def __init__(self, base: SearchStrategy[Ex], key: Optional[Hashable] = None):
23 super().__init__()
24 self.key = key
25 self.base = base
26
27 def __repr__(self) -> str:
28 if self.key is not None:
29 return f"shared({self.base!r}, key={self.key!r})"
30 else:
31 return f"shared({self.base!r})"
32
33 def calc_label(self) -> int:
34 return self.base.calc_label()
35
36 # Ideally would be -> Ex, but key collisions with different-typed values are
37 # possible. See https://github.com/HypothesisWorks/hypothesis/issues/4301.
38 def do_draw(self, data: ConjectureData) -> Any:
39 key = self.key or self
40 if key not in data._shared_strategy_draws:
41 drawn = data.draw(self.base)
42 data._shared_strategy_draws[key] = (drawn, self)
43 else:
44 drawn, other = data._shared_strategy_draws[key]
45
46 # Check that the strategies shared under this key are equivalent
47 if self.label != other.label:
48 warnings.warn(
49 f"Different strategies are shared under {key=}. This"
50 " risks drawing values that are not valid examples for the strategy,"
51 " or that have a narrower range than expected."
52 f" Conflicting strategies: ({self!r}, {other!r}).",
53 HypothesisWarning,
54 stacklevel=1,
55 )
56 return drawn