Skip to content

Commit 4695e3f

Browse files
authored
Merge pull request #4187 from reaganjlee/bundle-regression
Revert Refactor of `BundleReferenceStrategy` into `Bundle`
2 parents c274b1e + 536abf7 commit 4695e3f

File tree

3 files changed

+61
-25
lines changed

3 files changed

+61
-25
lines changed

hypothesis-python/RELEASE.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
RELEASE_TYPE: patch
2+
3+
This release brings back the old representation of :class:`hypothesis.stateful.Bundle`, reverting most changes of `PR #4124 <https://github.com/HypothesisWorks/hypothesis/pull/4124>`_.

hypothesis-python/src/hypothesis/stateful.py

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -455,8 +455,11 @@ def __attrs_post_init__(self):
455455
self.arguments_strategies = {}
456456
bundles = []
457457
for k, v in sorted(self.arguments.items()):
458+
assert not isinstance(v, BundleReferenceStrategy)
458459
if isinstance(v, Bundle):
459460
bundles.append(v)
461+
consume = isinstance(v, BundleConsumer)
462+
v = BundleReferenceStrategy(v.name, consume=consume)
460463
self.arguments_strategies[k] = v
461464
self.bundles = tuple(bundles)
462465

@@ -469,6 +472,26 @@ def __repr__(self) -> str:
469472
self_strategy = st.runner()
470473

471474

475+
class BundleReferenceStrategy(SearchStrategy):
476+
def __init__(self, name: str, *, consume: bool = False):
477+
self.name = name
478+
self.consume = consume
479+
480+
def do_draw(self, data):
481+
machine = data.draw(self_strategy)
482+
bundle = machine.bundle(self.name)
483+
if not bundle:
484+
data.mark_invalid(f"Cannot draw from empty bundle {self.name!r}")
485+
# Shrink towards the right rather than the left. This makes it easier
486+
# to delete data generated earlier, as when the error is towards the
487+
# end there can be a lot of hard to remove padding.
488+
position = data.draw_integer(0, len(bundle) - 1, shrink_towards=len(bundle))
489+
if self.consume:
490+
return bundle.pop(position) # pragma: no cover # coverage is flaky here
491+
else:
492+
return bundle[position]
493+
494+
472495
class Bundle(SearchStrategy[Ex]):
473496
"""A collection of values for use in stateful testing.
474497
@@ -495,32 +518,16 @@ def __init__(
495518
self, name: str, *, consume: bool = False, draw_references: bool = True
496519
) -> None:
497520
self.name = name
498-
self.consume = consume
521+
self.__reference_strategy = BundleReferenceStrategy(name, consume=consume)
499522
self.draw_references = draw_references
500523

501524
def do_draw(self, data):
502525
machine = data.draw(self_strategy)
503-
504-
bundle = machine.bundle(self.name)
505-
if not bundle:
506-
data.mark_invalid(f"Cannot draw from empty bundle {self.name!r}")
507-
# Shrink towards the right rather than the left. This makes it easier
508-
# to delete data generated earlier, as when the error is towards the
509-
# end there can be a lot of hard to remove padding.
510-
position = data.draw_integer(0, len(bundle) - 1, shrink_towards=len(bundle))
511-
if self.consume:
512-
reference = bundle.pop(
513-
position
514-
) # pragma: no cover # coverage is flaky here
515-
else:
516-
reference = bundle[position]
517-
518-
if self.draw_references:
519-
return reference
526+
reference = data.draw(self.__reference_strategy)
520527
return machine.names_to_values[reference.name]
521528

522529
def __repr__(self):
523-
consume = self.consume
530+
consume = self.__reference_strategy.consume
524531
if consume is False:
525532
return f"Bundle(name={self.name!r})"
526533
return f"Bundle(name={self.name!r}, {consume=})"
@@ -539,11 +546,18 @@ def available(self, data):
539546
def flatmap(self, expand):
540547
if self.draw_references:
541548
return type(self)(
542-
self.name, consume=self.consume, draw_references=False
549+
self.name,
550+
consume=self.__reference_strategy.consume,
551+
draw_references=False,
543552
).flatmap(expand)
544553
return super().flatmap(expand)
545554

546555

556+
class BundleConsumer(Bundle[Ex]):
557+
def __init__(self, bundle: Bundle[Ex]) -> None:
558+
super().__init__(bundle.name, consume=True)
559+
560+
547561
def consumes(bundle: Bundle[Ex]) -> SearchStrategy[Ex]:
548562
"""When introducing a rule in a RuleBasedStateMachine, this function can
549563
be used to mark bundles from which each value used in a step with the
@@ -559,10 +573,7 @@ def consumes(bundle: Bundle[Ex]) -> SearchStrategy[Ex]:
559573
"""
560574
if not isinstance(bundle, Bundle):
561575
raise TypeError("Argument to be consumed must be a bundle.")
562-
return type(bundle)(
563-
name=bundle.name,
564-
consume=True,
565-
)
576+
return BundleConsumer(bundle)
566577

567578

568579
@attr.s()
@@ -609,7 +620,7 @@ def _convert_targets(targets, target):
609620
)
610621
raise InvalidArgument(msg % (t, type(t)))
611622
while isinstance(t, Bundle):
612-
if t.consume:
623+
if isinstance(t, BundleConsumer):
613624
note_deprecation(
614625
f"Using consumes({t.name}) doesn't makes sense in this context. "
615626
"This will be an error in a future version of Hypothesis.",

hypothesis-python/tests/cover/test_stateful.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
reproduce_failure,
2525
seed,
2626
settings as Settings,
27+
strategies as st,
2728
)
2829
from hypothesis.control import current_build_context
2930
from hypothesis.database import ExampleDatabase
@@ -1346,3 +1347,24 @@ def use_directly(self, bun):
13461347

13471348
Machine.TestCase.settings = Settings(stateful_step_count=5, max_examples=10)
13481349
run_state_machine_as_test(Machine)
1350+
1351+
1352+
def test_use_bundle_within_other_strategies():
1353+
class Class:
1354+
def __init__(self, value):
1355+
self.value = value
1356+
1357+
class Machine(RuleBasedStateMachine):
1358+
my_bundle = Bundle("my_bundle")
1359+
1360+
@initialize(target=my_bundle)
1361+
def set_initial(self, /) -> str:
1362+
return "sample text"
1363+
1364+
@rule(instance=st.builds(Class, my_bundle))
1365+
def check(self, instance):
1366+
assert isinstance(instance, Class)
1367+
assert isinstance(instance.value, str)
1368+
1369+
Machine.TestCase.settings = Settings(stateful_step_count=5, max_examples=10)
1370+
run_state_machine_as_test(Machine)

0 commit comments

Comments
 (0)