lazy.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  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. from hypothesis.internal.compat import getfullargspec
  19. from hypothesis.internal.reflection import arg_string, \
  20. convert_keyword_arguments, convert_positional_arguments
  21. from hypothesis.searchstrategy.strategies import SearchStrategy
  22. unwrap_cache = {}
  23. unwrap_depth = 0
  24. def unwrap_strategies(s):
  25. global unwrap_depth
  26. if not isinstance(s, SearchStrategy):
  27. return s
  28. try:
  29. return unwrap_cache[s]
  30. except KeyError:
  31. pass
  32. unwrap_cache[s] = s
  33. try:
  34. unwrap_depth += 1
  35. try:
  36. result = unwrap_strategies(s.wrapped_strategy)
  37. unwrap_cache[s] = result
  38. try:
  39. assert result.force_has_reusable_values == \
  40. s.force_has_reusable_values
  41. except AttributeError:
  42. pass
  43. try:
  44. result.force_has_reusable_values = s.force_has_reusable_values
  45. except AttributeError:
  46. pass
  47. return result
  48. except AttributeError:
  49. return s
  50. finally:
  51. unwrap_depth -= 1
  52. if unwrap_depth <= 0:
  53. unwrap_cache.clear()
  54. assert unwrap_depth >= 0
  55. class LazyStrategy(SearchStrategy):
  56. """A strategy which is defined purely by conversion to and from another
  57. strategy.
  58. Its parameter and distribution come from that other strategy.
  59. """
  60. def __init__(self, function, args, kwargs):
  61. SearchStrategy.__init__(self)
  62. self.__wrapped_strategy = None
  63. self.__representation = None
  64. self.__function = function
  65. self.__args = args
  66. self.__kwargs = kwargs
  67. @property
  68. def supports_find(self):
  69. return self.wrapped_strategy.supports_find
  70. def calc_is_empty(self, recur):
  71. return recur(self.wrapped_strategy)
  72. def calc_has_reusable_values(self, recur):
  73. return recur(self.wrapped_strategy)
  74. def calc_is_cacheable(self, recur):
  75. for source in (self.__args, self.__kwargs.values()):
  76. for v in source:
  77. if isinstance(v, SearchStrategy) and not v.is_cacheable:
  78. return False
  79. return True
  80. @property
  81. def wrapped_strategy(self):
  82. if self.__wrapped_strategy is None:
  83. unwrapped_args = tuple(
  84. unwrap_strategies(s) for s in self.__args)
  85. unwrapped_kwargs = {
  86. k: unwrap_strategies(v)
  87. for k, v in self.__kwargs.items()
  88. }
  89. base = self.__function(
  90. *self.__args, **self.__kwargs
  91. )
  92. if (
  93. unwrapped_args == self.__args and
  94. unwrapped_kwargs == self.__kwargs
  95. ):
  96. self.__wrapped_strategy = base
  97. else:
  98. self.__wrapped_strategy = self.__function(
  99. *unwrapped_args,
  100. **unwrapped_kwargs)
  101. return self.__wrapped_strategy
  102. def do_validate(self):
  103. w = self.wrapped_strategy
  104. assert isinstance(w, SearchStrategy), \
  105. '%r returned non-strategy %r' % (self, w)
  106. w.validate()
  107. def __repr__(self):
  108. if self.__representation is None:
  109. _args = self.__args
  110. _kwargs = self.__kwargs
  111. argspec = getfullargspec(self.__function)
  112. defaults = dict(argspec.kwonlydefaults or {})
  113. if argspec.defaults is not None:
  114. for name, value in zip(reversed(argspec.args),
  115. reversed(argspec.defaults)):
  116. defaults[name] = value
  117. if len(argspec.args) > 1 or argspec.defaults:
  118. _args, _kwargs = convert_positional_arguments(
  119. self.__function, _args, _kwargs)
  120. else:
  121. _args, _kwargs = convert_keyword_arguments(
  122. self.__function, _args, _kwargs)
  123. kwargs_for_repr = dict(_kwargs)
  124. for k, v in defaults.items():
  125. if k in kwargs_for_repr and kwargs_for_repr[k] is defaults[k]:
  126. del kwargs_for_repr[k]
  127. self.__representation = '%s(%s)' % (
  128. self.__function.__name__,
  129. arg_string(
  130. self.__function, _args, kwargs_for_repr, reorder=False),
  131. )
  132. return self.__representation
  133. def do_draw(self, data):
  134. return data.draw(self.wrapped_strategy)