MongoEngine / mongoengine

A Python Object-Document-Mapper for working with MongoDB
http://mongoengine.org
MIT License
4.25k stars 1.23k forks source link

first() returns no result with certain data structures #840

Open staab opened 9 years ago

staab commented 9 years ago

This one took me a while to track down. It's fairly mysterious, as it only shows up with very particular data structures. Below is a quick script that you can run in the python shell as an example:

import mongoengine
import uuid

mongoengine.connect("test-db")

class TestDocument(mongoengine.Document):
    tech = mongoengine.fields.UUIDField(required=True, unique_with='name', binary=False)
    name = mongoengine.fields.StringField(required=True)
    public = mongoengine.fields.BooleanField(required=True, default=True)
    definition = mongoengine.fields.DictField(required=True)
    default = mongoengine.fields.DynamicField(required=True)

data = {
    'tech': uuid.UUID('ec3d9a97-bee0-4218-88ee-8e500ae7b11e'),
    'name': 'tools',
    'definition': {
        'type': 'object'
    }, 
    'default': {
        "name": "PLA",
        "print_temperature_c": 220,
        "bed_temperature_c": 120,
    }
}

ta = TestDocument(**data)
ta.save()
print TestDocument.objects(**data).count()
print TestDocument.objects(**data).all()
print TestDocument.objects(**data).first()

The issue is that count() returns 1, all() returns a list of length 1, and first returns None. I looked into the source, and I believe it may be related to BaseQuerySet.__getitem__, but I got stymied around line 150.

I would classify this as a fairly urgent bug, as it's a public part of the API that fails silently and mysteriously.

Thanks for the great wrapper by the way - interfaces with Django pretty well.

DavidBord commented 9 years ago

Your issue has to do with the fact that in python the order of the keys in a dict does not matter but in js it does. the way to bridge the gap is with: http://api.mongodb.org/python/current/api/bson/son.html I am telling you that now so that you'll be able to do a workaround without waiting for a mongoengine fix For instance, with your example, this works:

    import bson
    data = {
        'tech': uuid.UUID('ec3d9a97-bee0-4218-88ee-8e500ae7b11e'),
        'name': 'tools',
        'definition': {
            'type': 'object'
        },
        'default': bson.son.SON([("print_temperature_c", 220), ("bed_temperature_c", 120), ("name", "PLA")])
    }

    ta = TestDocument(**data)
    ta.save()
    print TestDocument.objects(**data).count()
    print TestDocument.objects(**data).all()
    print TestDocument.objects(default=bson.son.SON([("bed_temperature_c", 120), ("name", "PLA"), ("print_temperature_c", 220)])).first()

But notice there is still an issue here with the order of the keys for default that I am going to figure out

staab commented 9 years ago

Awesome, thanks for the workaround! We've ended up going with a different approach for this particular use case, but this is handy information to have. The difference between python dicts and json objects makes perfect sense.

MRigal commented 9 years ago

If you use Python >= 2.7, you could also use OrderedDict, it's what we do everywhere.

@DavidBord when addressing this issue, we could also consider raising a warning every time somebody tries to do something like that.