Django:Signal/Method 在 "AppConfig.ready()" 之后调用
Django: Signal/Method called after "AppConfig.ready()"
我有一个 AppConfig.ready()
实现,它取决于其他应用程序的准备情况。
是否有信号或方法(我可以实现)在调用所有应用程序 ready()
方法后被调用?
我知道django是按照INSTALLED_APPS
的顺序处理信号的。
但我不想强制执行 INSTALLED_APPS
的特定顺序。
示例:
INSTALLED_APPS=[
'app_a',
'app_b',
...
]
"app_a" 如何在 "app_b" 处理 AppConfig.ready()
后接收信号(或方法调用)?
(重新排序 INSTALLED_APPS
不是解决方案)
恐怕答案是否定的。填充应用程序注册表发生在 django.setup()
。如果您查看源代码,您会发现 apps.registry.Apps.populate()
nor django.setup()
都不会在完成时发送任何信号。
这里有一些想法:
您可以自己发送自定义信号,但这需要您在 Django 项目的所有入口点执行此操作,例如manage.py
、wsgi.py
和任何使用 django.setup()
.
的脚本
您可以连接到 request_started
并在调用处理程序时断开连接。
如果您正在初始化某种 属性,您可以将初始化推迟到第一次访问时。
这些方法中的任何一种是否适合您显然取决于您想要实现的目标。
所以有一个非常 hackish 的方式来完成你可能想要的...
在 django.apps.registry
内部是单例 apps
,Django 使用它来填充应用程序。请参阅 django.__init__.py
中的 setup
。
apps.populate
的工作方式是使用不可重入(基于线程)的锁定机制,只允许 apps.populate
以幂等、线程安全的方式发生。
Apps
class 的精简源,这是单例 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(self, 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(self, foo_ready):
with foo_ready:
foo_ready.wait() # wait until foo is ready
# setup bar
同样,这只允许您控制来自个人 AppConfig
的 ready
呼叫的流程。这不控制模型加载的顺序等。
但是,如果您的第一个断言是正确的,那么您的 app.ready
实现依赖于另一个应用程序首先准备就绪,这应该可以解决问题。
推理:
为什么使用条件? 使用 threading.Condition
而不是 threading.Event
的原因有两个。首先,条件被包裹在一个锁定层中。这意味着如果需要(访问共享资源等),您将继续在受控环境下运营。其次,由于这种严格的控制级别,留在 threading.Condition
的上下文中将允许您以某种理想的顺序链接配置。您可以通过以下代码片段了解如何完成此操作:
lock = threading.Lock()
foo_ready = threading.Condition(lock)
bar_ready = threading.Condition(lock)
baz_ready = threading.Condition(lock)
为什么使用 Deamonic 线程? 这样做的原因是,如果您的 Django 应用程序在 apps.populate
中获取和释放锁之间的某个时间死机,后台线程将继续自旋等待锁释放。将它们设置为守护进程模式将允许进程干净地退出而无需 .join
这些线程。
另一种解决方案:
子类 AppConfig 并在 ready
结束时发送信号。在您所有的应用程序中使用这个子类。如果您依赖于正在加载的一个,请连接到 signal/sender 对。
如果您需要更多详细信息,请不要犹豫!
此方法有一些微妙之处:
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
因此,设置如下:
- 当django第一次启动时,注册信号。
- 在每个
app_config.ready
结束时发送信号(以 AppConfig
实例作为发送者)
- 在需要响应信号的 AppConfigs 中,在
__init__
中向适当的发送者注册一个接收者。
让我知道进展如何!
如果您需要它为第三方应用程序工作,请记住您可以覆盖这些应用程序的 AppConfigs(惯例是将它们放在名为 apps 的目录中)。或者,你可以猴子补丁 AppConfig
您可以添加一个虚拟应用程序,其唯一目的是触发自定义 all_apps_are_ready
信号(或 AppConfig
上的方法调用)。
将此应用放在 INSTALLED_APPS
的末尾。
如果此应用收到 AppConfig.ready()
方法调用,您就知道所有其他应用都已准备就绪。
我有一个 AppConfig.ready()
实现,它取决于其他应用程序的准备情况。
是否有信号或方法(我可以实现)在调用所有应用程序 ready()
方法后被调用?
我知道django是按照INSTALLED_APPS
的顺序处理信号的。
但我不想强制执行 INSTALLED_APPS
的特定顺序。
示例:
INSTALLED_APPS=[
'app_a',
'app_b',
...
]
"app_a" 如何在 "app_b" 处理 AppConfig.ready()
后接收信号(或方法调用)?
(重新排序 INSTALLED_APPS
不是解决方案)
恐怕答案是否定的。填充应用程序注册表发生在 django.setup()
。如果您查看源代码,您会发现 apps.registry.Apps.populate()
nor django.setup()
都不会在完成时发送任何信号。
这里有一些想法:
您可以自己发送自定义信号,但这需要您在 Django 项目的所有入口点执行此操作,例如
manage.py
、wsgi.py
和任何使用django.setup()
. 的脚本
您可以连接到
request_started
并在调用处理程序时断开连接。如果您正在初始化某种 属性,您可以将初始化推迟到第一次访问时。
这些方法中的任何一种是否适合您显然取决于您想要实现的目标。
所以有一个非常 hackish 的方式来完成你可能想要的...
在 django.apps.registry
内部是单例 apps
,Django 使用它来填充应用程序。请参阅 django.__init__.py
中的 setup
。
apps.populate
的工作方式是使用不可重入(基于线程)的锁定机制,只允许 apps.populate
以幂等、线程安全的方式发生。
Apps
class 的精简源,这是单例 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(self, 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(self, foo_ready):
with foo_ready:
foo_ready.wait() # wait until foo is ready
# setup bar
同样,这只允许您控制来自个人 AppConfig
的 ready
呼叫的流程。这不控制模型加载的顺序等。
但是,如果您的第一个断言是正确的,那么您的 app.ready
实现依赖于另一个应用程序首先准备就绪,这应该可以解决问题。
推理:
为什么使用条件? 使用 threading.Condition
而不是 threading.Event
的原因有两个。首先,条件被包裹在一个锁定层中。这意味着如果需要(访问共享资源等),您将继续在受控环境下运营。其次,由于这种严格的控制级别,留在 threading.Condition
的上下文中将允许您以某种理想的顺序链接配置。您可以通过以下代码片段了解如何完成此操作:
lock = threading.Lock()
foo_ready = threading.Condition(lock)
bar_ready = threading.Condition(lock)
baz_ready = threading.Condition(lock)
为什么使用 Deamonic 线程? 这样做的原因是,如果您的 Django 应用程序在 apps.populate
中获取和释放锁之间的某个时间死机,后台线程将继续自旋等待锁释放。将它们设置为守护进程模式将允许进程干净地退出而无需 .join
这些线程。
另一种解决方案:
子类 AppConfig 并在 ready
结束时发送信号。在您所有的应用程序中使用这个子类。如果您依赖于正在加载的一个,请连接到 signal/sender 对。
如果您需要更多详细信息,请不要犹豫!
此方法有一些微妙之处:
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
因此,设置如下:
- 当django第一次启动时,注册信号。
- 在每个
app_config.ready
结束时发送信号(以AppConfig
实例作为发送者) - 在需要响应信号的 AppConfigs 中,在
__init__
中向适当的发送者注册一个接收者。
让我知道进展如何!
如果您需要它为第三方应用程序工作,请记住您可以覆盖这些应用程序的 AppConfigs(惯例是将它们放在名为 apps 的目录中)。或者,你可以猴子补丁 AppConfig
您可以添加一个虚拟应用程序,其唯一目的是触发自定义 all_apps_are_ready
信号(或 AppConfig
上的方法调用)。
将此应用放在 INSTALLED_APPS
的末尾。
如果此应用收到 AppConfig.ready()
方法调用,您就知道所有其他应用都已准备就绪。