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
11from collections.abc import Callable
12from typing import Any, Never, NoReturn
13
14from hypothesis.errors import CannotInvert
15from hypothesis.internal.conjecture.choice import ChoiceT
16from hypothesis.internal.conjecture.data import ConjectureData
17from hypothesis.internal.conjecture.junkdrawer import equal_values
18from hypothesis.internal.reflection import get_pretty_function_description
19from hypothesis.strategies._internal.strategies import (
20 Ex,
21 RecurT,
22 SampledFromStrategy,
23 SearchStrategy,
24 T,
25 is_hashable,
26)
27from hypothesis.strategies._internal.utils import cacheable, defines_strategy
28from hypothesis.utils.conventions import UniqueIdentifier
29
30
31class JustStrategy(SampledFromStrategy[Ex]):
32 """A strategy which always returns a single fixed value.
33
34 It's implemented as a length-one SampledFromStrategy so that all our
35 special-case logic for filtering and sets applies also to just(x).
36
37 The important difference from a SampledFromStrategy with only one
38 element to choose is that JustStrategy *never* touches the underlying
39 choice sequence, i.e. drawing neither reads from nor writes to `data`.
40 This is a reasonably important optimisation (or semantic distinction!)
41 for both JustStrategy and SampledFromStrategy.
42 """
43
44 @property
45 def value(self) -> Ex:
46 return self.elements[0]
47
48 def __repr__(self) -> str:
49 suffix = "".join(
50 f".{name}({get_pretty_function_description(f)})"
51 for name, f, _ in self._transformations
52 )
53 if self.value is None:
54 return "none()" + suffix
55 return f"just({get_pretty_function_description(self.value)}){suffix}"
56
57 def calc_is_cacheable(self, recur: RecurT) -> bool:
58 return is_hashable(self.value)
59
60 def do_filtered_draw(self, data: ConjectureData) -> Ex | UniqueIdentifier:
61 # The parent class's `do_draw` implementation delegates directly to
62 # `do_filtered_draw`, which we can greatly simplify in this case since
63 # we have exactly one value. (This also avoids drawing any data.)
64 return self._transform(self.value, data=data)
65
66 def _invert(self, value: Any) -> tuple[ChoiceT, ...]:
67 if not equal_values(self._transform(self.value, data=None), value):
68 raise CannotInvert(f"{value!r} is not produced by {self!r}")
69 return ()
70
71
72@defines_strategy(eager=True)
73def just(value: T) -> SearchStrategy[T]:
74 """Return a strategy which only generates ``value``.
75
76 Note: ``value`` is not copied. Be wary of using mutable values.
77
78 If ``value`` is the result of a callable, you can use
79 :func:`builds(callable) <hypothesis.strategies.builds>` instead
80 of ``just(callable())`` to get a fresh value each time.
81
82 Examples from this strategy do not shrink (because there is only one).
83 """
84 return JustStrategy([value])
85
86
87@defines_strategy(force_reusable_values=True)
88def none() -> SearchStrategy[None]:
89 """Return a strategy which only generates None.
90
91 Examples from this strategy do not shrink (because there is only
92 one).
93 """
94 return just(None)
95
96
97class Nothing(SearchStrategy["Never"]):
98 def calc_is_empty(self, recur: RecurT) -> bool:
99 return True
100
101 def do_draw(self, data: ConjectureData) -> NoReturn:
102 # This method should never be called because draw() will mark the
103 # data as invalid immediately because is_empty is True.
104 raise NotImplementedError("This should never happen")
105
106 def calc_has_reusable_values(self, recur: RecurT) -> bool:
107 return True
108
109 def __repr__(self) -> str:
110 return "nothing()"
111
112 def map(self, pack: Callable[[Any], Any]) -> SearchStrategy["Never"]:
113 return self
114
115 def filter(self, condition: Callable[[Any], Any]) -> "SearchStrategy[Never]":
116 return self
117
118 def flatmap(
119 self, expand: Callable[[Any], "SearchStrategy[Any]"]
120 ) -> "SearchStrategy[Never]":
121 return self
122
123
124NOTHING = Nothing()
125
126
127@cacheable
128@defines_strategy(eager=True)
129def nothing() -> SearchStrategy["Never"]:
130 """This strategy never successfully draws a value and will always reject on
131 an attempt to draw.
132
133 Examples from this strategy do not shrink (because there are none).
134 """
135 return NOTHING
136
137
138class BooleansStrategy(SearchStrategy[bool]):
139 def do_draw(self, data: ConjectureData) -> bool:
140 return data.draw_boolean()
141
142 def _invert(self, value: Any) -> tuple[ChoiceT, ...]:
143 if not isinstance(value, bool):
144 raise CannotInvert(f"{value!r} is not a bool")
145 return (value,)
146
147 def __repr__(self) -> str:
148 return "booleans()"