class C(Catalyst): max_value = IntegerField() min_value = IntegerField() def pre_dump(self, obj): return self.pre_load(obj) def post_dump(self, data, original_data): assert original_data is not None return self.post_load(data) def pre_load(self, data): keys = {field.key for field in self._load_fields.values()} extra_keys = set(data.keys()) - keys if extra_keys: raise ValidationError( f'This keys should not be present: {extra_keys}.') return data def post_load(self, data): if data['max_value'] < data['min_value']: raise ValidationError( '"max_value" must be larger than "min_value".') return data def pre_load_many(self, data): assert len(data) < 3 return data
def test_field_group(self): group = FieldGroup(declared_fields=['num']) self.assertFalse(hasattr(group, 'fields')) fields = {'num': IntegerField(), 'xxx': IntegerField()} group.set_fields(fields) self.assertEqual(set(group.fields), {'num'}) with self.assertRaises(ValueError): group.set_fields({}) with self.assertRaises(TypeError): group.set_fields({'num': None}) self.assertIsNone(group.load(None)) self.assertIsNone(group.dump(None)) # test "*" all fields, exclude FieldGroup group = FieldGroup(declared_fields='*') fields['group'] = group group.set_fields(fields) self.assertSetEqual(set(group.fields), {'xxx', 'num'}) # test override method @group.set_dump @group.set_load def test_override(data): data['xxx'] = 1 return data self.assertEqual(test_override, group.dump) self.assertEqual(test_override, group.load) self.assertEqual(group.dump({})['xxx'], 1) self.assertEqual(group.load({})['xxx'], 1) @group.set_dump def self_and_group(self, data, group): assert self is group assert isinstance(self, FieldGroup) data['xxx'] = 1 return data self.assertEqual(group.dump, self_and_group) self.assertEqual(group.dump({})['xxx'], 1)
class C(Catalyst): a = IntegerField() b = IntegerField() no_extra = FieldGroup(declared_fields=('a', 'b')) @staticmethod @no_extra.set_dump def inject_kwargs(data, **kwargs): assert set(kwargs) == {'group', 'original_method'} return data @staticmethod @no_extra.set_load def check_no_extra(data, original_data, group: FieldGroup = None): extra_fields = set(original_data) - set(group.declared_fields) if extra_fields: raise ValidationError(f"Invalid fields: '{extra_fields}'.") return data
class TestDataCatalyst(Catalyst): string = StringField(min_length=2, max_length=12, dump_default='default', load_default='default') integer = IntegerField(minimum=0, maximum=12, load_required=True) float_field = FloatField(name='float_', key='float', minimum=-1.1, maximum=1.1) bool_field = BooleanField(name='bool_', key='bool') func = CallableField(name='func', key='func', func_args=(1, 2, 3)) list_ = ListField(StringField())
def test_except_exception(self): catalyst = Catalyst(schema={'a': IntegerField(minimum=0)}, except_exception=(ValueError, ValidationError)) result = catalyst.load({'a': 'x'}) self.assertFalse(result.is_valid) self.assertIsInstance(result.errors['a'], ValueError) result = catalyst.load({'a': -1}) self.assertFalse(result.is_valid) self.assertIsInstance(result.errors['a'], ValidationError) with self.assertRaises(TypeError): catalyst.load(1) with self.assertRaises(TypeError): catalyst.load({'a': []})
def test_nest_field(self): with self.assertRaises(TypeError): NestedField() with self.assertRaises(TypeError): field = NestedField(Catalyst) fields = {'name': StringField(max_length=3)} field = NestedField(Catalyst(fields), name='a', key='a') self.assertEqual(field.dump({'name': '1'}), {'name': '1'}) self.assertEqual(field.dump({'name': '1234'}), {'name': '1234'}) with self.assertRaises(ValidationError): field.dump({'n': 'm'}) with self.assertRaises(ValidationError): field.dump(1) self.assertEqual(field.load({'name': '1'}), {'name': '1'}) self.assertDictEqual(field.load({'n': 'm'}), {}) with self.assertRaises(ValidationError): field.load({'name': '1234'}) with self.assertRaises(ValidationError): field.load(1) # list with dict items field = NestedField(Catalyst({'x': IntegerField()}), many=True) data = [{'x': 1}, {'x': 2}] self.assertListEqual(data, field.load(data)) data = [{'x': 1}, {'x': 'x'}] with self.assertRaises(ValidationError) as cm: field.load(data) result = cm.exception.detail self.assertIsInstance(result.errors[1]['x'], ValueError) data = [{'x': 1}, None] with self.assertRaises(ValidationError) as cm: field.load(data) result = cm.exception.detail self.assertIsInstance(result.errors[1]['load'], TypeError)
def test_separated_field(self): field = SeparatedField(separator=None) self.assertEqual(field.load('1 2 3'), ['1', '2', '3']) self.assertEqual(field.load('1'), ['1']) self.assertEqual(field.load(''), []) self.assertEqual(field.load({}), ['{}']) self.assertEqual(field.dump([1, '2', 3]), '1 2 3') self.assertEqual(field.dump([]), '') self.assertEqual(field.dump(None), None) with self.assertRaises(ValidationError): field.load(None) field = SeparatedField(IntegerField()) self.assertEqual(field.load('1,2,3'), [1, 2, 3]) self.assertEqual(field.dump([1, '2', 3]), '1,2,3') with self.assertRaises(ValidationError): field.load('1,a,3') with self.assertRaises(ValidationError): field.dump([1, 'a', 3]) with self.assertRaises(ValidationError) as cm: field.load({}) result = cm.exception.detail self.assertEqual(result.invalid_data[0], '{}')
class SumCatalyst(Catalyst): a = IntegerField() b = IntegerField() total = SumFields(decimal_field, declared_fields='*')
class TransformCatalyst(Catalyst): a = IntegerField() coordinate = NestedField(coordinate_catalyst) transform = TransformNested('coordinate')
def test_transform_nested(self): coordinate_catalyst = Catalyst({ 'x': IntegerField(), 'y': IntegerField() }) class TransformCatalyst(Catalyst): a = IntegerField() coordinate = NestedField(coordinate_catalyst) transform = TransformNested('coordinate') catalyst = TransformCatalyst() # test wrong fields with self.assertRaises(ValueError): catalyst.transform.set_fields(fields={}) with self.assertRaises(TypeError) as cm: catalyst.transform.set_fields( fields={'coordinate': IntegerField()}) self.assertIn('NestedField', str(cm.exception)) with self.assertRaises(ValueError) as cm: catalyst.transform.set_fields( fields={ 'coordinate': NestedField(coordinate_catalyst, many=True) }) self.assertIn('many=True', str(cm.exception)) # test valid loading_data = {'a': 0, 'x': 1, 'y': -1} dumping_data = {'a': 0, 'coordinate': {'x': 1, 'y': -1}} result = catalyst.load(loading_data) self.assertTrue(result.is_valid) self.assertDictEqual(result.valid_data, dumping_data) result = catalyst.dump(dumping_data) self.assertTrue(result.is_valid) self.assertDictEqual(result.valid_data, loading_data) # test invalid invalid_loading_data = {'a': 0, 'x': 'x', 'y': -1} result = catalyst.load(invalid_loading_data) self.assertFalse(result.is_valid) self.assertSetEqual(set(result.errors), {'x'}) self.assertDictEqual(result.invalid_data, {'x': 'x'}) invalid_dumping_data = {'a': 0, 'coordinate': {'x': 'x', 'y': -1}} result = catalyst.dump(invalid_dumping_data) self.assertFalse(result.is_valid) self.assertSetEqual(set(result.errors), {'coordinate'}) self.assertDictEqual(result.invalid_data, {'coordinate': {'x': 'x'}}) # test change process methods fields = {'x': catalyst.coordinate} with self.assertRaises(AttributeError): TransformNested('x', dump_method='wrong').set_fields(fields) with self.assertRaises(AttributeError): TransformNested('x', load_method='wrong').set_fields(fields) class ReversedTransformCatalyst(TransformCatalyst): coordinate = NestedField(coordinate_catalyst, dump_required=False) transform = TransformNested('coordinate', dump_method='flat_to_nested', load_method='nested_to_flat') reversed_catalyst = ReversedTransformCatalyst() dumping_data = {'a': 0, 'x': 1, 'y': -1} loading_data = {'a': 0, 'coordinate': {'x': 1, 'y': -1}} result = reversed_catalyst.dump(dumping_data) self.assertTrue(result.is_valid) self.assertDictEqual(result.valid_data, loading_data) result = reversed_catalyst.load(loading_data) self.assertTrue(result.is_valid) self.assertDictEqual(result.valid_data, dumping_data)
class B(A): raise_error = True b = IntegerField() c = FloatField()
class A(Catalyst): a = IntegerField() b = IntegerField() args = ListField(IntegerField()) kwargs = NestedField(Kwargs())
class Kwargs(Catalyst): c = IntegerField()
def test_list_field(self): with self.assertRaises(TypeError): ListField() with self.assertRaises(TypeError): field = ListField(FloatField) field = ListField(item_field=FloatField()) # dump self.assertListEqual(field.dump([1.0, 2.0, 3.0]), [1.0, 2.0, 3.0]) self.assertListEqual(field.dump([]), []) self.assertEqual(field.dump(None), None) with self.assertRaises(TypeError): field.dump(1) # load self.assertListEqual(field.load([1, 2, 3]), [1.0, 2.0, 3.0]) self.assertListEqual(field.load([]), []) with self.assertRaises(ValidationError) as cm: field.load([1, 'a', 3]) result = cm.exception.detail self.assertIsInstance(result.errors[1], ValueError) self.assertEqual(result.invalid_data[1], 'a') self.assertEqual(result.valid_data, [1.0, 3.0]) with self.assertRaises(TypeError): field.load(1) with self.assertRaises(ValidationError): field.load(None) field = ListField(item_field=FloatField(), all_errors=False) with self.assertRaises(ValidationError) as cm: field.load([1, 'a', 'b']) result = cm.exception.detail self.assertEqual(set(result.errors), {1}) self.assertEqual(result.invalid_data[1], 'a') self.assertEqual(result.valid_data, [1.0]) # two-dimensional array field = ListField(ListField(IntegerField())) data = [[1], [2]] self.assertListEqual(data, field.load(data)) data = [[1], ['x']] with self.assertRaises(ValidationError) as cm: field.load(data) result = cm.exception.detail self.assertIsInstance(result.errors[1][0], ValueError) data = [[1], None] with self.assertRaises(ValidationError) as cm: field.load(data) result = cm.exception.detail self.assertIsInstance(result.errors[1], ValidationError) # list with dict items field = ListField(NestedField(Catalyst({'x': IntegerField()}))) data = [{'x': 1}, {'x': 2}] self.assertListEqual(data, field.load(data)) data = [{'x': 1}, {'x': 'x'}] with self.assertRaises(ValidationError) as cm: field.load(data) result = cm.exception.detail self.assertIsInstance(result.errors[1]['x'], ValueError) data = [{'x': 1}, None] with self.assertRaises(ValidationError) as cm: field.load(data) result = cm.exception.detail self.assertIsInstance(result.errors[1], ValidationError) field = ListField(IntegerField(), 2, 3) self.assertListEqual(field.load(['1', '2']), [1, 2]) with self.assertRaises(ValidationError): field.load(['1']) with self.assertRaises(ValidationError): field.load(['1', '2', '3', '4'])
class ComparisonCatalyst(Catalyst): lower_limit = IntegerField() upper_limit = IntegerField() comparison = CompareFields('upper_limit', '>', 'lower_limit')
class B: base = IntegerField() a = IntegerField() b = IntegerField()
class C2(Catalyst): group = TestSetFields(declared_fields='*') field = IntegerField()
class User(Catalyst): uid = IntegerField() name = StringField()
class C(Catalyst): nums = ListField(IntegerField())
class A(Catalyst): raise_error = True a = IntegerField() b = StringField()
def test_dump_and_load(self): # test dump dump_data = TestData(string='xxx', integer=1, float_=1.1, bool_=True, list_=['a', 'b']) # dump from object result = test_catalyst.dump(dump_data) self.assertDictEqual( result.valid_data, { 'bool': True, 'float': 1.1, 'func': 6, 'integer': 1, 'list_': ['a', 'b'], 'string': 'xxx' }) # dump from dict dump_data_dict = { 'float_': 1.1, 'integer': 1, 'string': 'xxx', 'bool_': True, 'func': dump_data.func, 'list_': ['a', 'b'] } self.assertEqual( test_catalyst.dump(dump_data_dict).valid_data, result.valid_data) # test load load_data = { 'string': 'xxx', 'integer': 1, 'float': 1.1, 'bool': True, 'list_': ['a', 'b'] } load_result = { 'string': 'xxx', 'integer': 1, 'float_': 1.1, 'bool_': True, 'list_': ['a', 'b'] } # test valid data result = test_catalyst.load(load_data) self.assertTrue(result.is_valid) self.assertFalse(result.errors) self.assertFalse(result.invalid_data) self.assertDictEqual(result.valid_data, load_result) # test invalid data: wrong type result = test_catalyst.load(1) self.assertFalse(result.is_valid) self.assertEqual(result.valid_data, {}) self.assertEqual(result.invalid_data, 1) self.assertEqual(set(result.errors), {'load'}) # test process_aliases test_catalyst.process_aliases['load'] = 'xxx' result = test_catalyst.load(1) self.assertFalse(result.is_valid) self.assertEqual(set(result.errors), {'xxx'}) test_catalyst.process_aliases.clear() # test invalid data: validate errors invalid_data = {'string': 'xxx' * 20, 'integer': 100, 'float': 2} result = test_catalyst.load(invalid_data) self.assertFalse(result.is_valid) self.assertDictEqual(result.invalid_data, invalid_data) self.assertEqual(set(result.errors), {'string', 'integer', 'float'}) self.assertDictEqual(result.valid_data, {}) # test invalid_data: parse errors invalid_data = {'string': 'x', 'integer': 'str', 'float': []} result = test_catalyst.load(invalid_data) self.assertFalse(result.is_valid) self.assertDictEqual(result.invalid_data, invalid_data) self.assertEqual(set(result.errors), {'string', 'integer', 'float'}) self.assertIsInstance(result.errors['string'], ValidationError) self.assertIsInstance(result.errors['integer'], ValueError) self.assertIsInstance(result.errors['float'], TypeError) # raise_error & all_errors result = test_catalyst.load(invalid_data, raise_error=False) self.assertFalse(result.is_valid) self.assertEqual(set(result.errors), {'string', 'integer', 'float'}) with self.assertRaises(ValidationError) as ctx: test_catalyst.load(invalid_data, raise_error=True) self.assertEqual(set(ctx.exception.detail.errors), {'string', 'integer', 'float'}) catalyst_2 = TestDataCatalyst(all_errors=False) result = catalyst_2.load(invalid_data, raise_error=False) self.assertFalse(result.is_valid) self.assertEqual(len(result.errors), 1) with self.assertRaises(ValidationError) as ctx: catalyst_2.load(invalid_data, raise_error=True) self.assertEqual(len(ctx.exception.detail.errors), 1) # wrong process name with self.assertRaises(ValueError): test_catalyst._make_processor(1, False) # test return missing return_missing = lambda v: missing c = Catalyst( { 'a': IntegerField( formatter=return_missing, parser=return_missing, minimum=0) }, raise_error=True) result = c.load({'a': 1}) self.assertEqual(result.valid_data, {}) with self.assertRaises(ValidationError) as cm: c.dump({'a': 1}) result = cm.exception.detail self.assertIn('required', str(result.errors['a']))
def test_int_field(self): field = IntegerField(name='integer', key='integer', minimum=-10, maximum=100, error_messages={'not_between': '{self.maximum}'}) # dump self.assertEqual(field.dump(1), 1) self.assertEqual(field.dump(1.6), 1) self.assertEqual(field.dump('10'), 10) # load self.assertEqual(field.load(0), 0) self.assertEqual(field.load(1), 1) self.assertEqual(field.load('1'), 1) self.assertEqual(field.load(None), None) with self.assertRaises(ValidationError) as cm: field.load(111) self.assertEqual(cm.exception.msg, '100') with self.assertRaises(ValueError): field.load('') with self.assertRaises(ValueError): field.load('asd') with self.assertRaises(TypeError): field.load([])