Repository: pydantic/pydantic

Issue:
Doesn't correctly validate when type is Enum
# Bug

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
             pydantic version: 1.5.1
            pydantic compiled: True
                 install path: /Users/<redacted>/.venvs/<redacted>/lib/python3.6/site-packages/pydantic
               python version: 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 03:03:55)  [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)]
                     platform: Darwin-18.7.0-x86_64-i386-64bit
     optional deps. installed: []
```
Type validation works as expected when using children of Enums (e.g., [here](https://pydantic-docs.helpmanual.io/usage/types/#enums-and-choices)) but not when using Enums themselves.

```py
from enum import Enum

from pydantic import BaseModel


class FruitEnum(Enum):
    pear = 'pear'
    banana = 'banana'


class CookingModelA(BaseModel):
    fruit: FruitEnum


print(CookingModelA(fruit=FruitEnum.banana)) # works as expected


class CookingModelB(BaseModel):
    fruit: Enum


print(CookingModelB(fruit=FruitEnum.banana)) # fails validation with error
    """
    ValidationError: 1 validation error for CookingModelB
    fruit
      value is not a valid enumeration member; permitted:  (type=type_error.enum; enum_values=[])
    """
```
My expectation is that both should validate correctly, since `FruitEnum` is an instance of `Enum`.



Discussion:
To be clear, I understand why there is a need for the type of validation that is currently provided: [ensuring that an argument is one of the authorized values in a given enum](https://github.com/samuelcolvin/pydantic/blob/70d531ff4c1ae3980615cee07a948fe49b1bc7cf/pydantic/validators.py#L248).

But there is also a need to be able to check that an argument is an instance of `Enum`, without caring about what the keys and values of that `Enum` are.

Resolving patch:
diff --git a/pydantic/errors.py b/pydantic/errors.py
--- a/pydantic/errors.py
+++ b/pydantic/errors.py
@@ -30,6 +30,8 @@
     'UrlPortError',
     'UrlExtraError',
     'EnumError',
+    'IntEnumError',
+    'EnumMemberError',
     'IntegerError',
     'FloatError',
     'PathError',
@@ -216,7 +218,9 @@ class UrlExtraError(UrlError):
     msg_template = 'URL invalid, extra characters found after valid URL: {extra!r}'
 
 
-class EnumError(PydanticTypeError):
+class EnumMemberError(PydanticTypeError):
+    code = 'enum'
+
     def __str__(self) -> str:
         permitted = ', '.join(repr(v.value) for v in self.enum_values)  # type: ignore
         return f'value is not a valid enumeration member; permitted: {permitted}'
@@ -492,6 +496,16 @@ class CallableError(PydanticTypeError):
     msg_template = '{value} is not callable'
 
 
+class EnumError(PydanticTypeError):
+    code = 'enum_instance'
+    msg_template = '{value} is not a valid Enum instance'
+
+
+class IntEnumError(PydanticTypeError):
+    code = 'int_enum_instance'
+    msg_template = '{value} is not a valid IntEnum instance'
+
+
 class IPvAnyAddressError(PydanticValueError):
     msg_template = 'value is not a valid IPv4 or IPv6 address'
 
diff --git a/pydantic/validators.py b/pydantic/validators.py
--- a/pydantic/validators.py
+++ b/pydantic/validators.py
@@ -245,12 +245,12 @@ def frozenset_validator(v: Any) -> FrozenSet[Any]:
         raise errors.FrozenSetError()
 
 
-def enum_validator(v: Any, field: 'ModelField', config: 'BaseConfig') -> Enum:
+def enum_member_validator(v: Any, field: 'ModelField', config: 'BaseConfig') -> Enum:
     try:
         enum_v = field.type_(v)
     except ValueError:
         # field.type_ should be an enum, so will be iterable
-        raise errors.EnumError(enum_values=list(field.type_))
+        raise errors.EnumMemberError(enum_values=list(field.type_))
     return enum_v.value if config.use_enum_values else enum_v
 
 
@@ -405,6 +405,20 @@ def callable_validator(v: Any) -> AnyCallable:
     raise errors.CallableError(value=v)
 
 
+def enum_validator(v: Any) -> Enum:
+    if isinstance(v, Enum):
+        return v
+
+    raise errors.EnumError(value=v)
+
+
+def int_enum_validator(v: Any) -> IntEnum:
+    if isinstance(v, IntEnum):
+        return v
+
+    raise errors.IntEnumError(value=v)
+
+
 def make_literal_validator(type_: Any) -> Callable[[Any], Any]:
     permitted_choices = all_literal_values(type_)
     allowed_choices_set = set(permitted_choices)
@@ -502,8 +516,8 @@ def check(self, config: Type['BaseConfig']) -> bool:
 # order is important here, for example: bool is a subclass of int so has to come first, datetime before date same,
 # IPv4Interface before IPv4Address, etc
 _VALIDATORS: List[Tuple[Type[Any], List[Any]]] = [
-    (IntEnum, [int_validator, enum_validator]),
-    (Enum, [enum_validator]),
+    (IntEnum, [int_validator, enum_member_validator]),
+    (Enum, [enum_member_validator]),
     (
         str,
         [
@@ -565,6 +579,12 @@ def find_validators(  # noqa: C901 (ignore complexity)
     if is_literal_type(type_):
         yield make_literal_validator(type_)
         return
+    if type_ is Enum:
+        yield enum_validator
+        return
+    if type_ is IntEnum:
+        yield int_enum_validator
+        return
 
     class_ = get_class(type_)
     if class_ is not None:


Test patch:
diff --git a/tests/test_types.py b/tests/test_types.py
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -726,6 +726,31 @@ def test_int_enum_successful_for_str_int():
     assert repr(m.tool) == '<ToolEnum.wrench: 2>'
 
 
+def test_enum_type():
+    """it should validate any Enum"""
+
+    class Model(BaseModel):
+        my_enum: Enum
+
+    Model(my_enum=FruitEnum.banana)
+    Model(my_enum=ToolEnum.wrench)
+    with pytest.raises(ValidationError):
+        Model(my_enum='banana')
+
+
+def test_int_enum_type():
+    """it should validate any IntEnum"""
+
+    class Model(BaseModel):
+        my_int_enum: IntEnum
+
+    Model(my_int_enum=ToolEnum.wrench)
+    with pytest.raises(ValidationError):
+        Model(my_int_enum=FruitEnum.banana)
+    with pytest.raises(ValidationError):
+        Model(my_int_enum=2)
+
+
 @pytest.mark.skipif(not email_validator, reason='email_validator not installed')
 def test_string_success():
     class MoreStringsModel(BaseModel):