def enforce(self, data, schema, item_index, tree): # yo dawg, a recursive validator within a recursive validator anyone? if is_callable(schema) and hasattr(schema, '__validator_leaf__'): return schema(data[item_index], tree) if isinstance(data[item_index], dict) and isinstance(schema, tuple): try: _validator = Validator(data[item_index], schema) _validator.validate() except Invalid: e = sys.exc_info()[1] tree.append('list[%s]' % item_index) tree.extend(e.path) raise Invalid(e.schema_item, tree, reason=e._reason, pair='value') # FIXME this is utterly redundant, and also happens in # RecursiveValidator except SchemaError: e = sys.exc_info()[1] tree.extend(e.path) raise SchemaError('', tree, reason=e._reason, pair='value') elif isinstance(schema, tuple) and not isinstance(data[item_index], (tuple, dict)): raise SchemaError(data, tree, reason='iterable contains single items, schema does not') else: try: if is_callable(schema): schema(data[item_index]) else: ensure(data[item_index] == schema) except AssertionError: reason = sys.exc_info()[1] tree.append('list[%s]' % item_index) raise Invalid(schema, tree, reason=reason, pair='item')
def dictionary(_object, *args): """ Validates a given input is of type dictionary. Example usage:: data = {'a' : {'b': 1}} schema = ('a', dictionary) You can also use this as a decorator, as a way to check for the input before it even hits a validator you may be writing. .. note:: If the argument is a callable, the decorating behavior will be triggered, otherwise it will act as a normal function. """ error_msg = 'not of type dictionary' if is_callable(_object): _validator = _object @wraps(_validator) def decorated(value): ensure(isinstance(value, dict), error_msg) return _validator(value) return decorated try: ensure(isinstance(_object, dict), error_msg) except AssertionError: if args: msg = 'did not pass validation against callable: dictionary' raise Invalid('', msg=msg, reason=error_msg, *args) raise
def string(_object): """ Validates a given input is of type string. Example usage:: data = {'a' : 21} schema = (string, 21) You can also use this as a decorator, as a way to check for the input before it even hits a validator you may be writing. .. note:: If the argument is a callable, the decorating behavior will be triggered, otherwise it will act as a normal function. """ if is_callable(_object): _validator = _object @wraps(_validator) def decorated(value): ensure(isinstance(value, basestring), "not of type string") return _validator(value) return decorated ensure(isinstance(_object, basestring), "not of type string")
def enforce(self, data, schema, item_index, tree): if isinstance(data[item_index], dict) and isinstance(schema, tuple): try: _validator = Validator(data[item_index], schema) _validator.validate() except Invalid: e = sys.exc_info()[1] tree.append('list[%s]' % item_index) tree.extend(e.path) raise Invalid(e.schema_item, tree, pair='value') except SchemaError: # FIXME this is utterly redundant, and also happens in RecursiveValidator e = sys.exc_info()[1] tree.extend(e.path) raise SchemaError('', tree, reason=e._reason, pair='value') elif isinstance(schema, tuple) and not isinstance(data[item_index], (tuple, dict)): raise SchemaError(data, tree, reason='iterable contains single items, schema does not') else: try: if is_callable(schema): schema(data[item_index]) else: assert data[item_index] == schema except AssertionError: tree.append('list[%s]' % item_index) raise Invalid(schema, tree, pair='item')
def _format_message(self): if self._msg: return self._msg if is_callable(self.schema_item): msg = "did not pass validation against callable: %s" % (self.schema_item.__name__) else: msg = "did not match %s" % (repr(self.schema_item)) return msg
def _format_message(self): reason = self._formatted_reason() if self._msg: return self._msg if is_callable(self.schema_item): msg = "did not pass validation against callable: %s"\ "%s" % (self.schema_item.__name__, reason) else: msg = "did not match %s%s" % (repr(self.schema_item), reason) return msg
def enforce(self, data, schema, item_index, tree): # yo dawg, a recursive validator within a recursive validator anyone? if is_callable(schema) and hasattr(schema, '__validator_leaf__'): return schema(data[item_index], tree) if isinstance(data[item_index], dict) and isinstance(schema, tuple): try: _validator = Validator(data[item_index], schema) _validator.validate() except Invalid: e = sys.exc_info()[1] tree.append('list[%s]' % item_index) tree.extend(e.path) raise Invalid(e.schema_item, tree, reason=e._reason, pair='value') # FIXME this is utterly redundant, and also happens in # RecursiveValidator except SchemaError: e = sys.exc_info()[1] tree.extend(e.path) raise SchemaError('', tree, reason=e._reason, pair='value') elif isinstance(schema, tuple) and not isinstance(data[item_index], (tuple, dict)): raise SchemaError( data, tree, reason='iterable contains single items, schema does not') else: try: if is_callable(schema): schema(data[item_index]) else: ensure(data[item_index] == schema) except AssertionError: reason = sys.exc_info()[1] tree.append('list[%s]' % item_index) raise Invalid(schema, tree, reason=reason, pair='item')
def enforce(data_item, schema_item, tree, pair): if is_callable(schema_item): try: schema_item(data_item) except AssertionError: e = sys.exc_info()[1] raise Invalid(schema_item, tree, reason=e, pair=pair) else: try: assert data_item == schema_item except AssertionError: if pair == 'value': tree.append(data_item) raise Invalid(schema_item, tree, pair=pair)
def __call__(self, data, tree): index = len(data) - 1 validator = IterableValidator(data, self.schema, [], index=index) for item_index in range(len(data)): try: return validator.leaf(item_index) except Invalid: pass tree.append('list[]') if is_callable(self.schema): msg = "did not contain any valid items against callable: %s" % self.schema.__name__ else: msg = "did not contain any valid items matching %s" % repr(self.schema) raise Invalid(self.schema, tree, pair='value', msg=msg)
def enforce(self, data, schema, item_index, tree): # yo dawg, a recursive validator within a recursive validator anyone? if is_callable(schema) and hasattr(schema, '__validator_leaf__'): return schema(data, tree) try: _validate = Validator({}, self.schema) _validate.data = {0: data[item_index]} _validate.validate() except Invalid: e = sys.exc_info()[1] tree.extend(e.path) raise Invalid(e.schema_item, tree, pair='value', msg=e._msg, reason=e._reason) except SchemaError: e = sys.exc_info()[1] tree.extend(e.path) raise SchemaError('', tree, reason=e._reason, pair='value')
def __call__(self, data, tree): schema = expand_schema(self.schema) self.safe_type(data, tree) index = len(data) - 1 validator = IterableValidator(data, schema, [], index=index, name='AnyItem') for item_index in range(len(data)): try: return validator.leaf(item_index) except Invalid: pass tree.append('list[]') if is_callable(schema): msg = "did not contain any valid items against callable: %s" % schema.__name__ else: msg = "did not contain any valid items matching %s" % repr(schema) raise Invalid(schema, tree, pair='value', msg=msg)
def not_empty(_object): """ Validates the given input (has to be a valid data structure) is empty. Input *has* to be one of: `list`, `dict`, or `string`. It is specially useful when most of the validators being created are dealing with data structures that should not be empty. """ if is_callable(_object): _validator = _object @wraps(_validator) @instance_of() def decorated(value): ensure(value, "%s is empty" % safe_repr(value)) return _validator(value) return decorated try: ensure(len(_object), "%s is empty" % safe_repr(_object)) except TypeError: raise AssertionError("not of any valid types: [list, dict, str]")
def enforce(data_item, schema_item, tree, pair): schema_is_optional = hasattr(schema_item, 'is_optional') if is_callable(schema_item) and not schema_is_optional: try: schema_item(data_item) except AssertionError: e = sys.exc_info()[1] if pair == 'value': tree.append(data_item) raise Invalid(schema_item, tree, reason=e, pair=pair) else: try: if schema_is_optional: if is_empty(data_item): # we received nothing here return ensure(data_item == schema_item()) else: ensure(data_item == schema_item) except AssertionError: e = sys.exc_info()[1] if pair == 'value': tree.append(data_item) raise Invalid(schema_item, tree, reason=e, pair=pair)
def __init__(self, *args): for i in args: if not is_callable(i): raise TypeError("got a non-callable argument: %s" % repr(i)) self.args = args
def test_is_not_callable(self): result = utils.is_callable(1) assert result is False
def test_is_callable(self): result = utils.is_callable(self.fake_callable) assert result is True
def optional(_object): """ This decorator has a double functionality, it can wrap validators and make them optional or it can wrap keys and make that entry optional. **Optional Validator:** Allows to have validators work only when there is a value that contains some data, otherwise it will just not pass the information to the actual validator and will not fail as a result. As any normal decorator, it can be used corectly with the decorator syntax or in the actual schema. This is how it would look in a schema:: ('key', optional(my_validator)) Where ``my_validator`` can be any validator that accepts a single argument. In case a class based validator is being used (like the ``recursive`` or ``iterables`` then it would look like:: ('key', optional(class_validator(('key', 'value')))) Of course, the schema should vary depending on your needs, it is just the way of constructing the validator call that should be important. **Optional Keys:** Sometimes a given data structure may present optional entries. For example this data:: data = {'required': 1, 'optional': 2} To represent this, you will need to declare the `optional` key in the schema but by wrapping the key with this decorator you will basically tell the validation engine that if that key is present it should be validated, otherwise, it should be skipped. This is how the schema would look:: schema = (('required', 1), (optional('optional'), 1)) The above schema would allow data that is missing the ``optional`` key. The data below would pass validation without any issues:: data = {'required': 1} """ if is_callable(_object): validator = _object @wraps(validator) def decorated(value): if value: return validator(value) return return decorated else: def optional(*args): return _object optional.is_optional = True optional._object = _object return optional
def traverser(self, data, schema, tree): """ Traverses the dictionary, recursing onto itself if it sees appropriate key/value pairs that indicate that there is a need for more validation in a branch below us. """ if hasattr(schema, '__validator_leaf__'): return schema(data, tree) if hasattr(schema, 'must_validate'): # cherry picking? if not len(schema.must_validate): reason = "must_validate attribute must not be empty" raise SchemaError(data, tree, reason=reason) data = sift(data, schema.must_validate) schema = self.sanitize_optionals(data, schema, tree) self.is_alpha_ordered(data, schema, tree) validated_indexes = [] skip_missing_indexes = getattr(schema, 'must_validate', False) if len(data) < len(schema): # we have missing required items in data, but we don't know # which ones so find what may fail: data_keys = [v[0] for v in data.values()] schema_keys = [v[0] for v in schema.values()] def enforce_once(data_keys, schema_key): # XXX Go through all the data keys and try and see if they pass # validation against the schema. At this point it is impossible # to know which data key corresponds to what schema key # (because schema keys can be a function/callable) so it is # a *very* naive way to try and detect which one might be # missing for data_key in data_keys: failed = None try: enforce(data_key, schema_key, tree, pair='key') return except Invalid: failed = data_key, schema_key if failed: return failed # if there are no callables in the schema keys, just # find the missing data key directly if all([not is_callable(s) for s in schema_keys]): for schema_key in schema_keys: if schema_key not in data_keys: msg = "required key in data is missing: %s" % str( schema_key) raise Invalid(None, tree, reason=msg, pair='key') for schema_key in schema_keys: failure = enforce_once(data_keys, schema_key) if failure: _, failed_schema_key = failure msg = "required key in data is missing: %s" % str( failed_schema_key) raise Invalid(None, tree, reason=msg, pair='key') for index in range(len(data)): self.length_equality(data, schema, index, tree) key, value = data[index] skey, svalue = schema[index] tree.append(key) # Validate the key before anything, to prevent recursing self.key_leaf(data[index], schema[index], tree) # If a dict is a value we need to recurse. # XXX Should we check isinstance(value, ndict) ? if isinstance(value, dict) and len(value): self.traverser(value, svalue, tree) else: self.value_leaf(data[index], schema[index], tree) if tree: tree.pop() validated_indexes.append(index) # XXX There is a chance we might have missing items from # the incoming data that are labeled as required from the schema # we should make sure *here* that we account for that and raise # the appropriate exception. Since the loop finished and everything # seems to have passed, this lack of check will give false positives. missing_indexes = set(schema.keys()).difference(validated_indexes) if missing_indexes: if skip_missing_indexes: return for i in missing_indexes: if not hasattr(schema[i], 'is_optional'): required_key = schema[i][0] tree.append('item[%s]' % i) msg = "required item in schema is missing: %s" % str( required_key) raise Invalid(required_key, tree, reason=msg, pair='key')
def traverser(self, data, schema, tree): """ Traverses the dictionary, recursing onto itself if it sees appropriate key/value pairs that indicate that there is a need for more validation in a branch below us. """ if hasattr(schema, '__validator_leaf__'): return schema(data, tree) if hasattr(schema, 'must_validate'): # cherry picking? if not len(schema.must_validate): reason = "must_validate attribute must not be empty" raise SchemaError(data, tree, reason=reason) data = sift(data, schema.must_validate) schema = self.sanitize_optionals(data, schema, tree) self.is_alpha_ordered(data, schema, tree) validated_indexes = [] skip_missing_indexes = getattr(schema, 'must_validate', False) if len(data) < len(schema): # we have missing required items in data, but we don't know # which ones so find what may fail: data_keys = [v[0] for v in data.values()] schema_keys = [v[0] for v in schema.values()] def enforce_once(data_keys, schema_key): # XXX Go through all the data keys and try and see if they pass # validation against the schema. At this point it is impossible # to know which data key corresponds to what schema key # (because schema keys can be a function/callable) so it is # a *very* naive way to try and detect which one might be # missing for data_key in data_keys: failed = None try: enforce(data_key, schema_key, tree, pair='key') return except Invalid: failed = data_key, schema_key if failed: return failed # if there are no callables in the schema keys, just # find the missing data key directly if all([not is_callable(s) for s in schema_keys]): for schema_key in schema_keys: if schema_key not in data_keys: msg = "required key in data is missing: %s" % str(schema_key) raise Invalid(None, tree, reason=msg, pair='key') for schema_key in schema_keys: failure = enforce_once(data_keys, schema_key) if failure: _, failed_schema_key = failure msg = "required key in data is missing: %s" % str(failed_schema_key) raise Invalid(None, tree, reason=msg, pair='key') for index in range(len(data)): self.length_equality(data, schema, index, tree) key, value = data[index] skey, svalue = schema[index] tree.append(key) # Validate the key before anything, to prevent recursing self.key_leaf(data[index], schema[index], tree) # If a dict is a value we need to recurse. # XXX Should we check isinstance(value, ndict) ? if isinstance(value, dict) and len(value): self.traverser(value, svalue, tree) else: self.value_leaf(data[index], schema[index], tree) if tree: tree.pop() validated_indexes.append(index) # XXX There is a chance we might have missing items from # the incoming data that are labeled as required from the schema # we should make sure *here* that we account for that and raise # the appropriate exception. Since the loop finished and everything # seems to have passed, this lack of check will give false positives. missing_indexes = set(schema.keys()).difference(validated_indexes) if missing_indexes: if skip_missing_indexes: return for i in missing_indexes: if not hasattr(schema[i], 'is_optional'): required_key = schema[i][0] tree.append('item[%s]' % i) msg = "required item in schema is missing: %s" % str(required_key) raise Invalid(required_key, tree, reason=msg, pair='key')