仅在 Django 启动 ONCE 时执行代码?

Posted

技术标签:

【中文标题】仅在 Django 启动 ONCE 时执行代码?【英文标题】:Execute code when Django starts ONCE only? 【发布时间】:2011-10-11 03:19:14 【问题描述】:

我正在编写一个 Django 中间件类,我只想在启动时执行一次,以初始化一些其他任意代码。我遵循了 sdolan here 发布的非常好的解决方案,但是“Hello”消息输出到终端两次。例如

from django.core.exceptions import MiddlewareNotUsed
from django.conf import settings

class StartupMiddleware(object):
    def __init__(self):
        print "Hello world"
        raise MiddlewareNotUsed('Startup complete')

在我的 Django 设置文件中,我将类包含在 MIDDLEWARE_CLASSES 列表中。

但是当我使用 runserver 运行 Django 并请求页面时,我进入了终端

Django version 1.3, using settings 'config.server'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Hello world
[22/Jul/2011 15:54:36] "GET / HTTP/1.1" 200 698
Hello world
[22/Jul/2011 15:54:36] "GET /static/css/base.css HTTP/1.1" 200 0

任何想法为什么“Hello world”被打印两次?谢谢。

【问题讨论】:

只是为了好奇,你知道为什么 init.py 中的代码会被执行两次吗? @Mutant 它只在 runserver 下执行了两次......这是因为 runserver 首先加载应用程序以检查它们,然后实际启动服务器。即使在 runserver 自动重新加载时,代码也只会执行一次。 哇,我一直在这里......所以再次感谢您的评论@Pykler,这就是我想知道的。 【参考方案1】:

更新:Django 1.7 现在有一个hook for this

文件:myapp/apps.py

from django.apps import AppConfig
class MyAppConfig(AppConfig):
    name = 'myapp'
    verbose_name = "My Application"
    def ready(self):
        pass # startup code here

文件:myapp/__init__.py

default_app_config = 'myapp.apps.MyAppConfig'

对于 Django

第一个答案似乎不再起作用,urls.py 在第一次请求时加载。

最近的工作是将启动代码放在任何一个 INSTALLED_APPS init.py 中,例如myapp/__init__.py

def startup():
    pass # load a big thing

startup()

当使用./manage.py runserver ...这会执行两次,但那是因为 runserver 有一些技巧可以首先验证模型等...正常部署甚至当 runserver 自动重新加载时,这只会执行一次。

【讨论】:

我认为这会为每个加载项目的进程执行。所以,我想不出为什么这在任何部署场景下都不能完美运行。这确实适用于管理命令。 +1 我了解此解决方案可用于在服务器启动时执行一些任意代码,但是否可以共享​​>一些将加载的数据?例如,我想加载一个包含一个巨大矩阵的对象,将该矩阵放入一个变量中,并通过 web api 在用户可以执行的每个请求中使用它。有这种可能吗? 文档说这里不是进行任何数据库交互的地方。这使得它不适合很多代码。这段代码可以去哪里? 编辑:一种可能的技巧是检查命令行参数 any(x in sys.argv for x in ['makemigrations', 'migrate']) 如果您的脚本运行了两次,请查看以下答案:***.com/a/28504072/5443056【参考方案2】:

从下面 Pykler 的回答中更新:Django 1.7 现在有一个 hook for this


不要这样做。

您不希望一次性启动的“中间件”。

您想在***urls.py 中执行代码。该模块被导入并执行一次。

urls.py

from django.confs.urls.defaults import *
from my_app import one_time_startup

urlpatterns = ...

one_time_startup()

【讨论】:

@Andrei:管理命令完全是一个单独的问题。 all 管理命令之前的特殊一次性启动的想法很难理解。您必须提供具体的内容。也许在另一个问题中。 尝试在 urls.py 中打印简单文本,但绝对没有输出。发生了什么? urls.py 代码仅在第一次请求时执行(猜测它回答了@SteveK 的问题)(django 1.5) 这对每个worker执行一次,在我的例子中,它总共执行了3次。 @halilpazarlama 这个答案已经过时了——你应该使用 Pykler 的答案。【参考方案3】:

这个问题在博文Entry point hook for Django projects 中得到了很好的回答,它适用于 Django >= 1.4。

基本上,您可以使用<project>/wsgi.py 来执行此操作,它只会在服务器启动时运行一次,但不会在您运行命令或导入特定模块时运行。

import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", " project_name .settings")

# Run startup code!
....

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

【讨论】:

再次添加注释以确认此方法将只执行一次代码。不需要任何锁定机制。 这里添加的脚本在测试框架启动时似乎没有执行 这个答案结束了为期两天半的对根本不起作用的解决方案的搜索。 请注意,这是在向网站发出第一个请求时执行的,而不是在您启动 Apache 时执行的。【参考方案4】:

正如@Pykler 所建议的,在 Django 1.7+ 中,您应该使用他的回答中解释的钩子,但是如果您希望 仅在调用运行服务器时调用您的函数(而不是在制作时migrations、migrate、shell 等被调用),并且你想避免 AppRegistryNotReady 异常你必须做如下:

文件:myapp/apps.py

import sys
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'my_app'

    def ready(self):
        if 'runserver' not in sys.argv:
            return True
        # you must import your modules here 
        # to avoid AppRegistryNotReady exception 
        from .models import MyModel 
        # startup code here

【讨论】:

这是否在生产模式下运行? AFAIK 在产品中。模式没有“runserver”启动。 谢谢!我的应用中有 Advanced Python Scheduler,我不想在运行 manage.py 命令时运行调度程序。 你需要在某个时候运行 ready() 吗?【参考方案5】:

如果它对某人有帮助,除了pykler's 答案之外,“--noreload”选项会阻止 runserver 在启动时执行命令两次:

python manage.py runserver --noreload

但该命令也不会在其他代码更改后重新加载 runserver。

【讨论】:

谢谢这解决了我的问题!我希望当我部署时不会发生这种情况 作为替代方案,您可以检查os.environ.get('RUN_MAIN') 的内容以仅在主进程中执行一次您的代码(参见***.com/a/28504072) 是的,这个加上 pykler 的回答也对我有用,因为它阻止了多个 ready(self) 调用,同时仍然只能启动一次。干杯! Django 的runserver 默认启动两个具有不同(不同)pid 号的进程。 --noreload 使其启动一个进程。【参考方案6】:

请注意,您无法可靠地连接到数据库或与 AppConfig.ready 函数内的模型进行交互(请参阅文档中的 warning)。

如果您需要在启动代码中与数据库交互,一种可能性是使用connection_created 信号在连接到数据库时执行初始化代码。

from django.dispatch import receiver
from django.db.backends.signals import connection_created

@receiver(connection_created)
def my_receiver(connection, **kwargs):
    with connection.cursor() as cursor:
        # do something to the database

显然,此解决方案适用于每个数据库连接运行一次代码,而不是每个项目启动一次。因此,您需要一个合理的 CONN_MAX_AGE 设置值,这样您就不会在每个请求上重新运行初始化代码。另请注意,开发服务器会忽略CONN_MAX_AGE,因此您将在开发中对每个请求运行一次代码。

99% 的情况下这是一个坏主意 - 数据库初始化代码应该在迁移中进行 - 但在某些用例中,您无法避免延迟初始化,并且上述警告是可以接受的。

【讨论】:

如果您需要在启动代码中访问数据库,这是一个很好的解决方案。让它只运行一次的简单方法是让my_receiver 函数与connection_created 信号断开连接,具体而言,将以下内容添加到my_receiver 函数中:connection_created.disconnect(my_receiver)【参考方案7】:

如果您想在运行服务器时打印一次“hello world”,请将 print ("hello world") 放在 StartupMiddleware 类之外

from django.core.exceptions import MiddlewareNotUsed
from django.conf import settings

class StartupMiddleware(object):
    def __init__(self):
        #print "Hello world"
        raise MiddlewareNotUsed('Startup complete')

print "Hello world"

【讨论】:

嗨,奥斯卡!在 SO 上,我们希望答案包括英文解释,而不仅仅是代码。您能否简要解释一下您的代码如何/为什么解决问题?【参考方案8】:

使用 Django 3.1+,您可以编写此代码以在启动时只执行一次方法。与其他问题的不同之处在于检查了主启动进程(runserver默认启动2个进程,一个作为快速代码重载的观察者):

import os 
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'app_name'

    def ready(self):
        if os.environ.get('RUN_MAIN'):
            print("STARTUP AND EXECUTE HERE ONCE.")
            # call here your code

另一种解决方案是避免环境检查,但调用 --noreload 以仅强制一个进程。

【讨论】:

【参考方案9】:

就我而言,我使用 Django 来托管一个网站,并使用 Heroku。我在 Heroku 使用 1 个测功机(就像 1 个容器),这个测功机创建了两个工人。 我想在它上面运行一个不和谐的机器人。这个页面的方法我都试过了,都是无效的。

因为是部署,所以不应该使用manage.py。相反,它使用gunicorn,我不知道如何添加--noreload 参数。 每个工作人员运行一次 wsgi.py,因此每个代码将运行两次。并且两个worker的local env是一样的。

但我注意到一件事,每次 Heroku 部署时,它都使用同一个 pid worker。所以我只是

if not sys.argv[1] in ["makemigrations", "migrate"]: # Prevent execute in some manage command
    if os.getpid() == 1: # You should check which pid Heroku will use and choose one.
        code_I_want_excute_once_only()

我不确定 pid 将来是否会改变,希望它永远保持不变。如果您有更好的方法来检查是哪个工人,请告诉我。

【讨论】:

【参考方案10】:

我使用了来自here 的公认解决方案,它检查它是否作为服务器运行,而不是在执行其他managy.py 命令(如migrate)时运行

apps.py:

from .tasks import tasks

class myAppConfig(AppConfig):
    ...

    def ready(self, *args, **kwargs):
        is_manage_py = any(arg.casefold().endswith("manage.py") for arg in sys.argv)
        is_runserver = any(arg.casefold() == "runserver" for arg in sys.argv)

        if (is_manage_py and is_runserver) or (not is_manage_py):
            tasks.is_running_as_server = True

由于在开发模式下仍会执行两次,不使用参数--noreload,我添加了一个标志以在它作为服务器运行时触发并将我的启动代码放在urls.py中只调用一次。

tasks.py:

class tasks():
    is_running_as_server = False

    def runtask(msg):
        print(msg)

urls.py:

from . import tasks

task1 = tasks.tasks()

if task1.is_running_as_server:
    task1.runtask('This should print once and only when running as a server')

总而言之,我利用 AppConfig 中的 read() 函数来读取参数并了解代码是如何执行的。但由于在开发模式下,ready() 函数运行了两次,一次用于服务器,一次用于在代码更改时重新加载服务器,而urls.py 只为服务器执行一次。因此,在我的解决方案中,我将两者结合起来运行我的任务,并且仅在代码作为服务器执行时才运行。

【讨论】:

以上是关于仅在 Django 启动 ONCE 时执行代码?的主要内容,如果未能解决你的问题,请参考以下文章

如果结果已经可用,则仅在单击按钮时启动异步任务并执行操作,否则等待结果然后执行操作

将仅在启动时运行一次的 Elastic Beanstalk 配置命令放在哪里?

php require_once 尝试仅在我的生产服务器上包含第二次

如何仅在一个进程中使用 mod_wsgi 和 django 运行 Apache?

Django REST框架:POST请求:仅在数据不存在时如何保存数据

如何确保代码不会被多次调用,但不会像 dispatch_once 那样在整个程序执行中阻塞它?