From 4169b1e2fa7cdeb0b0ea48dbada4d353a17c3e70 Mon Sep 17 00:00:00 2001 From: Eric Jolibois Date: Mon, 30 Nov 2020 18:59:46 +0100 Subject: [PATCH] fix: support plain `typing.Tuple` (#2133) closes #2132 --- changes/2132-PrettyWood.md | 1 + pydantic/fields.py | 24 +++++++++++++++--------- tests/test_types.py | 9 ++++++--- 3 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 changes/2132-PrettyWood.md diff --git a/changes/2132-PrettyWood.md b/changes/2132-PrettyWood.md new file mode 100644 index 0000000..ed05c00 --- /dev/null +++ b/changes/2132-PrettyWood.md @@ -0,0 +1 @@ +Support plain `typing.Tuple` type \ No newline at end of file diff --git a/pydantic/fields.py b/pydantic/fields.py index 68f75ea..7d84835 100644 --- a/pydantic/fields.py +++ b/pydantic/fields.py @@ -346,7 +346,6 @@ class ModelField(Representation): Note: this method is **not** idempotent (because _type_analysis is not idempotent), e.g. calling it it multiple times may modify the field and configure it incorrectly. """ - self._set_default_and_type() if self.type_.__class__ == ForwardRef: # self.type_ is currently a ForwardRef and there's nothing we can do now, @@ -448,14 +447,19 @@ class ModelField(Representation): return if issubclass(origin, Tuple): # type: ignore - self.shape = SHAPE_TUPLE - 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}')) + # origin == Tuple without item type + if not get_args(self.type_): + self.type_ = Any + self.shape = SHAPE_TUPLE_ELLIPSIS + else: + self.shape = SHAPE_TUPLE + 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}')) return if issubclass(origin, List): @@ -605,6 +609,8 @@ class ModelField(Representation): e: errors_.PydanticTypeError if self.shape == SHAPE_LIST: e = errors_.ListError() + elif self.shape in (SHAPE_TUPLE, SHAPE_TUPLE_ELLIPSIS): + e = errors_.TupleError() elif self.shape == SHAPE_SET: e = errors_.SetError() elif self.shape == SHAPE_FROZENSET: diff --git a/tests/test_types.py b/tests/test_types.py index b07d140..9c97702 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -2341,21 +2341,24 @@ def test_generic_without_params(): class Model(BaseModel): generic_list: List generic_dict: Dict + generic_tuple: Tuple - m = Model(generic_list=[0, 'a'], generic_dict={0: 'a', 'a': 0}) - assert m.dict() == {'generic_list': [0, 'a'], 'generic_dict': {0: 'a', 'a': 0}} + m = Model(generic_list=[0, 'a'], generic_dict={0: 'a', 'a': 0}, generic_tuple=(1, 'q')) + assert m.dict() == {'generic_list': [0, 'a'], 'generic_dict': {0: 'a', 'a': 0}, 'generic_tuple': (1, 'q')} def test_generic_without_params_error(): class Model(BaseModel): generic_list: List generic_dict: Dict + generic_tuple: Tuple with pytest.raises(ValidationError) as exc_info: - Model(generic_list=0, generic_dict=0) + Model(generic_list=0, generic_dict=0, generic_tuple=0) assert exc_info.value.errors() == [ {'loc': ('generic_list',), 'msg': 'value is not a valid list', 'type': 'type_error.list'}, {'loc': ('generic_dict',), 'msg': 'value is not a valid dict', 'type': 'type_error.dict'}, + {'loc': ('generic_tuple',), 'msg': 'value is not a valid tuple', 'type': 'type_error.tuple'}, ]