Repository: samuelcolvin/pydantic (python)

Issue:
"none is not an allowed value" when using Union with Any
### Checks

* [x] I added a descriptive title to this issue
* [x] I have searched (google, github) for similar issues and couldn't find anything
* [x] I have read and followed [the docs](https://pydantic-docs.helpmanual.io/) and still think this is a bug

# Bug
It seems that `None` is allowed when a field's type is `Any` (as is expected, stated in the docs), but `None` is not allowed when a field's type is `Union[SomeType, Any]`.

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
             pydantic version: 1.8.2
            pydantic compiled: True
                 install path: C:\Users\TobyHarradine\PycharmProjects\orchestration\.venv\Lib\site-packages\pydantic
               python version: 3.7.9 (tags/v3.7.9:13c94747c7, Aug 17 2020, 18:58:18) [MSC v.1900 64 bit (AMD64)]
                     platform: Windows-10-10.0.19041-SP0
     optional deps. installed: ['typing-extensions']
```

Code which reproduces the issue:
```pycon
>>> import pydantic
>>> class M(pydantic.BaseModel):
...     a: Any
...
>>> M(a=1)
M(a=1)
>>> M(a=None)
M(a=None)
>>> class M(pydantic.BaseModel):
...     a: Union[int, Any]
...
>>> M(a=1)
M(a=1)
>>> M(a="abcd")
M(a='abcd')
>>> M(a=None)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "pydantic\main.py", line 406, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for M
a
  none is not an allowed value (type=type_error.none.not_allowed)

```

This may be somewhat related to #1624.

Pull request:
Allow `None` when using a `Union` containing `Any` or `object`
## Change Summary

This sets `allow_none` and `required` correctly when `Any` or `Object` are contained in a `Union` type.

## Related issue number

Resolves #3444.

## 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/3444-tharradine.md` file added describing change
  (see [changes/README.md](https://github.com/samuelcolvin/pydantic/blob/master/changes/README.md) for details)
* [x] My PR is ready to review, **please add a comment including the phrase "please review" to assign reviewers**


Patch:
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 8f45dcbb4..998af3f76 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -9,6 +9,7 @@
 ## Related issue number
 
 <!-- Are there any issues opened that will be resolved by merging this change? -->
+<!-- WARNING: please use "fix #123" style references so the issue is closed when this PR is merged. -->
 
 ## Checklist
 
diff --git a/changes/2618-nuno-andre.md b/changes/2618-nuno-andre.md
new file mode 100644
index 000000000..bf8522715
--- /dev/null
+++ b/changes/2618-nuno-andre.md
@@ -0,0 +1,1 @@
+Add `uniqueItems` option to `ConstrainedList`.
\ No newline at end of file
diff --git a/changes/2917-davidmreed.md b/changes/2917-davidmreed.md
new file mode 100644
index 000000000..243781b56
--- /dev/null
+++ b/changes/2917-davidmreed.md
@@ -0,0 +1,1 @@
+Make `SecretsSettingsSource` parse values being assigned to fields of complex types when sourced from a secrets file, just as when sourced from environment variables.
diff --git a/changes/3122-PrettyWood.md b/changes/3122-PrettyWood.md
new file mode 100644
index 000000000..b9dc1c245
--- /dev/null
+++ b/changes/3122-PrettyWood.md
@@ -0,0 +1,1 @@
+avoid dict coercion when using dict subclasses as field type
\ No newline at end of file
diff --git a/changes/3146-hi-ogawa.md b/changes/3146-hi-ogawa.md
new file mode 100644
index 000000000..d0f3cb475
--- /dev/null
+++ b/changes/3146-hi-ogawa.md
@@ -0,0 +1,1 @@
+Fix mypy plugin to collect fields based on `pydantic.utils.is_valid_field` so that it ignores untyped private variables
diff --git a/changes/3190-joaommartins.md b/changes/3190-joaommartins.md
new file mode 100644
index 000000000..74af4f13a
--- /dev/null
+++ b/changes/3190-joaommartins.md
@@ -0,0 +1,1 @@
+Always use `Enum` value as default in generated JSON schema.
\ No newline at end of file
diff --git a/changes/3327-michaelrios28.md b/changes/3327-michaelrios28.md
new file mode 100644
index 000000000..30aeb5b4f
--- /dev/null
+++ b/changes/3327-michaelrios28.md
@@ -0,0 +1,1 @@
+Changed the validator method name in the [Custom Errors example](https://pydantic-docs.helpmanual.io/usage/models/#custom-errors) to more accurately describe what the validator is doing; changed from `name_must_contain_space` to ` value_must_equal_bar`.
diff --git a/changes/3444-tharradine.md b/changes/3444-tharradine.md
new file mode 100644
index 000000000..6c9047c8a
--- /dev/null
+++ b/changes/3444-tharradine.md
@@ -0,0 +1,1 @@
+Fix issue where `None` was considered invalid when using a `Union` type containing `Any` or `object`
\ No newline at end of file
diff --git a/docs/examples/models_errors2.py b/docs/examples/models_errors2.py
index 28c81e633..7aa1b1569 100644
--- a/docs/examples/models_errors2.py
+++ b/docs/examples/models_errors2.py
@@ -5,7 +5,7 @@ class Model(BaseModel):
     foo: str
 
     @validator('foo')
-    def name_must_contain_space(cls, v):
+    def value_must_equal_bar(cls, v):
         if v != 'bar':
             raise ValueError('value must be "bar"')
 
diff --git a/docs/examples/models_errors3.py b/docs/examples/models_errors3.py
index 36ba646aa..32a34d65e 100644
--- a/docs/examples/models_errors3.py
+++ b/docs/examples/models_errors3.py
@@ -10,7 +10,7 @@ class Model(BaseModel):
     foo: str
 
     @validator('foo')
-    def name_must_contain_space(cls, v):
+    def value_must_equal_bar(cls, v):
         if v != 'bar':
             raise NotABarError(wrong_value=v)
         return v
diff --git a/docs/usage/schema.md b/docs/usage/schema.md
index 9c3419611..7618b362f 100644
--- a/docs/usage/schema.md
+++ b/docs/usage/schema.md
@@ -69,6 +69,8 @@ It has the following arguments:
   JSON Schema
 * `max_items`: for list values, this adds a corresponding validation and an annotation of `maxItems` to the
   JSON Schema
+* `unique_items`: for list values, this adds a corresponding validation and an annotation of `uniqueItems` to the
+  JSON Schema
 * `min_length`: for string values, this adds a corresponding validation and an annotation of `minLength` to the
   JSON Schema
 * `max_length`: for string values, this adds a corresponding validation and an annotation of `maxLength` to the
diff --git a/docs/usage/types.md b/docs/usage/types.md
index 37d779468..38e012ce1 100644
--- a/docs/usage/types.md
+++ b/docs/usage/types.md
@@ -800,6 +800,7 @@ The following arguments are available when using the `conlist` type function
 - `item_type: Type[T]`: type of the list items
 - `min_items: int = None`: minimum number of items in the list
 - `max_items: int = None`: maximum number of items in the list
+- `unique_items: bool = None`: enforces list elements to be unique
 
 ### Arguments to `conset`
 The following arguments are available when using the `conset` type function
diff --git a/pydantic/env_settings.py b/pydantic/env_settings.py
index 248d5dd82..b68aba8d1 100644
--- a/pydantic/env_settings.py
+++ b/pydantic/env_settings.py
@@ -216,7 +216,14 @@ class SecretsSettingsSource:
             for env_name in field.field_info.extra['env_names']:
                 path = secrets_path / env_name
                 if path.is_file():
-                    secrets[field.alias] = path.read_text().strip()
+                    secret_value = path.read_text().strip()
+                    if field.is_complex():
+                        try:
+                            secret_value = settings.__config__.json_loads(secret_value)
+                        except ValueError as e:
+                            raise SettingsError(f'error parsing JSON for "{env_name}"') from e
+
+                    secrets[field.alias] = secret_value
                 elif path.exists():
                     warnings.warn(
                         f'attempted to load secret file "{path}" but found a {path_type(path)} instead.',
diff --git a/pydantic/errors.py b/pydantic/errors.py
index 1b17cf12a..aa061ffda 100644
--- a/pydantic/errors.py
+++ b/pydantic/errors.py
@@ -48,6 +48,7 @@ __all__ = (
     'TupleLengthError',
     'ListMinLengthError',
     'ListMaxLengthError',
+    'ListUniqueItemsError',
     'SetMinLengthError',
     'SetMaxLengthError',
     'FrozenSetMinLengthError',
@@ -324,6 +325,11 @@ class ListMaxLengthError(PydanticValueError):
         super().__init__(limit_value=limit_value)
 
 
+class ListUniqueItemsError(PydanticValueError):
+    code = 'list.unique_items'
+    msg_template = 'the list has duplicated items'
+
+
 class SetMinLengthError(PydanticValueError):
     code = 'set.min_items'
     msg_template = 'ensure this value has at least {limit_value} items'
diff --git a/pydantic/fields.py b/pydantic/fields.py
index 106414f38..f7764aaa1 100644
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -34,7 +34,6 @@ from .typing import (
     Callable,
     ForwardRef,
     NoArgAnyCallable,
-    NoneType,
     display_as_type,
     get_args,
     get_origin,
@@ -103,6 +102,7 @@ class FieldInfo(Representation):
         'multiple_of',
         'min_items',
         'max_items',
+        'unique_items',
         'min_length',
         'max_length',
         'allow_mutation',
@@ -123,6 +123,7 @@ class FieldInfo(Representation):
         'multiple_of': None,
         'min_items': None,
         'max_items': None,
+        'unique_items': None,
         'allow_mutation': True,
     }
 
@@ -143,6 +144,7 @@ class FieldInfo(Representation):
         self.multiple_of = kwargs.pop('multiple_of', None)
         self.min_items = kwargs.pop('min_items', None)
         self.max_items = kwargs.pop('max_items', None)
+        self.unique_items = kwargs.pop('unique_items', None)
         self.min_length = kwargs.pop('min_length', None)
         self.max_length = kwargs.pop('max_length', None)
         self.allow_mutation = kwargs.pop('allow_mutation', True)
@@ -208,6 +210,7 @@ def Field(
     multiple_of: float = None,
     min_items: int = None,
     max_items: int = None,
+    unique_items: bool = None,
     min_length: int = None,
     max_length: int = None,
     allow_mutation: bool = True,
@@ -241,6 +244,12 @@ def Field(
       schema will have a ``maximum`` validation keyword
     :param multiple_of: only applies to numbers, requires the field to be "a multiple of". The
       schema will have a ``multipleOf`` validation keyword
+    :param min_items: only applies to lists, requires the field to have a minimum number of
+      elements. The schema will have a ``minItems`` validation keyword
+    :param max_items: only applies to lists, requires the field to have a maximum number of
+      elements. The schema will have a ``maxItems`` validation keyword
+    :param max_items: only applies to lists, requires the field not to have duplicated
+      elements. The schema will have a ``uniqueItems`` validation keyword
     :param min_length: only applies to strings, requires the field to have a minimum length. The
       schema will have a ``maximum`` validation keyword
     :param max_length: only applies to strings, requires the field to have a maximum length. The
@@ -268,6 +277,7 @@ def Field(
         multiple_of=multiple_of,
         min_items=min_items,
         max_items=max_items,
+        unique_items=unique_items,
         min_length=min_length,
         max_length=max_length,
         allow_mutation=allow_mutation,
@@ -554,19 +564,20 @@ class ModelField(Representation):
             if isinstance(self.type_, type) and isinstance(None, self.type_):
                 self.allow_none = True
             return
-        if origin is Annotated:
+        elif origin is Annotated:
             self.type_ = get_args(self.type_)[0]
             self._type_analysis()
             return
-        if origin is Callable:
+        elif origin is Callable:
             return
-        if is_union(origin):
+        elif is_union(origin):
             types_ = []
             for type_ in get_args(self.type_):
-                if type_ is NoneType:
+                if is_none_type(type_) or type_ is Any or type_ is object:
                     if self.required is Undefined:
                         self.required = False
                     self.allow_none = True
+                if is_none_type(type_):
                     continue
                 types_.append(type_)
 
@@ -580,8 +591,7 @@ class ModelField(Representation):
             else:
                 self.sub_fields = [self._create_sub_type(t, f'{self.name}_{display_as_type(t)}') for t in types_]
             return
-
-        if issubclass(origin, Tuple):  # type: ignore
+        elif issubclass(origin, Tuple):  # type: ignore
             # origin == Tuple without item type
             args = get_args(self.type_)
             if not args:  # plain tuple
@@ -599,8 +609,7 @@ class ModelField(Representation):
                 self.shape = SHAPE_TUPLE
                 self.sub_fields = [self._create_sub_type(t, f'{self.name}_{i}') for i, t in enumerate(args)]
             return
-
-        if issubclass(origin, List):
+        elif issubclass(origin, List):
             # Create self validators
             get_validators = getattr(self.type_, '__get_validators__', None)
             if get_validators:
@@ -636,6 +645,11 @@ class ModelField(Representation):
         elif issubclass(origin, Sequence):
             self.type_ = get_args(self.type_)[0]
             self.shape = SHAPE_SEQUENCE
+        # priority to most common mapping: dict
+        elif origin is dict or origin is Dict:
+            self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True)
+            self.type_ = get_args(self.type_)[1]
+            self.shape = SHAPE_DICT
         elif issubclass(origin, DefaultDict):
             self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True)
             self.type_ = get_args(self.type_)[1]
@@ -644,10 +658,6 @@ class ModelField(Representation):
             self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True)
             self.type_ = int
             self.shape = SHAPE_COUNTER
-        elif issubclass(origin, Dict):
-            self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True)
-            self.type_ = get_args(self.type_)[1]
-            self.shape = SHAPE_DICT
         elif issubclass(origin, Mapping):
             self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True)
             self.type_ = get_args(self.type_)[1]
diff --git a/pydantic/mypy.py b/pydantic/mypy.py
index 1dfd49e60..119c1a07c 100644
--- a/pydantic/mypy.py
+++ b/pydantic/mypy.py
@@ -1,6 +1,8 @@
 from configparser import ConfigParser
 from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type as TypingType, Union
 
+from pydantic.utils import is_valid_field
+
 try:
     import toml
 except ImportError:  # pragma: no cover
@@ -247,7 +249,7 @@ class PydanticModelTransformer:
                 continue
 
             lhs = stmt.lvalues[0]
-            if not isinstance(lhs, NameExpr):
+            if not isinstance(lhs, NameExpr) or not is_valid_field(lhs.name):
                 continue
 
             if not stmt.new_syntax and self.plugin_config.warn_untyped_fields:
@@ -268,7 +270,9 @@ class PydanticModelTransformer:
                 # Basically, it is an edge case when dealing with complex import logic
                 # This is the same logic used in the dataclasses plugin
                 continue
-            if not isinstance(node, Var):
+            if not isinstance(node, Var):  # pragma: no cover
+                # Don't know if this edge case still happens with the `is_valid_field` check above
+                # but better safe than sorry
                 continue
 
             # x: ClassVar[int] is ignored by dataclasses.
diff --git a/pydantic/schema.py b/pydantic/schema.py
index fd9012126..431503f70 100644
--- a/pydantic/schema.py
+++ b/pydantic/schema.py
@@ -922,7 +922,9 @@ def multitypes_literal_field_for_schema(values: Tuple[Any, ...], field: ModelFie
 
 
 def encode_default(dft: Any) -> Any:
-    if isinstance(dft, (int, float, str)):
+    if isinstance(dft, Enum):
+        return dft.value
+    elif isinstance(dft, (int, float, str)):
         return dft
     elif sequence_like(dft):
         t = dft.__class__
@@ -951,11 +953,9 @@ def get_annotation_from_field_info(
     :return: the same ``annotation`` if unmodified or a new annotation with validation in place
     """
     constraints = field_info.get_constraints()
-
     used_constraints: Set[str] = set()
     if constraints:
         annotation, used_constraints = get_annotation_with_constraints(annotation, field_info)
-
     if validate_assignment:
         used_constraints.add('allow_mutation')
 
@@ -999,9 +999,18 @@ def get_annotation_with_constraints(annotation: Any, field_info: FieldInfo) -> T
             if is_union(origin):
                 return Union[tuple(go(a) for a in args)]  # type: ignore
 
-            if issubclass(origin, List) and (field_info.min_items is not None or field_info.max_items is not None):
-                used_constraints.update({'min_items', 'max_items'})
-                return conlist(go(args[0]), min_items=field_info.min_items, max_items=field_info.max_items)
+            if issubclass(origin, List) and (
+                field_info.min_items is not None
+                or field_info.max_items is not None
+                or field_info.unique_items is not None
+            ):
+                used_constraints.update({'min_items', 'max_items', 'unique_items'})
+                return conlist(
+                    go(args[0]),
+                    min_items=field_info.min_items,
+                    max_items=field_info.max_items,
+                    unique_items=field_info.unique_items,
+                )
 
             if issubclass(origin, Set) and (field_info.min_items is not None or field_info.max_items is not None):
                 used_constraints.update({'min_items', 'max_items'})
diff --git a/pydantic/types.py b/pydantic/types.py
index a6dff2ad3..e3d6c1277 100644
--- a/pydantic/types.py
+++ b/pydantic/types.py
@@ -540,15 +540,18 @@ class ConstrainedList(list):  # type: ignore
 
     min_items: Optional[int] = None
     max_items: Optional[int] = None
+    unique_items: Optional[bool] = None
     item_type: Type[T]  # type: ignore
 
     @classmethod
     def __get_validators__(cls) -> 'CallableGenerator':
         yield cls.list_length_validator
+        if cls.unique_items:
+            yield cls.unique_items_validator
 
     @classmethod
     def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
-        update_not_none(field_schema, minItems=cls.min_items, maxItems=cls.max_items)
+        update_not_none(field_schema, minItems=cls.min_items, maxItems=cls.max_items, uniqueItems=cls.unique_items)
 
     @classmethod
     def list_length_validator(cls, v: 'Optional[List[T]]') -> 'Optional[List[T]]':
@@ -566,10 +569,22 @@ class ConstrainedList(list):  # type: ignore
 
         return v
 
+    @classmethod
+    def unique_items_validator(cls, v: 'List[T]') -> 'List[T]':
+        for i, value in enumerate(v, start=1):
+            if value in v[i:]:
+                raise errors.ListUniqueItemsError()
+
+        return v
 
-def conlist(item_type: Type[T], *, min_items: int = None, max_items: int = None) -> Type[List[T]]:
+
+def conlist(
+    item_type: Type[T], *, min_items: int = None, max_items: int = None, unique_items: bool = None
+) -> Type[List[T]]:
     # __args__ is needed to conform to typing generics api
-    namespace = {'min_items': min_items, 'max_items': max_items, 'item_type': item_type, '__args__': (item_type,)}
+    namespace = dict(
+        min_items=min_items, max_items=max_items, unique_items=unique_items, item_type=item_type, __args__=(item_type,)
+    )
     # We use new_class to be able to deal with Generic types
     return new_class('ConstrainedListValue', (ConstrainedList,), {}, lambda ns: ns.update(namespace))
 


Test patch:
diff --git a/tests/mypy/modules/plugin_success.py b/tests/mypy/modules/plugin_success.py
index 6fb6d6336..217eebcd3 100644
--- a/tests/mypy/modules/plugin_success.py
+++ b/tests/mypy/modules/plugin_success.py
@@ -1,6 +1,6 @@
 from typing import ClassVar, Optional, Union
 
-from pydantic import BaseModel, BaseSettings, Field, create_model
+from pydantic import BaseModel, BaseSettings, Field, create_model, validator
 from pydantic.dataclasses import dataclass
 
 
@@ -169,3 +169,15 @@ class SettingsModel(BaseSettings):
 
 
 settings = SettingsModel.construct()
+
+
+def f(name: str) -> str:
+    return name
+
+
+class ModelWithAllowReuseValidator(BaseModel):
+    name: str
+    _normalize_name = validator('name', allow_reuse=True)(f)
+
+
+model_with_allow_reuse_validator = ModelWithAllowReuseValidator(name='xyz')
diff --git a/tests/mypy/outputs/plugin-success-strict.txt b/tests/mypy/outputs/plugin-success-strict.txt
index 8e61a4a54..80365a9df 100644
--- a/tests/mypy/outputs/plugin-success-strict.txt
+++ b/tests/mypy/outputs/plugin-success-strict.txt
@@ -1,4 +1,3 @@
 29: error: Unexpected keyword argument "z" for "Model"  [call-arg]
 64: error: Untyped fields disallowed  [pydantic-field]
-79: error: Argument "x" to "OverrideModel" has incompatible type "float"; expected "int"  [arg-type]
-125: error: Untyped fields disallowed  [pydantic-field]
\ No newline at end of file
+79: error: Argument "x" to "OverrideModel" has incompatible type "float"; expected "int"  [arg-type]
\ No newline at end of file
diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py
index c5bb67a0b..227881b8f 100644
--- a/tests/test_edge_cases.py
+++ b/tests/test_edge_cases.py
@@ -83,6 +83,23 @@ def test_union_int_str():
     ]
 
 
+def test_union_int_any():
+    class Model(BaseModel):
+        v: Union[int, Any]
+
+    m = Model(v=123)
+    assert m.v == 123
+
+    m = Model(v='123')
+    assert m.v == 123
+
+    m = Model(v='foobar')
+    assert m.v == 'foobar'
+
+    m = Model(v=None)
+    assert m.v is None
+
+
 def test_union_priority():
     class ModelOne(BaseModel):
         v: Union[int, str] = ...
diff --git a/tests/test_main.py b/tests/test_main.py
index 4a968104a..ceb925db4 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -2,7 +2,20 @@ import sys
 from collections import defaultdict
 from copy import deepcopy
 from enum import Enum
-from typing import Any, Callable, ClassVar, Counter, DefaultDict, Dict, List, Mapping, Optional, Type, get_type_hints
+from typing import (
+    Any,
+    Callable,
+    ClassVar,
+    Counter,
+    DefaultDict,
+    Dict,
+    List,
+    Mapping,
+    Optional,
+    Type,
+    TypeVar,
+    get_type_hints,
+)
 from uuid import UUID, uuid4
 
 import pytest
@@ -1976,6 +1989,27 @@ def test_typing_coercion_dict():
     assert repr(m) == "Model(x={'one': 1, 'two': 2})"
 
 
+@pytest.mark.skipif(sys.version_info < (3, 7), reason='generic classes need 3.7')
+def test_typing_non_coercion_of_dict_subclasses():
+    KT = TypeVar('KT')
+    VT = TypeVar('VT')
+
+    class MyDict(Dict[KT, VT]):
+        def __repr__(self):
+            return f'MyDict({super().__repr__()})'
+
+    class Model(BaseModel):
+        a: MyDict
+        b: MyDict[str, int]
+        c: Dict[str, int]
+        d: Mapping[str, int]
+
+    assert (
+        repr(Model(a=MyDict({'a': 1}), b=MyDict({'a': '1'}), c=MyDict({'a': '1'}), d=MyDict({'a': '1'})))
+        == "Model(a=MyDict({'a': 1}), b=MyDict({'a': 1}), c={'a': 1}, d=MyDict({'a': 1}))"
+    )
+
+
 def test_typing_coercion_defaultdict():
     class Model(BaseModel):
         x: DefaultDict[int, str]
diff --git a/tests/test_schema.py b/tests/test_schema.py
index 709f4aaa9..709b468b2 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -1435,6 +1435,26 @@ def test_list_default():
     }
 
 
+def test_enum_str_default():
+    class MyEnum(str, Enum):
+        FOO = 'foo'
+
+    class UserModel(BaseModel):
+        friends: MyEnum = MyEnum.FOO
+
+    assert UserModel.schema()['properties']['friends']['default'] is MyEnum.FOO.value
+
+
+def test_enum_int_default():
+    class MyEnum(IntEnum):
+        FOO = 1
+
+    class UserModel(BaseModel):
+        friends: MyEnum = MyEnum.FOO
+
+    assert UserModel.schema()['properties']['friends']['default'] is MyEnum.FOO.value
+
+
 def test_dict_default():
     class UserModel(BaseModel):
         friends: Dict[str, float] = {'a': 1.1, 'b': 2.2}
diff --git a/tests/test_settings.py b/tests/test_settings.py
index 5fa2f549f..11f051993 100644
--- a/tests/test_settings.py
+++ b/tests/test_settings.py
@@ -830,6 +830,33 @@ def test_secrets_path_url(tmp_path):
     assert Settings().dict() == {'foo': 'http://www.example.com', 'bar': SecretStr('snap')}
 
 
+def test_secrets_path_json(tmp_path):
+    p = tmp_path / 'foo'
+    p.write_text('{"a": "b"}')
+
+    class Settings(BaseSettings):
+        foo: Dict[str, str]
+
+        class Config:
+            secrets_dir = tmp_path
+
+    assert Settings().dict() == {'foo': {'a': 'b'}}
+
+
+def test_secrets_path_invalid_json(tmp_path):
+    p = tmp_path / 'foo'
+    p.write_text('{"a": "b"')
+
+    class Settings(BaseSettings):
+        foo: Dict[str, str]
+
+        class Config:
+            secrets_dir = tmp_path
+
+    with pytest.raises(SettingsError, match='error parsing JSON for "foo"'):
+        Settings()
+
+
 def test_secrets_missing(tmp_path):
     class Settings(BaseSettings):
         foo: str
diff --git a/tests/test_types.py b/tests/test_types.py
index a0dddc0b4..fbb42ed01 100644
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -216,6 +216,39 @@ def test_constrained_list_too_short():
     ]
 
 
+def test_constrained_list_not_unique_hashable_items():
+    class ConListModelUnique(BaseModel):
+        v: conlist(int, unique_items=True)
+
+    with pytest.raises(ValidationError) as exc_info:
+        ConListModelUnique(v=[1, 1, 2, 2, 2, 3])
+    assert exc_info.value.errors() == [
+        {
+            'loc': ('v',),
+            'msg': 'the list has duplicated items',
+            'type': 'value_error.list.unique_items',
+        }
+    ]
+
+
+def test_constrained_list_not_unique_unhashable_items():
+    class ConListModelUnique(BaseModel):
+        v: conlist(Set[int], unique_items=True)
+
+    m = ConListModelUnique(v=[{1}, {2}, {3}])
+    assert m.v == [{1}, {2}, {3}]
+
+    with pytest.raises(ValidationError) as exc_info:
+        ConListModelUnique(v=[{1}, {1}, {2}, {2}, {2}, {3}])
+    assert exc_info.value.errors() == [
+        {
+            'loc': ('v',),
+            'msg': 'the list has duplicated items',
+            'type': 'value_error.list.unique_items',
+        }
+    ]
+
+
 def test_constrained_list_optional():
     class Model(BaseModel):
         req: Optional[conlist(str, min_items=1)] = ...
@@ -296,8 +329,8 @@ def test_constrained_list_item_type_fails():
 
 def test_conlist():
     class Model(BaseModel):
-        foo: List[int] = Field(..., min_items=2, max_items=4)
-        bar: conlist(str, min_items=1, max_items=4) = None
+        foo: List[int] = Field(..., min_items=2, max_items=4, unique_items=True)
+        bar: conlist(str, min_items=1, max_items=4, unique_items=False) = None
 
     assert Model(foo=[1, 2], bar=['spoon']).dict() == {'foo': [1, 2], 'bar': ['spoon']}
 
@@ -307,12 +340,29 @@ def test_conlist():
     with pytest.raises(ValidationError, match='ensure this value has at most 4 items'):
         Model(foo=list(range(5)))
 
+    with pytest.raises(ValidationError, match='the list has duplicated items'):
+        Model(foo=[1, 1, 2, 2])
+
     assert Model.schema() == {
         'title': 'Model',
         'type': 'object',
         'properties': {
-            'foo': {'title': 'Foo', 'type': 'array', 'items': {'type': 'integer'}, 'minItems': 2, 'maxItems': 4},
-            'bar': {'title': 'Bar', 'type': 'array', 'items': {'type': 'string'}, 'minItems': 1, 'maxItems': 4},
+            'foo': {
+                'title': 'Foo',
+                'type': 'array',
+                'items': {'type': 'integer'},
+                'minItems': 2,
+                'maxItems': 4,
+                'uniqueItems': True,
+            },
+            'bar': {
+                'title': 'Bar',
+                'type': 'array',
+                'items': {'type': 'string'},
+                'minItems': 1,
+                'maxItems': 4,
+                'uniqueItems': False,
+            },
         },
         'required': ['foo'],
     }