Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/hypothesis/strategies/_internal/collections.py: 30%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

174 statements  

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 copy 

12import math 

13from collections.abc import Callable, Iterable 

14from typing import Any, overload 

15 

16from hypothesis import strategies as st 

17from hypothesis.errors import InvalidArgument 

18from hypothesis.internal.conjecture import utils as cu 

19from hypothesis.internal.conjecture.data import ConjectureData 

20from hypothesis.internal.conjecture.engine import BUFFER_SIZE 

21from hypothesis.internal.conjecture.junkdrawer import LazySequenceCopy 

22from hypothesis.internal.conjecture.utils import combine_labels 

23from hypothesis.internal.filtering import get_integer_predicate_bounds 

24from hypothesis.internal.reflection import is_identity_function 

25from hypothesis.strategies._internal.strategies import ( 

26 T3, 

27 T4, 

28 T5, 

29 Ex, 

30 FilteredStrategy, 

31 RecurT, 

32 SampledFromStrategy, 

33 SearchStrategy, 

34 T, 

35 check_strategy, 

36 filter_not_satisfied, 

37) 

38from hypothesis.strategies._internal.utils import cacheable, defines_strategy 

39from hypothesis.utils.conventions import UniqueIdentifier 

40 

41 

42class TupleStrategy(SearchStrategy[tuple[Ex, ...]]): 

43 """A strategy responsible for fixed length tuples based on heterogeneous 

44 strategies for each of their elements.""" 

45 

46 def __init__(self, strategies: Iterable[SearchStrategy[Any]]): 

47 super().__init__() 

48 self.element_strategies = tuple(strategies) 

49 

50 def do_validate(self) -> None: 

51 for s in self.element_strategies: 

52 s.validate() 

53 

54 def calc_label(self) -> int: 

55 return combine_labels( 

56 self.class_label, *(s.label for s in self.element_strategies) 

57 ) 

58 

59 def __repr__(self) -> str: 

60 tuple_string = ", ".join(map(repr, self.element_strategies)) 

61 return f"TupleStrategy(({tuple_string}))" 

62 

63 def calc_has_reusable_values(self, recur: RecurT) -> bool: 

64 return all(recur(e) for e in self.element_strategies) 

65 

66 def do_draw(self, data: ConjectureData) -> tuple[Ex, ...]: 

67 return tuple(data.draw(e) for e in self.element_strategies) 

68 

69 def calc_is_empty(self, recur: RecurT) -> bool: 

70 return any(recur(e) for e in self.element_strategies) 

71 

72 

73@overload 

74def tuples() -> SearchStrategy[tuple[()]]: # pragma: no cover 

75 ... 

76 

77 

78@overload 

79def tuples(__a1: SearchStrategy[Ex]) -> SearchStrategy[tuple[Ex]]: # pragma: no cover 

80 ... 

81 

82 

83@overload 

84def tuples( 

85 __a1: SearchStrategy[Ex], __a2: SearchStrategy[T] 

86) -> SearchStrategy[tuple[Ex, T]]: # pragma: no cover 

87 ... 

88 

89 

90@overload 

91def tuples( 

92 __a1: SearchStrategy[Ex], __a2: SearchStrategy[T], __a3: SearchStrategy[T3] 

93) -> SearchStrategy[tuple[Ex, T, T3]]: # pragma: no cover 

94 ... 

95 

96 

97@overload 

98def tuples( 

99 __a1: SearchStrategy[Ex], 

100 __a2: SearchStrategy[T], 

101 __a3: SearchStrategy[T3], 

102 __a4: SearchStrategy[T4], 

103) -> SearchStrategy[tuple[Ex, T, T3, T4]]: # pragma: no cover 

104 ... 

105 

106 

107@overload 

108def tuples( 

109 __a1: SearchStrategy[Ex], 

110 __a2: SearchStrategy[T], 

111 __a3: SearchStrategy[T3], 

112 __a4: SearchStrategy[T4], 

113 __a5: SearchStrategy[T5], 

114) -> SearchStrategy[tuple[Ex, T, T3, T4, T5]]: # pragma: no cover 

115 ... 

116 

117 

118@overload 

119def tuples( 

120 *args: SearchStrategy[Any], 

121) -> SearchStrategy[tuple[Any, ...]]: # pragma: no cover 

122 ... 

123 

124 

125@cacheable 

126@defines_strategy() 

127def tuples(*args: SearchStrategy[Any]) -> SearchStrategy[tuple[Any, ...]]: 

128 """Return a strategy which generates a tuple of the same length as args by 

129 generating the value at index i from args[i]. 

130 

131 e.g. tuples(integers(), integers()) would generate a tuple of length 

132 two with both values an integer. 

133 

134 Examples from this strategy shrink by shrinking their component parts. 

135 """ 

136 for arg in args: 

137 check_strategy(arg) 

138 

139 return TupleStrategy(args) 

140 

141 

142class ListStrategy(SearchStrategy[list[Ex]]): 

143 """A strategy for lists which takes a strategy for its elements and the 

144 allowed lengths, and generates lists with the correct size and contents.""" 

145 

146 _nonempty_filters: tuple[Callable[[Any], Any], ...] = (bool, len, tuple, list) 

147 

148 def __init__( 

149 self, 

150 elements: SearchStrategy[Ex], 

151 min_size: int = 0, 

152 max_size: float | int | None = math.inf, 

153 ): 

154 super().__init__() 

155 self.min_size = min_size or 0 

156 self.max_size = max_size if max_size is not None else math.inf 

157 assert 0 <= self.min_size <= self.max_size 

158 self.average_size = min( 

159 max(self.min_size * 2, self.min_size + 5), 

160 0.5 * (self.min_size + self.max_size), 

161 ) 

162 self.element_strategy = elements 

163 if min_size > BUFFER_SIZE: 

164 raise InvalidArgument( 

165 f"{self!r} can never generate an example, because min_size is larger " 

166 "than Hypothesis supports. Including it is at best slowing down your " 

167 "tests for no benefit; at worst making them fail (maybe flakily) with " 

168 "a HealthCheck error." 

169 ) 

170 

171 def calc_label(self) -> int: 

172 return combine_labels(self.class_label, self.element_strategy.label) 

173 

174 def do_validate(self) -> None: 

175 self.element_strategy.validate() 

176 if self.is_empty: 

177 raise InvalidArgument( 

178 "Cannot create non-empty lists with elements drawn from " 

179 f"strategy {self.element_strategy!r} because it has no values." 

180 ) 

181 if self.element_strategy.is_empty and 0 < self.max_size < float("inf"): 

182 raise InvalidArgument( 

183 f"Cannot create a collection of max_size={self.max_size!r}, " 

184 "because no elements can be drawn from the element strategy " 

185 f"{self.element_strategy!r}" 

186 ) 

187 

188 def calc_is_empty(self, recur: RecurT) -> bool: 

189 if self.min_size == 0: 

190 return False 

191 return recur(self.element_strategy) 

192 

193 def do_draw(self, data: ConjectureData) -> list[Ex]: 

194 if self.element_strategy.is_empty: 

195 assert self.min_size == 0 

196 return [] 

197 

198 elements = cu.many( 

199 data, 

200 min_size=self.min_size, 

201 max_size=self.max_size, 

202 average_size=self.average_size, 

203 ) 

204 result = [] 

205 while elements.more(): 

206 result.append(data.draw(self.element_strategy)) 

207 return result 

208 

209 def __repr__(self) -> str: 

210 return ( 

211 f"{self.__class__.__name__}({self.element_strategy!r}, " 

212 f"min_size={self.min_size:_}, max_size={self.max_size:_})" 

213 ) 

214 

215 def filter(self, condition: Callable[[list[Ex]], Any]) -> SearchStrategy[list[Ex]]: 

216 if condition in self._nonempty_filters or is_identity_function(condition): 

217 assert self.max_size >= 1, "Always-empty is special cased in st.lists()" 

218 if self.min_size >= 1: 

219 return self 

220 new = copy.copy(self) 

221 new.min_size = 1 

222 return new 

223 

224 constraints, pred = get_integer_predicate_bounds(condition) 

225 if constraints.get("len") and ( 

226 "min_value" in constraints or "max_value" in constraints 

227 ): 

228 new = copy.copy(self) 

229 new.min_size = max( 

230 self.min_size, constraints.get("min_value", self.min_size) 

231 ) 

232 new.max_size = min( 

233 self.max_size, constraints.get("max_value", self.max_size) 

234 ) 

235 # Unsatisfiable filters are easiest to understand without rewriting. 

236 if new.min_size > new.max_size: 

237 return SearchStrategy.filter(self, condition) 

238 # Recompute average size; this is cheaper than making it into a property. 

239 new.average_size = min( 

240 max(new.min_size * 2, new.min_size + 5), 

241 0.5 * (new.min_size + new.max_size), 

242 ) 

243 if pred is None: 

244 return new 

245 return SearchStrategy.filter(new, condition) 

246 

247 return SearchStrategy.filter(self, condition) 

248 

249 

250class UniqueListStrategy(ListStrategy[Ex]): 

251 def __init__( 

252 self, 

253 elements: SearchStrategy[Ex], 

254 min_size: int, 

255 max_size: float | int | None, 

256 # TODO: keys are guaranteed to be Hashable, not just Any, but this makes 

257 # other things harder to type 

258 keys: tuple[Callable[[Ex], Any], ...], 

259 tuple_suffixes: SearchStrategy[tuple[Ex, ...]] | None, 

260 ): 

261 super().__init__(elements, min_size, max_size) 

262 self.keys = keys 

263 self.tuple_suffixes = tuple_suffixes 

264 

265 def do_draw(self, data: ConjectureData) -> list[Ex]: 

266 if self.element_strategy.is_empty: 

267 assert self.min_size == 0 

268 return [] 

269 

270 elements = cu.many( 

271 data, 

272 min_size=self.min_size, 

273 max_size=self.max_size, 

274 average_size=self.average_size, 

275 ) 

276 seen_sets: tuple[set[Ex], ...] = tuple(set() for _ in self.keys) 

277 # actually list[Ex], but if self.tuple_suffixes is present then Ex is a 

278 # tuple[T, ...] because self.element_strategy is a TuplesStrategy, and 

279 # appending a concrete tuple to `result: list[Ex]` makes mypy unhappy 

280 # without knowing that Ex = tuple. 

281 result: list[Any] = [] 

282 

283 # We construct a filtered strategy here rather than using a check-and-reject 

284 # approach because some strategies have special logic for generation under a 

285 # filter, and FilteredStrategy can consolidate multiple filters. 

286 def not_yet_in_unique_list(val: Ex) -> bool: # type: ignore # covariant type param 

287 return all( 

288 key(val) not in seen 

289 for key, seen in zip(self.keys, seen_sets, strict=True) 

290 ) 

291 

292 filtered = FilteredStrategy( 

293 self.element_strategy, conditions=(not_yet_in_unique_list,) 

294 ) 

295 while elements.more(): 

296 value = filtered.do_filtered_draw(data) 

297 if value is filter_not_satisfied: 

298 elements.reject(f"Aborted test because unable to satisfy {filtered!r}") 

299 else: 

300 assert not isinstance(value, UniqueIdentifier) 

301 for key, seen in zip(self.keys, seen_sets, strict=True): 

302 seen.add(key(value)) 

303 if self.tuple_suffixes is not None: 

304 value = (value, *data.draw(self.tuple_suffixes)) # type: ignore 

305 result.append(value) 

306 assert self.max_size >= len(result) >= self.min_size 

307 return result 

308 

309 

310class UniqueSampledListStrategy(UniqueListStrategy): 

311 def do_draw(self, data: ConjectureData) -> list[Ex]: 

312 assert isinstance(self.element_strategy, SampledFromStrategy) 

313 

314 should_draw = cu.many( 

315 data, 

316 min_size=self.min_size, 

317 max_size=self.max_size, 

318 average_size=self.average_size, 

319 ) 

320 seen_sets: tuple[set[Ex], ...] = tuple(set() for _ in self.keys) 

321 result: list[Any] = [] 

322 

323 remaining = LazySequenceCopy(self.element_strategy.elements) 

324 

325 while remaining and should_draw.more(): 

326 j = data.draw_integer(0, len(remaining) - 1) 

327 value = self.element_strategy._transform(remaining.pop(j)) 

328 if value is not filter_not_satisfied and all( 

329 key(value) not in seen 

330 for key, seen in zip(self.keys, seen_sets, strict=True) 

331 ): 

332 for key, seen in zip(self.keys, seen_sets, strict=True): 

333 seen.add(key(value)) 

334 if self.tuple_suffixes is not None: 

335 value = (value, *data.draw(self.tuple_suffixes)) 

336 result.append(value) 

337 else: 

338 should_draw.reject( 

339 "UniqueSampledListStrategy filter not satisfied or value already seen" 

340 ) 

341 assert self.max_size >= len(result) >= self.min_size 

342 return result 

343 

344 

345class FixedDictStrategy(SearchStrategy[dict[Any, Any]]): 

346 """A strategy which produces dicts with a fixed set of keys, given a 

347 strategy for each of their equivalent values. 

348 

349 e.g. {'foo' : some_int_strategy} would generate dicts with the single 

350 key 'foo' mapping to some integer. 

351 """ 

352 

353 def __init__( 

354 self, 

355 mapping: dict[Any, SearchStrategy[Any]], 

356 *, 

357 optional: dict[Any, SearchStrategy[Any]] | None, 

358 ): 

359 super().__init__() 

360 dict_type = type(mapping) 

361 self.mapping = mapping 

362 keys = tuple(mapping.keys()) 

363 self.fixed = st.tuples(*[mapping[k] for k in keys]).map( 

364 lambda value: dict_type(zip(keys, value, strict=True)) 

365 ) 

366 self.optional = optional 

367 

368 def do_draw(self, data: ConjectureData) -> dict[Any, Any]: 

369 value = data.draw(self.fixed) 

370 if self.optional is None: 

371 return value 

372 

373 remaining = [k for k, v in self.optional.items() if not v.is_empty] 

374 should_draw = cu.many( 

375 data, min_size=0, max_size=len(remaining), average_size=len(remaining) / 2 

376 ) 

377 while should_draw.more(): 

378 j = data.draw_integer(0, len(remaining) - 1) 

379 remaining[-1], remaining[j] = remaining[j], remaining[-1] 

380 key = remaining.pop() 

381 value[key] = data.draw(self.optional[key]) 

382 return value 

383 

384 def calc_is_empty(self, recur: RecurT) -> bool: 

385 return recur(self.fixed) 

386 

387 def __repr__(self) -> str: 

388 if self.optional is not None: 

389 return f"fixed_dictionaries({self.mapping!r}, optional={self.optional!r})" 

390 return f"fixed_dictionaries({self.mapping!r})"