import importlib.util
import re
import sys
from abc import ABC, abstractmethod
from collections.abc import Hashable
from decimal import Decimal
from enum import Enum
from typing import Any, Callable, Dict, FrozenSet, Generic, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union

import pytest
from dirty_equals import HasRepr, IsStr
from pydantic_core import PydanticSerializationError, core_schema
from typing_extensions import Annotated, get_args

from pydantic import (
    AnalyzedType,
    BaseModel,
    ConfigDict,
    PydanticInvalidForJsonSchema,
    PydanticSchemaGenerationError,
    ValidationError,
    constr,
    errors,
)
from pydantic.decorators import (
    field_serializer,
    field_validator,
    model_serializer,
    model_validator,
    root_validator,
    validator,
)
from pydantic.fields import Field, computed_field


def test_str_bytes():
    class Model(BaseModel):
        v: Union[str, bytes] = ...

    m = Model(v='s')
    assert m.v == 's'
    assert repr(m.model_fields['v']) == 'FieldInfo(annotation=Union[str, bytes], required=True)'

    m = Model(v=b'b')
    assert m.v == b'b'

    with pytest.raises(ValidationError) as exc_info:
        Model(v=None)
    # insert_assert(exc_info.value.errors())
    assert exc_info.value.errors() == [
        {'type': 'string_type', 'loc': ('v', 'str'), 'msg': 'Input should be a valid string', 'input': None},
        {'type': 'bytes_type', 'loc': ('v', 'bytes'), 'msg': 'Input should be a valid bytes', 'input': None},
    ]


def test_str_bytes_none():
    class Model(BaseModel):
        v: Union[None, str, bytes] = ...

    m = Model(v='s')
    assert m.v == 's'

    m = Model(v=b'b')
    assert m.v == b'b'

    m = Model(v=None)
    assert m.v is None


def test_union_int_str():
    class Model(BaseModel):
        v: Union[int, str] = ...

    m = Model(v=123)
    assert m.v == 123

    m = Model(v='123')
    assert m.v == '123'

    m = Model(v=b'foobar')
    assert m.v == 'foobar'

    m = Model(v=12.0)
    assert m.v == 12

    with pytest.raises(ValidationError) as exc_info:
        Model(v=None)
    # insert_assert(exc_info.value.errors())
    assert exc_info.value.errors() == [
        {'type': 'int_type', 'loc': ('v', 'int'), 'msg': 'Input should be a valid integer', 'input': None},
        {
            'type': 'string_type',
            'loc': ('v', 'str'),
            'msg': 'Input should be a valid string',
            'input': None,
        },
    ]


def test_union_int_any():
    class Model(BaseModel):
        v: Union[int, Any]

    m = Model(v=123)
    assert m.v == 123

    m = Model(v='123')
    assert m.v == '123'

    m = Model(v='foobar')
    assert m.v == 'foobar'

    m = Model(v=None)
    assert m.v is None


def test_typed_list():
    class Model(BaseModel):
        v: List[int] = ...

    m = Model(v=[1, 2, '3'])
    assert m.v == [1, 2, 3]

    with pytest.raises(ValidationError) as exc_info:
        Model(v=[1, 'x', 'y'])
    # insert_assert(exc_info.value.errors())
    assert exc_info.value.errors() == [
        {
            'type': 'int_parsing',
            'loc': ('v', 1),
            'msg': 'Input should be a valid integer, unable to parse string as an integer',
            'input': 'x',
        },
        {
            'type': 'int_parsing',
            'loc': ('v', 2),
            'msg': 'Input should be a valid integer, unable to parse string as an integer',
            'input': 'y',
        },
    ]

    with pytest.raises(ValidationError) as exc_info:
        Model(v=1)
    # insert_assert(exc_info.value.errors())
    assert exc_info.value.errors() == [
        {'type': 'list_type', 'loc': ('v',), 'msg': 'Input should be a valid list', 'input': 1}
    ]


def test_typed_set():
    class Model(BaseModel):
        v: Set[int] = ...

    assert Model(v={1, 2, '3'}).v == {1, 2, 3}
    assert Model(v=[1, 2, '3']).v == {1, 2, 3}

    with pytest.raises(ValidationError) as exc_info:
        Model(v=[1, 'x'])
    # insert_assert(exc_info.value.errors())
    assert exc_info.value.errors() == [
        {
            'type': 'int_parsing',
            'loc': ('v', 1),
            'msg': 'Input should be a valid integer, unable to parse string as an integer',
            'input': 'x',
        }
    ]


def test_dict_dict():
    class Model(BaseModel):
        v: Dict[str, int] = ...

    assert Model(v={'foo': 1}).model_dump() == {'v': {'foo': 1}}


def test_none_list():
    class Model(BaseModel):
        v: List[None] = [None]

    assert Model.model_json_schema() == {
        'title': 'Model',
        'type': 'object',
        'properties': {'v': {'title': 'V', 'default': [None], 'type': 'array', 'items': {'type': 'null'}}},
    }


@pytest.mark.parametrize(
    'value,result',
    [
        ({'a': 2, 'b': 4}, {'a': 2, 'b': 4}),
        ({b'a': '2', 'b': 4}, {'a': 2, 'b': 4}),
        # ([('a', 2), ('b', 4)], {'a': 2, 'b': 4}),
    ],
)
def test_typed_dict(value, result):
    class Model(BaseModel):
        v: Dict[str, int] = ...

    assert Model(v=value).v == result


@pytest.mark.parametrize(
    'value,errors',
    [
        (1, [{'type': 'dict_type', 'loc': ('v',), 'msg': 'Input should be a valid dictionary', 'input': 1}]),
        (
            {'a': 'b'},
            [
                {
                    'type': 'int_parsing',
                    'loc': ('v', 'a'),
                    'msg': 'Input should be a valid integer, unable to parse string as an integer',
                    'input': 'b',
                }
            ],
        ),
        (
            [1, 2, 3],
            [{'type': 'dict_type', 'loc': ('v',), 'msg': 'Input should be a valid dictionary', 'input': [1, 2, 3]}],
        ),
    ],
)
def test_typed_dict_error(value, errors):
    class Model(BaseModel):
        v: Dict[str, int] = ...

    with pytest.raises(ValidationError) as exc_info:
        Model(v=value)
    assert exc_info.value.errors() == errors


def test_dict_key_error():
    class Model(BaseModel):
        v: Dict[int, int] = ...

    assert Model(v={1: 2, '3': '4'}).v == {1: 2, 3: 4}

    with pytest.raises(ValidationError) as exc_info:
        Model(v={'foo': 2, '3': '4'})
    # insert_assert(exc_info.value.errors())
    assert exc_info.value.errors() == [
        {
            'type': 'int_parsing',
            'loc': ('v', 'foo', '[key]'),
            'msg': 'Input should be a valid integer, unable to parse string as an integer',
            'input': 'foo',
        }
    ]


def test_tuple():
    class Model(BaseModel):
        v: Tuple[int, float, bool]

    m = Model(v=['1.0', '2.2', 'true'])
    assert m.v == (1, 2.2, True)


def test_tuple_more():
    class Model(BaseModel):
        empty_tuple: Tuple[()]
        simple_tuple: tuple = None
        tuple_of_different_types: Tuple[int, float, str, bool] = None
        tuple_of_single_tuples: Tuple[Tuple[int], ...] = ()

    m = Model(
        empty_tuple=[],
        simple_tuple=[1, 2, 3, 4],
        tuple_of_different_types=[4, 3.1, 'str', 1],
        tuple_of_single_tuples=(('1',), (2,)),
    )
    assert m.model_dump() == {
        'empty_tuple': (),
        'simple_tuple': (1, 2, 3, 4),
        'tuple_of_different_types': (4, 3.1, 'str', True),
        'tuple_of_single_tuples': ((1,), (2,)),
    }


@pytest.mark.parametrize(
    'dict_cls,frozenset_cls,list_cls,set_cls,tuple_cls,type_cls',
    [
        (Dict, FrozenSet, List, Set, Tuple, Type),
        (dict, frozenset, list, set, tuple, type),
    ],
)
@pytest.mark.skipif(sys.version_info < (3, 9), reason='PEP585 generics only supported for python 3.9 and above')
def test_pep585_generic_types(dict_cls, frozenset_cls, list_cls, set_cls, tuple_cls, type_cls):
    class Type1:
        pass

    class Type2:
        pass

    class Model(BaseModel, arbitrary_types_allowed=True):
        a: dict_cls
        a1: dict_cls[str, int]
        b: frozenset_cls
        b1: frozenset_cls[int]
        c: list_cls
        c1: list_cls[int]
        d: set_cls
        d1: set_cls[int]
        e: tuple_cls
        e1: tuple_cls[int]
        e2: tuple_cls[int, ...]
        e3: tuple_cls[()]
        f: type_cls
        f1: type_cls[Type1]

    default_model_kwargs = dict(
        a={},
        a1={'a': '1'},
        b=[],
        b1=('1',),
        c=[],
        c1=('1',),
        d=[],
        d1=['1'],
        e=[],
        e1=['1'],
        e2=['1', '2'],
        e3=[],
        f=Type1,
        f1=Type1,
    )

    m = Model(**default_model_kwargs)
    assert m.a == {}
    assert m.a1 == {'a': 1}
    assert m.b == frozenset()
    assert m.b1 == frozenset({1})
    assert m.c == []
    assert m.c1 == [1]
    assert m.d == set()
    assert m.d1 == {1}
    assert m.e == ()
    assert m.e1 == (1,)
    assert m.e2 == (1, 2)
    assert m.e3 == ()
    assert m.f == Type1
    assert m.f1 == Type1

    with pytest.raises(ValidationError) as exc_info:
        Model(**(default_model_kwargs | {'e3': (1,)}))
    # insert_assert(exc_info.value.errors())
    assert exc_info.value.errors() == [
        {
            'type': 'too_long',
            'loc': ('e3',),
            'msg': 'Tuple should have at most 0 items after validation, not 1',
            'input': (1,),
            'ctx': {'field_type': 'Tuple', 'max_length': 0, 'actual_length': 1},
        }
    ]

    Model(**(default_model_kwargs | {'f': Type2}))

    with pytest.raises(ValidationError) as exc_info:
        Model(**(default_model_kwargs | {'f1': Type2}))
    # insert_assert(exc_info.value.errors())
    assert exc_info.value.errors() == [
        {
            'type': 'is_subclass_of',
            'loc': ('f1',),
            'msg': 'Input should be a subclass of test_pep585_generic_types.<locals>.Type1',
            'input': HasRepr(IsStr(regex=r".+\.Type2'>")),
            'ctx': {'class': 'test_pep585_generic_types.<locals>.Type1'},
        }
    ]


def test_tuple_length_error():
    class Model(BaseModel):
        v: Tuple[int, float, bool]
        w: Tuple[()]

    with pytest.raises(ValidationError) as exc_info:
        Model(v=[1, 2], w=[1])
    # insert_assert(exc_info.value.errors())
    assert exc_info.value.errors() == [
        {'type': 'missing', 'loc': ('v', 2), 'msg': 'Field required', 'input': [1, 2]},
        {
            'type': 'too_long',
            'loc': ('w',),
            'msg': 'Tuple should have at most 0 items after validation, not 1',
            'input': [1],
            'ctx': {'field_type': 'Tuple', 'max_length': 0, 'actual_length': 1},
        },
    ]


def test_tuple_invalid():
    class Model(BaseModel):
        v: Tuple[int, float, bool]

    with pytest.raises(ValidationError) as exc_info:
        Model(v='xxx')
    assert exc_info.value.errors() == [
        {'type': 'tuple_type', 'loc': ('v',), 'msg': 'Input should be a valid tuple', 'input': 'xxx'}
    ]


def test_tuple_value_error():
    class Model(BaseModel):
        v: Tuple[int, float, Decimal]

    with pytest.raises(ValidationError) as exc_info:
        Model(v=['x', 'y', 'x'])
    assert exc_info.value.errors() == [
        {
            'input': 'x',
            'loc': ('v', 0),
            'msg': 'Input should be a valid integer, unable to parse string as an ' 'integer',
            'type': 'int_parsing',
        },
        {
            'input': 'y',
            'loc': ('v', 1),
            'msg': 'Input should be a valid number, unable to parse string as an number',
            'type': 'float_parsing',
        },
        {'input': 'x', 'loc': ('v', 2), 'msg': 'Input should be a valid decimal', 'type': 'decimal_parsing'},
    ]


def test_recursive_list():
    class SubModel(BaseModel):
        name: str = ...
        count: int = None

    class Model(BaseModel):
        v: List[SubModel] = []

    m = Model(v=[])
    assert m.v == []

    m = Model(v=[{'name': 'testing', 'count': 4}])
    assert repr(m) == "Model(v=[SubModel(name='testing', count=4)])"
    assert m.v[0].name == 'testing'
    assert m.v[0].count == 4
    assert m.model_dump() == {'v': [{'count': 4, 'name': 'testing'}]}

    with pytest.raises(ValidationError) as exc_info:
        Model(v=['x'])
    assert exc_info.value.errors() == [
        {'input': 'x', 'loc': ('v', 0), 'msg': 'Input should be a valid dictionary', 'type': 'dict_type'}
    ]


def test_recursive_list_error():
    class SubModel(BaseModel):
        name: str = ...
        count: int = None

    class Model(BaseModel):
        v: List[SubModel] = []

    with pytest.raises(ValidationError) as exc_info:
        Model(v=[{}])
    assert exc_info.value.errors() == [
        {'input': {}, 'loc': ('v', 0, 'name'), 'msg': 'Field required', 'type': 'missing'}
    ]


def test_list_unions():
    class Model(BaseModel):
        v: List[Union[int, str]] = ...

    assert Model(v=[123, '456', 'foobar']).v == [123, '456', 'foobar']

    with pytest.raises(ValidationError) as exc_info:
        Model(v=[1, 2, None])

    assert exc_info.value.errors() == [
        {'input': None, 'loc': ('v', 2, 'int'), 'msg': 'Input should be a valid integer', 'type': 'int_type'},
        {'input': None, 'loc': ('v', 2, 'str'), 'msg': 'Input should be a valid string', 'type': 'string_type'},
    ]


def test_recursive_lists():
    class Model(BaseModel):
        v: List[List[Union[int, float]]] = ...

    assert Model(v=[[1, 2], [3, '4', '4.1']]).v == [[1, 2], [3, 4, 4.1]]
    assert Model.model_fields['v'].annotation == List[List[Union[int, float]]]
    assert Model.model_fields['v'].is_required()


class StrEnum(str, Enum):
    a = 'a10'
    b = 'b10'


def test_str_enum():
    class Model(BaseModel):
        v: StrEnum = ...

    assert Model(v='a10').v is StrEnum.a

    with pytest.raises(ValidationError):
        Model(v='different')


def test_any_dict():
    class Model(BaseModel):
        v: Dict[int, Any] = ...

    assert Model(v={1: 'foobar'}).model_dump() == {'v': {1: 'foobar'}}
    assert Model(v={123: 456}).model_dump() == {'v': {123: 456}}
    assert Model(v={2: [1, 2, 3]}).model_dump() == {'v': {2: [1, 2, 3]}}


def test_success_values_include():
    class Model(BaseModel):
        a: int = 1
        b: int = 2
        c: int = 3

    m = Model()
    assert m.model_dump() == {'a': 1, 'b': 2, 'c': 3}
    assert m.model_dump(include={'a'}) == {'a': 1}
    assert m.model_dump(exclude={'a'}) == {'b': 2, 'c': 3}
    assert m.model_dump(include={'a', 'b'}, exclude={'a'}) == {'b': 2}


def test_include_exclude_unset():
    class Model(BaseModel):
        a: int
        b: int
        c: int = 3
        d: int = 4
        e: int = 5
        f: int = 6

    m = Model(a=1, b=2, e=5, f=7)
    assert m.model_dump() == {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 7}
    assert m.model_fields_set == {'a', 'b', 'e', 'f'}
    assert m.model_dump(exclude_unset=True) == {'a': 1, 'b': 2, 'e': 5, 'f': 7}

    assert m.model_dump(include={'a'}, exclude_unset=True) == {'a': 1}
    assert m.model_dump(include={'c'}, exclude_unset=True) == {}

    assert m.model_dump(exclude={'a'}, exclude_unset=True) == {'b': 2, 'e': 5, 'f': 7}
    assert m.model_dump(exclude={'c'}, exclude_unset=True) == {'a': 1, 'b': 2, 'e': 5, 'f': 7}

    assert m.model_dump(include={'a', 'b', 'c'}, exclude={'b'}, exclude_unset=True) == {'a': 1}
    assert m.model_dump(include={'a', 'b', 'c'}, exclude={'a', 'c'}, exclude_unset=True) == {'b': 2}


def test_include_exclude_defaults():
    class Model(BaseModel):
        a: int
        b: int
        c: int = 3
        d: int = 4
        e: int = 5
        f: int = 6

    m = Model(a=1, b=2, e=5, f=7)
    assert m.model_dump() == {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 7}
    assert m.model_fields_set == {'a', 'b', 'e', 'f'}
    assert m.model_dump(exclude_defaults=True) == {'a': 1, 'b': 2, 'f': 7}

    assert m.model_dump(include={'a'}, exclude_defaults=True) == {'a': 1}
    assert m.model_dump(include={'c'}, exclude_defaults=True) == {}

    assert m.model_dump(exclude={'a'}, exclude_defaults=True) == {'b': 2, 'f': 7}
    assert m.model_dump(exclude={'c'}, exclude_defaults=True) == {'a': 1, 'b': 2, 'f': 7}

    assert m.model_dump(include={'a', 'b', 'c'}, exclude={'b'}, exclude_defaults=True) == {'a': 1}
    assert m.model_dump(include={'a', 'b', 'c'}, exclude={'a', 'c'}, exclude_defaults=True) == {'b': 2}

    assert m.model_dump(include={'a': 1}.keys()) == {'a': 1}
    assert m.model_dump(exclude={'a': 1}.keys()) == {'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 7}

    assert m.model_dump(include={'a': 1}.keys(), exclude_unset=True) == {'a': 1}
    assert m.model_dump(exclude={'a': 1}.keys(), exclude_unset=True) == {'b': 2, 'e': 5, 'f': 7}

    assert m.model_dump(include=['a']) == {'a': 1}
    assert m.model_dump(exclude=['a']) == {'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 7}

    assert m.model_dump(include=['a'], exclude_unset=True) == {'a': 1}
    assert m.model_dump(exclude=['a'], exclude_unset=True) == {'b': 2, 'e': 5, 'f': 7}


@pytest.mark.xfail(reason='pydantic-core include/exclude does not wrap negative ints')
def test_advanced_exclude():
    class SubSubModel(BaseModel):
        a: str
        b: str

    class SubModel(BaseModel):
        c: str
        d: List[SubSubModel]

    class Model(BaseModel):
        e: str
        f: SubModel

    m = Model(e='e', f=SubModel(c='foo', d=[SubSubModel(a='a', b='b'), SubSubModel(a='c', b='e')]))

    assert m.model_dump(exclude={'f': {'c': ..., 'd': {-1: {'a'}}}}) == {
        'e': 'e',
        'f': {'d': [{'a': 'a', 'b': 'b'}, {'b': 'e'}]},
    }
    assert m.model_dump(exclude={'e': ..., 'f': {'d'}}) == {'f': {'c': 'foo'}}


@pytest.mark.xfail(reason='pydantic-core include/exclude does not wrap negative ints')
def test_advanced_exclude_by_alias():
    class SubSubModel(BaseModel):
        a: str
        aliased_b: str = Field(..., alias='b_alias')

    class SubModel(BaseModel):
        aliased_c: str = Field(..., alias='c_alias')
        aliased_d: List[SubSubModel] = Field(..., alias='d_alias')

    class Model(BaseModel):
        aliased_e: str = Field(..., alias='e_alias')
        aliased_f: SubModel = Field(..., alias='f_alias')

    m = Model(
        e_alias='e',
        f_alias=SubModel(c_alias='foo', d_alias=[SubSubModel(a='a', b_alias='b'), SubSubModel(a='c', b_alias='e')]),
    )

    excludes = {'aliased_f': {'aliased_c': ..., 'aliased_d': {-1: {'a'}}}}
    assert m.model_dump(exclude=excludes, by_alias=True) == {
        'e_alias': 'e',
        'f_alias': {'d_alias': [{'a': 'a', 'b_alias': 'b'}, {'b_alias': 'e'}]},
    }

    excludes = {'aliased_e': ..., 'aliased_f': {'aliased_d'}}
    assert m.model_dump(exclude=excludes, by_alias=True) == {'f_alias': {'c_alias': 'foo'}}


@pytest.mark.xfail(reason='pydantic-core include/exclude does not wrap negative ints')
def test_advanced_value_include():
    class SubSubModel(BaseModel):
        a: str
        b: str

    class SubModel(BaseModel):
        c: str
        d: List[SubSubModel]

    class Model(BaseModel):
        e: str
        f: SubModel

    m = Model(e='e', f=SubModel(c='foo', d=[SubSubModel(a='a', b='b'), SubSubModel(a='c', b='e')]))

    assert m.model_dump(include={'f'}) == {'f': {'c': 'foo', 'd': [{'a': 'a', 'b': 'b'}, {'a': 'c', 'b': 'e'}]}}
    assert m.model_dump(include={'e'}) == {'e': 'e'}
    assert m.model_dump(include={'f': {'d': {0: ..., -1: {'b'}}}}) == {'f': {'d': [{'a': 'a', 'b': 'b'}, {'b': 'e'}]}}


@pytest.mark.xfail(reason='pydantic-core include/exclude does not wrap negative ints')
def test_advanced_value_exclude_include():
    class SubSubModel(BaseModel):
        a: str
        b: str

    class SubModel(BaseModel):
        c: str
        d: List[SubSubModel]

    class Model(BaseModel):
        e: str
        f: SubModel

    m = Model(e='e', f=SubModel(c='foo', d=[SubSubModel(a='a', b='b'), SubSubModel(a='c', b='e')]))

    assert m.model_dump(exclude={'f': {'c': ..., 'd': {-1: {'a'}}}}, include={'f'}) == {
        'f': {'d': [{'a': 'a', 'b': 'b'}, {'b': 'e'}]}
    }
    assert m.model_dump(exclude={'e': ..., 'f': {'d'}}, include={'e', 'f'}) == {'f': {'c': 'foo'}}

    assert m.model_dump(exclude={'f': {'d': {-1: {'a'}}}}, include={'f': {'d'}}) == {
        'f': {'d': [{'a': 'a', 'b': 'b'}, {'b': 'e'}]}
    }


@pytest.mark.parametrize(
    'exclude,expected',
    [
        pytest.param(
            {'subs': {'__all__': {'subsubs': {'__all__': {'i'}}}}},
            {'subs': [{'k': 1, 'subsubs': [{'j': 1}, {'j': 2}]}, {'k': 2, 'subsubs': [{'j': 3}]}]},
            id='Normal nested __all__',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs': {'__all__': {'i'}}}, 0: {'subsubs': {'__all__': {'j'}}}}},
            {'subs': [{'k': 1, 'subsubs': [{}, {}]}, {'k': 2, 'subsubs': [{'j': 3}]}]},
            id='Merge sub dicts 1',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs': ...}, 0: {'subsubs': {'__all__': {'j'}}}}},
            {'subs': [{'k': 1, 'subsubs': [{'i': 1}, {'i': 2}]}, {'k': 2}]},
            # {'subs': [{'k': 1                                 }, {'k': 2}]}
            id='Merge sub sets 2',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs': {'__all__': {'j'}}}, 0: {'subsubs': ...}}},
            {'subs': [{'k': 1}, {'k': 2, 'subsubs': [{'i': 3}]}]},
            id='Merge sub sets 3',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs': {0}}, 0: {'subsubs': {1}}}},
            {'subs': [{'k': 1, 'subsubs': []}, {'k': 2, 'subsubs': []}]},
            id='Merge sub sets 1',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs': {0: {'i'}}}, 0: {'subsubs': {1}}}},
            {'subs': [{'k': 1, 'subsubs': [{'j': 1}]}, {'k': 2, 'subsubs': [{'j': 3}]}]},
            id='Merge sub dict-set',
        ),
        pytest.param({'subs': {'__all__': {'subsubs'}, 0: {'k'}}}, {'subs': [{}, {'k': 2}]}, id='Different keys 1'),
        pytest.param(
            {'subs': {'__all__': {'subsubs': ...}, 0: {'k'}}}, {'subs': [{}, {'k': 2}]}, id='Different keys 2'
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs'}, 0: {'k': ...}}}, {'subs': [{}, {'k': 2}]}, id='Different keys 3'
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs': {'__all__': {'i'}, 0: {'j'}}}}},
            {'subs': [{'k': 1, 'subsubs': [{}, {'j': 2}]}, {'k': 2, 'subsubs': [{}]}]},
            id='Nested different keys 1',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs': {'__all__': {'i': ...}, 0: {'j'}}}}},
            {'subs': [{'k': 1, 'subsubs': [{}, {'j': 2}]}, {'k': 2, 'subsubs': [{}]}]},
            id='Nested different keys 2',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs': {'__all__': {'i'}, 0: {'j': ...}}}}},
            {'subs': [{'k': 1, 'subsubs': [{}, {'j': 2}]}, {'k': 2, 'subsubs': [{}]}]},
            id='Nested different keys 3',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs'}, 0: {'subsubs': {'__all__': {'j'}}}}},
            {'subs': [{'k': 1, 'subsubs': [{'i': 1}, {'i': 2}]}, {'k': 2}]},
            id='Ignore __all__ for index with defined exclude 1',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs': {'__all__': {'j'}}}, 0: ...}},
            {'subs': [{'k': 2, 'subsubs': [{'i': 3}]}]},
            id='Ignore __all__ for index with defined exclude 2',
        ),
        pytest.param(
            {'subs': {'__all__': ..., 0: {'subsubs'}}},
            {'subs': [{'k': 1}]},
            id='Ignore __all__ for index with defined exclude 3',
        ),
    ],
)
def test_advanced_exclude_nested_lists(exclude, expected):
    class SubSubModel(BaseModel):
        i: int
        j: int

    class SubModel(BaseModel):
        k: int
        subsubs: List[SubSubModel]

    class Model(BaseModel):
        subs: List[SubModel]

    m = Model(subs=[dict(k=1, subsubs=[dict(i=1, j=1), dict(i=2, j=2)]), dict(k=2, subsubs=[dict(i=3, j=3)])])

    assert m.model_dump(exclude=exclude) == expected


@pytest.mark.parametrize(
    'include,expected',
    [
        pytest.param(
            {'subs': {'__all__': {'subsubs': {'__all__': {'i'}}}}},
            {'subs': [{'subsubs': [{'i': 1}, {'i': 2}]}, {'subsubs': [{'i': 3}]}]},
            id='Normal nested __all__',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs': {'__all__': {'i'}}}, 0: {'subsubs': {'__all__': {'j'}}}}},
            {'subs': [{'subsubs': [{'i': 1, 'j': 1}, {'i': 2, 'j': 2}]}, {'subsubs': [{'i': 3}]}]},
            id='Merge sub dicts 1',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs': ...}, 0: {'subsubs': {'__all__': {'j'}}}}},
            {'subs': [{'subsubs': [{'j': 1}, {'j': 2}]}, {'subsubs': [{'i': 3, 'j': 3}]}]},
            id='Merge sub dicts 2',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs': {'__all__': {'j'}}}, 0: {'subsubs': ...}}},
            {'subs': [{'subsubs': [{'i': 1, 'j': 1}, {'i': 2, 'j': 2}]}, {'subsubs': [{'j': 3}]}]},
            id='Merge sub dicts 3',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs': {0}}, 0: {'subsubs': {1}}}},
            {'subs': [{'subsubs': [{'i': 1, 'j': 1}, {'i': 2, 'j': 2}]}, {'subsubs': [{'i': 3, 'j': 3}]}]},
            id='Merge sub sets',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs': {0: {'i'}}}, 0: {'subsubs': {1}}}},
            {'subs': [{'subsubs': [{'i': 1}, {'i': 2, 'j': 2}]}, {'subsubs': [{'i': 3}]}]},
            id='Merge sub dict-set',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs'}, 0: {'k'}}},
            {'subs': [{'k': 1, 'subsubs': [{'i': 1, 'j': 1}, {'i': 2, 'j': 2}]}, {'subsubs': [{'i': 3, 'j': 3}]}]},
            id='Nested different keys 1',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs': ...}, 0: {'k'}}},
            {'subs': [{'k': 1, 'subsubs': [{'i': 1, 'j': 1}, {'i': 2, 'j': 2}]}, {'subsubs': [{'i': 3, 'j': 3}]}]},
            id='Nested different keys 2',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs'}, 0: {'k': ...}}},
            {'subs': [{'k': 1, 'subsubs': [{'i': 1, 'j': 1}, {'i': 2, 'j': 2}]}, {'subsubs': [{'i': 3, 'j': 3}]}]},
            id='Nested different keys 3',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs': {'__all__': {'i'}, 0: {'j'}}}}},
            {'subs': [{'subsubs': [{'i': 1, 'j': 1}, {'i': 2}]}, {'subsubs': [{'i': 3, 'j': 3}]}]},
            id='Nested different keys 1',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs': {'__all__': {'i': ...}, 0: {'j'}}}}},
            {'subs': [{'subsubs': [{'i': 1, 'j': 1}, {'i': 2}]}, {'subsubs': [{'i': 3, 'j': 3}]}]},
            id='Nested different keys 2',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs': {'__all__': {'i'}, 0: {'j': ...}}}}},
            {'subs': [{'subsubs': [{'i': 1, 'j': 1}, {'i': 2}]}, {'subsubs': [{'i': 3, 'j': 3}]}]},
            id='Nested different keys 3',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs'}, 0: {'subsubs': {'__all__': {'j'}}}}},
            {'subs': [{'subsubs': [{'j': 1}, {'j': 2}]}, {'subsubs': [{'i': 3, 'j': 3}]}]},
            id='Ignore __all__ for index with defined include 1',
        ),
        pytest.param(
            {'subs': {'__all__': {'subsubs': {'__all__': {'j'}}}, 0: ...}},
            {'subs': [{'k': 1, 'subsubs': [{'i': 1, 'j': 1}, {'i': 2, 'j': 2}]}, {'subsubs': [{'j': 3}]}]},
            id='Ignore __all__ for index with defined include 2',
        ),
        pytest.param(
            {'subs': {'__all__': ..., 0: {'subsubs'}}},
            {'subs': [{'subsubs': [{'i': 1, 'j': 1}, {'i': 2, 'j': 2}]}, {'k': 2, 'subsubs': [{'i': 3, 'j': 3}]}]},
            id='Ignore __all__ for index with defined include 3',
        ),
    ],
)
def test_advanced_include_nested_lists(include, expected):
    class SubSubModel(BaseModel):
        i: int
        j: int

    class SubModel(BaseModel):
        k: int
        subsubs: List[SubSubModel]

    class Model(BaseModel):
        subs: List[SubModel]

    m = Model(subs=[dict(k=1, subsubs=[dict(i=1, j=1), dict(i=2, j=2)]), dict(k=2, subsubs=[dict(i=3, j=3)])])

    assert m.model_dump(include=include) == expected


def test_field_set_ignore_extra():
    class Model(BaseModel):
        model_config = ConfigDict(extra='ignore')
        a: int
        b: int
        c: int = 3

    m = Model(a=1, b=2)
    assert m.model_dump() == {'a': 1, 'b': 2, 'c': 3}
    assert m.model_fields_set == {'a', 'b'}
    assert m.model_dump(exclude_unset=True) == {'a': 1, 'b': 2}

    m2 = Model(a=1, b=2, d=4)
    assert m2.model_dump() == {'a': 1, 'b': 2, 'c': 3}
    assert m2.model_fields_set == {'a', 'b'}
    assert m2.model_dump(exclude_unset=True) == {'a': 1, 'b': 2}


def test_field_set_allow_extra():
    class Model(BaseModel):
        model_config = ConfigDict(extra='allow')
        a: int
        b: int
        c: int = 3

    m = Model(a=1, b=2)
    assert m.model_dump() == {'a': 1, 'b': 2, 'c': 3}
    assert m.model_fields_set == {'a', 'b'}
    assert m.model_dump(exclude_unset=True) == {'a': 1, 'b': 2}

    m2 = Model(a=1, b=2, d=4)
    assert m2.model_dump() == {'a': 1, 'b': 2, 'c': 3, 'd': 4}
    assert m2.model_fields_set == {'a', 'b', 'd'}
    assert m2.model_dump(exclude_unset=True) == {'a': 1, 'b': 2, 'd': 4}


def test_field_set_field_name():
    class Model(BaseModel):
        a: int
        field_set: int
        b: int = 3

    assert Model(a=1, field_set=2).model_dump() == {'a': 1, 'field_set': 2, 'b': 3}
    assert Model(a=1, field_set=2).model_dump(exclude_unset=True) == {'a': 1, 'field_set': 2}
    assert Model.model_construct(a=1, field_set=3).model_dump() == {'a': 1, 'field_set': 3, 'b': 3}


def test_values_order():
    class Model(BaseModel):
        a: int = 1
        b: int = 2
        c: int = 3

    m = Model(c=30, b=20, a=10)
    assert list(m) == [('a', 10), ('b', 20), ('c', 30)]


def test_inheritance():
    class Foo(BaseModel):
        a: float = ...

    with pytest.raises(
        TypeError,
        match=(
            "Field 'a' defined on a base class was overridden by a non-annotated attribute. "
            'All field definitions, including overrides, require a type annotation.'
        ),
    ):

        class Bar(Foo):
            x: float = 12.3
            a = 123.0

    class Bar2(Foo):
        x: float = 12.3
        a: float = 123.0

    assert Bar2().model_dump() == {'x': 12.3, 'a': 123.0}

    class Bar3(Foo):
        x: float = 12.3
        a: float = Field(default=123.0)

    assert Bar3().model_dump() == {'x': 12.3, 'a': 123.0}


def test_inheritance_subclass_default():
    class MyStr(str):
        pass

    # Confirm hint supports a subclass default
    class Simple(BaseModel):
        x: str = MyStr('test')

        model_config = dict(arbitrary_types_allowed=True)

    # Confirm hint on a base can be overridden with a subclass default on a subclass
    class Base(BaseModel):
        x: str
        y: str

    class Sub(Base):
        x: str = MyStr('test')
        y: MyStr = MyStr('test')  # force subtype

        model_config = dict(arbitrary_types_allowed=True)

    assert Sub.model_fields['x'].annotation == str
    assert Sub.model_fields['y'].annotation == MyStr


def test_invalid_type():
    with pytest.raises(PydanticSchemaGenerationError) as exc_info:

        class Model(BaseModel):
            x: 43 = 123

    assert 'Unable to generate pydantic-core schema for 43' in exc_info.value.args[0]


class CustomStr(str):
    def foobar(self):
        return 7


@pytest.mark.parametrize(
    'value,expected',
    [
        ('a string', 'a string'),
        (b'some bytes', 'some bytes'),
        (bytearray('foobar', encoding='utf8'), 'foobar'),
        (StrEnum.a, 'a10'),
        (CustomStr('whatever'), 'whatever'),
    ],
)
def test_valid_string_types(value, expected):
    class Model(BaseModel):
        v: str

    assert Model(v=value).v == expected


@pytest.mark.parametrize(
    'value,errors',
    [
        (
            {'foo': 'bar'},
            [{'input': {'foo': 'bar'}, 'loc': ('v',), 'msg': 'Input should be a valid string', 'type': 'string_type'}],
        ),
        (
            [1, 2, 3],
            [{'input': [1, 2, 3], 'loc': ('v',), 'msg': 'Input should be a valid string', 'type': 'string_type'}],
        ),
    ],
)
def test_invalid_string_types(value, errors):
    class Model(BaseModel):
        v: str

    with pytest.raises(ValidationError) as exc_info:
        Model(v=value)
    assert exc_info.value.errors() == errors


def test_inheritance_config():
    class Parent(BaseModel):
        a: str

    class Child(Parent):
        model_config = ConfigDict(str_to_lower=True)
        b: str

    m1 = Parent(a='A')
    m2 = Child(a='A', b='B')
    assert repr(m1) == "Parent(a='A')"
    assert repr(m2) == "Child(a='a', b='b')"


def test_partial_inheritance_config():
    class Parent(BaseModel):
        a: int = Field(ge=0)

    class Child(Parent):
        b: int = Field(ge=0)

    Child(a=0, b=0)
    with pytest.raises(ValidationError) as exc_info:
        Child(a=-1, b=0)
    assert exc_info.value.errors() == [
        {
            'ctx': {'ge': 0},
            'input': -1,
            'loc': ('a',),
            'msg': 'Input should be greater than or equal to 0',
            'type': 'greater_than_equal',
        }
    ]

    with pytest.raises(ValidationError) as exc_info:
        Child(a=0, b=-1)
    assert exc_info.value.errors() == [
        {
            'ctx': {'ge': 0},
            'input': -1,
            'loc': ('b',),
            'msg': 'Input should be greater than or equal to 0',
            'type': 'greater_than_equal',
        }
    ]


def test_annotation_inheritance():
    class A(BaseModel):
        integer: int = 1

    class B(A):
        integer: int = 2

    assert B.model_fields['integer'].annotation == int

    class C(A):
        integer: str = 'G'

    assert C.__annotations__['integer'] == str
    assert C.model_fields['integer'].annotation == str

    with pytest.raises(
        TypeError,
        match=(
            "Field 'integer' defined on a base class was overridden by a non-annotated attribute. "
            "All field definitions, including overrides, require a type annotation."
        ),
    ):

        class D(A):
            integer = 'G'


def test_string_none():
    class Model(BaseModel):
        model_config = ConfigDict(extra='ignore')
        a: constr(min_length=20, max_length=1000) = ...

    with pytest.raises(ValidationError) as exc_info:
        Model(a=None)
    assert exc_info.value.errors() == [
        {'input': None, 'loc': ('a',), 'msg': 'Input should be a valid string', 'type': 'string_type'}
    ]


# def test_return_errors_ok():
#     class Model(BaseModel):
#         foo: int
#         bar: List[int]
#
#     assert validate_model(Model, {'foo': '123', 'bar': (1, 2, 3)}) == (
#         {'foo': 123, 'bar': [1, 2, 3]},
#         {'foo', 'bar'},
#         None,
#     )
#     d, f, e = validate_model(Model, {'foo': '123', 'bar': (1, 2, 3)}, False)
#     assert d == {'foo': 123, 'bar': [1, 2, 3]}
#     assert f == {'foo', 'bar'}
#     assert e is None


# def test_return_errors_error():
#     class Model(BaseModel):
#         foo: int
#         bar: List[int]
#
#     d, f, e = validate_model(Model, {'foo': '123', 'bar': (1, 2, 'x')}, False)
#     assert d == {'foo': 123}
#     assert f == {'foo', 'bar'}
#     assert e.errors() == [{'loc': ('bar', 2), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'}]
#
#     d, f, e = validate_model(Model, {'bar': (1, 2, 3)}, False)
#     assert d == {'bar': [1, 2, 3]}
#     assert f == {'bar'}
#     assert e.errors() == [{'loc': ('foo',), 'msg': 'field required', 'type': 'value_error.missing'}]


def test_optional_required():
    class Model(BaseModel):
        bar: Optional[int]

    assert Model(bar=123).model_dump() == {'bar': 123}
    assert Model(bar=None).model_dump() == {'bar': None}

    with pytest.raises(ValidationError) as exc_info:
        Model()
    assert exc_info.value.errors() == [{'input': {}, 'loc': ('bar',), 'msg': 'Field required', 'type': 'missing'}]


@pytest.mark.xfail(reason='items yielded by __get_validators__ are not inspected for valid signatures')
def test_invalid_validator():
    class InvalidValidator:
        @classmethod
        def __get_validators__(cls):
            yield cls.has_wrong_arguments

        @classmethod
        def has_wrong_arguments(cls, value, bar):
            pass

    with pytest.raises(errors.PydanticUserError, match='Invalid signature for validator'):

        class InvalidValidatorModel(BaseModel):
            model_config = dict(arbitrary_types_allowed=True)
            x: InvalidValidator = ...


def test_unable_to_infer():
    with pytest.raises(
        errors.PydanticUserError,
        match=re.escape(
            "A non-annotated attribute was detected: `x = None`. All model fields require a type annotation; "
            "if `x` is not meant to be a field, you may be able to resolve this error by annotating it as a "
            "`ClassVar` or updating `model_config['ignored_types']`"
        ),
    ):

        class InvalidDefinitionModel(BaseModel):
            x = None


def test_multiple_errors():
    class Model(BaseModel):
        a: Union[None, int, float, Decimal]

    with pytest.raises(ValidationError) as exc_info:
        Model(a='foobar')

    assert exc_info.value.errors() == [
        {
            'type': 'int_parsing',
            'loc': ('a', 'int'),
            'msg': 'Input should be a valid integer, unable to parse string as an integer',
            'input': 'foobar',
        },
        {
            'type': 'float_parsing',
            'loc': ('a', 'float'),
            'msg': 'Input should be a valid number, unable to parse string as an number',
            'input': 'foobar',
        },
        {
            'type': 'decimal_parsing',
            'loc': (
                'a',
                'lax-or-strict[lax=function-after[DecimalValidator(allow_inf_nan=False, check_digits=False, strict=False)(), union[is-instance[Decimal],int,float,constrained-str]],strict=custom-error[function-after[DecimalValidator(allow_inf_nan=False, check_digits=False, strict=False)(), is-instance[Decimal]]]]',  # noqa: E501
            ),
            'msg': 'Input should be a valid decimal',
            'input': 'foobar',
        },
    ]

    assert Model(a=1.5).a == 1.5
    assert Model(a=None).a is None


def test_validate_default():
    class Model(BaseModel):
        model_config = ConfigDict(validate_default=True)
        a: int
        b: int

    with pytest.raises(ValidationError) as exc_info:
        Model()
    assert exc_info.value.errors() == [
        {'input': {}, 'loc': ('a',), 'msg': 'Field required', 'type': 'missing'},
        {'input': {}, 'loc': ('b',), 'msg': 'Field required', 'type': 'missing'},
    ]


def test_force_extra():
    class Model(BaseModel):
        model_config = ConfigDict(extra='ignore')
        foo: int

    assert Model.model_config['extra'] == 'ignore'


def test_submodel_different_type():
    class Foo(BaseModel):
        a: int

    class Bar(BaseModel):
        b: int

    class Spam(BaseModel):
        c: Foo

    assert Spam(c={'a': '123'}).model_dump() == {'c': {'a': 123}}
    with pytest.raises(ValidationError):
        Spam(c={'b': '123'})

    assert Spam(c=Foo(a='123')).model_dump() == {'c': {'a': 123}}
    with pytest.raises(ValidationError):
        Spam(c=Bar(b='123'))


def test_self():
    class Model(BaseModel):
        self: str

    m = Model.model_validate(dict(self='some value'))
    assert m.model_dump() == {'self': 'some value'}
    assert m.self == 'some value'
    assert m.model_json_schema() == {
        'title': 'Model',
        'type': 'object',
        'properties': {'self': {'title': 'Self', 'type': 'string'}},
        'required': ['self'],
    }


def test_self_recursive():
    class SubModel(BaseModel):
        self: int

    class Model(BaseModel):
        sm: SubModel

    m = Model.model_validate({'sm': {'self': '123'}})
    assert m.model_dump() == {'sm': {'self': 123}}


@pytest.mark.xfail(reason='need to detect and error if you override __init__; need to suggest a migration path')
def test_nested_init():
    class NestedModel(BaseModel):
        self: str
        modified_number: int = 1

        def __init__(someinit, **kwargs):
            super().__init__(**kwargs)
            someinit.modified_number += 1

    class TopModel(BaseModel):
        self: str
        nest: NestedModel

    # TODO: Do we want any changes to this behavior in v2? (Currently the __init__-override is not called)
    #   "I guess this should be an error or warning. If you want to do stuff on init, you should use model_post_init"
    #   https://github.com/pydantic/pydantic/pull/5151#discussion_r1130684097
    #   -
    #   I think we can detect and warn/error if you override `__init__`. If we do that,
    #   we'll need to add a note to the migration guide about it.
    m = TopModel.model_validate(dict(self='Top Model', nest=dict(self='Nested Model', modified_number=0)))
    assert m.self == 'Top Model'
    assert m.nest.self == 'Nested Model'
    assert m.nest.modified_number == 1


def test_init_inspection():
    class Foobar(BaseModel):
        x: int

        def __init__(self, **data) -> None:
            with pytest.raises(AttributeError):
                assert self.x
            super().__init__(**data)

    Foobar(x=1)


def test_type_on_annotation():
    class FooBar:
        pass

    class Model(BaseModel):
        a: int = int
        b: Type[int]
        c: Type[int] = int
        d: FooBar = FooBar
        e: Type[FooBar]
        f: Type[FooBar] = FooBar
        g: Sequence[Type[FooBar]] = [FooBar]
        h: Union[Type[FooBar], Sequence[Type[FooBar]]] = FooBar
        i: Union[Type[FooBar], Sequence[Type[FooBar]]] = [FooBar]

        model_config = dict(arbitrary_types_allowed=True)

    assert Model.model_fields.keys() == set('abcdefghi')


def test_assign_type():
    class Parent:
        def echo(self):
            return 'parent'

    class Child(Parent):
        def echo(self):
            return 'child'

    class Different:
        def echo(self):
            return 'different'

    class Model(BaseModel):
        v: Type[Parent] = Parent

    assert Model(v=Parent).v().echo() == 'parent'
    assert Model().v().echo() == 'parent'
    assert Model(v=Child).v().echo() == 'child'
    with pytest.raises(ValidationError) as exc_info:
        Model(v=Different)
    assert exc_info.value.errors() == [
        {
            'ctx': {'class': 'test_assign_type.<locals>.Parent'},
            'input': HasRepr("<class 'tests.test_edge_cases.test_assign_type.<locals>.Different'>"),
            'loc': ('v',),
            'msg': 'Input should be a subclass of test_assign_type.<locals>.Parent',
            'type': 'is_subclass_of',
        }
    ]


def test_optional_subfields():
    class Model(BaseModel):
        a: Optional[int]

    assert Model.model_fields['a'].annotation == Optional[int]

    with pytest.raises(ValidationError) as exc_info:
        Model(a='foobar')
    assert exc_info.value.errors() == [
        {
            'input': 'foobar',
            'loc': ('a',),
            'msg': 'Input should be a valid integer, unable to parse string as an ' 'integer',
            'type': 'int_parsing',
        }
    ]

    with pytest.raises(ValidationError) as exc_info:
        Model()
    assert exc_info.value.errors() == [{'input': {}, 'loc': ('a',), 'msg': 'Field required', 'type': 'missing'}]

    assert Model(a=None).a is None
    assert Model(a=12).a == 12


def test_validated_optional_subfields():
    class Model(BaseModel):
        a: Optional[int]

        @field_validator('a')
        @classmethod
        def check_a(cls, v):
            return v

    assert Model.model_fields['a'].annotation == Optional[int]

    with pytest.raises(ValidationError) as exc_info:
        Model(a='foobar')
    assert exc_info.value.errors() == [
        {
            'input': 'foobar',
            'loc': ('a',),
            'msg': 'Input should be a valid integer, unable to parse string as an ' 'integer',
            'type': 'int_parsing',
        }
    ]

    with pytest.raises(ValidationError) as exc_info:
        Model()
    assert exc_info.value.errors() == [{'input': {}, 'loc': ('a',), 'msg': 'Field required', 'type': 'missing'}]

    assert Model(a=None).a is None
    assert Model(a=12).a == 12


def test_optional_field_constraints():
    class MyModel(BaseModel):
        my_int: Optional[int] = Field(..., ge=3)

    with pytest.raises(ValidationError) as exc_info:
        MyModel(my_int=2)
    assert exc_info.value.errors() == [
        {
            'ctx': {'ge': 3},
            'input': 2,
            'loc': ('my_int',),
            'msg': 'Input should be greater than or equal to 3',
            'type': 'greater_than_equal',
        }
    ]


def test_field_str_shape():
    class Model(BaseModel):
        a: List[int]

    assert repr(Model.model_fields['a']) == 'FieldInfo(annotation=List[int], required=True)'
    assert str(Model.model_fields['a']) == 'annotation=List[int] required=True'


T1 = TypeVar('T1')
T2 = TypeVar('T2')


class DisplayGen(Generic[T1, T2]):
    def __init__(self, t1: T1, t2: T2):
        self.t1 = t1
        self.t2 = t2

    @classmethod
    def __get_validators__(cls):
        def validator(v):
            return v

        yield validator


@pytest.mark.parametrize(
    'type_,expected',
    [
        (int, 'int'),
        (Optional[int], 'Union[int, NoneType]'),
        (Union[None, int, str], 'Union[NoneType, int, str]'),
        (Union[int, str, bytes], 'Union[int, str, bytes]'),
        (List[int], 'List[int]'),
        (Tuple[int, str, bytes], 'Tuple[int, str, bytes]'),
        (Union[List[int], Set[bytes]], 'Union[List[int], Set[bytes]]'),
        (List[Tuple[int, int]], 'List[Tuple[int, int]]'),
        (Dict[int, str], 'Dict[int, str]'),
        (FrozenSet[int], 'FrozenSet[int]'),
        (Tuple[int, ...], 'Tuple[int, ...]'),
        (Optional[List[int]], 'Union[List[int], NoneType]'),
        (dict, 'dict'),
        pytest.param(
            DisplayGen[bool, str],
            'DisplayGen[bool, str]',
            marks=pytest.mark.skipif(sys.version_info[:2] <= (3, 9), reason='difference in __name__ between versions'),
        ),
        pytest.param(
            DisplayGen[bool, str],
            'tests.test_edge_cases.DisplayGen[bool, str]',
            marks=pytest.mark.skipif(sys.version_info[:2] > (3, 9), reason='difference in __name__ between versions'),
        ),
    ],
)
def test_field_type_display(type_, expected):
    class Model(BaseModel):
        a: type_

        model_config = dict(arbitrary_types_allowed=True)

    assert re.search(fr'\(annotation={re.escape(expected)},', str(Model.model_fields))


def test_any_none():
    class MyModel(BaseModel):
        foo: Any

    m = MyModel(foo=None)
    assert dict(m) == {'foo': None}


def test_type_var_any():
    Foobar = TypeVar('Foobar')

    class MyModel(BaseModel):
        foo: Foobar

    assert MyModel.model_json_schema() == {
        'properties': {'foo': {'title': 'Foo'}},
        'required': ['foo'],
        'title': 'MyModel',
        'type': 'object',
    }
    assert MyModel(foo=None).foo is None
    assert MyModel(foo='x').foo == 'x'
    assert MyModel(foo=123).foo == 123


def test_type_var_constraint():
    Foobar = TypeVar('Foobar', int, str)

    class MyModel(BaseModel):
        foo: Foobar

    assert MyModel.model_json_schema() == {
        'title': 'MyModel',
        'type': 'object',
        'properties': {'foo': {'title': 'Foo', 'anyOf': [{'type': 'integer'}, {'type': 'string'}]}},
        'required': ['foo'],
    }
    with pytest.raises(ValidationError) as exc_info:
        MyModel(foo=None)
    assert exc_info.value.errors() == [
        {'input': None, 'loc': ('foo', 'int'), 'msg': 'Input should be a valid integer', 'type': 'int_type'},
        {'input': None, 'loc': ('foo', 'str'), 'msg': 'Input should be a valid string', 'type': 'string_type'},
    ]

    with pytest.raises(ValidationError):
        MyModel(foo=[1, 2, 3])
    assert exc_info.value.errors() == [
        {'input': None, 'loc': ('foo', 'int'), 'msg': 'Input should be a valid integer', 'type': 'int_type'},
        {'input': None, 'loc': ('foo', 'str'), 'msg': 'Input should be a valid string', 'type': 'string_type'},
    ]

    assert MyModel(foo='x').foo == 'x'
    assert MyModel(foo=123).foo == 123


def test_type_var_bound():
    Foobar = TypeVar('Foobar', bound=int)

    class MyModel(BaseModel):
        foo: Foobar

    assert MyModel.model_json_schema() == {
        'title': 'MyModel',
        'type': 'object',
        'properties': {'foo': {'title': 'Foo', 'type': 'integer'}},
        'required': ['foo'],
    }
    with pytest.raises(ValidationError) as exc_info:
        MyModel(foo=None)
    assert exc_info.value.errors() == [
        {'input': None, 'loc': ('foo',), 'msg': 'Input should be a valid integer', 'type': 'int_type'}
    ]

    with pytest.raises(ValidationError):
        MyModel(foo='x')
    assert exc_info.value.errors() == [
        {'input': None, 'loc': ('foo',), 'msg': 'Input should be a valid integer', 'type': 'int_type'}
    ]
    assert MyModel(foo=123).foo == 123


def test_dict_bare():
    class MyModel(BaseModel):
        foo: Dict

    m = MyModel(foo={'x': 'a', 'y': None})
    assert m.foo == {'x': 'a', 'y': None}


def test_list_bare():
    class MyModel(BaseModel):
        foo: List

    m = MyModel(foo=[1, 2, None])
    assert m.foo == [1, 2, None]


def test_dict_any():
    class MyModel(BaseModel):
        foo: Dict[str, Any]

    m = MyModel(foo={'x': 'a', 'y': None})
    assert m.foo == {'x': 'a', 'y': None}


def test_modify_fields():
    class Foo(BaseModel):
        foo: List[List[int]]

        @field_validator('foo')
        @classmethod
        def check_something(cls, value):
            return value

    class Bar(Foo):
        pass

    assert repr(Foo.model_fields['foo']) == 'FieldInfo(annotation=List[List[int]], required=True)'
    assert repr(Bar.model_fields['foo']) == 'FieldInfo(annotation=List[List[int]], required=True)'
    assert Foo(foo=[[0, 1]]).foo == [[0, 1]]
    assert Bar(foo=[[0, 1]]).foo == [[0, 1]]


def test_exclude_none():
    class MyModel(BaseModel):
        a: Optional[int] = None
        b: int = 2

    m = MyModel(a=5)
    assert m.model_dump(exclude_none=True) == {'a': 5, 'b': 2}

    m = MyModel(b=3)
    assert m.model_dump(exclude_none=True) == {'b': 3}
    assert m.model_dump_json(exclude_none=True) == '{"b":3}'


def test_exclude_none_recursive():
    class ModelA(BaseModel):
        a: Optional[int] = None
        b: int = 1

    class ModelB(BaseModel):
        c: int
        d: int = 2
        e: ModelA
        f: Optional[str] = None

    m = ModelB(c=5, e={'a': 0})
    assert m.model_dump() == {'c': 5, 'd': 2, 'e': {'a': 0, 'b': 1}, 'f': None}
    assert m.model_dump(exclude_none=True) == {'c': 5, 'd': 2, 'e': {'a': 0, 'b': 1}}
    assert dict(m) == {'c': 5, 'd': 2, 'e': ModelA(a=0), 'f': None}

    m = ModelB(c=5, e={'b': 20}, f='test')
    assert m.model_dump() == {'c': 5, 'd': 2, 'e': {'a': None, 'b': 20}, 'f': 'test'}
    assert m.model_dump(exclude_none=True) == {'c': 5, 'd': 2, 'e': {'b': 20}, 'f': 'test'}
    assert dict(m) == {'c': 5, 'd': 2, 'e': ModelA(b=20), 'f': 'test'}


def test_exclude_none_with_extra():
    class MyModel(BaseModel):
        model_config = ConfigDict(extra='allow')
        a: str = 'default'
        b: Optional[str] = None

    m = MyModel(a='a', c='c')

    assert m.model_dump(exclude_none=True) == {'a': 'a', 'c': 'c'}
    assert m.model_dump() == {'a': 'a', 'b': None, 'c': 'c'}

    m = MyModel(a='a', b='b', c=None)

    assert m.model_dump(exclude_none=True) == {'a': 'a', 'b': 'b'}
    assert m.model_dump() == {'a': 'a', 'b': 'b', 'c': None}


def test_str_method_inheritance():
    import pydantic

    class Foo(pydantic.BaseModel):
        x: int = 3
        y: int = 4

        def __str__(self):
            return str(self.y + self.x)

    class Bar(Foo):
        z: bool = False

    assert str(Foo()) == '7'
    assert str(Bar()) == '7'


def test_repr_method_inheritance():
    import pydantic

    class Foo(pydantic.BaseModel):
        x: int = 3
        y: int = 4

        def __repr__(self):
            return repr(self.y + self.x)

    class Bar(Foo):
        z: bool = False

    assert repr(Foo()) == '7'
    assert repr(Bar()) == '7'


def test_optional_validator():
    val_calls = []

    class Model(BaseModel):
        something: Optional[str]

        @field_validator('something')
        @classmethod
        def check_something(cls, v):
            val_calls.append(v)
            return v

    with pytest.raises(ValidationError) as exc_info:
        assert Model().model_dump() == {'something': None}
    assert exc_info.value.errors() == [{'input': {}, 'loc': ('something',), 'msg': 'Field required', 'type': 'missing'}]

    assert Model(something=None).model_dump() == {'something': None}
    assert Model(something='hello').model_dump() == {'something': 'hello'}
    assert val_calls == [None, 'hello']


def test_required_optional():
    class Model(BaseModel):
        nullable1: Optional[int] = ...
        nullable2: Optional[int] = Field(...)

    with pytest.raises(ValidationError) as exc_info:
        Model()
    assert exc_info.value.errors() == [
        {'input': {}, 'loc': ('nullable1',), 'msg': 'Field required', 'type': 'missing'},
        {'input': {}, 'loc': ('nullable2',), 'msg': 'Field required', 'type': 'missing'},
    ]
    with pytest.raises(ValidationError) as exc_info:
        Model(nullable1=1)
    assert exc_info.value.errors() == [
        {'input': {'nullable1': 1}, 'loc': ('nullable2',), 'msg': 'Field required', 'type': 'missing'}
    ]
    with pytest.raises(ValidationError) as exc_info:
        Model(nullable2=2)
    assert exc_info.value.errors() == [
        {'input': {'nullable2': 2}, 'loc': ('nullable1',), 'msg': 'Field required', 'type': 'missing'}
    ]
    assert Model(nullable1=None, nullable2=None).model_dump() == {'nullable1': None, 'nullable2': None}
    assert Model(nullable1=1, nullable2=2).model_dump() == {'nullable1': 1, 'nullable2': 2}
    with pytest.raises(ValidationError) as exc_info:
        Model(nullable1='some text')
    assert exc_info.value.errors() == [
        {
            'input': 'some text',
            'loc': ('nullable1',),
            'msg': 'Input should be a valid integer, unable to parse string as an ' 'integer',
            'type': 'int_parsing',
        },
        {'input': {'nullable1': 'some text'}, 'loc': ('nullable2',), 'msg': 'Field required', 'type': 'missing'},
    ]


def test_required_any():
    class Model(BaseModel):
        optional1: Any
        optional2: Any = None
        optional3: Optional[Any] = None
        nullable1: Any = ...
        nullable2: Any = Field(...)
        nullable3: Optional[Any]

    with pytest.raises(ValidationError) as exc_info:
        Model()
    assert exc_info.value.errors() == [
        {'input': {}, 'loc': ('optional1',), 'msg': 'Field required', 'type': 'missing'},
        {'input': {}, 'loc': ('nullable1',), 'msg': 'Field required', 'type': 'missing'},
        {'input': {}, 'loc': ('nullable2',), 'msg': 'Field required', 'type': 'missing'},
        {'input': {}, 'loc': ('nullable3',), 'msg': 'Field required', 'type': 'missing'},
    ]
    with pytest.raises(ValidationError) as exc_info:
        Model(nullable1='a')
    assert exc_info.value.errors() == [
        {'input': {'nullable1': 'a'}, 'loc': ('optional1',), 'msg': 'Field required', 'type': 'missing'},
        {'input': {'nullable1': 'a'}, 'loc': ('nullable2',), 'msg': 'Field required', 'type': 'missing'},
        {'input': {'nullable1': 'a'}, 'loc': ('nullable3',), 'msg': 'Field required', 'type': 'missing'},
    ]
    with pytest.raises(ValidationError) as exc_info:
        Model(nullable2=False)
    assert exc_info.value.errors() == [
        {'input': {'nullable2': False}, 'loc': ('optional1',), 'msg': 'Field required', 'type': 'missing'},
        {'input': {'nullable2': False}, 'loc': ('nullable1',), 'msg': 'Field required', 'type': 'missing'},
        {'input': {'nullable2': False}, 'loc': ('nullable3',), 'msg': 'Field required', 'type': 'missing'},
    ]
    with pytest.raises(ValidationError) as exc_info:
        assert Model(nullable1=None, nullable2=None).model_dump() == {
            'optional1': None,
            'optional2': None,
            'nullable1': None,
            'nullable2': None,
        }
    assert exc_info.value.errors() == [
        {
            'input': {'nullable1': None, 'nullable2': None},
            'loc': ('optional1',),
            'msg': 'Field required',
            'type': 'missing',
        },
        {
            'input': {'nullable1': None, 'nullable2': None},
            'loc': ('nullable3',),
            'msg': 'Field required',
            'type': 'missing',
        },
    ]
    assert Model(optional1=None, nullable1=1, nullable2='two', nullable3=None).model_dump() == {
        'optional1': None,
        'optional2': None,
        'optional3': None,
        'nullable1': 1,
        'nullable2': 'two',
        'nullable3': None,
    }
    assert Model(optional1='op1', optional2=False, nullable1=1, nullable2='two', nullable3='three').model_dump() == {
        'optional1': 'op1',
        'optional2': False,
        'optional3': None,
        'nullable1': 1,
        'nullable2': 'two',
        'nullable3': 'three',
    }


@pytest.mark.xfail(reason='need to modify loc of ValidationError')
def test_custom_generic_validators():
    T1 = TypeVar('T1')
    T2 = TypeVar('T2')

    class MyGen(Generic[T1, T2]):
        def __init__(self, t1: T1, t2: T2):
            self.t1 = t1
            self.t2 = t2

        @classmethod
        def __get_pydantic_core_schema__(cls, source: Any, handler: Callable[[Any], core_schema.CoreSchema]):
            schema = core_schema.is_instance_schema(cls)

            args = get_args(source)
            if not args:
                return schema

            t1_f = AnalyzedType(args[0]).validate_python
            t2_f = AnalyzedType(args[1]).validate_python

            def validate(v, info):
                if not args:
                    return v
                # TODO: Collect these errors, rather than stopping early, and modify the loc to make the test pass
                t1_f(v.t1)
                t2_f(v.t2)
                return v

            return core_schema.general_after_validator_function(validate, schema)

    class Model(BaseModel):
        a: str
        gen: MyGen[str, bool]
        gen2: MyGen

        model_config = dict(arbitrary_types_allowed=True)

    with pytest.raises(ValidationError) as exc_info:
        Model(a='foo', gen='invalid', gen2='invalid')
    assert exc_info.value.errors() == [
        {
            'ctx': {'class': 'test_custom_generic_validators.<locals>.MyGen'},
            'input': 'invalid',
            'loc': ('gen',),
            'msg': 'Input should be an instance of test_custom_generic_validators.<locals>.MyGen',
            'type': 'is_instance_of',
        },
        {
            'ctx': {'class': 'test_custom_generic_validators.<locals>.MyGen'},
            'input': 'invalid',
            'loc': ('gen2',),
            'msg': 'Input should be an instance of test_custom_generic_validators.<locals>.MyGen',
            'type': 'is_instance_of',
        },
    ]

    with pytest.raises(ValidationError) as exc_info:
        Model(a='foo', gen=MyGen(t1='bar', t2='baz'), gen2=MyGen(t1='bar', t2='baz'))
    assert exc_info.value.errors() == [
        {
            'input': 'baz',
            'loc': ('gen', 't2'),
            'msg': 'Input should be a valid boolean, unable to interpret input',
            'type': 'bool_parsing',
        }
    ]

    m = Model(a='foo', gen=MyGen(t1='bar', t2=True), gen2=MyGen(t1=1, t2=2))
    assert m.a == 'foo'
    assert m.gen.t1 == 'bar'
    assert m.gen.t2 is True
    assert m.gen2.t1 == 1
    assert m.gen2.t2 == 2


def test_custom_generic_arbitrary_allowed():
    T1 = TypeVar('T1')
    T2 = TypeVar('T2')

    class MyGen(Generic[T1, T2]):
        def __init__(self, t1: T1, t2: T2):
            self.t1 = t1
            self.t2 = t2

    class Model(BaseModel):
        a: str
        gen: MyGen[str, bool]

        model_config = dict(arbitrary_types_allowed=True)

    with pytest.raises(ValidationError) as exc_info:
        Model(a='foo', gen='invalid')
    assert exc_info.value.errors() == [
        {
            'ctx': {'class': 'test_custom_generic_arbitrary_allowed.<locals>.MyGen'},
            'input': 'invalid',
            'loc': ('gen',),
            'msg': 'Input should be an instance of ' 'test_custom_generic_arbitrary_allowed.<locals>.MyGen',
            'type': 'is_instance_of',
        }
    ]

    # No validation, no exception
    m = Model(a='foo', gen=MyGen(t1='bar', t2='baz'))
    assert m.a == 'foo'
    assert m.gen.t1 == 'bar'
    assert m.gen.t2 == 'baz'

    m = Model(a='foo', gen=MyGen(t1='bar', t2=True))
    assert m.a == 'foo'
    assert m.gen.t1 == 'bar'
    assert m.gen.t2 is True


def test_custom_generic_disallowed():
    T1 = TypeVar('T1')
    T2 = TypeVar('T2')

    class MyGen(Generic[T1, T2]):
        def __init__(self, t1: T1, t2: T2):
            self.t1 = t1
            self.t2 = t2

    match = (
        r'Unable to generate pydantic-core schema for (.*)MyGen\[str, bool\](.*). '
        r'Setting `arbitrary_types_allowed=True` in the model_config may prevent this error.'
    )
    with pytest.raises(TypeError, match=match):

        class Model(BaseModel):
            a: str
            gen: MyGen[str, bool]


def test_hashable_required():
    class Model(BaseModel):
        v: Hashable

    Model(v=None)
    with pytest.raises(ValidationError) as exc_info:
        Model(v=[])
    assert exc_info.value.errors() == [
        {'input': [], 'loc': ('v',), 'msg': 'Input should be hashable', 'type': 'is_hashable'}
    ]
    with pytest.raises(ValidationError) as exc_info:
        Model()
    assert exc_info.value.errors() == [{'input': {}, 'loc': ('v',), 'msg': 'Field required', 'type': 'missing'}]


@pytest.mark.parametrize('default', [1, None])
def test_hashable_optional(default):
    class Model(BaseModel):
        v: Hashable = default

    Model(v=None)
    Model()


def test_hashable_serialization():
    class Model(BaseModel):
        v: Hashable

    class HashableButNotSerializable:
        def __hash__(self):
            return 0

    assert Model(v=(1,)).model_dump_json() == '{"v":[1]}'
    m = Model(v=HashableButNotSerializable())
    with pytest.raises(
        PydanticSerializationError, match='Unable to serialize unknown type:.*HashableButNotSerializable'
    ):
        m.model_dump_json()


def test_hashable_json_schema():
    class Model(BaseModel):
        v: Hashable

    with pytest.raises(
        PydanticInvalidForJsonSchema,
        match=re.escape(
            "Cannot generate a JsonSchema for core_schema.IsInstanceSchema (<class 'collections.abc.Hashable'>)"
        ),
    ):
        Model.model_json_schema()


def test_default_factory_called_once():
    """It should never call `default_factory` more than once even when `validate_all` is set"""

    v = 0

    def factory() -> int:
        nonlocal v
        v += 1
        return v

    class MyModel(BaseModel):
        model_config = ConfigDict(validate_default=True)
        id: int = Field(default_factory=factory)

    m1 = MyModel()
    assert m1.id == 1

    class MyBadModel(BaseModel):
        model_config = ConfigDict(validate_default=True)
        id: List[str] = Field(default_factory=factory)

    with pytest.raises(ValidationError) as exc_info:
        MyBadModel()
    assert v == 2  # `factory` has been called to run validation
    assert exc_info.value.errors() == [
        {'input': 2, 'loc': ('id',), 'msg': 'Input should be a valid list', 'type': 'list_type'}
    ]


def test_default_factory_validator_child():
    class Parent(BaseModel):
        foo: List[str] = Field(default_factory=list)

        @field_validator('foo', mode='before')
        @classmethod
        def mutate_foo(cls, v):
            return [f'{x}-1' for x in v]

    assert Parent(foo=['a', 'b']).foo == ['a-1', 'b-1']

    class Child(Parent):
        pass

    assert Child(foo=['a', 'b']).foo == ['a-1', 'b-1']


def test_resolve_annotations_module_missing(tmp_path):
    # see https://github.com/pydantic/pydantic/issues/2363
    file_path = tmp_path / 'module_to_load.py'
    # language=Python
    file_path.write_text(
        """
from pydantic import BaseModel
class User(BaseModel):
    id: int
    name: str = 'Jane Doe'
"""
    )

    spec = importlib.util.spec_from_file_location('my_test_module', file_path)
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    assert module.User(id=12).model_dump() == {'id': 12, 'name': 'Jane Doe'}


def test_iter_coverage():
    class MyModel(BaseModel):
        x: int = 1
        y: str = 'a'

    with pytest.warns(
        DeprecationWarning, match='The private method `_iter` will be removed and should no longer be used.'
    ):
        assert list(MyModel()._iter(by_alias=True)) == [('x', 1), ('y', 'a')]


def test_frozen_config_and_field():
    class Foo(BaseModel):
        model_config = ConfigDict(frozen=False, validate_assignment=True)
        a: str = Field(...)

    assert Foo.model_fields['a'].metadata == []

    f = Foo(a='x')
    f.a = 'y'
    assert f.model_dump() == {'a': 'y'}

    class Bar(BaseModel):
        model_config = ConfigDict(validate_assignment=True)
        a: str = Field(..., frozen=True)
        c: Annotated[str, Field(frozen=True)]

    assert Bar.model_fields['a'].frozen

    b = Bar(a='x', c='z')
    with pytest.raises(ValidationError) as exc_info:
        b.a = 'y'
    assert exc_info.value.errors() == [{'input': 'y', 'loc': ('a',), 'msg': 'Field is frozen', 'type': 'frozen_field'}]

    with pytest.raises(ValidationError) as exc_info:
        b.c = 'y'
    assert exc_info.value.errors() == [{'input': 'y', 'loc': ('c',), 'msg': 'Field is frozen', 'type': 'frozen_field'}]

    assert b.model_dump() == {'a': 'x', 'c': 'z'}


def test_arbitrary_types_allowed_custom_eq():
    class Foo:
        def __eq__(self, other):
            if other.__class__ is not Foo:
                raise TypeError(f'Cannot interpret {other.__class__.__name__!r} as a valid type')
            return True

    class Model(BaseModel):
        model_config = ConfigDict(arbitrary_types_allowed=True)
        x: Foo = Foo()

    assert Model().x == Foo()


def test_bytes_subclass():
    class MyModel(BaseModel):
        my_bytes: bytes

    class BytesSubclass(bytes):
        def __new__(cls, data: bytes):
            self = bytes.__new__(cls, data)
            return self

    m = MyModel(my_bytes=BytesSubclass(b'foobar'))
    assert m.my_bytes.__class__ == BytesSubclass


@pytest.mark.xfail(reason='subclass not preserved for field of type int')
def test_int_subclass():
    class MyModel(BaseModel):
        my_int: int

    class IntSubclass(int):
        def __new__(cls, data: int):
            self = int.__new__(cls, data)
            return self

    m = MyModel(my_int=IntSubclass(123))
    # TODO: Is this still the behavior we want in v2? (Currently m.my_int.__class__ is int)
    #   "yes, because in pydantic-core we cast the value to a rust i64, so the sub-type information is lost."
    #   (more detail about how to handle this in: https://github.com/pydantic/pydantic/pull/5151#discussion_r1130691036)
    assert m.my_int.__class__ == IntSubclass


def test_model_issubclass():
    assert not issubclass(int, BaseModel)

    class MyModel(BaseModel):
        x: int

    assert issubclass(MyModel, BaseModel)

    class Custom:
        __fields__ = True

    assert not issubclass(Custom, BaseModel)


@pytest.mark.xfail(reason='"long int", see details below')
def test_long_int():
    """
    see https://github.com/pydantic/pydantic/issues/1477 and in turn, https://github.com/python/cpython/issues/95778
    """

    class Model(BaseModel):
        x: int

    # TODO: The next line now raises the following error:
    #     E       pydantic_core._pydantic_core.ValidationError: 1 validation error for Model
    #     E       x
    #     E         Input should be a finite number [type=finite_number,
    #     input_value='111111111111111111111111...11111111111111111111111', input_type=str]
    #   Do we need to resolve this? How hard would that be in pydantic_core? Is it worth it?
    #   -
    #   "in pydantic-core we use an i64, which constrains the max and min values. Since that's massively more
    #   performant, and there are very few real world uses for int > i64:MAX, the error is correct."
    #   https://github.com/pydantic/pydantic/pull/5151#discussion_r1130693762
    #   -
    #   I think before modifying this test and removing the xfail, we should create a new test
    #   that handles the following line without failure using the is-instance approach described in the comment
    #   linked above.
    assert Model(x='1' * 4_300).x == int('1' * 4_300)
    assert Model(x=b'1' * 4_300).x == int('1' * 4_300)
    assert Model(x=bytearray(b'1' * 4_300)).x == int('1' * 4_300)

    too_long = '1' * 4_301
    with pytest.raises(ValidationError) as exc_info:
        Model(x=too_long)

    assert exc_info.value.errors() == [
        {
            'loc': ('x',),
            'msg': 'value is not a valid integer',
            'type': 'type_error.integer',
        },
    ]

    too_long_b = too_long.encode('utf-8')
    with pytest.raises(ValidationError):
        Model(x=too_long_b)
    with pytest.raises(ValidationError):
        Model(x=bytearray(too_long_b))

    # this used to hang indefinitely
    with pytest.raises(ValidationError):
        Model(x='1' * (10**7))


def test_parent_field_with_default():
    class Parent(BaseModel):
        a: int = 1
        b: int = Field(2)

    class Child(Parent):
        c: int = 3

    c = Child()
    assert c.a == 1
    assert c.b == 2
    assert c.c == 3


@pytest.mark.parametrize(
    'bases',
    [
        (BaseModel, ABC),
        (ABC, BaseModel),
        (BaseModel,),
    ],
)
def test_abstractmethod_missing_for_all_decorators(bases):
    class AbstractSquare(*bases):
        side: float

        @field_validator('side')
        @classmethod
        @abstractmethod
        def my_field_validator(cls, v):
            raise NotImplementedError

        @model_validator(mode='wrap')
        @classmethod
        @abstractmethod
        def my_model_validator(cls, values, handler, info):
            raise NotImplementedError

        @root_validator(skip_on_failure=True)
        @classmethod
        @abstractmethod
        def my_root_validator(cls, values):
            raise NotImplementedError

        with pytest.warns(DeprecationWarning):

            @validator('side')
            @classmethod
            @abstractmethod
            def my_validator(cls, value, **kwargs):
                raise NotImplementedError

        @model_serializer(mode='wrap')
        @abstractmethod
        def my_model_serializer(self, handler, info):
            raise NotImplementedError

        @field_serializer('side')
        @abstractmethod
        def my_serializer(self, v, _info):
            raise NotImplementedError

        @computed_field
        @property
        @abstractmethod
        def my_computed_field(self):
            raise NotImplementedError

    class Square(AbstractSquare):
        pass

    with pytest.raises(
        TypeError,
        match=(
            "Can't instantiate abstract class Square with abstract methods"
            " my_computed_field,"
            " my_field_validator,"
            " my_model_serializer,"
            " my_model_validator,"
            " my_root_validator,"
            " my_serializer,"
            " my_validator"
        ),
    ):
        Square(side=1.0)
