Repository: pydantic/pydantic

Issue:
duplicated errors when validators raise ValidationError
# Bug

As a work around for #619 I tried the following

```py
from pydantic import VERSION, BaseModel, Union, validator
from typing_extensions import Literal
print('pydantic version:', VERSION)

class Foo(BaseModel):
    model_type: Literal['foo']
    f: int

class Bar(BaseModel):
    model_type: Literal['bar']
    b: int

class MyModel(BaseModel):
    foobar: Union[Foo, Bar]

    @validator('foobar', pre=True)
    def check_action(cls, v):
        if isinstance(v, dict):
            model_type = v.get('model_type')
            if model_type == 'foo':
                return Foo(**v)
            if model_type == 'var':
                return Bar(**v)
        return v

MyModel(foobar={'model_type': 'foo', 'f': 'x'})
```
Output:
```
pydantic version: 0.32.1
Traceback (most recent call last):
  File "test.py", line 31, in <module>
    MyModel(foobar={'model_type': 'foo', 'f': 'x'})
  File "pydantic/main.py", line 275, in pydantic.main.BaseModel.__init__
  File "pydantic/main.py", line 785, in pydantic.main.validate_model
pydantic.error_wrappers.ValidationError: 2 validation errors for MyModel
foobar -> f
  value is not a valid integer (type=type_error.integer)
foobar -> f
  value is not a valid integer (type=type_error.integer)
```

When validators raise `ValidationError` the errors are duplicated.

Won't be that common, but should be fixed.


Repeated error when validator raises an exception
# Bug

Please complete:
* OS: **Ubuntu**
* Python version `import sys; print(sys.version)`: **3.7.4**
* Pydantic version `import pydantic; print(pydantic.VERSION)`: **v0.32.1**

```py
from typing import Optional

from pydantic import BaseModel, validator

class Foobar(BaseModel):
    foo: Optional[str] = None

    @validator('foo', always=True)
    def check_foo(cls, v):
        if not v:
            raise ValueError('custom error, foo is required')
        return v

print(Foobar(foo='x'))
print(Foobar())
```

Outputs:
```
pydantic.error_wrappers.ValidationError: 2 validation errors for Foobar
foo
  none is not an allowed value (type=type_error.none.not_allowed)
foo
  custom error, foo is required (type=value_error)
```

If i add `pre=True`, the error is even weirder:
```
pydantic.error_wrappers.ValidationError: 2 validation errors for Foobar
foo
  custom error, foo is required (type=value_error)
foo
  custom error, foo is required (type=value_error)
```


Discussion:

I'll look, if this requires breaking changes it can go in v1, if not I guess it could go in v0.32.2
Weirdly doesn't happen if you remove remove `Optional[]`.

Resolving patch:
diff --git a/pydantic/error_wrappers.py b/pydantic/error_wrappers.py
--- a/pydantic/error_wrappers.py
+++ b/pydantic/error_wrappers.py
@@ -42,6 +42,9 @@ def dict(self, *, loc_prefix: Optional[Tuple[str, ...]] = None) -> Dict[str, Any
 
         return d
 
+    def __repr__(self) -> str:
+        return f'<ErrorWrapper {self.dict()}>'
+
 
 # ErrorList is something like Union[List[Union[List[ErrorWrapper], ErrorWrapper]], ErrorWrapper]
 # but recursive, therefore just use:


Test patch:
diff --git a/tests/test_errors.py b/tests/test_errors.py
--- a/tests/test_errors.py
+++ b/tests/test_errors.py
@@ -1,6 +1,13 @@
+from typing import Optional, Union
+
 import pytest
 
-from pydantic import PydanticTypeError
+from pydantic import BaseModel, PydanticTypeError, ValidationError, validator
+
+try:
+    from typing_extensions import Literal
+except ImportError:
+    Literal = None
 
 
 def test_pydantic_error():
@@ -14,3 +21,61 @@ def __init__(self, *, test_ctx: int) -> None:
     with pytest.raises(TestError) as exc_info:
         raise TestError(test_ctx='test_value')
     assert str(exc_info.value) == 'test message template "test_value"'
+
+
+@pytest.mark.skipif(not Literal, reason='typing_extensions not installed')
+def test_interval_validation_error():
+    class Foo(BaseModel):
+        model_type: Literal['foo']
+        f: int
+
+    class Bar(BaseModel):
+        model_type: Literal['bar']
+        b: int
+
+    class MyModel(BaseModel):
+        foobar: Union[Foo, Bar]
+
+        @validator('foobar', pre=True, whole=True)
+        def check_action(cls, v):
+            if isinstance(v, dict):
+                model_type = v.get('model_type')
+                if model_type == 'foo':
+                    return Foo(**v)
+                if model_type == 'bar':
+                    return Bar(**v)
+            raise ValueError('not valid Foo or Bar')
+
+    m1 = MyModel(foobar={'model_type': 'foo', 'f': '1'})
+    assert m1.foobar.f == 1
+    assert isinstance(m1.foobar, Foo)
+
+    m2 = MyModel(foobar={'model_type': 'bar', 'b': '2'})
+    assert m2.foobar.b == 2
+    assert isinstance(m2.foobar, BaseModel)
+
+    with pytest.raises(ValidationError) as exc_info:
+        MyModel(foobar={'model_type': 'foo', 'f': 'x'})
+    assert exc_info.value.errors() == [
+        {'loc': ('foobar', 'f'), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'}
+    ]
+
+
+def test_error_on_optional():
+    class Foobar(BaseModel):
+        foo: Optional[str] = None
+
+        @validator('foo', always=True, whole=True)
+        def check_foo(cls, v):
+            raise ValueError('custom error')
+
+    with pytest.raises(ValidationError) as exc_info:
+        Foobar(foo='x')
+    assert exc_info.value.errors() == [{'loc': ('foo',), 'msg': 'custom error', 'type': 'value_error'}]
+    assert repr(exc_info.value.raw_errors[0]) == (
+        "<ErrorWrapper {'loc': ('foo',), 'msg': 'custom error', 'type': 'value_error'}>"
+    )
+
+    with pytest.raises(ValidationError) as exc_info:
+        Foobar(foo=None)
+    assert exc_info.value.errors() == [{'loc': ('foo',), 'msg': 'custom error', 'type': 'value_error'}]