Repository: samuelcolvin/pydantic (python)

Issue:
Nested Models with optional members fail on 1.6.1
# Bug

Defining a model with Optional nested recursive submodels is throwing a required value error when validating the model.  The nested models also contain nested submodels that container other recursive models potentially.  This works on 1.6.0 and all versions prior that we have been using the last year. It only breaks when we move to version 1.6.1

Here is a sample of the model that we are using with basic one letter named attributes.
```
class Spec(BaseModel):
    spec_fields: List[str] = Field(..., alias="fields")
    filter: Optional[str]
    sort: Optional[str]


class PSpec(Spec):
    g: Optional[GSpec]

class GSpec(Spec):
    p: Optional[PSpec]

class Filter(BaseModel):
    g: Optional[GSpec]
    p: Optional[PSpec]
```

Trying with the following values will generate an error
```
        data = {"p": {"sort": "some_field:asc", "fields": []}}
        result = Filter(**data)
```
The error generated is:
```
E   pydantic.error_wrappers.ValidationError: 1 validation error for Filter
E   p -> g
E     field required (type=value_error.missing)
```

It seems to be disregarding the Optional attribute for GSpec field in the nested PSpec model.
Same code on 1.6.0 works as expected.

Thanks!





Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
pydantic version: 1.6.1
            pydantic compiled: True
                 install path: /Users/..../.local/share/virtualenvs/..../lib/python3.7/site-packages/pydantic
               python version: 3.7.3 (default, Mar 27 2019, 09:23:15)  [Clang 10.0.1 (clang-1001.0.46.3)]
                     platform: Darwin-19.5.0-x86_64-i386-64bit
     optional deps. installed: ['typing-extensions', 'email-validator']
```
<!-- or if you're using pydantic prior to v1.3, manually include: OS, python version and pydantic version -->

<!-- Please read the [docs](https://pydantic-docs.helpmanual.io/) and search through issues to
confirm your bug hasn't already been reported. -->

<!-- Where possible please include a self-contained code snippet describing your bug: -->

```py
import pydantic

...
```


Pull request:
fix: forward ref with nested models and optional fields
## Change Summary
PR #1712 introduced a regression for forward refs in `ModelField.prepare`
as it would not return early for forward refs anymore.
Optional fields would hence have `required` set to `True`.

## Related issue number
closes #1736

## 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/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md
index 838581180..b87a10e70 100644
--- a/.github/ISSUE_TEMPLATE/bug.md
+++ b/.github/ISSUE_TEMPLATE/bug.md
@@ -4,6 +4,14 @@ about: Create a bug report to help us improve pydantic
 labels: bug
 ---
 
+### Checks
+
+* [ ] I added a descriptive title to this issue
+* [ ] I have searched (google, github) for similar issues and couldn't find anything
+* [ ] I have read and followed [the docs](https://pydantic-docs.helpmanual.io/) and still think this is a bug
+
+<!-- Sorry to sound so draconian, but every second saved replying to issues is time spend improving pydantic :-) -->
+
 # Bug
 
 Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index c643fafaf..5a43008e5 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -4,6 +4,18 @@ about: Suggest a new feature or change to pydantic
 labels: feature request
 ---
 
+### Checks
+
+* [ ] I added a descriptive title to this issue
+* [ ] I have searched (google, github) for similar issues and couldn't find anything
+* [ ] I have read and followed [the docs](https://pydantic-docs.helpmanual.io/) and still think this feature/change is needed
+* [ ] After submitting this, I commit to one of:
+    * Look through open issues and helped at least one other person
+    * Hit the "watch" button on this repo to receive notifications and I commit to help at least 2 people that ask questions in the future
+    * Implement a Pull Request for a confirmed bug
+
+<!-- Sorry to sound so draconian, but every second saved replying to issues is time spend improving pydantic :-) -->
+
 # Feature Request
 
 Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md
index 5618c8b8c..d30d3268c 100644
--- a/.github/ISSUE_TEMPLATE/question.md
+++ b/.github/ISSUE_TEMPLATE/question.md
@@ -4,6 +4,18 @@ about: Ask a question about how to use pydantic
 labels: question
 ---
 
+### Checks
+
+* [ ] I added a descriptive title to this issue
+* [ ] I have searched (google, github) for similar issues and couldn't find anything
+* [ ] I have read and followed [the docs](https://pydantic-docs.helpmanual.io/) and couldn't find an answer
+* [ ] After submitting this, I commit to one of:
+    * Look through open issues and helped at least one other person
+    * Hit the "watch" button on this repo to receive notifications and I commit to help at least 2 people that ask questions in the future
+    * Implement a Pull Request for a confirmed bug
+
+<!-- Sorry to sound so draconian, but every second saved replying to issues is time spend improving pydantic :-) -->
+
 # Question
 
 Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index ff228e0dd..3acffa71c 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -4,19 +4,9 @@ updates:
 - package-ecosystem: pip
   directory: /
   schedule:
-    interval: daily
+    interval: weekly
 
-- package-ecosystem: pip
-  directory: /tests
-  schedule:
-    interval: monthly
-
-- package-ecosystem: pip
-  directory: /docs
-  schedule:
-    interval: monthly
-
-- package-ecosystem: pip
-  directory: /benchmarks
+- package-ecosystem: github-actions
+  directory: /
   schedule:
     interval: monthly
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 22aa1bc72..c306821c6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -24,7 +24,7 @@ jobs:
     - uses: actions/checkout@v2
 
     - name: set up python
-      uses: actions/setup-python@v1
+      uses: actions/setup-python@v2
       with:
         python-version: ${{ matrix.python-version }}
 
@@ -56,7 +56,7 @@ jobs:
       run: make test
 
     - run: coverage xml
-    - uses: codecov/codecov-action@v1.0.10
+    - uses: codecov/codecov-action@v1.0.13
       with:
         file: ./coverage.xml
         env_vars: COMPILED,DEPS,PYTHON,OS
@@ -71,7 +71,7 @@ jobs:
       run: make test
 
     - run: coverage xml
-    - uses: codecov/codecov-action@v1.0.10
+    - uses: codecov/codecov-action@v1.0.13
       with:
         file: ./coverage.xml
         env_vars: COMPILED,DEPS,PYTHON,OS
@@ -89,7 +89,7 @@ jobs:
       run: make test
 
     - run: coverage xml
-    - uses: codecov/codecov-action@v1.0.10
+    - uses: codecov/codecov-action@v1.0.13
       with:
         file: ./coverage.xml
         env_vars: COMPILED,DEPS,PYTHON,OS
@@ -115,7 +115,7 @@ jobs:
     - uses: actions/checkout@v2
 
     - name: set up python
-      uses: actions/setup-python@v1
+      uses: actions/setup-python@v2
       with:
         python-version: ${{ matrix.python-version }}
 
@@ -133,7 +133,7 @@ jobs:
 
     - run: coverage xml
 
-    - uses: codecov/codecov-action@v1.0.10
+    - uses: codecov/codecov-action@v1.0.13
       with:
         file: ./coverage.xml
         env_vars: COMPILED,DEPS,PYTHON,OS
@@ -145,7 +145,7 @@ jobs:
     - uses: actions/checkout@v2
 
     - name: set up python
-      uses: actions/setup-python@v1
+      uses: actions/setup-python@v2
       with:
         python-version: '3.7'
 
@@ -161,7 +161,7 @@ jobs:
     - uses: actions/checkout@v2
 
     - name: set up python
-      uses: actions/setup-python@v1
+      uses: actions/setup-python@v2
       with:
         python-version: '3.7'
 
@@ -201,7 +201,7 @@ jobs:
     - uses: actions/checkout@v2
 
     - name: set up python
-      uses: actions/setup-python@v1
+      uses: actions/setup-python@v2
       with:
         python-version: '3.8'
 
@@ -237,7 +237,7 @@ jobs:
         twine check dist/*
 
     - name: Store dist artifacts
-      uses: actions/upload-artifact@v1
+      uses: actions/upload-artifact@v2
       with:
         name: pypi_files
         path: dist
@@ -252,12 +252,12 @@ jobs:
       - uses: actions/checkout@v2
 
       - name: set up python
-        uses: actions/setup-python@v1
+        uses: actions/setup-python@v2
         with:
           python-version: '3.8'
 
       - name: get dist artifacts
-        uses: actions/download-artifact@v1
+        uses: actions/download-artifact@v2
         with:
           name: pypi_files
 
diff --git a/changes/1561-ojomio.md b/changes/1561-ojomio.md
new file mode 100644
index 000000000..dcc9ded35
--- /dev/null
+++ b/changes/1561-ojomio.md
@@ -0,0 +1,2 @@
+Allow descendant Settings models to override env variable names for the fields defined in parent Settings models with `env` in their `Config`.
+Previously only `env_prefix` configuration option was applicable
diff --git a/changes/1663-samuelcolvin.md b/changes/1663-samuelcolvin.md
new file mode 100644
index 000000000..1b3fec980
--- /dev/null
+++ b/changes/1663-samuelcolvin.md
@@ -0,0 +1,1 @@
+add `config` to `@validate_arguments`
diff --git a/changes/1736-PrettyWood.md b/changes/1736-PrettyWood.md
new file mode 100644
index 000000000..64a22abfc
--- /dev/null
+++ b/changes/1736-PrettyWood.md
@@ -0,0 +1,1 @@
+Fix behaviour with forward refs and optional fields in nested models
\ No newline at end of file
diff --git a/docs/examples/validation_decorator_config.py b/docs/examples/validation_decorator_config.py
new file mode 100644
index 000000000..b25c7f956
--- /dev/null
+++ b/docs/examples/validation_decorator_config.py
@@ -0,0 +1,26 @@
+from pydantic import ValidationError, validate_arguments
+
+
+class Foobar:
+    def __init__(self, v: str):
+        self.v = v
+
+    def __add__(self, other: 'Foobar') -> str:
+        return f'{self} + {other}'
+
+    def __str__(self) -> str:
+        return f'Foobar({self.v})'
+
+
+@validate_arguments(config=dict(arbitrary_types_allowed=True))
+def add_foobars(a: Foobar, b: Foobar):
+    return a + b
+
+
+c = add_foobars(Foobar('a'), Foobar('b'))
+print(c)
+
+try:
+    add_foobars(1, 2)
+except ValidationError as e:
+    print(e)
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 88883f990..b7ed3b193 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -2,8 +2,8 @@ ansi2html==1.5.2
 mkdocs==1.1.2
 markdown==3.2.2
 mkdocs-exclude==1.0.2
-mkdocs-material==5.4.0
-markdown-include==0.5.1
+mkdocs-material==5.5.12
+markdown-include==0.6.0
 pygments==2.6.1
 sqlalchemy   # pyup: ignore
 orjson       # pyup: ignore
diff --git a/docs/usage/validation_decorator.md b/docs/usage/validation_decorator.md
index 9012134f8..3161d0719 100644
--- a/docs/usage/validation_decorator.md
+++ b/docs/usage/validation_decorator.md
@@ -78,8 +78,23 @@ _(This script is complete, it should run "as is")_
 ```py
 {!.tmp_examples/validation_decorator_async.py!}
 ```
-_(This script is complete, it should run "as is")_
 
+## Custom Config
+
+The model behind `validate_arguments` can be customised using a config setting which is equivalent to 
+setting the `Config` sub-class in normal models.
+
+!!! warning
+    The `fields` and `alias_generator` properties of `Config` which allow aliases to be configured are not supported
+    yet with `@validate_arguments`, using them will raise an error.
+    
+Configuration is set using the `config` keyword argument to the decorator, it may be either a config class
+or a dict of properties which are converted to a class later.
+
+```py
+{!.tmp_examples/validation_decorator_config.py!}
+```
+_(This script is complete, it should run "as is")_
 
 ## Limitations
 
@@ -126,7 +141,9 @@ in future.
 
 ### Config and Validators
 
-Custom [`Config`](model_config.md) and [validators](validators.md) are not yet supported.
+`fields` and `alias_generator` on custom [`Config`](model_config.md) are not supported, see [above](#custom-config).
+ 
+Neither are [validators](validators.md).
 
 ### Model fields and reserved arguments
 
diff --git a/pydantic/decorator.py b/pydantic/decorator.py
index 133326ca1..589baaaca 100644
--- a/pydantic/decorator.py
+++ b/pydantic/decorator.py
@@ -1,5 +1,5 @@
 from functools import wraps
-from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Tuple, TypeVar, cast, get_type_hints
+from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Tuple, Type, TypeVar, Union, cast, get_type_hints
 
 from . import validator
 from .errors import ConfigError
@@ -12,22 +12,30 @@ if TYPE_CHECKING:
     from .typing import AnyCallable
 
     Callable = TypeVar('Callable', bound=AnyCallable)
+    ConfigType = Union[None, Type[Any], Dict[str, Any]]
 
 
-def validate_arguments(function: 'Callable') -> 'Callable':
+def validate_arguments(func: 'Callable' = None, *, config: 'ConfigType' = None) -> 'Callable':
     """
     Decorator to validate the arguments passed to a function.
     """
-    vd = ValidatedFunction(function)
 
-    @wraps(function)
-    def wrapper_function(*args: Any, **kwargs: Any) -> Any:
-        return vd.call(*args, **kwargs)
+    def validate(_func: 'Callable') -> 'Callable':
+        vd = ValidatedFunction(_func, config)
 
-    wrapper_function.vd = vd  # type: ignore
-    wrapper_function.raw_function = vd.raw_function  # type: ignore
-    wrapper_function.model = vd.model  # type: ignore
-    return cast('Callable', wrapper_function)
+        @wraps(_func)
+        def wrapper_function(*args: Any, **kwargs: Any) -> Any:
+            return vd.call(*args, **kwargs)
+
+        wrapper_function.vd = vd  # type: ignore
+        wrapper_function.raw_function = vd.raw_function  # type: ignore
+        wrapper_function.model = vd.model  # type: ignore
+        return cast('Callable', wrapper_function)
+
+    if func:
+        return validate(func)
+    else:
+        return cast('Callable', validate)
 
 
 ALT_V_ARGS = 'v__args'
@@ -36,7 +44,7 @@ V_POSITIONAL_ONLY_NAME = 'v__positional_only'
 
 
 class ValidatedFunction:
-    def __init__(self, function: 'Callable'):
+    def __init__(self, function: 'Callable', config: 'ConfigType'):  # noqa C901
         from inspect import Parameter, signature
 
         parameters: Mapping[str, Parameter] = signature(function).parameters
@@ -100,7 +108,7 @@ class ValidatedFunction:
             # same with kwargs
             fields[self.v_kwargs_name] = Dict[Any, Any], None
 
-        self.create_model(fields, takes_args, takes_kwargs)
+        self.create_model(fields, takes_args, takes_kwargs, config)
 
     def call(self, *args: Any, **kwargs: Any) -> Any:
         values = self.build_values(args, kwargs)
@@ -170,9 +178,24 @@ class ValidatedFunction:
         else:
             return self.raw_function(**d)
 
-    def create_model(self, fields: Dict[str, Any], takes_args: bool, takes_kwargs: bool) -> None:
+    def create_model(self, fields: Dict[str, Any], takes_args: bool, takes_kwargs: bool, config: 'ConfigType') -> None:
         pos_args = len(self.arg_mapping)
 
+        class CustomConfig:
+            pass
+
+        if not TYPE_CHECKING:  # pragma: no branch
+            if isinstance(config, dict):
+                CustomConfig = type('Config', (), config)  # noqa: F811
+            elif config is not None:
+                CustomConfig = config  # noqa: F811
+
+        if hasattr(CustomConfig, 'fields') or hasattr(CustomConfig, 'alias_generator'):
+            raise ConfigError(
+                'Setting the "fields" and "alias_generator" property on custom Config for '
+                '@validate_arguments is not yet supported, please remove.'
+            )
+
         class DecoratorBaseModel(BaseModel):
             @validator(self.v_args_name, check_fields=False, allow_reuse=True)
             def check_args(cls, v: List[Any]) -> List[Any]:
@@ -196,7 +219,7 @@ class ValidatedFunction:
                 keys = ', '.join(map(repr, v))
                 raise TypeError(f'positional-only argument{plural} passed as keyword argument{plural}: {keys}')
 
-            class Config:
+            class Config(CustomConfig):
                 extra = Extra.forbid
 
         self.model = create_model(to_camel(self.raw_function.__name__), __base__=DecoratorBaseModel, **fields)
diff --git a/pydantic/env_settings.py b/pydantic/env_settings.py
index 503ff4ed3..59ed44b0d 100644
--- a/pydantic/env_settings.py
+++ b/pydantic/env_settings.py
@@ -4,7 +4,7 @@ from pathlib import Path
 from typing import AbstractSet, Any, Dict, List, Mapping, Optional, Union
 
 from .fields import ModelField
-from .main import BaseModel, Extra
+from .main import BaseConfig, BaseModel, Extra
 from .typing import display_as_type
 from .utils import deep_update, sequence_like
 
@@ -85,7 +85,7 @@ class BaseSettings(BaseModel):
             d[field.alias] = env_val
         return d
 
-    class Config:
+    class Config(BaseConfig):
         env_prefix = ''
         env_file = None
         env_file_encoding = None
@@ -97,7 +97,9 @@ class BaseSettings(BaseModel):
         @classmethod
         def prepare_field(cls, field: ModelField) -> None:
             env_names: Union[List[str], AbstractSet[str]]
-            env = field.field_info.extra.get('env')
+            field_info_from_config = cls.get_field_info(field.name)
+
+            env = field_info_from_config.get('env') or field.field_info.extra.get('env')
             if env is None:
                 if field.has_alias:
                     warnings.warn(
diff --git a/pydantic/fields.py b/pydantic/fields.py
index 01792b628..c162616d7 100644
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -1,6 +1,5 @@
 import warnings
 from collections.abc import Iterable as CollectionsIterable
-from copy import deepcopy
 from typing import (
     TYPE_CHECKING,
     Any,
@@ -36,7 +35,7 @@ from .typing import (
     is_new_type,
     new_type_supertype,
 )
-from .utils import PyObjectStr, Representation, lenient_issubclass, sequence_like
+from .utils import PyObjectStr, Representation, lenient_issubclass, sequence_like, smart_deepcopy
 from .validators import constant_validator, dict_validator, find_validators, validate_json
 
 Required: Any = Ellipsis
@@ -271,14 +270,7 @@ class ModelField(Representation):
         self.prepare()
 
     def get_default(self) -> Any:
-        if self.default_factory is not None:
-            value = self.default_factory()
-        elif self.default is None:
-            # deepcopy is quite slow on None
-            value = None
-        else:
-            value = deepcopy(self.default)
-        return value
+        return smart_deepcopy(self.default) if self.default_factory is None else self.default_factory()
 
     @classmethod
     def infer(
@@ -342,6 +334,11 @@ class ModelField(Representation):
         """
 
         self._set_default_and_type()
+        if self.type_.__class__ == ForwardRef:
+            # self.type_ is currently a ForwardRef and there's nothing we can do now,
+            # user will need to call model.update_forward_refs()
+            return
+
         self._type_analysis()
         if self.required is Undefined:
             self.required = True
@@ -374,11 +371,6 @@ class ModelField(Representation):
         if self.type_ is None:
             raise errors_.ConfigError(f'unable to infer type for attribute "{self.name}"')
 
-        if self.type_.__class__ == ForwardRef:
-            # self.type_ is currently a ForwardRef and there's nothing we can do now,
-            # user will need to call model.update_forward_refs()
-            return
-
         if self.required is False and default_value is None:
             self.allow_none = True
 
diff --git a/pydantic/main.py b/pydantic/main.py
index c872f1e3b..c6c486ada 100644
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -42,6 +42,7 @@ from .utils import (
     generate_model_signature,
     lenient_issubclass,
     sequence_like,
+    smart_deepcopy,
     unique_list,
     validate_field_name,
 )
@@ -219,7 +220,7 @@ class ModelMetaclass(ABCMeta):
         pre_root_validators, post_root_validators = [], []
         for base in reversed(bases):
             if _is_base_model_class_defined and issubclass(base, BaseModel) and base != BaseModel:
-                fields.update(deepcopy(base.__fields__))
+                fields.update(smart_deepcopy(base.__fields__))
                 config = inherit_config(base.__config__, config)
                 validators = inherit_validators(base.__validators__, validators)
                 pre_root_validators += base.__pre_root_validators__
@@ -527,7 +528,7 @@ class BaseModel(Representation, metaclass=ModelMetaclass):
         Default values are respected, but no other validation is performed.
         """
         m = cls.__new__(cls)
-        object.__setattr__(m, '__dict__', {**deepcopy(cls.__field_defaults__), **values})
+        object.__setattr__(m, '__dict__', {**smart_deepcopy(cls.__field_defaults__), **values})
         if _fields_set is None:
             _fields_set = set(values.keys())
         object.__setattr__(m, '__fields_set__', _fields_set)
@@ -558,6 +559,7 @@ class BaseModel(Representation, metaclass=ModelMetaclass):
         )
 
         if deep:
+            # chances of having empty dict here are quite low for using smart_deepcopy
             v = deepcopy(v)
 
         cls = self.__class__
diff --git a/pydantic/utils.py b/pydantic/utils.py
index d692f4f53..42a4d30e6 100644
--- a/pydantic/utils.py
+++ b/pydantic/utils.py
@@ -1,6 +1,9 @@
 import warnings
+import weakref
+from collections import OrderedDict, defaultdict, deque
+from copy import deepcopy
 from itertools import islice
-from types import GeneratorType
+from types import BuiltinFunctionType, CodeType, FunctionType, GeneratorType, LambdaType, ModuleType
 from typing import (
     TYPE_CHECKING,
     AbstractSet,
@@ -20,7 +23,7 @@ from typing import (
     no_type_check,
 )
 
-from .typing import display_as_type
+from .typing import NoneType, display_as_type
 from .version import version_info
 
 if TYPE_CHECKING:
@@ -50,6 +53,41 @@ __all__ = (
     'ClassAttribute',
 )
 
+# these are types that are returned unchanged by deepcopy
+IMMUTABLE_NON_COLLECTIONS_TYPES: Set[Type[Any]] = {
+    int,
+    float,
+    complex,
+    str,
+    bool,
+    bytes,
+    type,
+    NoneType,
+    FunctionType,
+    BuiltinFunctionType,
+    LambdaType,
+    weakref.ref,
+    CodeType,
+    # note: including ModuleType will differ from behaviour of deepcopy by not producing error.
+    # It might be not a good idea in general, but considering that this function used only internally
+    # against default values of fields, this will allow to actually have a field with module as default value
+    ModuleType,
+    NotImplemented.__class__,
+    Ellipsis.__class__,
+}
+
+# these are types that if empty, might be copied with simple copy() instead of deepcopy()
+BUILTIN_COLLECTIONS: Set[Type[Any]] = {
+    list,
+    set,
+    tuple,
+    frozenset,
+    dict,
+    OrderedDict,
+    defaultdict,
+    deque,
+}
+
 
 def import_string(dotted_path: str) -> Any:
     """
@@ -241,7 +279,8 @@ def unique_list(input_list: Union[List[T], Tuple[T, ...]]) -> List[T]:
 
 
 def update_normalized_all(
-    item: Union['AbstractSetIntStr', 'MappingIntStrAny'], all_items: Union['AbstractSetIntStr', 'MappingIntStrAny'],
+    item: Union['AbstractSetIntStr', 'MappingIntStrAny'],
+    all_items: Union['AbstractSetIntStr', 'MappingIntStrAny'],
 ) -> Union['AbstractSetIntStr', 'MappingIntStrAny']:
     """
     Update item based on what all items contains.
@@ -533,3 +572,22 @@ class ClassAttribute:
         if instance is None:
             return self.value
         raise AttributeError(f'{self.name!r} attribute of {owner.__name__!r} is class-only')
+
+
+Obj = TypeVar('Obj')
+
+
+def smart_deepcopy(obj: Obj) -> Obj:
+    """
+    Return type as is for immutable built-in types
+    Use obj.copy() for built-in empty collections
+    Use copy.deepcopy() for non-empty collections and unknown objects
+    """
+
+    obj_type = obj.__class__
+    if obj_type in IMMUTABLE_NON_COLLECTIONS_TYPES:
+        return obj  # fastest case: obj is immutable and not collection therefore will not be copied anyway
+    elif not obj and obj_type in BUILTIN_COLLECTIONS:
+        # faster way for empty collections, no need to copy its members
+        return obj if obj_type is tuple else obj.copy()  # type: ignore  # tuple doesn't have copy method
+    return deepcopy(obj)  # slowest way when we actually might need a deepcopy
diff --git a/requirements.txt b/requirements.txt
index 349378ede..006d6e2bc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,7 +4,7 @@
 -r tests/requirements.txt
 
 Cython==0.29.21;sys_platform!='win32'
-devtools==0.5.1
+devtools==0.6.0
 email-validator==1.1.1
 dataclasses==0.6; python_version < '3.7'
 typing-extensions==3.7.4.1; python_version < '3.8'
diff --git a/setup.cfg b/setup.cfg
index 621c2b2ed..732c297e2 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,5 @@
 [tool:pytest]
 testpaths = tests
-timeout = 10
 filterwarnings =
     error
     ignore::DeprecationWarning:distutils


Test patch:
diff --git a/tests/requirements.txt b/tests/requirements.txt
index 9d619c769..b7f098bb9 100644
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -1,13 +1,13 @@
-black==19.10b0
-coverage==5.2
+black==20.8b1
+coverage==5.2.1
 flake8==3.8.3
 flake8-quotes==3.2.0
-isort==5.1.4
+isort==5.5.1
 mypy==0.782
 pycodestyle==2.6.0
 pyflakes==2.2.0
-pytest==5.4.3
-pytest-cov==2.10.0
-pytest-mock==3.2.0
+pytest==6.0.1
+pytest-cov==2.10.1
+pytest-mock==3.3.1
 pytest-sugar==0.9.4
 twine==3.2.0
diff --git a/tests/test_decorator.py b/tests/test_decorator.py
index 472796177..75928f27f 100644
--- a/tests/test_decorator.py
+++ b/tests/test_decorator.py
@@ -70,6 +70,7 @@ def test_wrap():
     assert issubclass(foo_bar.model, BaseModel)
     assert foo_bar.model.__fields__.keys() == {'a', 'b', 'args', 'kwargs'}
     assert foo_bar.model.__name__ == 'FooBar'
+    assert foo_bar.model.schema()['title'] == 'FooBar'
     # signature is slightly different on 3.6
     if sys.version_info >= (3, 7):
         assert repr(inspect.signature(foo_bar)) == '<Signature (a: int, b: int)>'
@@ -262,3 +263,57 @@ def test_class_method():
         {'loc': ('a',), 'msg': 'field required', 'type': 'value_error.missing'},
         {'loc': ('b',), 'msg': 'field required', 'type': 'value_error.missing'},
     ]
+
+
+def test_config_title():
+    @validate_arguments(config=dict(title='Testing'))
+    def foo(a: int, b: int):
+        return f'{a}, {b}'
+
+    assert foo(1, 2) == '1, 2'
+    assert foo(1, b=2) == '1, 2'
+    assert foo.model.schema()['title'] == 'Testing'
+
+
+def test_config_title_cls():
+    class Config:
+        title = 'Testing'
+
+    @validate_arguments(config=Config)
+    def foo(a: int, b: int):
+        return f'{a}, {b}'
+
+    assert foo(1, 2) == '1, 2'
+    assert foo(1, b=2) == '1, 2'
+    assert foo.model.schema()['title'] == 'Testing'
+
+
+def test_config_fields():
+    with pytest.raises(ConfigError, match='Setting the "fields" and "alias_generator" property on custom Config for @'):
+
+        @validate_arguments(config=dict(fields={'b': 'bang'}))
+        def foo(a: int, b: int):
+            return f'{a}, {b}'
+
+
+def test_config_arbitrary_types_allowed():
+    class EggBox:
+        def __str__(self) -> str:
+            return 'EggBox()'
+
+    @validate_arguments(config=dict(arbitrary_types_allowed=True))
+    def foo(a: int, b: EggBox):
+        return f'{a}, {b}'
+
+    assert foo(1, EggBox()) == '1, EggBox()'
+    with pytest.raises(ValidationError) as exc_info:
+        assert foo(1, 2) == '1, 2'
+
+    assert exc_info.value.errors() == [
+        {
+            'loc': ('b',),
+            'msg': 'instance of EggBox expected',
+            'type': 'type_error.arbitrary_type',
+            'ctx': {'expected_arbitrary_type': 'EggBox'},
+        },
+    ]
diff --git a/tests/test_forward_ref.py b/tests/test_forward_ref.py
index d640dd7a5..420079dfb 100644
--- a/tests/test_forward_ref.py
+++ b/tests/test_forward_ref.py
@@ -439,3 +439,36 @@ else:
     raise AssertionError('error not raised')
     """
     )
+
+
+@skip_pre_37
+def test_forward_ref_optional(create_module):
+    module = create_module(
+        """
+from __future__ import annotations
+from pydantic import BaseModel, Field
+from typing import List, Optional
+
+
+class Spec(BaseModel):
+    spec_fields: List[str] = Field(..., alias="fields")
+    filter: Optional[str]
+    sort: Optional[str]
+
+
+class PSpec(Spec):
+    g: Optional[GSpec]
+
+
+class GSpec(Spec):
+    p: Optional[PSpec]
+
+PSpec.update_forward_refs()
+
+class Filter(BaseModel):
+    g: Optional[GSpec]
+    p: Optional[PSpec]
+    """
+    )
+    Filter = module.Filter
+    assert isinstance(Filter(p={'sort': 'some_field:asc', 'fields': []}), Filter)
diff --git a/tests/test_settings.py b/tests/test_settings.py
index cd7117e7a..d24aae349 100644
--- a/tests/test_settings.py
+++ b/tests/test_settings.py
@@ -1,5 +1,5 @@
 import os
-from typing import Dict, List, Set
+from typing import Dict, List, Optional, Set
 
 import pytest
 
@@ -221,6 +221,107 @@ def test_env_inheritance_field(env):
     assert SettingsChild(foobar='abc').foobar == 'abc'
 
 
+def test_env_prefix_inheritance_config(env):
+    env.set('foobar', 'foobar')
+    env.set('prefix_foobar', 'prefix_foobar')
+
+    env.set('foobar_parent_from_field', 'foobar_parent_from_field')
+    env.set('foobar_child_from_field', 'foobar_child_from_field')
+
+    env.set('foobar_parent_from_config', 'foobar_parent_from_config')
+    env.set('foobar_child_from_config', 'foobar_child_from_config')
+
+    # . Child prefix does not override explicit parent field config
+    class Parent(BaseSettings):
+        foobar: str = Field(None, env='foobar_parent_from_field')
+
+    class Child(Parent):
+        class Config:
+            env_prefix = 'prefix_'
+
+    assert Child().foobar == 'foobar_parent_from_field'
+
+    # c. Child prefix does not override explicit parent class config
+    class Parent(BaseSettings):
+        foobar: str = None
+
+        class Config:
+            fields = {
+                'foobar': {'env': ['foobar_parent_from_config']},
+            }
+
+    class Child(Parent):
+        class Config:
+            env_prefix = 'prefix_'
+
+    assert Child().foobar == 'foobar_parent_from_config'
+
+    # d. Child prefix overrides parent with implicit config
+    class Parent(BaseSettings):
+        foobar: str = None
+
+    class Child(Parent):
+        class Config:
+            env_prefix = 'prefix_'
+
+    assert Child().foobar == 'prefix_foobar'
+
+
+def test_env_inheritance_config(env):
+    env.set('foobar', 'foobar')
+    env.set('prefix_foobar', 'prefix_foobar')
+
+    env.set('foobar_parent_from_field', 'foobar_parent_from_field')
+    env.set('foobar_child_from_field', 'foobar_child_from_field')
+
+    env.set('foobar_parent_from_config', 'foobar_parent_from_config')
+    env.set('foobar_child_from_config', 'foobar_child_from_config')
+
+    # a. Child class config overrides prefix and parent field config
+    class Parent(BaseSettings):
+        foobar: str = Field(None, env='foobar_parent_from_field')
+
+    class Child(Parent):
+        class Config:
+            env_prefix = 'prefix_'
+            fields = {
+                'foobar': {'env': ['foobar_child_from_config']},
+            }
+
+    assert Child().foobar == 'foobar_child_from_config'
+
+    # b. Child class config overrides prefix and parent class config
+    class Parent(BaseSettings):
+        foobar: str = None
+
+        class Config:
+            fields = {
+                'foobar': {'env': ['foobar_parent_from_config']},
+            }
+
+    class Child(Parent):
+        class Config:
+            env_prefix = 'prefix_'
+            fields = {
+                'foobar': {'env': ['foobar_child_from_config']},
+            }
+
+    assert Child().foobar == 'foobar_child_from_config'
+
+    # . Child class config overrides prefix and parent with implicit config
+    class Parent(BaseSettings):
+        foobar: Optional[str]
+
+    class Child(Parent):
+        class Config:
+            env_prefix = 'prefix_'
+            fields = {
+                'foobar': {'env': ['foobar_child_from_field']},
+            }
+
+    assert Child().foobar == 'foobar_child_from_field'
+
+
 def test_env_invalid(env):
     with pytest.raises(TypeError, match=r'invalid field env: 123 \(int\); should be string, list or set'):
 
diff --git a/tests/test_utils.py b/tests/test_utils.py
index d59f2e3ed..935038f04 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,6 +1,7 @@
 import os
 import re
 import string
+from copy import deepcopy
 from distutils.version import StrictVersion
 from enum import Enum
 from typing import NewType, Union
@@ -13,12 +14,14 @@ from pydantic.dataclasses import dataclass
 from pydantic.fields import Undefined
 from pydantic.typing import Literal, all_literal_values, display_as_type, is_new_type, new_type_supertype
 from pydantic.utils import (
+    BUILTIN_COLLECTIONS,
     ClassAttribute,
     ValueItems,
     deep_update,
     get_model,
     import_string,
     lenient_issubclass,
+    smart_deepcopy,
     truncate,
     unique_list,
 )
@@ -347,3 +350,30 @@ def test_all_literal_values():
 
     L312 = Literal['3', Literal[L1, L2]]
     assert sorted(all_literal_values(L312)) == sorted(('1', '2', '3'))
+
+
+@pytest.mark.parametrize(
+    'obj',
+    (1, 1.0, '1', b'1', int, None, test_all_literal_values, len, test_all_literal_values.__code__, lambda: ..., ...),
+)
+def test_smart_deepcopy_immutable_non_sequence(obj, mocker):
+    # make sure deepcopy is not used
+    # (other option will be to use obj.copy(), but this will produce error as none of given objects have this method)
+    mocker.patch('pydantic.utils.deepcopy', side_effect=RuntimeError)
+    assert smart_deepcopy(obj) is deepcopy(obj) is obj
+
+
+@pytest.mark.parametrize('empty_collection', (collection() for collection in BUILTIN_COLLECTIONS))
+def test_smart_deepcopy_empty_collection(empty_collection, mocker):
+    mocker.patch('pydantic.utils.deepcopy', side_effect=RuntimeError)  # make sure deepcopy is not used
+    if not isinstance(empty_collection, (tuple, frozenset)):  # empty tuple or frozenset are always the same object
+        assert smart_deepcopy(empty_collection) is not empty_collection
+
+
+@pytest.mark.parametrize(
+    'collection', (c.fromkeys((1,)) if issubclass(c, dict) else c((1,)) for c in BUILTIN_COLLECTIONS)
+)
+def test_smart_deepcopy_collection(collection, mocker):
+    expected_value = object()
+    mocker.patch('pydantic.utils.deepcopy', return_value=expected_value)
+    assert smart_deepcopy(collection) is expected_value