Writing Validators¶
PayloadValidator is the main validator class that must be sub-classed to write validators for validating your JSON. For example:
>>> class PersonValidator(PayloadValidator):
... name = datatypes.String()
... age = datatypes.Integer()
>>>
>>> payload = dict(name='Man', age=23)
>>> PersonValidator().validate(payload)
(True, None)
Every field/key in the payload that you wish to validate must be added as an attribute in the sub-class of incoming.PayloadValidator. The attribute must be initialized and should be a an object of one of the classes of incoming.datatypes. These classes provide validation tests. See the Available Datatypes. And see Creating your own datatypes.
Validating Nested JSON¶
Validating nested JSON is similar to how you validate other payloads. Simply, write a validator for each JSON and then use the incoming.datatypes.JSON datatype to validate the nested JSON:
>>> class AddressValidator(PayloadValidator):
... street = datatypes.String()
... country = datatypes.String()
...
>>> class PersonValidator(PayloadValidator):
... name = datatypes.String()
... age = datatypes.Integer()
... address = datatypes.JSON(AddressValidator)
...
>>> PersonValidator().validate(dict(name='Some name', age=19, address=dict(street='Brannan, SF', country='USA')))
(True, None)
>>>
>>> PersonValidator().validate(dict(name='Some name', age=19, address=dict(street='Brannan, SF', country=0)))
(False, {'address': ['Invalid data. Expected JSON.', {'country': ['Invalid data. Expected a string.']}]})
If you want to use a nested class instead, just remember to define the inner class before using it, so that the name of the inner class is available in the scope of the parent class:
>>> class PersonValidator(PayloadValidator):
... class AddressValidator(PayloadValidator):
... street = datatypes.String()
... country = datatypes.String()
...
... name = datatypes.String()
... age = datatypes.Integer()
... address = datatypes.JSON(AddressValidator)
...
>>> PersonValidator().validate(dict(name='Some name', age=19, address=dict(street='Brannan, SF', country='USA')))
(True, None)
>>>
>>> PersonValidator().validate(dict(name='Some name', age=19, address=dict(street='Brannan, SF', country=0)))
(False, {'address': ['Invalid data. Expected JSON.', {'country': ['Invalid data. Expected a string.']}]})
Custom error messages for every field¶
Every datatype provides its own default error message (_DEFAULT_ERROR) which are very generic and good enough if you are using just those datatypes and not validation functions. However, you would mostly want to have your own error messages for every field according to your application’s requirements.
incoming allows you to specify different error messages for every field so that whenever validation fails, these error messages are used instead of the default error messages.
For example:
def validate_age(val, *args, **kwargs):
if not isinstance(val, int):
return False
if val < 0:
return False
return True
class PersonValidator(PayloadValidator):
required = False
name = datatypes.String(required=True, error='Name must be a string.')
age = datatypes.Function(validate_age,
error='Age can never be negative.')
hobbies = datatypes.Array(error='Please provide hobbies in an array.')
Custom validation methods¶
incoming lets you write custom validation methods for validating complex cases as well. These functions must return a bool, True if validation passes, else False.
A few things to keep in mind¶
- A validation function can be any callable. The callable should be passed to incoming.datatypes.Function as an argument.
- If the callable is a regular method or classmethod or staticmethod on on the validator class, then pass just the name of the method as a string.
- Validation functions (callables) must return bool values only. True shall be passed when the validation test passes, False otherwise.
- Validation callables get the following arguments: * val - the value which must be validated * key - the field/key in the payload that held val. * payload - the entire payload that is being validated. * errors - incoming.incoming.PayloadErrors object.
- For the above point mentioned above, you may want to use your validation methods elsewhere outside the scope of incoming where the above arguments are not necessary. In that case, use *args and **kwargs.
Writing validation functions¶
Write your validation functions anywhere and pass incoming.datatypes.Function the function you have written for validation.
def validate_age(val, *args, **kwargs):
if not isinstance(val, int):
return False
if val < 0:
return False
return True
class PersonValidator(PayloadValidator):
required = False
name = datatypes.String(required=True)
age = datatypes.Function(validate_age)
hobbies = datatypes.Array()
Validation functions bound by other classes¶
If you would like to organize your validation functions by keeping them in a different class for some sort of organization that you prefer, you can do that like so:
class Validations(object):
# You can write staticmethods
@staticmethod
def validate_age(val, *args, **kwargs):
if not isinstance(val, int):
return False
if val < 0:
return False
return True
# You can write classmethods as well
@classmethod
def validate_hobbies(val, *args, **kwargs):
if not isinstance(val, list):
return False
return True
class PersonValidator(PayloadValidator):
required = False
name = datatypes.String(required=True)
age = datatypes.Function(Validations.validate_age)
hobbies = datatypes.Function(Validations.validate_hobbies)
Validation methods in incoming.PayloadValidator sub-classes¶
For the sake of organization, it would probably make more sense to have all the validation methods in your validator classes. You can do that like so:
class PersonValidator(PayloadValidator):
required = True
name = datatypes.String()
# Note that validation method's name is provided as an str value
age = datatypes.Function('validate_age')
hobbies = datatypes.Function('validate_hobbies')
sex = datatypes.Function('validate_sex')
# You can write regular methods for validation
def validate_age(self, val, *args, **kwargs):
if not isinstance(val, int):
return False
if val < 0:
return False
return True
# You can write staticmethods for validation
@staticmethod
def validate_hobbies(val, *args, **kwargs):
if not isinstance(val, list):
return False
return True
# You can write classmethods for validation
@classmethod
def validate_sex(cls, val, *wargs, **kwargs):
if val not in ('male', 'female'):
return False
return True
Specifying required fields¶
required can be set on all the fields or on particular fields as well. All fields are required by default. You can explicitly specify this like so:
>>> class PersonValidator(PayloadValidator):
... required = False
...
... name = datatypes.String(required=True)
... age = datatypes.Integer(required=True)
... hobbies = datatypes.Array()
>>>
>>> payload = dict(name='Man', age=23)
>>> PersonValidator().validate(payload)
(True, None)
Depending upon your use-case, you may have more required fields than fields that are not required and vice-versa. incoming can help you with that. The above example can be written this way as well:
>>> class PersonValidator(PayloadValidator):
... required = True
...
... name = datatypes.String())
... age = datatypes.Integer()
... hobbies = datatypes.Array(required=False)
>>>
>>> payload = dict(name='Man', age=23)
>>> PersonValidator().validate(payload)
(True, None)
Overriding required at the time of validation¶
There can be some cases in which you might want to override the required setting defined in the validator class at the time of validating the payload.
This can be done like so:
>>> class PersonValidator(PayloadValidator):
... required = False
...
... name = datatypes.String(required=True)
... age = datatypes.Integer(required=True)
... hobbies = datatypes.Array()
>>>
>>> payload = dict(name='Man', age=23)
>>> PersonValidator().validate(payload, required=True)
(False, {'hobbies': ['Expecting a value for this field.']})
Error messages for required fields¶
Check incoming.PayloadValidator.required_error for the default error message for required fields i.e. when required fields are missing in the payload.
Override this error message like so:
>>> class PersonValidator(PayloadValidator):
... required = False
... required_error = 'A value for this field must be provided.'
...
... name = datatypes.String(required=True)
... age = datatypes.Integer(required=True)
... hobbies = datatypes.Array()
strict Mode¶
strict mode, when turned on, makes sure that no extra key/field in the payload is provided. If any extra key/field is provided, validation fails and reports in the errors.
Example:
>>> class PersonValidator(PayloadValidator):
... strict = True
...
... name = datatypes.String()
... age = datatypes.Integer()
... hobbies = datatypes.Array(required=False)
>>>
>>> payload = dict(name='Man', age=23, extra=0)
>>> PersonValidator().validate(payload)
(False, {'extra': ['Unexpected field.']})
Note
strict mode is turned off by default.
Overriding strict at the time of validation¶
There can be some cases in which you might want to override the strict setting defined in the validator class at the time of validating the payload.
This can be done like so:
>>> class PersonValidator(PayloadValidator):
... strict = True
...
... name = datatypes.String()
... age = datatypes.Integer()
... hobbies = datatypes.Array(required=False)
>>>
>>> payload = dict(name='Man', age=23, extra=0)
>>> PersonValidator().validate(payload, strict=False)
(True, None)
Error messages for strict fields¶
Check incoming.PayloadValidator.strict_error for the default error message for strict mode i.e. when strict mode is on and when extra fields are present in the payload.
Override this error message like so:
>>> class PersonValidator(PayloadValidator):
... strict = True
... strict_error = 'This field is not allowed.'
...
... name = datatypes.String()
... age = datatypes.Integer()
... hobbies = datatypes.Array(required=False)
PayloadValidator Class¶
- class incoming.PayloadValidator(*args, **kwargs)¶
Main validator class that must be sub-classed to define the schema for validating incoming payload.
- required = True¶
If all fields/keys are required by default When required is set in the incoming.PayloadValidator sub-class, then all the fields are required by default. In case a field must not be required, it should be set at the field/key level.
Note
this attribute can be overridden in the sub-class.
- required_error = 'Expecting a value for this field.'¶
Error message used for reporting when a required field is missing
Note
this attribute can be overridden in the sub-class.
- strict = False¶
strict mode is off by default. strict mode makes sure that any field that is not defined in the schema, validation result would fail and the extra key would be reported in errors.
Note
this attribute can be overridden in the sub-class.
- strict_error = 'Unexpected field.'¶
The error message that will be used when strict mode is on and an extra field is present in the payload.
Note
this attribute can be overridden in the sub-class.
- validate(payload, required=None, strict=None)¶
Validates a given JSON payload according to the rules defiined for all the fields/keys in the sub-class.
Parameters: - payload (dict) – deserialized JSON object.
- required (bool) – if every field/key is required and must be present in the payload.
- strict (bool) – if validate() should detect and report any fields/keys that are present in the payload but not defined in the sub-class.
Returns: a tuple of two items. First item is a bool indicating if the payload was successfully validated and the second item is None. If the payload was not valid, then then the second item is a dict of errors.
PayloadErrors Class¶
- class incoming.incoming.PayloadErrors¶
PayloadErrors holds errors detected by PayloadValidator and provides helper methods for working with errors. An instance of this class maintains a dictionary of errors where the keys are supposed to be the type of error and the value of the keys are list objects that hold error strings.
Ideally, the keys should be name of the field/key in the payload and the value should be a list object that holds errors related to that field.
- has_errors()¶
Checks if any errors were added to an object of this class.
Returns bool: True if has errors, else False.
- to_dict()¶
Return a dict of errors with keys as the type of error and values as a list of errors.
Returns dict: a dictionary of errors