import sys
from enum import Enum
from typing import Any, Callable, ClassVar, Dict, List, Mapping, Optional, Type, get_type_hints
from uuid import UUID, uuid4

import pytest

from pydantic import (
    BaseModel,
    ConfigError,
    Extra,
    Field,
    NoneBytes,
    NoneStr,
    Required,
    ValidationError,
    constr,
    root_validator,
    validator,
)
from pydantic.typing import Literal


def test_success():
    # same as below but defined here so class definition occurs inside the test
    class Model(BaseModel):
        a: float
        b: int = 10

    m = Model(a=10.2)
    assert m.a == 10.2
    assert m.b == 10


class UltraSimpleModel(BaseModel):
    a: float
    b: int = 10


def test_ultra_simple_missing():
    with pytest.raises(ValidationError) as exc_info:
        UltraSimpleModel()
    assert exc_info.value.errors() == [{'loc': ('a',), 'msg': 'field required', 'type': 'value_error.missing'}]


def test_ultra_simple_failed():
    with pytest.raises(ValidationError) as exc_info:
        UltraSimpleModel(a='x', b='x')
    assert exc_info.value.errors() == [
        {'loc': ('a',), 'msg': 'value is not a valid float', 'type': 'type_error.float'},
        {'loc': ('b',), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'},
    ]


def test_ultra_simple_repr():
    m = UltraSimpleModel(a=10.2)
    assert str(m) == 'a=10.2 b=10'
    assert repr(m) == 'UltraSimpleModel(a=10.2, b=10)'
    assert repr(m.__fields__['a']) == "ModelField(name='a', type=float, required=True)"
    assert repr(m.__fields__['b']) == "ModelField(name='b', type=int, required=False, default=10)"
    assert dict(m) == {'a': 10.2, 'b': 10}
    assert m.dict() == {'a': 10.2, 'b': 10}
    assert m.json() == '{"a": 10.2, "b": 10}'
    with pytest.raises(DeprecationWarning, match=r'`model.to_string\(\)` method is deprecated'):
        assert m.to_string() == 'a=10.2 b=10'


def test_default_factory_field():
    def myfunc():
        return 1

    class Model(BaseModel):
        a: int = Field(default_factory=myfunc)

    m = Model()
    assert str(m) == 'a=1'
    assert (
        repr(m.__fields__['a']) == "ModelField(name='a', type=int, required=False, default_factory='<function myfunc>')"
    )
    assert dict(m) == {'a': 1}
    assert m.json() == '{"a": 1}'


def test_default_factory_no_type_field():
    def myfunc():
        return 1

    with pytest.raises(ConfigError) as e:

        class Model(BaseModel):
            a = Field(default_factory=myfunc)

    assert str(e.value) == "you need to set the type of field 'a' when using `default_factory`"


def test_comparing():
    m = UltraSimpleModel(a=10.2, b='100')
    assert m == {'a': 10.2, 'b': 100}
    assert m == UltraSimpleModel(a=10.2, b=100)


def test_nullable_strings_success():
    class NoneCheckModel(BaseModel):
        existing_str_value = 'foo'
        required_str_value: str = ...
        required_str_none_value: NoneStr = ...
        existing_bytes_value = b'foo'
        required_bytes_value: bytes = ...
        required_bytes_none_value: NoneBytes = ...

    m = NoneCheckModel(
        required_str_value='v1', required_str_none_value=None, required_bytes_value='v2', required_bytes_none_value=None
    )
    assert m.required_str_value == 'v1'
    assert m.required_str_none_value is None
    assert m.required_bytes_value == b'v2'
    assert m.required_bytes_none_value is None


def test_nullable_strings_fails():
    class NoneCheckModel(BaseModel):
        existing_str_value = 'foo'
        required_str_value: str = ...
        required_str_none_value: NoneStr = ...
        existing_bytes_value = b'foo'
        required_bytes_value: bytes = ...
        required_bytes_none_value: NoneBytes = ...

    with pytest.raises(ValidationError) as exc_info:
        NoneCheckModel(
            required_str_value=None,
            required_str_none_value=None,
            required_bytes_value=None,
            required_bytes_none_value=None,
        )
    assert exc_info.value.errors() == [
        {'loc': ('required_str_value',), 'msg': 'none is not an allowed value', 'type': 'type_error.none.not_allowed'},
        {
            'loc': ('required_bytes_value',),
            'msg': 'none is not an allowed value',
            'type': 'type_error.none.not_allowed',
        },
    ]


class RecursiveModel(BaseModel):
    grape: bool = ...
    banana: UltraSimpleModel = ...


def test_recursion():
    m = RecursiveModel(grape=1, banana={'a': 1})
    assert m.grape is True
    assert m.banana.a == 1.0
    assert m.banana.b == 10
    assert repr(m) == 'RecursiveModel(grape=True, banana=UltraSimpleModel(a=1.0, b=10))'


def test_recursion_fails():
    with pytest.raises(ValidationError):
        RecursiveModel(grape=1, banana=123)


def test_not_required():
    class Model(BaseModel):
        a: float = None

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


def test_infer_type():
    class Model(BaseModel):
        a = False
        b = ''
        c = 0

    assert Model().a is False
    assert Model().b == ''
    assert Model().c == 0


def test_allow_extra():
    class Model(BaseModel):
        a: float = ...

        class Config:
            extra = Extra.allow

    assert Model(a='10.2', b=12).dict() == {'a': 10.2, 'b': 12}


def test_forbidden_extra_success():
    class ForbiddenExtra(BaseModel):
        foo = 'whatever'

        class Config:
            extra = Extra.forbid

    m = ForbiddenExtra()
    assert m.foo == 'whatever'

    m = ForbiddenExtra(foo=1)
    assert m.foo == '1'


def test_forbidden_extra_fails():
    class ForbiddenExtra(BaseModel):
        foo = 'whatever'

        class Config:
            extra = Extra.forbid

    with pytest.raises(ValidationError) as exc_info:
        ForbiddenExtra(foo='ok', bar='wrong', spam='xx')
    assert exc_info.value.errors() == [
        {'loc': ('bar',), 'msg': 'extra fields not permitted', 'type': 'value_error.extra'},
        {'loc': ('spam',), 'msg': 'extra fields not permitted', 'type': 'value_error.extra'},
    ]


def test_disallow_mutation():
    class Model(BaseModel):
        a: float

    model = Model(a=0.2)
    with pytest.raises(ValueError, match='"Model" object has no field "b"'):
        model.b = 2


def test_extra_allowed():
    class Model(BaseModel):
        a: float

        class Config:
            extra = Extra.allow

    model = Model(a=0.2, b=0.1)
    assert model.b == 0.1

    assert not hasattr(model, 'c')
    model.c = 1
    assert hasattr(model, 'c')
    assert model.c == 1


def test_extra_ignored():
    class Model(BaseModel):
        a: float

        class Config:
            extra = Extra.ignore

    model = Model(a=0.2, b=0.1)
    assert not hasattr(model, 'b')

    with pytest.raises(ValueError, match='"Model" object has no field "c"'):
        model.c = 1


def test_set_attr():
    m = UltraSimpleModel(a=10.2)
    assert m.dict() == {'a': 10.2, 'b': 10}

    m.b = 20
    assert m.dict() == {'a': 10.2, 'b': 20}


def test_set_attr_invalid():
    class UltraSimpleModel(BaseModel):
        a: float = ...
        b: int = 10

    m = UltraSimpleModel(a=10.2)
    assert m.dict() == {'a': 10.2, 'b': 10}

    with pytest.raises(ValueError) as exc_info:
        m.c = 20
    assert '"UltraSimpleModel" object has no field "c"' in exc_info.value.args[0]


def test_any():
    class AnyModel(BaseModel):
        a: Any = 10

    assert AnyModel().a == 10
    assert AnyModel(a='foobar').a == 'foobar'


def test_alias():
    class SubModel(BaseModel):
        c = 'barfoo'

        class Config:
            fields = {'c': {'alias': '_c'}}

    class Model(BaseModel):
        a = 'foobar'
        b: SubModel = SubModel()

        class Config:
            fields = {'a': {'alias': '_a'}}

    assert Model().a == 'foobar'
    assert Model().b.c == 'barfoo'
    assert Model().dict() == {'a': 'foobar', 'b': {'c': 'barfoo'}}
    assert Model(_a='different').a == 'different'
    assert Model(b={'_c': 'different'}).b.c == 'different'
    assert Model(_a='different', b={'_c': 'different'}).dict() == {'a': 'different', 'b': {'c': 'different'}}
    assert Model(_a='different', b={'_c': 'different'}).dict(by_alias=True) == {
        '_a': 'different',
        'b': {'_c': 'different'},
    }


def test_population_by_field_name():
    class Model(BaseModel):
        a: str

        class Config:
            allow_population_by_field_name = True
            fields = {'a': {'alias': '_a'}}

    assert Model(a='different').a == 'different'
    assert Model(a='different').dict() == {'a': 'different'}
    assert Model(a='different').dict(by_alias=True) == {'_a': 'different'}


def test_field_order():
    class Model(BaseModel):
        c: float
        b: int = 10
        a: str
        d: dict = {}

    assert list(Model.__fields__.keys()) == ['c', 'b', 'a', 'd']


def test_required():
    # same as below but defined here so class definition occurs inside the test
    class Model(BaseModel):
        a: float = Required
        b: int = 10

    m = Model(a=10.2)
    assert m.dict() == dict(a=10.2, b=10)

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


def test_not_immutability():
    class TestModel(BaseModel):
        a: int = 10

        class Config:
            allow_mutation = True
            extra = Extra.forbid

    m = TestModel()
    assert m.a == 10
    m.a = 11
    assert m.a == 11
    with pytest.raises(ValueError) as exc_info:
        m.b = 11
    assert '"TestModel" object has no field "b"' in exc_info.value.args[0]


def test_immutability():
    class TestModel(BaseModel):
        a: int = 10

        class Config:
            allow_mutation = False
            extra = Extra.forbid

    m = TestModel()
    assert m.a == 10
    with pytest.raises(TypeError) as exc_info:
        m.a = 11
    assert '"TestModel" is immutable and does not support item assignment' in exc_info.value.args[0]
    with pytest.raises(ValueError) as exc_info:
        m.b = 11
    assert '"TestModel" object has no field "b"' in exc_info.value.args[0]


def test_const_validates():
    class Model(BaseModel):
        a: int = Field(3, const=True)

    m = Model(a=3)
    assert m.a == 3


def test_const_uses_default():
    class Model(BaseModel):
        a: int = Field(3, const=True)

    m = Model()
    assert m.a == 3


def test_const_validates_after_type_validators():
    # issue #1410
    class Model(BaseModel):
        a: int = Field(3, const=True)

    m = Model(a='3')
    assert m.a == 3


def test_const_with_wrong_value():
    class Model(BaseModel):
        a: int = Field(3, const=True)

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

    assert exc_info.value.errors() == [
        {
            'loc': ('a',),
            'msg': 'unexpected value; permitted: 3',
            'type': 'value_error.const',
            'ctx': {'given': 4, 'permitted': [3]},
        }
    ]


def test_const_with_validator():
    class Model(BaseModel):
        a: int = Field(3, const=True)

        @validator('a')
        def validate(v):
            return v

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

    assert exc_info.value.errors() == [
        {
            'loc': ('a',),
            'msg': 'unexpected value; permitted: 3',
            'type': 'value_error.const',
            'ctx': {'given': 4, 'permitted': [3]},
        }
    ]


def test_const_list():
    class SubModel(BaseModel):
        b: int

    class Model(BaseModel):
        a: List[SubModel] = Field([SubModel(b=1), SubModel(b=2), SubModel(b=3)], const=True)
        b: List[SubModel] = Field([{'b': 4}, {'b': 5}, {'b': 6}], const=True)

    m = Model()
    assert m.a == [SubModel(b=1), SubModel(b=2), SubModel(b=3)]
    assert m.b == [SubModel(b=4), SubModel(b=5), SubModel(b=6)]
    assert m.schema() == {
        'definitions': {
            'SubModel': {
                'properties': {'b': {'title': 'B', 'type': 'integer'}},
                'required': ['b'],
                'title': 'SubModel',
                'type': 'object',
            }
        },
        'properties': {
            'a': {
                'const': [SubModel(b=1), SubModel(b=2), SubModel(b=3)],
                'items': {'$ref': '#/definitions/SubModel'},
                'title': 'A',
                'type': 'array',
            },
            'b': {
                'const': [{'b': 4}, {'b': 5}, {'b': 6}],
                'items': {'$ref': '#/definitions/SubModel'},
                'title': 'B',
                'type': 'array',
            },
        },
        'title': 'Model',
        'type': 'object',
    }


def test_const_list_with_wrong_value():
    class SubModel(BaseModel):
        b: int

    class Model(BaseModel):
        a: List[SubModel] = Field([SubModel(b=1), SubModel(b=2), SubModel(b=3)], const=True)
        b: List[SubModel] = Field([{'b': 4}, {'b': 5}, {'b': 6}], const=True)

    with pytest.raises(ValidationError) as exc_info:
        Model(a=[{'b': 3}, {'b': 1}, {'b': 2}], b=[{'b': 6}, {'b': 5}])

    assert exc_info.value.errors() == [
        {
            'ctx': {
                'given': [{'b': 3}, {'b': 1}, {'b': 2}],
                'permitted': [[SubModel(b=1), SubModel(b=2), SubModel(b=3)]],
            },
            'loc': ('a',),
            'msg': 'unexpected value; permitted: [SubModel(b=1), SubModel(b=2), SubModel(b=3)]',
            'type': 'value_error.const',
        },
        {
            'ctx': {'given': [{'b': 6}, {'b': 5}], 'permitted': [[{'b': 4}, {'b': 5}, {'b': 6}]]},
            'loc': ('b',),
            'msg': "unexpected value; permitted: [{'b': 4}, {'b': 5}, {'b': 6}]",
            'type': 'value_error.const',
        },
    ]
    assert exc_info.value.json().startswith('[')

    with pytest.raises(ValidationError) as exc_info:
        Model(a=[SubModel(b=3), SubModel(b=1), SubModel(b=2)], b=[SubModel(b=3), SubModel(b=1)])

    assert exc_info.value.errors() == [
        {
            'ctx': {
                'given': [SubModel(b=3), SubModel(b=1), SubModel(b=2)],
                'permitted': [[SubModel(b=1), SubModel(b=2), SubModel(b=3)]],
            },
            'loc': ('a',),
            'msg': 'unexpected value; permitted: [SubModel(b=1), SubModel(b=2), SubModel(b=3)]',
            'type': 'value_error.const',
        },
        {
            'ctx': {'given': [SubModel(b=3), SubModel(b=1)], 'permitted': [[{'b': 4}, {'b': 5}, {'b': 6}]]},
            'loc': ('b',),
            'msg': "unexpected value; permitted: [{'b': 4}, {'b': 5}, {'b': 6}]",
            'type': 'value_error.const',
        },
    ]
    assert exc_info.value.json().startswith('[')


def test_const_validation_json_serializable():
    class SubForm(BaseModel):
        field: int

    class Form(BaseModel):
        field1: SubForm = Field({'field': 2}, const=True)
        field2: List[SubForm] = Field([{'field': 2}], const=True)

    with pytest.raises(ValidationError) as exc_info:
        # Fails
        Form(field1={'field': 1}, field2=[{'field': 1}])

    # This should not raise an Json error
    exc_info.value.json()


class ValidateAssignmentModel(BaseModel):
    a: int = 2
    b: constr(min_length=1)

    class Config:
        validate_assignment = True


def test_validating_assignment_pass():
    p = ValidateAssignmentModel(a=5, b='hello')
    p.a = 2
    assert p.a == 2
    assert p.dict() == {'a': 2, 'b': 'hello'}
    p.b = 'hi'
    assert p.b == 'hi'
    assert p.dict() == {'a': 2, 'b': 'hi'}


def test_validating_assignment_fail():
    p = ValidateAssignmentModel(a=5, b='hello')

    with pytest.raises(ValidationError) as exc_info:
        p.a = 'b'
    assert exc_info.value.errors() == [
        {'loc': ('a',), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'}
    ]

    with pytest.raises(ValidationError) as exc_info:
        p.b = ''
    assert exc_info.value.errors() == [
        {
            'loc': ('b',),
            'msg': 'ensure this value has at least 1 characters',
            'type': 'value_error.any_str.min_length',
            'ctx': {'limit_value': 1},
        }
    ]


def test_validating_assignment_pre_root_validator_fail():
    class Model(BaseModel):
        current_value: float = Field(..., alias='current')
        max_value: float

        class Config:
            validate_assignment = True

        @root_validator(pre=True)
        def values_are_not_string(cls, values):
            if any(isinstance(x, str) for x in values.values()):
                raise ValueError('values cannot be a string')
            return values

    m = Model(current=100, max_value=200)
    with pytest.raises(ValidationError) as exc_info:
        m.current_value = '100'
    assert exc_info.value.errors() == [
        {
            'loc': ('__root__',),
            'msg': 'values cannot be a string',
            'type': 'value_error',
        }
    ]


def test_validating_assignment_post_root_validator_fail():
    class Model(BaseModel):
        current_value: float = Field(..., alias='current')
        max_value: float

        class Config:
            validate_assignment = True

        @root_validator
        def current_lessequal_max(cls, values):
            current_value = values.get('current_value')
            max_value = values.get('max_value')
            if current_value > max_value:
                raise ValueError('current_value cannot be greater than max_value')
            return values

        @root_validator(skip_on_failure=True)
        def current_lessequal_300(cls, values):
            current_value = values.get('current_value')
            if current_value > 300:
                raise ValueError('current_value cannot be greater than 300')
            return values

        @root_validator
        def current_lessequal_500(cls, values):
            current_value = values.get('current_value')
            if current_value > 500:
                raise ValueError('current_value cannot be greater than 500')
            return values

    m = Model(current=100, max_value=200)
    m.current_value = '100'
    with pytest.raises(ValidationError) as exc_info:
        m.current_value = 1000
    assert exc_info.value.errors() == [
        {'loc': ('__root__',), 'msg': 'current_value cannot be greater than max_value', 'type': 'value_error'},
        {
            'loc': ('__root__',),
            'msg': 'current_value cannot be greater than 500',
            'type': 'value_error',
        },
    ]


def test_root_validator_many_values_change():
    """It should run root_validator on assignment and update ALL concerned fields"""

    class Rectangle(BaseModel):
        width: float
        height: float
        area: float = None

        class Config:
            validate_assignment = True

        @root_validator
        def set_area(cls, values):
            values['area'] = values['width'] * values['height']
            return values

    r = Rectangle(width=1, height=1)
    assert r.area == 1
    r.height = 5
    assert r.area == 5


def test_enum_values():
    FooEnum = Enum('FooEnum', {'foo': 'foo', 'bar': 'bar'})

    class Model(BaseModel):
        foo: FooEnum = None

        class Config:
            use_enum_values = True

    m = Model(foo='foo')
    # this is the actual value, so has not "values" field
    assert not isinstance(m.foo, FooEnum)
    assert m.foo == 'foo'


@pytest.mark.skipif(not Literal, reason='typing_extensions not installed')
def test_literal_enum_values():
    FooEnum = Enum('FooEnum', {'foo': 'foo_value', 'bar': 'bar_value'})

    class Model(BaseModel):
        baz: Literal[FooEnum.foo]
        boo: str = 'hoo'

        class Config:
            use_enum_values = True

    m = Model(baz=FooEnum.foo)
    assert m.dict() == {'baz': 'foo_value', 'boo': 'hoo'}
    assert m.baz.value == 'foo_value'

    with pytest.raises(ValidationError) as exc_info:
        Model(baz=FooEnum.bar)

    assert exc_info.value.errors() == [
        {
            'loc': ('baz',),
            'msg': "unexpected value; permitted: <FooEnum.foo: 'foo_value'>",
            'type': 'value_error.const',
            'ctx': {'given': FooEnum.bar, 'permitted': (FooEnum.foo,)},
        },
    ]


def test_enum_raw():
    FooEnum = Enum('FooEnum', {'foo': 'foo', 'bar': 'bar'})

    class Model(BaseModel):
        foo: FooEnum = None

    m = Model(foo='foo')
    assert isinstance(m.foo, FooEnum)
    assert m.foo != 'foo'
    assert m.foo.value == 'foo'


def test_set_tuple_values():
    class Model(BaseModel):
        foo: set
        bar: tuple

    m = Model(foo=['a', 'b'], bar=['c', 'd'])
    assert m.foo == {'a', 'b'}
    assert m.bar == ('c', 'd')
    assert m.dict() == {'foo': {'a', 'b'}, 'bar': ('c', 'd')}


def test_default_copy():
    class User(BaseModel):
        friends: List[int] = []

    u1 = User()
    u2 = User()
    assert u1.friends is not u2.friends


class ArbitraryType:
    pass


def test_arbitrary_type_allowed_validation_success():
    class ArbitraryTypeAllowedModel(BaseModel):
        t: ArbitraryType

        class Config:
            arbitrary_types_allowed = True

    arbitrary_type_instance = ArbitraryType()
    m = ArbitraryTypeAllowedModel(t=arbitrary_type_instance)
    assert m.t == arbitrary_type_instance


def test_arbitrary_type_allowed_validation_fails():
    class ArbitraryTypeAllowedModel(BaseModel):
        t: ArbitraryType

        class Config:
            arbitrary_types_allowed = True

    class C:
        pass

    with pytest.raises(ValidationError) as exc_info:
        ArbitraryTypeAllowedModel(t=C())
    assert exc_info.value.errors() == [
        {
            'loc': ('t',),
            'msg': 'instance of ArbitraryType expected',
            'type': 'type_error.arbitrary_type',
            'ctx': {'expected_arbitrary_type': 'ArbitraryType'},
        }
    ]


def test_arbitrary_types_not_allowed():
    with pytest.raises(RuntimeError) as exc_info:

        class ArbitraryTypeNotAllowedModel(BaseModel):
            t: ArbitraryType

    assert exc_info.value.args[0].startswith('no validator found for')


def test_type_type_validation_success():
    class ArbitraryClassAllowedModel(BaseModel):
        t: Type[ArbitraryType]

    arbitrary_type_class = ArbitraryType
    m = ArbitraryClassAllowedModel(t=arbitrary_type_class)
    assert m.t == arbitrary_type_class


def test_type_type_subclass_validation_success():
    class ArbitraryClassAllowedModel(BaseModel):
        t: Type[ArbitraryType]

    class ArbitrarySubType(ArbitraryType):
        pass

    arbitrary_type_class = ArbitrarySubType
    m = ArbitraryClassAllowedModel(t=arbitrary_type_class)
    assert m.t == arbitrary_type_class


def test_type_type_validation_fails_for_instance():
    class ArbitraryClassAllowedModel(BaseModel):
        t: Type[ArbitraryType]

    class C:
        pass

    with pytest.raises(ValidationError) as exc_info:
        ArbitraryClassAllowedModel(t=C)
    assert exc_info.value.errors() == [
        {
            'loc': ('t',),
            'msg': 'subclass of ArbitraryType expected',
            'type': 'type_error.subclass',
            'ctx': {'expected_class': 'ArbitraryType'},
        }
    ]


def test_type_type_validation_fails_for_basic_type():
    class ArbitraryClassAllowedModel(BaseModel):
        t: Type[ArbitraryType]

    with pytest.raises(ValidationError) as exc_info:
        ArbitraryClassAllowedModel(t=1)
    assert exc_info.value.errors() == [
        {
            'loc': ('t',),
            'msg': 'subclass of ArbitraryType expected',
            'type': 'type_error.subclass',
            'ctx': {'expected_class': 'ArbitraryType'},
        }
    ]


def test_bare_type_type_validation_success():
    class ArbitraryClassAllowedModel(BaseModel):
        t: Type

    arbitrary_type_class = ArbitraryType
    m = ArbitraryClassAllowedModel(t=arbitrary_type_class)
    assert m.t == arbitrary_type_class


def test_bare_type_type_validation_fails():
    class ArbitraryClassAllowedModel(BaseModel):
        t: Type

    arbitrary_type = ArbitraryType()
    with pytest.raises(ValidationError) as exc_info:
        ArbitraryClassAllowedModel(t=arbitrary_type)
    assert exc_info.value.errors() == [{'loc': ('t',), 'msg': 'a class is expected', 'type': 'type_error.class'}]


def test_annotation_field_name_shadows_attribute():
    with pytest.raises(NameError):
        # When defining a model that has an attribute with the name of a built-in attribute, an exception is raised
        class BadModel(BaseModel):
            schema: str  # This conflicts with the BaseModel's schema() class method


def test_value_field_name_shadows_attribute():
    # When defining a model that has an attribute with the name of a built-in attribute, an exception is raised
    with pytest.raises(NameError):

        class BadModel(BaseModel):
            schema = 'abc'  # This conflicts with the BaseModel's schema() class method


def test_class_var():
    class MyModel(BaseModel):
        a: ClassVar
        b: ClassVar[int] = 1
        c: int = 2

    assert list(MyModel.__fields__.keys()) == ['c']


def test_fields_set():
    class MyModel(BaseModel):
        a: int
        b: int = 2

    m = MyModel(a=5)
    assert m.__fields_set__ == {'a'}

    m.b = 2
    assert m.__fields_set__ == {'a', 'b'}

    m = MyModel(a=5, b=2)
    assert m.__fields_set__ == {'a', 'b'}


def test_exclude_unset_dict():
    class MyModel(BaseModel):
        a: int
        b: int = 2

    m = MyModel(a=5)
    assert m.dict(exclude_unset=True) == {'a': 5}

    m = MyModel(a=5, b=3)
    assert m.dict(exclude_unset=True) == {'a': 5, 'b': 3}


def test_exclude_unset_recursive():
    class ModelA(BaseModel):
        a: int
        b: int = 1

    class ModelB(BaseModel):
        c: int
        d: int = 2
        e: ModelA

    m = ModelB(c=5, e={'a': 0})
    assert m.dict() == {'c': 5, 'd': 2, 'e': {'a': 0, 'b': 1}}
    assert m.dict(exclude_unset=True) == {'c': 5, 'e': {'a': 0}}
    assert dict(m) == {'c': 5, 'd': 2, 'e': {'a': 0, 'b': 1}}


def test_dict_exclude_unset_populated_by_alias():
    class MyModel(BaseModel):
        a: str = Field('default', alias='alias_a')
        b: str = Field('default', alias='alias_b')

        class Config:
            allow_population_by_field_name = True

    m = MyModel(alias_a='a')

    assert m.dict(exclude_unset=True) == {'a': 'a'}
    assert m.dict(exclude_unset=True, by_alias=True) == {'alias_a': 'a'}


def test_dict_exclude_unset_populated_by_alias_with_extra():
    class MyModel(BaseModel):
        a: str = Field('default', alias='alias_a')
        b: str = Field('default', alias='alias_b')

        class Config:
            extra = 'allow'

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

    assert m.dict(exclude_unset=True) == {'a': 'a', 'c': 'c'}
    assert m.dict(exclude_unset=True, by_alias=True) == {'alias_a': 'a', 'c': 'c'}


def test_exclude_defaults():
    class Model(BaseModel):
        mandatory: str
        nullable_mandatory: Optional[str] = ...
        facultative: str = 'x'
        nullable_facultative: Optional[str] = None

    m = Model(mandatory='a', nullable_mandatory=None)
    assert m.dict(exclude_defaults=True) == {
        'mandatory': 'a',
        'nullable_mandatory': None,
    }

    m = Model(mandatory='a', nullable_mandatory=None, facultative='y', nullable_facultative=None)
    assert m.dict(exclude_defaults=True) == {
        'mandatory': 'a',
        'nullable_mandatory': None,
        'facultative': 'y',
    }

    m = Model(mandatory='a', nullable_mandatory=None, facultative='y', nullable_facultative='z')
    assert m.dict(exclude_defaults=True) == {
        'mandatory': 'a',
        'nullable_mandatory': None,
        'facultative': 'y',
        'nullable_facultative': 'z',
    }


def test_dir_fields():
    class MyModel(BaseModel):
        attribute_a: int
        attribute_b: int = 2

    m = MyModel(attribute_a=5)

    assert 'dict' in dir(m)
    assert 'json' in dir(m)
    assert 'attribute_a' in dir(m)
    assert 'attribute_b' in dir(m)


def test_dict_with_extra_keys():
    class MyModel(BaseModel):
        a: str = Field(None, alias='alias_a')

        class Config:
            extra = Extra.allow

    m = MyModel(extra_key='extra')
    assert m.dict() == {'a': None, 'extra_key': 'extra'}
    assert m.dict(by_alias=True) == {'alias_a': None, 'extra_key': 'extra'}


def test_root():
    class MyModel(BaseModel):
        __root__: str

    m = MyModel(__root__='a')
    assert m.dict() == {'__root__': 'a'}
    assert m.__root__ == 'a'


def test_root_list():
    class MyModel(BaseModel):
        __root__: List[str]

    m = MyModel(__root__=['a'])
    assert m.dict() == {'__root__': ['a']}
    assert m.__root__ == ['a']


def test_encode_nested_root():
    house_dict = {'pets': ['dog', 'cats']}

    class Pets(BaseModel):
        __root__: List[str]

    class House(BaseModel):
        pets: Pets

    assert House(**house_dict).dict() == house_dict

    class PetsDeep(BaseModel):
        __root__: Pets

    class HouseDeep(BaseModel):
        pets: PetsDeep

    assert HouseDeep(**house_dict).dict() == house_dict


def test_root_failed():
    with pytest.raises(ValueError, match='__root__ cannot be mixed with other fields'):

        class MyModel(BaseModel):
            __root__: str
            a: str


def test_root_undefined_failed():
    class MyModel(BaseModel):
        a: List[str]

    with pytest.raises(ValidationError) as exc_info:
        MyModel(__root__=['a'])
        assert exc_info.value.errors() == [{'loc': ('a',), 'msg': 'field required', 'type': 'value_error.missing'}]


def test_parse_root_as_mapping():
    class MyModel(BaseModel):
        __root__: Mapping[str, str]

    assert MyModel.parse_obj({1: 2}).__root__ == {'1': '2'}

    with pytest.raises(ValidationError) as exc_info:
        MyModel.parse_obj({'__root__': {'1': '2'}})
    assert exc_info.value.errors() == [
        {'loc': ('__root__', '__root__'), 'msg': 'str type expected', 'type': 'type_error.str'}
    ]


def test_parse_obj_non_mapping_root():
    class MyModel(BaseModel):
        __root__: List[str]

    assert MyModel.parse_obj(['a']).__root__ == ['a']
    assert MyModel.parse_obj({'__root__': ['a']}).__root__ == ['a']
    with pytest.raises(ValidationError) as exc_info:
        MyModel.parse_obj({'__not_root__': ['a']})
    assert exc_info.value.errors() == [
        {'loc': ('__root__',), 'msg': 'value is not a valid list', 'type': 'type_error.list'}
    ]
    with pytest.raises(ValidationError):
        MyModel.parse_obj({'__root__': ['a'], 'other': 1})
    assert exc_info.value.errors() == [
        {'loc': ('__root__',), 'msg': 'value is not a valid list', 'type': 'type_error.list'}
    ]


def test_untouched_types():
    from pydantic import BaseModel

    class _ClassPropertyDescriptor:
        def __init__(self, getter):
            self.getter = getter

        def __get__(self, instance, owner):
            return self.getter(owner)

    classproperty = _ClassPropertyDescriptor

    class Model(BaseModel):
        class Config:
            keep_untouched = (classproperty,)

        @classproperty
        def class_name(cls) -> str:
            return cls.__name__

    assert Model.class_name == 'Model'
    assert Model().class_name == 'Model'


def test_custom_types_fail_without_keep_untouched():
    from pydantic import BaseModel

    class _ClassPropertyDescriptor:
        def __init__(self, getter):
            self.getter = getter

        def __get__(self, instance, owner):
            return self.getter(owner)

    classproperty = _ClassPropertyDescriptor

    with pytest.raises(RuntimeError) as e:

        class Model(BaseModel):
            @classproperty
            def class_name(cls) -> str:
                return cls.__name__

        Model.class_name

    assert str(e.value) == (
        "no validator found for <class 'tests.test_main.test_custom_types_fail_without_keep_untouched.<locals>."
        "_ClassPropertyDescriptor'>, see `arbitrary_types_allowed` in Config"
    )

    class Model(BaseModel):
        class Config:
            arbitrary_types_allowed = True

        @classproperty
        def class_name(cls) -> str:
            return cls.__name__

    with pytest.raises(AttributeError) as e:
        Model.class_name
    assert str(e.value) == "type object 'Model' has no attribute 'class_name'"


def test_model_iteration():
    class Foo(BaseModel):
        a: int = 1
        b: int = 2

    class Bar(BaseModel):
        c: int
        d: Foo

    m = Bar(c=3, d={})
    assert m.dict() == {'c': 3, 'd': {'a': 1, 'b': 2}}
    assert list(m) == [('c', 3), ('d', Foo())]
    assert dict(m) == {'c': 3, 'd': Foo()}


def test_model_export_nested_list():
    class Foo(BaseModel):
        a: int = 1
        b: int = 2

    class Bar(BaseModel):
        c: int
        foos: List[Foo]

    m = Bar(c=3, foos=[Foo(a=1, b=2), Foo(a=3, b=4)])

    assert m.dict(exclude={'foos': {0: {'a'}, 1: {'a'}}}) == {'c': 3, 'foos': [{'b': 2}, {'b': 4}]}

    with pytest.raises(TypeError, match='expected integer keys'):
        m.dict(exclude={'foos': {'a'}})
    with pytest.raises(TypeError, match='expected integer keys'):
        m.dict(exclude={'foos': {0: ..., 'a': ...}})
    with pytest.raises(TypeError, match='Unexpected type'):
        m.dict(exclude={'foos': {0: 1}})
    with pytest.raises(TypeError, match='Unexpected type'):
        m.dict(exclude={'foos': {'__all__': 1}})

    assert m.dict(exclude={'foos': {0: {'b'}, '__all__': {'a'}}}) == {'c': 3, 'foos': [{}, {'b': 4}]}
    assert m.dict(exclude={'foos': {'__all__': {'a'}}}) == {'c': 3, 'foos': [{'b': 2}, {'b': 4}]}
    assert m.dict(exclude={'foos': {'__all__'}}) == {'c': 3, 'foos': []}

    with pytest.raises(ValueError, match='set with keyword "__all__" must not contain other elements'):
        m.dict(exclude={'foos': {1, '__all__'}})


def test_model_export_dict_exclusion():
    class Foo(BaseModel):
        a: int = 1
        bars: List[Dict[str, int]]

    m = Foo(a=1, bars=[{'w': 0, 'x': 1}, {'y': 2}, {'w': -1, 'z': 3}])

    excludes = {'bars': {0}}
    assert m.dict(exclude=excludes) == {'a': 1, 'bars': [{'y': 2}, {'w': -1, 'z': 3}]}
    assert excludes == {'bars': {0}}
    excludes = {'bars': {'__all__'}}
    assert m.dict(exclude=excludes) == {'a': 1, 'bars': []}
    assert excludes == {'bars': {'__all__'}}
    excludes = {'bars': {'__all__': {'w'}}}
    assert m.dict(exclude=excludes) == {'a': 1, 'bars': [{'x': 1}, {'y': 2}, {'z': 3}]}
    assert excludes == {'bars': {'__all__': {'w'}}}


def test_custom_init_subclass_params():
    class DerivedModel(BaseModel):
        def __init_subclass__(cls, something):
            cls.something = something

    # if this raises a TypeError, then there is a regression of issue 867:
    # pydantic.main.MetaModel.__new__ should include **kwargs at the end of the
    # method definition and pass them on to the super call at the end in order
    # to allow the special method __init_subclass__ to be defined with custom
    # parameters on extended BaseModel classes.
    class NewModel(DerivedModel, something=2):
        something = 1

    assert NewModel.something == 2


def test_update_forward_refs_does_not_modify_module_dict():
    class MyModel(BaseModel):
        field: Optional['MyModel']  # noqa: F821

    MyModel.update_forward_refs()

    assert 'MyModel' not in sys.modules[MyModel.__module__].__dict__


def test_two_defaults():
    with pytest.raises(ValueError, match='^cannot specify both default and default_factory$'):

        class Model(BaseModel):
            a: int = Field(default=3, default_factory=lambda: 3)


def test_default_factory():
    class ValueModel(BaseModel):
        uid: UUID = uuid4()

    m1 = ValueModel()
    m2 = ValueModel()
    assert m1.uid == m2.uid

    class DynamicValueModel(BaseModel):
        uid: UUID = Field(default_factory=uuid4)

    m1 = DynamicValueModel()
    m2 = DynamicValueModel()
    assert isinstance(m1.uid, UUID)
    assert m1.uid != m2.uid

    # With a callable: we still should be able to set callables as defaults
    class FunctionModel(BaseModel):
        a: int = 1
        uid: Callable[[], UUID] = Field(uuid4)

    m = FunctionModel()
    assert m.uid is uuid4

    # Returning a singleton from a default_factory is supported
    class MySingleton:
        pass

    MY_SINGLETON = MySingleton()

    class SingletonFieldModel(BaseModel):
        singleton: MySingleton = Field(default_factory=lambda: MY_SINGLETON)

        class Config:
            arbitrary_types_allowed = True

    assert SingletonFieldModel().singleton is SingletonFieldModel().singleton


def test_default_factory_called_once():
    """It should call only once the given factory by default"""

    class Seq:
        def __init__(self):
            self.v = 0

        def __call__(self):
            self.v += 1
            return self.v

    class MyModel(BaseModel):
        id: int = Field(default_factory=Seq())

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


def test_default_factory_called_once_2():
    """It should call only once the given factory by default"""

    v = 0

    def factory():
        nonlocal v
        v += 1
        return v

    class MyModel(BaseModel):
        id: int = Field(default_factory=factory)

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


def test_default_factory_validate_children():
    class Child(BaseModel):
        x: int

    class Parent(BaseModel):
        children: List[Child] = Field(default_factory=list)

    Parent(children=[{'x': 1}, {'x': 2}])
    with pytest.raises(ValidationError) as exc_info:
        Parent(children=[{'x': 1}, {'y': 2}])

    assert exc_info.value.errors() == [
        {'loc': ('children', 1, 'x'), 'msg': 'field required', 'type': 'value_error.missing'},
    ]


def test_default_factory_parse():
    class Inner(BaseModel):
        val: int = Field(0)

    class Outer(BaseModel):
        inner_1: Inner = Field(default_factory=Inner)
        inner_2: Inner = Field(Inner())

    default = Outer().dict()
    parsed = Outer.parse_obj(default)
    assert parsed.dict() == {'inner_1': {'val': 0}, 'inner_2': {'val': 0}}
    assert repr(parsed) == 'Outer(inner_1=Inner(val=0), inner_2=Inner(val=0))'


@pytest.mark.skipif(sys.version_info < (3, 7), reason='field constraints are set but not enforced with python 3.6')
def test_none_min_max_items():
    # None default
    class Foo(BaseModel):
        foo: List = Field(None)
        bar: List = Field(None, min_items=0)
        baz: List = Field(None, max_items=10)

    f1 = Foo()
    f2 = Foo(bar=None)
    f3 = Foo(baz=None)
    f4 = Foo(bar=None, baz=None)
    for f in (f1, f2, f3, f4):
        assert f.foo is None
        assert f.bar is None
        assert f.baz is None


def test_reuse_same_field():
    required_field = Field(...)

    class Model1(BaseModel):
        required: str = required_field

    class Model2(BaseModel):
        required: str = required_field

    with pytest.raises(ValidationError):
        Model1.parse_obj({})
    with pytest.raises(ValidationError):
        Model2.parse_obj({})


def test_base_config_type_hinting():
    class M(BaseModel):
        a: int

    get_type_hints(M.__config__)
