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

Enhancement: support creation of Hypothesis strategies #387

Open
guacs opened this issue Sep 26, 2023 · 4 comments · May be fixed by #397
Open

Enhancement: support creation of Hypothesis strategies #387

guacs opened this issue Sep 26, 2023 · 4 comments · May be fixed by #397
Labels
enhancement New feature or request

Comments

@guacs
Copy link
Member

guacs commented Sep 26, 2023

Summary

I was playing around with hypothesis and noticed that it can't create strategies when there are constraints on the fields. So, I'm proposing that polyfactory supports creation of strategies for a given model, with the constraints being respected.

Currently, there seems to be plans to support this as seen in this issue, however that would require the users use the annotated types for the constraints. Furthermore, while pydantic v1 did use to integrate with hypothesis, the latest version does not so as documented here.

Would this be something that would be helpful and worth implementing? Opinions, @litestar-org/members?

Basic Example

from typing import Annotated, Any

import msgspec
from hypothesis import given
from hypothesis import strategies as st
from hypothesis.strategies import SearchStrategy
from msgspec import Struct
from msgspec.structs import asdict

from polyfactory.factories.msgspec_factory import MsgspecFactory
from polyfactory.field_meta import FieldMeta


class Foo(Struct):
    foo: Annotated[int, msgspec.Meta(ge=100)]


def handle_constrained_int(field_meta: FieldMeta) -> SearchStrategy[int]:
    return st.integers(min_value=field_meta.constraints.get("ge"))


class FooFactory(MsgspecFactory[Foo]):
    __model__ = Foo

    @classmethod
    def create_hypothesis_strategy(cls) -> SearchStrategy[Foo]:
        st_kwargs: dict[str, SearchStrategy[Any]] = {}
        for field in cls.get_model_fields():
            if field.annotation is int:
                st_kwargs[field.name] = handle_constrained_int(field)

        return st.builds(cls.__model__, **st_kwargs)


polyfactory_foo_st = FooFactory.create_hypothesis_strategy()
hypothesis_foo_st = st.builds(Foo)


@given(polyfactory_foo_st)
def test_polyfactory_foo_st(foo: Foo):
    foo_dict = asdict(foo)
    _ = msgspec.convert(foo_dict, Foo)


@given(hypothesis_foo_st)
def test_hypothesis_foo_st(foo: Foo):
    foo_dict = asdict(foo)
    _ = msgspec.convert(foo_dict, Foo) # this will fail the msgspec validation


if __name__ == "__main__":
    # test_polyfactory_foo_st()
    test_hypothesis_foo_st()

Drawbacks and Impact

No response

Unresolved questions

No response


Funding

  • If you would like to see an issue prioritized, make a pledge towards it!
  • We receive the pledge once the issue is completed & verified
Fund with Polar
@guacs guacs added the enhancement New feature or request label Sep 26, 2023
@sobolevn
Copy link
Member

Yes :)

See my https://sobolevn.me/2021/02/make-tests-a-part-of-your-app article, feel free to assign some tasks on me.

@guacs
Copy link
Member Author

guacs commented Sep 27, 2023

Yes :)

See my https://sobolevn.me/2021/02/make-tests-a-part-of-your-app article, feel free to assign some tasks on me.

That's great! I'll try to get the basic structure ready for it, and then you can make improvements/changes in that branch?

Also, for the following case from your article:

import deal

@deal.pre(lambda a, b: a >= 0 and b >= 0)
@deal.raises(ZeroDivisionError)  # this function can raise if `b=0`, it is ok
def div(a: int, b: int) -> float:
    return a / b

Do you think at some point mypy would be able to handle the annotated (maybe only for annotated-types) constraints as well? So, I would expect something like the following:

def div(a: Annotated[int, Ge(0)], b: Annotated[int, Ge(0)]) -> float:
    return a / b
    
a = -1
b = 23
div(a, b) # type check error

if a >= 0 and b >= 0:
    div(a, b) # no type check error

@sobolevn
Copy link
Member

By design mypy won't treat Annotated[T] as something different from just T. But! We have phantom-types for that :)

https://github.com/antonagestam/phantom-types

@guacs
Copy link
Member Author

guacs commented Sep 27, 2023

By design mypy won't treat Annotated[T] as something different from just T. But! We have phantom-types for that :)

https://github.com/antonagestam/phantom-types

Oh this is really cool

@guacs guacs linked a pull request Oct 1, 2023 that will close this issue
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants