Repository: MongoEngine/mongoengine

Issue:
AttributeError at calling QuerySet.count() on mongomock
After updating to mongoengine 0.21.0, I got a following error when counting the number of documents by QuerySet.count().
```
Traceback (most recent call last):
  File "mongoengine_mongomock_count.py", line 15, in <module>
    main()
  File "mongoengine_mongomock_count.py", line 12, in main
    print(Doc.objects().count())
  File "/Users/dtakahashi/Library/Python/3.8/lib/python/site-packages/mongoengine/queryset/queryset.py", line 144, in count
    return super().count(with_limit_and_skip)
  File "/Users/dtakahashi/Library/Python/3.8/lib/python/site-packages/mongoengine/queryset/base.py", line 430, in count
    filter=self._cursor._Cursor__spec,
AttributeError: 'Cursor' object has no attribute '_Cursor__spec'
```
As the message says, mongomock seems not to have the attribute `Cursor._Cursor__spec`.

The mongomock is not an intended backend for this library, so I understand there are possibly many incompatibilities. But workarounds are (if any) quite helpful because the mongomock is useful for CI tests.
Thank you very much in advance.

The code for reproducing the error is here.
```python3
import mongoengine


class Doc(mongoengine.Document):
    name = mongoengine.StringField()

def main():
    mongoengine.connect(host='mongomock://localhost/test')
    for i in range(3):
        Doc(name=f'i{i}').save()
    print(Doc.objects().count())

if __name__ == '__main__':
    main()
```


Discussion:
Argh, indeed in the changes related to migrating from Cursor.count to Collection.count_documents, I had to use some internals of the pymongo cursor object which are obviously not present on mongomock's cursors.

I'm not very fond of adding code at the core of mongoengine to accommodate for the use of mongomock but I'll check if there is a workaround
I deeply appreciate you.
For the meantime, on CI, I monkeypatched mongomock to redirect `Cursor._Cursor__spec` to `Cursor._spec` as a workaround. I agree not to add this kind of "fix" to the mainline.
Hi, I'm the maintainer of mongomock. I see how we could patch mongomock to work for you, but I don't think this is a good thing to use internals of pymongo like that. Do you foresee a way where you would stop using the internals of pymongo? or maybe ask pymongo library to make this a public API of cursors?
@daitakahashi Could you explain how do you monkeypatched mongomock? 
We use mongomock only with pytest, so I abused pytest.monckypatch.
@daitakahashi Sorry bother you again, I was implementing your idea but I could not patch the attribute
```python
monkeypatch.setattr(mongomock.collection.Cursor, '_Cursor__spec', mongomock.collection.Cursor._spec, raising=False)
```
The above patch throws this error:
```
AttributeError: type object 'Cursor' has no attribute '_spec'
```
@daitakahashi Nevermind, I figured out when printing what the heck was in `_spec`

Thanks 
@agarrido19 do you mind sharing the final solution? It's not clear.
@bagerard  Do you know how are `mongoengine.queryset.QuerySet._query` and `pymongo.cursor.Cursor.__spec` different? Those dicts look quite similar in my naive experiments (only 2 simple cases, though).
Good catch @daitakahashi. I must admit I didn't consider ._query too much and rushed into using Cursor.__spec since that one was guaranteed to be reliable. I just checked and the 2 are strictly equal when I'm running the whole MongoEngine test suite which is a good sign. I'll dig more into it with some edge cases to see if this remain valid

Resolving patch:
diff --git a/mongoengine/queryset/base.py b/mongoengine/queryset/base.py
--- a/mongoengine/queryset/base.py
+++ b/mongoengine/queryset/base.py
@@ -420,7 +420,7 @@ def count(self, with_limit_and_skip=False):
 
         count = count_documents(
             collection=self._cursor.collection,
-            filter=self._cursor._Cursor__spec,
+            filter=self._query,
             **kwargs,
         )
 


Test patch:
diff --git a/tests/document/test_instance.py b/tests/document/test_instance.py
--- a/tests/document/test_instance.py
+++ b/tests/document/test_instance.py
@@ -2900,50 +2900,32 @@ def __str__(self):
         # Checks
         assert ",".join([str(b) for b in Book.objects.all()]) == "1,2,3,4,5,6,7,8,9"
         # bob related books
-        assert (
-            ",".join(
-                [
-                    str(b)
-                    for b in Book.objects.filter(
-                        Q(extra__a=bob) | Q(author=bob) | Q(extra__b=bob)
-                    )
-                ]
-            )
-            == "1,2,3,4"
+        bob_books_qs = Book.objects.filter(
+            Q(extra__a=bob) | Q(author=bob) | Q(extra__b=bob)
         )
+        assert [str(b) for b in bob_books_qs] == ["1", "2", "3", "4"]
+        assert bob_books_qs.count() == 4
 
         # Susan & Karl related books
-        assert (
-            ",".join(
-                [
-                    str(b)
-                    for b in Book.objects.filter(
-                        Q(extra__a__all=[karl, susan])
-                        | Q(author__all=[karl, susan])
-                        | Q(extra__b__all=[karl.to_dbref(), susan.to_dbref()])
-                    )
-                ]
-            )
-            == "1"
+        susan_karl_books_qs = Book.objects.filter(
+            Q(extra__a__all=[karl, susan])
+            | Q(author__all=[karl, susan])
+            | Q(extra__b__all=[karl.to_dbref(), susan.to_dbref()])
         )
+        assert [str(b) for b in susan_karl_books_qs] == ["1"]
+        assert susan_karl_books_qs.count() == 1
 
         # $Where
-        assert (
-            ",".join(
-                [
-                    str(b)
-                    for b in Book.objects.filter(
-                        __raw__={
-                            "$where": """
+        custom_qs = Book.objects.filter(
+            __raw__={
+                "$where": """
                                             function(){
                                                 return this.name == '1' ||
                                                        this.name == '2';}"""
-                        }
-                    )
-                ]
-            )
-            == "1,2"
+            }
         )
+        assert [str(b) for b in custom_qs] == ["1", "2"]
+        assert custom_qs.count() == 2
 
     def test_switch_db_instance(self):
         register_connection("testdb-1", "mongoenginetest2")
diff --git a/tests/queryset/test_transform.py b/tests/queryset/test_transform.py
--- a/tests/queryset/test_transform.py
+++ b/tests/queryset/test_transform.py
@@ -104,18 +104,17 @@ class BlogPost(Document):
         post = BlogPost(**data)
         post.save()
 
-        assert "postTitle" in BlogPost.objects(title=data["title"])._query
-        assert not ("title" in BlogPost.objects(title=data["title"])._query)
-        assert BlogPost.objects(title=data["title"]).count() == 1
+        qs = BlogPost.objects(title=data["title"])
+        assert qs._query == {"postTitle": data["title"]}
+        assert qs.count() == 1
 
-        assert "_id" in BlogPost.objects(pk=post.id)._query
-        assert BlogPost.objects(pk=post.id).count() == 1
+        qs = BlogPost.objects(pk=post.id)
+        assert qs._query == {"_id": post.id}
+        assert qs.count() == 1
 
-        assert (
-            "postComments.commentContent"
-            in BlogPost.objects(comments__content="test")._query
-        )
-        assert BlogPost.objects(comments__content="test").count() == 1
+        qs = BlogPost.objects(comments__content="test")
+        assert qs._query == {"postComments.commentContent": "test"}
+        assert qs.count() == 1
 
         BlogPost.drop_collection()