Halo, Habr! Saya akan menceritakan kisah kejenuhan profesional saya.
Kebetulan saya benci treadmill rutin. Saya punya beberapa proyek menggunakan Seledri . Setiap kali tugas menjadi lebih rumit daripada 2 + 2 = 5
, templat solusi dikurangi untuk membuat kelas yang melakukan tugas, dan fungsi starter yang dapat digunakan Celery - pelat ketel. Dalam artikel ini saya akan memberi tahu Anda bagaimana saya berjuang dengan pelat dan apa yang terjadi.

Titik awal
Pertimbangkan tugas seledri yang biasa. Ada kelas yang melakukan tugas, dan fungsi starter yang membuat instance kelas dan memulai salah satu metodenya, di mana semua logika tugas diimplementasikan dan penanganan kesalahan diwarisi:
class MyTask( FirstMixin, SecondMixin, ThirdMixin, ): def main(self): data = self.do_something() response = self.remote_call(data) parsed = self.parser(response) return self.process(parsed) @app.task(bind=True) def my_task(self, arg1, arg2): instance = MyTask( celery_task=self, arg1=arg1, arg2=arg2, ) return instance.full_task()
Pada saat yang sama, metode full_task
mencakup panggilan ke main
, namun, ia juga menangani penanganan kesalahan, pencatatan dan omong kosong lain yang tidak terkait langsung dengan tugas utama.
Ide taskclass
Pada akar taskclass terletak ide sederhana: di kelas dasar, Anda dapat menentukan metode kelas task
, menerapkan perilaku fungsi starter di dalamnya, dan kemudian mewarisi:
class BaseTask: def __init__(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) def full_task(self): try: return self.main() except: self.celery_task.retry(countdown=30) @classmethod def task(cls, task, **kwargs): self = cls( celery_task=celery_task, **kwargs, ) return self.full_task()
Semua kebosanan tambahan dikumpulkan di kelas dasar. Kami tidak akan kembali padanya lagi. Kami menyadari logika tugas:
@app.taskcls(bind=True) class MyTask( BaseTask, FirstMixin, SecondMixin, ThirdMixin, ): def main(self): data = self.do_something() response = self.remote_call(data) parsed = self.parser(response) return self.process(parsed)
Tidak ada lagi kulit, lebih baik. Namun, bagaimana dengan titik masuknya?
MyTask.task.delay(...)
MyTask.task
memiliki semua metode tugas yang biasa: delay
, apply_async
, dan, secara umum, itu.
Sekarang argumen sang dekorator. Sangat menarik untuk menarik bind = True
ke dalam setiap tugas. Apakah mungkin untuk melewatkan argumen default melalui kelas dasar?
class BaseTask: class MetaTask: bind = True def __init__(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) def full_task(self): try: return self.main() except: self.celery_task.retry(countdown=30) @classmethod def task(cls, task, **kwargs): self = cls( celery_task=celery_task, **kwargs, ) return self.full_task()
Kelas MetaTask
bersarang berisi argumen default dan akan tersedia untuk semua kelas anak. Menariknya, itu juga bisa diwariskan:
class BaseHasTimeout(BaseTask): class MetaTask(BaseTask.MetaTask): timeout = 42
Argumen yang diteruskan ke dekorator @app.taskcls
memiliki prioritas tertinggi:
@app.taskcls(timeout=20) class MyTask( BaseHasTimeout, FirstMixin, SecondMixin, ThirdMixin, ): def main(self): ...
Akibatnya, batas waktu untuk tugas tersebut adalah 20.
Melampaui
Dalam aplikasi web, seringkali ada kebutuhan untuk memulai tugas dari pandangan. Dalam hal adhesi tinggi, tampilan dan tugas dapat digabungkan:
class BaseViewTask: @classmethod def task(cls, **kwargs):
By the way, untuk menghindari tabrakan nama, kelas bersarang disebut MetaTask
, bukan Meta
, seperti dalam Django.
Kesimpulan
Fungsi ini diharapkan di Celery 4.5 . Namun, saya juga menyiapkan paket yang memungkinkan Anda untuk mencoba dekorator taskcls hari ini. Gagasan dari paket ini adalah ketika Anda memutakhirkan Seledri ke versi 4.5, Anda dapat menghapus impornya tanpa mengubah satu baris kode pun.