3

Currently I am using hypothesis fixed_dictionaries strategy to generate a dictionary with specific keys and data types that are considered valid for my application. I need a strategy which produces this fixed dictionary as well as others with specific keys removed. Or a dictionary with a certain minimal set of keys with optional additional ones, preferably in a way that produces the various combinations of these optional keys.

This is an example of the json schema that needs to be validated, with the 2 optional fields. I'd like to generate all possible valid data for this schema.

'user_stub': {
    '_id':        {'type': 'string'},
    'username':   {'type': 'string'},
    'social':     {'type': 'string'},
    'api_name':   {'type':     'string',
                   'required': False},
    'profile_id': {'type':     'integer',
                   'required': False},
}

This is what I came up with but it is incorrect because it retains the keys but uses None as the value, and I want instead that the keys are removed.

return st.fixed_dictionaries({
    '_id':        st.text(),
    'username':   st.text(),
    'social':     st.text(),
    'api_name':   st.one_of(st.none(),
                            st.text()),
    'profile_id': st.one_of(st.none(),
                            st.integers()),
})

EDIT: updated composite strategy ->

Seems like it would be best to separate the additional optional dictionaries based on the type of data being returned, otherwise might get keys with mismatched values.

@st.composite
def generate_data(draw):
    base_data = st.fixed_dictionaries({
        '_id':      st.text(),
        'username': st.text(),
        'social':   st.text(),
    })
    optional_strs = st.dictionaries(
        keys=st.just('api_name'),
        values=st.text()
    )
    optional_ints = st.dictionaries(
        keys=st.just('profile_id'),
        values=st.integers()
    )

    b = draw(base_data)
    s = draw(optional_strs)
    i = draw(optional_ints)
    return {**b, **s, **i}  # noice
Milo
  • 335
  • 4
  • 15

2 Answers2

5

Draw from a fixed_dictionaries strategy for the required entries and from a dictionaries strategy for the optional ones. Then combine them into one dictionary by merging them.

You can either do that in your test, or create a composite strategy that does that for you.

das-g
  • 9,718
  • 4
  • 38
  • 80
  • Thanks for the rapid reply, I had overlooked the regular dictionaries strategy, am I understanding correctly that it will reduce down to a dictionary with no keys then? That is exactly what I need thank you – Milo May 30 '18 at 11:38
  • "am I understanding correctly that it will reduce down to a dictionary with no keys then?" That's my understanding, too, but I haven't tried it, merely read the documentation. – das-g May 31 '18 at 09:53
2

das-g provided a working solution!

Another way to do this would be

fixed_dictionaries(dict(
    required=text(),
    optional=none()|text(),  # note None first, for shrinking
)).map(
    lambda d: {k: v for k, v in d.items() if v is not None}
)
Zac Hatfield-Dodds
  • 2,455
  • 6
  • 19