MongoEngine / mongoengine

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

LazyReference field attribute error on access #2165

Open erodrig opened 4 years ago

erodrig commented 4 years ago

This happens on python version 3.7.4 and 3.6.8 using mongoengine version from 0.15.3 up to 0.18.2

Code works correctly on python version 3.6.8 with mongoversion 0.15

Starting from a clean python installation, only dependency installed mongoengine.

When trying to access an attribute from one of a reference, instead of fetching the reference or either failing to get the data without manually doing a fetch it fails way before.

verification.py is the snippet of code found below:

  File "verification.py", line 41, in <module>
    print(match.status)
  File "C:\Users\sinap\scoop\apps\python\current\lib\site-packages\mongoengine\base\datastructures.py", line 468, in __getattr__
    raise AttributeError()
AttributeError

The code that fails is

    def __getattr__(self, name):
        if not object.__getattribute__(self, 'passthrough'):
            raise AttributeError()
        document = self.fetch()

so is trying to define if it needs to fetch or not by configuration attribute passthrough, but it never gets the chance because the code fails at that point.

This is a sample code that I made to test this case:

# -*- coding: utf-8 -*-
import logging
import mongoengine
from mongoengine import fields
from mongoengine import connect

# -*- coding: utf-8 -*-
import logging
import mongoengine
from mongoengine import fields
from mongoengine import connect
from mongoengine.connection import get_db

class SomethingToMatch(mongoengine.Document):
    status = fields.StringField(required=True)

    def get_status(self):
        return "status"

class MatcheableExperiment(mongoengine.Document):
    status = fields.StringField(required=True)
    matches = fields.ListField(fields.LazyReferenceField("SomethingToMatch", passthrough= True))

DATABASE = {"host": "localhost", "port": 27017, "db": "MADIBA", "username": "", "password": ""}

connect(**DATABASE, connect=False)

first = SomethingToMatch(status="Here")
first.save()

match = MatcheableExperiment(status="where")
match.matches = [first]
match.save()

db_object = MatcheableExperiment.objects(id=match.id).get()
for match in db_object.matches:
    print(match.get_status())

I have check and in tag version v0.15.1 it was already broken.

bagerard commented 4 years ago

Hi @erodrig , I'm not sure to get the problem. From what I see it seems to behave as expected.

In the following piece of code:

for match in db_object.matches:
    print(match.status)

match is a LazyReference, so you would need to call .fetch() in order to access status without error

or

If you want your code to work as is, you'd need to define passthrough=True in

class MatcheableExperiment(mongoengine.Document):
    status = fields.StringField(required=True)
    matches = fields.ListField(fields.GenericLazyReferenceField(passthrough=True))

and your code will work just fine (tested with latest version)

erodrig commented 4 years ago

I found what is the problem, pass through is working fine, the issue happens when trying to call a function defined on the class of the document lets say in the example you have

db_object = MatcheableExperiment.objects(id=match.id).get() for match in db_object.matches: print(match.get_status())

Given the mongoengine does later

        return document[name]

this will fail with raise AttributeError()