Repository: pydantic/pydantic

Issue:
BaseSetting Optional[conset(...)] not validated correctly
### 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

<!-- 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())"`:
```
             pydantic version: 1.7.3
            pydantic compiled: True
                 install path: /home/mihai/.virtualenvs/pydantic/lib/python3.9/site-packages/pydantic
               python version: 3.9.1 (default, Dec 13 2020, 11:55:53)  [GCC 10.2.0]
                     platform: Linux-5.10.12-3-ck-x86_64-with-glibc2.32
     optional deps. installed: []
```

```py
from typing import Optional

from pydantic import BaseModel, BaseSettings, IPvAnyNetwork, conset


class InfoModelSet(BaseModel):
    network_segments: Optional[set[IPvAnyNetwork]]


class InfoModelConset(BaseModel):
    network_segments: Optional[conset(IPvAnyNetwork, min_items=1)]


class InfoSettingsSet(BaseSettings):
    network_segments: Optional[set[IPvAnyNetwork]]


class InfoSettingsConset(BaseSettings):
    network_segments: Optional[conset(IPvAnyNetwork, min_items=1)]


print(f"Info model set: {InfoModelSet()}")
print(f"Info model conset: {InfoModelConset()}")
print(f"Info settings set: {InfoSettingsSet()}")
print()
print(f"Info settings conset: {InfoSettingsConset()}")
```

Results in:
```
Info model set: network_segments=None
Info model conset: network_segments=None
Info settings set: network_segments=None

Traceback (most recent call last):
  File "/home/mihai/bug.py", line 25, in <module>
    print(f"Info settings conset: {InfoSettingsConset()}")
  File "pydantic/env_settings.py", line 34, in pydantic.env_settings.BaseSettings.__init__
  File "pydantic/main.py", line 362, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for InfoSettingsConset
network_segments
  value is not a valid set (type=type_error.set)
```

I would have expected the sample to pass without raising errors and `network_segments` of the `InfoSettingsConset` instance to be `None`.


Discussion:

Resolving patch:
diff --git a/pydantic/types.py b/pydantic/types.py
--- a/pydantic/types.py
+++ b/pydantic/types.py
@@ -103,7 +103,6 @@
 
 if TYPE_CHECKING:
     from .dataclasses import Dataclass  # noqa: F401
-    from .fields import ModelField
     from .main import BaseConfig, BaseModel  # noqa: F401
     from .typing import CallableGenerator
 
@@ -159,8 +158,8 @@ def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
         update_not_none(field_schema, minItems=cls.min_items, maxItems=cls.max_items)
 
     @classmethod
-    def list_length_validator(cls, v: 'Optional[List[T]]', field: 'ModelField') -> 'Optional[List[T]]':
-        if v is None and not field.required:
+    def list_length_validator(cls, v: 'Optional[List[T]]') -> 'Optional[List[T]]':
+        if v is None:
             return None
 
         v = list_validator(v)
@@ -201,7 +200,10 @@ def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
         update_not_none(field_schema, minItems=cls.min_items, maxItems=cls.max_items)
 
     @classmethod
-    def set_length_validator(cls, v: 'Optional[Set[T]]', field: 'ModelField') -> 'Optional[Set[T]]':
+    def set_length_validator(cls, v: 'Optional[Set[T]]') -> 'Optional[Set[T]]':
+        if v is None:
+            return None
+
         v = set_validator(v)
         v_len = len(v)
 


Test patch:
diff --git a/tests/test_types.py b/tests/test_types.py
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -156,6 +156,34 @@ class ConListModelMin(BaseModel):
     ]
 
 
+def test_constrained_list_optional():
+    class Model(BaseModel):
+        req: Optional[conlist(str, min_items=1)] = ...
+        opt: Optional[conlist(str, min_items=1)]
+
+    assert Model(req=None).dict() == {'req': None, 'opt': None}
+    assert Model(req=None, opt=None).dict() == {'req': None, 'opt': None}
+
+    with pytest.raises(ValidationError) as exc_info:
+        Model(req=[], opt=[])
+    assert exc_info.value.errors() == [
+        {
+            'loc': ('req',),
+            'msg': 'ensure this value has at least 1 items',
+            'type': 'value_error.list.min_items',
+            'ctx': {'limit_value': 1},
+        },
+        {
+            'loc': ('opt',),
+            'msg': 'ensure this value has at least 1 items',
+            'type': 'value_error.list.min_items',
+            'ctx': {'limit_value': 1},
+        },
+    ]
+
+    assert Model(req=['a'], opt=['a']).dict() == {'req': ['a'], 'opt': ['a']}
+
+
 def test_constrained_list_constraints():
     class ConListModelBoth(BaseModel):
         v: conlist(int, min_items=7, max_items=11)
@@ -307,6 +335,34 @@ class ConSetModelMin(BaseModel):
     ]
 
 
+def test_constrained_set_optional():
+    class Model(BaseModel):
+        req: Optional[conset(str, min_items=1)] = ...
+        opt: Optional[conset(str, min_items=1)]
+
+    assert Model(req=None).dict() == {'req': None, 'opt': None}
+    assert Model(req=None, opt=None).dict() == {'req': None, 'opt': None}
+
+    with pytest.raises(ValidationError) as exc_info:
+        Model(req=set(), opt=set())
+    assert exc_info.value.errors() == [
+        {
+            'loc': ('req',),
+            'msg': 'ensure this value has at least 1 items',
+            'type': 'value_error.set.min_items',
+            'ctx': {'limit_value': 1},
+        },
+        {
+            'loc': ('opt',),
+            'msg': 'ensure this value has at least 1 items',
+            'type': 'value_error.set.min_items',
+            'ctx': {'limit_value': 1},
+        },
+    ]
+
+    assert Model(req={'a'}, opt={'a'}).dict() == {'req': {'a'}, 'opt': {'a'}}
+
+
 def test_constrained_set_constraints():
     class ConSetModelBoth(BaseModel):
         v: conset(int, min_items=7, max_items=11)