Skip to content

Commit bae0275

Browse files
committed
reduce the scope of the change to only affect EnumField + add test to
ensure historical behavior is kept (at least until it is consciously decided to be changed)
1 parent 2bac070 commit bae0275

File tree

7 files changed

+98
-14
lines changed

7 files changed

+98
-14
lines changed

docs/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Development
1818
- Addition of Decimal128Field: :class:`~mongoengine.fields.Decimal128Field` for accurate representation of Decimals (much better than the legacy field DecimalField).
1919
Although it could work to switch an existing DecimalField to Decimal128Field without applying a migration script,
2020
it is not recommended to do so (DecimalField uses float/str to store the value, Decimal128Field uses Decimal128).
21+
- BREAKING CHANGE: When using ListField(EnumField) or DictField(EnumField), the values weren't always cast into the Enum (#2531)
2122

2223
Changes in 0.25.0
2324
=================

mongoengine/base/fields.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,9 @@ def __set__(self, instance, value):
134134
# If setting to None and there is a default value provided for this
135135
# field, then set the value to the default value.
136136
if value is None:
137-
if not self.null and self.default is not None:
137+
if self.null:
138+
value = None
139+
elif self.default is not None:
138140
value = self.default
139141
if callable(value):
140142
value = value()
@@ -281,16 +283,15 @@ def _lazy_load_refs(instance, name, ref_values, *, max_depth):
281283
return documents
282284

283285
def __set__(self, instance, value):
284-
unprocessable_fields = (
285-
ComplexBaseField,
286-
_import_class("EmbeddedDocumentField"),
287-
_import_class("FileField"),
288-
)
289-
if self.field and not isinstance(self.field, unprocessable_fields):
286+
# Some fields e.g EnumField are converted upon __set__
287+
# So it is fair to mimic the same behavior when using e.g ListField(EnumField)
288+
EnumField = _import_class("EnumField")
289+
if self.field and isinstance(self.field, EnumField):
290290
if isinstance(value, (list, tuple)):
291291
value = [self.field.to_python(sub_val) for sub_val in value]
292292
elif isinstance(value, dict):
293293
value = {key: self.field.to_python(sub) for key, sub in value.items()}
294+
294295
return super().__set__(instance, value)
295296

296297
def __get__(self, instance, owner):
@@ -445,7 +446,7 @@ def to_mongo(self, value, use_db_field=True, fields=None):
445446
" have been saved to the database"
446447
)
447448

448-
# If its a document that is not inheritable it won't have
449+
# If it's a document that is not inheritable it won't have
449450
# any _cls data so make it a generic reference allows
450451
# us to dereference
451452
meta = getattr(v, "_meta", {})

mongoengine/fields.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1122,7 +1122,7 @@ class MapField(DictField):
11221122
"""
11231123

11241124
def __init__(self, field=None, *args, **kwargs):
1125-
# XXX ValidationError raised outside of the "validate" method.
1125+
# XXX ValidationError raised outside the "validate" method.
11261126
if not isinstance(field, BaseField):
11271127
self.error("Argument to MapField constructor must be a valid field")
11281128
super().__init__(field=field, *args, **kwargs)

tests/fields/test_binary_field.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ class Attachment(Document):
3131
assert MIME_TYPE == attachment_1.content_type
3232
assert BLOB == bytes(attachment_1.blob)
3333

34+
def test_bytearray_conversion_to_bytes(self):
35+
class Dummy(Document):
36+
blob = BinaryField()
37+
38+
byte_arr = bytearray(b"\x00\x00\x00\x00\x00")
39+
dummy = Dummy(blob=byte_arr)
40+
assert isinstance(dummy.blob, bytes)
41+
3442
def test_validation_succeeds(self):
3543
"""Ensure that valid values can be assigned to binary fields."""
3644

tests/fields/test_enum_field.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,20 +121,24 @@ def test_embedding_in_complex_field(self):
121121
assert model.status == Status.NEW
122122
assert model.statuses == [Status.NEW]
123123
assert model.color_mapping == {"red": Color.RED}
124+
124125
model.reload()
125126
assert model.status == Status.NEW
126127
assert model.statuses == [Status.NEW]
127128
assert model.color_mapping == {"red": Color.RED}
129+
128130
model.status = "done"
129131
model.color_mapping = {"blue": 2}
130132
model.statuses = ["new", "done"]
133+
model.save()
131134
assert model.status == Status.DONE
132-
assert model.color_mapping == {"blue": Color.BLUE}, model.color_mapping
133-
assert model.statuses == [Status.NEW, Status.DONE], model.statuses
134-
model = model.save().reload()
135+
assert model.statuses == [Status.NEW, Status.DONE]
136+
assert model.color_mapping == {"blue": Color.BLUE}
137+
138+
model.reload()
135139
assert model.status == Status.DONE
136-
assert model.color_mapping == {"blue": Color.BLUE}, model.color_mapping
137-
assert model.statuses == [Status.NEW, Status.DONE], model.statuses
140+
assert model.color_mapping == {"blue": Color.BLUE}
141+
assert model.statuses == [Status.NEW, Status.DONE]
138142

139143
with pytest.raises(ValidationError, match="must be one of ..Status"):
140144
model.statuses = [1]

tests/fields/test_fields.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,46 @@
4444

4545

4646
class TestField(MongoDBTestCase):
47+
def test_constructor_set_historical_behavior_is_kept(self):
48+
class MyDoc(Document):
49+
oid = ObjectIdField()
50+
51+
doc = MyDoc()
52+
doc.oid = str(ObjectId())
53+
assert isinstance(doc.oid, str)
54+
55+
# not modified on save (historical behavior)
56+
doc.save()
57+
assert isinstance(doc.oid, str)
58+
59+
# reloading goes through constructor so it is expected to go through to_python
60+
doc.reload()
61+
assert isinstance(doc.oid, ObjectId)
62+
63+
def test_constructor_set_list_field_historical_behavior_is_kept(self):
64+
# Although the behavior is not consistent between regular field and a ListField
65+
# This is the historical behavior so we must make sure we don't modify it (unless consciously done of course)
66+
67+
class MyOIDSDoc(Document):
68+
oids = ListField(ObjectIdField())
69+
70+
# constructor goes through to_python so casting occurs
71+
doc = MyOIDSDoc(oids=[str(ObjectId())])
72+
assert isinstance(doc.oids[0], ObjectId)
73+
74+
# constructor goes through to_python so casting occurs
75+
doc = MyOIDSDoc()
76+
doc.oids = [str(ObjectId())]
77+
assert isinstance(doc.oids[0], str)
78+
79+
doc.save()
80+
assert isinstance(doc.oids[0], str)
81+
82+
# reloading goes through constructor so it is expected to go through to_python
83+
# and cast
84+
doc.reload()
85+
assert isinstance(doc.oids[0], ObjectId)
86+
4787
def test_default_values_nothing_set(self):
4888
"""Ensure that default field values are used when creating
4989
a document.

tests/fields/test_object_id_field.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import pytest
2+
from bson import ObjectId
3+
4+
from mongoengine import Document, ObjectIdField, ValidationError
5+
from tests.utils import MongoDBTestCase, get_as_pymongo
6+
7+
8+
class TestObjectIdField(MongoDBTestCase):
9+
def test_storage(self):
10+
class MyDoc(Document):
11+
oid = ObjectIdField()
12+
13+
doc = MyDoc(oid=ObjectId())
14+
doc.save()
15+
assert get_as_pymongo(doc) == {"_id": doc.id, "oid": doc.oid}
16+
17+
def test_constructor_converts_str_to_ObjectId(self):
18+
class MyDoc(Document):
19+
oid = ObjectIdField()
20+
21+
doc = MyDoc(oid=str(ObjectId()))
22+
assert isinstance(doc.oid, ObjectId)
23+
24+
def test_validation_works(self):
25+
class MyDoc(Document):
26+
oid = ObjectIdField()
27+
28+
doc = MyDoc(oid="not-an-oid!")
29+
with pytest.raises(ValidationError, match="Invalid ObjectID"):
30+
doc.save()

0 commit comments

Comments
 (0)