Repository: MongoEngine/mongoengine

Issue:
Documentation Error. 'full_result' no longer return a dictionary and that may lead to bugs or a least plenty of head scratching 
Hi all,

There seems to be a clash between the documentation and the recently updated code that probably dealt with the deprecated warnings threw by Pymongo as denoted by issue #1491. 
In the following environment the snippet below worked as expected. That's because mongoengine was retrieving a dict-like object when the full_result flag was given to the ``update`` method.

```
mongoengine     0.15.0   
more-itertools  4.2.0    
pbr             4.0.4    
pip             10.0.1   
pluggy          0.6.0    
py              1.5.3    
pyaml           17.12.1  
pymongo         3.6.1 
``` 

```
.
.
.
kwargs = {(key if key != many_unique else 'add_to_set__' + key): value for key, value in updates.items()}
    pk = bson.ObjectId() if not self.id else self.id
    result = self.__class__.objects(id=pk).update(upsert=True, full_result=True, **kwargs)

    try:
        _id = result['upserted']
    except KeyError:
        _id = self.id

    return _id
```

...However such code fails for the following environment

```
mongoengine     0.16.3    
more-itertools  5.0.0     
pip             19.0.2    
pluggy          0.8.1     
py              1.7.0     
pymongo         3.7.2  
```
So the snippet needs to be changed as follows:

```
.
.
.
kwargs = {(key if key != many_unique else 'add_to_set__' + key): value for key, value in updates.items()}
        pk = bson.ObjectId() if not self.id else self.id
        print("pk: ---->", pk)
        result = self.__class__.objects(id=pk).update_one(upsert=True, full_result=True, **kwargs)

        return result.upserted_id if result.upserted_id else self.id
```
This is because mongoengine is now delegating the update of the document to the Pymongo's ``update_one`` method as shown here https://github.com/MongoEngine/mongoengine/blob/6c6446765ec9819d303d49cbfe3d1d2f576ba129/mongoengine/queryset/base.py#L527
And Pymongo returns an UpdateResult object for ``update_one`` that is not subscriptable http://api.mongodb.com/python/current/api/pymongo/collection.html#pymongo.collection.Collection.update_one

So I think you may need to update the documentation http://docs.mongoengine.org/apireference.html#mongoengine.queryset.QuerySet.update so that it says no longer a dict-like is returned. Besides, I think that adding explicitly the issue with 'full_result' in the changelog would be beneficial for those using an older version of Mongoengine.

It'd be also helpful if the ``update_one`` method in the Api Rererence documentation clarifies that one still can use the **full_result** flag as part of the **update argument. It may sound trivial but as one reads the doc quick it seems that **full_result** is no longer available in ``update_one`` vs  it does in ``update``.

It took me a while to dig in and see what was going on.
HIH.

**Notes:**

 `result = self.__class__.objects(id=pk).update(upsert=True, full_result=True, **kwargs)` would work too. I just used update_one because I really needed to update only one document.



Discussion:
Hi @d2gex, thanks for the detailed explanation. This was indeed a breaking change (introduced in 0.15.2 or 0.15.3) that wasn't reported in the changelog. All your suggestions make sense, I'll push a PR shortly to address the different documentations

Resolving patch:
diff --git a/mongoengine/queryset/base.py b/mongoengine/queryset/base.py
--- a/mongoengine/queryset/base.py
+++ b/mongoengine/queryset/base.py
@@ -498,11 +498,12 @@ def update(self, upsert=False, multi=True, write_concern=None,
             ``save(..., write_concern={w: 2, fsync: True}, ...)`` will
             wait until at least two servers have recorded the write and
             will force an fsync on the primary server.
-        :param full_result: Return the full result dictionary rather than just the number
-            updated, e.g. return
-            ``{'n': 2, 'nModified': 2, 'ok': 1.0, 'updatedExisting': True}``.
+        :param full_result: Return the associated ``pymongo.UpdateResult`` rather than just the number
+            updated items
         :param update: Django-style update keyword arguments
 
+        :returns the number of updated documents (unless ``full_result`` is True)
+
         .. versionadded:: 0.2
         """
         if not update and not upsert:
@@ -566,7 +567,7 @@ def upsert_one(self, write_concern=None, **update):
             document = self._document.objects.with_id(atomic_update.upserted_id)
         return document
 
-    def update_one(self, upsert=False, write_concern=None, **update):
+    def update_one(self, upsert=False, write_concern=None, full_result=False, **update):
         """Perform an atomic update on the fields of the first document
         matched by the query.
 
@@ -577,12 +578,19 @@ def update_one(self, upsert=False, write_concern=None, **update):
             ``save(..., write_concern={w: 2, fsync: True}, ...)`` will
             wait until at least two servers have recorded the write and
             will force an fsync on the primary server.
+        :param full_result: Return the associated ``pymongo.UpdateResult`` rather than just the number
+            updated items
         :param update: Django-style update keyword arguments
-
+            full_result
+        :returns the number of updated documents (unless ``full_result`` is True)
         .. versionadded:: 0.2
         """
         return self.update(
-            upsert=upsert, multi=False, write_concern=write_concern, **update)
+            upsert=upsert,
+            multi=False,
+            write_concern=write_concern,
+            full_result=full_result,
+            **update)
 
     def modify(self, upsert=False, full_response=False, remove=False, new=False, **update):
         """Update and return the updated document.


Test patch:
diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py
--- a/tests/queryset/queryset.py
+++ b/tests/queryset/queryset.py
@@ -2233,6 +2233,19 @@ class Bar(Document):
         bar.reload()
         self.assertEqual(len(bar.foos), 0)
 
+    def test_update_one_check_return_with_full_result(self):
+        class BlogTag(Document):
+            name = StringField(required=True)
+
+        BlogTag.drop_collection()
+
+        BlogTag(name='garbage').save()
+        default_update = BlogTag.objects.update_one(name='new')
+        self.assertEqual(default_update, 1)
+
+        full_result_update = BlogTag.objects.update_one(name='new', full_result=True)
+        self.assertIsInstance(full_result_update, UpdateResult)
+
     def test_update_one_pop_generic_reference(self):
 
         class BlogTag(Document):