Skip to content

Commit 619db4d

Browse files
make fields typed as lists optional (elastic#1858)
1 parent d045a99 commit 619db4d

File tree

3 files changed

+68
-34
lines changed

3 files changed

+68
-34
lines changed

elasticsearch_dsl/document_base.py

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -166,24 +166,11 @@ def __init__(self, name: str, bases: Tuple[type, ...], attrs: Dict[str, Any]):
166166
field_defaults = {}
167167
for name in fields:
168168
value = None
169-
if name in attrs:
170-
# this field has a right-side value, which can be field
171-
# instance on its own or wrapped with mapped_field()
172-
value = attrs[name]
173-
if isinstance(value, dict):
174-
# the mapped_field() wrapper function was used so we need
175-
# to look for the field instance and also record any
176-
# dataclass-style defaults
177-
value = attrs[name].get("_field")
178-
default_value = attrs[name].get("default") or attrs[name].get(
179-
"default_factory"
180-
)
181-
if default_value:
182-
field_defaults[name] = default_value
183-
if value is None:
184-
# the field does not have an explicit field instance given in
185-
# a right-side assignment, so we need to figure out what field
186-
# type to use from the annotation
169+
required = None
170+
multi = None
171+
if name in annotations:
172+
# the field has a type annotation, so next we try to figure out
173+
# what field type we can use
187174
type_ = annotations[name]
188175
required = True
189176
multi = False
@@ -201,9 +188,11 @@ def __init__(self, name: str, bases: Tuple[type, ...], attrs: Dict[str, Any]):
201188
elif type_.__origin__ in [list, List]:
202189
# List[type] -> mark instance as multi
203190
multi = True
191+
required = False
204192
type_ = type_.__args__[0]
205193
else:
206194
break
195+
field = None
207196
field_args: List[Any] = []
208197
field_kwargs: Dict[str, Any] = {}
209198
if not isinstance(type_, type):
@@ -215,10 +204,39 @@ def __init__(self, name: str, bases: Tuple[type, ...], attrs: Dict[str, Any]):
215204
elif type_ in self.type_annotation_map:
216205
# use best field type for the type hint provided
217206
field, field_kwargs = self.type_annotation_map[type_]
218-
else:
219-
raise TypeError(f"Cannot map type {type_}")
220-
field_kwargs = {"multi": multi, "required": required, **field_kwargs}
221-
value = field(*field_args, **field_kwargs)
207+
208+
if field:
209+
field_kwargs = {
210+
"multi": multi,
211+
"required": required,
212+
**field_kwargs,
213+
}
214+
value = field(*field_args, **field_kwargs)
215+
216+
if name in attrs:
217+
# this field has a right-side value, which can be field
218+
# instance on its own or wrapped with mapped_field()
219+
attr_value = attrs[name]
220+
if isinstance(attr_value, dict):
221+
# the mapped_field() wrapper function was used so we need
222+
# to look for the field instance and also record any
223+
# dataclass-style defaults
224+
attr_value = attrs[name].get("_field")
225+
default_value = attrs[name].get("default") or attrs[name].get(
226+
"default_factory"
227+
)
228+
if default_value:
229+
field_defaults[name] = default_value
230+
if attr_value:
231+
value = attr_value
232+
if required is not None:
233+
value._required = required
234+
if multi is not None:
235+
value._multi = multi
236+
237+
if value is None:
238+
raise TypeError(f"Cannot map field {name}")
239+
222240
self.mapping.field(name, value)
223241
if name in attrs:
224242
del attrs[name]

tests/_async/test_document.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -707,28 +707,32 @@ class TypedDoc(AsyncDocument):
707707
assert doc.s4 == "foo"
708708
with raises(ValidationException) as exc_info:
709709
doc.full_clean()
710-
assert set(exc_info.value.args[0].keys()) == {"st", "li", "k1"}
710+
assert set(exc_info.value.args[0].keys()) == {"st", "k1", "k2", "s1", "s2", "s3"}
711711

712712
doc.st = "s"
713713
doc.li = [1, 2, 3]
714-
doc.k1 = "k"
714+
doc.k1 = "k1"
715+
doc.k2 = "k2"
716+
doc.s1 = "s1"
717+
doc.s2 = "s2"
718+
doc.s3 = "s3"
715719
doc.full_clean()
716720

717721
doc.ob = TypedInnerDoc()
718722
with raises(ValidationException) as exc_info:
719723
doc.full_clean()
720724
assert set(exc_info.value.args[0].keys()) == {"ob"}
721-
assert set(exc_info.value.args[0]["ob"][0].args[0].keys()) == {"st", "li"}
725+
assert set(exc_info.value.args[0]["ob"][0].args[0].keys()) == {"st"}
722726

723727
doc.ob.st = "s"
724728
doc.ob.li = [1]
725729
doc.full_clean()
726730

727-
doc.ns.append(TypedInnerDoc(st="s"))
731+
doc.ns.append(TypedInnerDoc(li=[1, 2]))
728732
with raises(ValidationException) as exc_info:
729733
doc.full_clean()
730734

731-
doc.ns[0].li = [1, 2]
735+
doc.ns[0].st = "s"
732736
doc.full_clean()
733737

734738
doc.ip = "1.2.3.4"
@@ -749,8 +753,12 @@ class TypedDoc(AsyncDocument):
749753
}
750754
],
751755
"ip": "1.2.3.4",
752-
"k1": "k",
756+
"k1": "k1",
757+
"k2": "k2",
753758
"k3": "foo",
759+
"s1": "s1",
760+
"s2": "s2",
761+
"s3": "s3",
754762
"s4": "foo",
755763
}
756764

tests/_sync/test_document.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -707,28 +707,32 @@ class TypedDoc(Document):
707707
assert doc.s4 == "foo"
708708
with raises(ValidationException) as exc_info:
709709
doc.full_clean()
710-
assert set(exc_info.value.args[0].keys()) == {"st", "li", "k1"}
710+
assert set(exc_info.value.args[0].keys()) == {"st", "k1", "k2", "s1", "s2", "s3"}
711711

712712
doc.st = "s"
713713
doc.li = [1, 2, 3]
714-
doc.k1 = "k"
714+
doc.k1 = "k1"
715+
doc.k2 = "k2"
716+
doc.s1 = "s1"
717+
doc.s2 = "s2"
718+
doc.s3 = "s3"
715719
doc.full_clean()
716720

717721
doc.ob = TypedInnerDoc()
718722
with raises(ValidationException) as exc_info:
719723
doc.full_clean()
720724
assert set(exc_info.value.args[0].keys()) == {"ob"}
721-
assert set(exc_info.value.args[0]["ob"][0].args[0].keys()) == {"st", "li"}
725+
assert set(exc_info.value.args[0]["ob"][0].args[0].keys()) == {"st"}
722726

723727
doc.ob.st = "s"
724728
doc.ob.li = [1]
725729
doc.full_clean()
726730

727-
doc.ns.append(TypedInnerDoc(st="s"))
731+
doc.ns.append(TypedInnerDoc(li=[1, 2]))
728732
with raises(ValidationException) as exc_info:
729733
doc.full_clean()
730734

731-
doc.ns[0].li = [1, 2]
735+
doc.ns[0].st = "s"
732736
doc.full_clean()
733737

734738
doc.ip = "1.2.3.4"
@@ -749,8 +753,12 @@ class TypedDoc(Document):
749753
}
750754
],
751755
"ip": "1.2.3.4",
752-
"k1": "k",
756+
"k1": "k1",
757+
"k2": "k2",
753758
"k3": "foo",
759+
"s1": "s1",
760+
"s2": "s2",
761+
"s3": "s3",
754762
"s4": "foo",
755763
}
756764

0 commit comments

Comments
 (0)