From 7530e77b466e6c10867ce634253cd1477ec2ee13 Mon Sep 17 00:00:00 2001 From: PrettyWood Date: Thu, 4 Feb 2021 21:50:24 +0100 Subject: [PATCH] fix: support empty tuple type --- changes/2318-PrettyWood.md | 1 + pydantic/fields.py | 18 ++++++++++-------- tests/test_edge_cases.py | 20 ++++++++++++++++---- 3 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 changes/2318-PrettyWood.md diff --git a/changes/2318-PrettyWood.md b/changes/2318-PrettyWood.md new file mode 100644 index 0000000000..6ffb3cfb18 --- /dev/null +++ b/changes/2318-PrettyWood.md @@ -0,0 +1 @@ +Support empty tuple type diff --git a/pydantic/fields.py b/pydantic/fields.py index ebe2d56efa..3c339ce0cd 100644 --- a/pydantic/fields.py +++ b/pydantic/fields.py @@ -449,18 +449,20 @@ def _type_analysis(self) -> None: # noqa: C901 (ignore complexity) if issubclass(origin, Tuple): # type: ignore # origin == Tuple without item type - if not get_args(self.type_): + args = get_args(self.type_) + if not args: # plain tuple self.type_ = Any self.shape = SHAPE_TUPLE_ELLIPSIS - else: + elif len(args) == 2 and args[1] is Ellipsis: # e.g. Tuple[int, ...] + self.type_ = args[0] + self.shape = SHAPE_TUPLE_ELLIPSIS + elif args == ((),): # Tuple[()] means empty tuple self.shape = SHAPE_TUPLE + self.type_ = Any self.sub_fields = [] - for i, t in enumerate(get_args(self.type_)): - if t is Ellipsis: - self.type_ = get_args(self.type_)[0] - self.shape = SHAPE_TUPLE_ELLIPSIS - return - self.sub_fields.append(self._create_sub_type(t, f'{self.name}_{i}')) + else: + 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): diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py index 20977ce23a..04d34aa606 100644 --- a/tests/test_edge_cases.py +++ b/tests/test_edge_cases.py @@ -194,26 +194,38 @@ class Model(BaseModel): def test_tuple_more(): class Model(BaseModel): + empty_tuple: Tuple[()] simple_tuple: tuple = None tuple_of_different_types: Tuple[int, float, str, bool] = None - m = Model(simple_tuple=[1, 2, 3, 4], tuple_of_different_types=[4, 3, 2, 1]) - assert m.dict() == {'simple_tuple': (1, 2, 3, 4), 'tuple_of_different_types': (4, 3.0, '2', True)} + m = Model(empty_tuple=[], simple_tuple=[1, 2, 3, 4], tuple_of_different_types=[4, 3, 2, 1]) + assert m.dict() == { + 'empty_tuple': (), + 'simple_tuple': (1, 2, 3, 4), + 'tuple_of_different_types': (4, 3.0, '2', True), + } def test_tuple_length_error(): class Model(BaseModel): v: Tuple[int, float, bool] + w: Tuple[()] with pytest.raises(ValidationError) as exc_info: - Model(v=[1, 2]) + Model(v=[1, 2], w=[1]) assert exc_info.value.errors() == [ { 'loc': ('v',), 'msg': 'wrong tuple length 2, expected 3', 'type': 'value_error.tuple.length', 'ctx': {'actual_length': 2, 'expected_length': 3}, - } + }, + { + 'loc': ('w',), + 'msg': 'wrong tuple length 1, expected 0', + 'type': 'value_error.tuple.length', + 'ctx': {'actual_length': 1, 'expected_length': 0}, + }, ]