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

Document how to make a Strategy for enums #2693

Closed
ysangkok opened this issue Dec 8, 2020 · 11 comments · Fixed by #2695
Closed

Document how to make a Strategy for enums #2693

ysangkok opened this issue Dec 8, 2020 · 11 comments · Fixed by #2695
Assignees
Labels
legibility make errors helpful and Hypothesis grokable

Comments

@ysangkok
Copy link

ysangkok commented Dec 8, 2020

If one tries builds(Enum), it fails horribly:

>>> from hypothesis.strategies import builds
>>> from enum import Enum
>>> builds(Enum("One",[chr(x) for x in range(ord('A'),ord('C'))])).example()
Traceback:
  File "/home/janus/.pyenv/versions/3.9.0rc2/lib/python3.9/site-packages/hypothesis/strategies/_internal/core.py", line 1318, in <lambda>
    lambda value: target(*value[0], **value[1])  # type: ignore
TypeError: __call__() missing 1 required positional argument: 'value'

It would be nice if the right way was documented, or if builds detected that it was being called on an enum. It is a standard library class.

sampled_from(list(Enum)) does work, but it may not be obvious to everybody.

This was tested with version 5.41.5 on Python 3.9.0rc2.

@Zac-HD Zac-HD added the legibility make errors helpful and Hypothesis grokable label Dec 8, 2020
@Zac-HD
Copy link
Member

Zac-HD commented Dec 8, 2020

As the docs note, sampled_from(Enum) works directly, and even has special handling for Flag enums to generate combinations of options.

I actually don't think we should change the semantics of builds() away from "calls the target with arguments", even when there's a clear alternative interpretation (though it's not that clear given that enums are actually callable!). It's not a bad API style for interactive tools, but as a testing framework we prioritize clear and simple mental models to minimize the risk of surprising test behaviour.

However, I'd be happy to detect this particular problem and raise an error with a helpful message explaining what to do instead.

@Zac-HD Zac-HD self-assigned this Dec 8, 2020
@ysangkok
Copy link
Author

ysangkok commented Dec 8, 2020

Ok! The confusion mainly stems from the fact that I can build dataclasses using builds, even if they contain Enums. It seemed odd to me that I would call a different function based on whether the outermost type is a sum type or a product type. I would very much appreciate a more helpful error message! Thanks.

@Zac-HD
Copy link
Member

Zac-HD commented Dec 8, 2020

It seemed odd to me that I would call a different function based on whether the outermost type is a sum type or a product type.

Ah - the distinction is actually "builds(target) calls the target, sampled_from(target) returns elements from the target sequence". Sum-type vs product-type is a nice mental model to have, but not quite applicable here.

Knowing that's the distinction you had in mind will help me write the new error message though - thanks!

@ysangkok
Copy link
Author

Thanks a lot @Zac-HD , I appreciate it a lot!

@ThatXliner
Copy link

ThatXliner commented Apr 21, 2021

But how do we generate enums, though? I've tried

@st.composite
def generate_enum(draw):
    return draw(
        st.builds(
            Enum,
            st.from_regex(r"(?a)[_a-zA-Z][_a-zA-Z0-9]*"),
            st.from_regex(r"(?a)[_a-zA-Z][_a-zA-Z0-9]*( [_a-zA-Z][_a-zA-Z0-9]*)*"),
        )
    )

But got

TypeError: Attempted to reuse key: 'A'

and

ValueError: type name must not contain null characters

I also saw #2923 . Maybe I'm just dumb. Because what I'm trying is not working

@Zac-HD
Copy link
Member

Zac-HD commented Apr 21, 2021

This issue is about generating instances of a particular Enum subclass. To generate arbitrary Enum types, you could

from enum import Enum
from hypothesis import strategies as st 

def enums():
    names = st.text().filter(str.isidentifier)  # or st.from_regex(), etc.
    values = st.lists(names, min_size=1, unique=True).map(" ".join)
    return st.builds(Enum, names, values)

though a more efficient strategy for Python identifiers can be found here. You could also be more general about the values with st.lists(st.tuples(names, st.from_type(Hashable)), min_size=1, unique_by=itemgetter(0)) but it's unclear whether that's worth the trouble.

@ThatXliner
Copy link

ThatXliner commented Apr 21, 2021

Funny, because I also tried a little harder with making the enum strategy and I got (where variable_names() would be the identifier-generating strategy)

@st.composite
def generate_enum(draw):
    return draw(
        st.builds(
            Enum,
            variable_names(),
            st.dictionaries(variable_names(), st.text()),
        ),
    )

I think you guys should really add two new strategies: Enums and/or identifiers

@rsokl
Copy link
Contributor

rsokl commented Apr 21, 2021

I think you guys should really add two new strategies: Enums and/or identifiers

What is insufficient about the strategy that you and @Zac-HD both posted? It makes standard use of composite and builds, and it is concise and easy to reason about.

@ThatXliner
Copy link

I think you guys should really add two new strategies: Enums and/or identifiers

What is insufficient about the strategy that you and @Zac-HD both posted? It makes standard use of composite and builds, and it is concise and easy to reason about.

Hmm, true. Though it did take me a while to figure. I guess we shouldn't add too much extra strategies

@ThatXliner
Copy link

As the docs note, sampled_from(Enum) works directly, and even has special handling for Flag enums to generate combinations of options.

I get

 hypothesis.errors.InvalidArgument: Cannot sample from generate_enum(), not an ordered collection.

@Zac-HD
Copy link
Member

Zac-HD commented Apr 22, 2021

Because generate_enum() returns a strategy, not an ordered collection.

@HypothesisWorks HypothesisWorks locked as off-topic and limited conversation to collaborators Apr 22, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
legibility make errors helpful and Hypothesis grokable
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants