mongokit
mongokit copied to clipboard
i18n + autorefs == broken when retrieving an object for the first time
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?
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...
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...
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...
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.