Repository: samuelcolvin/pydantic (python)

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`.


Pull request description:
feat(field): add `Enum` and `IntEnum` as valid types for fields
_This is a proposal as I agree with the author of the issue. Feel free to close it if need be_

## Change Summary
Support `Enum` type as follows

```python
class Model(BaseModel):
    fruit: Enum
```

## Related issue number

closes #1735

## Checklist

* [x] Unit tests for the changes exist
* [x] Tests pass on CI and coverage remains at 100%
* [x] Documentation reflects the changes where applicable
* [x] `changes/<pull request or issue id>-<github username>.md` file added describing change
  (see [changes/README.md](https://github.com/samuelcolvin/pydantic/blob/master/changes/README.md) for details)


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 52084de03..97e5776b0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,7 +15,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        python-version: ['3.6', '3.7', '3.8']
+        python-version: ['3.6', '3.7', '3.8', '3.9']
     env:
       PYTHON: ${{ matrix.python-version }}
       OS: ubuntu
diff --git a/changes/1735-PrettyWood.md b/changes/1735-PrettyWood.md
new file mode 100644
index 000000000..6d7a68bcf
--- /dev/null
+++ b/changes/1735-PrettyWood.md
@@ -0,0 +1,1 @@
+add `Enum` and `IntEnum` as valid types for fields
\ No newline at end of file
diff --git a/changes/1748-PrettyWood.md b/changes/1748-PrettyWood.md
new file mode 100644
index 000000000..e2d366e07
--- /dev/null
+++ b/changes/1748-PrettyWood.md
@@ -0,0 +1,1 @@
+Support custom title, description and default in schema of enums
\ No newline at end of file
diff --git a/changes/1832-PrettyWood.md b/changes/1832-PrettyWood.md
new file mode 100644
index 000000000..c468d2c49
--- /dev/null
+++ b/changes/1832-PrettyWood.md
@@ -0,0 +1,1 @@
+add basic support for python 3.9
diff --git a/docs/build/exec_examples.py b/docs/build/exec_examples.py
index 2f167f527..396c424d7 100755
--- a/docs/build/exec_examples.py
+++ b/docs/build/exec_examples.py
@@ -207,6 +207,13 @@ def exec_examples():
         else:
             lines = lines[ignore_above + 1 :]
 
+        try:
+            ignore_below = lines.index('# ignore-below')
+        except ValueError:
+            pass
+        else:
+            lines = lines[:ignore_below]
+
         lines = '\n'.join(lines).split('\n')
         if any(len(l) > MAX_LINE_LENGTH for l in lines):
             error(f'lines longer than {MAX_LINE_LENGTH} characters')
diff --git a/docs/examples/models_parse.py b/docs/examples/models_parse.py
index 8de6a037d..004ca08d6 100644
--- a/docs/examples/models_parse.py
+++ b/docs/examples/models_parse.py
@@ -1,5 +1,7 @@
 import pickle
 from datetime import datetime
+from pathlib import Path
+
 from pydantic import BaseModel, ValidationError
 
 
@@ -31,8 +33,10 @@ m = User.parse_raw(
 )
 print(m)
 
-path = 'data.json'
-with open(path, 'w') as f:
-    f.write('{"id": 123, "name": "James"}')
+path = Path('data.json')
+path.write_text('{"id": 123, "name": "James"}')
 m = User.parse_file(path)
 print(m)
+# ignore-below
+if path.exists():
+    path.unlink()
diff --git a/docs/usage/types.md b/docs/usage/types.md
index 9f30ab6f3..24015db6d 100644
--- a/docs/usage/types.md
+++ b/docs/usage/types.md
@@ -130,10 +130,16 @@ with custom properties and validation.
   see [Pydantic Types](#pydantic-types) for other custom IP address types
 
 `enum.Enum`
+: checks that the value is a valid Enum instance
+
+`subclass of enum.Enum`
 : checks that the value is a valid member of the enum;
   see [Enums and Choices](#enums-and-choices) for more details
 
 `enum.IntEnum`
+: checks that the value is a valid IntEnum instance
+
+`subclass of enum.IntEnum`
 : checks that the value is a valid member of the integer enum;
   see [Enums and Choices](#enums-and-choices) for more details
 
diff --git a/pydantic/errors.py b/pydantic/errors.py
index 1abb9d8ad..d996bb341 100644
--- a/pydantic/errors.py
+++ b/pydantic/errors.py
@@ -30,6 +30,8 @@ __all__ = (
     '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/fields.py b/pydantic/fields.py
index 2619b4b16..d9bd85613 100644
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -31,6 +31,8 @@ from .typing import (
     NoArgAnyCallable,
     NoneType,
     display_as_type,
+    get_args,
+    get_origin,
     is_literal_type,
     is_new_type,
     new_type_supertype,
@@ -390,7 +392,7 @@ class ModelField(Representation):
         elif lenient_issubclass(self.type_, Json):
             self.type_ = Any
             self.parse_json = True
-        elif isinstance(self.type_, TypeVar):  # type: ignore
+        elif isinstance(self.type_, TypeVar):
             if self.type_.__bound__:
                 self.type_ = self.type_.__bound__
             elif self.type_.__constraints__:
@@ -411,7 +413,7 @@ class ModelField(Representation):
         elif is_literal_type(self.type_):
             return
 
-        origin = getattr(self.type_, '__origin__', None)
+        origin = get_origin(self.type_)
         if origin is None:
             # field is not "typing" object eg. Union, Dict, List etc.
             # allow None for virtual superclasses of NoneType, e.g. Hashable
@@ -422,7 +424,7 @@ class ModelField(Representation):
             return
         if origin is Union:
             types_ = []
-            for type_ in self.type_.__args__:
+            for type_ in get_args(self.type_):
                 if type_ is NoneType:
                     if self.required is Undefined:
                         self.required = False
@@ -444,9 +446,9 @@ class ModelField(Representation):
         if issubclass(origin, Tuple):  # type: ignore
             self.shape = SHAPE_TUPLE
             self.sub_fields = []
-            for i, t in enumerate(self.type_.__args__):
+            for i, t in enumerate(get_args(self.type_)):
                 if t is Ellipsis:
-                    self.type_ = self.type_.__args__[0]
+                    self.type_ = get_args(self.type_)[0]
                     self.shape = SHAPE_TUPLE_ELLIPSIS
                     return
                 self.sub_fields.append(self._create_sub_type(t, f'{self.name}_{i}'))
@@ -460,7 +462,7 @@ class ModelField(Representation):
                     {f'list_{i}': Validator(validator, pre=True) for i, validator in enumerate(get_validators())}
                 )
 
-            self.type_ = self.type_.__args__[0]
+            self.type_ = get_args(self.type_)[0]
             self.shape = SHAPE_LIST
         elif issubclass(origin, Set):
             # Create self validators
@@ -470,22 +472,22 @@ class ModelField(Representation):
                     {f'set_{i}': Validator(validator, pre=True) for i, validator in enumerate(get_validators())}
                 )
 
-            self.type_ = self.type_.__args__[0]
+            self.type_ = get_args(self.type_)[0]
             self.shape = SHAPE_SET
         elif issubclass(origin, FrozenSet):
-            self.type_ = self.type_.__args__[0]
+            self.type_ = get_args(self.type_)[0]
             self.shape = SHAPE_FROZENSET
         elif issubclass(origin, Sequence):
-            self.type_ = self.type_.__args__[0]
+            self.type_ = get_args(self.type_)[0]
             self.shape = SHAPE_SEQUENCE
         elif issubclass(origin, Mapping):
-            self.key_field = self._create_sub_type(self.type_.__args__[0], 'key_' + self.name, for_keys=True)
-            self.type_ = self.type_.__args__[1]
+            self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True)
+            self.type_ = get_args(self.type_)[1]
             self.shape = SHAPE_MAPPING
         # Equality check as almost everything inherits form Iterable, including str
         # check for Iterable and CollectionsIterable, as it could receive one even when declared with the other
         elif origin in {Iterable, CollectionsIterable}:
-            self.type_ = self.type_.__args__[0]
+            self.type_ = get_args(self.type_)[0]
             self.shape = SHAPE_ITERABLE
             self.sub_fields = [self._create_sub_type(self.type_, f'{self.name}_type')]
         elif issubclass(origin, Type):  # type: ignore
@@ -494,7 +496,7 @@ class ModelField(Representation):
             # Is a Pydantic-compatible generic that handles itself
             # or we have arbitrary_types_allowed = True
             self.shape = SHAPE_GENERIC
-            self.sub_fields = [self._create_sub_type(t, f'{self.name}_{i}') for i, t in enumerate(self.type_.__args__)]
+            self.sub_fields = [self._create_sub_type(t, f'{self.name}_{i}') for i, t in enumerate(get_args(self.type_))]
             self.type_ = origin
             return
         else:
diff --git a/pydantic/generics.py b/pydantic/generics.py
index 64562227d..002e7edab 100644
--- a/pydantic/generics.py
+++ b/pydantic/generics.py
@@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, Dict, Tuple, Type, TypeVar, Uni
 from .class_validators import gather_all_validators
 from .fields import FieldInfo, ModelField
 from .main import BaseModel, create_model
+from .typing import get_origin
 from .utils import lenient_issubclass
 
 _generic_types_cache: Dict[Tuple[Type[Any], Union[Any, Tuple[Any, ...]]], Type[BaseModel]] = {}
@@ -29,7 +30,7 @@ class GenericModel(BaseModel):
             raise TypeError('Cannot parameterize a concrete instantiation of a generic model')
         if not isinstance(params, tuple):
             params = (params,)
-        if cls is GenericModel and any(isinstance(param, TypeVar) for param in params):  # type: ignore
+        if cls is GenericModel and any(isinstance(param, TypeVar) for param in params):
             raise TypeError('Type parameters should be placed on typing.Generic, not GenericModel')
         if not hasattr(cls, '__parameters__'):
             raise TypeError(f'Type {cls.__name__} must inherit from typing.Generic before being parameterized')
@@ -37,7 +38,7 @@ class GenericModel(BaseModel):
         check_parameters_count(cls, params)
         typevars_map: Dict[TypeVarType, Type[Any]] = dict(zip(cls.__parameters__, params))
         type_hints = get_type_hints(cls).items()
-        instance_type_hints = {k: v for k, v in type_hints if getattr(v, '__origin__', None) is not ClassVar}
+        instance_type_hints = {k: v for k, v in type_hints if get_origin(v) is not ClassVar}
         concrete_type_hints: Dict[str, Type[Any]] = {
             k: resolve_type_hint(v, typevars_map) for k, v in instance_type_hints.items()
         }
@@ -79,7 +80,7 @@ class GenericModel(BaseModel):
 
 
 def resolve_type_hint(type_: Any, typevars_map: Dict[Any, Any]) -> Type[Any]:
-    if hasattr(type_, '__origin__') and getattr(type_, '__parameters__', None):
+    if get_origin(type_) and getattr(type_, '__parameters__', None):
         concrete_type_args = tuple([typevars_map[x] for x in type_.__parameters__])
         return type_[concrete_type_args]
     return typevars_map.get(type_, type_)
@@ -113,4 +114,4 @@ def _parameterize_generic_field(field_type: Type[Any], typevars_map: Dict[TypeVa
 
 
 def _is_typevar(v: Any) -> bool:
-    return isinstance(v, TypeVar)  # type: ignore
+    return isinstance(v, TypeVar)
diff --git a/pydantic/main.py b/pydantic/main.py
index c6c486ada..12dedecf0 100644
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -33,7 +33,7 @@ from .json import custom_pydantic_encoder, pydantic_encoder
 from .parse import Protocol, load_file, load_str_bytes
 from .schema import model_schema
 from .types import PyObject, StrBytes
-from .typing import AnyCallable, ForwardRef, is_classvar, resolve_annotations, update_field_forward_refs
+from .typing import AnyCallable, ForwardRef, get_origin, is_classvar, resolve_annotations, update_field_forward_refs
 from .utils import (
     ClassAttribute,
     GetterDict,
@@ -257,7 +257,7 @@ class ModelMetaclass(ABCMeta):
                     if (
                         isinstance(value, untouched_types)
                         and ann_type != PyObject
-                        and not lenient_issubclass(getattr(ann_type, '__origin__', None), Type)
+                        and not lenient_issubclass(get_origin(ann_type), Type)
                     ):
                         continue
                     fields[ann_name] = inferred = ModelField.infer(
diff --git a/pydantic/schema.py b/pydantic/schema.py
index 15b506678..533620f74 100644
--- a/pydantic/schema.py
+++ b/pydantic/schema.py
@@ -56,7 +56,7 @@ from .types import (
     conset,
     constr,
 )
-from .typing import ForwardRef, Literal, is_callable_type, is_literal_type, literal_values
+from .typing import ForwardRef, Literal, get_args, get_origin, is_callable_type, is_literal_type, literal_values
 from .utils import get_model, lenient_issubclass, sequence_like
 
 if TYPE_CHECKING:
@@ -149,6 +149,30 @@ def model_schema(
     return m_schema
 
 
+def get_field_info_schema(field: ModelField) -> Tuple[Dict[str, Any], bool]:
+    schema_overrides = False
+
+    # If no title is explicitly set, we don't set title in the schema for enums.
+    # The behaviour is the same as `BaseModel` reference, where the default title
+    # is in the definitions part of the schema.
+    schema: Dict[str, Any] = {}
+    if field.field_info.title or not lenient_issubclass(field.type_, Enum):
+        schema['title'] = field.field_info.title or field.alias.title().replace('_', ' ')
+
+    if field.field_info.title:
+        schema_overrides = True
+
+    if field.field_info.description:
+        schema['description'] = field.field_info.description
+        schema_overrides = True
+
+    if not field.required and not field.field_info.const and field.default is not None:
+        schema['default'] = encode_default(field.default)
+        schema_overrides = True
+
+    return schema, schema_overrides
+
+
 def field_schema(
     field: ModelField,
     *,
@@ -172,18 +196,7 @@ def field_schema(
     :return: tuple of the schema for this field and additional definitions
     """
     ref_prefix = ref_prefix or default_prefix
-    schema_overrides = False
-    s = dict(title=field.field_info.title or field.alias.title().replace('_', ' '))
-    if field.field_info.title:
-        schema_overrides = True
-
-    if field.field_info.description:
-        s['description'] = field.field_info.description
-        schema_overrides = True
-
-    if not field.required and not field.field_info.const and field.default is not None:
-        s['default'] = encode_default(field.default)
-        schema_overrides = True
+    s, schema_overrides = get_field_info_schema(field)
 
     validation_schema = get_field_schema_validations(field)
     if validation_schema:
@@ -228,6 +241,11 @@ def get_field_schema_validations(field: ModelField) -> Dict[str, Any]:
     a Pydantic ``FieldInfo`` with validation arguments.
     """
     f_schema: Dict[str, Any] = {}
+
+    if lenient_issubclass(field.type_, Enum):
+        # schema is already updated by `enum_process_schema`
+        return f_schema
+
     if lenient_issubclass(field.type_, (str, bytes)):
         for attr_name, t, keyword in _str_types_attrs:
             attr = getattr(field.field_info, attr_name, None)
@@ -651,6 +669,11 @@ def add_field_type_to_schema(field_type: Any, schema: Dict[str, Any]) -> None:
             break
 
 
+def get_schema_ref(ref_name: str, schema_overrides: bool) -> Dict[str, Any]:
+    schema_ref = {'$ref': ref_name}
+    return {'allOf': [schema_ref]} if schema_overrides else schema_ref
+
+
 def field_singleton_schema(  # noqa: C901 (ignore complexity)
     field: ModelField,
     *,
@@ -703,7 +726,8 @@ def field_singleton_schema(  # noqa: C901 (ignore complexity)
 
     if lenient_issubclass(field_type, Enum):
         enum_name = normalize_name(field_type.__name__)
-        f_schema = {'$ref': ref_prefix + enum_name}
+        f_schema, schema_overrides = get_field_info_schema(field)
+        f_schema.update(get_schema_ref(ref_prefix + enum_name, schema_overrides))
         definitions[enum_name] = enum_process_schema(field_type)
     else:
         add_field_type_to_schema(field_type, f_schema)
@@ -734,11 +758,8 @@ def field_singleton_schema(  # noqa: C901 (ignore complexity)
             nested_models.update(sub_nested_models)
         else:
             nested_models.add(model_name)
-        schema_ref = {'$ref': ref_prefix + model_name}
-        if not schema_overrides:
-            return schema_ref, definitions, nested_models
-        else:
-            return {'allOf': [schema_ref]}, definitions, nested_models
+        schema_ref = get_schema_ref(ref_prefix + model_name, schema_overrides)
+        return schema_ref, definitions, nested_models
 
     raise ValueError(f'Value not declarable with JSON Schema, field: {field}')
 
@@ -806,9 +827,9 @@ def get_annotation_from_field_info(annotation: Any, field_info: FieldInfo, field
             or lenient_issubclass(type_, (ConstrainedList, ConstrainedSet))
         ):
             return type_
-        origin = getattr(type_, '__origin__', None)
+        origin = get_origin(type_)
         if origin is not None:
-            args: Tuple[Any, ...] = type_.__args__
+            args: Tuple[Any, ...] = get_args(type_)
             if any(isinstance(a, ForwardRef) for a in args):
                 # forward refs cause infinite recursion below
                 return type_
diff --git a/pydantic/tools.py b/pydantic/tools.py
index d0d320b5a..19d992b46 100644
--- a/pydantic/tools.py
+++ b/pydantic/tools.py
@@ -31,7 +31,7 @@ T = TypeVar('T')
 
 
 def parse_obj_as(type_: Type[T], obj: Any, *, type_name: Optional[NameFactory] = None) -> T:
-    model_type = _get_parsing_type(type_, type_name=type_name)
+    model_type = _get_parsing_type(type_, type_name=type_name)  # type: ignore[arg-type]
     return model_type(__root__=obj).__root__
 
 
diff --git a/pydantic/typing.py b/pydantic/typing.py
index 070691eeb..5b55ae7ec 100644
--- a/pydantic/typing.py
+++ b/pydantic/typing.py
@@ -17,6 +17,7 @@ from typing import (  # type: ignore
     Type,
     Union,
     _eval_type,
+    cast,
 )
 
 try:
@@ -44,12 +45,20 @@ if sys.version_info < (3, 7):
         return type_._eval_type(globalns, localns)
 
 
-else:
+elif sys.version_info < (3, 9):
 
     def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
         return type_._evaluate(globalns, localns)
 
 
+else:
+
+    def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
+        # Even though it is the right signature for python 3.9, mypy complains with
+        # `error: Too many arguments for "_evaluate" of "ForwardRef"` hence the cast...
+        return cast(Any, type_)._evaluate(globalns, localns, set())
+
+
 if sys.version_info < (3, 7):
     from typing import Callable as Callable
 
@@ -70,8 +79,55 @@ if sys.version_info < (3, 8):
             from typing_extensions import Literal
         except ImportError:
             Literal = None
+
+    def get_args(t: Type[Any]) -> Tuple[Any, ...]:
+        return getattr(t, '__args__', ())
+
+    def get_origin(t: Type[Any]) -> Optional[Type[Any]]:
+        return getattr(t, '__origin__', None)
+
+
 else:
-    from typing import Literal
+    from typing import Literal, get_args as typing_get_args, get_origin as typing_get_origin
+
+    def get_origin(tp: Type[Any]) -> Type[Any]:
+        """
+        We can't directly use `typing.get_origin` since we need a fallback to support
+        custom generic classes like `ConstrainedList`
+        It should be useless once https://github.com/cython/cython/issues/3537 is
+        solved and https://github.com/samuelcolvin/pydantic/pull/1753 is merged.
+        """
+        return typing_get_origin(tp) or getattr(tp, '__origin__', None)
+
+    def generic_get_args(tp: Type[Any]) -> Tuple[Any, ...]:
+        """
+        In python 3.9, `typing.Dict`, `typing.List`, ...
+        do have an empty `__args__` by default (instead of the generic ~T for example).
+        In order to still support `Dict` for example and consider it as `Dict[Any, Any]`,
+        we retrieve the `_nparams` value that tells us how many parameters it needs.
+        """
+        if hasattr(tp, '_nparams'):
+            return (Any,) * tp._nparams
+        return ()
+
+    def get_args(tp: Type[Any]) -> Tuple[Any, ...]:
+        """Get type arguments with all substitutions performed.
+
+        For unions, basic simplifications used by Union constructor are performed.
+        Examples::
+            get_args(Dict[str, int]) == (str, int)
+            get_args(int) == ()
+            get_args(Union[int, Union[T, int], str][int]) == (int, str)
+            get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
+            get_args(Callable[[], T][int]) == ([], int)
+        """
+        try:
+            args = typing_get_args(tp)
+        except IndexError:
+            args = ()
+        # the fallback is needed for the same reasons as `get_origin` (see above)
+        return args or getattr(tp, '__args__', ()) or generic_get_args(tp)
+
 
 if TYPE_CHECKING:
     from .fields import ModelField
@@ -115,6 +171,8 @@ __all__ = (
     'CallableGenerator',
     'ReprArgs',
     'CallableGenerator',
+    'get_args',
+    'get_origin',
 )
 
 
@@ -167,16 +225,16 @@ def resolve_annotations(raw_annotations: Dict[str, Type[Any]], module_name: Opti
 
 
 def is_callable_type(type_: Type[Any]) -> bool:
-    return type_ is Callable or getattr(type_, '__origin__', None) is Callable
+    return type_ is Callable or get_origin(type_) is Callable
 
 
 if sys.version_info >= (3, 7):
 
     def is_literal_type(type_: Type[Any]) -> bool:
-        return Literal is not None and getattr(type_, '__origin__', None) is Literal
+        return Literal is not None and get_origin(type_) is Literal
 
     def literal_values(type_: Type[Any]) -> Tuple[Any, ...]:
-        return type_.__args__
+        return get_args(type_)
 
 
 else:
@@ -217,12 +275,15 @@ def new_type_supertype(type_: Type[Any]) -> Type[Any]:
     return type_
 
 
-def _check_classvar(v: Type[Any]) -> bool:
+def _check_classvar(v: Optional[Type[Any]]) -> bool:
+    if v is None:
+        return False
+
     return v.__class__ == ClassVar.__class__ and (sys.version_info < (3, 7) or getattr(v, '_name', None) == 'ClassVar')
 
 
 def is_classvar(ann_type: Type[Any]) -> bool:
-    return _check_classvar(ann_type) or _check_classvar(getattr(ann_type, '__origin__', None))
+    return _check_classvar(ann_type) or _check_classvar(get_origin(ann_type))
 
 
 def update_field_forward_refs(field: 'ModelField', globalns: Any, localns: Any) -> None:
@@ -243,13 +304,13 @@ def get_class(type_: Type[Any]) -> Union[None, bool, Type[Any]]:
     without brackets. Otherwise returns None.
     """
     try:
-        origin = getattr(type_, '__origin__')
+        origin = get_origin(type_)
         if origin is None:  # Python 3.6
             origin = type_
         if issubclass(origin, Type):  # type: ignore
-            if type_.__args__ is None or not isinstance(type_.__args__[0], type):
+            if not get_args(type_) or not isinstance(get_args(type_)[0], type):
                 return True
-            return type_.__args__[0]
-    except AttributeError:
+            return get_args(type_)[0]
+    except (AttributeError, TypeError):
         pass
     return None
diff --git a/pydantic/utils.py b/pydantic/utils.py
index 42a4d30e6..38bce03c6 100644
--- a/pydantic/utils.py
+++ b/pydantic/utils.py
@@ -436,10 +436,10 @@ class GetterDict(Representation):
         return item in self.keys()
 
     def __eq__(self, other: Any) -> bool:
-        return dict(self) == dict(other.items())  # type: ignore
+        return dict(self) == dict(other.items())
 
     def __repr_args__(self) -> 'ReprArgs':
-        return [(None, dict(self))]  # type: ignore
+        return [(None, dict(self))]
 
     def __repr_name__(self) -> str:
         return f'GetterDict[{display_as_type(self._obj)}]'
diff --git a/pydantic/validators.py b/pydantic/validators.py
index a53c83119..515ffd02e 100644
--- 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 @@ class IfConfig:
 # 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:
diff --git a/setup.cfg b/setup.cfg
index 732c297e2..18ca8393b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -16,7 +16,7 @@ per-file-ignores =
     docs/examples/types_constrained.py: F722
 
 [bdist_wheel]
-python-tag = py36.py37.py38
+python-tag = py36.py37.py38.py39
 
 [coverage:run]
 source = pydantic
diff --git a/setup.py b/setup.py
index 755d90931..4f5f6b846 100644
--- a/setup.py
+++ b/setup.py
@@ -105,6 +105,7 @@ setup(
         'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: 3.8',
+        'Programming Language :: Python :: 3.9',
         'Intended Audience :: Developers',
         'Intended Audience :: Information Technology',
         'Intended Audience :: System Administrators',


Test patch:
diff --git a/tests/requirements.txt b/tests/requirements.txt
index b7a012b01..8fee4e032 100644
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -2,8 +2,8 @@ black==20.8b1
 coverage==5.3
 flake8==3.8.4
 flake8-quotes==3.2.0
-isort==5.6.1
-mypy==0.782
+isort==5.6.4
+mypy==0.790
 pycodestyle==2.6.0
 pyflakes==2.2.0
 pytest==6.1.1
diff --git a/tests/test_schema.py b/tests/test_schema.py
index b08690ef2..ef5d2b0eb 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -250,6 +250,103 @@ def test_enum_modify_schema():
     }
 
 
+def test_enum_schema_custom_field():
+    class FooBarEnum(str, Enum):
+        foo = 'foo'
+        bar = 'bar'
+
+    class Model(BaseModel):
+        pika: FooBarEnum = Field(alias='pikalias', title='Pikapika!', description='Pika is definitely the best!')
+        bulbi: FooBarEnum = Field('foo', alias='bulbialias', title='Bulbibulbi!', description='Bulbi is not...')
+        cara: FooBarEnum
+
+    assert Model.schema() == {
+        'definitions': {
+            'FooBarEnum': {
+                'description': 'An enumeration.',
+                'enum': ['foo', 'bar'],
+                'title': 'FooBarEnum',
+                'type': 'string',
+            }
+        },
+        'properties': {
+            'pikalias': {
+                'allOf': [{'$ref': '#/definitions/FooBarEnum'}],
+                'description': 'Pika is definitely the best!',
+                'title': 'Pikapika!',
+            },
+            'bulbialias': {
+                'allOf': [{'$ref': '#/definitions/FooBarEnum'}],
+                'description': 'Bulbi is not...',
+                'title': 'Bulbibulbi!',
+                'default': 'foo',
+            },
+            'cara': {'$ref': '#/definitions/FooBarEnum'},
+        },
+        'required': ['pikalias', 'cara'],
+        'title': 'Model',
+        'type': 'object',
+    }
+
+
+def test_enum_and_model_have_same_behaviour():
+    class Names(str, Enum):
+        rick = 'Rick'
+        morty = 'Morty'
+        summer = 'Summer'
+
+    class Pika(BaseModel):
+        a: str
+
+    class Foo(BaseModel):
+        enum: Names
+        titled_enum: Names = Field(
+            ...,
+            title='Title of enum',
+            description='Description of enum',
+        )
+        model: Pika
+        titled_model: Pika = Field(
+            ...,
+            title='Title of model',
+            description='Description of model',
+        )
+
+    assert Foo.schema() == {
+        'definitions': {
+            'Pika': {
+                'properties': {'a': {'title': 'A', 'type': 'string'}},
+                'required': ['a'],
+                'title': 'Pika',
+                'type': 'object',
+            },
+            'Names': {
+                'description': 'An enumeration.',
+                'enum': ['Rick', 'Morty', 'Summer'],
+                'title': 'Names',
+                'type': 'string',
+            },
+        },
+        'properties': {
+            'enum': {'$ref': '#/definitions/Names'},
+            'model': {'$ref': '#/definitions/Pika'},
+            'titled_enum': {
+                'allOf': [{'$ref': '#/definitions/Names'}],
+                'description': 'Description of enum',
+                'title': 'Title of enum',
+            },
+            'titled_model': {
+                'allOf': [{'$ref': '#/definitions/Pika'}],
+                'description': 'Description of model',
+                'title': 'Title of model',
+            },
+        },
+        'required': ['enum', 'titled_enum', 'model', 'titled_model'],
+        'title': 'Foo',
+        'type': 'object',
+    }
+
+
 def test_json_schema():
     class Model(BaseModel):
         a = b'foobar'
diff --git a/tests/test_types.py b/tests/test_types.py
index 6752c6072..cde1cd0fe 100644
--- 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):