Skip to content

Commit

Permalink
Merge pull request #2148 from HypothesisWorks/pyup-scheduled-update-2…
Browse files Browse the repository at this point in the history
…019-10-21

Scheduled weekly dependency update for week 42
  • Loading branch information
Zac-HD committed Oct 21, 2019
2 parents d13c4ee + 78d4cda commit 2697eff
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 150 deletions.
4 changes: 4 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
RELEASE_TYPE: patch

This patch is to ensure that our internals remain comprehensible to
:pypi:`mypy` 0.740 - there is no user-visible change.
2 changes: 1 addition & 1 deletion hypothesis-python/src/hypothesis/searchstrategy/regex.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def clear_cache_after_draw(draw, base_strategy):
class Context(object):
__slots__ = ["flags"]

def __init__(self, groups=None, flags=0):
def __init__(self, flags):
self.flags = flags


Expand Down
273 changes: 137 additions & 136 deletions hypothesis-python/src/hypothesis/searchstrategy/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,158 +78,159 @@ def one_of_strategies(xs):
return OneOfStrategy(xs)


class SearchStrategy(Generic[Ex]):
"""A SearchStrategy is an object that knows how to explore data of a given
type.
Except where noted otherwise, methods on this class are not part of
the public API and their behaviour may change significantly between
minor version releases. They will generally be stable between patch
releases.
def recursive_property(name, default):
"""Handle properties which may be mutually recursive among a set of
strategies.
These are essentially lazily cached properties, with the ability to set
an override: If the property has not been explicitly set, we calculate
it on first access and memoize the result for later.
The problem is that for properties that depend on each other, a naive
calculation strategy may hit infinite recursion. Consider for example
the property is_empty. A strategy defined as x = st.deferred(lambda: x)
is certainly empty (in order to draw a value from x we would have to
draw a value from x, for which we would have to draw a value from x,
...), but in order to calculate it the naive approach would end up
calling x.is_empty in order to calculate x.is_empty in order to etc.
The solution is one of fixed point calculation. We start with a default
value that is the value of the property in the absence of evidence to
the contrary, and then update the values of the property for all
dependent strategies until we reach a fixed point.
The approach taken roughly follows that in section 4.2 of Adams,
Michael D., Celeste Hollenbeck, and Matthew Might. "On the complexity
and performance of parsing with derivatives." ACM SIGPLAN Notices 51.6
(2016): 224-236.
"""
cache_key = "cached_" + name
calculation = "calc_" + name
force_key = "force_" + name

supports_find = True
validate_called = False
__label = None
def forced_value(target):
try:
return getattr(target, force_key)
except AttributeError:
return getattr(target, cache_key)

def recursive_property(name, default):
"""Handle properties which may be mutually recursive among a set of
strategies.
These are essentially lazily cached properties, with the ability to set
an override: If the property has not been explicitly set, we calculate
it on first access and memoize the result for later.
The problem is that for properties that depend on each other, a naive
calculation strategy may hit infinite recursion. Consider for example
the property is_empty. A strategy defined as x = st.deferred(lambda: x)
is certainly empty (in order to draw a value from x we would have to
draw a value from x, for which we would have to draw a value from x,
...), but in order to calculate it the naive approach would end up
calling x.is_empty in order to calculate x.is_empty in order to etc.
The solution is one of fixed point calculation. We start with a default
value that is the value of the property in the absence of evidence to
the contrary, and then update the values of the property for all
dependent strategies until we reach a fixed point.
The approach taken roughly follows that in section 4.2 of Adams,
Michael D., Celeste Hollenbeck, and Matthew Might. "On the complexity
and performance of parsing with derivatives." ACM SIGPLAN Notices 51.6
(2016): 224-236.
"""
cache_key = "cached_" + name
calculation = "calc_" + name
force_key = "force_" + name
def accept(self):
try:
return forced_value(self)
except AttributeError:
pass

def forced_value(target):
try:
return getattr(target, force_key)
except AttributeError:
return getattr(target, cache_key)
mapping = {}
sentinel = object()
hit_recursion = [False]

def accept(self):
# For a first pass we do a direct recursive calculation of the
# property, but we block recursively visiting a value in the
# computation of its property: When that happens, we simply
# note that it happened and return the default value.
def recur(strat):
try:
return forced_value(self)
return forced_value(strat)
except AttributeError:
pass
result = mapping.get(strat, sentinel)
if result is calculating:
hit_recursion[0] = True
return default
elif result is sentinel:
mapping[strat] = calculating
mapping[strat] = getattr(strat, calculation)(recur)
return mapping[strat]
return result

mapping = {}
sentinel = object()
hit_recursion = [False]
recur(self)

# If we hit self-recursion in the computation of any strategy
# value, our mapping at the end is imprecise - it may or may
# not have the right values in it. We now need to proceed with
# a more careful fixed point calculation to get the exact
# values. Hopefully our mapping is still pretty good and it
# won't take a large number of updates to reach a fixed point.
if hit_recursion[0]:
needs_update = set(mapping)

# We track which strategies use which in the course of
# calculating their property value. If A ever uses B in
# the course of calculating its value, then whenever the
# value of B changes we might need to update the value of
# A.
listeners = defaultdict(set)
else:
needs_update = None

# For a first pass we do a direct recursive calculation of the
# property, but we block recursively visiting a value in the
# computation of its property: When that happens, we simply
# note that it happened and return the default value.
def recur(strat):
def recur2(strat):
def recur_inner(other):
try:
return forced_value(strat)
return forced_value(other)
except AttributeError:
pass
result = mapping.get(strat, sentinel)
if result is calculating:
hit_recursion[0] = True
listeners[other].add(strat)
result = mapping.get(other, sentinel)
if result is sentinel:
needs_update.add(other)
mapping[other] = default
return default
elif result is sentinel:
mapping[strat] = calculating
mapping[strat] = getattr(strat, calculation)(recur)
return mapping[strat]
return result

recur(self)

# If we hit self-recursion in the computation of any strategy
# value, our mapping at the end is imprecise - it may or may
# not have the right values in it. We now need to proceed with
# a more careful fixed point calculation to get the exact
# values. Hopefully our mapping is still pretty good and it
# won't take a large number of updates to reach a fixed point.
if hit_recursion[0]:
needs_update = set(mapping)

# We track which strategies use which in the course of
# calculating their property value. If A ever uses B in
# the course of calculating its value, then whenever the
# value of B changes we might need to update the value of
# A.
listeners = defaultdict(set)
else:
needs_update = None

def recur2(strat):
def recur_inner(other):
try:
return forced_value(other)
except AttributeError:
pass
listeners[other].add(strat)
result = mapping.get(other, sentinel)
if result is sentinel:
needs_update.add(other)
mapping[other] = default
return default
return result

return recur_inner

count = 0
seen = set()
while needs_update:
count += 1
# If we seem to be taking a really long time to stabilize we
# start tracking seen values to attempt to detect an infinite
# loop. This should be impossible, and most code will never
# hit the count, but having an assertion for it means that
# testing is easier to debug and we don't just have a hung
# test.
# Note: This is actually covered, by test_very_deep_deferral
# in tests/cover/test_deferred_strategies.py. Unfortunately it
# runs into a coverage bug. See
# https://bitbucket.org/ned/coveragepy/issues/605/
# for details.
if count > 50: # pragma: no cover
key = frozenset(mapping.items())
assert key not in seen, (key, name)
seen.add(key)
to_update = needs_update
needs_update = set()
for strat in to_update:
new_value = getattr(strat, calculation)(recur2(strat))
if new_value != mapping[strat]:
needs_update.update(listeners[strat])
mapping[strat] = new_value

# We now have a complete and accurate calculation of the
# property values for everything we have seen in the course of
# running this calculation. We simultaneously update all of
# them (not just the strategy we started out with).
for k, v in mapping.items():
setattr(k, cache_key, v)
return getattr(self, cache_key)

accept.__name__ = name
return property(accept)
return recur_inner

count = 0
seen = set()
while needs_update:
count += 1
# If we seem to be taking a really long time to stabilize we
# start tracking seen values to attempt to detect an infinite
# loop. This should be impossible, and most code will never
# hit the count, but having an assertion for it means that
# testing is easier to debug and we don't just have a hung
# test.
# Note: This is actually covered, by test_very_deep_deferral
# in tests/cover/test_deferred_strategies.py. Unfortunately it
# runs into a coverage bug. See
# https://bitbucket.org/ned/coveragepy/issues/605/
# for details.
if count > 50: # pragma: no cover
key = frozenset(mapping.items())
assert key not in seen, (key, name)
seen.add(key)
to_update = needs_update
needs_update = set()
for strat in to_update:
new_value = getattr(strat, calculation)(recur2(strat))
if new_value != mapping[strat]:
needs_update.update(listeners[strat])
mapping[strat] = new_value

# We now have a complete and accurate calculation of the
# property values for everything we have seen in the course of
# running this calculation. We simultaneously update all of
# them (not just the strategy we started out with).
for k, v in mapping.items():
setattr(k, cache_key, v)
return getattr(self, cache_key)

accept.__name__ = name
return property(accept)


class SearchStrategy(Generic[Ex]):
"""A SearchStrategy is an object that knows how to explore data of a given
type.
Except where noted otherwise, methods on this class are not part of
the public API and their behaviour may change significantly between
minor version releases. They will generally be stable between patch
releases.
"""

supports_find = True
validate_called = False
__label = None

# Returns True if this strategy can never draw a value and will always
# result in the data being marked invalid.
Expand Down
4 changes: 2 additions & 2 deletions requirements/coverage.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
#
coverage==4.5.4
lark-parser==0.7.7
numpy==1.17.2
pandas==0.25.1
numpy==1.17.3
pandas==0.25.2
python-dateutil==2.8.0 # via pandas
pytz==2019.3
six==1.12.0 # via python-dateutil
2 changes: 1 addition & 1 deletion requirements/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#
apipkg==1.5 # via execnet
atomicwrites==1.3.0 # via pytest
attrs==19.2.0
attrs==19.3.0
execnet==1.7.1 # via pytest-xdist
importlib-metadata==0.23 # via pluggy, pytest
more-itertools==7.2.0 # via pytest, zipp
Expand Down
20 changes: 10 additions & 10 deletions requirements/tools.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
#
alabaster==0.7.12 # via sphinx
appdirs==1.4.3 # via black
astroid==2.3.1 # via pylint
astroid==2.3.2 # via pylint
atomicwrites==1.3.0 # via pytest
attrs==19.2.0
attrs==19.3.0
autoflake==1.3.1
babel==2.7.0 # via sphinx
backcall==0.1.0 # via ipython
Expand Down Expand Up @@ -46,9 +46,9 @@ lazy-object-proxy==1.4.2 # via astroid
markupsafe==1.1.1 # via jinja2
mccabe==0.6.1 # via flake8, pylint
more-itertools==7.2.0 # via pytest, zipp
mypy-extensions==0.4.2 # via mypy
mypy==0.730
numpy==1.17.2
mypy-extensions==0.4.3 # via mypy
mypy==0.740
numpy==1.17.3
packaging==19.2 # via dparse, pytest, pyupio, safety, sphinx, tox
parso==0.5.1 # via jedi
pbr==5.4.3 # via stevedore
Expand All @@ -63,24 +63,24 @@ py==1.8.0 # via pytest, tox
pycodestyle==2.5.0 # via flake8
pydocstyle==4.0.1 # via flake8-docstrings
pyflakes==2.1.1 # via autoflake, flake8
pygithub==1.43.8 # via pyupio
pygithub==1.44 # via pyupio
pygments==2.4.2 # via ipython, readme-renderer, sphinx
pyjwt==1.7.1 # via pygithub
pylint==2.4.2
pylint==2.4.3
pyparsing==2.4.2 # via packaging
pytest==5.2.1
python-dateutil==2.8.0
python-gitlab==1.12.1 # via pyupio
pytz==2019.3 # via babel, django
pyupgrade==1.24.1
pyupgrade==1.25.1
pyupio==1.0.2
pyyaml==5.1.2 # via bandit, dparse, pyupio
readme-renderer==24.0 # via twine
requests-toolbelt==0.9.1 # via twine
requests==2.22.0
restructuredtext-lint==1.3.0
safety==1.8.5 # via pyupio
six==1.12.0 # via astroid, bandit, bleach, dparse, packaging, pip-tools, prompt-toolkit, python-dateutil, python-gitlab, pyupio, readme-renderer, stevedore, tox, traitlets
six==1.12.0 # via astroid, bandit, bleach, dparse, packaging, pip-tools, prompt-toolkit, pygithub, python-dateutil, python-gitlab, pyupio, readme-renderer, stevedore, tox, traitlets
smmap2==2.0.5 # via gitdb2
snowballstemmer==2.0.0 # via pydocstyle, sphinx
sphinx-rtd-theme==0.4.3
Expand All @@ -102,7 +102,7 @@ twine==2.0.0
typed-ast==1.4.0 # via astroid, mypy
typing-extensions==3.7.4 # via mypy
urllib3==1.25.6 # via requests
virtualenv==16.7.5 # via tox
virtualenv==16.7.6 # via tox
wcwidth==0.1.7 # via prompt-toolkit, pytest
webencodings==0.5.1 # via bleach
wrapt==1.11.2 # via astroid, deprecated
Expand Down

0 comments on commit 2697eff

Please sign in to comment.