6
\$\begingroup\$

I created a Python function to check the types from a list and also check the keys inside an dictionary within that list.

I have the following data:

[
   dict(name='Frank', tags=['dog', 'cat']), 
   dict(name='Manfred', tags=['cat', 'chicken'])
]

My function looks like this:

def _validate_data(self, data):
    if not isinstance(data, list):
        raise('not a list')
    else:
        for element in data:
            if not isinstance(element, dict):
                raise('not a dictionary')
            else:
                if not all(key in element for key in ('name', 'tags')):
                    raise('Keys not inside dictionary')

This works fine, but I don't like the structure and I also think there may be a smarter way to code this function. I hope someone could give me some nice and helpful hints.

\$\endgroup\$
8
\$\begingroup\$

There're some modules that might help you get rid of the structure you're complaining about like marshmallow or voluptous and since you didn't added the reinventing-the wheel tag I guess that's perfectly okay.

For the sake of example, I'll refer to the former one because IMO it better fits our purpose (and is also probably clearer).

From the docs:

Marshmallow is an ORM/ODM/framework-agnostic library for converting complex datatypes, such as objects, to and from native Python datatypes.

In short, marshmallow schemas can be used to:

  1. Validate input data.
  2. Deserialize input data to app-level objects.
  3. Serialize app-level objects to primitive Python types. The serialized objects can then be rendered to standard formats such as JSON for use in an HTTP API.

First of, you'll need to define your Schema:

class DataSchema(Schema):
    name = fields.String(required=True)
    tags = fields.List(fields.String(), required=True)

In the above, name and tags are the keys of our dictionaries. In our class I've specified each key type (str and list). They're also mandatory, so I added required=True.

Next, to validate our top-level list, we need to instantiate our list item schema with many=True argument and then just load the data we need:

data, errors = DataSchema(many=True).load([
    {'name': 'Frank', 'tags': ['dog', 'cat']},
    {'name': 'Manfred', 'tags': ['dog', 'chicken']}
])

Printing the above:

print(data)
print(errors)

Will have the following output:

[{'name': 'Frank', 'tags': ['dog', 'cat']}, {'name': 'Manfred', 'tags': ['dog', 'chicken']}]
{}

Now, if we try to pass an invalid data to our dict, the errors will warn us about this. For example, passing a str instead of a list in our tags key will result in:

[{'name': 'Frank'}, {'name': 'Manfred', 'tags': ['dog', 'chicken']}]
{0: {'tags': ['Not a valid list.']}}

Full code:

from marshmallow import Schema, fields


class DataSchema(Schema):
    name = fields.String(required=True)
    tags = fields.List(fields.String(), required=True)

data, errors = DataSchema(many=True).load([
    {'name': 'Frank', 'tags': ['dog', 'cat']},
    {'name': 'Manfred', 'tags': ['dog', 'chicken']}
])

Now, IDK if the above will be valid for all the test-cases (e.g it might allow you to pass an empty list), but it should give you a good overview of what you can achieve with this module.

\$\endgroup\$

protected by Community Oct 2 '18 at 13:50

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.