Skip to content

Commit

Permalink
Register recursive types
Browse files Browse the repository at this point in the history
  • Loading branch information
Zac-HD committed Nov 29, 2021
1 parent 4d7506f commit 47b03ae
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 2 deletions.
19 changes: 19 additions & 0 deletions hypothesis-python/RELEASE.rst
@@ -0,0 +1,19 @@
RELEASE_TYPE: minor

This release teaches :func:`~hypothesis.strategies.builds` to use
:func:`~hypothesis.strategies.deferred` when resolving unrecognised type hints,
so that you can conveniently register strategies for recursive types
with constraints on some arguments (:issue:`3026`):

.. code-block:: python
class RecursiveClass:
def __init__(self, value: int, next_node: typing.Optional["SomeClass"]):
assert value > 0
self.value = value
self.next_node = next_node
st.register_type_strategy(
RecursiveClass,
st.builds(RecursiveClass, value=st.integers(min_value=1))
)
17 changes: 15 additions & 2 deletions hypothesis-python/src/hypothesis/strategies/_internal/core.py
Expand Up @@ -886,8 +886,21 @@ def builds(
raise InvalidArgument(
f"passed infer for {badargs}, but there is no type annotation"
)
for kw in set(hints) & (required | to_infer):
kwargs[kw] = from_type(hints[kw])
infer_for = {k: v for k, v in hints.items() if k in (required | to_infer)}
if infer_for:
from hypothesis.strategies._internal.types import _global_type_lookup

for kw, t in infer_for.items():
if (
getattr(t, "__module__", None) in ("builtins", "typing")
or t in _global_type_lookup
):
kwargs[kw] = from_type(t)
else:
# We defer resolution of these type annotations so that the obvious
# approach to registering recursive types just works. See
# https://github.com/HypothesisWorks/hypothesis/issues/3026
kwargs[kw] = deferred(lambda t=t: from_type(t))
return BuildsStrategy(target, args, kwargs)


Expand Down
18 changes: 18 additions & 0 deletions hypothesis-python/tests/cover/test_lookup.py
Expand Up @@ -547,6 +547,24 @@ def test_resolving_recursive_type():
assert isinstance(st.builds(Tree).example(), Tree)


class SomeClass:
def __init__(self, value: int, next_node: typing.Optional["SomeClass"]) -> None:
assert value > 0
self.value = value
self.next_node = next_node

def __repr__(self) -> str:
return f"SomeClass({self.value}, next_node={self.next_node})"


def test_resolving_recursive_type_with_registered_constraint():
with temp_registered(
SomeClass, st.builds(SomeClass, value=st.integers(min_value=1))
):
find_any(st.from_type(SomeClass), lambda s: s.next_node is None)
find_any(st.from_type(SomeClass), lambda s: s.next_node is not None)


@given(from_type(typing.Tuple[()]))
def test_resolves_empty_Tuple_issue_1583_regression(ex):
# See e.g. https://github.com/python/mypy/commit/71332d58
Expand Down

0 comments on commit 47b03ae

Please sign in to comment.