def errors(self, value): if value['foo'] != value['baz']: return [ Error('Value foo does not match value baz', pointer='foo') ] return []
def errors(self, value): # type: (AnyType) -> ListType[Error] try: hash(value) except TypeError: return [ Error('Value is not hashable'), ] return []
def errors(self, value): errors = [] for i, v in enumerate(value): if v > 500: errors.append( Error('Whoop custom error', pointer='{}'.format(i))) return errors
def errors(self, value): # type: (AnyType) -> ListType[Error] if not isinstance(value, self.valid_type): return [ Error('Not an instance of {}'.format( getattr(self.valid_type, '__name__', repr(self.valid_type)))) ] return []
def errors(self, value): errors = [] for v in value: if v > 500: errors.append( Error('Whoop custom error', pointer='{}'.format(v))) return errors
def test_decimal(self): # type: () -> None """ Tests decimal.Decimal object validation """ self.assertEqual([], Decimal().errors(decimal.Decimal('1'))) self.assertEqual([], Decimal().errors(decimal.Decimal('1.4'))) self.assertEqual([], Decimal().errors(decimal.Decimal('-3.14159'))) self.assertEqual([Error('Not a decimal')], Decimal().errors('-3.14159')) self.assertEqual([Error('Not a decimal')], Decimal().errors(-3.14159)) self.assertEqual([Error('Not a decimal')], Decimal().errors(15)) self.assertEqual( [Error('Value not > 6')], Decimal(lt=12, gt=6).errors(decimal.Decimal('6')), ) self.assertEqual( [Error('Value not < 12')], Decimal(lt=12, gt=6).errors(decimal.Decimal('12')), ) self.assertEqual([], Decimal(lt=12, gt=6).errors(decimal.Decimal('6.1'))) self.assertEqual([], Decimal(lt=12, gt=6).errors(decimal.Decimal('11.9'))) self.assertEqual( [Error('Value not >= 6')], Decimal(lte=12, gte=6).errors(decimal.Decimal('5.9')), ) self.assertEqual( [Error('Value not <= 12')], Decimal(lte=12, gte=6).errors(decimal.Decimal('12.1')), ) self.assertEqual([], Decimal(lte=12, gte=6).errors(decimal.Decimal('6'))) self.assertEqual([], Decimal(lte=12, gte=6).errors(decimal.Decimal('12')))
def test_ipv4address(self): # type: () -> None schema = IPv4Address() self.assertEqual( schema.errors('127.0.0.1'), [], ) self.assertEqual( schema.errors('127.300.0.1'), [Error('Not a valid IPv4 address')], ) self.assertEqual( schema.errors('127.0.0'), [Error('Not a valid IPv4 address')], ) self.assertEqual( schema.errors('a2.12.55.3'), [Error('Not a valid IPv4 address')], )
def test_latitude(self): # type: () -> None schema = Latitude() self.assertEqual( schema.errors(89) or [], [], ) self.assertEqual( schema.errors(-1.3412) or [], [], ) self.assertEqual( schema.errors(180), [Error('Value not <= 90')], ) self.assertEqual( schema.errors(-91), [Error('Value not >= -90')], )
def test_hashable(self): # type: () -> None assert Hashable('Another description 2').introspect() == { 'type': 'hashable', 'description': 'Another description 2', } assert Hashable().errors('this is hashable') == [] assert Hashable().errors({'this', 'is', 'not', 'hashable' }) == [Error('Value is not hashable')]
def test_limited_longitude(self): # type: () -> None schema = Longitude(lte=-50) self.assertEqual( schema.errors(-51.2) or [], [], ) self.assertEqual( schema.errors(-49.32) or [], [Error('Value not <= -50')], )
def errors(self, value): # type: (AnyType) -> ListType[Error] # Get any basic type errors result = super(IPv4Address, self).errors(value) if result: return result # Check for IPv4-ness if ipv4_regex.match(value): return [] else: return [Error('Not a valid IPv4 address')]
def errors(self, value): # type: (AnyType) -> ListType[Error] if not isinstance(value, Mapping): return [Error('Not a mapping (dictionary)')] # check for extra keys (object is allowed in case this gets validated twice) extra_keys = [ k for k in six.iterkeys(value) if k not in ('path', 'kwargs', 'object') ] if extra_keys: return [ Error( 'Extra keys present: {}'.format(', '.join( six.text_type(k) for k in sorted(extra_keys))), code=ERROR_CODE_UNKNOWN, ) ] sentinel = object() path = value.get('path', sentinel) if path is sentinel and not self.default_path: return [ Error('Missing key (and no default specified): path', code=ERROR_CODE_MISSING, pointer='path') ] if not path or path is sentinel: path = self.default_path errors = self._populate_schema_cache_if_necessary(path) if errors: return [update_pointer(e, 'path') for e in errors] if isinstance(value, MutableMapping): value['path'] = path # in case it was defaulted if self.add_class_object_to_dict: value['object'] = PythonPath.resolve_python_path(path) return [ update_pointer(e, 'kwargs') for e in self._schema_cache[path].errors(value.get('kwargs', {})) ]
def errors(self, value): # type: (AnyType) -> ListType[Error] try: is_valid = value in self.values except TypeError: # Unhashable values can't be used for membership checks. is_valid = False if not is_valid: return [Error(self._error_message, code=ERROR_CODE_UNKNOWN)] return []
def errors(self, value): # type: (AnyType) -> ListType[Error] if not isinstance(value, self.valid_types): return [Error(self.type_error)] result = [] if self.max_length is not None and len(value) > self.max_length: result.append( Error('List is longer than {}'.format(self.max_length)), ) elif self.min_length is not None and len(value) < self.min_length: result.append( Error('List is shorter than {}'.format(self.min_length)), ) for lazy_pointer, element in self._enumerate(value): result.extend( update_pointer(error, lazy_pointer.get()) for error in (self.contents.errors(element) or [])) if not result and self.additional_validator: return self.additional_validator.errors(value) return result
def errors(self, value): # type: (AnyType) -> ListType[Error] if not isinstance(value, tuple): return [Error('Not a tuple')] result = [] if len(value) != len(self.contents): result.append( Error( 'Number of elements {} does not match expected {}'.format( len(value), len(self.contents)))) for i, (c_elem, v_elem) in enumerate(zip(self.contents, value)): result.extend( update_pointer(error, i) for error in (c_elem.errors(v_elem) or [])) if not result and self.additional_validator: return self.additional_validator.errors(value) return result
def test_schemaless(self): # type: () -> None schema = ClassConfigurationSchema(base_class=BaseSomething) config = { 'path': 'tests.test_fields_meta:SomethingWithJustKwargs' } # type: dict value = schema.instantiate_from(config) assert config['object'] == SomethingWithJustKwargs assert isinstance(value, SomethingWithJustKwargs) assert value.kwargs == {} config = { 'path': 'tests.test_fields_meta:SomethingWithJustKwargs', 'kwargs': { 'dog': 'Bree', 'cute': True, 'cat': b'Pumpkin' }, } value = schema.instantiate_from(config) assert config['object'] == SomethingWithJustKwargs assert isinstance(value, SomethingWithJustKwargs) assert value.kwargs == {'dog': 'Bree', 'cute': True, 'cat': b'Pumpkin'} config = { 'path': 'tests.test_fields_meta:SomethingWithJustKwargs', 'kwargs': { b'Not unicode': False } } with pytest.raises(ValidationError) as error_context: schema.instantiate_from(config) assert error_context.value.args[0] == [ Error('Not a unicode string', code='INVALID', pointer='kwargs.Not unicode') if six.PY2 else Error('Not a unicode string', code='INVALID', pointer='kwargs.{!r}'.format(b'Not unicode')) ] assert config['object'] == SomethingWithJustKwargs
def errors(self, value): # type: (AnyType) -> ListType[Error] switch_value, valid = self._get_switch_value(value) if not valid: return [ Error("Invalid switch value '{}'".format(switch_value), code=ERROR_CODE_UNKNOWN) ] # Get field field = self.contents_map[switch_value] # Run field errors return field.errors(value)
def errors(self, value): # type: (AnyType) -> ListType[Error] if not isinstance(value, six.text_type): return [Error('Not a unicode string')] try: thing = self.resolve_python_path(value) except ValueError: return [ Error('Value "{}" is not a valid Python import path'.format( value)) ] except ImportError as e: return [Error('ImportError: {}'.format(six.text_type(e.args[0])))] except AttributeError as e: return [ Error('AttributeError: {}'.format(six.text_type(e.args[0]))) ] if self.value_schema: return self.value_schema.errors(thing) return []
def _get_errors_for_currency_amount( currency_code, # type: six.text_type value, # type: int valid_currencies, # type: AbstractSet[six.text_type] gt, # type: Optional[int] gte, # type: Optional[int] lt, # type: Optional[int] lte, # type: Optional[int] ): errors = [] if currency_code not in valid_currencies: errors.append(Error('Not a valid currency code', code=ERROR_CODE_INVALID)) if gt is not None and value <= gt: errors.append(Error('Value not > {}'.format(gt), code=ERROR_CODE_INVALID)) if lt is not None and value >= lt: errors.append(Error('Value not < {}'.format(lt), code=ERROR_CODE_INVALID)) if gte is not None and value < gte: errors.append(Error('Value not >= {}'.format(gte), code=ERROR_CODE_INVALID)) if lte is not None and value > lte: errors.append(Error('Value not <= {}'.format(lte), code=ERROR_CODE_INVALID)) return errors
def errors(self, value): # type: (AnyType) -> ListType[Error] if not isinstance(value, six.text_type): return [Error('Not a unicode string currency amount')] parts = self._format.split(value) if len(parts) != 2: return [Error('Currency string does not match format CUR,1234 or CUR:1234')] currency = parts[0] try: value = int(parts[1]) except ValueError: return [Error('Currency amount {} cannot be converted to an integer'.format(parts[1]))] return _get_errors_for_currency_amount( currency, value, self.valid_currencies, self.gt, self.gte, self.lt, self.lte, )
def test_schemaless_dict_empty(self): # type: () -> None """ Tests the schemaless dict without any schema at all (so the default Hashable: Anything) """ schema = SchemalessDictionary() self.assertEqual(schema.errors({'key': 'value'}), []) self.assertEqual(schema.errors('a thing'), [Error('Not a dict')]) self.assertEqual(schema.introspect(), { 'type': 'schemaless_dictionary', })
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_multi_constant(self): # type: () -> None """ Tests constants with multiple options """ schema = Constant(42, 36, 81, 9231) self.assertEqual( schema.errors(9231), [], ) self.assertEqual( schema.errors(81), [], ) self.assertEqual( schema.errors(360000), [ Error('Value is not one of: 36, 42, 81, 9231', code=ERROR_CODE_UNKNOWN) ], ) self.assertEqual( schema.errors([42]), [ Error('Value is not one of: 36, 42, 81, 9231', code=ERROR_CODE_UNKNOWN) ], ) with pytest.raises(TypeError): Constant(42, 36, 81, 9231, description='foo', unsupported='bar') with pytest.raises(ValueError): Constant() with pytest.raises(TypeError): Constant(42, 36, 81, 9231, description=b'not unicode')
def errors(self, value): # type: (AnyType) -> ListType[Error] if not isinstance(value, currint.Amount): return [Error( 'Not a currint.Amount instance', code=ERROR_CODE_INVALID, )] return _get_errors_for_currency_amount( value.currency.code, value.value, self.valid_currencies, self.gt, self.gte, self.lt, self.lte, )
def instantiate_from( self, configuration ): # type: (MutableMapping[HashableType, AnyType]) -> AnyType if not isinstance(configuration, MutableMapping): raise ValidationError( [Error('Not a mutable mapping (dictionary)')]) errors = self.errors(configuration) if errors: raise ValidationError(errors) clazz = configuration.get('object') if not clazz: clazz = PythonPath.resolve_python_path(configuration['path']) return clazz(**configuration.get('kwargs', {}))
def test_type_path(self): # type: () -> None schema = TypePath(description='This is another test') assert schema.errors(b'Nope nope nope') == [ Error('Not a unicode string') ] assert schema.errors('Nope nope nope') == [ Error('Value "Nope nope nope" is not a valid Python import path') ] assert schema.errors('foo.bar:Hello') == [ Error('ImportError: No module named foo.bar' if six. PY2 else "ImportError: No module named 'foo'") ] assert schema.errors('conformity.fields:NotARealField') == [ Error( "AttributeError: 'module' object has no attribute 'NotARealField'" if six.PY2 else "AttributeError: module 'conformity.fields' has no attribute 'NotARealField'" ) ] assert schema.errors('conformity.fields:UnicodeString') == [] assert schema.errors('conformity.fields.UnicodeString') == [] assert schema.errors('conformity.fields.ByteString') == [] assert schema.errors('conformity.fields:ByteString') == [] assert schema.errors('tests.test_fields_meta.Foo') == [] assert schema.errors('tests.test_fields_meta.Bar') == [] assert schema.errors('tests.test_fields_meta.Baz') == [] assert schema.errors('tests.test_fields_meta.Qux') == [] assert schema.errors('tests.test_fields_meta:Qux.InnerQux') == [] schema = TypePath(base_classes=Foo) assert schema.errors('tests.test_fields_meta.Foo') == [] assert schema.errors('tests.test_fields_meta.Bar') == [] assert schema.errors('tests.test_fields_meta.Baz') == [ Error('Type {} is not one of or a subclass of one of: {}'.format( Baz, Foo)), ] assert schema.errors('conformity.fields.UnicodeString') == [ Error('Type {} is not one of or a subclass of one of: {}'.format( TypePath.resolve_python_path( 'conformity.fields.UnicodeString'), Foo, )), ] assert schema.introspect() == { 'type': 'python_path', 'value_schema': { 'type': 'type_reference', 'base_classes': [six.text_type(Foo)], } } assert TypePath.resolve_python_path( 'tests.test_fields_meta.Qux') == Qux assert TypePath.resolve_python_path( 'tests.test_fields_meta:Qux.InnerQux') == Qux.InnerQux
def test_integers(self): # type: () -> None schema = Integer(gt=0, lt=10) self.assertEqual([], schema.errors(1)) self.assertEqual([Error('Not an integer')], schema.errors('one')) self.assertEqual([Error('Not an integer')], schema.errors(True)) self.assertEqual([Error('Value not > 0')], schema.errors(0)) self.assertEqual([Error('Value not < 10')], schema.errors(10)) schema = Integer(gte=0, lte=10) self.assertEqual([Error('Value not >= 0')], schema.errors(-1)) self.assertEqual([Error('Value not <= 10')], schema.errors(11))
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)) == []
def test_object_instance(self): # type: () -> None class Thing(object): pass class Thingy(Thing): pass class SomethingElse(object): pass schema = ObjectInstance(Thing, description='Yessiree') self.assertEqual(schema.errors(Thing()), []) # subclasses are valid self.assertEqual(schema.errors(Thingy()), []) self.assertEqual(schema.errors(SomethingElse()), [Error('Not an instance of Thing')]) assert schema.introspect() == { 'type': 'object_instance', 'description': 'Yessiree', 'valid_type': repr(Thing), } schema = ObjectInstance((Thing, SomethingElse)) assert schema.errors(Thing()) == [] assert schema.errors(Thingy()) == [] assert schema.errors(SomethingElse()) == [] with pytest.raises(TypeError): # noinspection PyTypeChecker ObjectInstance('not a type') # type: ignore with pytest.raises(TypeError): # noinspection PyTypeChecker ObjectInstance( (Thing, SomethingElse, 'also not a type')) # type: ignore
def test_ipv6address(self): # type: () -> None schema = IPv6Address() self.assertEqual( schema.errors('::2'), [], ) self.assertEqual( schema.errors('abdf::4'), [], ) self.assertEqual( schema.errors('34de:e23d::233e:32'), [], ) self.assertEqual( schema.errors('::ffff:222.1.41.90'), [], ) self.assertEqual( schema.errors('1232:d4af:6023:1afc:cfed:0239d:0934:0923d'), [], ) self.assertEqual( schema.errors('1232:d4af:6023:1afc:cfed:0239d:0934:0923d:3421'), [Error('Not a valid IPv6 address (too many colons)')], ) self.assertEqual( schema.errors('1:::42'), [Error('Not a valid IPv6 address (shortener not bounded)')], ) self.assertEqual( schema.errors('1351:z::3'), [Error('Not a valid IPv6 address (invalid hextet)')], ) self.assertEqual( schema.errors('dead:beef::3422:23::1'), [Error('Not a valid IPv6 address (multiple shorteners)')], ) self.assertEqual( schema.errors('dead:beef::127.0.0.1:0'), [Error('Not a valid IPv6 address (v4 section not at end)')], ) self.assertEqual( schema.errors('dead:beef::127.0.0.300'), [Error('Not a valid IPv6 address (v4 section not valid address)')], )