def test_adding_multiple_errors(self): builder = ValidationErrorBuilder() builder.add_error('foo', 'error 1') builder.add_error('bar', 'error 2') builder.add_error('bam', 'error 3') assert {'foo': 'error 1', 'bar': 'error 2', 'bam': 'error 3'} == \ builder.errors
def test_raise_errors_on_non_empty_builder_raises_ValidationError(self): builder = ValidationErrorBuilder() builder.add_error('foo', 'error 1') with pytest.raises(ValidationError) as excinfo: builder.raise_errors() assert excinfo.value.messages == builder.errors
def __call__(self, value, context=None): if not is_sequence(value): self._fail('invalid', data=value) error_builder = ValidationErrorBuilder() for idx, item in enumerate(value): for validator in self.validators: try: validator(item) except ValidationError as ve: error_builder.add_errors({idx: ve.messages}) error_builder.raise_errors()
def load(self, data, *args, **kwargs): if data is MISSING or data is None: self._fail('required') if not is_dict(data): self._fail('invalid') errors_builder = ValidationErrorBuilder() result = {} for name, field in iteritems(self.fields): try: loaded = field.load(name, data, *args, **kwargs) if loaded != MISSING: result[name] = loaded except ValidationError as ve: errors_builder.add_error(name, ve.messages) if self.allow_extra_fields is False: field_names = [name for name, _ in iteritems(self.fields)] for name in data: if name not in field_names: errors_builder.add_error(name, self._error_messages['unknown']) errors_builder.raise_errors() result = super(Object, self).load(result, *args, **kwargs) if self.constructor: result = self.constructor(**result) return result
def test_adding_multiple_nested_errors(self): builder = ValidationErrorBuilder() builder.add_error('foo.bar', 'error 1') builder.add_error('foo.baz.bam', 'error 2') builder.add_error('quux', 'error 3') assert {'foo': {'bar': 'error 1', 'baz': {'bam': 'error 2'}}, 'quux': 'error 3'} == builder.errors
def test_adding_multiple_nested_errors(self): builder = ValidationErrorBuilder() builder.add_error('foo.bar', 'error 1') builder.add_error('foo.baz.bam', 'error 2') builder.add_error('quux', 'error 3') assert { 'foo': { 'bar': 'error 1', 'baz': { 'bam': 'error 2' } }, 'quux': 'error 3' } == builder.errors
def dump(self, obj, *args, **kwargs): if obj is MISSING or obj is None: self._fail('required') errors_builder = ValidationErrorBuilder() result = OrderedDict() if self.ordered else {} for name, field in iteritems(self.fields): try: dumped = field.dump(name, obj, *args, **kwargs) if dumped != MISSING: result[name] = dumped except ValidationError as ve: errors_builder.add_error(name, ve.messages) errors_builder.raise_errors() return super(Object, self).dump(result, *args, **kwargs)
def load(self, data, context=None): """Deserialize data from primitive types. Raises :exc:`~lollipop.errors.ValidationError` if data is invalid. :param data: Data to deserialize. :param context: Context data. :returns: Loaded data :raises: :exc:`~lollipop.errors.ValidationError` """ errors_builder = ValidationErrorBuilder() for validator in self._validators: try: validator(data, context) except ValidationError as ve: errors_builder.add_errors(ve.messages) errors_builder.raise_errors() return data
def dump(self, value, *args, **kwargs): if value is MISSING or value is None: self._fail('required') if not is_list(value): self._fail('invalid') errors_builder = ValidationErrorBuilder() items = [] for idx, item in enumerate(value): try: items.append(self.item_type.dump(item, *args, **kwargs)) except ValidationError as ve: errors_builder.add_errors({idx: ve.messages}) errors_builder.raise_errors() return super(List, self).dump(items, *args, **kwargs)
def load(self, data, *args, **kwargs): if data is MISSING or data is None: self._fail('required') # TODO: Make more intelligent check for collections if not is_list(data): self._fail('invalid') errors_builder = ValidationErrorBuilder() items = [] for idx, item in enumerate(data): try: items.append(self.item_type.load(item, *args, **kwargs)) except ValidationError as ve: errors_builder.add_errors({idx: ve.messages}) errors_builder.raise_errors() return super(List, self).load(items, *args, **kwargs)
def dump(self, value, *args, **kwargs): if value is MISSING or value is None: self._fail('required') if not is_dict(value): self._fail('invalid') errors_builder = ValidationErrorBuilder() result = {} for k, v in iteritems(value): value_type = self.value_types.get(k) if value_type is None: continue try: result[k] = value_type.dump(v, *args, **kwargs) except ValidationError as ve: errors_builder.add_error(k, ve.messages) errors_builder.raise_errors() return super(Dict, self).dump(result, *args, **kwargs)
def dump(self, value, *args, **kwargs): if value is MISSING or value is None: self._fail('required') if not is_list(value): self._fail('invalid') if len(value) != len(self.item_types): self._fail('invalid_length', expected_length=len(self.item_types)) errors_builder = ValidationErrorBuilder() result = [] for idx, (item_type, item) in enumerate(zip(self.item_types, value)): try: result.append(item_type.dump(item, *args, **kwargs)) except ValidationError as ve: errors_builder.add_errors({idx: ve.messages}) errors_builder.raise_errors() return super(Tuple, self).dump(result, *args, **kwargs)
def test_empty_errors(self): builder = ValidationErrorBuilder() assert None == builder.errors
def test_raise_errors_on_empty_builder_does_nothing(self): builder = ValidationErrorBuilder() builder.raise_errors()
def test_adding_merging_errors(self): builder = ValidationErrorBuilder() builder.add_errors({'foo': {'bar': 'error 1'}}) builder.add_errors({'foo': {'baz': 'error 2'}}) assert {'foo': {'bar': 'error 1', 'baz': 'error 2'}} == builder.errors
def test_adding_nested_errors(self): builder = ValidationErrorBuilder() builder.add_error('foo.bar', 'error 1') assert {'foo': {'bar': 'error 1'}} == builder.errors
def test_adding_field_error(self): builder = ValidationErrorBuilder() builder.add_error('foo', 'error 1') assert {'foo': 'error 1'} == builder.errors
def load_into(self, obj, data, inplace=True, *args, **kwargs): """Load data and update existing object. :param obj: Object to update with deserialized data. :param data: Raw data to get value to deserialize from. :param bool inplace: If True update data inplace; otherwise - create new data. :param kwargs: Same keyword arguments as for :meth:`Type.load`. :returns: Updated object. :raises: :exc:`~lollipop.errors.ValidationError` """ if obj is None: raise ValueError('Load target should not be None') if data is MISSING: return if data is None: self._fail('required') if not is_dict(data): self._fail('invalid') errors_builder = ValidationErrorBuilder() data1 = {} for name, field in iteritems(self.fields): try: if name in data: # Load new data value = field.load_into(obj, name, data, inplace=not self.immutable and inplace) else: # Retrive data from existing object value = field.get_value(name, obj, *args, **kwargs) if value is not MISSING: data1[name] = value except ValidationError as ve: errors_builder.add_error(name, ve.messages) if self.allow_extra_fields is False: field_names = [name for name, _ in iteritems(self.fields)] for name in data: if name not in field_names: errors_builder.add_error(name, self._error_messages['unknown']) errors_builder.raise_errors() data2 = super(Object, self).load(data1, *args, **kwargs) if self.immutable or not inplace: result = data2 if self.constructor: result = self.constructor(**result) else: for name, field in iteritems(self.fields): if name not in data: continue try: field.set_value(name, obj, data2.get(name, MISSING)) except ValidationError as ve: raise ValidationError({name: ve.messages}) result = obj return result