def test_all(self): # type: () -> None schema = All(Constant('one'), UnicodeString()) self.assertEqual( schema.errors('one'), [], ) self.assertEqual( len(schema.errors('two')), 1, ) assert schema.introspect() == { 'type': 'all', 'requirements': [ { 'type': 'constant', 'values': ['one'] }, { 'type': 'unicode' }, ] } with pytest.raises(TypeError): # noinspection PyTypeChecker All('not a field') # type: ignore with pytest.raises(TypeError): All(Constant('one'), UnicodeString(), description=b'Not unicode') with pytest.raises(TypeError): All(Constant('one'), UnicodeString(), unsupported='argument')
def test_nullable(self): constant = Constant('one', 'two') schema = Nullable(constant) self.assertEqual([], schema.errors(None)) self.assertEqual([], schema.errors('one')) self.assertEqual([], schema.errors('two')) self.assertEqual(1, len(schema.errors('three'))) self.assertEqual( { 'type': 'nullable', 'nullable': constant.introspect() }, schema.introspect()) boolean = Boolean(description='This is a test description') schema = Nullable(boolean) self.assertEqual([], schema.errors(None)) self.assertIsNone(schema.errors(True)) self.assertIsNone(schema.errors(False)) self.assertEqual(1, len(schema.errors('true'))) self.assertEqual(1, len(schema.errors(1))) self.assertEqual({ 'type': 'nullable', 'nullable': boolean.introspect() }, schema.introspect()) string = UnicodeString() schema = Nullable(string) self.assertEqual([], schema.errors(None)) self.assertIsNone(schema.errors('hello, world')) self.assertEqual(1, len(schema.errors(b'hello, world'))) self.assertEqual({ 'type': 'nullable', 'nullable': string.introspect() }, schema.introspect())
def test_tuple(self): with pytest.raises(TypeError): Tuple(UnicodeString(), Integer(), Boolean(), additional_validator='Not a validator') # type: ignore field = Tuple(UnicodeString(), Integer(), Boolean()) assert field.errors(['foo', 'bar', 'baz']) == [Error(message='Not a tuple')] assert field.errors({'foo': 'bar'}) == [Error(message='Not a tuple')] assert field.errors({'foo', 'bar', 'baz'}) == [Error(message='Not a tuple')] assert field.errors( ('foo', 'bar', True)) == [Error(message='Not an integer', pointer='1')] assert field.errors(('foo', 12, False)) == [] assert field.errors(('foo', 12, True)) == [] class V(AdditionalCollectionValidator[TupleType[AnyType]]): def errors(self, value): if value[2] is not True: return [Error('The third value must be True', pointer='2')] return [] field = Tuple(UnicodeString(), Integer(), Boolean(), additional_validator=V()) assert field.errors(('foo', 12, False)) == [ Error(message='The third value must be True', pointer='2') ] assert field.errors(('foo', 12, True)) == []
def test_validate_method(self): schema = Dictionary( { 'name': UnicodeString(max_length=20), 'greeting': UnicodeString(), }, optional_keys=['greeting']) class Greeter(object): @classmethod @validate_method(schema, UnicodeString()) def greeter(cls, name, greeting='Hello'): # Special case to check return value stuff if name == 'error': return 5 return '%s, %s!' % (greeting, name) self.assertEqual(Greeter.greeter(name='Andrew'), 'Hello, Andrew!') self.assertEqual(Greeter.greeter(name='Andrew', greeting='Ahoy'), 'Ahoy, Andrew!') with self.assertRaises(ValidationError): Greeter.greeter(name='Andrewverylongnameperson') with self.assertRaises(ValidationError): Greeter.greeter(name='Andrew', greeeeeeting='Boo') with self.assertRaises(ValidationError): Greeter.greeter(name='error') with self.assertRaises(PositionalError): Greeter.greeter('Andrew')
def test_validator_arguments_validation(self): # type: () -> None with pytest.raises(ValueError): @validate_call(List(UnicodeString()), UnicodeString()) # type: ignore def something(_foo): pass with pytest.raises(ValueError): @validate_call(args=SchemalessDictionary(), kwargs=None, returns=UnicodeString()) # type: ignore def something_else(_foo): pass
class RequestSchemaPrediction(Dictionary): contents = { 'currency': UnicodeString(), 'country_code': UnicodeString(), 'address_postal_code': UnicodeString(), 'category': UnicodeString(), } optional_keys = [ 'currency', 'address_postal_code', ]
class ResponseSchemaPurchase(Dictionary): contents = { 'id': UnicodeString(), 'currency': UnicodeString(), 'address_postal_code': UnicodeString(), 'country_code': UnicodeString(), 'category': UnicodeString(), 'ticket_price_amount': Integer(), 'purchased_at': DateTime(), 'quantity_sold': Integer(), }
def test_list(self): # type: () -> None schema = List(UnicodeString(), min_length=4, max_length=8) assert schema.errors(['foo', 'bar', 'baz', 'qux']) == [] assert schema.errors(['foo', 'bar', 'baz']) == [Error('List is shorter than 4')] assert schema.errors( ['foo', 'bar', 'baz', 'qux', 'foo', 'bar', 'baz', 'qux', 'foo' ], ) == [Error('List is longer than 8')] with pytest.raises(ValueError): List(UnicodeString(), min_length=21, max_length=20)
def test_dictionary(self): with pytest.raises(TypeError): # noinspection PyTypeChecker Dictionary({'foo': UnicodeString()}, additional_validator='Not a validator') # type: ignore field = Dictionary({ 'foo': UnicodeString(), 'bar': Integer(), 'baz': UnicodeString(), }) assert field.errors(['foo', 'bar', 'baz']) == [Error(message='Not a dict')] assert field.errors( ('foo', 'bar', 'baz')) == [Error(message='Not a dict')] assert field.errors({'foo', 'bar', 'baz'}) == [Error(message='Not a dict')] assert field.errors({ 'foo': 'Hello', 'bar': 12, 'baz': True }) == [ Error(message='Not a unicode string', pointer='baz'), ] assert field.errors({ 'foo': 'Hello', 'bar': 12, 'baz': 'Goodbye' }) == [] class V(AdditionalCollectionValidator[Mapping[HashableType, AnyType]]): def errors(self, value): if value['foo'] != value['baz']: return [ Error('Value foo does not match value baz', pointer='foo') ] return [] field = field.extend(additional_validator=V()) assert field.errors({ 'foo': 'Hello', 'bar': 12, 'baz': 'Goodbye' }) == [ Error(message='Value foo does not match value baz', pointer='foo'), ] assert field.errors({'foo': 'Hello', 'bar': 12, 'baz': 'Hello'}) == []
def test_validate(self): # type: () -> None schema = Dictionary({ 'name': UnicodeString(max_length=20), 'greeting': UnicodeString(), }, optional_keys=('greeting', )) validate(schema, {'name': 'Andrew'}) validate(schema, {'name': 'Andrew', 'greeting': 'Ahoy-hoy'}) with self.assertRaises(ValidationError): validate(schema, {'name': 'Andrewverylongnameperson'}) with self.assertRaises(ValidationError): validate(schema, {'name': 'Andrew', 'greeeeeeting': 'Ahoy-hoy'})
def test_schemaless_dictionary(self): with pytest.raises(TypeError): # noinspection PyTypeChecker SchemalessDictionary( key_type=UnicodeString(), value_type=Integer(), additional_validator='Not a validator', # type: ignore ) field = SchemalessDictionary(key_type=UnicodeString(), value_type=Integer()) assert field.errors(['foo', 'bar', 'baz']) == [Error(message='Not a dict')] assert field.errors( ('foo', 'bar', 'baz')) == [Error(message='Not a dict')] assert field.errors({'foo', 'bar', 'baz'}) == [Error(message='Not a dict')] assert field.errors({ 'foo': 42, 'bar': 11, 'baz': 'Goodbye' }) == [ Error(message='Not an integer', pointer='baz'), ] assert field.errors({'foo': 42, 'bar': 11, 'baz': 91}) == [] class V(AdditionalCollectionValidator[Mapping[HashableType, AnyType]]): def errors(self, value): if value['foo'] != value['baz']: return [ Error('Value foo does not match value baz', pointer='foo') ] return [] field = SchemalessDictionary(key_type=UnicodeString(), value_type=Integer(), additional_validator=V()) assert field.errors({ 'foo': 42, 'bar': 11, 'baz': 91 }) == [ Error(message='Value foo does not match value baz', pointer='foo'), ] assert field.errors({'foo': 42, 'bar': 11, 'baz': 42}) == []
def test_schemaless_dict(self): # type: () -> None """ Tests the schemaless dict with some schema """ schema = SchemalessDictionary(Integer(), UnicodeString(), min_length=1, max_length=5) self.assertEqual(schema.errors({1: 'value'}), []) assert schema.errors( {}) == [Error('Dict contains fewer than 1 value(s)')] self.assertEqual( schema.errors({ 'x': 123, 2: 'foo', 3: 'bar', 4: 'baz', 5: 'qux', 6: 'too many' }), [ Error('Dict contains more than 5 value(s)'), Error('Not an integer', pointer='x'), Error('Not a unicode string', pointer='x'), ], ) self.assertEqual( schema.introspect(), { 'type': 'schemaless_dictionary', 'key_type': { 'type': 'integer' }, 'value_type': { 'type': 'unicode' }, 'max_length': 5, 'min_length': 1, }) with pytest.raises(ValueError): SchemalessDictionary(Integer(), UnicodeString(), min_length=12, max_length=11)
def test_python_path(self): # type: () -> None schema = PythonPath() assert schema.errors('tests.test_fields_meta.MY_FOO') == [] assert schema.errors('tests.test_fields_meta.MY_BAR') == [] assert schema.errors('tests.test_fields_meta:MY_QUX') == [] assert schema.errors('tests.test_fields_meta.MY_DICT') == [] assert schema.errors('tests.test_fields_meta:Qux.INNER_CONSTANT') == [] schema = PythonPath(ObjectInstance(Foo)) assert schema.errors('tests.test_fields_meta.MY_FOO') == [] assert schema.errors('tests.test_fields_meta.MY_BAR') == [] assert schema.errors('tests.test_fields_meta:MY_QUX') == [ Error('Not an instance of Foo') ] assert schema.errors('tests.test_fields_meta.MY_DICT') == [ Error('Not an instance of Foo') ] assert schema.errors('tests.test_fields_meta:Qux.INNER_CONSTANT') == [ Error('Not an instance of Foo') ] schema = PythonPath(SchemalessDictionary()) assert schema.errors('tests.test_fields_meta.MY_DICT') == [] assert schema.errors('tests.test_fields_meta:MY_QUX') == [ Error('Not a dict') ] schema = PythonPath(UnicodeString()) assert schema.errors('tests.test_fields_meta:Qux.INNER_CONSTANT') == [] assert schema.errors('tests.test_fields_meta.MY_DICT') == [ Error('Not a unicode string') ]
def test_schemaless_dict(self): """ Tests the schemaless dict with some schema """ schema = SchemalessDictionary(Integer(), UnicodeString()) self.assertEqual(schema.errors({1: u'value'}), []) self.assertEqual( schema.errors({'x': 123}), [ Error('Not an integer', pointer='x'), Error('Not a unicode string', pointer='x'), ], ) self.assertEqual( schema.introspect(), { 'type': 'schemaless_dictionary', 'key_type': { 'type': 'integer' }, 'value_type': { 'type': 'unicode' }, })
class Greeter(object): @classmethod @validate_method(schema, UnicodeString()) def greeter(cls, name, greeting='Hello'): # Special case to check return value stuff if name == 'error': return 5 return '%s, %s!' % (greeting, name)
def test_set(self): # type: () -> None with pytest.raises(TypeError): # noinspection PyTypeChecker Set(UnicodeString(), additional_validator='Not a validator') # type: ignore field = Set(UnicodeString()) assert field.errors( ('hello', 'goodbye')) == [Error(message='Not a set or frozenset')] assert field.errors({'hello': 'goodbye' }) == [Error(message='Not a set or frozenset')] assert field.errors(['hello', 'goodbye' ]) == [Error(message='Not a set or frozenset')] assert field.errors( {'hello', 2}) == [Error(message='Not a unicode string', pointer='[2]')] assert field.errors(frozenset( ('hello', 2))) == [Error(message='Not a unicode string', pointer='[2]')] assert field.errors({'hello', 'goodbye'}) == [] assert field.errors(frozenset(('hello', 'goodbye'))) == [] class V(AdditionalCollectionValidator[AbstractSet]): def errors(self, value): errors = [] for v in value: if v > 500: errors.append( Error('Whoop custom error', pointer='{}'.format(v))) return errors field = Set(Integer(), additional_validator=V()) assert field.errors({501, 'Not a number'}) == [ Error(message='Not an integer', pointer='[Not a number]') ] assert field.errors( {501, 499}) == [Error(message='Whoop custom error', pointer='501')] assert field.errors(frozenset( (501, 499))) == [Error(message='Whoop custom error', pointer='501')] assert field.errors({500, 499}) == [] assert field.errors(frozenset((500, 499))) == []
def test_all(self): schema = All(Constant('one'), UnicodeString()) self.assertEqual( schema.errors('one'), [], ) self.assertEqual( len(schema.errors('two')), 1, )
def test_sequence(self): # type: () -> None with pytest.raises(TypeError): # noinspection PyTypeChecker Sequence(UnicodeString(), additional_validator='Not a validator') # type: ignore field = Sequence(UnicodeString()) assert field.errors({'hello': 'goodbye'}) == [Error(message='Not a sequence')] assert field.errors({'hello', 'goodbye'}) == [Error(message='Not a sequence')] assert field.errors( ['hello', 2]) == [Error(message='Not a unicode string', pointer='1')] assert field.errors( (1, 'world')) == [Error(message='Not a unicode string', pointer='0')] assert field.errors(['hello', 'goodbye']) == [] assert field.errors(('hello', 'goodbye')) == [] class V(AdditionalCollectionValidator[SequenceType]): def errors(self, value): errors = [] for i, v in enumerate(value): if v > 500: errors.append( Error('Whoop another error', pointer='{}'.format(i))) return errors field = Sequence(Integer(), additional_validator=V()) assert field.errors([501, 'Not a number dude']) == [ Error(message='Not an integer', pointer='1') ] assert field.errors( [501, 499]) == [Error(message='Whoop another error', pointer='0')] assert field.errors( (501, 499)) == [Error(message='Whoop another error', pointer='0')] assert field.errors([500, 499]) == [] assert field.errors((500, 499)) == []
class Helper(object): @classmethod @validate_method(schema, UnicodeString()) def greeter(cls, name, greeting='Hello'): # Special case to check return value stuff if name == 'error': return 5 return '{}, {}!'.format(greeting, name) @staticmethod @validate_call(args=Tuple(Integer(), Integer()), kwargs=None, returns=Integer()) def args_method(one, two): return one + two # noinspection PyMethodMayBeStatic @validate_method( args=List(UnicodeString()), kwargs=SchemalessDictionary(value_type=UnicodeString()), returns=List(UnicodeString()), ) def args_and_kwargs_method(self, *args, **kwargs): return [s.format(**kwargs) for s in args]
def test_tuple(self): schema = Tuple(Integer(gt=0), UnicodeString(), Constant('I love tuples')) self.assertEqual(schema.errors((1, 'test', 'I love tuples')), []) # too short self.assertEqual( schema.errors((1, 'test')), [Error('Number of elements 2 does not match expected 3')]) # too long self.assertEqual( schema.errors((1, 'test', 'I love tuples', '... and coffee')), [Error('Number of elements 4 does not match expected 3')]) self.assertEqual(schema.errors(( -1, None, 'I hate tuples', )), [ Error('Value not > 0', pointer='0'), Error('Not a unicode string', pointer='1'), Error( 'Value is not "I love tuples"', code=ERROR_CODE_UNKNOWN, pointer='2', ), ]) self.assertEqual( schema.introspect(), { 'type': 'tuple', 'contents': [ { 'type': 'integer', 'gt': 0 }, { 'type': 'unicode' }, { 'type': 'constant', 'values': ['I love tuples'] }, ] })
from conformity.fields import ( Boolean, Dictionary, Integer, List, SchemalessDictionary, UnicodeString, ) ActionRequestSchema = Dictionary( { 'action': UnicodeString(), 'body': SchemalessDictionary(key_type=UnicodeString()), }, optional_keys=['body'], ) ControlHeaderSchema = Dictionary( { 'continue_on_error': Boolean(), }, allow_extra_keys=True, ) ContextHeaderSchema = Dictionary( { 'switches': List(Integer()), 'correlation_id': UnicodeString(), }, allow_extra_keys=True, )
def test_polymorph(self): card = Dictionary({ 'payment_type': Constant('card'), 'number': UnicodeString(), 'cvc': UnicodeString(description='Card Verification Code'), }) bankacc = Dictionary({ 'payment_type': Constant('bankacc'), 'routing': UnicodeString(description='US RTN or foreign equivalent'), 'account': UnicodeString(), }) schema = Polymorph( 'payment_type', { 'card': card, 'bankacc': bankacc, }, ) self.assertEqual( schema.errors({ 'payment_type': 'card', 'number': '1234567890123456', 'cvc': '000', }), [], ) self.assertEqual( schema.errors({ 'payment_type': 'bankacc', 'routing': '13456790', 'account': '13910399', }), [], ) self.assertEqual( schema.introspect(), { 'type': 'polymorph', 'contents_map': { 'bankacc': { 'type': 'dictionary', 'allow_extra_keys': False, 'contents': { 'account': { 'type': 'unicode' }, 'payment_type': { 'type': 'constant', 'values': ['bankacc'], }, 'routing': { 'type': 'unicode', 'description': 'US RTN or foreign equivalent', }, }, 'optional_keys': [], }, 'card': { 'type': 'dictionary', 'allow_extra_keys': False, 'contents': { 'cvc': { 'type': 'unicode', 'description': 'Card Verification Code', }, 'number': { 'type': 'unicode' }, 'payment_type': { 'type': 'constant', 'values': ['card'], }, }, 'optional_keys': [], }, }, 'switch_field': 'payment_type', }, )
def test_dictionary_subclass(self): # type: () -> None """ Tests that subclassing a Dictionary allows you to provide the same options as instantiating it. """ class Coordinate(Dictionary): contents = { 'x': Float(), 'y': Float(), 'z': Float(), } optional_keys = ('z', ) schema = Coordinate(description='Where the treasure is') # type: Base # Test the options work right self.assertEqual( schema.errors({ 'x': 4.4, 'y': 65.21 }), [], ) self.assertEqual( schema.errors({ 'x': 4.4, 'y': 65.21, 'z': 5542 }), [], ) self.assertEqual( len(schema.errors({ 'x': 'HERRING', 'z': 5542 })), 2, ) # Test you can't make a dict without contents with self.assertRaises(ValueError): Dictionary() # Test not overriding one field class TwoDeeCoordinate(Dictionary): contents = { 'x': Float(), 'y': Float(), } schema2d = TwoDeeCoordinate(description='Where the treasure is') self.assertEqual( len(schema2d.errors({ 'x': 3.14, 'z': 5542 })), 2, ) class Another(Dictionary): allow_extra_keys = True description = 'Yep' schema = Another({'foo': UnicodeString()}) assert schema.introspect() == { 'type': 'dictionary', 'contents': { 'foo': { 'type': 'unicode', } }, 'description': 'Yep', 'allow_extra_keys': True, 'optional_keys': [], } with pytest.raises(TypeError): # noinspection PyTypeChecker Another({'foo': UnicodeString()}, optional_keys=1234) # type: ignore class BadDict1(Dictionary): allow_extra_keys = 'not a bool' # type: ignore with pytest.raises(TypeError): BadDict1(contents={}) class BadDict2(Dictionary): description = b'not a unicode' # type: ignore with pytest.raises(TypeError): BadDict2(contents={}) class BadDict3(Dictionary): contents = 'not a dict' # type: ignore with pytest.raises(TypeError): BadDict3() class ExtraDict(Dictionary): contents = {} # type: ignore optional_keys = ('one', 'two' ) # type: TupleType[HashableType, ...] d = ExtraDict() assert d.optional_keys == frozenset({'one', 'two'}) class ExtraExtraDict(ExtraDict): optional_keys = () # type: TupleType[HashableType, ...] d = ExtraExtraDict() assert d.optional_keys == frozenset({})
def test_strings(self): # type: () -> None schema = UnicodeString() self.assertEqual([], schema.errors('')) self.assertEqual( [], schema.errors( 'Foo bar baz qux foo bar baz qux foo bar baz qux foo bar baz qux foo bar' )) self.assertEqual([Error('Not a unicode string')], schema.errors(b'Test')) schema = UnicodeString(min_length=5, max_length=10) self.assertEqual([Error('String must have a length of at least 5')], schema.errors('')) self.assertEqual([Error('String must have a length of at least 5')], schema.errors('1234')) self.assertEqual([], schema.errors('12345')) self.assertEqual([], schema.errors('1234567890')) self.assertEqual([Error('String must have a length no more than 10')], schema.errors('12345678901')) schema = UnicodeString(allow_blank=False) self.assertEqual([Error('String cannot be blank')], schema.errors('')) self.assertEqual([Error('String cannot be blank')], schema.errors(' ')) self.assertEqual([Error('String cannot be blank')], schema.errors(' \n ')) self.assertEqual([], schema.errors('foo')) schema = ByteString() self.assertEqual([], schema.errors(b'')) self.assertEqual( [], schema.errors( b'Foo bar baz qux foo bar baz qux foo bar baz qux foo bar baz qux foo' )) self.assertEqual([Error('Not a byte string')], schema.errors('Test')) schema = ByteString(min_length=5, max_length=10) self.assertEqual([Error('String must have a length of at least 5')], schema.errors(b'')) self.assertEqual([Error('String must have a length of at least 5')], schema.errors(b'1234')) self.assertEqual([], schema.errors(b'12345')) self.assertEqual([], schema.errors(b'1234567890')) self.assertEqual([Error('String must have a length no more than 10')], schema.errors(b'12345678901')) with pytest.raises(ValueError): UnicodeString(min_length=6, max_length=5)
def test_tuple(self): # type: () -> None schema = Tuple(Integer(gt=0), UnicodeString(), Constant('I love tuples')) self.assertEqual(schema.errors((1, 'test', 'I love tuples')), []) # too short self.assertEqual( schema.errors((1, 'test')), [Error('Number of elements 2 does not match expected 3')]) # too long self.assertEqual( schema.errors((1, 'test', 'I love tuples', '... and coffee')), [Error('Number of elements 4 does not match expected 3')]) self.assertEqual(schema.errors(( -1, None, 'I hate tuples', )), [ Error('Value not > 0', pointer='0'), Error('Not a unicode string', pointer='1'), Error( 'Value is not "I love tuples"', code=ERROR_CODE_UNKNOWN, pointer='2', ), ]) self.assertEqual( schema.introspect(), { 'type': 'tuple', 'contents': [ { 'type': 'integer', 'gt': 0 }, { 'type': 'unicode' }, { 'type': 'constant', 'values': ['I love tuples'] }, ] }) with pytest.raises(TypeError): # noinspection PyTypeChecker Tuple('not a field') # type: ignore with pytest.raises(TypeError): Tuple(Integer(gt=0), UnicodeString(), Constant('I love tuples'), description=b'Not a unicode string') with pytest.raises(TypeError): Tuple(Integer(gt=0), UnicodeString(), Constant('I love tuples'), unsupported='argument')
def test_dictionary_ordering(self): # type: () -> None schema1 = Dictionary( OrderedDict(( ('foo', UnicodeString()), ('bar', Boolean()), ('baz', List(Integer())), )), optional_keys=('foo', ), description='Hello, world', ) assert schema1.introspect()['contents'] == { 'baz': List(Integer()).introspect(), 'foo': UnicodeString().introspect(), 'bar': Boolean().introspect(), } assert schema1.introspect()['display_order'] == ['foo', 'bar', 'baz'] schema2 = schema1.extend( OrderedDict(( ('bar', Integer()), ('qux', Set(UnicodeString())), ('moon', Tuple(Decimal(), UnicodeString())), ))) assert schema2.introspect()['contents'] == { 'baz': List(Integer()).introspect(), 'foo': UnicodeString().introspect(), 'moon': Tuple(Decimal(), UnicodeString()).introspect(), 'bar': Integer().introspect(), 'qux': Set(UnicodeString()).introspect(), } assert schema2.introspect()['display_order'] == [ 'foo', 'bar', 'baz', 'qux', 'moon' ] assert not schema1.errors({'bar': True, 'foo': 'Hello', 'baz': [15]}) errors = schema1.errors({ 'baz': 'Nope', 'foo': False, 'bar': ['Heck nope'] }) assert errors == [ Error(code='INVALID', pointer='foo', message='Not a unicode string'), Error(code='INVALID', pointer='bar', message='Not a boolean'), Error(code='INVALID', pointer='baz', message='Not a list'), ] assert not schema2.errors( { 'bar': 91, 'foo': 'Hello', 'qux': {'Yes'}, 'baz': [15], 'moon': (decimal.Decimal('15.25'), 'USD') }, ) errors = schema2.errors({ 'baz': 'Nope', 'foo': False, 'bar': ['Heck nope'], 'qux': 'Denied', 'moon': 72 }) assert errors == [ Error(code='INVALID', pointer='foo', message='Not a unicode string'), Error(code='INVALID', pointer='bar', message='Not an integer'), Error(code='INVALID', pointer='baz', message='Not a list'), Error(code='INVALID', pointer='qux', message='Not a set or frozenset'), Error(code='INVALID', pointer='moon', message='Not a tuple'), ]
def test_dictionary_extension(self): # type: () -> None schema1 = Dictionary( { 'foo': UnicodeString(), 'bar': Boolean(), }, optional_keys=('foo', ), description='Hello, world', ) schema2 = schema1.extend( { 'bar': Integer(), 'baz': List(Integer()), }, optional_keys=('baz', ), ) schema3 = schema1.extend( { 'bar': Integer(), 'baz': List(Integer()), }, optional_keys=('baz', ), allow_extra_keys=True, description='Goodbye, universe', replace_optional_keys=True, ) self.assertEqual( Dictionary( { 'foo': UnicodeString(), 'bar': Integer(), 'baz': List(Integer()), }, optional_keys=( 'foo', 'baz', ), allow_extra_keys=False, description='Hello, world', ).introspect(), schema2.introspect(), ) self.assertEqual( Dictionary( { 'foo': UnicodeString(), 'bar': Integer(), 'baz': List(Integer()), }, optional_keys=('baz', ), allow_extra_keys=True, description='Goodbye, universe', ).introspect(), schema3.introspect(), ) assert 'display_order' not in schema1.introspect() assert 'display_order' not in schema2.introspect() assert 'display_order' not in schema3.introspect()
def test_complex(self): # type: () -> None schema = Dictionary({ 'child_ids': List(Integer(gt=0)), 'address': Dictionary( { 'line1': UnicodeString(), 'line2': UnicodeString(), 'city': UnicodeString(), 'postcode': UnicodeString(), 'state': UnicodeString(), 'country': UnicodeString(), }, optional_keys=('line2', 'state'), ), 'unique_things': Set(UnicodeString()), }) self.assertEqual( schema.errors(None), [Error('Not a dict')], ) self.assertEqual( sorted( schema.errors( { 'child_ids': [1, 2, 'ten'], 'unsolicited_item': 'Should not be here', 'another_bad': 'Also extra', 'unique_things': ['hello', 'world'], }, )), sorted([ Error('Not an integer', pointer='child_ids.2'), Error('Missing key: address', code=ERROR_CODE_MISSING, pointer='address'), Error('Extra keys present: another_bad, unsolicited_item', code=ERROR_CODE_UNKNOWN), Error('Not a set or frozenset', pointer='unique_things'), ]), ) self.assertEqual( schema.errors({ 'child_ids': [1, 2, 3, 4], 'address': { 'line1': '115 5th Street', 'city': 'San Francisco', 'state': 'CA', 'country': 'USA', 'postcode': '94103', }, 'unique_things': {'hello', b'world'}, }), [ Error('Not a unicode string', pointer='unique_things.[{}]'.format(str(b'world'))) ], ) self.assertEqual( schema.errors({ 'child_ids': [1, 2, 3, 4], 'address': { 'line1': '115 5th Street', 'city': 'San Francisco', 'state': 'CA', 'country': 'USA', 'postcode': '94103', }, 'unique_things': {'hello', 'world'}, }), [], ) introspection = schema.introspect() self.assertEqual('dictionary', introspection['type']) self.assertFalse(introspection['allow_extra_keys']) self.assertEqual([], introspection['optional_keys']) self.assertEqual(3, len(introspection['contents'])) self.assertIn('child_ids', introspection['contents']) self.assertEqual( { 'type': 'list', 'contents': { 'gt': 0, 'type': 'integer' }, }, introspection['contents']['child_ids'], ) self.assertIn('address', introspection['contents']) self.assertEqual('dictionary', introspection['contents']['address']['type']) self.assertFalse( introspection['contents']['address']['allow_extra_keys']) self.assertEqual( {'line2', 'state'}, set(introspection['contents']['address']['optional_keys'])) self.assertEqual( { 'city': { 'type': 'unicode' }, 'country': { 'type': 'unicode' }, 'line1': { 'type': 'unicode' }, 'line2': { 'type': 'unicode' }, 'postcode': { 'type': 'unicode' }, 'state': { 'type': 'unicode' }, }, introspection['contents']['address']['contents'], ) self.assertEqual( { 'type': 'set', 'contents': { 'type': 'unicode' }, }, introspection['contents']['unique_things'], )
def test_subclass_definition(self): # type: () -> None class ImmutableDict(Mapping): def __init__(self, underlying): self.underlying = underlying def __contains__(self, item): return item in self.underlying def __getitem__(self, k): return self.underlying[k] def get(self, k, default=None): return self.underlying.get(k, default) def __iter__(self): return iter(self.underlying) def __len__(self): return len(self.underlying) def keys(self): return self.underlying.keys() def items(self): return self.underlying.items() def values(self): return self.underlying.values() class ExtendedSchema(ClassConfigurationSchema): base_class = BaseSomething default_path = 'tests.test_fields_meta.AnotherSomething' description = 'Neat-o schema thing' schema = ExtendedSchema() config = {} # type: dict with pytest.raises(ValidationError) as error_context: schema.instantiate_from(config) assert error_context.value.args[0] == [ Error('Missing key: baz', code='MISSING', pointer='kwargs.baz'), ] assert config['object'] == AnotherSomething config = {'kwargs': {'baz': None}} value = schema.instantiate_from(config) assert isinstance(value, AnotherSomething) assert value.baz is None assert value.qux == 'unset' assert config['object'] == AnotherSomething config2 = ImmutableDict({'kwargs': {'baz': None}}) assert schema.errors(config2) == [] assert 'object' not in config2 assert schema.introspect() == { 'type': 'class_config_dictionary', 'description': 'Neat-o schema thing', 'default_path': 'tests.test_fields_meta.AnotherSomething', 'base_class': 'BaseSomething', 'switch_field': 'path', 'switch_field_schema': TypePath(base_classes=BaseSomething).introspect(), 'kwargs_field': 'kwargs', 'kwargs_contents_map': { 'tests.test_fields_meta.AnotherSomething': Dictionary( { 'baz': Nullable(UnicodeString()), 'qux': Boolean() }, optional_keys=('qux', ), ).introspect(), }, }
def test_inline_definition_no_default_or_base_class( self): # type: () -> None schema = ClassConfigurationSchema() assert schema.errors('Not a dict') == [ Error('Not a mapping (dictionary)') ] assert schema.errors({ 'foo': 'bar', 'baz': 'qux', 'path': 'unprocessed', 'kwargs': {}, 'object': Foo }) == [Error('Extra keys present: baz, foo', code='UNKNOWN')] assert schema.errors({}) == [ Error('Missing key (and no default specified): path', code='MISSING', pointer='path'), ] assert schema.errors({'path': 'foo.bar:Hello'}) == [ Error( 'ImportError: No module named foo.bar' if six.PY2 else "ImportError: No module named 'foo'", pointer='path', ) ] assert schema.errors({'path': 'tests.test_fields_meta.Foo'}) == [ Error( "Neither class 'tests.test_fields_meta.Foo' nor one of its superclasses was decorated with " "@ClassConfigurationSchema.provider", pointer='path', ) ] assert schema.errors({ 'path': 'tests.test_fields_meta:InvalidProvider' }) == [ Error( "Class 'tests.test_fields_meta:InvalidProvider' attribute '_conformity_initialization_schema' should be a " "Dictionary or SchemalessDictionary Conformity field or one of their subclasses", pointer='path', ) ] config = { 'path': 'tests.test_fields_meta:BasicProvider' } # type: Dict[HashableType, AnyType] assert sorted(schema.errors(config)) == [ Error('Missing key: bar', code='MISSING', pointer='kwargs.bar'), Error('Missing key: foo', code='MISSING', pointer='kwargs.foo'), ] assert config['object'] == BasicProvider with pytest.raises(ValidationError) as error_context: # noinspection PyTypeChecker schema.instantiate_from('Not a dict') # type: ignore assert error_context.value.args[0] == [ Error('Not a mutable mapping (dictionary)') ] config = {'path': 'tests.test_fields_meta:BasicProvider'} with pytest.raises(ValidationError) as error_context: schema.instantiate_from(config) assert sorted(error_context.value.args[0]) == [ Error('Missing key: bar', code='MISSING', pointer='kwargs.bar'), Error('Missing key: foo', code='MISSING', pointer='kwargs.foo'), ] assert config['object'] == BasicProvider config = { 'path': 'tests.test_fields_meta:BasicProvider', 'kwargs': { 'foo': 'Fine', 'bar': 'Bad' } } assert schema.errors(config) == [ Error('Not a boolean', pointer='kwargs.bar') ] assert config['object'] == BasicProvider config = { 'path': 'tests.test_fields_meta:BasicProvider', 'kwargs': { 'foo': 'Fine', 'bar': 'Bad' } } with pytest.raises(ValidationError) as error_context: schema.instantiate_from(config) assert error_context.value.args[0] == [ Error('Not a boolean', pointer='kwargs.bar') ] assert config['object'] == BasicProvider config = { 'path': 'tests.test_fields_meta:BasicProvider', 'kwargs': { 'foo': 'Fine', 'bar': True } } assert schema.errors(config) == [] assert config['object'] == BasicProvider config = { 'path': 'tests.test_fields_meta:BasicProvider', 'kwargs': { 'foo': 'Fine', 'bar': True } } value = schema.instantiate_from(config) assert isinstance(value, BasicProvider) assert value.foo == 'Fine' assert value.bar is True assert config['object'] == BasicProvider schema = ClassConfigurationSchema() with pytest.raises(ValidationError): schema.initiate_cache_for('foo.bar:Hello') schema.initiate_cache_for('tests.test_fields_meta.BasicProvider') schema.initiate_cache_for('tests.test_fields_meta:BasicProvider') assert schema.introspect() == { 'type': 'class_config_dictionary', 'base_class': 'object', 'switch_field': 'path', 'switch_field_schema': TypePath(base_classes=object).introspect(), 'kwargs_field': 'kwargs', 'kwargs_contents_map': { 'tests.test_fields_meta.BasicProvider': Dictionary({ 'foo': UnicodeString(), 'bar': Boolean() }, ).introspect(), 'tests.test_fields_meta:BasicProvider': Dictionary({ 'foo': UnicodeString(), 'bar': Boolean() }, ).introspect(), }, } schema = ClassConfigurationSchema(add_class_object_to_dict=False) config = { 'path': 'tests.test_fields_meta:BasicProvider', 'kwargs': { 'foo': 'Fine', 'bar': True } } value = schema.instantiate_from(config) assert isinstance(value, BasicProvider) assert value.foo == 'Fine' assert value.bar is True assert 'object' not in config