Python 芹菜自动重新加载任何更改

Python 芹菜自动重新加载任何更改,python,celery,django-celery,Python,Celery,Django Celery,当settings.py中的cellery\u IMPORTS模块发生更改时,我可以让芹菜自动重新加载 我试图给母模块提供检测更改的功能,即使在子模块上也是如此,但它没有检测到子模块中的更改。这让我明白了检测不是由芹菜递归完成的。我在文档中搜索了它,但没有收到任何关于我的问题的回复 将项目中与芹菜相关的所有内容都添加到芹菜导入中,以检测更改,这让我非常困扰 有没有办法告诉芹菜“当项目的任何地方发生任何变化时,自动重新加载自己” 谢谢大家! 您可以使用-I |--include手动包含其他模块。将

settings.py
中的
cellery\u IMPORTS
模块发生更改时,我可以让芹菜自动重新加载

我试图给母模块提供检测更改的功能,即使在子模块上也是如此,但它没有检测到子模块中的更改。这让我明白了检测不是由芹菜递归完成的。我在文档中搜索了它,但没有收到任何关于我的问题的回复

将项目中与芹菜相关的所有内容都添加到芹菜导入中,以检测更改,这让我非常困扰

有没有办法告诉芹菜“当项目的任何地方发生任何变化时,自动重新加载自己”


谢谢大家!

您可以使用
-I |--include
手动包含其他模块。将其与GNU工具(如
find
awk
相结合,您将能够找到所有
.py
文件并将其包含在内

$ celery -A app worker --autoreload --include=$(find . -name "*.py" -type f | awk '{sub("\./",""); gsub("/", "."); sub(".py",""); print}' ORS=',' | sed 's/.$//')
让我们解释一下:

find . -name "*.py" -type f
find
递归搜索包含
.py
的所有文件。输出如下所示:

./app.py
./some_package/foopy
./some_package/bar.py
然后:

此行将
find
的输出作为输入,并删除所有出现的
/
。然后它将所有的
/
替换为
。最后一个
sub()
删除将
.py
替换为空字符串
ORS
将所有换行符替换为
。这将产生:

app,some_package.foo,some_package.bar,
最后一个命令sed将删除最后一个

因此,正在执行的命令如下所示:

$ celery -A app worker --autoreload --include=app,some_package.foo,some_package.bar
如果您的源代码中有一个
virtualenv
,您可以通过将
-path.path\u添加到您的\u env-prune-o
来排除它:

$ celery -A app worker --autoreload --include=$(find . -path .path_to_your_env -prune -o -name "*.py" -type f | awk '{sub("\./",""); gsub("/", "."); sub(".py",""); print}' ORS=',' | sed 's/.$//')

OrangeTux的解决方案不适合我,所以我编写了一个小Python脚本来实现大致相同的效果。它使用inotify监视文件更改,如果在“修改”中检测到
、在“属性”中检测到
、或在“删除”中检测到
,则会触发芹菜重启

#!/usr/bin/env python
"""Runs a celery worker, and reloads on a file change. Run as ./run_celery [directory]. If
directory is not given, default to cwd."""
import os
import sys
import signal
import time

import multiprocessing
import subprocess
import threading

import inotify.adapters


CELERY_CMD = tuple("celery -A amcat.amcatcelery worker -l info -Q amcat".split())
CHANGE_EVENTS = ("IN_MODIFY", "IN_ATTRIB", "IN_DELETE")
WATCH_EXTENSIONS = (".py",)

def watch_tree(stop, path, event):
    """
    @type stop: multiprocessing.Event
    @type event: multiprocessing.Event
    """
    path = os.path.abspath(path)

    for e in inotify.adapters.InotifyTree(path).event_gen():
        if stop.is_set():
            break

        if e is not None:
            _, attrs, path, filename = e

            if filename is None:
                continue

            if any(filename.endswith(ename) for ename in WATCH_EXTENSIONS):
                continue

            if any(ename in attrs for ename in CHANGE_EVENTS):
                event.set()


class Watcher(threading.Thread):
    def __init__(self, path):
        super(Watcher, self).__init__()
        self.celery = subprocess.Popen(CELERY_CMD)
        self.stop_event_wtree = multiprocessing.Event()
        self.event_triggered_wtree = multiprocessing.Event()
        self.wtree = multiprocessing.Process(target=watch_tree, args=(self.stop_event_wtree, path, self.event_triggered_wtree))
        self.wtree.start()
        self.running = True

    def run(self):
        while self.running:
            if self.event_triggered_wtree.is_set():
                self.event_triggered_wtree.clear()
                self.restart_celery()
            time.sleep(1)

    def join(self, timeout=None):
        self.running = False
        self.stop_event_wtree.set()
        self.celery.terminate()
        self.wtree.join()
        self.celery.wait()
        super(Watcher, self).join(timeout=timeout)

    def restart_celery(self):
        self.celery.terminate()
        self.celery.wait()
        self.celery = subprocess.Popen(CELERY_CMD)


if __name__ == '__main__':
    watcher = Watcher(sys.argv[1] if len(sys.argv) > 1 else ".")
    watcher.start()

    signal.signal(signal.SIGINT, lambda signal, frame: watcher.join())
    signal.pause()
您可能应该更改
Cellery\u CMD
,或任何其他全局变量。

Cellery
--autoreload
不起作用

因为您使用的是django,所以可以为此编写一个管理命令。 Django具有autoreload实用程序,runserver使用该实用程序在代码更改时重新启动WSGI服务器

同样的功能也可用于重新装载芹菜工人。创建一个名为芹菜的单独管理命令。编写一个函数以终止现有工作进程并启动新工作进程。现在将此函数挂接到autoreload,如下所示

import shlex
import subprocess

from django.core.management.base import BaseCommand
from django.utils import autoreload


def restart_celery():
    cmd = 'pkill celery'
    subprocess.call(shlex.split(cmd))
    cmd = 'celery worker -l info -A foo'
    subprocess.call(shlex.split(cmd))


class Command(BaseCommand):

    def handle(self, *args, **options):
        print('Starting celery worker with autoreload...')

        # For Django>=2.2
        autoreload.run_with_reloader(restart_celery) 

        # For django<2.1
        # autoreload.main(restart_celery)
导入shlex
导入子流程
从django.core.management.base导入BaseCommand
从django.utils导入自动加载
def restart_芹菜():
cmd='pkill芹菜'
子进程调用(shlex.split(cmd))
cmd='芹菜工人-l信息-A foo'
子进程调用(shlex.split(cmd))
类命令(BaseCommand):
def句柄(自身、*参数、**选项):
打印('正在启动芹菜工人与autoreload…')
#对于Django>=2.2
自动加载。使用重新加载程序运行(重新启动芹菜)

#对于django这是我在django工作的方式:

#worker_dev.py(放在manage.py旁边)
从django.utils导入自动加载
def run_芹菜():
从projectname导入芹菜应用程序
芹菜应用程序工人主([“-Aprojectname”、“-linfo”、“-Psolo”])
打印(“使用autoreload启动芹菜工人…”)
自动加载。使用加载程序运行(运行芹菜)
然后运行
python worker\u dev.py
。这具有在docker容器内工作的优点。

您可以使用watchmedo

pip install watchdog
watchmedo auto-restart --directory=./ --pattern=*.py --recursive -- celery worker --app=worker.app --concurrency=1 --loglevel=INFO
通过watchmedo间接启动芹菜工人

pip install watchdog
watchmedo auto-restart --directory=./ --pattern=*.py --recursive -- celery worker --app=worker.app --concurrency=1 --loglevel=INFO

您可能希望使用
gsub(“/”,”)而不是
sub(“/”,”)?如果您的模块之一包含
*py
(即任何后跟
py
)的字符,则这三个字符将从模块名中删除(在我的情况下,将“copy_thing”更改为“c_thing”)。要修复它,请将
sub(“.py”,”)
更新为
sub(\\.py$”)
。似乎从
4.0版开始,
--autoreload
功能:(在新芹菜中,
--autoreload
选项已被弃用,不再有效。最好是发送广播消息关闭代理,并在顶部放置一些东西,如
supervisord
自动重新启动代理。我在生产中使用该选项,远程代理从web应用程序下载包启动时自动加载。这将引发异常-
AttributeError:module'django.utils.autoreload'没有属性'main'
如果您使用的是django 2.2+,
autoreload.main()
已被删除。请使用
autoreload.run\u并重新加载(restart\u芹菜)
相反。如何只运行一个芹菜worker?此示例运行两个实例。此代码不会真正为我重新加载worker,因为它只跟踪由命令而不是worker加载的python文件。它应该跟踪属于django项目的所有文件,因为自动重新加载由django处理。如果它不是django的一部分,则不会重新加载。br在Windows上可能directory=./参数应该在Windows上以不同的方式写入。我认为这与此无关,我得到了
AttributeError:模块“os”没有属性“setsid”
作为错误。谢谢,这工作得很好!