django-simpletask5 0.2.0__tar.gz → 0.2.1__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.0 → django_simpletask5-0.2.1}/PKG-INFO +44 -5
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/README.md +43 -4
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/core/defaults.py +1 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/core/publisher.py +12 -1
- django_simpletask5-0.2.1/django_simpletask5/executors/base.py +35 -0
- django_simpletask5-0.2.1/django_simpletask5/executors/exceptions.py +13 -0
- django_simpletask5-0.2.1/django_simpletask5/executors/retry_timeout.py +66 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/management/commands/django_simpletask_crontab.py +5 -1
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/management/commands/django_simpletask_executor.py +5 -0
- django_simpletask5-0.2.1/django_simpletask5/migrations/0008_taskexecution_pickup_timeout_seconds.py +20 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/models.py +3 -1
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5.egg-info/PKG-INFO +44 -5
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5.egg-info/SOURCES.txt +2 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/pyproject.toml +1 -1
- django_simpletask5-0.2.0/django_simpletask5/executors/base.py +0 -20
- django_simpletask5-0.2.0/django_simpletask5/executors/retry_timeout.py +0 -47
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/LICENSE +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/__init__.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/admin.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/apps.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/core/__init__.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/core/cronjob_registry.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/core/executor_scanner.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/core/lock.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/core/message_queue.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/core/signals.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/core/worker_registry.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/dashboards.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/executors/__init__.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/executors/archive.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/executors/bash_script.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/executors/loader.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/executors/ping_pong.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/executors/python_script.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/executors/simple_request.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/executors/status_check.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.po +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/management/__init__.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/management/commands/__init__.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/management/commands/django_simpletask_archive.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/management/commands/django_simpletask_sync_cronjobs.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/migrations/0001_initial.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/migrations/0002_taskexecution_done_tasks_count_and_more.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/migrations/0003_alter_cronjob_options_remove_cronjob_name_and_more.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/migrations/0004_alter_cronjob_is_modified_by_user.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/migrations/0005_alter_cronjob_is_modified_by_user_help.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/migrations/0006_alter_cronjob_options_alter_taskexecution_created_at_and_more.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/migrations/0007_rename_task_model_to_executor_class_in_stat.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/migrations/__init__.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/services/__init__.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/services/archive.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5.egg-info/dependency_links.txt +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5.egg-info/requires.txt +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5.egg-info/top_level.txt +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/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.1
|
|
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>
|
|
@@ -50,7 +50,8 @@ Dynamic: license-file
|
|
|
50
50
|
- **自定义事件** — 通过 `task.trigger('event_name')` 触发任意事件
|
|
51
51
|
- **灵活的执行器映射** — 不同事件可绑定不同的执行器类
|
|
52
52
|
- **队列路由** — 不同事件可路由到不同优先级队列
|
|
53
|
-
- **重试与超时** —
|
|
53
|
+
- **重试与超时** — 失败自动重试(指数退避),支持执行超时与 Pickup 超时两种检测
|
|
54
|
+
- **拒绝回退** — 执行器可通过 `RejectException` 拒绝当前任务,消息自动重新入队等待下次调度
|
|
54
55
|
- **加密字段** — 敏感数据自动加密存储
|
|
55
56
|
- **Cron 调度** — 内置 crontab 守护进程,支持代码注册与数据库覆盖
|
|
56
57
|
- **归档统计** — 已完成执行记录自动归档为加密 JSONL,并生成日统计
|
|
@@ -156,10 +157,15 @@ DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE = 'django_simpletask5.queue.high_priority'
|
|
|
156
157
|
### 执行与重试
|
|
157
158
|
|
|
158
159
|
```python
|
|
159
|
-
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES = 3
|
|
160
|
-
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = 600
|
|
160
|
+
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES = 3 # 默认最大重试次数
|
|
161
|
+
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = 600 # 默认执行超时(秒),任务开始执行后的超时限制
|
|
162
|
+
DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS = 3600 # 默认 Pickup 超时(秒),等待 Worker 消费的时间限制
|
|
161
163
|
```
|
|
162
164
|
|
|
165
|
+
框架区分两种超时:
|
|
166
|
+
- **执行超时**(`timeout_seconds`)— 任务进入 `running` 状态后,必须在指定时间内完成,否则被 `StatusCheckExecutor` 标记超时。适用于保护实际执行不 hang 死。
|
|
167
|
+
- **Pickup 超时**(`pickup_timeout_seconds`)— 任务创建后等待 Worker 消费的时间上限,也用于被拒绝回退后等待重新调度的保护。通常设得比执行超时更长,因为排队等待时间不可控。
|
|
168
|
+
|
|
163
169
|
### 归档
|
|
164
170
|
|
|
165
171
|
```python
|
|
@@ -235,6 +241,33 @@ class OrderCreateExecutor(BaseExecutor):
|
|
|
235
241
|
return 'ok'
|
|
236
242
|
```
|
|
237
243
|
|
|
244
|
+
执行器可以通过 `executor_class` 属性覆盖超时配置:
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
class OrderCreateExecutor(BaseExecutor):
|
|
248
|
+
timeout_seconds = 300 # 执行超时 5 分钟
|
|
249
|
+
pickup_timeout_seconds = 1800 # Pickup 超时 30 分钟
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### 拒绝回退
|
|
253
|
+
|
|
254
|
+
执行器在执行过程中遇到临时条件不满足(如资源上限),可以抛出 `RejectException` 拒绝本次执行:
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
from django_simpletask5.executors.exceptions import RejectException
|
|
258
|
+
|
|
259
|
+
class MyExecutor(BaseExecutor):
|
|
260
|
+
def execute(self, execution: TaskExecution) -> str | None:
|
|
261
|
+
if not self._can_proceed():
|
|
262
|
+
raise RejectException('Resource limit reached, try again later')
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
框架捕获后会:
|
|
266
|
+
- 调用 `message.reject(requeue=True)` 将消息放回队列尾部,下次重新调度
|
|
267
|
+
- `TaskExecution` 状态恢复为 `pending`,`started_at` 清空,等待下次 Worker 消费
|
|
268
|
+
- **不消耗**重试次数
|
|
269
|
+
- 刷新 `expire_time`(使用 Pickup 超时),防止被 `StatusCheckExecutor` 误回收
|
|
270
|
+
|
|
238
271
|
### 3. 启动 Worker
|
|
239
272
|
|
|
240
273
|
默认只监听 `default` 队列,`high_priority` 队列需要单独启动 Worker 处理。
|
|
@@ -315,11 +348,17 @@ Task 模型变更 → Django 信号 → 创建 TaskExecution 并发布到消息
|
|
|
315
348
|
↓
|
|
316
349
|
Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保存结果
|
|
317
350
|
↓
|
|
318
|
-
|
|
351
|
+
失败时自动重试/拒绝时重新入队,完成后归档
|
|
319
352
|
```
|
|
320
353
|
|
|
321
354
|
## Releases
|
|
322
355
|
|
|
356
|
+
### 0.2.1
|
|
357
|
+
|
|
358
|
+
- **拒绝回退机制** — 新增 `RejectException`,执行器可抛出该异常拒绝当前任务,消息自动重新入队等待下次调度,不消耗重试次数
|
|
359
|
+
- **区分两种超时** — 引入 `pickup_timeout_seconds`(等待 Worker 消费超时)与 `timeout_seconds`(执行超时)两种超时配置,`expire_time` 改用 Pickup 超时计算,超时检测更精准
|
|
360
|
+
- **文档更新** — README 补充拒绝回退机制的使用说明和两种超时的详细解释
|
|
361
|
+
|
|
323
362
|
### 0.2.0
|
|
324
363
|
|
|
325
364
|
- **破坏性变更**: 移除 `Task` 模型上的自定义主键,`task_id` 不再是主键字段,改为 `unique=True` 的唯一标识字段。所有 `Task` 子模型将自动获得 Django 默认的自增 `id` 主键。已有数据库需要迁移处理。
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
- **自定义事件** — 通过 `task.trigger('event_name')` 触发任意事件
|
|
12
12
|
- **灵活的执行器映射** — 不同事件可绑定不同的执行器类
|
|
13
13
|
- **队列路由** — 不同事件可路由到不同优先级队列
|
|
14
|
-
- **重试与超时** —
|
|
14
|
+
- **重试与超时** — 失败自动重试(指数退避),支持执行超时与 Pickup 超时两种检测
|
|
15
|
+
- **拒绝回退** — 执行器可通过 `RejectException` 拒绝当前任务,消息自动重新入队等待下次调度
|
|
15
16
|
- **加密字段** — 敏感数据自动加密存储
|
|
16
17
|
- **Cron 调度** — 内置 crontab 守护进程,支持代码注册与数据库覆盖
|
|
17
18
|
- **归档统计** — 已完成执行记录自动归档为加密 JSONL,并生成日统计
|
|
@@ -117,10 +118,15 @@ DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE = 'django_simpletask5.queue.high_priority'
|
|
|
117
118
|
### 执行与重试
|
|
118
119
|
|
|
119
120
|
```python
|
|
120
|
-
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES = 3
|
|
121
|
-
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = 600
|
|
121
|
+
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES = 3 # 默认最大重试次数
|
|
122
|
+
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = 600 # 默认执行超时(秒),任务开始执行后的超时限制
|
|
123
|
+
DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS = 3600 # 默认 Pickup 超时(秒),等待 Worker 消费的时间限制
|
|
122
124
|
```
|
|
123
125
|
|
|
126
|
+
框架区分两种超时:
|
|
127
|
+
- **执行超时**(`timeout_seconds`)— 任务进入 `running` 状态后,必须在指定时间内完成,否则被 `StatusCheckExecutor` 标记超时。适用于保护实际执行不 hang 死。
|
|
128
|
+
- **Pickup 超时**(`pickup_timeout_seconds`)— 任务创建后等待 Worker 消费的时间上限,也用于被拒绝回退后等待重新调度的保护。通常设得比执行超时更长,因为排队等待时间不可控。
|
|
129
|
+
|
|
124
130
|
### 归档
|
|
125
131
|
|
|
126
132
|
```python
|
|
@@ -196,6 +202,33 @@ class OrderCreateExecutor(BaseExecutor):
|
|
|
196
202
|
return 'ok'
|
|
197
203
|
```
|
|
198
204
|
|
|
205
|
+
执行器可以通过 `executor_class` 属性覆盖超时配置:
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
class OrderCreateExecutor(BaseExecutor):
|
|
209
|
+
timeout_seconds = 300 # 执行超时 5 分钟
|
|
210
|
+
pickup_timeout_seconds = 1800 # Pickup 超时 30 分钟
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### 拒绝回退
|
|
214
|
+
|
|
215
|
+
执行器在执行过程中遇到临时条件不满足(如资源上限),可以抛出 `RejectException` 拒绝本次执行:
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from django_simpletask5.executors.exceptions import RejectException
|
|
219
|
+
|
|
220
|
+
class MyExecutor(BaseExecutor):
|
|
221
|
+
def execute(self, execution: TaskExecution) -> str | None:
|
|
222
|
+
if not self._can_proceed():
|
|
223
|
+
raise RejectException('Resource limit reached, try again later')
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
框架捕获后会:
|
|
227
|
+
- 调用 `message.reject(requeue=True)` 将消息放回队列尾部,下次重新调度
|
|
228
|
+
- `TaskExecution` 状态恢复为 `pending`,`started_at` 清空,等待下次 Worker 消费
|
|
229
|
+
- **不消耗**重试次数
|
|
230
|
+
- 刷新 `expire_time`(使用 Pickup 超时),防止被 `StatusCheckExecutor` 误回收
|
|
231
|
+
|
|
199
232
|
### 3. 启动 Worker
|
|
200
233
|
|
|
201
234
|
默认只监听 `default` 队列,`high_priority` 队列需要单独启动 Worker 处理。
|
|
@@ -276,11 +309,17 @@ Task 模型变更 → Django 信号 → 创建 TaskExecution 并发布到消息
|
|
|
276
309
|
↓
|
|
277
310
|
Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保存结果
|
|
278
311
|
↓
|
|
279
|
-
|
|
312
|
+
失败时自动重试/拒绝时重新入队,完成后归档
|
|
280
313
|
```
|
|
281
314
|
|
|
282
315
|
## Releases
|
|
283
316
|
|
|
317
|
+
### 0.2.1
|
|
318
|
+
|
|
319
|
+
- **拒绝回退机制** — 新增 `RejectException`,执行器可抛出该异常拒绝当前任务,消息自动重新入队等待下次调度,不消耗重试次数
|
|
320
|
+
- **区分两种超时** — 引入 `pickup_timeout_seconds`(等待 Worker 消费超时)与 `timeout_seconds`(执行超时)两种超时配置,`expire_time` 改用 Pickup 超时计算,超时检测更精准
|
|
321
|
+
- **文档更新** — README 补充拒绝回退机制的使用说明和两种超时的详细解释
|
|
322
|
+
|
|
284
323
|
### 0.2.0
|
|
285
324
|
|
|
286
325
|
- **破坏性变更**: 移除 `Task` 模型上的自定义主键,`task_id` 不再是主键字段,改为 `unique=True` 的唯一标识字段。所有 `Task` 子模型将自动获得 Django 默认的自增 `id` 主键。已有数据库需要迁移处理。
|
|
@@ -4,6 +4,7 @@ DJANGO_SIMPLETASK_LOCK_TIMEOUT = getattr(settings, 'DJANGO_SIMPLETASK_LOCK_TIMEO
|
|
|
4
4
|
|
|
5
5
|
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES = getattr(settings, 'DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES', 3)
|
|
6
6
|
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = getattr(settings, 'DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS', 600)
|
|
7
|
+
DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS = getattr(settings, 'DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS', 3600)
|
|
7
8
|
|
|
8
9
|
DJANGO_SIMPLETASK_ARCHIVE_PATH = getattr(settings, 'DJANGO_SIMPLETASK_ARCHIVE_PATH', 'django_simpletask5_archives')
|
|
9
10
|
DJANGO_SIMPLETASK_ARCHIVE_SALT = getattr(settings, 'DJANGO_SIMPLETASK_ARCHIVE_SALT', 'django-simpletask5-archive')
|
|
@@ -6,7 +6,7 @@ from django.db import transaction
|
|
|
6
6
|
from django.utils import timezone
|
|
7
7
|
|
|
8
8
|
from django_simpletask5.models import TaskExecution
|
|
9
|
-
from django_simpletask5.core.defaults import DJANGO_SIMPLETASK_DEFAULT_QUEUE, DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS
|
|
9
|
+
from django_simpletask5.core.defaults import DJANGO_SIMPLETASK_DEFAULT_QUEUE, DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS, DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS
|
|
10
10
|
from django_simpletask5.executors.loader import load_executor_class
|
|
11
11
|
|
|
12
12
|
logger = logging.getLogger(__name__)
|
|
@@ -22,6 +22,16 @@ def _resolve_timeout_seconds(executor_class_path: str) -> int:
|
|
|
22
22
|
return DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
def _resolve_pickup_timeout_seconds(executor_class_path: str) -> int:
|
|
26
|
+
try:
|
|
27
|
+
cls = load_executor_class(executor_class_path)
|
|
28
|
+
if cls.pickup_timeout_seconds is not None:
|
|
29
|
+
return cls.pickup_timeout_seconds
|
|
30
|
+
except Exception:
|
|
31
|
+
logger.warning('Failed to load executor %s for pickup timeout resolution', executor_class_path)
|
|
32
|
+
return DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS
|
|
33
|
+
|
|
34
|
+
|
|
25
35
|
def create_and_publish(task, trigger_event, extra_context=None):
|
|
26
36
|
executor_class_path = task.get_executor_class_path(event=trigger_event)
|
|
27
37
|
queue_name = task.get_queue_name(event=trigger_event)
|
|
@@ -43,6 +53,7 @@ def create_and_publish(task, trigger_event, extra_context=None):
|
|
|
43
53
|
executor_class=executor_class_path,
|
|
44
54
|
status='pending',
|
|
45
55
|
timeout_seconds=_resolve_timeout_seconds(executor_class_path),
|
|
56
|
+
pickup_timeout_seconds=_resolve_pickup_timeout_seconds(executor_class_path),
|
|
46
57
|
context=json.dumps(serialized_context, default=str) if serialized_context else None,
|
|
47
58
|
)
|
|
48
59
|
execution.save()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
2
|
+
|
|
3
|
+
from django.utils import timezone
|
|
4
|
+
|
|
5
|
+
from django_simpletask5.models import TaskExecution
|
|
6
|
+
from django_simpletask5.core.defaults import DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseExecutor:
|
|
10
|
+
timeout_seconds: int | None = None
|
|
11
|
+
pickup_timeout_seconds: int | None = None
|
|
12
|
+
total_tasks_count: int | None = None
|
|
13
|
+
done_tasks_count: int | None = None
|
|
14
|
+
|
|
15
|
+
def get_tasks_count(self, execution: TaskExecution) -> int | None:
|
|
16
|
+
return 1
|
|
17
|
+
|
|
18
|
+
def get_tasks(self, execution: TaskExecution):
|
|
19
|
+
yield None
|
|
20
|
+
|
|
21
|
+
def execute(self, execution: TaskExecution, task=None) -> str | None:
|
|
22
|
+
raise NotImplementedError('Subclasses must implement execute()')
|
|
23
|
+
|
|
24
|
+
def reject(self, execution: TaskExecution, reason: str = '') -> None:
|
|
25
|
+
"""Handle task rejection - revert to pending and refresh expire_time."""
|
|
26
|
+
execution.status = 'pending'
|
|
27
|
+
execution.started_at = None
|
|
28
|
+
execution.expire_time = timezone.now() + timedelta(
|
|
29
|
+
seconds=execution.pickup_timeout_seconds or DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS,
|
|
30
|
+
)
|
|
31
|
+
execution.save(update_fields=['status', 'started_at', 'expire_time', 'updated_at'])
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def get_parameter_schema(cls):
|
|
35
|
+
return []
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class RejectException(Exception):
|
|
2
|
+
"""Raise to reject and requeue the current task execution.
|
|
3
|
+
|
|
4
|
+
Raise this inside an executor's ``execute()`` method to signal that the
|
|
5
|
+
task should not be consumed now. The framework will:
|
|
6
|
+
- Not ack the message (calls ``message.reject(requeue=True)``)
|
|
7
|
+
- Revert ``TaskExecution`` status to ``pending`` and clear ``started_at``
|
|
8
|
+
- Not consume a retry attempt
|
|
9
|
+
- Refresh ``expire_time`` using pickup timeout
|
|
10
|
+
|
|
11
|
+
This is useful for transient conditions like resource limits or
|
|
12
|
+
temporary unavailability that should be retried later.
|
|
13
|
+
"""
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from django.db import models, transaction
|
|
5
|
+
|
|
6
|
+
from django_simpletask5.models import TaskExecution
|
|
7
|
+
from django_simpletask5.executors.base import BaseExecutor
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _publish_message_by_id(execution_id):
|
|
11
|
+
from django_simpletask5.core.publisher import _publish_message
|
|
12
|
+
try:
|
|
13
|
+
exec_record = TaskExecution.objects.get(pk=execution_id)
|
|
14
|
+
_publish_message(exec_record)
|
|
15
|
+
except TaskExecution.DoesNotExist:
|
|
16
|
+
logger.warning('Execution %s not found for retry publish', execution_id)
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RetryTimeoutExecutor(BaseExecutor):
|
|
22
|
+
def execute(self, execution: TaskExecution, task=None) -> str | None:
|
|
23
|
+
from functools import partial
|
|
24
|
+
from django_simpletask5.core.publisher import _publish_message
|
|
25
|
+
|
|
26
|
+
timeout_records = TaskExecution.objects.filter(
|
|
27
|
+
status='timeout',
|
|
28
|
+
retry_count__lt=models.F('max_retries'),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Pickup timeouts — task was never picked up, don't consume retry
|
|
32
|
+
pickup_ids = list(
|
|
33
|
+
timeout_records.filter(started_at__isnull=True)
|
|
34
|
+
.values_list('pk', flat=True)
|
|
35
|
+
)
|
|
36
|
+
if pickup_ids:
|
|
37
|
+
TaskExecution.objects.filter(pk__in=pickup_ids).update(
|
|
38
|
+
status='retry',
|
|
39
|
+
)
|
|
40
|
+
for exec_id in pickup_ids:
|
|
41
|
+
transaction.on_commit(
|
|
42
|
+
partial(_publish_message_by_id, exec_id)
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Execution timeouts — task was actually running, consume retry
|
|
46
|
+
exec_ids = list(
|
|
47
|
+
timeout_records.filter(started_at__isnull=False)
|
|
48
|
+
.values_list('pk', flat=True)
|
|
49
|
+
)
|
|
50
|
+
if exec_ids:
|
|
51
|
+
TaskExecution.objects.filter(pk__in=exec_ids).update(
|
|
52
|
+
status='retry',
|
|
53
|
+
retry_count=models.F('retry_count') + 1,
|
|
54
|
+
)
|
|
55
|
+
for exec_id in exec_ids:
|
|
56
|
+
transaction.on_commit(
|
|
57
|
+
partial(_publish_message_by_id, exec_id)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
total = len(pickup_ids) + len(exec_ids)
|
|
61
|
+
return json.dumps({
|
|
62
|
+
'retried': total,
|
|
63
|
+
'pickup_timeout_retried': len(pickup_ids),
|
|
64
|
+
'exec_timeout_retried': len(exec_ids),
|
|
65
|
+
'success': True,
|
|
66
|
+
})
|
|
@@ -10,7 +10,7 @@ from django.utils import timezone
|
|
|
10
10
|
|
|
11
11
|
from django_simpletask5.models import CronJob, TaskExecution
|
|
12
12
|
from django_simpletask5.core.lock import get_lock_manager
|
|
13
|
-
from django_simpletask5.core.defaults import DJANGO_SIMPLETASK_LOCK_TIMEOUT, DJANGO_SIMPLETASK_DEFAULT_QUEUE, DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS
|
|
13
|
+
from django_simpletask5.core.defaults import DJANGO_SIMPLETASK_LOCK_TIMEOUT, DJANGO_SIMPLETASK_DEFAULT_QUEUE, DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS, DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS
|
|
14
14
|
from django_simpletask5.executors.loader import load_executor_class
|
|
15
15
|
|
|
16
16
|
logger = logging.getLogger(__name__)
|
|
@@ -142,10 +142,13 @@ class Command(BaseCommand):
|
|
|
142
142
|
context.update(cron_params)
|
|
143
143
|
|
|
144
144
|
timeout_seconds = DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS
|
|
145
|
+
pickup_timeout_seconds = DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS
|
|
145
146
|
try:
|
|
146
147
|
cls = load_executor_class(cronjob.executor_class)
|
|
147
148
|
if cls.timeout_seconds is not None:
|
|
148
149
|
timeout_seconds = cls.timeout_seconds
|
|
150
|
+
if cls.pickup_timeout_seconds is not None:
|
|
151
|
+
pickup_timeout_seconds = cls.pickup_timeout_seconds
|
|
149
152
|
except Exception:
|
|
150
153
|
pass
|
|
151
154
|
|
|
@@ -154,6 +157,7 @@ class Command(BaseCommand):
|
|
|
154
157
|
executor_class=cronjob.executor_class,
|
|
155
158
|
status='pending',
|
|
156
159
|
timeout_seconds=timeout_seconds,
|
|
160
|
+
pickup_timeout_seconds=pickup_timeout_seconds,
|
|
157
161
|
context=json.dumps(context, default=str),
|
|
158
162
|
)
|
|
159
163
|
execution.save()
|
|
@@ -12,6 +12,7 @@ from django.db import close_old_connections, OperationalError, InterfaceError
|
|
|
12
12
|
from django.utils import timezone
|
|
13
13
|
|
|
14
14
|
from django_simpletask5.models import TaskExecution
|
|
15
|
+
from django_simpletask5.executors.exceptions import RejectException
|
|
15
16
|
from django_simpletask5.executors.loader import load_executor
|
|
16
17
|
from django_simpletask5.core.lock import get_lock_manager
|
|
17
18
|
from django_simpletask5.core.defaults import DJANGO_SIMPLETASK_LOCK_TIMEOUT, DJANGO_SIMPLETASK_DEFAULT_QUEUE, DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS
|
|
@@ -177,6 +178,10 @@ class ExecutorWorker:
|
|
|
177
178
|
execution.save(update_fields=['status', 'result', 'done_tasks_count', 'finished_at', 'updated_at'])
|
|
178
179
|
|
|
179
180
|
logger.info('Execution %s completed with status %s', execution_id, execution.status)
|
|
181
|
+
except RejectException as e:
|
|
182
|
+
logger.debug('Execution %s rejected (%s), requeueing', execution_id, e)
|
|
183
|
+
executor.reject(execution, reason=str(e))
|
|
184
|
+
return False
|
|
180
185
|
except Exception as e:
|
|
181
186
|
logger.exception('Execution %s failed', execution_id)
|
|
182
187
|
execution.error_message = str(e)
|
django_simpletask5-0.2.1/django_simpletask5/migrations/0008_taskexecution_pickup_timeout_seconds.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Generated by Django 5.2.15 on 2026-07-11 14:54
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
("django_simpletask5", "0007_rename_task_model_to_executor_class_in_stat"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AddField(
|
|
14
|
+
model_name="taskexecution",
|
|
15
|
+
name="pickup_timeout_seconds",
|
|
16
|
+
field=models.IntegerField(
|
|
17
|
+
default=3600, verbose_name="Pickup timeout seconds"
|
|
18
|
+
),
|
|
19
|
+
),
|
|
20
|
+
]
|
|
@@ -11,6 +11,7 @@ from django_safe_fields.fields import SafeTextField
|
|
|
11
11
|
from django_simpletask5.core.defaults import (
|
|
12
12
|
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES,
|
|
13
13
|
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS,
|
|
14
|
+
DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS,
|
|
14
15
|
DJANGO_SIMPLETASK_FIELD_CIPHER_CLASS,
|
|
15
16
|
)
|
|
16
17
|
|
|
@@ -120,6 +121,7 @@ class TaskExecution(models.Model):
|
|
|
120
121
|
retry_count = models.IntegerField(verbose_name=_('Retry count'), default=0)
|
|
121
122
|
max_retries = models.IntegerField(verbose_name=_('Max retries'), default=DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES)
|
|
122
123
|
timeout_seconds = models.IntegerField(verbose_name=_('Timeout seconds'), default=DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS)
|
|
124
|
+
pickup_timeout_seconds = models.IntegerField(verbose_name=_('Pickup timeout seconds'), default=DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS)
|
|
123
125
|
expire_time = models.DateTimeField(verbose_name=_('Expire time'), null=True, blank=True, db_index=True)
|
|
124
126
|
started_at = models.DateTimeField(verbose_name=_('Started at'), null=True, blank=True)
|
|
125
127
|
finished_at = models.DateTimeField(verbose_name=_('Finished at'), null=True, blank=True)
|
|
@@ -203,7 +205,7 @@ class TaskExecution(models.Model):
|
|
|
203
205
|
|
|
204
206
|
def save(self, *args, **kwargs):
|
|
205
207
|
if not self.expire_time:
|
|
206
|
-
timeout = self.
|
|
208
|
+
timeout = self.pickup_timeout_seconds or DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS
|
|
207
209
|
self.expire_time = timezone.now() + timedelta(seconds=timeout)
|
|
208
210
|
super().save(*args, **kwargs)
|
|
209
211
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-simpletask5
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
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>
|
|
@@ -50,7 +50,8 @@ Dynamic: license-file
|
|
|
50
50
|
- **自定义事件** — 通过 `task.trigger('event_name')` 触发任意事件
|
|
51
51
|
- **灵活的执行器映射** — 不同事件可绑定不同的执行器类
|
|
52
52
|
- **队列路由** — 不同事件可路由到不同优先级队列
|
|
53
|
-
- **重试与超时** —
|
|
53
|
+
- **重试与超时** — 失败自动重试(指数退避),支持执行超时与 Pickup 超时两种检测
|
|
54
|
+
- **拒绝回退** — 执行器可通过 `RejectException` 拒绝当前任务,消息自动重新入队等待下次调度
|
|
54
55
|
- **加密字段** — 敏感数据自动加密存储
|
|
55
56
|
- **Cron 调度** — 内置 crontab 守护进程,支持代码注册与数据库覆盖
|
|
56
57
|
- **归档统计** — 已完成执行记录自动归档为加密 JSONL,并生成日统计
|
|
@@ -156,10 +157,15 @@ DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE = 'django_simpletask5.queue.high_priority'
|
|
|
156
157
|
### 执行与重试
|
|
157
158
|
|
|
158
159
|
```python
|
|
159
|
-
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES = 3
|
|
160
|
-
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = 600
|
|
160
|
+
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES = 3 # 默认最大重试次数
|
|
161
|
+
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = 600 # 默认执行超时(秒),任务开始执行后的超时限制
|
|
162
|
+
DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS = 3600 # 默认 Pickup 超时(秒),等待 Worker 消费的时间限制
|
|
161
163
|
```
|
|
162
164
|
|
|
165
|
+
框架区分两种超时:
|
|
166
|
+
- **执行超时**(`timeout_seconds`)— 任务进入 `running` 状态后,必须在指定时间内完成,否则被 `StatusCheckExecutor` 标记超时。适用于保护实际执行不 hang 死。
|
|
167
|
+
- **Pickup 超时**(`pickup_timeout_seconds`)— 任务创建后等待 Worker 消费的时间上限,也用于被拒绝回退后等待重新调度的保护。通常设得比执行超时更长,因为排队等待时间不可控。
|
|
168
|
+
|
|
163
169
|
### 归档
|
|
164
170
|
|
|
165
171
|
```python
|
|
@@ -235,6 +241,33 @@ class OrderCreateExecutor(BaseExecutor):
|
|
|
235
241
|
return 'ok'
|
|
236
242
|
```
|
|
237
243
|
|
|
244
|
+
执行器可以通过 `executor_class` 属性覆盖超时配置:
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
class OrderCreateExecutor(BaseExecutor):
|
|
248
|
+
timeout_seconds = 300 # 执行超时 5 分钟
|
|
249
|
+
pickup_timeout_seconds = 1800 # Pickup 超时 30 分钟
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### 拒绝回退
|
|
253
|
+
|
|
254
|
+
执行器在执行过程中遇到临时条件不满足(如资源上限),可以抛出 `RejectException` 拒绝本次执行:
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
from django_simpletask5.executors.exceptions import RejectException
|
|
258
|
+
|
|
259
|
+
class MyExecutor(BaseExecutor):
|
|
260
|
+
def execute(self, execution: TaskExecution) -> str | None:
|
|
261
|
+
if not self._can_proceed():
|
|
262
|
+
raise RejectException('Resource limit reached, try again later')
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
框架捕获后会:
|
|
266
|
+
- 调用 `message.reject(requeue=True)` 将消息放回队列尾部,下次重新调度
|
|
267
|
+
- `TaskExecution` 状态恢复为 `pending`,`started_at` 清空,等待下次 Worker 消费
|
|
268
|
+
- **不消耗**重试次数
|
|
269
|
+
- 刷新 `expire_time`(使用 Pickup 超时),防止被 `StatusCheckExecutor` 误回收
|
|
270
|
+
|
|
238
271
|
### 3. 启动 Worker
|
|
239
272
|
|
|
240
273
|
默认只监听 `default` 队列,`high_priority` 队列需要单独启动 Worker 处理。
|
|
@@ -315,11 +348,17 @@ Task 模型变更 → Django 信号 → 创建 TaskExecution 并发布到消息
|
|
|
315
348
|
↓
|
|
316
349
|
Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保存结果
|
|
317
350
|
↓
|
|
318
|
-
|
|
351
|
+
失败时自动重试/拒绝时重新入队,完成后归档
|
|
319
352
|
```
|
|
320
353
|
|
|
321
354
|
## Releases
|
|
322
355
|
|
|
356
|
+
### 0.2.1
|
|
357
|
+
|
|
358
|
+
- **拒绝回退机制** — 新增 `RejectException`,执行器可抛出该异常拒绝当前任务,消息自动重新入队等待下次调度,不消耗重试次数
|
|
359
|
+
- **区分两种超时** — 引入 `pickup_timeout_seconds`(等待 Worker 消费超时)与 `timeout_seconds`(执行超时)两种超时配置,`expire_time` 改用 Pickup 超时计算,超时检测更精准
|
|
360
|
+
- **文档更新** — README 补充拒绝回退机制的使用说明和两种超时的详细解释
|
|
361
|
+
|
|
323
362
|
### 0.2.0
|
|
324
363
|
|
|
325
364
|
- **破坏性变更**: 移除 `Task` 模型上的自定义主键,`task_id` 不再是主键字段,改为 `unique=True` 的唯一标识字段。所有 `Task` 子模型将自动获得 Django 默认的自增 `id` 主键。已有数据库需要迁移处理。
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5.egg-info/SOURCES.txt
RENAMED
|
@@ -24,6 +24,7 @@ django_simpletask5/executors/__init__.py
|
|
|
24
24
|
django_simpletask5/executors/archive.py
|
|
25
25
|
django_simpletask5/executors/base.py
|
|
26
26
|
django_simpletask5/executors/bash_script.py
|
|
27
|
+
django_simpletask5/executors/exceptions.py
|
|
27
28
|
django_simpletask5/executors/loader.py
|
|
28
29
|
django_simpletask5/executors/ping_pong.py
|
|
29
30
|
django_simpletask5/executors/python_script.py
|
|
@@ -45,6 +46,7 @@ django_simpletask5/migrations/0004_alter_cronjob_is_modified_by_user.py
|
|
|
45
46
|
django_simpletask5/migrations/0005_alter_cronjob_is_modified_by_user_help.py
|
|
46
47
|
django_simpletask5/migrations/0006_alter_cronjob_options_alter_taskexecution_created_at_and_more.py
|
|
47
48
|
django_simpletask5/migrations/0007_rename_task_model_to_executor_class_in_stat.py
|
|
49
|
+
django_simpletask5/migrations/0008_taskexecution_pickup_timeout_seconds.py
|
|
48
50
|
django_simpletask5/migrations/__init__.py
|
|
49
51
|
django_simpletask5/services/__init__.py
|
|
50
52
|
django_simpletask5/services/archive.py
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
from django_simpletask5.models import TaskExecution
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class BaseExecutor:
|
|
5
|
-
timeout_seconds: int | None = None
|
|
6
|
-
total_tasks_count: int | None = None
|
|
7
|
-
done_tasks_count: int | None = None
|
|
8
|
-
|
|
9
|
-
def get_tasks_count(self, execution: TaskExecution) -> int | None:
|
|
10
|
-
return 1
|
|
11
|
-
|
|
12
|
-
def get_tasks(self, execution: TaskExecution):
|
|
13
|
-
yield None
|
|
14
|
-
|
|
15
|
-
def execute(self, execution: TaskExecution, task=None) -> str | None:
|
|
16
|
-
raise NotImplementedError('Subclasses must implement execute()')
|
|
17
|
-
|
|
18
|
-
@classmethod
|
|
19
|
-
def get_parameter_schema(cls):
|
|
20
|
-
return []
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import logging
|
|
3
|
-
|
|
4
|
-
from django.db import models, transaction
|
|
5
|
-
|
|
6
|
-
from django_simpletask5.models import TaskExecution
|
|
7
|
-
from django_simpletask5.executors.base import BaseExecutor
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def _publish_message_by_id(execution_id):
|
|
11
|
-
from django_simpletask5.core.publisher import _publish_message
|
|
12
|
-
try:
|
|
13
|
-
exec_record = TaskExecution.objects.get(pk=execution_id)
|
|
14
|
-
_publish_message(exec_record)
|
|
15
|
-
except TaskExecution.DoesNotExist:
|
|
16
|
-
logger.warning('Execution %s not found for retry publish', execution_id)
|
|
17
|
-
|
|
18
|
-
logger = logging.getLogger(__name__)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class RetryTimeoutExecutor(BaseExecutor):
|
|
22
|
-
def execute(self, execution: TaskExecution, task=None) -> str | None:
|
|
23
|
-
retryable = TaskExecution.objects.filter(
|
|
24
|
-
status='timeout',
|
|
25
|
-
retry_count__lt=models.F('max_retries'),
|
|
26
|
-
)
|
|
27
|
-
ids = list(retryable.values_list('pk', flat=True))
|
|
28
|
-
|
|
29
|
-
if not ids:
|
|
30
|
-
return json.dumps({'retried': 0, 'success': True})
|
|
31
|
-
|
|
32
|
-
TaskExecution.objects.filter(pk__in=ids).update(
|
|
33
|
-
status='retry',
|
|
34
|
-
retry_count=models.F('retry_count') + 1,
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
from django_simpletask5.core.publisher import _publish_message
|
|
38
|
-
from functools import partial
|
|
39
|
-
for exec_id in ids:
|
|
40
|
-
transaction.on_commit(
|
|
41
|
-
partial(_publish_message_by_id, exec_id)
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
return json.dumps({
|
|
45
|
-
'retried': len(ids),
|
|
46
|
-
'success': True,
|
|
47
|
-
})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/core/cronjob_registry.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/core/executor_scanner.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/core/message_queue.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/core/worker_registry.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/executors/__init__.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/executors/archive.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/executors/bash_script.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/executors/loader.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/executors/ping_pong.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/executors/python_script.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/executors/simple_request.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/executors/status_check.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/management/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/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
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/migrations/__init__.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/services/__init__.py
RENAMED
|
File without changes
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5/services/archive.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5.egg-info/requires.txt
RENAMED
|
File without changes
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.1}/django_simpletask5.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|