Skip to content

Commit

Permalink
Merge pull request #3164 from Zac-HD/defer-builds-inference
Browse files Browse the repository at this point in the history
Register `builds()` of recursive types with constraints
  • Loading branch information
Zac-HD committed Nov 29, 2021
2 parents 4d7506f + 3ffecc1 commit 96d0e4a
Show file tree
Hide file tree
Showing 5 changed files with 60 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))
)
5 changes: 5 additions & 0 deletions hypothesis-python/src/hypothesis/extra/ghostwriter.py
Expand Up @@ -53,6 +53,11 @@
-e, --except OBJ_NAME dotted name of exception(s) to ignore
-h, --help Show this message and exit.
.. tip::
Using a light theme? Hypothesis respects `NO_COLOR <https://no-color.org/>`__
and :envvar:`DJANGO_COLORS=light <django:DJANGO_COLORS>`.
.. note::
The ghostwriter requires :pypi:`black`, but the generated code only
Expand Down
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)) # type: ignore
return BuildsStrategy(target, args, kwargs)


Expand Down
3 changes: 3 additions & 0 deletions hypothesis-python/src/hypothesis/utils/terminal.py
Expand Up @@ -23,6 +23,9 @@ def guess_background_color():
See also https://stackoverflow.com/questions/2507337/ and
https://unix.stackexchange.com/questions/245378/
"""
django_colors = os.getenv("DJANGO_COLORS")
if django_colors in ("light", "dark"):
return django_colors
# Guessing based on the $COLORFGBG environment variable
try:
fg, *_, bg = os.getenv("COLORFGBG").split(";")
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 96d0e4a

Please sign in to comment.