def test_latitude(self): 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_ipv4address(self): 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_limited_longitude(self): schema = Longitude(lte=-50) self.assertEqual( schema.errors(-51.2) or [], [], ) self.assertEqual( schema.errors(-49.32) or [], [Error('Value not <= -50')], )
def test_non_whitelisted_address(self): # type: () -> None whitelisted_domains = ['a-whitelisted-domain'] schema = EmailAddress(whitelist=whitelisted_domains) self.assertEqual( schema.errors('a-name@non-whitelisted-domain'), [ Error('Not a valid email address (invalid domain field)', pointer='non-whitelisted-domain') ], )
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] 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): 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_error_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_error_pointer(e, 'kwargs') for e in self._schema_cache[path].errors(value.get('kwargs', {})) ]
def errors(self, value): if not isinstance(value, tuple): return [ Error('Not a tuple'), ] result = [] if len(value) != len(self.contents): result.append( Error('Number of elements %d does not match expected %d' % (len(value), len(self.contents))) ) for i, (c_elem, v_elem) in enumerate(zip(self.contents, value)): result.extend( _update_error_pointer(error, i) for error in (c_elem.errors(v_elem) or []) ) return result
def errors(self, value): 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 longer than %s' % self.max_length), ) elif self.min_length is not None and len(value) < self.min_length: result.append( Error('List is shorter than %s' % self.min_length), ) for lazy_pointer, element in self._enumerate(value): result.extend( _update_error_pointer(error, lazy_pointer.get()) for error in (self.contents.errors(element) or []) ) return result
def errors(self, value): if not isinstance(value, self.valid_type): return [ Error('Not a %s' % self.valid_noun), ] elif self.min_length is not None and len(value) < self.min_length: return [ Error('String must have a length of at least %s' % self.min_length), ] elif self.max_length is not None and len(value) > self.max_length: return [ Error('String must have a length no more than %s' % self.max_length), ] elif not (self.allow_blank or value.strip()): return [ Error('String cannot be blank'), ]
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(six.text_type(e.args[0]))] except AttributeError as e: return [Error(six.text_type(e.args[0]))] if self.value_schema: return self.value_schema.errors(thing) return []
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_error_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 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_error_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 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): # Get any basic type errors result = super(EmailAddress, self).errors(value) if result: return result if not value or '@' not in value: return [Error('Not a valid email address (missing @ sign)')] user_part, domain_part = value.rsplit('@', 1) if not self.user_regex.match(user_part): return [Error('Not a valid email address (invalid local user field)', pointer=user_part)] if domain_part in self.domain_whitelist or self.is_domain_valid(domain_part): return [] else: try: domain_part = domain_part.encode('idna').decode('ascii') if self.is_domain_valid(domain_part): return [] except UnicodeError: pass return [Error('Not a valid email address (invalid domain field)', pointer=domain_part)]
def errors(self, value): if not isinstance(value, self.valid_type) or isinstance(value, bool): return [ Error('Not %s' % self.valid_noun), ] elif self.gt is not None and value <= self.gt: return [ Error('Value not > %s' % self.gt), ] elif self.lt is not None and value >= self.lt: return [ Error('Value not < %s' % self.lt), ] elif self.gte is not None and value < self.gte: return [ Error('Value not >= %s' % self.gte), ] elif self.lte is not None and value > self.lte: return [ Error('Value not <= %s' % 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): if not isinstance(value, dict): return [Error('Not a dict')] result = [] if self.max_length is not None and len(value) > self.max_length: result.append( Error('Dict contains more than {} value(s)'.format( self.max_length))) elif self.min_length is not None and len(value) < self.min_length: result.append( Error('Dict contains fewer than {} value(s)'.format( self.min_length))) for key, field in value.items(): result.extend( update_error_pointer(error, key) for error in (self.key_type.errors(key) or [])) result.extend( update_error_pointer(error, key) for error in (self.value_type.errors(field) or [])) return result
def errors(self, value): if type(value) not in self.valid_types and ( not self.valid_isinstance or not isinstance(value, self.valid_isinstance)): # using stricter type checking, because date is subclass of datetime, but they're not comparable return [ Error('Not a %s instance' % self.valid_noun), ] elif self.gt is not None and value <= self.gt: return [ Error('Value not > %s' % self.gt), ] elif self.lt is not None and value >= self.lt: return [ Error('Value not < %s' % self.lt), ] elif self.gte is not None and value < self.gte: return [ Error('Value not >= %s' % self.gte), ] elif self.lte is not None and value > self.lte: return [ Error('Value not <= %s' % self.lte), ]
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 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 errors(self, value): if not isinstance(value, dict): return [ Error('Not a dict'), ] result = [] for key, field in value.items(): result.extend( _update_error_pointer(error, key) for error in (self.key_type.errors(key) or []) ) result.extend( _update_error_pointer(error, key) for error in (self.value_type.errors(field) or []) ) return result
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 errors(self, value): # Get switch field value bits = self.switch_field.split('.') switch_value = value for bit in bits: switch_value = switch_value[bit] # Get field if switch_value not in self.contents_map: if '__default__' in self.contents_map: switch_value = '__default__' else: return [ Error('Invalid switch value {}'.format(switch_value), code=ERROR_CODE_UNKNOWN), ] 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, 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 test_objectinstance(self): class Thing(object): pass class Thingy(Thing): pass class SomethingElse(object): pass schema = ObjectInstance(Thing) self.assertEqual(schema.errors(Thing()), []) # subclasses are valid self.assertEqual(schema.errors(Thingy()), []) self.assertEqual(schema.errors(SomethingElse()), [Error('Not an instance of Thing')])
def test_multi_constant(self): """ 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) ], )