Skip to content

Commit 0c27490

Browse files
committed
Merge branch 'master' of github.com:MongoEngine/mongoengine into add_migration_documentation
2 parents f8d3712 + fc3ccf9 commit 0c27490

File tree

10 files changed

+109
-74
lines changed

10 files changed

+109
-74
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,3 +258,4 @@ that much better:
258258
* Leonardo Domingues (https://github.com/leodmgs)
259259
* Agustin Barto (https://github.com/abarto)
260260
* Stankiewicz Mateusz (https://github.com/mas15)
261+
* Felix Schultheiß (https://github.com/felix-smashdocs)

docs/changelog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Changelog
66
Development
77
===========
88
- (Fill this out as you fix issues and develop your features).
9+
- Bug fix in DynamicDocument which isn not parsing known fields in constructor like Document do #2412
910
- When using pymongo >= 3.7, make use of Collection.count_documents instead of Collection.count
1011
and Cursor.count that got deprecated in pymongo >= 3.7.
1112
This should have a negative impact on performance of count see Issue #2219
@@ -14,6 +15,7 @@ Development
1415
- Bug fix in ListField when updating the first item, it was saving the whole list, instead of
1516
just replacing the first item (as it's usually done) #2392
1617
- Add EnumField: ``mongoengine.fields.EnumField``
18+
- Refactoring - Remove useless code related to Document.__only_fields and Queryset.only_fields
1719

1820
Changes in 0.20.0
1921
=================

docs/guide/connecting.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ the :attr:`host` to
3131

3232
connect('project1', host='mongodb://localhost/database_name')
3333

34+
.. note:: URI containing SRV records (e.g mongodb+srv://server.example.com/) can be used as well as the :attr:`host`
35+
3436
.. note:: Database, username and password from URI string overrides
3537
corresponding parameters in :func:`~mongoengine.connect`: ::
3638

mongoengine/base/document.py

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@ def __init__(self, *args, **values):
6464
It may contain additional reserved keywords, e.g. "__auto_convert".
6565
:param __auto_convert: If True, supplied values will be converted
6666
to Python-type values via each field's `to_python` method.
67-
:param __only_fields: A set of fields that have been loaded for
68-
this document. Empty if all fields have been loaded.
6967
:param _created: Indicates whether this is a brand new document
7068
or whether it's already been persisted before. Defaults to true.
7169
"""
@@ -80,8 +78,6 @@ def __init__(self, *args, **values):
8078

8179
__auto_convert = values.pop("__auto_convert", True)
8280

83-
__only_fields = set(values.pop("__only_fields", values))
84-
8581
_created = values.pop("_created", True)
8682

8783
signals.pre_init.send(self.__class__, document=self, values=values)
@@ -106,36 +102,31 @@ def __init__(self, *args, **values):
106102
self._dynamic_fields = SON()
107103

108104
# Assign default values to the instance.
109-
# We set default values only for fields loaded from DB. See
110-
# https://github.com/mongoengine/mongoengine/issues/399 for more info.
111105
for key, field in self._fields.items():
112-
if self._db_field_map.get(key, key) in __only_fields:
106+
if self._db_field_map.get(key, key) in values:
113107
continue
114108
value = getattr(self, key, None)
115109
setattr(self, key, value)
116110

117111
if "_cls" not in values:
118112
self._cls = self._class_name
119113

120-
# Set passed values after initialisation
121-
if self._dynamic:
122-
dynamic_data = {}
123-
for key, value in values.items():
124-
if key in self._fields or key == "_id":
125-
setattr(self, key, value)
126-
else:
114+
# Set actual values
115+
dynamic_data = {}
116+
FileField = _import_class("FileField")
117+
for key, value in values.items():
118+
key = self._reverse_db_field_map.get(key, key)
119+
field = self._fields.get(key)
120+
if field or key in ("id", "pk", "_cls"):
121+
if __auto_convert and value is not None:
122+
if field and not isinstance(field, FileField):
123+
value = field.to_python(value)
124+
setattr(self, key, value)
125+
else:
126+
if self._dynamic:
127127
dynamic_data[key] = value
128-
else:
129-
FileField = _import_class("FileField")
130-
for key, value in values.items():
131-
key = self._reverse_db_field_map.get(key, key)
132-
if key in self._fields or key in ("id", "pk", "_cls"):
133-
if __auto_convert and value is not None:
134-
field = self._fields.get(key)
135-
if field and not isinstance(field, FileField):
136-
value = field.to_python(value)
137-
setattr(self, key, value)
138128
else:
129+
# For strict Document
139130
self._data[key] = value
140131

141132
# Set any get_<field>_display methods
@@ -758,11 +749,8 @@ def _get_collection_name(cls):
758749
return cls._meta.get("collection", None)
759750

760751
@classmethod
761-
def _from_son(cls, son, _auto_dereference=True, only_fields=None, created=False):
752+
def _from_son(cls, son, _auto_dereference=True, created=False):
762753
"""Create an instance of a Document (subclass) from a PyMongo SON."""
763-
if not only_fields:
764-
only_fields = []
765-
766754
if son and not isinstance(son, dict):
767755
raise ValueError(
768756
"The source SON object needs to be of type 'dict' but a '%s' was found"
@@ -817,9 +805,7 @@ def _from_son(cls, son, _auto_dereference=True, only_fields=None, created=False)
817805
if cls.STRICT:
818806
data = {k: v for k, v in data.items() if k in cls._fields}
819807

820-
obj = cls(
821-
__auto_convert=False, _created=created, __only_fields=only_fields, **data
822-
)
808+
obj = cls(__auto_convert=False, _created=created, **data)
823809
obj._changed_fields = []
824810
if not _auto_dereference:
825811
obj._fields = fields

mongoengine/document.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -464,9 +464,9 @@ def _save_create(self, doc, force_insert, write_concern):
464464
# insert_one will provoke UniqueError alongside save does not
465465
# therefore, it need to catch and call replace_one.
466466
if "_id" in doc:
467-
raw_object = wc_collection.find_one_and_replace(
468-
{"_id": doc["_id"]}, doc
469-
)
467+
select_dict = {"_id": doc["_id"]}
468+
select_dict = self._integrate_shard_key(doc, select_dict)
469+
raw_object = wc_collection.find_one_and_replace(select_dict, doc)
470470
if raw_object:
471471
return doc["_id"]
472472

@@ -489,6 +489,23 @@ def _get_update_doc(self):
489489

490490
return update_doc
491491

492+
def _integrate_shard_key(self, doc, select_dict):
493+
"""Integrates the collection's shard key to the `select_dict`, which will be used for the query.
494+
The value from the shard key is taken from the `doc` and finally the select_dict is returned.
495+
"""
496+
497+
# Need to add shard key to query, or you get an error
498+
shard_key = self._meta.get("shard_key", tuple())
499+
for k in shard_key:
500+
path = self._lookup_field(k.split("."))
501+
actual_key = [p.db_field for p in path]
502+
val = doc
503+
for ak in actual_key:
504+
val = val[ak]
505+
select_dict[".".join(actual_key)] = val
506+
507+
return select_dict
508+
492509
def _save_update(self, doc, save_condition, write_concern):
493510
"""Update an existing document.
494511
@@ -504,15 +521,7 @@ def _save_update(self, doc, save_condition, write_concern):
504521

505522
select_dict["_id"] = object_id
506523

507-
# Need to add shard key to query, or you get an error
508-
shard_key = self._meta.get("shard_key", tuple())
509-
for k in shard_key:
510-
path = self._lookup_field(k.split("."))
511-
actual_key = [p.db_field for p in path]
512-
val = doc
513-
for ak in actual_key:
514-
val = val[ak]
515-
select_dict[".".join(actual_key)] = val
524+
select_dict = self._integrate_shard_key(doc, select_dict)
516525

517526
update_doc = self._get_update_doc()
518527
if update_doc:
@@ -919,7 +928,7 @@ def ensure_indexes(cls):
919928

920929
@classmethod
921930
def list_indexes(cls):
922-
""" Lists all of the indexes that should be created for given
931+
"""Lists all of the indexes that should be created for given
923932
collection. It includes all the indexes from super- and sub-classes.
924933
"""
925934
if cls._meta.get("abstract"):
@@ -984,7 +993,7 @@ def get_indexes_spec(cls):
984993

985994
@classmethod
986995
def compare_indexes(cls):
987-
""" Compares the indexes defined in MongoEngine with the ones
996+
"""Compares the indexes defined in MongoEngine with the ones
988997
existing in the database. Returns any missing/extra indexes.
989998
"""
990999

mongoengine/fields.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1623,7 +1623,9 @@ def prepare_query_value(self, op, value):
16231623

16241624

16251625
class EnumField(BaseField):
1626-
"""Enumeration Field. Values are stored underneath as strings.
1626+
"""Enumeration Field. Values are stored underneath as is,
1627+
so it will only work with simple types (str, int, etc) that
1628+
are bson encodable
16271629
Example usage:
16281630
.. code-block:: python
16291631

mongoengine/queryset/base.py

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ def __init__(self, document, collection):
8888
self._hint = -1 # Using -1 as None is a valid value for hint
8989
self._collation = None
9090
self._batch_size = None
91-
self.only_fields = []
9291
self._max_time_ms = None
9392
self._comment = None
9493

@@ -190,19 +189,15 @@ def __getitem__(self, key):
190189
if queryset._scalar:
191190
return queryset._get_scalar(
192191
queryset._document._from_son(
193-
queryset._cursor[key],
194-
_auto_dereference=self._auto_dereference,
195-
only_fields=self.only_fields,
192+
queryset._cursor[key], _auto_dereference=self._auto_dereference,
196193
)
197194
)
198195

199196
if queryset._as_pymongo:
200197
return queryset._cursor[key]
201198

202199
return queryset._document._from_son(
203-
queryset._cursor[key],
204-
_auto_dereference=self._auto_dereference,
205-
only_fields=self.only_fields,
200+
queryset._cursor[key], _auto_dereference=self._auto_dereference,
206201
)
207202

208203
raise TypeError("Provide a slice or an integer index")
@@ -719,12 +714,10 @@ def modify(
719714

720715
if full_response:
721716
if result["value"] is not None:
722-
result["value"] = self._document._from_son(
723-
result["value"], only_fields=self.only_fields
724-
)
717+
result["value"] = self._document._from_son(result["value"])
725718
else:
726719
if result is not None:
727-
result = self._document._from_son(result, only_fields=self.only_fields)
720+
result = self._document._from_son(result)
728721

729722
return result
730723

@@ -757,18 +750,14 @@ def in_bulk(self, object_ids):
757750
docs = self._collection.find({"_id": {"$in": object_ids}}, **self._cursor_args)
758751
if self._scalar:
759752
for doc in docs:
760-
doc_map[doc["_id"]] = self._get_scalar(
761-
self._document._from_son(doc, only_fields=self.only_fields)
762-
)
753+
doc_map[doc["_id"]] = self._get_scalar(self._document._from_son(doc))
763754
elif self._as_pymongo:
764755
for doc in docs:
765756
doc_map[doc["_id"]] = doc
766757
else:
767758
for doc in docs:
768759
doc_map[doc["_id"]] = self._document._from_son(
769-
doc,
770-
only_fields=self.only_fields,
771-
_auto_dereference=self._auto_dereference,
760+
doc, _auto_dereference=self._auto_dereference,
772761
)
773762

774763
return doc_map
@@ -841,7 +830,6 @@ def _clone_into(self, new_qs):
841830
"_collation",
842831
"_auto_dereference",
843832
"_search_text",
844-
"only_fields",
845833
"_max_time_ms",
846834
"_comment",
847835
"_batch_size",
@@ -1045,7 +1033,6 @@ def only(self, *fields):
10451033
.. versionchanged:: 0.5 - Added subfield support
10461034
"""
10471035
fields = {f: QueryFieldList.ONLY for f in fields}
1048-
self.only_fields = list(fields.keys())
10491036
return self.fields(True, **fields)
10501037

10511038
def exclude(self, *fields):
@@ -1310,10 +1297,7 @@ def to_json(self, *args, **kwargs):
13101297
def from_json(self, json_data):
13111298
"""Converts json data to unsaved objects"""
13121299
son_data = json_util.loads(json_data)
1313-
return [
1314-
self._document._from_son(data, only_fields=self.only_fields)
1315-
for data in son_data
1316-
]
1300+
return [self._document._from_son(data) for data in son_data]
13171301

13181302
def aggregate(self, pipeline, *suppl_pipeline, **kwargs):
13191303
"""Perform a aggregate function based in your queryset params
@@ -1638,9 +1622,7 @@ def __next__(self):
16381622
return raw_doc
16391623

16401624
doc = self._document._from_son(
1641-
raw_doc,
1642-
_auto_dereference=self._auto_dereference,
1643-
only_fields=self.only_fields,
1625+
raw_doc, _auto_dereference=self._auto_dereference,
16441626
)
16451627

16461628
if self._scalar:

tests/document/test_dynamic.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@ def test_simple_dynamic_document(self):
3737
# Confirm no changes to self.Person
3838
assert not hasattr(self.Person, "age")
3939

40+
def test_dynamic_document_parse_values_in_constructor_like_document_do(self):
41+
class ProductDynamicDocument(DynamicDocument):
42+
title = StringField()
43+
price = FloatField()
44+
45+
class ProductDocument(Document):
46+
title = StringField()
47+
price = FloatField()
48+
49+
product = ProductDocument(title="Blabla", price="12.5")
50+
dyn_product = ProductDynamicDocument(title="Blabla", price="12.5")
51+
assert product.price == dyn_product.price == 12.5
52+
4053
def test_change_scope_of_variable(self):
4154
"""Test changing the scope of a dynamic field has no adverse effects"""
4255
p = self.Person()

tests/document/test_instance.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ class Animal(Document):
500500
doc.reload()
501501
Animal.drop_collection()
502502

503-
def test_update_shard_key_routing(self):
503+
def test_save_update_shard_key_routing(self):
504504
"""Ensures updating a doc with a specified shard_key includes it in
505505
the query.
506506
"""
@@ -528,6 +528,29 @@ class Animal(Document):
528528

529529
Animal.drop_collection()
530530

531+
def test_save_create_shard_key_routing(self):
532+
"""Ensures inserting a doc with a specified shard_key includes it in
533+
the query.
534+
"""
535+
536+
class Animal(Document):
537+
_id = UUIDField(binary=False, primary_key=True, default=uuid.uuid4)
538+
is_mammal = BooleanField()
539+
name = StringField()
540+
meta = {"shard_key": ("is_mammal",)}
541+
542+
Animal.drop_collection()
543+
doc = Animal(is_mammal=True, name="Dog")
544+
545+
with query_counter() as q:
546+
doc.save()
547+
query_op = q.db.system.profile.find({"ns": "mongoenginetest.animal"})[0]
548+
assert query_op["op"] == "command"
549+
assert query_op["command"]["findAndModify"] == "animal"
550+
assert set(query_op["command"]["query"].keys()) == set(["_id", "is_mammal"])
551+
552+
Animal.drop_collection()
553+
531554
def test_reload_with_changed_fields(self):
532555
"""Ensures reloading will not affect changed fields"""
533556

@@ -3411,7 +3434,7 @@ class Test(Document):
34113434
assert obj3 != dbref2
34123435
assert dbref2 != obj3
34133436

3414-
def test_default_values(self):
3437+
def test_default_values_dont_get_override_upon_save_when_only_is_used(self):
34153438
class Person(Document):
34163439
created_on = DateTimeField(default=lambda: datetime.utcnow())
34173440
name = StringField()

0 commit comments

Comments
 (0)