与mongoengine ReferenceField的怪异

时间:2011-12-07 23:16:45

标签: python django mod-wsgi mongoengine

这是一个令人费解的问题,甚至难以命名,更不用说描述了。我将从基本事实开始,然后给出可能相关的背景信息。

考虑两个mongoengine文档模型:

class Bar(Document):
    # ...
    # field definitions
    # ...
    def bar_func(self):
        pass  # ...or some arbitrary code


class Foo(Document):
    bar = ReferenceField(Bar)

以下不一致在我们的生产服务器上生成AttributeError

# Assume foo_id references a valid Foo document in Mongo
# and that its 'bar' reference is to a valid Bar document.
foo = Foo.objects.with_id(foo_id)
foo.bar.bar_func()  # <-- AttributeError on 'bar_func'

如果我将调试代码放在错误位置之前,则将type(foo.bar)作为字符串进行评估会生成<class 'bson.dbref.DBRef'>。显然,DBRef没有bar_func属性,但为什么要返回DBRef而不是Bar的实例?

进一步调试代码显示ReferenceField.__get__mongoengine/fields.py函数中的以下条件失败:

if isinstance(value, (pymongo.dbref.DBRef)):
        value = _get_db().dereference(value)

(pymongo.dbref.DBRef)实际上是bson.dbref.DBRef,似乎与type(foo.bar)相同!为什么isinstance会失败?

这是事情真的奇怪的地方:

id(type(foo.bar)) == id(bson.dbref.DBRef)  # <-- Evaluates to False!

换句话说,type(foo.bar) bson.dbref.DBRef不同,而是直接引用bson.dbref.DBRef获得的 __dict__。实际上,检查这两种类型的type(foo.bar)会显示其功能和属性的不同内存位置。

注意:为方便起见,我会调用fooDBRef bson.dbref.DBRef返回的类型,以区别于DBRef引用的类型。

为了进一步调试,我修改了DBRef代码以添加一个元类,该元类在创建DBRef类型时检查系统模块,并存储这些模块的ID列表。 fooDBRef的额外类属性。结果显示,创建bson.dbref.DBRef时存在的模块集与创建裸site_a类型时存在的模块集完全不同。一个模块ID与另一个模块ID不同。

一些可能相关的因素:

  • 发生此错误的服务器在Apache下运行mod_wsgi。
  • 服务器在wsgi下运行两个不同的Django站点(称为site_bsite_a.foo_app.models)。
  • Foo在site_b.bar_app.models中定义,条形在site_a中定义。
  • site_b.bar_app settings.py在INSTALLED_APPS中有site_a
  • 产生错误的请求由site_b.*处理。
  • 创建sys.modules时,fooDBRef中有site_a.*个模块,但没有bson.dbref.DBRef个模块。 httpd reload反之亦然。
  • fooDBRef之后,错误有时会消失一段时间,并在0-10次尝试中返回。

任何人都可以帮我找出导致bson.dbref.DBRef与{{1}}不同的原因吗?

1 个答案:

答案 0 :(得分:4)

您使用mod_wsgi的嵌入模式还是守护进程模式?如果使用mod_wsgi的守护进程模式,您是将每个站点委派给不同的守护程序进程组,然后又强制应用程序在主Python解释器中运行?

可能是这样的情况,mongodb Python客户端模块可能无法在Python子解释器中正常工作,尤其是当该模块同时在同一进程的不同子解释器中使用时。

因此,您可能必须使用WSGIDaemonProcess / WSGIProcessGroup在单独的守护进程组中运行每个站点,然后使用带有'%{GLOBAL}'参数的WSGIApplicationGroup强制Python主解释器的用户。

请注意,在强制使用两个站点的主解释器时,您不能再使用嵌入模式,也不能让它们都在同一个守护进程组中运行。这就是为什么你需要强制每个人在单独的守护进程组中运行。