Skip to content

Commit

Permalink
Error on pointless class attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
Zac-HD committed Nov 19, 2023
1 parent fdf1c4f commit b5ce73a
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 5 deletions.
17 changes: 14 additions & 3 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
RELEASE_TYPE: patch
RELEASE_TYPE: minor

This patch updates our vendored `list of top-level domains <https://www.iana.org/domains/root/db>`__,
which is used by the provisional :func:`~hypothesis.provisional.domains` strategy.
This release makes it an error to assign ``settings = settings(...)``
as a class attribute on a :class:`~hypothesis.stateful.RuleBasedStateMachine`.
This has never had any effect, and it should be used as a decorator instead:

.. code-block: python
class BadMachine(RuleBasedStateMachine):
"""This doesn't do anything, and is now an error!"""
settings = settings(derandomize=True)
@settings(derandomize=True)
class GoodMachine(RuleBasedStateMachine):
"""This is the right way to do it :-)"""
14 changes: 12 additions & 2 deletions hypothesis-python/src/hypothesis/stateful.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,10 @@ def run_state_machine(factory, data):
class StateMachineMeta(type):
def __setattr__(cls, name, value):
if name == "settings" and isinstance(value, Settings):
descr = f"settings({value.show_changed()})"
raise AttributeError(
f"Assigning {cls.__name__}.settings = {value} does nothing. Assign "
f"to {cls.__name__}.TestCase.settings, or use @{value} as a decorator "
f"Assigning {cls.__name__}.settings = {descr} does nothing. Assign "
f"to {cls.__name__}.TestCase.settings, or use @{descr} as a decorator "
f"on the {cls.__name__} class."
)
return super().__setattr__(name, value)
Expand Down Expand Up @@ -255,6 +256,15 @@ def __init__(self) -> None:
self._initialize_rules_to_run = copy(self.initialize_rules())
self._rules_strategy = RuleStrategy(self)

if isinstance(s := vars(type(self)).get("settings"), Settings):
tname = type(self).__name__
descr = f"settings({s.show_changed()})"
raise InvalidDefinition(
f"Assigning settings = {descr} as a class attribute does nothing. "
f"Assign to {tname}.TestCase.settings, or use @{descr} as a decorator "
f"on the {tname} class."
)

def _pretty_print(self, value):
if isinstance(value, VarReference):
return value.name
Expand Down
16 changes: 16 additions & 0 deletions hypothesis-python/tests/cover/test_stateful.py
Original file line number Diff line number Diff line change
Expand Up @@ -1215,3 +1215,19 @@ def test_min_steps_argument():

# (oh, and it's OK if you ask for more than we're actually going to take)
run_state_machine_as_test(MinStepsMachine, _min_steps=20)


class ErrorsOnClassAttributeSettings(RuleBasedStateMachine):
settings = Settings(derandomize=True)

@rule()
def step(self):
pass


def test_fails_on_settings_class_attribute():
with pytest.raises(
InvalidDefinition,
match="Assigning .+ as a class attribute does nothing",
):
run_state_machine_as_test(ErrorsOnClassAttributeSettings)

0 comments on commit b5ce73a

Please sign in to comment.