Skip to content

Commit

Permalink
Merge pull request #4124 from reaganjlee/consumes-refactor
Browse files Browse the repository at this point in the history
Refactor `BundleReferenceStrategy` into `Bundle`
  • Loading branch information
Zac-HD authored Oct 5, 2024
2 parents 83c22d9 + 16eab2f commit 8e1806d
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 58 deletions.
3 changes: 3 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
RELEASE_TYPE: patch

This release refactors internals of :class:`hypothesis.stateful.Bundle` to have a more consistent representation internally.
2 changes: 1 addition & 1 deletion hypothesis-python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def local_file(name):
"pytest": ["pytest>=4.6"],
"dpcontracts": ["dpcontracts>=0.4"],
"redis": ["redis>=3.0.0"],
"crosshair": ["hypothesis-crosshair>=0.0.14", "crosshair-tool>=0.0.72"],
"crosshair": ["hypothesis-crosshair>=0.0.14", "crosshair-tool>=0.0.73"],
# zoneinfo is an odd one: every dependency is conditional, because they're
# only necessary on old versions of Python or Windows systems or emscripten.
"zoneinfo": [
Expand Down
65 changes: 31 additions & 34 deletions hypothesis-python/src/hypothesis/stateful.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ def output(s):
for k, v in list(data.items()):
if isinstance(v, VarReference):
data[k] = machine.names_to_values[v.name]
elif isinstance(v, list) and all(
isinstance(item, VarReference) for item in v
):
data[k] = [machine.names_to_values[item.name] for item in v]

label = f"execute:rule:{rule.function.__name__}"
start = perf_counter()
Expand Down Expand Up @@ -300,6 +304,10 @@ def __init__(self) -> None:
def _pretty_print(self, value):
if isinstance(value, VarReference):
return value.name
elif isinstance(value, list) and all(
isinstance(item, VarReference) for item in value
):
return "[" + ", ".join([item.name for item in value]) + "]"
self.__stream.seek(0)
self.__stream.truncate(0)
self.__printer.output_width = 0
Expand Down Expand Up @@ -457,11 +465,8 @@ def __attrs_post_init__(self):
self.arguments_strategies = {}
bundles = []
for k, v in sorted(self.arguments.items()):
assert not isinstance(v, BundleReferenceStrategy)
if isinstance(v, Bundle):
bundles.append(v)
consume = isinstance(v, BundleConsumer)
v = BundleReferenceStrategy(v.name, consume=consume)
self.arguments_strategies[k] = v
self.bundles = tuple(bundles)

Expand All @@ -474,26 +479,6 @@ def __repr__(self) -> str:
self_strategy = st.runner()


class BundleReferenceStrategy(SearchStrategy):
def __init__(self, name: str, *, consume: bool = False):
self.name = name
self.consume = consume

def do_draw(self, data):
machine = data.draw(self_strategy)
bundle = machine.bundle(self.name)
if not bundle:
data.mark_invalid(f"Cannot draw from empty bundle {self.name!r}")
# Shrink towards the right rather than the left. This makes it easier
# to delete data generated earlier, as when the error is towards the
# end there can be a lot of hard to remove padding.
position = data.draw_integer(0, len(bundle) - 1, shrink_towards=len(bundle))
if self.consume:
return bundle.pop(position) # pragma: no cover # coverage is flaky here
else:
return bundle[position]


class Bundle(SearchStrategy[Ex]):
"""A collection of values for use in stateful testing.
Expand All @@ -518,15 +503,29 @@ class MyStateMachine(RuleBasedStateMachine):

def __init__(self, name: str, *, consume: bool = False) -> None:
self.name = name
self.__reference_strategy = BundleReferenceStrategy(name, consume=consume)
self.consume = consume

def do_draw(self, data):
machine = data.draw(self_strategy)
reference = data.draw(self.__reference_strategy)
return machine.names_to_values[reference.name]

bundle = machine.bundle(self.name)
if not bundle:
data.mark_invalid(f"Cannot draw from empty bundle {self.name!r}")
# Shrink towards the right rather than the left. This makes it easier
# to delete data generated earlier, as when the error is towards the
# end there can be a lot of hard to remove padding.
position = data.draw_integer(0, len(bundle) - 1, shrink_towards=len(bundle))
if self.consume:
reference = bundle.pop(
position
) # pragma: no cover # coverage is flaky here
else:
reference = bundle[position]

return reference

def __repr__(self):
consume = self.__reference_strategy.consume
consume = self.consume
if consume is False:
return f"Bundle(name={self.name!r})"
return f"Bundle(name={self.name!r}, {consume=})"
Expand All @@ -543,11 +542,6 @@ def available(self, data):
return bool(machine.bundle(self.name))


class BundleConsumer(Bundle[Ex]):
def __init__(self, bundle: Bundle[Ex]) -> None:
super().__init__(bundle.name, consume=True)


def consumes(bundle: Bundle[Ex]) -> SearchStrategy[Ex]:
"""When introducing a rule in a RuleBasedStateMachine, this function can
be used to mark bundles from which each value used in a step with the
Expand All @@ -563,7 +557,10 @@ def consumes(bundle: Bundle[Ex]) -> SearchStrategy[Ex]:
"""
if not isinstance(bundle, Bundle):
raise TypeError("Argument to be consumed must be a bundle.")
return BundleConsumer(bundle)
return type(bundle)(
name=bundle.name,
consume=True,
)


@attr.s()
Expand Down Expand Up @@ -610,7 +607,7 @@ def _convert_targets(targets, target):
)
raise InvalidArgument(msg % (t, type(t)))
while isinstance(t, Bundle):
if isinstance(t, BundleConsumer):
if t.consume:
note_deprecation(
f"Using consumes({t.name}) doesn't makes sense in this context. "
"This will be an error in a future version of Hypothesis.",
Expand Down
8 changes: 4 additions & 4 deletions requirements/coverage.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ exceptiongroup==1.2.2 ; python_version < "3.11"
# pytest
execnet==2.1.1
# via pytest-xdist
fakeredis==2.25.0
fakeredis==2.25.1
# via -r requirements/coverage.in
iniconfig==2.0.0
# via pytest
Expand All @@ -38,7 +38,7 @@ libcst==1.4.0
# via -r requirements/coverage.in
mypy-extensions==1.0.0
# via black
numpy==2.1.1
numpy==2.1.2
# via
# -r requirements/coverage.in
# pandas
Expand Down Expand Up @@ -80,15 +80,15 @@ pytz==2024.2
# pandas
pyyaml==6.0.2
# via libcst
redis==5.1.0
redis==5.1.1
# via fakeredis
six==1.16.0
# via python-dateutil
sortedcontainers==2.4.0
# via
# fakeredis
# hypothesis (hypothesis-python/setup.py)
tomli==2.0.1
tomli==2.0.2
# via
# black
# coverage
Expand Down
13 changes: 7 additions & 6 deletions requirements/fuzzing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ exceptiongroup==1.2.2 ; python_version < "3.11"
# pytest
execnet==2.1.1
# via pytest-xdist
fakeredis==2.25.0
fakeredis==2.25.1
# via -r requirements/coverage.in
flask==3.0.3
# via dash
hypofuzz==24.9.1
# via -r requirements/fuzzing.in
hypothesis[cli]==6.112.1
hypothesis[cli]==6.112.2
# via hypofuzz
idna==3.10
# via requests
Expand Down Expand Up @@ -87,7 +87,7 @@ mypy-extensions==1.0.0
# via black
nest-asyncio==1.6.0
# via dash
numpy==2.1.1
numpy==2.1.2
# via
# -r requirements/coverage.in
# pandas
Expand Down Expand Up @@ -139,15 +139,15 @@ pytz==2024.2
# pandas
pyyaml==6.0.2
# via libcst
redis==5.1.0
redis==5.1.1
# via fakeredis
requests==2.32.3
# via
# dash
# hypofuzz
retrying==1.3.4
# via dash
rich==13.8.1
rich==13.9.2
# via hypothesis
six==1.16.0
# via
Expand All @@ -160,7 +160,7 @@ sortedcontainers==2.4.0
# hypothesis (hypothesis-python/setup.py)
tenacity==9.0.0
# via plotly
tomli==2.0.1
tomli==2.0.2
# via
# black
# coverage
Expand All @@ -171,6 +171,7 @@ typing-extensions==4.12.2
# black
# dash
# fakeredis
# rich
tzdata==2024.2
# via pandas
urllib3==2.2.3
Expand Down
2 changes: 1 addition & 1 deletion requirements/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@ pytest-xdist==3.6.1
# via -r requirements/test.in
sortedcontainers==2.4.0
# via hypothesis (hypothesis-python/setup.py)
tomli==2.0.1
tomli==2.0.2
# via pytest
22 changes: 12 additions & 10 deletions requirements/tools.txt
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ importlib-metadata==8.5.0
# twine
iniconfig==2.0.0
# via pytest
ipython==8.27.0
ipython==8.28.0
# via -r requirements/tools.in
isort==5.13.2
# via shed
Expand Down Expand Up @@ -151,7 +151,7 @@ nh3==0.2.18
# via readme-renderer
nodeenv==1.9.1
# via pyright
numpy==2.1.1
numpy==2.1.2
# via -r requirements/tools.in
ordered-set==4.1.0
# via pelican
Expand Down Expand Up @@ -203,11 +203,11 @@ pygments==2.18.0
# sphinx
pyproject-api==1.8.0
# via tox
pyproject-hooks==1.1.0
pyproject-hooks==1.2.0
# via
# build
# pip-tools
pyright==1.1.382.post1
pyright==1.1.383
# via -r requirements/tools.in
pytest==8.3.3
# via -r requirements/tools.in
Expand Down Expand Up @@ -238,11 +238,11 @@ restructuredtext-lint==1.4.0
# via -r requirements/tools.in
rfc3986==2.0.0
# via twine
rich==13.8.1
rich==13.9.2
# via
# pelican
# twine
ruff==0.6.8
ruff==0.6.9
# via -r requirements/tools.in
secretstorage==3.3.3
# via keyring
Expand Down Expand Up @@ -299,7 +299,7 @@ stack-data==0.6.3
# via ipython
tokenize-rt==6.0.0
# via pyupgrade
tomli==2.0.1
tomli==2.0.2
# via
# autoflake
# black
Expand All @@ -310,7 +310,7 @@ tomli==2.0.1
# pytest
# sphinx
# tox
tox==4.20.0
tox==4.21.2
# via -r requirements/tools.in
traitlets==5.14.3
# via
Expand All @@ -324,9 +324,9 @@ types-click==7.1.8
# via -r requirements/tools.in
types-pyopenssl==24.1.0.20240722
# via types-redis
types-pytz==2024.2.0.20240913
types-pytz==2024.2.0.20241003
# via -r requirements/tools.in
types-redis==4.6.0.20240903
types-redis==4.6.0.20241004
# via -r requirements/tools.in
types-setuptools==75.1.0.20240917
# via types-cffi
Expand All @@ -339,6 +339,8 @@ typing-extensions==4.12.2
# ipython
# mypy
# pyright
# rich
# tox
unidecode==1.3.8
# via pelican
urllib3==2.2.3
Expand Down
4 changes: 2 additions & 2 deletions tooling/src/hypothesistooling/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,8 +450,8 @@ def run_tox(task, version, *args):
"3.9": "3.9.20",
"3.10": "3.10.15",
"3.11": "3.11.10",
"3.12": "3.12.6",
"3.13": "3.13.0rc2",
"3.12": "3.12.7",
"3.13": "3.13.0rc3",
"3.13t": "3.13t-dev",
"3.14": "3.14-dev",
"3.14t": "3.14t-dev",
Expand Down

0 comments on commit 8e1806d

Please sign in to comment.