Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hypothesis fails with ForwardRefs and recursive pydantic models #3519

Closed
apirogov opened this issue Nov 30, 2022 · 3 comments · Fixed by #3521
Closed

Hypothesis fails with ForwardRefs and recursive pydantic models #3519

apirogov opened this issue Nov 30, 2022 · 3 comments · Fixed by #3521
Labels
bug something is clearly wrong here interop how to play nicely with other packages

Comments

@apirogov
Copy link

apirogov commented Nov 30, 2022

I would like to use hypothesis to generate instances of pydantic models, but it looks like it cannot handle recursive models with ForwardRefs.

(Quite) minimal example triggering the problem:

from pydantic import BaseModel
from typing import Optional, Union
from hypothesis import given, strategies as st

class Dummy(BaseModel):
    x: Optional[Union[int, Dummy]]

Dummy.update_forward_refs()


@given(st.builds(Dummy))
def test_dummy(obj):
    print(obj.json(indent=2))

When running this with pytest, it fails with:

E           hypothesis.errors.ResolutionFailed: Could not resolve ForwardRef('Optional[Union[int, Dummy]]') to a strategy; consider using register_type_strategy

.../site-packages/hypothesis/strategies/_internal/types.py:442: ResolutionFailed

Extra info:

  • Python 3.8
  • pydantic 1.10.2
  • hypothesis 6.58
  • pytest 7.0.1
@Zac-HD
Copy link
Member

Zac-HD commented Dec 2, 2022

Thanks for the report - I cut this down further to:

import typing
from pydantic import BaseModel
from hypothesis.internal import compat

class Dummy(BaseModel):
    x: "typing.Union[int, Dummy]"

Dummy.update_forward_refs()
print(typing.get_type_hints(Dummy))
print(compat.get_type_hints(Dummy))
{'__slots__': typing.Tuple[str, ...], 'x': typing.Union[int, __main__.Dummy]}
{'__slots__': typing.Tuple[str, ...], 'x': ForwardRef('typing.Union[int, Dummy]'), 'data': typing.Any, 'return': <class 'NoneType'>}

digging even further, I think this is because Hypothesis prioritizes the annotations in a .__signature__ attribute over the results of typing.get_type_hints(). That still seems correct in general, but arguably we shouldn't do so when the new value is a ForwardRef and there's an existing non-forward-ref annotation.

@Zac-HD Zac-HD added bug something is clearly wrong here interop how to play nicely with other packages labels Dec 2, 2022
@rsokl
Copy link
Contributor

rsokl commented Dec 3, 2022

With #3521

from pydantic import BaseModel
from typing import Optional, Union
from hypothesis import given, strategies as st

class Dummy(BaseModel):
    x: Union[int, "Dummy"]

Dummy.update_forward_refs()


@given(st.builds(Dummy))
def test_dummy(obj):
    print(obj)

test_dummy()
x=0
x=22658
x=Dummy(x=0)
x=Dummy(x=16792)
x=Dummy(x=0)
x=0
x=0
x=2271479883078318998
x=Dummy(x=0)
x=Dummy(x=Dummy(x=0))
...

@apirogov
Copy link
Author

apirogov commented Dec 5, 2022

Thanks for the quick fix! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug something is clearly wrong here interop how to play nicely with other packages
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants