Django 启动时仅执行一次代码?
- 2025-01-08 08:50:00
- admin 原创
- 19
问题描述:
我正在编写一个 Django 中间件类,我只想在启动时执行一次,以初始化一些其他任意代码。我遵循了 sdolan在此处发布的非常好的解决方案,但“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”被打印了两次吗?谢谢。
解决方案 1:
更新:Django 1.7 现在有了一个钩子
文件: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 <1.7
第一个答案似乎不再起作用,urls.py 是在第一次请求时加载的。
最近有效的方法是将启动代码放在任何一个 INSTALLED_APPS init.py中,例如myapp/__init__.py
def startup():
pass # load a big thing
startup()
当使用./manage.py runserver
...时,这会执行两次,但这是因为 runserver 有一些技巧来首先验证模型等...正常部署,甚至当 runserver 自动重新加载时,这只会执行一次。
解决方案 2:
以下是 Pykler 的回答的更新:Django 1.7 现在有一个钩子
不要这样做。
您不需要一次性启动“中间件”。
您想要在顶层执行代码urls.py
。该模块被导入并执行一次。
urls.py
from django.confs.urls.defaults import *
from my_app import one_time_startup
urlpatterns = ...
one_time_startup()
解决方案 3:
博客文章“Django 项目的入口点钩子”很好地回答了这个问题,它适用于 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()
解决方案 4:
按照@Pykler的建议,在Django 1.7+中你应该使用他在答案中解释的钩子,但如果你想要你的函数只在调用运行服务器时被调用(而不是在进行迁移、迁移、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
解决方案 5:
如果它对某人有帮助,除了pykler 的回答之外,“--noreload”选项还可以防止 runserver 在启动时执行命令两次:
python manage.py runserver --noreload
但是在其他代码改变后,该命令也不会重新加载运行服务器。
解决方案 6:
标准溶液
使用 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来强制仅一个进程。
替代方法
首先要回答的问题是,为什么我们需要执行一次代码:通常我们需要初始化一些服务、数据库中的数据或某些一次性操作。90% 的时间是一些数据库初始化或作业队列。
使用 AppConfig.ready() 方法的方法不可靠,在生产中并不总是可重现的,并且不能保证只执行一次(但至少一次是不一样的)。要使某件事完全可预测并且只执行一次,最好的方法是开发一个 Django BaseCommand 并从启动脚本中调用它。
例如,我们可以在“myapp”中的文件“app/management/commands/init_tasks.py”中编写代码:
from django.core.management.base import BaseCommand
from project.apps.myapp.tasks import scheduler
from project import logger, initialize_database_data
class Command(BaseCommand):
help = "Init scheduler or do some staff in the database."
def handle(self, *args, **options):
scheduler.reload_jobs()
initialize_database_data()
logger.info("Inited")
最后,我们可以使用启动脚本“Start.bat”(在示例中为 Windows 批处理)来设置完整的应用程序启动:
start /b python manage.py qcluster
start /b python manage.py runserver 0.0.0.0:8000
start /b python manage.py init_tasks
解决方案 7:
请注意,您无法可靠地连接到数据库或与AppConfig.ready
函数内的模型交互(请参阅文档中的 警告)。
如果您需要在启动代码中与数据库交互,一种可能性是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% 的时间里这是一个坏主意 - 数据库初始化代码应该进行迁移 - 但有一些用例无法避免延迟初始化,并且上述警告是可以接受的。
解决方案 8:
如果您希望在运行服务器时打印一次“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"
解决方案 9:
对于那些希望在使用 gunicorn 的生产环境中启动时仅运行一次代码的人来说,可以使用gunicorn 提供的--preload命令。
默认情况下,Django 对任何启动代码使用多个 worker,但是通过传递 --preload 命令,Django 将仅在父 worker 中运行启动命令。
这篇文章很好地解释了如何添加 --preload 命令。然后只需在 ready 函数下添加要在启动时运行的代码,如下所示
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'app_name'
def ready(self):
code_to_run()
解决方案 10:
就我而言,我使用 Django 来托管网站,并使用 Heroku。我在 Heroku 上使用 1 个 dyno(就像 1 个容器),并且这个 dyno 创建了两个工作器。我想在其上运行一个 discord 机器人。我尝试了此页面上的所有方法,但均无效。
因为是部署,所以不应该使用manage.py,而是用gunicorn,不知道该怎么加--noreload
参数,每个worker都运行一次wsgi.py,所以每段代码都会运行两次,而且两个worker的本地环境是一样的。
但我注意到一件事,每次 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 将来是否会改变,希望它永远保持不变。如果您有更好的方法来检查它是哪个 worker,请告诉我。
解决方案 11:
我使用了这里接受的解决方案,它检查它是否作为服务器运行,而不是在执行其他managy.py
命令时运行,例如migrate
应用程序.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
该代码只被调用一次。
任务.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
只为服务器执行一次。所以在我的解决方案中,我将两者结合起来,只在代码作为服务器执行时运行一次我的任务。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理必备:盘点2024年13款好用的项目管理软件