我有一个AppConfig.ready()实现,具体取决于其他应用程序的就绪状态。
是否有信号或方法(我可以实现)在调用所有应用程序ready()
方法之后被调用?
我知道django按照INSTALLED_APPS的顺序处理信号。
但是我不想对用户执行特定的排序。
在这种情况下,A在B之前需要B。但是也许其他用例在B之前需要B。对我来说,订购INSTALLED_APPS并不是解决方案。
答案 0 :(得分:1)
恐怕答案是否定的。填充应用程序注册表发生在django.setup()
中。如果您查看源代码,将会发现apps.registry.Apps.populate()
和django.setup()
都不会在完成时发送任何信号。
以下是一些想法:
您可以自己调度自定义信号,但这需要您在Django项目的所有入口点都执行此操作,例如manage.py
,wsgi.py
以及使用django.setup()
的所有脚本。
您可以连接到request_started
并在调用处理程序时断开连接。
如果您要初始化某种属性,则可以将该初始化推迟到第一次访问。
这些方法中的任何一种是否对您有用,显然取决于您要实现的目标。
答案 1 :(得分:1)
因此,有一种非常骇人的方式来完成您可能想要的...
在django.apps.registry
内是单例apps
,Django使用它来填充应用程序。参见setup
中的django.__init__.py
。
apps.populate
的工作方式是使用不可重入(基于线程)的锁定机制,仅允许apps.populate
以幂等,线程安全的方式发生。
Apps
类的精简源代码,它是从以下实例化单例apps
的地方:
class Apps(object):
def __init__(self, installed_apps=()):
# Lock for thread-safe population.
self._lock = threading.Lock()
def populate(self, installed_apps=None):
if self.ready:
return
with self._lock:
if self.ready:
return
for app_config in self.get_app_configs():
app_config.ready()
self.ready = True
有了这些知识,您可以创建一些在特定条件下等待的threading.Thread
。这些使用者线程将利用threading.Condition
发送跨线程信号(这将强制执行您的订购问题)。这是一个如何工作的模拟示例:
import threading
from django.apps import apps, AppConfig
# here we are using the "apps._lock" to synchronize our threads, which
# is the dirty little trick that makes this work
foo_ready = threading.Condition(apps._lock)
class FooAppConfig(AppConfig):
name = "foo"
def ready(self):
t = threading.Thread(name='Foo.ready', target=self._ready_foo, args=(foo_ready,))
t.daemon = True
t.start()
def _ready_foo(foo_ready):
with foo_ready:
# setup foo
foo_ready.notifyAll() # let everyone else waiting continue
class BarAppConfig(AppConfig):
name = "bar"
def ready(self):
t = threading.Thread(name='Bar.ready', target=self._ready_bar, args=(foo_ready,))
t.daemon = True
t.start()
def _ready_bar(foo_ready):
with foo_ready:
foo_ready.wait() # wait until foo is ready
# setup bar
同样,这仅允许您控制来自各个ready
的{{1}}呼叫的流程。这不会控制订单模型的加载等。
但是,如果您的第一个断言是正确的,则您有一个AppConfig
实现,这取决于首先准备好另一个应用程序,这应该可以解决问题。
理由:
为什么要使用条件?之所以使用app.ready
而不是threading.Condition
有两个原因。首先,条件被包裹在锁定层中。这意味着,如果需要(访问共享资源等),您将在受控的情况下继续操作。其次,由于控制的严格程度,停留在threading.Event
的上下文中将使您可以按期望的顺序链接配置。您可以使用以下代码片段查看该操作如何完成:
threading.Condition
为什么使用Deamonic线程?原因是,如果您的Django应用程序在获取和释放lock = threading.Lock()
foo_ready = threading.Condition(lock)
bar_ready = threading.Condition(lock)
baz_ready = threading.Condition(lock)
中的锁之间死了一段时间,则后台线程将继续旋转以等待锁释放。将它们设置为守护程序模式将使进程干净退出,而无需apps.populate
这些线程。
答案 2 :(得分:0)
替代解决方案:
子类化AppConfig并在ready
的末尾发送信号。在所有应用程序中使用此子类。如果您依赖于正在加载的信号,请连接到该信号/发送器对。
如果您需要更多详细信息,请不要犹豫!
此方法有一些细微之处:
1)将信号定义放在何处(我怀疑在manage.py中可以使用,或者甚至可以猴子补丁django.setup
以确保它在任何地方都被调用)。您可以放置一个core
应用程序,该应用程序始终是installed_apps中第一个应用程序,或者在django始终会在加载任何AppConfig
之前加载该应用程序的地方。
2)在哪里注册信号接收器(您应该可以在AppConfig.__init__
或在该文件中全局注册)。
请参见https://docs.djangoproject.com/en/dev/ref/applications/#how-applications-are-loaded
因此,设置如下:
app_config.ready
发送信号的末尾(以AppConfig
实例为发送者)__init__
中向适当的发送方注册接收方。让我知道怎么回事!
如果需要它用于第三方应用程序,请记住,您可以覆盖这些应用程序的AppConfig(常规做法是将它们放置在名为apps的目录中)。或者,您可以猴子修补AppConfig
答案 3 :(得分:0)
您可以添加一个虚拟应用程序,该应用程序仅用于触发自定义“ all_apps_are_ready”信号(或AppConfig上的方法调用)。
将此应用放在INSTALLED_APPS的末尾。
如果此应用收到AppConfig.ready()方法调用,则说明所有其他应用均已准备就绪。