Skip to content

Commit

Permalink
fix: allow None for type Optional[conset / conlist] (#2321)
Browse files Browse the repository at this point in the history
  • Loading branch information
PrettyWood committed Feb 13, 2021
1 parent 40a925f commit add3a67
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 4 deletions.
1 change: 1 addition & 0 deletions changes/2320-PrettyWood.md
@@ -0,0 +1 @@
fix: allow `None` for type `Optional[conset / conlist]`
10 changes: 6 additions & 4 deletions pydantic/types.py
Expand Up @@ -106,7 +106,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

Expand Down Expand Up @@ -187,8 +186,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)
Expand Down Expand Up @@ -229,7 +228,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)

Expand Down
56 changes: 56 additions & 0 deletions tests/test_types.py
Expand Up @@ -172,6 +172,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)
Expand Down Expand Up @@ -323,6 +351,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)
Expand Down

0 comments on commit add3a67

Please sign in to comment.