1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
|
import typing
import pytest
from pydantic_core import SchemaError, SchemaValidator, ValidationError, core_schema
from pydantic_core import core_schema as cs
class Foo:
pass
class Bar(Foo):
pass
class Spam:
pass
def test_validate_json() -> None:
v = SchemaValidator(cs.is_instance_schema(cls=Foo))
with pytest.raises(ValidationError) as exc_info:
v.validate_json('"foo"')
assert exc_info.value.errors()[0]['type'] == 'needs_python_object'
def test_is_instance():
v = SchemaValidator(cs.is_instance_schema(cls=Foo))
foo = Foo()
assert v.validate_python(foo) == foo
assert v.isinstance_python(foo) is True
bar = Bar()
assert v.validate_python(bar) == bar
s = Spam()
assert v.isinstance_python(s) is False
with pytest.raises(ValidationError) as exc_info:
v.validate_python(s)
assert exc_info.value.errors(include_url=False) == [
{
'type': 'is_instance_of',
'loc': (),
'msg': 'Input should be an instance of Foo',
'input': s,
'ctx': {'class': 'Foo'},
}
]
with pytest.raises(ValidationError, match='type=is_instance_of'):
v.validate_python(Foo)
@pytest.mark.parametrize(
'schema_class,input_val,value',
[
(Foo, Foo(), True),
(Foo, Foo, False),
(Foo, Bar(), True),
(Foo, Bar, False),
(Bar, Foo(), False),
(Bar, Foo, False),
(dict, {1: 2}, True),
(dict, {1, 2}, False),
(type, Foo, True),
(type, Foo(), False),
],
)
def test_is_instance_cases(schema_class, input_val, value):
v = SchemaValidator(cs.is_instance_schema(cls=schema_class))
assert v.isinstance_python(input_val) == value
@pytest.mark.parametrize('input_cls', [123, 'foo', Foo(), [], {1: 2}])
def test_is_instance_invalid(input_cls):
with pytest.raises(SchemaError, match="SchemaError: 'cls' must be valid as the first argument to 'isinstance'"):
SchemaValidator(cs.is_instance_schema(cls=input_cls))
class HasIsInstanceMeta(type):
def __instancecheck__(self, instance) -> bool:
if 'error' in repr(instance):
# an error here comes from a problem in the schema, not in the input value, so raise as internal error
raise TypeError('intentional error')
return 'true' in repr(instance)
class HasIsInstance(metaclass=HasIsInstanceMeta):
pass
def test_instancecheck():
v = SchemaValidator(cs.is_instance_schema(cls=HasIsInstance))
assert v.validate_python('true') == 'true'
with pytest.raises(ValidationError, match='type=is_instance_of'):
v.validate_python('other')
with pytest.raises(TypeError, match='intentional error'):
v.validate_python('error')
def test_repr():
v = SchemaValidator(cs.union_schema(choices=[cs.int_schema(), cs.is_instance_schema(cls=Foo)]))
assert v.isinstance_python(4) is True
assert v.isinstance_python(Bar()) is True
assert v.isinstance_python('foo') is False
with pytest.raises(ValidationError, match=r'is-instance\[Foo\]\s+Input should be an instance of Foo'):
v.validate_python('foo')
@pytest.mark.parametrize(
'input_val,value',
[
(Foo, True),
(Foo(), False),
(str, True),
('foo', False),
(int, True),
(1, False),
(type, True),
(type('Foobar', (), {'x': 1}), True),
],
)
def test_is_type(input_val, value):
v = SchemaValidator(cs.is_instance_schema(cls=type))
assert v.isinstance_python(input_val) == value
def test_is_instance_dict():
v = SchemaValidator(
core_schema.dict_schema(
keys_schema=core_schema.is_instance_schema(str), values_schema=core_schema.is_instance_schema(int)
)
)
assert v.isinstance_python({'foo': 1}) is True
assert v.isinstance_python({1: 1}) is False
def test_is_instance_dict_not_str():
v = SchemaValidator(core_schema.dict_schema(keys_schema=core_schema.is_instance_schema(int)))
assert v.isinstance_python({1: 1}) is True
assert v.isinstance_python({'foo': 1}) is False
def test_is_instance_sequence():
v = SchemaValidator(core_schema.is_instance_schema(typing.Sequence))
assert v.isinstance_python(1) is False
assert v.isinstance_python([1]) is True
with pytest.raises(ValidationError, match=r'Input should be an instance of typing.Sequence \[type=is_instance_of,'):
v.validate_python(1)
def test_is_instance_tuple():
v = SchemaValidator(core_schema.is_instance_schema((int, str)))
assert v.isinstance_python(1) is True
assert v.isinstance_python('foobar') is True
assert v.isinstance_python([1]) is False
with pytest.raises(ValidationError, match=r"Input should be an instance of \(<class 'int'>, <class 'str'>\)"):
v.validate_python([1])
def test_class_repr():
v = SchemaValidator(core_schema.is_instance_schema(int, cls_repr='Foobar'))
assert v.validate_python(1) == 1
with pytest.raises(ValidationError, match=r'Input should be an instance of Foobar \[type=is_instance_of,'):
v.validate_python('1')
def test_is_instance_json_type_before_validator():
# See https://github.com/pydantic/pydantic/issues/6573 - when using a
# "before" validator to coerce JSON to valid Python input it should be
# possible to use isinstance validation. This gives a way for things
# such as type to have a valid input from JSON.
schema = core_schema.is_instance_schema(type)
v = SchemaValidator(schema)
with pytest.raises(ValidationError) as exc_info:
v.validate_json('null')
assert exc_info.value.errors()[0]['type'] == 'needs_python_object'
# now wrap in a before validator
def set_type_to_int(input: None) -> type:
return int
schema = core_schema.no_info_before_validator_function(set_type_to_int, schema)
v = SchemaValidator(schema)
assert v.validate_json('null') == int
|