django-simpletask5 0.2.7__tar.gz → 0.2.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/PKG-INFO +41 -4
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/README.md +40 -3
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/publisher.py +12 -4
- django_simpletask5-0.2.8/django_simpletask5/migrations/0013_alter_taskexecution_task_id.py +24 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/models.py +50 -9
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5.egg-info/PKG-INFO +41 -4
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5.egg-info/SOURCES.txt +1 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/pyproject.toml +1 -1
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/LICENSE +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/__init__.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/admin.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/apps.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/__init__.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/cronjob_registry.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/defaults.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/executor_scanner.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/lock.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/logging_utils.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/message_queue.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/reclaim.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/security.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/signals.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/worker_registry.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/dashboards.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/__init__.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/archive.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/base.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/bash_script.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/exceptions.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/loader.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/ping_pong.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/python_script.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/retry_timeout.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/simple_request.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/status_check.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.po +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/management/__init__.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/management/commands/__init__.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/management/commands/django_simpletask_archive.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/management/commands/django_simpletask_crontab.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/management/commands/django_simpletask_executor.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/management/commands/django_simpletask_queues.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/management/commands/django_simpletask_sync_cronjobs.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0001_initial.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0002_taskexecution_done_tasks_count_and_more.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0003_alter_cronjob_options_remove_cronjob_name_and_more.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0004_alter_cronjob_is_modified_by_user.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0005_alter_cronjob_is_modified_by_user_help.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0006_alter_cronjob_options_alter_taskexecution_created_at_and_more.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0007_rename_task_model_to_executor_class_in_stat.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0008_taskexecution_pickup_timeout_seconds.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0009_add_cronjob_app_label.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0010_taskexecution_id_alter_taskexecution_execution_id.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0011_taskexecutionstat_canceled_count.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0012_alter_taskexecution_status.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/__init__.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/services/__init__.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/services/archive.py +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/static/django_simpletask5/css/dashboard.css +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/static/django_simpletask5/js/select2_executor.js +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5.egg-info/dependency_links.txt +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5.egg-info/requires.txt +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5.egg-info/top_level.txt +0 -0
- {django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-simpletask5
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Lightweight async task execution framework for Django
|
|
5
5
|
Author-email: rRR0VrFP <rrr0vrfp@qq.com>
|
|
6
6
|
Maintainer-email: rRR0VrFP <rrr0vrfp@qq.com>
|
|
@@ -278,7 +278,7 @@ class OrderTask(Task):
|
|
|
278
278
|
```
|
|
279
279
|
|
|
280
280
|
要点:
|
|
281
|
-
- 主键为 Django 默认自增 `id
|
|
281
|
+
- 主键为 Django 默认自增 `id`。`Task` 不再内置 `task_id` 字段,业务标识由业务模型自行定义(自定义主键或自然键均可)
|
|
282
282
|
- 保存 / 删除 / 批量创建自动触发事件,详见「事件处理机制」
|
|
283
283
|
- 事件上下文、update 字段判定、delete 拦截等行为的完整说明见对应章节
|
|
284
284
|
|
|
@@ -354,7 +354,7 @@ OrderTask.objects.create(order_id='ORD-001', customer_name='Test', amount=Decima
|
|
|
354
354
|
| `update` | 字段值发生变化后保存 | `task_id`、`trigger_event`、`data`、`old_data`、`new_data`、`changed_fields` |
|
|
355
355
|
| `delete` | 调用 `instance.delete()` | `task_id`、`trigger_event`、`data` |
|
|
356
356
|
|
|
357
|
-
执行器通过 `execution.get_context_dict()`
|
|
357
|
+
执行器通过 `execution.get_context_dict()` 获取以上上下文。其中 `task_id` 是业务对象主键的字符串表示(如 `"42"`、UUID 字符串或自定义键);业务对象反查见「模型与生命周期」。
|
|
358
358
|
|
|
359
359
|
#### update 事件的字段变更判定
|
|
360
360
|
|
|
@@ -393,6 +393,8 @@ class OrderDeleteExecutor(BaseExecutor):
|
|
|
393
393
|
|
|
394
394
|
这样实现删除操作与外部系统清理的解耦,且支持失败重试。
|
|
395
395
|
|
|
396
|
+
`confirm_delete()` 与 `execution.get_task()` 都会通过 `TaskExecution.task_id`(业务主键字符串)反查业务对象,反查逻辑由业务模型上的 `get_task_instance()` 提供(见「模型与生命周期」)。
|
|
397
|
+
|
|
396
398
|
### 事件 → 执行器映射
|
|
397
399
|
|
|
398
400
|
每个事件消息最终要由一个执行器处理。`executor_class` 声明「事件名 → 执行器类路径」的分派表,可以是字符串(所有事件共用同一个执行器)或字典(按事件分派):
|
|
@@ -568,10 +570,38 @@ class OrderCreateExecutor(BaseExecutor):
|
|
|
568
570
|
|
|
569
571
|
所有业务任务模型继承 `Task`:
|
|
570
572
|
|
|
571
|
-
-
|
|
573
|
+
- 主键为 Django 默认自增 `id`,无需自定义;**不内置** `task_id` 字段,业务标识由业务模型按需定义
|
|
572
574
|
- 提供 `trigger(event, extra_context)`、`skip_publish()`、`get_executor_class_path(event)`、`get_queue_name(event)` 等方法
|
|
573
575
|
- 保存、删除、批量创建自动触发事件(见「事件处理机制」)
|
|
574
576
|
|
|
577
|
+
#### 业务对象反查:`get_task_instance()`
|
|
578
|
+
|
|
579
|
+
`TaskExecution.task_id` 存的是业务对象主键的字符串表示(`CharField`)。需要反查业务对象时(`execution.get_task()` / `execution.confirm_delete()`),框架先通过 `task_model` 解析出模型类,再调用该类的 `get_task_instance(task_id)` 完成查询。
|
|
580
|
+
|
|
581
|
+
默认实现在 `Task` 基类中按主键 `pk` 查询,并通过内置的 `_coerce_task_pk()` 兼容常见主键类型:
|
|
582
|
+
|
|
583
|
+
- `AutoField` / `BigAutoField` / `SmallAutoField` — 字符串转为 `int`
|
|
584
|
+
- `UUIDField` — 字符串转为 `uuid.UUID`
|
|
585
|
+
- `CharField` / `TextField` 等字符串主键 — 原样使用
|
|
586
|
+
|
|
587
|
+
```python
|
|
588
|
+
task = execution.get_task() # 返回业务对象(反查不到返回 None)
|
|
589
|
+
execution.confirm_delete() # 反查并真实删除业务对象
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
业务模型使用自定义主键或非主键自然键时,**重载 `get_task_instance()`** 即可,框架会自动改走你的实现:
|
|
593
|
+
|
|
594
|
+
```python
|
|
595
|
+
class OrderTask(Task):
|
|
596
|
+
code = models.CharField(max_length=64, unique=True) # 非主键自然键
|
|
597
|
+
|
|
598
|
+
@classmethod
|
|
599
|
+
def get_task_instance(cls, task_id):
|
|
600
|
+
if task_id is None:
|
|
601
|
+
return None
|
|
602
|
+
return cls.objects.filter(code=task_id).first()
|
|
603
|
+
```
|
|
604
|
+
|
|
575
605
|
### TaskExecution 状态机
|
|
576
606
|
|
|
577
607
|
每条事件消息对应一条 `TaskExecution` 记录,状态流转如下:
|
|
@@ -702,6 +732,13 @@ Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保
|
|
|
702
732
|
|
|
703
733
|
## Releases
|
|
704
734
|
|
|
735
|
+
### 0.2.8
|
|
736
|
+
|
|
737
|
+
- **移除 `Task` 冗余的 `task_id` 字段** — `TaskExecution.task_id` 由 UUID 改为 `CharField(max_length=255)`,直接用于存放业务对象主键字符串(`str(instance.pk)`),不再维护一份与业务主键重复的 UUID。消息上下文中的 `task_id` 字段名不变,值语义从 UUID 变为业务主键字符串(如 `"42"`)
|
|
738
|
+
- **业务对象反查下沉到模型层** — 新增 `Task.get_task_instance(task_id)` 类方法作为默认反查实现(按 `pk` 查询,内置 `_coerce_task_pk()` 兼容 `int` / `UUID` / 字符串主键);`TaskExecution.get_task()` / `confirm_delete()` 改为解析 `task_model` 后委托给该方法。业务模型使用自定义主键或自然键时重载 `get_task_instance()` 即可
|
|
739
|
+
- **新增迁移** — `django_simpletask5.0013_alter_taskexecution_task_id`(字段改 CharField)、`django_simpletask5_example.0003_remove_ordertask_task_id`
|
|
740
|
+
- **破坏性变更**: 依赖 `TaskExecution.task_id` 为 UUID 类型的外部消费方需适配为业务主键字符串;业务 `Task` 子类若曾使用内置 `task_id` 字段需移除
|
|
741
|
+
|
|
705
742
|
### 0.2.7
|
|
706
743
|
|
|
707
744
|
- **运行中任务回收机制重构** — 取消原先按 `expire_time` 将 running 直接判为超时的语义;`StatusCheckExecutor` 仅兜底回收长期未被 Worker 获取的 pending。新增 `core/reclaim`,由 crontab 每 ~5 秒基于执行锁探测运行中任务:锁已过期(执行器异常退出)即快速重投或直接置失败,降低孤儿任务滞留时间
|
|
@@ -238,7 +238,7 @@ class OrderTask(Task):
|
|
|
238
238
|
```
|
|
239
239
|
|
|
240
240
|
要点:
|
|
241
|
-
- 主键为 Django 默认自增 `id
|
|
241
|
+
- 主键为 Django 默认自增 `id`。`Task` 不再内置 `task_id` 字段,业务标识由业务模型自行定义(自定义主键或自然键均可)
|
|
242
242
|
- 保存 / 删除 / 批量创建自动触发事件,详见「事件处理机制」
|
|
243
243
|
- 事件上下文、update 字段判定、delete 拦截等行为的完整说明见对应章节
|
|
244
244
|
|
|
@@ -314,7 +314,7 @@ OrderTask.objects.create(order_id='ORD-001', customer_name='Test', amount=Decima
|
|
|
314
314
|
| `update` | 字段值发生变化后保存 | `task_id`、`trigger_event`、`data`、`old_data`、`new_data`、`changed_fields` |
|
|
315
315
|
| `delete` | 调用 `instance.delete()` | `task_id`、`trigger_event`、`data` |
|
|
316
316
|
|
|
317
|
-
执行器通过 `execution.get_context_dict()`
|
|
317
|
+
执行器通过 `execution.get_context_dict()` 获取以上上下文。其中 `task_id` 是业务对象主键的字符串表示(如 `"42"`、UUID 字符串或自定义键);业务对象反查见「模型与生命周期」。
|
|
318
318
|
|
|
319
319
|
#### update 事件的字段变更判定
|
|
320
320
|
|
|
@@ -353,6 +353,8 @@ class OrderDeleteExecutor(BaseExecutor):
|
|
|
353
353
|
|
|
354
354
|
这样实现删除操作与外部系统清理的解耦,且支持失败重试。
|
|
355
355
|
|
|
356
|
+
`confirm_delete()` 与 `execution.get_task()` 都会通过 `TaskExecution.task_id`(业务主键字符串)反查业务对象,反查逻辑由业务模型上的 `get_task_instance()` 提供(见「模型与生命周期」)。
|
|
357
|
+
|
|
356
358
|
### 事件 → 执行器映射
|
|
357
359
|
|
|
358
360
|
每个事件消息最终要由一个执行器处理。`executor_class` 声明「事件名 → 执行器类路径」的分派表,可以是字符串(所有事件共用同一个执行器)或字典(按事件分派):
|
|
@@ -528,10 +530,38 @@ class OrderCreateExecutor(BaseExecutor):
|
|
|
528
530
|
|
|
529
531
|
所有业务任务模型继承 `Task`:
|
|
530
532
|
|
|
531
|
-
-
|
|
533
|
+
- 主键为 Django 默认自增 `id`,无需自定义;**不内置** `task_id` 字段,业务标识由业务模型按需定义
|
|
532
534
|
- 提供 `trigger(event, extra_context)`、`skip_publish()`、`get_executor_class_path(event)`、`get_queue_name(event)` 等方法
|
|
533
535
|
- 保存、删除、批量创建自动触发事件(见「事件处理机制」)
|
|
534
536
|
|
|
537
|
+
#### 业务对象反查:`get_task_instance()`
|
|
538
|
+
|
|
539
|
+
`TaskExecution.task_id` 存的是业务对象主键的字符串表示(`CharField`)。需要反查业务对象时(`execution.get_task()` / `execution.confirm_delete()`),框架先通过 `task_model` 解析出模型类,再调用该类的 `get_task_instance(task_id)` 完成查询。
|
|
540
|
+
|
|
541
|
+
默认实现在 `Task` 基类中按主键 `pk` 查询,并通过内置的 `_coerce_task_pk()` 兼容常见主键类型:
|
|
542
|
+
|
|
543
|
+
- `AutoField` / `BigAutoField` / `SmallAutoField` — 字符串转为 `int`
|
|
544
|
+
- `UUIDField` — 字符串转为 `uuid.UUID`
|
|
545
|
+
- `CharField` / `TextField` 等字符串主键 — 原样使用
|
|
546
|
+
|
|
547
|
+
```python
|
|
548
|
+
task = execution.get_task() # 返回业务对象(反查不到返回 None)
|
|
549
|
+
execution.confirm_delete() # 反查并真实删除业务对象
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
业务模型使用自定义主键或非主键自然键时,**重载 `get_task_instance()`** 即可,框架会自动改走你的实现:
|
|
553
|
+
|
|
554
|
+
```python
|
|
555
|
+
class OrderTask(Task):
|
|
556
|
+
code = models.CharField(max_length=64, unique=True) # 非主键自然键
|
|
557
|
+
|
|
558
|
+
@classmethod
|
|
559
|
+
def get_task_instance(cls, task_id):
|
|
560
|
+
if task_id is None:
|
|
561
|
+
return None
|
|
562
|
+
return cls.objects.filter(code=task_id).first()
|
|
563
|
+
```
|
|
564
|
+
|
|
535
565
|
### TaskExecution 状态机
|
|
536
566
|
|
|
537
567
|
每条事件消息对应一条 `TaskExecution` 记录,状态流转如下:
|
|
@@ -662,6 +692,13 @@ Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保
|
|
|
662
692
|
|
|
663
693
|
## Releases
|
|
664
694
|
|
|
695
|
+
### 0.2.8
|
|
696
|
+
|
|
697
|
+
- **移除 `Task` 冗余的 `task_id` 字段** — `TaskExecution.task_id` 由 UUID 改为 `CharField(max_length=255)`,直接用于存放业务对象主键字符串(`str(instance.pk)`),不再维护一份与业务主键重复的 UUID。消息上下文中的 `task_id` 字段名不变,值语义从 UUID 变为业务主键字符串(如 `"42"`)
|
|
698
|
+
- **业务对象反查下沉到模型层** — 新增 `Task.get_task_instance(task_id)` 类方法作为默认反查实现(按 `pk` 查询,内置 `_coerce_task_pk()` 兼容 `int` / `UUID` / 字符串主键);`TaskExecution.get_task()` / `confirm_delete()` 改为解析 `task_model` 后委托给该方法。业务模型使用自定义主键或自然键时重载 `get_task_instance()` 即可
|
|
699
|
+
- **新增迁移** — `django_simpletask5.0013_alter_taskexecution_task_id`(字段改 CharField)、`django_simpletask5_example.0003_remove_ordertask_task_id`
|
|
700
|
+
- **破坏性变更**: 依赖 `TaskExecution.task_id` 为 UUID 类型的外部消费方需适配为业务主键字符串;业务 `Task` 子类若曾使用内置 `task_id` 字段需移除
|
|
701
|
+
|
|
665
702
|
### 0.2.7
|
|
666
703
|
|
|
667
704
|
- **运行中任务回收机制重构** — 取消原先按 `expire_time` 将 running 直接判为超时的语义;`StatusCheckExecutor` 仅兜底回收长期未被 Worker 获取的 pending。新增 `core/reclaim`,由 crontab 每 ~5 秒基于执行锁探测运行中任务:锁已过期(执行器异常退出)即快速重投或直接置失败,降低孤儿任务滞留时间
|
|
@@ -39,7 +39,7 @@ def create_and_publish(task, trigger_event, extra_context=None):
|
|
|
39
39
|
queue_name = task.get_queue_name(event=trigger_event)
|
|
40
40
|
|
|
41
41
|
context = {
|
|
42
|
-
'task_id':
|
|
42
|
+
'task_id': _task_identity(task),
|
|
43
43
|
'trigger_event': trigger_event,
|
|
44
44
|
'data': _model_to_dict(task),
|
|
45
45
|
}
|
|
@@ -49,7 +49,7 @@ def create_and_publish(task, trigger_event, extra_context=None):
|
|
|
49
49
|
serialized_context = _serialize_context(context)
|
|
50
50
|
|
|
51
51
|
execution = TaskExecution(
|
|
52
|
-
task_id=task
|
|
52
|
+
task_id=_task_identity(task),
|
|
53
53
|
task_model=task._meta.label,
|
|
54
54
|
trigger_event=trigger_event,
|
|
55
55
|
executor_class=executor_class_path,
|
|
@@ -74,7 +74,7 @@ def bulk_create_and_publish(tasks, trigger_event, extra_context=None):
|
|
|
74
74
|
queue_name = task.get_queue_name(event=trigger_event)
|
|
75
75
|
|
|
76
76
|
context = {
|
|
77
|
-
'task_id':
|
|
77
|
+
'task_id': _task_identity(task),
|
|
78
78
|
'trigger_event': trigger_event,
|
|
79
79
|
'data': _model_to_dict(task),
|
|
80
80
|
}
|
|
@@ -84,7 +84,7 @@ def bulk_create_and_publish(tasks, trigger_event, extra_context=None):
|
|
|
84
84
|
serialized_context = _serialize_context(context)
|
|
85
85
|
|
|
86
86
|
execution = TaskExecution(
|
|
87
|
-
task_id=task
|
|
87
|
+
task_id=_task_identity(task),
|
|
88
88
|
task_model=task._meta.label,
|
|
89
89
|
trigger_event=trigger_event,
|
|
90
90
|
executor_class=executor_class_path,
|
|
@@ -165,6 +165,14 @@ def _serialize_value(value):
|
|
|
165
165
|
return str(value)
|
|
166
166
|
|
|
167
167
|
|
|
168
|
+
def _task_identity(instance):
|
|
169
|
+
"""返回业务对象唯一标识字符串,用作 TaskExecution.task_id。
|
|
170
|
+
|
|
171
|
+
业务实例唯一性由其真实主键承载,而非额外的 task_id UUID。
|
|
172
|
+
"""
|
|
173
|
+
return str(instance.pk) if instance.pk is not None else None
|
|
174
|
+
|
|
175
|
+
|
|
168
176
|
def _model_to_dict(instance):
|
|
169
177
|
data = {}
|
|
170
178
|
for field in instance._meta.fields:
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Generated by Django 5.2.16 on 2026-09-03 06:32
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
("django_simpletask5", "0012_alter_taskexecution_status"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterField(
|
|
14
|
+
model_name="taskexecution",
|
|
15
|
+
name="task_id",
|
|
16
|
+
field=models.CharField(
|
|
17
|
+
blank=True,
|
|
18
|
+
db_index=True,
|
|
19
|
+
max_length=255,
|
|
20
|
+
null=True,
|
|
21
|
+
verbose_name="Task ID",
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
]
|
|
@@ -42,8 +42,6 @@ class TaskManager(models.Manager):
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
class Task(models.Model):
|
|
45
|
-
task_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
|
46
|
-
|
|
47
45
|
objects = TaskManager()
|
|
48
46
|
|
|
49
47
|
class Meta:
|
|
@@ -114,6 +112,23 @@ class Task(models.Model):
|
|
|
114
112
|
return self._derive_executor_class_path(event)
|
|
115
113
|
return self.executor_class
|
|
116
114
|
|
|
115
|
+
@classmethod
|
|
116
|
+
def get_task_instance(cls, task_id):
|
|
117
|
+
"""根据 Execution 记录的 task_id(业务主键字符串)反查业务实例。
|
|
118
|
+
|
|
119
|
+
默认实现按主键(pk)查询,并兼容 int / UUID / 字符串主键;
|
|
120
|
+
业务子类可在自定义主键查询逻辑时重载此方法。
|
|
121
|
+
"""
|
|
122
|
+
if task_id is None:
|
|
123
|
+
return None
|
|
124
|
+
pk = _coerce_task_pk(cls, task_id)
|
|
125
|
+
if pk is None:
|
|
126
|
+
return None
|
|
127
|
+
try:
|
|
128
|
+
return cls.objects.get(pk=pk)
|
|
129
|
+
except cls.DoesNotExist:
|
|
130
|
+
return None
|
|
131
|
+
|
|
117
132
|
def _derive_executor_class_path(self, event=None):
|
|
118
133
|
app_label = self._meta.app_label
|
|
119
134
|
class_name = self.__class__.__name__
|
|
@@ -142,6 +157,30 @@ class Task(models.Model):
|
|
|
142
157
|
raise _DeleteIntercepted(self)
|
|
143
158
|
|
|
144
159
|
|
|
160
|
+
def _coerce_task_pk(model, task_id):
|
|
161
|
+
"""将 TaskExecution.task_id 字符串转换为业务模型主键的实际类型。
|
|
162
|
+
|
|
163
|
+
业务模型的主键可能是 int(AutoField/BigAutoField)、UUID(UUIDField)
|
|
164
|
+
或字符串(CharField/TextField 等)。无法正确转换时返回 None。
|
|
165
|
+
"""
|
|
166
|
+
if task_id is None:
|
|
167
|
+
return None
|
|
168
|
+
pk_field = model._meta.pk
|
|
169
|
+
if isinstance(pk_field, models.AutoField) \
|
|
170
|
+
or isinstance(pk_field, models.BigAutoField) \
|
|
171
|
+
or isinstance(pk_field, models.SmallAutoField):
|
|
172
|
+
try:
|
|
173
|
+
return int(task_id)
|
|
174
|
+
except (TypeError, ValueError):
|
|
175
|
+
return None
|
|
176
|
+
if isinstance(pk_field, models.UUIDField):
|
|
177
|
+
try:
|
|
178
|
+
return uuid.UUID(str(task_id))
|
|
179
|
+
except (ValueError, AttributeError, TypeError):
|
|
180
|
+
return None
|
|
181
|
+
return task_id
|
|
182
|
+
|
|
183
|
+
|
|
145
184
|
class TaskExecution(models.Model):
|
|
146
185
|
STATUS_CHOICES = [
|
|
147
186
|
('pending', _('Pending')),
|
|
@@ -154,7 +193,7 @@ class TaskExecution(models.Model):
|
|
|
154
193
|
]
|
|
155
194
|
|
|
156
195
|
execution_id = models.UUIDField(verbose_name=_('Execution ID'), unique=True, default=uuid.uuid4, editable=False)
|
|
157
|
-
task_id = models.
|
|
196
|
+
task_id = models.CharField(verbose_name=_('Task ID'), max_length=255, null=True, blank=True, db_index=True)
|
|
158
197
|
task_model = models.CharField(verbose_name=_('Task model'), max_length=256, null=True, blank=True, help_text='app_label.ModelName')
|
|
159
198
|
trigger_event = models.CharField(verbose_name=_('Trigger event'), max_length=64, db_index=True)
|
|
160
199
|
executor_class = models.CharField(verbose_name=_('Executor class'), max_length=512, db_index=True)
|
|
@@ -191,20 +230,22 @@ class TaskExecution(models.Model):
|
|
|
191
230
|
if task_model is None:
|
|
192
231
|
return
|
|
193
232
|
try:
|
|
194
|
-
instance = task_model.
|
|
233
|
+
instance = task_model.get_task_instance(self.task_id)
|
|
234
|
+
if instance is None:
|
|
235
|
+
return
|
|
195
236
|
instance._force_delete = True
|
|
196
237
|
instance.delete()
|
|
197
|
-
except
|
|
238
|
+
except Exception:
|
|
198
239
|
pass
|
|
199
240
|
|
|
200
241
|
def get_task(self):
|
|
201
242
|
if not self.task_id or not self.task_model:
|
|
202
243
|
return None
|
|
203
244
|
try:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
return model.
|
|
245
|
+
model = self.get_task_model()
|
|
246
|
+
if model is None:
|
|
247
|
+
return None
|
|
248
|
+
return model.get_task_instance(self.task_id)
|
|
208
249
|
except Exception:
|
|
209
250
|
return None
|
|
210
251
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-simpletask5
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Lightweight async task execution framework for Django
|
|
5
5
|
Author-email: rRR0VrFP <rrr0vrfp@qq.com>
|
|
6
6
|
Maintainer-email: rRR0VrFP <rrr0vrfp@qq.com>
|
|
@@ -278,7 +278,7 @@ class OrderTask(Task):
|
|
|
278
278
|
```
|
|
279
279
|
|
|
280
280
|
要点:
|
|
281
|
-
- 主键为 Django 默认自增 `id
|
|
281
|
+
- 主键为 Django 默认自增 `id`。`Task` 不再内置 `task_id` 字段,业务标识由业务模型自行定义(自定义主键或自然键均可)
|
|
282
282
|
- 保存 / 删除 / 批量创建自动触发事件,详见「事件处理机制」
|
|
283
283
|
- 事件上下文、update 字段判定、delete 拦截等行为的完整说明见对应章节
|
|
284
284
|
|
|
@@ -354,7 +354,7 @@ OrderTask.objects.create(order_id='ORD-001', customer_name='Test', amount=Decima
|
|
|
354
354
|
| `update` | 字段值发生变化后保存 | `task_id`、`trigger_event`、`data`、`old_data`、`new_data`、`changed_fields` |
|
|
355
355
|
| `delete` | 调用 `instance.delete()` | `task_id`、`trigger_event`、`data` |
|
|
356
356
|
|
|
357
|
-
执行器通过 `execution.get_context_dict()`
|
|
357
|
+
执行器通过 `execution.get_context_dict()` 获取以上上下文。其中 `task_id` 是业务对象主键的字符串表示(如 `"42"`、UUID 字符串或自定义键);业务对象反查见「模型与生命周期」。
|
|
358
358
|
|
|
359
359
|
#### update 事件的字段变更判定
|
|
360
360
|
|
|
@@ -393,6 +393,8 @@ class OrderDeleteExecutor(BaseExecutor):
|
|
|
393
393
|
|
|
394
394
|
这样实现删除操作与外部系统清理的解耦,且支持失败重试。
|
|
395
395
|
|
|
396
|
+
`confirm_delete()` 与 `execution.get_task()` 都会通过 `TaskExecution.task_id`(业务主键字符串)反查业务对象,反查逻辑由业务模型上的 `get_task_instance()` 提供(见「模型与生命周期」)。
|
|
397
|
+
|
|
396
398
|
### 事件 → 执行器映射
|
|
397
399
|
|
|
398
400
|
每个事件消息最终要由一个执行器处理。`executor_class` 声明「事件名 → 执行器类路径」的分派表,可以是字符串(所有事件共用同一个执行器)或字典(按事件分派):
|
|
@@ -568,10 +570,38 @@ class OrderCreateExecutor(BaseExecutor):
|
|
|
568
570
|
|
|
569
571
|
所有业务任务模型继承 `Task`:
|
|
570
572
|
|
|
571
|
-
-
|
|
573
|
+
- 主键为 Django 默认自增 `id`,无需自定义;**不内置** `task_id` 字段,业务标识由业务模型按需定义
|
|
572
574
|
- 提供 `trigger(event, extra_context)`、`skip_publish()`、`get_executor_class_path(event)`、`get_queue_name(event)` 等方法
|
|
573
575
|
- 保存、删除、批量创建自动触发事件(见「事件处理机制」)
|
|
574
576
|
|
|
577
|
+
#### 业务对象反查:`get_task_instance()`
|
|
578
|
+
|
|
579
|
+
`TaskExecution.task_id` 存的是业务对象主键的字符串表示(`CharField`)。需要反查业务对象时(`execution.get_task()` / `execution.confirm_delete()`),框架先通过 `task_model` 解析出模型类,再调用该类的 `get_task_instance(task_id)` 完成查询。
|
|
580
|
+
|
|
581
|
+
默认实现在 `Task` 基类中按主键 `pk` 查询,并通过内置的 `_coerce_task_pk()` 兼容常见主键类型:
|
|
582
|
+
|
|
583
|
+
- `AutoField` / `BigAutoField` / `SmallAutoField` — 字符串转为 `int`
|
|
584
|
+
- `UUIDField` — 字符串转为 `uuid.UUID`
|
|
585
|
+
- `CharField` / `TextField` 等字符串主键 — 原样使用
|
|
586
|
+
|
|
587
|
+
```python
|
|
588
|
+
task = execution.get_task() # 返回业务对象(反查不到返回 None)
|
|
589
|
+
execution.confirm_delete() # 反查并真实删除业务对象
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
业务模型使用自定义主键或非主键自然键时,**重载 `get_task_instance()`** 即可,框架会自动改走你的实现:
|
|
593
|
+
|
|
594
|
+
```python
|
|
595
|
+
class OrderTask(Task):
|
|
596
|
+
code = models.CharField(max_length=64, unique=True) # 非主键自然键
|
|
597
|
+
|
|
598
|
+
@classmethod
|
|
599
|
+
def get_task_instance(cls, task_id):
|
|
600
|
+
if task_id is None:
|
|
601
|
+
return None
|
|
602
|
+
return cls.objects.filter(code=task_id).first()
|
|
603
|
+
```
|
|
604
|
+
|
|
575
605
|
### TaskExecution 状态机
|
|
576
606
|
|
|
577
607
|
每条事件消息对应一条 `TaskExecution` 记录,状态流转如下:
|
|
@@ -702,6 +732,13 @@ Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保
|
|
|
702
732
|
|
|
703
733
|
## Releases
|
|
704
734
|
|
|
735
|
+
### 0.2.8
|
|
736
|
+
|
|
737
|
+
- **移除 `Task` 冗余的 `task_id` 字段** — `TaskExecution.task_id` 由 UUID 改为 `CharField(max_length=255)`,直接用于存放业务对象主键字符串(`str(instance.pk)`),不再维护一份与业务主键重复的 UUID。消息上下文中的 `task_id` 字段名不变,值语义从 UUID 变为业务主键字符串(如 `"42"`)
|
|
738
|
+
- **业务对象反查下沉到模型层** — 新增 `Task.get_task_instance(task_id)` 类方法作为默认反查实现(按 `pk` 查询,内置 `_coerce_task_pk()` 兼容 `int` / `UUID` / 字符串主键);`TaskExecution.get_task()` / `confirm_delete()` 改为解析 `task_model` 后委托给该方法。业务模型使用自定义主键或自然键时重载 `get_task_instance()` 即可
|
|
739
|
+
- **新增迁移** — `django_simpletask5.0013_alter_taskexecution_task_id`(字段改 CharField)、`django_simpletask5_example.0003_remove_ordertask_task_id`
|
|
740
|
+
- **破坏性变更**: 依赖 `TaskExecution.task_id` 为 UUID 类型的外部消费方需适配为业务主键字符串;业务 `Task` 子类若曾使用内置 `task_id` 字段需移除
|
|
741
|
+
|
|
705
742
|
### 0.2.7
|
|
706
743
|
|
|
707
744
|
- **运行中任务回收机制重构** — 取消原先按 `expire_time` 将 running 直接判为超时的语义;`StatusCheckExecutor` 仅兜底回收长期未被 Worker 获取的 pending。新增 `core/reclaim`,由 crontab 每 ~5 秒基于执行锁探测运行中任务:锁已过期(执行器异常退出)即快速重投或直接置失败,降低孤儿任务滞留时间
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5.egg-info/SOURCES.txt
RENAMED
|
@@ -55,6 +55,7 @@ django_simpletask5/migrations/0009_add_cronjob_app_label.py
|
|
|
55
55
|
django_simpletask5/migrations/0010_taskexecution_id_alter_taskexecution_execution_id.py
|
|
56
56
|
django_simpletask5/migrations/0011_taskexecutionstat_canceled_count.py
|
|
57
57
|
django_simpletask5/migrations/0012_alter_taskexecution_status.py
|
|
58
|
+
django_simpletask5/migrations/0013_alter_taskexecution_task_id.py
|
|
58
59
|
django_simpletask5/migrations/__init__.py
|
|
59
60
|
django_simpletask5/services/__init__.py
|
|
60
61
|
django_simpletask5/services/archive.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/cronjob_registry.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/executor_scanner.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/logging_utils.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/message_queue.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/core/worker_registry.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/__init__.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/archive.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/bash_script.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/exceptions.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/loader.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/ping_pong.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/python_script.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/retry_timeout.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/simple_request.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/executors/status_check.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/management/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/0001_initial.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/migrations/__init__.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/services/__init__.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5/services/archive.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5.egg-info/requires.txt
RENAMED
|
File without changes
|
{django_simpletask5-0.2.7 → django_simpletask5-0.2.8}/django_simpletask5.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|