mongokit icon indicating copy to clipboard operation
mongokit copied to clipboard

i18n + autorefs == broken when retrieving an object for the first time

Open ThiefMaster opened this issue 10 years ago • 4 comments

Testcase and exception: https://gist.github.com/ThiefMaster/a80f619efceb2901d048

Retrieving the object for the first time on a new connection fails, afterwards it works. This is very strange. Any idea what's causing this and how to fix it?

ThiefMaster avatar Apr 16 '14 10:04 ThiefMaster

Thank for the test case. The issue, here, come from the use_autorefs feature. For the time being, you can just disable this feature and use plain ids to make relations...

namlook avatar Apr 16 '14 16:04 namlook

Ok, using DBRef for Test.structure.other helped. And with this property it even works like with autorefs:

@property
def other(self):
    real_other = self.__getattr__('other')
    if real_other is None:
        return None
    return self.connection[self.__database__].dereference(real_other, Other)

And the best thing is, this does not interfere with assigning to t.other (of course I need to assign a DBRef instead of the Other instance directly). I guess you are using __setattr__ to handle assignments? When writing this code I actually expected the assignment to fail because I never defined a setter for my property...

ThiefMaster avatar Apr 16 '14 22:04 ThiefMaster

Yep, __setattr__ is used with use_dot_notation: https://github.com/namlook/mongokit/blob/master/mongokit/schema_document.py#L398

Thanks for investigating... Your solution would be a workaround for now... Maybe we should check if the value is a DBRef in __getattr__ and then dereferencing the value against the model...

namlook avatar Apr 17 '14 15:04 namlook

This is the final version of my code to work around the issue and still having most of the comfort autorefs provide.(besides having to call .get_dbref() which could probably be avoided too by giving the property a setter function):

def make_ref(field, doc):
    """Creates a property to explictly resolve a DBRef.

    :param field: Name of the document field
    :param doc: Class (or class name) of the corresponding document subclass.

    If the field's structure entry is a tuple, doc needs to be a tuple, too.
    Any None entry will pass through the original value without any dereferencing.
    """
    def _get_cache(obj):
        try:
            return obj.__ref_cache
        except AttributeError:
            obj.__ref_cache = {}
            return obj.__ref_cache

    def _get_class(doc):
        if not isinstance(doc, basestring):
            return doc
        # Lookup doc by its name
        return db._registered_documents[doc]._obj_class

    def _dereference(self, obj, doc_class=None):
        if doc_class is None:
            doc_class = doc
        # We need to cache the dereferencing to ensure `x.field is x.field`
        cache = _get_cache(obj)
        try:
            return cache[field]
        except KeyError:
            cache[field] = self.db.dereference(obj, _get_class(doc_class))
            return cache[field]

    def _dereference_tuple(self, obj):
        return [_dereference(self, o, d) if d is not None else o for o, d in zip(obj, doc)]

    def _accessor(self):
        real = self.__getattr__(field)
        if real is None:
            return None
        elif isinstance(self.structure[field], tuple):
            return _dereference_tuple(self, real)
        elif isinstance(real, list):
            return [_dereference(self, x) for x in real]
        else:
            return _dereference(self, real)

    return property(_accessor)

self.db is provided by Flask-MongoKit; in my test script (w/o Flask-MongoKit) I was using self.connection[self.__database__] instead.

ThiefMaster avatar Apr 18 '14 23:04 ThiefMaster