Muat semula pekerja Saderi secara automatik dengan arahan Django tersuai

WBOY
Lepaskan: 2024-07-22 09:40:11
asal
1003 人浏览过

Automatically reload Celery workers with a custom Django command

Saderi sebelum ini mempunyai bendera --autoreload yang telah dialih keluar. Walau bagaimanapun, Django mempunyai muat semula automatik terbina dalam perintah runserver manage.pynya. Ketiadaan muat semula automatik dalam pekerja Saderi mencipta pengalaman pembangunan yang mengelirukan: mengemas kini kod Python menyebabkan pelayan Django memuat semula dengan kod semasa, tetapi sebarang tugas yang dijalankan oleh pelayan akan menjalankan kod lapuk dalam pekerja Saderi.

Siaran ini akan menunjukkan kepada anda cara membina perintah runworker manage.py tersuai yang memuatkan semula pekerja Saderi secara automatik semasa pembangunan. Perintah itu akan dimodelkan mengikut runserver, dan kami akan melihat cara pemuatan semula automatik Django berfungsi di bawah hud.

Sebelum kita mulakan

Siaran ini menganggap bahawa anda mempunyai aplikasi Django dengan Celery telah dipasang (panduan). Ia juga menganggap anda mempunyai pemahaman tentang perbezaan antara projek dan aplikasi dalam Django.

Semua pautan ke kod sumber dan dokumentasi adalah untuk versi semasa Django dan Celery pada masa penerbitan (Julai, 2024). Jika anda membaca ini pada masa hadapan yang jauh, keadaan mungkin telah berubah.

Akhir sekali, direktori projek utama akan dinamakan my_project dalam contoh siaran.

Penyelesaian: arahan tersuai

Kami akan mencipta perintah manage.py tersuai yang dipanggil runworker. Kerana Django menyediakan muat semula automatik melalui arahan runsevernya, kami akan menggunakan kod sumber runserver sebagai asas arahan tersuai kami.

Anda boleh mencipta arahan dalam Django dengan membuat pengurusan/perintah/ direktori dalam mana-mana aplikasi projek anda. Setelah direktori telah dibuat, anda boleh meletakkan fail Python dengan nama perintah yang anda ingin buat dalam direktori tersebut (dokumen).

Dengan mengandaikan projek anda mempunyai aplikasi bernama polls, kami akan mencipta fail di polls/management/commands/runworker.py dan menambah kod berikut:

# polls/management/commands/runworker.py

import sys
from datetime import datetime

from celery.signals import worker_init

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

from my_project.celery import app as celery_app


class Command(BaseCommand):
    help = "Starts a Celery worker instance with auto-reloading for development."

    # Validation is called explicitly each time the worker instance is reloaded.
    requires_system_checks = []
    suppressed_base_arguments = {"--verbosity", "--traceback"}

    def add_arguments(self, parser):
        parser.add_argument(
            "--skip-checks",
            action="store_true",
            help="Skip system checks.",
        )
        parser.add_argument(
            "--loglevel",
            choices=("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "FATAL"),
            type=str.upper,  # Transforms user input to uppercase.
            default="INFO",
        )

    def handle(self, *args, **options):
        autoreload.run_with_reloader(self.run_worker, **options)

    def run_worker(self, **options):
        # If an exception was silenced in ManagementUtility.execute in order
        # to be raised in the child process, raise it now.
        autoreload.raise_last_exception()

        if not options["skip_checks"]:
            self.stdout.write("Performing system checks...\n\n")
            self.check(display_num_errors=True)

        # Need to check migrations here, so can't use the
        # requires_migrations_check attribute.
        self.check_migrations()

        # Print Django info to console when the worker initializes.
        worker_init.connect(self.on_worker_init)

        # Start the Celery worker.
        celery_app.worker_main(
            [
                "--app",
                "my_project",
                "--skip-checks",
                "worker",
                "--loglevel",
                options["loglevel"],
            ]
        )

    def on_worker_init(self, sender, **kwargs):
        quit_command = "CTRL-BREAK" if sys.platform == "win32" else "CONTROL-C"

        now = datetime.now().strftime("%B %d, %Y - %X")
        version = self.get_version()
        print(
            f"{now}\n"
            f"Django version {version}, using settings {settings.SETTINGS_MODULE!r}\n"
            f"Quit the worker instance with {quit_command}.",
            file=self.stdout,
        )
Salin selepas log masuk

PENTING: Pastikan anda menggantikan semua kejadian my_project dengan nama projek Django anda.

Jika anda ingin menyalin dan menampal kod ini dan meneruskan pengaturcaraan anda, anda boleh berhenti di sini dengan selamat tanpa membaca sisa siaran ini. Ini adalah penyelesaian elegan yang akan memberi perkhidmatan yang baik kepada anda semasa anda membangunkan projek Django & Celery anda. Walau bagaimanapun, jika anda ingin mengetahui lebih lanjut tentang cara ia berfungsi, teruskan membaca.

Cara ia berfungsi (pilihan)

Daripada menyemak kod ini baris demi baris, saya akan membincangkan bahagian yang paling menarik mengikut topik. Jika anda belum biasa dengan arahan tersuai Django, anda mungkin mahu menyemak dokumen sebelum meneruskan.

Muat semula automatik

Bahagian ini terasa paling ajaib. Dalam badan kaedah handle() arahan, terdapat panggilan ke autoreload.run_with_reloader() dalaman Django. Ia menerima fungsi panggil balik yang akan dilaksanakan setiap kali fail Python ditukar dalam projek. Bagaimana sebenarnya itu berfungsi?

Mari kita lihat versi ringkas kod sumber fungsi autoreload.run_with_reloader(). Fungsi yang dipermudahkan menulis semula, menyelaraskan dan memadamkan kod untuk memberikan kejelasan tentang pengendaliannya.

# NOTE: This has been dramatically pared down for clarity.

def run_with_reloader(callback_func, *args, **kwargs):
    # NOTE: This will evaluate to False the first time it is run.
    is_inside_subprocess = os.getenv("RUN_MAIN") == "true"

    if is_inside_subprocess:
        # The reloader watches for Python file changes.
        reloader = get_reloader()

        django_main_thread = threading.Thread(
            target=callback_func, args=args, kwargs=kwargs
        )
        django_main_thread.daemon = True
        django_main_thread.start()

        # When the code changes, the reloader exits with return code 3.
        reloader.run(django_main_thread)

    else:
        # Returns Python path and the arguments passed to the command.
        # Example output: ['/path/to/python', './manage.py', 'runworker']
        args = get_child_arguments()

        subprocess_env = {**os.environ, "RUN_MAIN": "true"}
        while True:
            # Rerun the manage.py command in a subprocess.
            p = subprocess.run(args, env=subprocess_env, close_fds=False)
            if p.returncode != 3:
                sys.exit(p.returncode)
Salin selepas log masuk

Apabila manage.py runworker dijalankan dalam baris arahan, ia akan terlebih dahulu memanggil kaedah handle() yang akan memanggil run_with_reloader().

Di dalam run_with_reloader(), ia akan menyemak sama ada pembolehubah persekitaran yang dipanggil RUN_MAIN mempunyai nilai "true". Apabila fungsi pertama kali dipanggil, RUN_MAIN sepatutnya tidak mempunyai nilai.

Apabila RUN_MAIN tidak ditetapkan kepada "true", run_with_reloader() akan memasuki gelung. Di dalam gelung, ia akan memulakan subproses yang menjalankan semula manage.py [nama_perintah] yang telah dihantar, kemudian tunggu sehingga subproses itu keluar. Jika subproses keluar dengan kod pulangan 3, lelaran gelung seterusnya akan memulakan subproses baharu dan menunggu. Gelung akan berjalan sehingga subproses mengembalikan kod keluar yang bukan 3 (atau sehingga pengguna keluar dengan ctrl + c). Sebaik sahaja ia mendapat kod pulangan bukan 3, ia akan keluar dari program sepenuhnya.

Subproses yang dihasilkan menjalankan perintah manage.py sekali lagi (dalam kes kami manage.py runworker), dan sekali lagi arahan itu akan memanggil run_with_reloader(). Kali ini, RUN_MAIN akan ditetapkan kepada "true" kerana arahan sedang berjalan dalam subproses.

Sekarang run_with_reloader() tahu ia sedang dalam proses kecil, ia akan mendapat pemuat semula yang memerhatikan perubahan fail, meletakkan fungsi panggil balik yang disediakan dalam urutan dan menghantarnya kepada pemuat semula yang mula melihat perubahan.

Apabila pemuat semula mengesan perubahan fail, ia menjalankan sys.exit(3). Ini keluar daripada subproses, yang mencetuskan lelaran seterusnya bagi gelung daripada kod yang melahirkan subproses. Seterusnya, subproses baharu dilancarkan yang menggunakan versi kod yang dikemas kini.

Vérifications et migrations du système

Par défaut, les commandes Django effectuent des vérifications du système avant d'exécuter leur méthode handle(). Cependant, dans le cas de runserver et de notre commande runworker personnalisée, nous souhaiterons reporter leur exécution jusqu'à ce que nous soyons dans le rappel que nous fournissons à run_with_reloader(). Dans notre cas, il s'agit de notre méthode run_worker(). Cela nous permet d'exécuter la commande avec un rechargement automatique tout en corrigeant les vérifications système interrompues.

Pour reporter l'exécution des vérifications du système, la valeur de l'attribut require_system_checks est définie sur une liste vide et les vérifications sont effectuées en appelant self.check() dans le corps de run_worker(). Comme runserver, notre commande runworker personnalisée vérifie également si toutes les migrations ont été exécutées et affiche un avertissement s'il y a des migrations en attente.

Parce que nous effectuons déjà les vérifications du système de Django dans la méthode run_worker(), nous désactivons les vérifications du système dans Celery en lui passant l'indicateur --skip-checks pour éviter les travaux en double.

Tout le code relatif aux vérifications et aux migrations du système a été extrait directement du code source de la commande runserver.

céleri_app.worker_main()

Notre implémentation lance le travailleur Celery directement depuis Python en utilisant celery_app.worker_main() plutôt que de passer par Celery.

on_worker_init()

Ce code s'exécute lorsque le travailleur est initialisé, affichant la date et l'heure, la version de Django et la commande pour quitter. Il est calqué sur les informations qui s'affichent au démarrage de runserver.

Autre passe-partout du serveur d'exécution

Les lignes suivantes ont également été supprimées de la source runserver :

  • suppressed_base_arguments = {"--verbosity", "--traceback"}
  • autoreload.raise_last_exception()

Niveau de journalisation

Notre commande personnalisée a un niveau de journalisation configurable au cas où le développeur souhaite ajuster le paramètre à partir de la CLI sans modifier le code.

Aller plus loin

J'ai fouillé et poussé le code source de Django & Celery pour construire cette implémentation, et il existe de nombreuses opportunités pour l'étendre. Vous pouvez configurer la commande pour accepter davantage d'arguments de travail de Celery. Alternativement, vous pouvez créer une commande manage.py personnalisée qui recharge automatiquement n'importe quelle commande shell comme David Browne l'a fait dans cet Gist.

Si vous avez trouvé cela utile, n'hésitez pas à laisser un like ou un commentaire. Merci d'avoir lu.

以上是Muat semula pekerja Saderi secara automatik dengan arahan Django tersuai的详细内容。更多信息请关注PHP中文网其他相关文章!

sumber:dev.to
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan
Tentang kita Penafian Sitemap
Laman web PHP Cina:Latihan PHP dalam talian kebajikan awam,Bantu pelajar PHP berkembang dengan cepat!