Rumah >pembangunan bahagian belakang >Tutorial Python >Muat semula pekerja Saderi secara automatik dengan arahan Django tersuai

Muat semula pekerja Saderi secara automatik dengan arahan Django tersuai

WBOY
WBOYasal
2024-07-22 09:40:111332semak imbas

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 pemuatan 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 tersebut akan dimodelkan mengikut runserver dan kami akan melihat cara pemuatan semula automatik Django berfungsi di bawah penutup.

Sebelum kita mulakan

Siaran ini menganggap bahawa anda mempunyai apl 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. Oleh kerana Django menyediakan muat semula automatik melalui arahan runsevernya, kami akan menggunakan kod sumber runserver sebagai asas perintah 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 membuat 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,
        )

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. Bagaimanakah 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)

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 memantau 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.

Pemeriksaan & migrasi sistem

Secara lalai, arahan Django melakukan semakan sistem sebelum mereka menjalankan kaedah handle() mereka. Walau bagaimanapun, dalam kes runserver & perintah runworker tersuai kami, kami akan menangguhkan menjalankannya sehingga kami berada di dalam panggilan balik yang kami sediakan untuk run_with_reloader(). Dalam kes kami, ini ialah kaedah run_worker() kami. Ini membolehkan kami menjalankan arahan dengan muat semula automatik sambil membetulkan semakan sistem yang rosak.

Untuk menangguhkan menjalankan semakan sistem, nilai atribut require_system_checks ditetapkan kepada senarai kosong dan semakan dilakukan dengan memanggil self.check() dalam badan run_worker(). Seperti runserver, perintah runworker tersuai kami juga menyemak untuk melihat sama ada semua migrasi telah dijalankan dan ia memaparkan amaran jika terdapat migrasi yang belum selesai.

Oleh kerana kami sudah melakukan semakan sistem Django dalam kaedah run_worker(), kami melumpuhkan semakan sistem dalam Celery dengan menghantarnya bendera --skip-checks untuk mengelakkan kerja pendua.

Semua kod yang berkaitan dengan semakan dan pemindahan sistem telah ditarik balik terus daripada kod sumber arahan runserver.

celery_app.worker_main()

Pelaksanaan kami melancarkan pekerja Saderi terus daripada Python menggunakan celery_app.worker_main() dan bukannya menggunakan Saderi.

on_worker_init()

Kod ini dilaksanakan apabila pekerja dimulakan, memaparkan tarikh & masa, versi Django dan arahan untuk berhenti. Ia dimodelkan selepas maklumat yang dipaparkan apabila runserver but.

Pelat dandang runserver lain

Barisan berikut juga telah diangkat daripada sumber runserver:

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

Tahap log

Arahan tersuai kami mempunyai tahap log boleh dikonfigurasikan sekiranya pembangun ingin melaraskan tetapan daripada CLI tanpa mengubah suai kod.

Melangkah lebih jauh

Saya mencucuk-dan-menucuk kod sumber untuk Django & Celery untuk membina pelaksanaan ini, dan terdapat banyak peluang untuk melanjutkannya. Anda boleh mengkonfigurasi arahan untuk menerima lebih banyak hujah pekerja Celery. Sebagai alternatif, anda boleh mencipta perintah manage.py tersuai yang memuatkan semula sebarang perintah shell secara automatik seperti yang dilakukan oleh David Browne dalam Gist ini.

Jika anda mendapati ini berguna, sila tinggalkan suka atau komen. Terima kasih kerana membaca.

Atas ialah kandungan terperinci Muat semula pekerja Saderi secara automatik dengan arahan Django tersuai. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
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
Artikel sebelumnya:Fungsi Python 4Artikel seterusnya:Fungsi Python 4