collections.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. # coding=utf-8
  2. #
  3. # This file is part of Hypothesis, which may be found at
  4. # https://github.com/HypothesisWorks/hypothesis-python
  5. #
  6. # Most of this work is copyright (C) 2013-2018 David R. MacIver
  7. # (david@drmaciver.com), but it contains contributions by others. See
  8. # CONTRIBUTING.rst for a full list of people who may hold copyright, and
  9. # consult the git log if you need to determine who owns an individual
  10. # contribution.
  11. #
  12. # This Source Code Form is subject to the terms of the Mozilla Public License,
  13. # v. 2.0. If a copy of the MPL was not distributed with this file, You can
  14. # obtain one at http://mozilla.org/MPL/2.0/.
  15. #
  16. # END HEADER
  17. from __future__ import division, print_function, absolute_import
  18. import hypothesis.internal.conjecture.utils as cu
  19. from hypothesis.errors import InvalidArgument
  20. from hypothesis.internal.compat import OrderedDict, hbytes
  21. from hypothesis.searchstrategy.strategies import SearchStrategy, \
  22. MappedSearchStrategy, one_of_strategies
  23. class TupleStrategy(SearchStrategy):
  24. """A strategy responsible for fixed length tuples based on heterogenous
  25. strategies for each of their elements."""
  26. def __init__(self,
  27. strategies, tuple_type):
  28. SearchStrategy.__init__(self)
  29. strategies = tuple(strategies)
  30. self.element_strategies = strategies
  31. def do_validate(self):
  32. for s in self.element_strategies:
  33. s.validate()
  34. def __repr__(self):
  35. if len(self.element_strategies) == 1:
  36. tuple_string = '%s,' % (repr(self.element_strategies[0]),)
  37. else:
  38. tuple_string = ', '.join(map(repr, self.element_strategies))
  39. return 'TupleStrategy((%s))' % (
  40. tuple_string,
  41. )
  42. def calc_has_reusable_values(self, recur):
  43. return all(recur(e) for e in self.element_strategies)
  44. def newtuple(self, xs):
  45. """Produce a new tuple of the correct type."""
  46. return tuple(xs)
  47. def do_draw(self, data):
  48. return self.newtuple(
  49. data.draw(e) for e in self.element_strategies
  50. )
  51. def calc_is_empty(self, recur):
  52. return any(recur(e) for e in self.element_strategies)
  53. TERMINATOR = hbytes(b'\0')
  54. class ListStrategy(SearchStrategy):
  55. """A strategy for lists which takes an intended average length and a
  56. strategy for each of its element types and generates lists containing any
  57. of those element types.
  58. The conditional distribution of the length is geometric, and the
  59. conditional distribution of each parameter is whatever their
  60. strategies define.
  61. """
  62. def __init__(
  63. self,
  64. strategies, average_length=50.0, min_size=0, max_size=float('inf')
  65. ):
  66. SearchStrategy.__init__(self)
  67. assert average_length > 0
  68. self.average_length = average_length
  69. strategies = tuple(strategies)
  70. self.min_size = min_size or 0
  71. self.max_size = max_size or float('inf')
  72. self.element_strategy = one_of_strategies(strategies)
  73. def do_validate(self):
  74. self.element_strategy.validate()
  75. if self.is_empty:
  76. raise InvalidArgument((
  77. 'Cannot create non-empty lists with elements drawn from '
  78. 'strategy %r because it has no values.') % (
  79. self.element_strategy,))
  80. def calc_is_empty(self, recur):
  81. if self.min_size == 0:
  82. return False
  83. else:
  84. return recur(self.element_strategy)
  85. def do_draw(self, data):
  86. if self.element_strategy.is_empty:
  87. assert self.min_size == 0
  88. return []
  89. elements = cu.many(
  90. data,
  91. min_size=self.min_size, max_size=self.max_size,
  92. average_size=self.average_length
  93. )
  94. result = []
  95. while elements.more():
  96. result.append(data.draw(self.element_strategy))
  97. return result
  98. def __repr__(self):
  99. return (
  100. 'ListStrategy(%r, min_size=%r, average_size=%r, max_size=%r)'
  101. ) % (
  102. self.element_strategy, self.min_size, self.average_length,
  103. self.max_size
  104. )
  105. class UniqueListStrategy(SearchStrategy):
  106. def __init__(
  107. self,
  108. elements, min_size, max_size, average_size,
  109. key
  110. ):
  111. super(UniqueListStrategy, self).__init__()
  112. assert min_size <= average_size <= max_size
  113. self.min_size = min_size
  114. self.max_size = max_size
  115. self.average_size = average_size
  116. self.element_strategy = elements
  117. self.key = key
  118. def do_validate(self):
  119. self.element_strategy.validate()
  120. if self.is_empty:
  121. raise InvalidArgument((
  122. 'Cannot create non-empty lists with elements drawn from '
  123. 'strategy %r because it has no values.') % (
  124. self.element_strategy,))
  125. def calc_is_empty(self, recur):
  126. if self.min_size == 0:
  127. return False
  128. else:
  129. return recur(self.element_strategy)
  130. def do_draw(self, data):
  131. if self.element_strategy.is_empty:
  132. assert self.min_size == 0
  133. return []
  134. elements = cu.many(
  135. data,
  136. min_size=self.min_size, max_size=self.max_size,
  137. average_size=self.average_size
  138. )
  139. seen = set()
  140. result = []
  141. while elements.more():
  142. value = data.draw(self.element_strategy)
  143. k = self.key(value)
  144. if k in seen:
  145. elements.reject()
  146. else:
  147. seen.add(k)
  148. result.append(value)
  149. assert self.max_size >= len(result) >= self.min_size
  150. return result
  151. class FixedKeysDictStrategy(MappedSearchStrategy):
  152. """A strategy which produces dicts with a fixed set of keys, given a
  153. strategy for each of their equivalent values.
  154. e.g. {'foo' : some_int_strategy} would
  155. generate dicts with the single key 'foo' mapping to some integer.
  156. """
  157. def __init__(self, strategy_dict):
  158. self.dict_type = type(strategy_dict)
  159. if isinstance(strategy_dict, OrderedDict):
  160. self.keys = tuple(strategy_dict.keys())
  161. else:
  162. try:
  163. self.keys = tuple(sorted(
  164. strategy_dict.keys(),
  165. ))
  166. except TypeError:
  167. self.keys = tuple(sorted(
  168. strategy_dict.keys(), key=repr,
  169. ))
  170. super(FixedKeysDictStrategy, self).__init__(
  171. strategy=TupleStrategy(
  172. (strategy_dict[k] for k in self.keys), tuple
  173. )
  174. )
  175. def calc_is_empty(self, recur):
  176. return recur(self.mapped_strategy)
  177. def __repr__(self):
  178. return 'FixedKeysDictStrategy(%r, %r)' % (
  179. self.keys, self.mapped_strategy)
  180. def pack(self, value):
  181. return self.dict_type(zip(self.keys, value))