Repository: samuelcolvin/pydantic
Pull request #2222 (2020-12-26 13:04:47)

Update link to include and exclude docs page
<!-- Thank you for your contribution! -->
<!-- Unless your change is trivial, please create an issue to discuss the change before creating a PR -->
<!-- See https://pydantic-docs.helpmanual.io/contributing/ for help on Contributing -->

## Change Summary

<!-- Please give a short summary of the changes. -->
Docs only change

Not sure if I should prefix the commit msg with `fix:`, and if I should include a `.md` file in the changes folder for such a small change; let me know.

## Related issue number

<!-- Are there any issues opened that will be resolved by merging this change? -->

## 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
* [ ] `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)


codecov[bot]: # [Codecov](https://codecov.io/gh/samuelcolvin/pydantic/pull/2222?src=pr&el=h1) Report
> Merging [#2222](https://codecov.io/gh/samuelcolvin/pydantic/pull/2222?src=pr&el=desc) (5434589) into [master](https://codecov.io/gh/samuelcolvin/pydantic/commit/3496a473c73156fb02edf7dd8a1b1617a76a1b60?el=desc) (3496a47) will **decrease** coverage by `0.36%`.
> The diff coverage is `n/a`.


```diff
@@             Coverage Diff             @@
##            master    #2222      +/-   ##
===========================================
- Coverage   100.00%   99.63%   -0.37%     
===========================================
  Files           21       21              
  Lines         4121     4121              
  Branches       829      829              
===========================================
- Hits          4121     4106      -15     
- Misses           0       12      +12     
- Partials         0        3       +3     
```


| [Impacted Files](https://codecov.io/gh/samuelcolvin/pydantic/pull/2222?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pydantic/version.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2222/diff?src=pr&el=tree#diff-cHlkYW50aWMvdmVyc2lvbi5weQ==) | `88.23% <0.00%> (-11.77%)` | :arrow_down: |
| [pydantic/typing.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2222/diff?src=pr&el=tree#diff-cHlkYW50aWMvdHlwaW5nLnB5) | `94.30% <0.00%> (-5.70%)` | :arrow_down: |
| [pydantic/env\_settings.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2222/diff?src=pr&el=tree#diff-cHlkYW50aWMvZW52X3NldHRpbmdzLnB5) | `97.93% <0.00%> (-2.07%)` | :arrow_down: |
| [pydantic/networks.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2222/diff?src=pr&el=tree#diff-cHlkYW50aWMvbmV0d29ya3MucHk=) | `99.22% <0.00%> (-0.78%)` | :arrow_down: |
| [pydantic/utils.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2222/diff?src=pr&el=tree#diff-cHlkYW50aWMvdXRpbHMucHk=) | `99.29% <0.00%> (-0.71%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/samuelcolvin/pydantic/pull/2222?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/samuelcolvin/pydantic/pull/2222?src=pr&el=footer). Last update [3496a47...5434589](https://codecov.io/gh/samuelcolvin/pydantic/pull/2222?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 81288f452..53c7b153c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -72,7 +72,7 @@ jobs:
       run: make test
 
     - run: coverage xml
-    - uses: codecov/codecov-action@v1.0.15
+    - uses: codecov/codecov-action@v1.1.1
       with:
         file: ./coverage.xml
         env_vars: COMPILED,DEPS,PYTHON,OS
@@ -87,7 +87,7 @@ jobs:
       run: make test
 
     - run: coverage xml
-    - uses: codecov/codecov-action@v1.0.15
+    - uses: codecov/codecov-action@v1.1.1
       with:
         file: ./coverage.xml
         env_vars: COMPILED,DEPS,PYTHON,OS
@@ -105,7 +105,7 @@ jobs:
       run: make test
 
     - run: coverage xml
-    - uses: codecov/codecov-action@v1.0.15
+    - uses: codecov/codecov-action@v1.1.1
       with:
         file: ./coverage.xml
         env_vars: COMPILED,DEPS,PYTHON,OS
@@ -143,7 +143,7 @@ jobs:
 
     - run: coverage xml
 
-    - uses: codecov/codecov-action@v1.0.15
+    - uses: codecov/codecov-action@v1.1.1
       with:
         file: ./coverage.xml
         env_vars: COMPILED,DEPS,PYTHON,OS
diff --git a/HISTORY.md b/HISTORY.md
index 692c8e2c2..c5aeda5b0 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,3 +1,16 @@
+## v1.7.3 (2020-11-30)
+
+Thank you to pydantic's sponsors:
+@timdrijvers, @BCarley, @chdsbd, @tiangolo, @matin, @linusg, @kevinalh, @jorgecarleitao, @koxudaxi, @primer-api,
+@mkeen, @meadsteve for their kind support.
+
+* fix: set right default value for required (optional) fields, #2142 by @PrettyWood
+* fix: support `underscore_attrs_are_private` with generic models, #2138 by @PrettyWood
+* fix: update all modified field values in `root_validator` when `validate_assignment` is on, #2116 by @PrettyWood
+* Allow pickling of `pydantic.dataclasses.dataclass` dynamically created from a built-in `dataclasses.dataclass`, #2111 by @aimestereo
+* Fix a regression where Enum fields would not propagate keyword arguments to the schema, #2109 by @bm424
+* Ignore `__doc__` as private attribute when `Config.underscore_attrs_are_private` is set, #2090 by @PrettyWood
+
 ## v1.7.2 (2020-11-01)
 
 * fix slow `GenericModel` concrete model creation, allow `GenericModel` concrete name reusing in module, #2078 by @MrMrRobat
diff --git a/changes/1911-TrDex.md b/changes/1911-TrDex.md
new file mode 100644
index 000000000..997802283
--- /dev/null
+++ b/changes/1911-TrDex.md
@@ -0,0 +1,2 @@
+Add `rediss` (Redis over SSL) protocol to `RedisDsn`
+Allow URLs without `user` part (e.g., `rediss://:pass@localhost`)
\ No newline at end of file
diff --git a/changes/1960-EdwardBetts.md b/changes/1960-EdwardBetts.md
new file mode 100644
index 000000000..c424d4dde
--- /dev/null
+++ b/changes/1960-EdwardBetts.md
@@ -0,0 +1,1 @@
+Use % for percentage in string format of colors
diff --git a/changes/2095-PrettyWood.md b/changes/2095-PrettyWood.md
new file mode 100644
index 000000000..9f643028f
--- /dev/null
+++ b/changes/2095-PrettyWood.md
@@ -0,0 +1,1 @@
+add validator for `None`, `NoneType` or `Literal[None]`
\ No newline at end of file
diff --git a/changes/2127-PrettyWood.md b/changes/2127-PrettyWood.md
new file mode 100644
index 000000000..dd39881e6
--- /dev/null
+++ b/changes/2127-PrettyWood.md
@@ -0,0 +1,2 @@
+Add a bound method `validate` to functions decorated with `validate_arguments`
+to validate parameters without actually calling the function
\ No newline at end of file
diff --git a/changes/2166-PrettyWood.md b/changes/2166-PrettyWood.md
new file mode 100644
index 000000000..8d882957a
--- /dev/null
+++ b/changes/2166-PrettyWood.md
@@ -0,0 +1,1 @@
+fix: ensure to always return one of the values in `Literal` field type
\ No newline at end of file
diff --git a/changes/2168-andrewmwhite.md b/changes/2168-andrewmwhite.md
new file mode 100644
index 000000000..6b906fcdd
--- /dev/null
+++ b/changes/2168-andrewmwhite.md
@@ -0,0 +1,2 @@
+fix URL regex to parse fragment without query string
+
diff --git a/changes/2213-PrettyWood.md b/changes/2213-PrettyWood.md
new file mode 100644
index 000000000..241538382
--- /dev/null
+++ b/changes/2213-PrettyWood.md
@@ -0,0 +1,1 @@
+Do not ignore annotated fields when type is `Union[Type[...], ...]`
\ No newline at end of file
diff --git a/changes/947-daviskirk.md b/changes/947-daviskirk.md
new file mode 100644
index 000000000..d50a1a1de
--- /dev/null
+++ b/changes/947-daviskirk.md
@@ -0,0 +1,1 @@
+Fix bug where generic models with fields where the typevar is nested in another type `a: List[T]` are considered to be concrete. This allows these models to be subclassed and composed as expected.
diff --git a/docs/build/schema_mapping.py b/docs/build/schema_mapping.py
index 578172746..4f204b358 100755
--- a/docs/build/schema_mapping.py
+++ b/docs/build/schema_mapping.py
@@ -11,6 +11,13 @@ import re
 from pathlib import Path
 
 table = [
+    [
+        'None',
+        'null',
+        '',
+        'JSON Schema Core',
+        'Same for `type(None)` or `Literal[None]`'
+    ],
     [
         'bool',
         'boolean',
diff --git a/docs/examples/types_urls.py b/docs/examples/types_urls.py
index 9874d3a6e..fb49280ff 100644
--- a/docs/examples/types_urls.py
+++ b/docs/examples/types_urls.py
@@ -7,6 +7,7 @@ class MyModel(BaseModel):
 
 m = MyModel(url='http://www.example.com')
 print(m.url)
+
 try:
     MyModel(url='ftp://invalid.url')
 except ValidationError as e:
diff --git a/docs/examples/validation_decorator_validate.py b/docs/examples/validation_decorator_validate.py
new file mode 100644
index 000000000..849b8af0d
--- /dev/null
+++ b/docs/examples/validation_decorator_validate.py
@@ -0,0 +1,17 @@
+from pydantic import validate_arguments, ValidationError
+
+
+@validate_arguments
+def slow_sum(a: int, b: int) -> int:
+    print(f'Called with a={a}, b={b}')
+    return a + b
+
+
+slow_sum(1, 1)
+
+slow_sum.validate(2, 2)
+
+try:
+    slow_sum.validate(1, 'b')
+except ValidationError as exc:
+    print(exc)
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 49078122f..1b8ba1000 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -3,7 +3,7 @@ flake8==3.8.4
 flake8-quotes==3.2.0
 mkdocs==1.1.2
 mkdocs-exclude==1.0.2
-mkdocs-material==6.1.7
+mkdocs-material==6.2.3
 markdown-include==0.6.0
 sqlalchemy
 orjson
diff --git a/docs/usage/exporting_models.md b/docs/usage/exporting_models.md
index 8478457b8..26a6b0fa4 100644
--- a/docs/usage/exporting_models.md
+++ b/docs/usage/exporting_models.md
@@ -7,8 +7,8 @@ This is the primary way of converting a model to a dictionary. Sub-models will b
 
 Arguments:
 
-* `include`: fields to include in the returned dictionary; see [below](#advanced-include-exclude)
-* `exclude`: fields to exclude from the returned dictionary; see [below](#advanced-include-exclude)
+* `include`: fields to include in the returned dictionary; see [below](#advanced-include-and-exclude)
+* `exclude`: fields to exclude from the returned dictionary; see [below](#advanced-include-and-exclude)
 * `by_alias`: whether field aliases should be used as keys in the returned dictionary; default `False`
 * `exclude_unset`: whether fields which were not explicitly set when creating the model should
   be excluded from the returned dictionary; default `False`.
@@ -44,8 +44,8 @@ _(This script is complete, it should run "as is")_
 
 Arguments:
 
-* `include`: fields to include in the returned dictionary; see [below](#advanced-include-exclude)
-* `exclude`: fields to exclude from the returned dictionary; see [below](#advanced-include-exclude)
+* `include`: fields to include in the returned dictionary; see [below](#advanced-include-and-exclude)
+* `exclude`: fields to exclude from the returned dictionary; see [below](#advanced-include-and-exclude)
 * `update`: a dictionary of values to change when creating the copied model
 * `deep`: whether to make a deep copy of the new model; default `False`
 
@@ -64,8 +64,8 @@ only the value for the `__root__` key is serialised)
 
 Arguments:
 
-* `include`: fields to include in the returned dictionary; see [below](#advanced-include-exclude)
-* `exclude`: fields to exclude from the returned dictionary; see [below](#advanced-include-exclude)
+* `include`: fields to include in the returned dictionary; see [below](#advanced-include-and-exclude)
+* `exclude`: fields to exclude from the returned dictionary; see [below](#advanced-include-and-exclude)
 * `by_alias`: whether field aliases should be used as keys in the returned dictionary; default `False`
 * `exclude_unset`: whether fields which were not set when creating the model and have their default values should
   be excluded from the returned dictionary; default `False`.
diff --git a/docs/usage/types.md b/docs/usage/types.md
index 9a41fb1a7..7f1c4d301 100644
--- a/docs/usage/types.md
+++ b/docs/usage/types.md
@@ -11,6 +11,9 @@ with custom properties and validation.
 [Strict Types](#strict-types); if you need to constrain the values allowed (e.g. to require a positive int) see
 [Constrained Types](#constrained-types).
 
+`None`, `type(None)` or `Literal[None]` (equivalent according to [PEP 484](https://www.python.org/dev/peps/pep-0484/#using-none))
+: allows only `None` value
+
 `bool`
 : see [Booleans](#booleans) below for details on how bools are validated and what values are permitted
 
@@ -526,8 +529,8 @@ For URI/URL validation the following types are available:
 - `AnyHttpUrl`: schema `http` or `https`, TLD not required
 - `HttpUrl`: schema `http` or `https`, TLD required, max length 2083
 - `PostgresDsn`: schema `postgres` or `postgresql`, user info required, TLD not required
-- `RedisDsn`: schema `redis`, user info not required, tld not required (CHANGED: user info not required from
-  **v1.6** onwards)
+- `RedisDsn`: schema `redis` or `rediss`, user info not required, tld not required (CHANGED: user info
+  not required from **v1.6** onwards), user info may be passed without user part (e.g., `rediss://:pass@localhost`)
 - `stricturl`, method with the following keyword arguments:
     - `strip_whitespace: bool = True`
     - `min_length: int = 1`
diff --git a/docs/usage/validation_decorator.md b/docs/usage/validation_decorator.md
index 3161d0719..03578c826 100644
--- a/docs/usage/validation_decorator.md
+++ b/docs/usage/validation_decorator.md
@@ -29,9 +29,10 @@ As with the rest of *pydantic*, types can be coerced by the decorator before the
 _(This script is complete, it should run "as is")_
 
 A few notes:
-* through they're passed as strings `path` and `regex` are converted to a `Path` object and regex respectively,
-  by the decorator
-* `max` has no type annotation, so will be considered as `Any` by the decorator
+
+- though they're passed as strings, `path` and `regex` are converted to a `Path` object and regex respectively
+by the decorator
+- `max` has no type annotation, so will be considered as `Any` by the decorator
 
 Type coercion like this can be extremely helpful but also confusing or not desired,
 see [below](#coercion-and-strictness) for a discussion of `validate_arguments`'s limitations in this regard.
@@ -61,6 +62,17 @@ defined to return a function with the same signature as the function it decorate
 since we trick mypy into thinking the function returned by the decorator is the same as the function being
 decorated; access to the [raw function](#raw-function) or other attributes will require `type: ignore`.
 
+## Validate without calling the function
+
+By default, arguments validation is done by directly calling the decorated function with parameters.
+But what if you wanted to validate them without *actually* calling the function?
+To do that you can call the `validate` method bound to the decorated function.
+
+```py
+{!.tmp_examples/validation_decorator_validate.py!}
+```
+_(This script is complete, it should run "as is")_
+
 ## Raw function
 
 The raw function which was decorated is accessible, this is useful if in some scenarios you trust your input
diff --git a/pydantic/color.py b/pydantic/color.py
index 97fc76bf5..b074f2e31 100644
--- a/pydantic/color.py
+++ b/pydantic/color.py
@@ -156,10 +156,10 @@ class Color(Representation):
         """
         if self._rgba.alpha is None:
             h, s, li = self.as_hsl_tuple(alpha=False)  # type: ignore
-            return f'hsl({h * 360:0.0f}, {s * 100:0.0f}%, {li * 100:0.0f}%)'
+            return f'hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%})'
         else:
             h, s, li, a = self.as_hsl_tuple(alpha=True)  # type: ignore
-            return f'hsl({h * 360:0.0f}, {s * 100:0.0f}%, {li * 100:0.0f}%, {round(a, 2)})'
+            return f'hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%}, {round(a, 2)})'
 
     def as_hsl_tuple(self, *, alpha: Optional[bool] = None) -> HslColorTuple:
         """
diff --git a/pydantic/decorator.py b/pydantic/decorator.py
index 0d7ca8822..98bc4e21b 100644
--- a/pydantic/decorator.py
+++ b/pydantic/decorator.py
@@ -52,6 +52,7 @@ def validate_arguments(func: Optional['AnyCallableT'] = None, *, config: 'Config
             return vd.call(*args, **kwargs)
 
         wrapper_function.vd = vd  # type: ignore
+        wrapper_function.validate = vd.init_model_instance  # type: ignore
         wrapper_function.raw_function = vd.raw_function  # type: ignore
         wrapper_function.model = vd.model  # type: ignore
         return wrapper_function
@@ -134,9 +135,12 @@ class ValidatedFunction:
 
         self.create_model(fields, takes_args, takes_kwargs, config)
 
-    def call(self, *args: Any, **kwargs: Any) -> Any:
+    def init_model_instance(self, *args: Any, **kwargs: Any) -> BaseModel:
         values = self.build_values(args, kwargs)
-        m = self.model(**values)
+        return self.model(**values)
+
+    def call(self, *args: Any, **kwargs: Any) -> Any:
+        m = self.init_model_instance(*args, **kwargs)
         return self.execute(m)
 
     def build_values(self, args: Tuple[Any, ...], kwargs: Dict[str, Any]) -> Dict[str, Any]:
diff --git a/pydantic/errors.py b/pydantic/errors.py
index 4e43a1f08..db2df4f4b 100644
--- a/pydantic/errors.py
+++ b/pydantic/errors.py
@@ -17,6 +17,7 @@ __all__ = (
     'NoneIsNotAllowedError',
     'NoneIsAllowedError',
     'WrongConstantError',
+    'NotNoneError',
     'BoolError',
     'BytesError',
     'DictError',
@@ -160,6 +161,11 @@ class WrongConstantError(PydanticValueError):
         return f'unexpected value; permitted: {permitted}'
 
 
+class NotNoneError(PydanticTypeError):
+    code = 'not_none'
+    msg_template = 'value is not None'
+
+
 class BoolError(PydanticTypeError):
     msg_template = 'value could not be parsed to a boolean'
 
diff --git a/pydantic/fields.py b/pydantic/fields.py
index 7d848359d..4a0469f97 100644
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -28,6 +28,7 @@ from .error_wrappers import ErrorWrapper
 from .errors import NoneIsNotAllowedError
 from .types import Json, JsonWrapper
 from .typing import (
+    NONE_TYPES,
     Callable,
     ForwardRef,
     NoArgAnyCallable,
@@ -307,7 +308,7 @@ class ModelField(Representation):
         required: 'BoolUndefined' = Undefined
         if value is Required:
             required = True
-            value = Ellipsis
+            value = None
         elif value is not Undefined:
             required = False
         field_info.alias = field_info.alias or field_info_from_config.get('alias')
@@ -368,7 +369,7 @@ class ModelField(Representation):
         when we want to validate the default value i.e. when `validate_all` is set to True.
         """
         if self.default_factory is not None:
-            if self.type_ is None:
+            if self.type_ is Undefined:
                 raise errors_.ConfigError(
                     f'you need to set the type of field {self.name!r} when using `default_factory`'
                 )
@@ -377,11 +378,11 @@ class ModelField(Representation):
 
         default_value = self.get_default()
 
-        if default_value is not None and self.type_ is None:
+        if default_value is not None and self.type_ is Undefined:
             self.type_ = default_value.__class__
             self.outer_type_ = self.type_
 
-        if self.type_ is None:
+        if self.type_ is Undefined:
             raise errors_.ConfigError(f'unable to infer type for attribute "{self.name}"')
 
         if self.required is False and default_value is None:
@@ -571,7 +572,10 @@ class ModelField(Representation):
                 return v, errors
 
         if v is None:
-            if self.allow_none:
+            if self.type_ in NONE_TYPES:
+                # keep validating
+                pass
+            elif self.allow_none:
                 if self.post_validators:
                     return self._apply_validators(v, values, loc, cls, self.post_validators)
                 else:
@@ -753,12 +757,6 @@ class ModelField(Representation):
                 return v, ErrorWrapper(exc, loc)
         return v, None
 
-    def include_in_schema(self) -> bool:
-        """
-        False if this is a simple field just allowing None as used in Unions/Optional.
-        """
-        return self.type_ != NoneType
-
     def is_complex(self) -> bool:
         """
         Whether the field is "complex" eg. env variables should be parsed as JSON.
diff --git a/pydantic/generics.py b/pydantic/generics.py
index 76be7599b..be09b50a5 100644
--- a/pydantic/generics.py
+++ b/pydantic/generics.py
@@ -1,10 +1,14 @@
 import sys
+import typing
 from typing import (
     TYPE_CHECKING,
     Any,
     ClassVar,
     Dict,
     Generic,
+    Iterable,
+    Iterator,
+    List,
     Optional,
     Tuple,
     Type,
@@ -17,8 +21,8 @@ from typing import (
 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
+from .typing import display_as_type, get_args, get_origin, typing_base
+from .utils import all_identical, lenient_issubclass
 
 _generic_types_cache: Dict[Tuple[Type[Any], Union[Any, Tuple[Any, ...]]], Type[BaseModel]] = {}
 GenericModelT = TypeVar('GenericModelT', bound='GenericModel')
@@ -37,6 +41,16 @@ class GenericModel(BaseModel):
 
     # Setting the return type as Type[Any] instead of Type[BaseModel] prevents PyCharm warnings
     def __class_getitem__(cls: Type[GenericModelT], params: Union[Type[Any], Tuple[Type[Any], ...]]) -> Type[Any]:
+        """Instantiates a new class from a generic class `cls` and type variables `params`.
+
+        :param params: Tuple of types the class . Given a generic class
+            `Model` with 2 type variables and a concrete model `Model[str, int]`,
+            the value `(str, int)` would be passed to `params`.
+        :return: New model class inheriting from `cls` with instantiated
+            types described by `params`. If no parameters are given, `cls` is
+            returned as is.
+
+        """
         cached = _generic_types_cache.get((cls, params))
         if cached is not None:
             return cached
@@ -50,16 +64,24 @@ class GenericModel(BaseModel):
             raise TypeError(f'Type {cls.__name__} must inherit from typing.Generic before being parameterized')
 
         check_parameters_count(cls, params)
+        # Build map from generic typevars to passed params
         typevars_map: Dict[TypeVarType, Type[Any]] = dict(zip(cls.__parameters__, params))
+        if all_identical(typevars_map.keys(), typevars_map.values()) and typevars_map:
+            return cls  # if arguments are equal to parameters it's the same object
+
+        # Recursively walk class type hints and replace generic typevars
+        # with concrete types that were passed.
         type_hints = get_type_hints(cls).items()
         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()
+            k: replace_types(v, typevars_map) for k, v in instance_type_hints.items()
         }
 
+        # Create new model with original model as parent inserting fields with
+        # updated type hints.
         model_name = cls.__concrete_name__(params)
         validators = gather_all_validators(cls)
-        fields = _build_generic_fields(cls.__fields__, concrete_type_hints, typevars_map)
+        fields = _build_generic_fields(cls.__fields__, concrete_type_hints)
         model_module, called_globally = get_caller_frame_info()
         created_model = cast(
             Type[GenericModel],  # casting ensures mypy is aware of the __concrete__ and __parameters__ attributes
@@ -82,12 +104,20 @@ class GenericModel(BaseModel):
                 reference_name += '_'
 
         created_model.Config = cls.Config
-        concrete = all(not _is_typevar(v) for v in concrete_type_hints.values())
-        created_model.__concrete__ = concrete
-        if not concrete:
-            parameters = tuple(v for v in concrete_type_hints.values() if _is_typevar(v))
-            parameters = tuple({k: None for k in parameters}.keys())  # get unique params while maintaining order
-            created_model.__parameters__ = parameters
+
+        # Find any typevars that are still present in the model.
+        # If none are left, the model is fully "concrete", otherwise the new
+        # class is a generic class as well taking the found typevars as
+        # parameters.
+        new_params = tuple(
+            {param: None for param in iter_contained_typevars(typevars_map.values())}
+        )  # use dict as ordered set
+        created_model.__concrete__ = not new_params
+        if new_params:
+            created_model.__parameters__ = new_params
+
+        # Save created model in cache so we don't end up creating duplicate
+        # models that should be identical.
         _generic_types_cache[(cls, params)] = created_model
         if len(params) == 1:
             _generic_types_cache[(cls, params[0])] = created_model
@@ -95,19 +125,74 @@ class GenericModel(BaseModel):
 
     @classmethod
     def __concrete_name__(cls: Type[Any], params: Tuple[Type[Any], ...]) -> str:
+        """Compute class name for child classes.
+
+        :param params: Tuple of types the class . Given a generic class
+            `Model` with 2 type variables and a concrete model `Model[str, int]`,
+            the value `(str, int)` would be passed to `params`.
+        :return: String representing a the new class where `params` are
+            passed to `cls` as type variables.
+
+        This method can be overridden to achieve a custom naming scheme for GenericModels.
         """
-        This method can be overridden to achieve a custom naming scheme for GenericModels
-        """
-        param_names = [param.__name__ if hasattr(param, '__name__') else str(param) for param in params]
+        param_names = [display_as_type(param) for param in params]
         params_component = ', '.join(param_names)
         return f'{cls.__name__}[{params_component}]'
 
 
-def resolve_type_hint(type_: Any, typevars_map: Dict[Any, Any]) -> Type[Any]:
-    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_)
+def replace_types(type_: Any, type_map: Dict[Any, Any]) -> Any:
+    """Return type with all occurances of `type_map` keys recursively replaced with their values.
+
+    :param type_: Any type, class or generic alias
+    :type_map: Mapping from `TypeVar` instance to concrete types.
+    :return: New type representing the basic structure of `type_` with all
+        `typevar_map` keys recursively replaced.
+
+    >>> replace_types(Tuple[str, Union[List[str], float]], {str: int})
+    Tuple[int, Union[List[int], float]]
+
+    """
+    if not type_map:
+        return type_
+
+    type_args = get_args(type_)
+    origin_type = get_origin(type_)
+
+    # Having type args is a good indicator that this is a typing module
+    # class instantiation or a generic alias of some sort.
+    if type_args:
+        resolved_type_args = tuple(replace_types(arg, type_map) for arg in type_args)
+        if all_identical(type_args, resolved_type_args):
+            # If all arguments are the same, there is no need to modify the
+            # type or create a new object at all
+            return type_
+        if origin_type is not None and isinstance(type_, typing_base) and not isinstance(origin_type, typing_base):
+            # In python < 3.9 generic aliases don't exist so any of these like `list`,
+            # `type` or `collections.abc.Callable` need to be translated.
+            # See: https://www.python.org/dev/peps/pep-0585
+            origin_type = getattr(typing, type_._name)
+        return origin_type[resolved_type_args]
+
+    # We handle pydantic generic models separately as they don't have the same
+    # semantics as "typing" classes or generic aliases
+    if not origin_type and lenient_issubclass(type_, GenericModel) and not type_.__concrete__:
+        type_args = type_.__parameters__
+        resolved_type_args = tuple(replace_types(t, type_map) for t in type_args)
+        if all_identical(type_args, resolved_type_args):
+            return type_
+        return type_[resolved_type_args]
+
+    # Handle special case for typehints that can have lists as arguments.
+    # `typing.Callable[[int, str], int]` is an example for this.
+    if isinstance(type_, (List, list)):
+        resolved_list = list(replace_types(element, type_map) for element in type_)
+        if all_identical(type_, resolved_list):
+            return type_
+        return resolved_list
+
+    # If all else fails, we try to resolve the type directly and otherwise just
+    # return the input with no modifications.
+    return type_map.get(type_, type_)
 
 
 def check_parameters_count(cls: Type[GenericModel], parameters: Tuple[Any, ...]) -> None:
@@ -118,27 +203,26 @@ def check_parameters_count(cls: Type[GenericModel], parameters: Tuple[Any, ...])
         raise TypeError(f'Too {description} parameters for {cls.__name__}; actual {actual}, expected {expected}')
 
 
+def iter_contained_typevars(v: Any) -> Iterator[TypeVarType]:
+    """Recursively iterate through all subtypes and type args of `v` and yield any typevars that are found."""
+    if isinstance(v, TypeVar):
+        yield v
+    elif hasattr(v, '__parameters__') and not get_origin(v) and lenient_issubclass(v, GenericModel):
+        yield from v.__parameters__
+    elif isinstance(v, Iterable):
+        for var in v:
+            yield from iter_contained_typevars(var)
+    else:
+        args = get_args(v)
+        for arg in args:
+            yield from iter_contained_typevars(arg)
+
+
 def _build_generic_fields(
     raw_fields: Dict[str, ModelField],
     concrete_type_hints: Dict[str, Type[Any]],
-    typevars_map: Dict[TypeVarType, Type[Any]],
 ) -> Dict[str, Tuple[Type[Any], FieldInfo]]:
-    return {
-        k: (_parameterize_generic_field(v, typevars_map), raw_fields[k].field_info)
-        for k, v in concrete_type_hints.items()
-        if k in raw_fields
-    }
-
-
-def _parameterize_generic_field(field_type: Type[Any], typevars_map: Dict[TypeVarType, Type[Any]]) -> Type[Any]:
-    if lenient_issubclass(field_type, GenericModel) and not field_type.__concrete__:
-        parameters = tuple(typevars_map.get(param, param) for param in field_type.__parameters__)
-        field_type = field_type[parameters]
-    return field_type
-
-
-def _is_typevar(v: Any) -> bool:
-    return isinstance(v, TypeVar)
+    return {k: (v, raw_fields[k].field_info) for k, v in concrete_type_hints.items() if k in raw_fields}
 
 
 def get_caller_frame_info() -> Tuple[Optional[str], bool]:
diff --git a/pydantic/main.py b/pydantic/main.py
index 4834cbf12..10a3ee889 100644
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -34,7 +34,15 @@ from .json import custom_pydantic_encoder, pydantic_encoder
 from .parse import Protocol, load_file, load_str_bytes
 from .schema import default_ref_template, model_schema
 from .types import PyObject, StrBytes
-from .typing import AnyCallable, ForwardRef, get_origin, is_classvar, resolve_annotations, update_field_forward_refs
+from .typing import (
+    AnyCallable,
+    ForwardRef,
+    get_args,
+    get_origin,
+    is_classvar,
+    resolve_annotations,
+    update_field_forward_refs,
+)
 from .utils import (
     ROOT_KEY,
     ClassAttribute,
@@ -253,10 +261,13 @@ class ModelMetaclass(ABCMeta):
                 elif is_valid_field(ann_name):
                     validate_field_name(bases, ann_name)
                     value = namespace.get(ann_name, Undefined)
+                    allowed_types = get_args(ann_type) if get_origin(ann_type) is Union else (ann_type,)
                     if (
                         isinstance(value, untouched_types)
                         and ann_type != PyObject
-                        and not lenient_issubclass(get_origin(ann_type), Type)
+                        and not any(
+                            lenient_issubclass(get_origin(allowed_type), Type) for allowed_type in allowed_types
+                        )
                     ):
                         continue
                     fields[ann_name] = inferred = ModelField.infer(
@@ -285,7 +296,7 @@ class ModelMetaclass(ABCMeta):
                     inferred = ModelField.infer(
                         name=var_name,
                         value=value,
-                        annotation=annotations.get(var_name),
+                        annotation=annotations.get(var_name, Undefined),
                         class_validators=vg.get_validators(var_name),
                         config=config,
                     )
@@ -788,12 +799,12 @@ class BaseModel(Representation, metaclass=ModelMetaclass):
         value_include = ValueItems(self, include) if include else None
 
         for field_key, v in self.__dict__.items():
-            if (
-                (allowed_keys is not None and field_key not in allowed_keys)
-                or (exclude_none and v is None)
-                or (exclude_defaults and getattr(self.__fields__.get(field_key), 'default', _missing) == v)
-            ):
+            if (allowed_keys is not None and field_key not in allowed_keys) or (exclude_none and v is None):
                 continue
+            if exclude_defaults:
+                model_field = self.__fields__.get(field_key)
+                if not getattr(model_field, 'required', True) and getattr(model_field, 'default', _missing) == v:
+                    continue
             if by_alias and field_key in self.__fields__:
                 dict_key = self.__fields__[field_key].alias
             else:
diff --git a/pydantic/networks.py b/pydantic/networks.py
index 2418e5b6c..d5d74794b 100644
--- a/pydantic/networks.py
+++ b/pydantic/networks.py
@@ -43,7 +43,6 @@ __all__ = [
     'validate_email',
 ]
 
-
 _url_regex_cache = None
 _ascii_domain_regex_cache = None
 _int_domain_regex_cache = None
@@ -54,14 +53,14 @@ def url_regex() -> Pattern[str]:
     if _url_regex_cache is None:
         _url_regex_cache = re.compile(
             r'(?:(?P<scheme>[a-z][a-z0-9+\-.]+)://)?'  # scheme https://tools.ietf.org/html/rfc3986#appendix-A
-            r'(?:(?P<user>[^\s:/]+)(?::(?P<password>[^\s/]*))?@)?'  # user info
+            r'(?:(?P<user>[^\s:/]*)(?::(?P<password>[^\s/]*))?@)?'  # user info
             r'(?:'
             r'(?P<ipv4>(?:\d{1,3}\.){3}\d{1,3})|'  # ipv4
             r'(?P<ipv6>\[[A-F0-9]*:[A-F0-9:]+\])|'  # ipv6
             r'(?P<domain>[^\s/:?#]+)'  # domain, validation occurs later
             r')?'
             r'(?::(?P<port>\d+))?'  # port
-            r'(?P<path>/[^\s?]*)?'  # path
+            r'(?P<path>/[^\s?#]*)?'  # path
             r'(?:\?(?P<query>[^\s#]+))?'  # query
             r'(?:#(?P<fragment>\S+))?',  # fragment
             re.IGNORECASE,
@@ -147,8 +146,9 @@ class AnyUrl(str):
         url = scheme + '://'
         if user:
             url += user
-            if password:
-                url += ':' + password
+        if password:
+            url += ':' + password
+        if user or password:
             url += '@'
         url += host
         if port:
@@ -183,20 +183,7 @@ class AnyUrl(str):
         assert m, 'URL regex failed unexpectedly'
 
         parts = m.groupdict()
-        scheme = parts['scheme']
-        if scheme is None:
-            raise errors.UrlSchemeError()
-
-        if cls.allowed_schemes and scheme.lower() not in cls.allowed_schemes:
-            raise errors.UrlSchemePermittedError(cls.allowed_schemes)
-
-        port = parts['port']
-        if port is not None and int(port) > 65_535:
-            raise errors.UrlPortError()
-
-        user = parts['user']
-        if cls.user_required and user is None:
-            raise errors.UrlUserInfoError()
+        parts = cls.validate_parts(parts)
 
         host, tld, host_type, rebuild = cls.validate_host(parts)
 
@@ -205,18 +192,41 @@ class AnyUrl(str):
 
         return cls(
             None if rebuild else url,
-            scheme=scheme,
-            user=user,
+            scheme=parts['scheme'],
+            user=parts['user'],
             password=parts['password'],
             host=host,
             tld=tld,
             host_type=host_type,
-            port=port,
+            port=parts['port'],
             path=parts['path'],
             query=parts['query'],
             fragment=parts['fragment'],
         )
 
+    @classmethod
+    def validate_parts(cls, parts: Dict[str, str]) -> Dict[str, str]:
+        """
+        A method used to validate parts of an URL.
+        Could be overridden to set default values for parts if missing
+        """
+        scheme = parts['scheme']
+        if scheme is None:
+            raise errors.UrlSchemeError()
+
+        if cls.allowed_schemes and scheme.lower() not in cls.allowed_schemes:
+            raise errors.UrlSchemePermittedError(cls.allowed_schemes)
+
+        port = parts['port']
+        if port is not None and int(port) > 65_535:
+            raise errors.UrlPortError()
+
+        user = parts['user']
+        if cls.user_required and user is None:
+            raise errors.UrlUserInfoError()
+
+        return parts
+
     @classmethod
     def validate_host(cls, parts: Dict[str, str]) -> Tuple[str, Optional[str], str, bool]:
         host, tld, host_type, rebuild = None, None, None, False
@@ -279,7 +289,19 @@ class PostgresDsn(AnyUrl):
 
 
 class RedisDsn(AnyUrl):
-    allowed_schemes = {'redis'}
+    allowed_schemes = {'redis', 'rediss'}
+
+    @classmethod
+    def validate_parts(cls, parts: Dict[str, str]) -> Dict[str, str]:
+        defaults = {
+            'domain': 'localhost' if not (parts['ipv4'] or parts['ipv6']) else '',
+            'port': '6379',
+            'path': '/0',
+        }
+        for key, value in defaults.items():
+            if not parts[key]:
+                parts[key] = value
+        return super().validate_parts(parts)
 
 
 def stricturl(
diff --git a/pydantic/schema.py b/pydantic/schema.py
index e51d74ce1..bb4324523 100644
--- a/pydantic/schema.py
+++ b/pydantic/schema.py
@@ -57,7 +57,16 @@ from .types import (
     conset,
     constr,
 )
-from .typing import ForwardRef, Literal, get_args, get_origin, is_callable_type, is_literal_type, literal_values
+from .typing import (
+    NONE_TYPES,
+    ForwardRef,
+    Literal,
+    get_args,
+    get_origin,
+    is_callable_type,
+    is_literal_type,
+    literal_values,
+)
 from .utils import ROOT_KEY, get_model, lenient_issubclass, sequence_like
 
 if TYPE_CHECKING:
@@ -640,7 +649,6 @@ def field_singleton_sub_fields_schema(
     """
     definitions = {}
     nested_models: Set[str] = set()
-    sub_fields = [sf for sf in sub_fields if sf.include_in_schema()]
     if len(sub_fields) == 1:
         return field_type_schema(
             sub_fields[0],
@@ -759,6 +767,8 @@ def field_singleton_schema(  # noqa: C901 (ignore complexity)
         )
     if field.type_ is Any or field.type_.__class__ == TypeVar:
         return {}, definitions, nested_models  # no restrictions
+    if field.type_ in NONE_TYPES:
+        return {'type': 'null'}, definitions, nested_models
     if is_callable_type(field.type_):
         raise SkipField(f'Callable {field.name} was excluded from schema since JSON schema has no equivalent type.')
     f_schema: Dict[str, Any] = {}
diff --git a/pydantic/typing.py b/pydantic/typing.py
index e71228f67..8f8cbac92 100644
--- a/pydantic/typing.py
+++ b/pydantic/typing.py
@@ -25,6 +25,12 @@ try:
 except ImportError:
     from typing import _Final as typing_base  # type: ignore
 
+try:
+    from typing import GenericAlias  # type: ignore
+except ImportError:
+    # python < 3.9 does not have GenericAlias (list[int], tuple[str, ...] and so on)
+    GenericAlias = ()
+
 
 if sys.version_info < (3, 7):
     if TYPE_CHECKING:
@@ -74,7 +80,7 @@ else:
     AnyCallable = TypingCallable[..., Any]
     NoArgAnyCallable = TypingCallable[[], Any]
 
-if sys.version_info < (3, 8):
+if sys.version_info < (3, 8):  # noqa: C901
     if TYPE_CHECKING:
         from typing_extensions import Literal
     else:  # due to different mypy warnings raised during CI for python 3.7 and 3.8
@@ -83,8 +89,34 @@ if sys.version_info < (3, 8):
         except ImportError:
             Literal = None
 
-    def get_args(t: Type[Any]) -> Tuple[Any, ...]:
-        return getattr(t, '__args__', ())
+    if sys.version_info < (3, 7):
+
+        def get_args(t: Type[Any]) -> Tuple[Any, ...]:
+            """Simplest get_args compatability layer possible.
+
+            The Python 3.6 typing module does not have `_GenericAlias` so
+            this won't work for everything. In particular this will not
+            support the `generics` module (we don't support generic models in
+            python 3.6).
+
+            """
+            return getattr(t, '__args__', ())
+
+    else:
+        from typing import _GenericAlias
+
+        def get_args(t: Type[Any]) -> Tuple[Any, ...]:
+            """Compatability version of get_args for python 3.7.
+
+            Mostly compatible with the python 3.8 `typing` module version
+            and able to handle almost all use cases.
+            """
+            if isinstance(t, _GenericAlias):
+                res = t.__args__
+                if t.__origin__ is Callable and res and res[0] is not Ellipsis:
+                    res = (list(res[:-1]), res[-1])
+                return res
+            return getattr(t, '__args__', ())
 
     def get_origin(t: Type[Any]) -> Optional[Type[Any]]:
         return getattr(t, '__origin__', None)
@@ -149,6 +181,7 @@ __all__ = (
     'AnyCallable',
     'NoArgAnyCallable',
     'NoneType',
+    'NONE_TYPES',
     'display_as_type',
     'resolve_annotations',
     'is_callable_type',
@@ -176,10 +209,13 @@ __all__ = (
 
 
 NoneType = None.__class__
+NONE_TYPES: Set[Any] = {None, NoneType}
+if Literal:
+    NONE_TYPES.add(Literal[None])
 
 
 def display_as_type(v: Type[Any]) -> str:
-    if not isinstance(v, typing_base) and not isinstance(v, type):
+    if not isinstance(v, typing_base) and not isinstance(v, GenericAlias) and not isinstance(v, type):
         v = v.__class__
 
     if isinstance(v, type) and issubclass(v, Enum):
@@ -190,6 +226,10 @@ def display_as_type(v: Type[Any]) -> str:
         else:
             return 'enum'
 
+    if isinstance(v, GenericAlias):
+        # Generic alias are constructs like `list[int]`
+        return str(v).replace('typing.', '')
+
     try:
         return v.__name__
     except AttributeError:
diff --git a/pydantic/utils.py b/pydantic/utils.py
index c75f4e1cf..ab2f3bcdf 100644
--- a/pydantic/utils.py
+++ b/pydantic/utils.py
@@ -2,7 +2,7 @@ import warnings
 import weakref
 from collections import OrderedDict, defaultdict, deque
 from copy import deepcopy
-from itertools import islice
+from itertools import islice, zip_longest
 from types import BuiltinFunctionType, CodeType, FunctionType, GeneratorType, LambdaType, ModuleType
 from typing import (
     TYPE_CHECKING,
@@ -11,6 +11,7 @@ from typing import (
     Callable,
     Dict,
     Generator,
+    Iterable,
     Iterator,
     List,
     Mapping,
@@ -639,3 +640,22 @@ def is_valid_private_name(name: str) -> bool:
         '__orig_bases__',
         '__qualname__',
     }
+
+
+_EMPTY = object()
+
+
+def all_identical(left: Iterable[Any], right: Iterable[Any]) -> bool:
+    """
+    Check that the items of `left` are the same objects as those in `right`.
+
+    >>> a, b = object(), object()
+    >>> all_identical([a, b, a], [a, b, a])
+    True
+    >>> all_identical([a, b, [a]], [a, b, [a]])  # new list object, while "equal" is not "identical"
+    False
+    """
+    for left_item, right_item in zip_longest(left, right, fillvalue=_EMPTY):
+        if left_item is not right_item:
+            return False
+    return True
diff --git a/pydantic/validators.py b/pydantic/validators.py
index c47ee2f4f..cea25c11a 100644
--- a/pydantic/validators.py
+++ b/pydantic/validators.py
@@ -27,8 +27,10 @@ from uuid import UUID
 from . import errors
 from .datetime_parse import parse_date, parse_datetime, parse_duration, parse_time
 from .typing import (
+    NONE_TYPES,
     AnyCallable,
     ForwardRef,
+    Literal,
     all_literal_values,
     display_as_type,
     get_class,
@@ -440,12 +442,17 @@ def int_enum_validator(v: Any) -> IntEnum:
 
 def make_literal_validator(type_: Any) -> Callable[[Any], Any]:
     permitted_choices = all_literal_values(type_)
-    allowed_choices_set = set(permitted_choices)
+
+    # To have a O(1) complexity and still return one of the values set inside the `Literal`,
+    # we create a dict with the set values (a set causes some problems with the way intersection works).
+    # In some cases the set value and checked value can indeed be different (see `test_literal_validator_str_enum`)
+    allowed_choices = {v: v for v in permitted_choices}
 
     def literal_validator(v: Any) -> Any:
-        if v not in allowed_choices_set:
+        try:
+            return allowed_choices[v]
+        except KeyError:
             raise errors.WrongConstantError(given=v, permitted=permitted_choices)
-        return v
 
     return literal_validator
 
@@ -511,6 +518,12 @@ def any_class_validator(v: Any) -> Type[T]:
     raise errors.ClassError()
 
 
+def none_validator(v: Any) -> 'Literal[None]':
+    if v is None:
+        return v
+    raise errors.NotNoneError()
+
+
 def pattern_validator(v: Any) -> Pattern[str]:
     if isinstance(v, Pattern):
         return v
@@ -589,6 +602,9 @@ def find_validators(  # noqa: C901 (ignore complexity)
     type_type = type_.__class__
     if type_type == ForwardRef or type_type == TypeVar:
         return
+    if type_ in NONE_TYPES:
+        yield none_validator
+        return
     if type_ is Pattern:
         yield pattern_validator
         return
diff --git a/pydantic/version.py b/pydantic/version.py
index 4ba6f2dfb..dd840e984 100644
--- a/pydantic/version.py
+++ b/pydantic/version.py
@@ -1,6 +1,6 @@
 __all__ = 'VERSION', 'version_info'
 
-VERSION = '1.7.2'
+VERSION = '1.7.3'
 
 
 def version_info() -> str:


Test patch:
diff --git a/tests/requirements-linting.txt b/tests/requirements-linting.txt
index 336553d16..0dc0c0602 100644
--- a/tests/requirements-linting.txt
+++ b/tests/requirements-linting.txt
@@ -5,4 +5,4 @@ isort==5.6.4
 mypy==0.790
 pycodestyle==2.6.0
 pyflakes==2.2.0
-twine==3.2.0
+twine==3.3.0
diff --git a/tests/requirements-testing.txt b/tests/requirements-testing.txt
index bb600754b..beb21c594 100644
--- a/tests/requirements-testing.txt
+++ b/tests/requirements-testing.txt
@@ -1,4 +1,4 @@
-coverage==5.3
+coverage==5.3.1
 # pin importlib-metadata as upper versions need typing-extensions to work if on python < 3.8
 importlib-metadata==3.1.0;python_version<"3.8"
 mypy==0.790
diff --git a/tests/test_create_model.py b/tests/test_create_model.py
index f9f2862a1..44ea34c0f 100644
--- a/tests/test_create_model.py
+++ b/tests/test_create_model.py
@@ -182,3 +182,15 @@ def test_repeat_base_usage():
     assert model.__fields__.keys() == {'a', 'b'}
     assert model2.__fields__.keys() == {'a', 'c'}
     assert model3.__fields__.keys() == {'a', 'b', 'd'}
+
+
+def test_dynamic_and_static():
+    class A(BaseModel):
+        x: int
+        y: float
+        z: str
+
+    DynamicA = create_model('A', x=(int, ...), y=(float, ...), z=(str, ...))
+
+    for field_name in ('x', 'y', 'z'):
+        assert A.__fields__[field_name].default == DynamicA.__fields__[field_name].default
diff --git a/tests/test_dataclasses.py b/tests/test_dataclasses.py
index e087455b6..197db324b 100644
--- a/tests/test_dataclasses.py
+++ b/tests/test_dataclasses.py
@@ -407,7 +407,7 @@ def test_fields():
     fields = user.__pydantic_model__.__fields__
 
     assert fields['id'].required is True
-    assert fields['id'].default is Ellipsis
+    assert fields['id'].default is None
 
     assert fields['name'].required is False
     assert fields['name'].default == 'John Doe'
@@ -426,7 +426,7 @@ def test_default_factory_field():
     fields = user.__pydantic_model__.__fields__
 
     assert fields['id'].required is True
-    assert fields['id'].default is Ellipsis
+    assert fields['id'].default is None
 
     assert fields['aliases'].required is False
     assert fields['aliases'].default == {'John': 'Joey'}
diff --git a/tests/test_decorator.py b/tests/test_decorator.py
index 4710d5141..ed7efd3ba 100644
--- a/tests/test_decorator.py
+++ b/tests/test_decorator.py
@@ -318,3 +318,17 @@ def test_config_arbitrary_types_allowed():
             'ctx': {'expected_arbitrary_type': 'EggBox'},
         },
     ]
+
+
+def test_validate(mocker):
+    stub = mocker.stub(name='on_something_stub')
+
+    @validate_arguments
+    def func(s: str, count: int, *, separator: bytes = b''):
+        stub(s, count, separator)
+
+    func.validate('qwe', 2)
+    with pytest.raises(ValidationError):
+        func.validate(['qwe'], 2)
+
+    stub.assert_not_called()
diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py
index d1060c1fc..20977ce23 100644
--- a/tests/test_edge_cases.py
+++ b/tests/test_edge_cases.py
@@ -2,7 +2,7 @@ import sys
 from collections.abc import Hashable
 from decimal import Decimal
 from enum import Enum
-from typing import Any, Dict, FrozenSet, Generic, List, Optional, Set, Tuple, Type, TypeVar, Union
+from typing import Any, Dict, FrozenSet, Generic, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union
 
 import pytest
 
@@ -1122,8 +1122,11 @@ def test_type_on_annotation():
         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]
 
-    assert Model.__fields__.keys() == {'b', 'c', 'e', 'f'}
+    assert Model.__fields__.keys() == {'b', 'c', 'e', 'f', 'g', 'h', 'i'}
 
 
 def test_assign_type():
diff --git a/tests/test_generics.py b/tests/test_generics.py
index ecd39b8f3..07847e471 100644
--- a/tests/test_generics.py
+++ b/tests/test_generics.py
@@ -1,11 +1,11 @@
 import sys
 from enum import Enum
-from typing import Any, ClassVar, Dict, Generic, List, Optional, Tuple, Type, TypeVar, Union
+from typing import Any, Callable, ClassVar, Dict, Generic, List, Optional, Sequence, Tuple, Type, TypeVar, Union
 
 import pytest
 
 from pydantic import BaseModel, Field, ValidationError, root_validator, validator
-from pydantic.generics import GenericModel, _generic_types_cache
+from pydantic.generics import GenericModel, _generic_types_cache, iter_contained_typevars, replace_types
 
 skip_36 = pytest.mark.skipif(sys.version_info < (3, 7), reason='generics only supported for python 3.7 and above')
 
@@ -17,7 +17,10 @@ def test_generic_name():
     class Result(GenericModel, Generic[data_type]):
         data: data_type
 
-    assert Result[List[int]].__name__ == 'Result[typing.List[int]]'
+    if sys.version_info >= (3, 9):
+        assert Result[list[int]].__name__ == 'Result[list[int]]'
+    assert Result[List[int]].__name__ == 'Result[List[int]]'
+    assert Result[int].__name__ == 'Result[int]'
 
 
 @skip_36
@@ -248,35 +251,6 @@ def test_generic_config():
         result.data = 2
 
 
-@skip_36
-def test_deep_generic():
-    T = TypeVar('T')
-    S = TypeVar('S')
-    R = TypeVar('R')
-
-    class OuterModel(GenericModel, Generic[T, S, R]):
-        a: Dict[R, Optional[List[T]]]
-        b: Optional[Union[S, R]]
-        c: R
-        d: float
-
-    class InnerModel(GenericModel, Generic[T, R]):
-        c: T
-        d: R
-
-    class NormalModel(BaseModel):
-        e: int
-        f: str
-
-    inner_model = InnerModel[int, str]
-    generic_model = OuterModel[inner_model, NormalModel, int]
-
-    inner_models = [inner_model(c=1, d='a')]
-    generic_model(a={1: inner_models, 2: None}, b=None, c=1, d=1.5)
-    generic_model(a={}, b=NormalModel(e=1, f='a'), c=1, d=1.5)
-    generic_model(a={}, b=1, c=1, d=1.5)
-
-
 @skip_36
 def test_enum_generic():
     T = TypeVar('T')
@@ -498,6 +472,26 @@ def test_partial_specification():
     ]
 
 
+@skip_36
+def test_partial_specification_with_inner_typevar():
+    AT = TypeVar('AT')
+    BT = TypeVar('BT')
+
+    class Model(GenericModel, Generic[AT, BT]):
+        a: List[AT]
+        b: List[BT]
+
+    partial_model = Model[str, BT]
+    assert partial_model.__concrete__ is False
+    concrete_model = partial_model[int]
+    assert concrete_model.__concrete__ is True
+
+    # nested resolution of partial models should work as expected
+    nested_resolved = concrete_model(a=[123], b=['456'])
+    assert nested_resolved.a == ['123']
+    assert nested_resolved.b == [456]
+
+
 @skip_36
 def test_partial_specification_name():
     AT = TypeVar('AT')
@@ -681,7 +675,11 @@ def test_generic_model_from_function_pickle_fail(create_module):
 
 
 @skip_36
-def test_generic_model_redefined_without_cache_fail(create_module):
+def test_generic_model_redefined_without_cache_fail(create_module, monkeypatch):
+
+    # match identity checker otherwise we never get to the redefinition check
+    monkeypatch.setattr('pydantic.generics.all_identical', lambda left, right: False)
+
     @create_module
     def module():
         from typing import Generic, TypeVar
@@ -756,3 +754,264 @@ def test_get_caller_frame_info_when_sys_getframe_undefined():
         assert get_caller_frame_info() == (None, False)
     finally:  # just to make sure we always setting original attribute back
         sys._getframe = getframe
+
+
+@skip_36
+def test_iter_contained_typevars():
+    T = TypeVar('T')
+    T2 = TypeVar('T2')
+
+    class Model(GenericModel, Generic[T]):
+        a: T
+
+    assert list(iter_contained_typevars(Model[T])) == [T]
+    assert list(iter_contained_typevars(Optional[List[Union[str, Model[T]]]])) == [T]
+    assert list(iter_contained_typevars(Optional[List[Union[str, Model[int]]]])) == []
+    assert list(iter_contained_typevars(Optional[List[Union[str, Model[T], Callable[[T2, T], str]]]])) == [T, T2, T]
+
+
+@skip_36
+def test_nested_identity_parameterization():
+    T = TypeVar('T')
+    T2 = TypeVar('T2')
+
+    class Model(GenericModel, Generic[T]):
+        a: T
+
+    assert Model[T][T][T] is Model
+    assert Model[T] is Model
+    assert Model[T2] is not Model
+
+
+@skip_36
+def test_replace_types():
+    T = TypeVar('T')
+
+    class Model(GenericModel, Generic[T]):
+        a: T
+
+    assert replace_types(T, {T: int}) is int
+    assert replace_types(List[Union[str, list, T]], {T: int}) == List[Union[str, list, int]]
+    assert replace_types(Callable, {T: int}) == Callable
+    assert replace_types(Callable[[int, str, T], T], {T: int}) == Callable[[int, str, int], int]
+    assert replace_types(T, {}) is T
+    assert replace_types(Model[List[T]], {T: int}) == Model[List[T]][int]
+    assert replace_types(T, {}) is T
+    assert replace_types(Type[T], {T: int}) == Type[int]
+    assert replace_types(Model[T], {T: T}) == Model[T]
+
+    if sys.version_info >= (3, 9):
+        # Check generic aliases (subscripted builtin types) to make sure they
+        # resolve correctly (don't get translated to typing versions for
+        # example)
+        assert replace_types(list[Union[str, list, T]], {T: int}) == list[Union[str, list, int]]
+
+
+@skip_36
+def test_replace_types_identity_on_unchanged():
+    T = TypeVar('T')
+    U = TypeVar('U')
+
+    type_ = List[Union[str, Callable[[list], Optional[str]], U]]
+    assert replace_types(type_, {T: int}) is type_
+
+
+@skip_36
+def test_deep_generic():
+    T = TypeVar('T')
+    S = TypeVar('S')
+    R = TypeVar('R')
+
+    class OuterModel(GenericModel, Generic[T, S, R]):
+        a: Dict[R, Optional[List[T]]]
+        b: Optional[Union[S, R]]
+        c: R
+        d: float
+
+    class InnerModel(GenericModel, Generic[T, R]):
+        c: T
+        d: R
+
+    class NormalModel(BaseModel):
+        e: int
+        f: str
+
+    inner_model = InnerModel[int, str]
+    generic_model = OuterModel[inner_model, NormalModel, int]
+
+    inner_models = [inner_model(c=1, d='a')]
+    generic_model(a={1: inner_models, 2: None}, b=None, c=1, d=1.5)
+    generic_model(a={}, b=NormalModel(e=1, f='a'), c=1, d=1.5)
+    generic_model(a={}, b=1, c=1, d=1.5)
+
+    assert InnerModel.__concrete__ is False
+    assert inner_model.__concrete__ is True
+
+
+@skip_36
+def test_deep_generic_with_inner_typevar():
+    T = TypeVar('T')
+
+    class OuterModel(GenericModel, Generic[T]):
+        a: List[T]
+
+    class InnerModel(OuterModel[T], Generic[T]):
+        pass
+
+    assert InnerModel[int].__concrete__ is True
+    assert InnerModel.__concrete__ is False
+
+    with pytest.raises(ValidationError):
+        InnerModel[int](a=['wrong'])
+    assert InnerModel[int](a=['1']).a == [1]
+
+
+@skip_36
+def test_deep_generic_with_referenced_generic():
+    T = TypeVar('T')
+    R = TypeVar('R')
+
+    class ReferencedModel(GenericModel, Generic[R]):
+        a: R
+
+    class OuterModel(GenericModel, Generic[T]):
+        a: ReferencedModel[T]
+
+    class InnerModel(OuterModel[T], Generic[T]):
+        pass
+
+    assert InnerModel[int].__concrete__ is True
+    assert InnerModel.__concrete__ is False
+
+    with pytest.raises(ValidationError):
+        InnerModel[int](a={'a': 'wrong'})
+    assert InnerModel[int](a={'a': 1}).a.a == 1
+
+
+@skip_36
+def test_deep_generic_with_referenced_inner_generic():
+    T = TypeVar('T')
+
+    class ReferencedModel(GenericModel, Generic[T]):
+        a: T
+
+    class OuterModel(GenericModel, Generic[T]):
+        a: Optional[List[Union[ReferencedModel[T], str]]]
+
+    class InnerModel(OuterModel[T], Generic[T]):
+        pass
+
+    assert InnerModel[int].__concrete__ is True
+    assert InnerModel.__concrete__ is False
+
+    with pytest.raises(ValidationError):
+        InnerModel[int](a=['s', {'a': 'wrong'}])
+    assert InnerModel[int](a=['s', {'a': 1}]).a[1].a == 1
+
+    assert InnerModel[int].__fields__['a'].outer_type_ == List[Union[ReferencedModel[int], str]]
+    assert (InnerModel[int].__fields__['a'].sub_fields[0].sub_fields[0].outer_type_.__fields__['a'].outer_type_) == int
+
+
+@skip_36
+def test_deep_generic_with_multiple_typevars():
+    T = TypeVar('T')
+    U = TypeVar('U')
+
+    class OuterModel(GenericModel, Generic[T]):
+        data: List[T]
+
+    class InnerModel(OuterModel[T], Generic[U, T]):
+        extra: U
+
+    ConcreteInnerModel = InnerModel[int, float]
+    assert ConcreteInnerModel.__fields__['data'].outer_type_ == List[float]
+    assert ConcreteInnerModel.__fields__['extra'].outer_type_ == int
+
+    assert ConcreteInnerModel(data=['1'], extra='2').dict() == {'data': [1.0], 'extra': 2}
+
+
+@skip_36
+def test_deep_generic_with_multiple_inheritance():
+    K = TypeVar('K')
+    V = TypeVar('V')
+    T = TypeVar('T')
+
+    class OuterModelA(GenericModel, Generic[K, V]):
+        data: Dict[K, V]
+
+    class OuterModelB(GenericModel, Generic[T]):
+        stuff: List[T]
+
+    class InnerModel(OuterModelA[K, V], OuterModelB[T], Generic[K, V, T]):
+        extra: int
+
+    ConcreteInnerModel = InnerModel[int, float, str]
+
+    assert ConcreteInnerModel.__fields__['data'].outer_type_ == Dict[int, float]
+    assert ConcreteInnerModel.__fields__['stuff'].outer_type_ == List[str]
+    assert ConcreteInnerModel.__fields__['extra'].outer_type_ == int
+
+    ConcreteInnerModel(data={1.1: '5'}, stuff=[123], extra=5).dict() == {
+        'data': {1: 5},
+        'stuff': ['123'],
+        'extra': 5,
+    }
+
+
+@skip_36
+def test_generic_with_referenced_generic_type_1():
+    T = TypeVar('T')
+
+    class ModelWithType(GenericModel, Generic[T]):
+        # Type resolves to type origin of "type" which is non-subscriptible for
+        # python < 3.9 so we want to make sure it works for other versions
+        some_type: Type[T]
+
+    class ReferenceModel(GenericModel, Generic[T]):
+        abstract_base_with_type: ModelWithType[T]
+
+    ReferenceModel[int]
+
+
+@skip_36
+def test_generic_with_referenced_nested_typevar():
+    T = TypeVar('T')
+
+    class ModelWithType(GenericModel, Generic[T]):
+        # Type resolves to type origin of "collections.abc.Sequence" which is
+        # non-subscriptible for
+        # python < 3.9 so we want to make sure it works for other versions
+        some_type: Sequence[T]
+
+    class ReferenceModel(GenericModel, Generic[T]):
+        abstract_base_with_type: ModelWithType[T]
+
+    ReferenceModel[int]
+
+
+@skip_36
+def test_generic_with_callable():
+    T = TypeVar('T')
+
+    class Model(GenericModel, Generic[T]):
+        # Callable is a test for any type that accepts a list as an argument
+        some_callable: Callable[[Optional[int], T], None]
+
+    Model[str].__concrete__ is True
+    Model.__concrete__ is False
+
+
+@skip_36
+def test_generic_with_partial_callable():
+    T = TypeVar('T')
+    U = TypeVar('U')
+
+    class Model(GenericModel, Generic[T, U]):
+        t: T
+        u: U
+        # Callable is a test for any type that accepts a list as an argument
+        some_callable: Callable[[Optional[int], str], None]
+
+    Model[str, U].__concrete__ is False
+    Model[str, U].__parameters__ == [U]
+    Model[str, int].__concrete__ is False
diff --git a/tests/test_networks.py b/tests/test_networks.py
index c69ba0a78..79dfb41e6 100644
--- a/tests/test_networks.py
+++ b/tests/test_networks.py
@@ -190,6 +190,13 @@ def test_user_no_password():
     assert url.host == 'example.org'
 
 
+def test_user_info_no_user():
+    url = validate_url('http://:password@example.org')
+    assert url.user == ''
+    assert url.password == 'password'
+    assert url.host == 'example.org'
+
+
 def test_at_in_path():
     url = validate_url('https://twitter.com/@handle')
     assert url.scheme == 'https'
@@ -199,6 +206,15 @@ def test_at_in_path():
     assert url.path == '/@handle'
 
 
+def test_fragment_without_query():
+    url = validate_url('https://pydantic-docs.helpmanual.io/usage/types/#constrained-types')
+    assert url.scheme == 'https'
+    assert url.host == 'pydantic-docs.helpmanual.io'
+    assert url.path == '/usage/types/'
+    assert url.query is None
+    assert url.fragment == 'constrained-types'
+
+
 @pytest.mark.parametrize(
     'value',
     [
@@ -321,21 +337,35 @@ def test_redis_dsns():
     class Model(BaseModel):
         a: RedisDsn
 
-    m = Model(a='redis://user:pass@localhost:5432/app')
-    assert m.a == 'redis://user:pass@localhost:5432/app'
+    m = Model(a='redis://user:pass@localhost:1234/app')
+    assert m.a == 'redis://user:pass@localhost:1234/app'
     assert m.a.user == 'user'
     assert m.a.password == 'pass'
 
+    m = Model(a='rediss://user:pass@localhost:1234/app')
+    assert m.a == 'rediss://user:pass@localhost:1234/app'
+
+    m = Model(a='rediss://:pass@localhost:1234')
+    assert m.a == 'rediss://:pass@localhost:1234/0'
+
     with pytest.raises(ValidationError) as exc_info:
         Model(a='http://example.org')
     assert exc_info.value.errors()[0]['type'] == 'value_error.url.scheme'
 
-    # password is not required for redis
-    m = Model(a='redis://localhost:5432/app')
-    assert m.a == 'redis://localhost:5432/app'
+    # Password is not required for Redis protocol
+    m = Model(a='redis://localhost:1234/app')
+    assert m.a == 'redis://localhost:1234/app'
     assert m.a.user is None
     assert m.a.password is None
 
+    # Only schema is required for Redis protocol. Otherwise it will be set to default
+    # https://www.iana.org/assignments/uri-schemes/prov/redis
+    m = Model(a='rediss://')
+    assert m.a.scheme == 'rediss'
+    assert m.a.host == 'localhost'
+    assert m.a.port == '6379'
+    assert m.a.path == '/0'
+
 
 def test_custom_schemes():
     class Model(BaseModel):
diff --git a/tests/test_types.py b/tests/test_types.py
index 457817c8f..672072e10 100644
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -69,17 +69,13 @@ from pydantic import (
     errors,
     validator,
 )
+from pydantic.typing import Literal, NoneType
 
 try:
     import email_validator
 except ImportError:
     email_validator = None
 
-try:
-    import typing_extensions
-except ImportError:
-    typing_extensions = None
-
 
 class ConBytesModel(BaseModel):
     v: conbytes(max_length=10) = b'foobar'
@@ -2396,10 +2392,10 @@ def test_generic_without_params_error():
     ]
 
 
-@pytest.mark.skipif(not typing_extensions, reason='typing_extensions not installed')
+@pytest.mark.skipif(not Literal, reason='typing_extensions not installed')
 def test_literal_single():
     class Model(BaseModel):
-        a: typing_extensions.Literal['a']
+        a: Literal['a']
 
     Model(a='a')
     with pytest.raises(ValidationError) as exc_info:
@@ -2414,10 +2410,10 @@ def test_literal_single():
     ]
 
 
-@pytest.mark.skipif(not typing_extensions, reason='typing_extensions not installed')
+@pytest.mark.skipif(not Literal, reason='typing_extensions not installed')
 def test_literal_multiple():
     class Model(BaseModel):
-        a_or_b: typing_extensions.Literal['a', 'b']
+        a_or_b: Literal['a', 'b']
 
     Model(a_or_b='a')
     Model(a_or_b='b')
@@ -2609,3 +2605,59 @@ def test_deque_json():
         v: Deque[int]
 
     assert Model(v=deque((1, 2, 3))).json() == '{"v": [1, 2, 3]}'
+
+
+none_value_type_cases = (None, type(None), NoneType)
+if Literal:
+    none_value_type_cases += (Literal[None],)
+
+
+@pytest.mark.parametrize('value_type', none_value_type_cases)
+def test_none(value_type):
+    class Model(BaseModel):
+        my_none: value_type
+        my_none_list: List[value_type]
+        my_none_dict: Dict[str, value_type]
+        my_json_none: Json[value_type]
+
+    Model(
+        my_none=None,
+        my_none_list=[None] * 3,
+        my_none_dict={'a': None, 'b': None},
+        my_json_none='null',
+    )
+
+    assert Model.schema() == {
+        'title': 'Model',
+        'type': 'object',
+        'properties': {
+            'my_none': {'title': 'My None', 'type': 'null'},
+            'my_none_list': {
+                'title': 'My None List',
+                'type': 'array',
+                'items': {'type': 'null'},
+            },
+            'my_none_dict': {
+                'title': 'My None Dict',
+                'type': 'object',
+                'additionalProperties': {'type': 'null'},
+            },
+            'my_json_none': {'title': 'My Json None', 'type': 'null'},
+        },
+        'required': ['my_none', 'my_none_list', 'my_none_dict', 'my_json_none'],
+    }
+
+    with pytest.raises(ValidationError) as exc_info:
+        Model(
+            my_none='qwe',
+            my_none_list=[1, None, 'qwe'],
+            my_none_dict={'a': 1, 'b': None},
+            my_json_none='"a"',
+        )
+    assert exc_info.value.errors() == [
+        {'loc': ('my_none',), 'msg': 'value is not None', 'type': 'type_error.not_none'},
+        {'loc': ('my_none_list', 0), 'msg': 'value is not None', 'type': 'type_error.not_none'},
+        {'loc': ('my_none_list', 2), 'msg': 'value is not None', 'type': 'type_error.not_none'},
+        {'loc': ('my_none_dict', 'a'), 'msg': 'value is not None', 'type': 'type_error.not_none'},
+        {'loc': ('my_json_none',), 'msg': 'value is not None', 'type': 'type_error.not_none'},
+    ]
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 8dd736c38..1850c0f73 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -27,6 +27,7 @@ from pydantic.utils import (
     BUILTIN_COLLECTIONS,
     ClassAttribute,
     ValueItems,
+    all_identical,
     deep_update,
     get_model,
     import_string,
@@ -60,11 +61,18 @@ def test_import_no_attr():
     assert exc_info.value.args[0] == 'Module "os" does not define a "foobar" attribute'
 
 
-@pytest.mark.parametrize('value,expected', ((str, 'str'), ('string', 'str'), (Union[str, int], 'Union[str, int]')))
+@pytest.mark.parametrize(
+    'value,expected', ((str, 'str'), ('string', 'str'), (Union[str, int], 'Union[str, int]'), (list, 'list'))
+)
 def test_display_as_type(value, expected):
     assert display_as_type(value) == expected
 
 
+@pytest.mark.skipif(sys.version_info < (3, 9), reason='generic aliases are not available in python < 3.9')
+def test_display_as_type_generic_alias():
+    assert display_as_type(list[[Union[str, int]]]) == 'list[[Union[str, int]]]'
+
+
 def test_display_as_type_enum():
     class SubField(Enum):
         a = 1
@@ -446,3 +454,18 @@ def test_resolve_annotations_no_module():
     # TODO: is there a better test for this, can this case really happen?
     fr = ForwardRef('Foo')
     assert resolve_annotations({'Foo': ForwardRef('Foo')}, None) == {'Foo': fr}
+
+
+def test_all_identical():
+    a, b = object(), object()
+    c = [b]
+    assert all_identical([a, b], [a, b]) is True
+    assert all_identical([a, b], [a, b]) is True
+    assert all_identical([a, b, b], [a, b, b]) is True
+    assert all_identical([a, c, b], [a, c, b]) is True
+
+    assert all_identical([], [a]) is False, 'Expected iterables with different lengths to evaluate to `False`'
+    assert all_identical([a], []) is False, 'Expected iterables with different lengths to evaluate to `False`'
+    assert (
+        all_identical([a, [b], b], [a, [b], b]) is False
+    ), 'New list objects are different objects and should therefor not be identical.'
diff --git a/tests/test_validators.py b/tests/test_validators.py
index 62a68b031..a77b8da02 100644
--- a/tests/test_validators.py
+++ b/tests/test_validators.py
@@ -1,5 +1,6 @@
 from collections import deque
 from datetime import datetime
+from enum import Enum
 from itertools import product
 from typing import Dict, List, Optional, Tuple
 
@@ -1135,6 +1136,28 @@ def test_literal_validator():
     ]
 
 
+@pytest.mark.skipif(not Literal, reason='typing_extensions not installed')
+def test_literal_validator_str_enum():
+    class Bar(str, Enum):
+        FIZ = 'fiz'
+        FUZ = 'fuz'
+
+    class Foo(BaseModel):
+        bar: Bar
+        barfiz: Literal[Bar.FIZ]
+        fizfuz: Literal[Bar.FIZ, Bar.FUZ]
+
+    my_foo = Foo.parse_obj({'bar': 'fiz', 'barfiz': 'fiz', 'fizfuz': 'fiz'})
+    assert my_foo.bar is Bar.FIZ
+    assert my_foo.barfiz is Bar.FIZ
+    assert my_foo.fizfuz is Bar.FIZ
+
+    my_foo = Foo.parse_obj({'bar': 'fiz', 'barfiz': 'fiz', 'fizfuz': 'fuz'})
+    assert my_foo.bar is Bar.FIZ
+    assert my_foo.barfiz is Bar.FIZ
+    assert my_foo.fizfuz is Bar.FUZ
+
+
 @pytest.mark.skipif(not Literal, reason='typing_extensions not installed')
 def test_nested_literal_validator():
     L1 = Literal['foo']