From 3ba804ea687248ace11f0a8a01cb4efd341b6c8a Mon Sep 17 00:00:00 2001 From: Matt Fulgo Date: Mon, 26 Sep 2022 15:48:31 -0400 Subject: [PATCH] Allows Optional lists with unique_items check When using `unique_items` with an `Optional[List[T]]` field, the field validator would raise the following error if the field was not provided: > `'NoneType' object is not iterable (type=type_error)` Updating the validator to return `None` in these cases avoids the issue. Fixes #3957, #4050, #4119 --- changes/4568-mfulgo.md | 1 + pydantic/types.py | 5 ++++- tests/test_validators.py | 18 +++++++++++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 changes/4568-mfulgo.md diff --git a/changes/4568-mfulgo.md b/changes/4568-mfulgo.md new file mode 100644 index 0000000000..dfbe63d9fc --- /dev/null +++ b/changes/4568-mfulgo.md @@ -0,0 +1 @@ +Fixes error passing None for optional lists with `unique_items` diff --git a/pydantic/types.py b/pydantic/types.py index eaf679d48e..9438a6a829 100644 --- a/pydantic/types.py +++ b/pydantic/types.py @@ -599,7 +599,10 @@ def list_length_validator(cls, v: 'Optional[List[T]]') -> 'Optional[List[T]]': return v @classmethod - def unique_items_validator(cls, v: 'List[T]') -> 'List[T]': + def unique_items_validator(cls, v: 'Optional[List[T]]') -> 'Optional[List[T]]': + if v is None: + return None + for i, value in enumerate(v, start=1): if value in v[i:]: raise errors.ListUniqueItemsError() diff --git a/tests/test_validators.py b/tests/test_validators.py index 778085880d..de67ffe472 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -7,7 +7,7 @@ import pytest from typing_extensions import Literal -from pydantic import BaseModel, ConfigError, Extra, Field, ValidationError, errors, validator +from pydantic import BaseModel, ConfigError, Extra, Field, ValidationError, conlist, errors, validator from pydantic.class_validators import make_generic_validator, root_validator @@ -1329,3 +1329,19 @@ def post_root(cls, values): B(x='pika') assert validate_stub.call_args_list == [mocker.call('B', 'pre'), mocker.call('B', 'post')] + + +def test_list_unique_items_with_optional(): + class Model(BaseModel): + foo: Optional[List[str]] = Field(None, unique_items=True) + bar: conlist(str, unique_items=True) = Field(None) + + assert Model().dict() == {'foo': None, 'bar': None} + assert Model(foo=None, bar=None).dict() == {'foo': None, 'bar': None} + assert Model(foo=['k1'], bar=['k1']).dict() == {'foo': ['k1'], 'bar': ['k1']} + with pytest.raises(ValidationError) as exc_info: + Model(foo=['k1', 'k1'], bar=['k1', 'k1']) + assert exc_info.value.errors() == [ + {'loc': ('foo',), 'msg': 'the list has duplicated items', 'type': 'value_error.list.unique_items'}, + {'loc': ('bar',), 'msg': 'the list has duplicated items', 'type': 'value_error.list.unique_items'}, + ]