Flask_Mongoengine: Not a mongoengine object after using aggregate

915 Views Asked by At

When use aggregate with mongoengine, it return a CommandCursor instead of mongoengine object list, which mean that the mongonengine is not really be used,

For example: if some document doesn't has a title field, a error will be raised. How can I convert my results to mongoengine object?

class Post(Document):
    title = StringField(max_length=120, required=True)
    author = ReferenceField(User)

Host.objects()
# [<Post: Post object>, <Post: Post object>, ...]

pipeline = [
    {
        "$match": {
            'types': type,
        }
    },
    {
        "$project": {
            "name": 1,
            'brating': {
                "$divide": [
                    {"$add": ["$total_score", 60]},
                    {"$add": ["$total_votes", 20]}
                ]
            }
        }
    },
    {"$sort": {"brating": -1}},
    {"$limit": 100}

]

Host.objects.aggregate(*pipeline)
# <class 'pymongo.command_cursor.CommandCursor'>

list(Host.objects.aggregate(*pipeline))
# <class 'list'>
1

There are 1 best solutions below

6
On BEST ANSWER

The aggregate function is just a shortcut to the underlying pymongo function.

The documents that come back from aggregate may involve some $group or other stage that means they bear no relation to your object model so mongoengine couldn't convert them to mongoengine objects.

In the case of your pipeline you are using a $project stage to return a new type of document which only has name and brating fields.

Mongoengine isn't going to be able to do what you want here so you have a couple options:

  • Store the brating field on the Post documents. Initialise the rating at 0 when the post is created and when $total_score or $total_votes are updated, also update the rating.

  • Accept that you are getting back non-mongoengine objects and handle them accordingly. The cursor will yield normal python dictionaries which you can then access the fields post['name'] or post['brating'] in your client code.

  • Use a normal .objects query and sort on the client side.

The final step will obliviously be a problem if you have lots of documents but for a small number try something like:

posts = Post.objects(types=type).only("name", "total_score", "total_votes")
top_posts = sorted(list(posts),key=lambda p: (p.total_score+60)/(p.total_votes+20))[:100]