django-simpletask5 0.2.0__tar.gz → 0.2.2__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.2}/PKG-INFO +102 -7
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/README.md +100 -6
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/__init__.py +1 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/admin.py +31 -14
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/core/cronjob_registry.py +19 -6
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/core/defaults.py +18 -2
- django_simpletask5-0.2.2/django_simpletask5/core/lock.py +22 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/core/message_queue.py +23 -25
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/core/publisher.py +59 -1
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/core/worker_registry.py +5 -2
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/dashboards.py +28 -22
- django_simpletask5-0.2.2/django_simpletask5/executors/base.py +35 -0
- django_simpletask5-0.2.2/django_simpletask5/executors/exceptions.py +13 -0
- django_simpletask5-0.2.2/django_simpletask5/executors/retry_timeout.py +66 -0
- django_simpletask5-0.2.2/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.po +68 -36
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/management/commands/django_simpletask_crontab.py +6 -2
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/management/commands/django_simpletask_executor.py +143 -66
- django_simpletask5-0.2.2/django_simpletask5/migrations/0008_taskexecution_pickup_timeout_seconds.py +20 -0
- django_simpletask5-0.2.2/django_simpletask5/migrations/0009_add_cronjob_app_label.py +20 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/models.py +48 -1
- django_simpletask5-0.2.2/django_simpletask5/static/django_simpletask5/css/dashboard.css +16 -0
- django_simpletask5-0.2.2/django_simpletask5/static/django_simpletask5/js/select2_executor.js +89 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5.egg-info/PKG-INFO +102 -7
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5.egg-info/SOURCES.txt +6 -1
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5.egg-info/requires.txt +1 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/pyproject.toml +3 -2
- django_simpletask5-0.2.0/django_simpletask5/core/lock.py +0 -24
- 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/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/LICENSE +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/apps.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/core/__init__.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/core/executor_scanner.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/core/signals.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/executors/__init__.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/executors/archive.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/executors/bash_script.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/executors/loader.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/executors/ping_pong.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/executors/python_script.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/executors/simple_request.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/executors/status_check.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/management/__init__.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/management/commands/__init__.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/management/commands/django_simpletask_archive.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/management/commands/django_simpletask_sync_cronjobs.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/migrations/0001_initial.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/migrations/0002_taskexecution_done_tasks_count_and_more.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/migrations/0003_alter_cronjob_options_remove_cronjob_name_and_more.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/migrations/0004_alter_cronjob_is_modified_by_user.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/migrations/0005_alter_cronjob_is_modified_by_user_help.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/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.2}/django_simpletask5/migrations/0007_rename_task_model_to_executor_class_in_stat.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/migrations/__init__.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/services/__init__.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/services/archive.py +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5.egg-info/dependency_links.txt +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5.egg-info/top_level.txt +0 -0
- {django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/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.2
|
|
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>
|
|
@@ -29,6 +29,7 @@ Requires-Dist: django-app-requires>=0.3.6
|
|
|
29
29
|
Requires-Dist: django-admin-dashboards>=0.1.1
|
|
30
30
|
Requires-Dist: django-checkbox-normalize>=0.2.2
|
|
31
31
|
Requires-Dist: django-tabbed-changeform-admin>=0.1.9
|
|
32
|
+
Requires-Dist: django-static-fontawesome>=5.0.2
|
|
32
33
|
Requires-Dist: cryptography>=48.0.0
|
|
33
34
|
Requires-Dist: croniter>=6.2.2
|
|
34
35
|
Requires-Dist: requests>=2.34.2
|
|
@@ -50,7 +51,8 @@ Dynamic: license-file
|
|
|
50
51
|
- **自定义事件** — 通过 `task.trigger('event_name')` 触发任意事件
|
|
51
52
|
- **灵活的执行器映射** — 不同事件可绑定不同的执行器类
|
|
52
53
|
- **队列路由** — 不同事件可路由到不同优先级队列
|
|
53
|
-
- **重试与超时** —
|
|
54
|
+
- **重试与超时** — 失败自动重试(指数退避),支持执行超时与 Pickup 超时两种检测
|
|
55
|
+
- **拒绝回退** — 执行器可通过 `RejectException` 拒绝当前任务,消息自动重新入队等待下次调度
|
|
54
56
|
- **加密字段** — 敏感数据自动加密存储
|
|
55
57
|
- **Cron 调度** — 内置 crontab 守护进程,支持代码注册与数据库覆盖
|
|
56
58
|
- **归档统计** — 已完成执行记录自动归档为加密 JSONL,并生成日统计
|
|
@@ -134,6 +136,19 @@ DJANGO_SIMPLETASK_RABBITMQ_PASSWORD = 'guest'
|
|
|
134
136
|
|
|
135
137
|
### 分布式锁
|
|
136
138
|
|
|
139
|
+
默认使用 Django 缓存中的 Redis 连接,只需配置好 Django 的 `CACHES`:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
CACHES = {
|
|
143
|
+
'default': {
|
|
144
|
+
'BACKEND': 'django_redis.cache.RedisCache',
|
|
145
|
+
'LOCATION': 'redis://127.0.0.1:6379/1',
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
如需自定义锁引擎或连接参数:
|
|
151
|
+
|
|
137
152
|
```python
|
|
138
153
|
DJANGO_SIMPLETASK_LOCK_CONFIG = {
|
|
139
154
|
'global_lock_engine_class': 'globallock.redis_global_lock.RedisGlobalLock',
|
|
@@ -149,17 +164,22 @@ DJANGO_SIMPLETASK_LOCK_TIMEOUT = 600 # 锁超时时间(秒)
|
|
|
149
164
|
### 队列路由
|
|
150
165
|
|
|
151
166
|
```python
|
|
152
|
-
DJANGO_SIMPLETASK_DEFAULT_QUEUE = '
|
|
153
|
-
DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE = '
|
|
167
|
+
DJANGO_SIMPLETASK_DEFAULT_QUEUE = 'myproject:django-simpletask5:queue:default' # 默认队列
|
|
168
|
+
DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE = 'myproject:django-simpletask5:queue:high_priority' # 高优先级队列
|
|
154
169
|
```
|
|
155
170
|
|
|
156
171
|
### 执行与重试
|
|
157
172
|
|
|
158
173
|
```python
|
|
159
|
-
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES = 3
|
|
160
|
-
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = 600
|
|
174
|
+
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES = 3 # 默认最大重试次数
|
|
175
|
+
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = 600 # 默认执行超时(秒),任务开始执行后的超时限制
|
|
176
|
+
DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS = 3600 # 默认 Pickup 超时(秒),等待 Worker 消费的时间限制
|
|
161
177
|
```
|
|
162
178
|
|
|
179
|
+
框架区分两种超时:
|
|
180
|
+
- **执行超时**(`timeout_seconds`)— 任务进入 `running` 状态后,必须在指定时间内完成,否则被 `StatusCheckExecutor` 标记超时。适用于保护实际执行不 hang 死。
|
|
181
|
+
- **Pickup 超时**(`pickup_timeout_seconds`)— 任务创建后等待 Worker 消费的时间上限,也用于被拒绝回退后等待重新调度的保护。通常设得比执行超时更长,因为排队等待时间不可控。
|
|
182
|
+
|
|
163
183
|
### 归档
|
|
164
184
|
|
|
165
185
|
```python
|
|
@@ -235,6 +255,33 @@ class OrderCreateExecutor(BaseExecutor):
|
|
|
235
255
|
return 'ok'
|
|
236
256
|
```
|
|
237
257
|
|
|
258
|
+
执行器可以通过 `executor_class` 属性覆盖超时配置:
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
class OrderCreateExecutor(BaseExecutor):
|
|
262
|
+
timeout_seconds = 300 # 执行超时 5 分钟
|
|
263
|
+
pickup_timeout_seconds = 1800 # Pickup 超时 30 分钟
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### 拒绝回退
|
|
267
|
+
|
|
268
|
+
执行器在执行过程中遇到临时条件不满足(如资源上限),可以抛出 `RejectException` 拒绝本次执行:
|
|
269
|
+
|
|
270
|
+
```python
|
|
271
|
+
from django_simpletask5.executors.exceptions import RejectException
|
|
272
|
+
|
|
273
|
+
class MyExecutor(BaseExecutor):
|
|
274
|
+
def execute(self, execution: TaskExecution) -> str | None:
|
|
275
|
+
if not self._can_proceed():
|
|
276
|
+
raise RejectException('Resource limit reached, try again later')
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
框架捕获后会:
|
|
280
|
+
- 调用 `message.reject(requeue=True)` 将消息放回队列尾部,下次重新调度
|
|
281
|
+
- `TaskExecution` 状态恢复为 `pending`,`started_at` 清空,等待下次 Worker 消费
|
|
282
|
+
- **不消耗**重试次数
|
|
283
|
+
- 刷新 `expire_time`(使用 Pickup 超时),防止被 `StatusCheckExecutor` 误回收
|
|
284
|
+
|
|
238
285
|
### 3. 启动 Worker
|
|
239
286
|
|
|
240
287
|
默认只监听 `default` 队列,`high_priority` 队列需要单独启动 Worker 处理。
|
|
@@ -263,6 +310,31 @@ order = OrderTask.objects.get(order_id='ORD-001')
|
|
|
263
310
|
order.trigger('refund', extra_context={'refund_amount': '50.00'})
|
|
264
311
|
```
|
|
265
312
|
|
|
313
|
+
### 6. 抑制异步消息
|
|
314
|
+
|
|
315
|
+
创建或更新 Task 时,可通过 `_skip_publish` 机制禁止触发 RabbitMQ 消息通知,不会创建 `TaskExecution` 也不发送异步消息。提供四种使用方式:
|
|
316
|
+
|
|
317
|
+
```python
|
|
318
|
+
# 方式一:设置实例属性
|
|
319
|
+
order = OrderTask(order_id='ORD-001', customer_name='Test', amount=Decimal('100.00'))
|
|
320
|
+
order._skip_publish = True
|
|
321
|
+
order.save()
|
|
322
|
+
|
|
323
|
+
# 方式二:调用 skip_publish() 方法(支持链式调用)
|
|
324
|
+
order.skip_publish().save()
|
|
325
|
+
|
|
326
|
+
# 方式三:save 时传入 skip_publish=True
|
|
327
|
+
order.save(skip_publish=True)
|
|
328
|
+
|
|
329
|
+
# 方式四:构造或 create 时直接传入(推荐)
|
|
330
|
+
OrderTask.objects.create(
|
|
331
|
+
order_id='ORD-001', customer_name='Test', amount=Decimal('100.00'),
|
|
332
|
+
skip_publish=True,
|
|
333
|
+
)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
`_skip_publish` 是临时实例属性,不会持久化到数据库,后续正常操作不受影响。
|
|
337
|
+
|
|
266
338
|
## Cron 任务
|
|
267
339
|
|
|
268
340
|
### 在代码中定义 Cron 任务
|
|
@@ -315,11 +387,34 @@ Task 模型变更 → Django 信号 → 创建 TaskExecution 并发布到消息
|
|
|
315
387
|
↓
|
|
316
388
|
Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保存结果
|
|
317
389
|
↓
|
|
318
|
-
|
|
390
|
+
失败时自动重试/拒绝时重新入队,完成后归档
|
|
319
391
|
```
|
|
320
392
|
|
|
321
393
|
## Releases
|
|
322
394
|
|
|
395
|
+
### 0.2.2
|
|
396
|
+
|
|
397
|
+
- **抑制异步消息** — 新增 `_skip_publish` 机制,创建/更新/删除 Task 时可通过 `skip_publish()` 方法、`save(skip_publish=True)`、构造时传入 `skip_publish=True` 等方式禁止触发 RabbitMQ 消息通知
|
|
398
|
+
- **批量创建触发异步消息** — `TaskQuerySet` + `TaskManager` 接管 `bulk_create`,批量创建 Task 后自动生成 TaskExecution 并推送 MQ;子类覆盖 `objects` 时发出警告提示
|
|
399
|
+
- **分布式锁默认引擎** — 默认使用 `DjangoRedisGlobalLock`,复用 Django `CACHES['default']` 配置(需为 Redis 缓存),无需手动配置 `DJANGO_SIMPLETASK_LOCK_CONFIG`
|
|
400
|
+
- **Redis key 命名统一** — 所有 Redis key 格式规范为 `{项目前缀}:django-simpletask5:{用途}:{id}`,项目前缀从 `ROOT_URLCONF` 自动提取,也可通过 `DJANGO_SIMPLETASK_KEY_PREFIX` 显式覆盖
|
|
401
|
+
- **CronJob 管理增强** — 新增 `app_label` 字段(自动检测),列表页展示并支持过滤
|
|
402
|
+
- **内置 CronJob 重命名** — Status Check → Task Timeout Detection,Retry Timeout → Retry Failed Tasks,Ping/Pong → Health Check 等
|
|
403
|
+
- **Dashboard 重做** — 新增今日失败卡片,成功率按 90%/80% 分绿色/橙色/红色三级,卡片颜色统一使用 info 蓝色
|
|
404
|
+
- **Pending/Running 显示修复** — 改为展示 `pending / running` 双值而非仅 pending
|
|
405
|
+
- **成功率显示优化** — `(22/25)` 与 `88%` 同行显示,字号缩小颜色淡化,高度对齐
|
|
406
|
+
- **Redis 不可达降级** — Worker 数量显示 `-` 而非 `0`,区分无数据与异常
|
|
407
|
+
- **按钮优化** — 改用 Font Awesome 图标,统一 `min-width` 对齐,title 改为中文描述
|
|
408
|
+
- **翻译补充** — 补充 Run/Activate/Deactivate 及所有内置 CronJob 名称的中文翻译
|
|
409
|
+
- **新增依赖** — `django-static-fontawesome` 用于按钮图标
|
|
410
|
+
- **示例应用** — 示例 cronjobs 模块在 AppConfig.ready() 中导入确保注册生效
|
|
411
|
+
|
|
412
|
+
### 0.2.1
|
|
413
|
+
|
|
414
|
+
- **拒绝回退机制** — 新增 `RejectException`,执行器可抛出该异常拒绝当前任务,消息自动重新入队等待下次调度,不消耗重试次数
|
|
415
|
+
- **区分两种超时** — 引入 `pickup_timeout_seconds`(等待 Worker 消费超时)与 `timeout_seconds`(执行超时)两种超时配置,`expire_time` 改用 Pickup 超时计算,超时检测更精准
|
|
416
|
+
- **文档更新** — README 补充拒绝回退机制的使用说明和两种超时的详细解释
|
|
417
|
+
|
|
323
418
|
### 0.2.0
|
|
324
419
|
|
|
325
420
|
- **破坏性变更**: 移除 `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,并生成日统计
|
|
@@ -95,6 +96,19 @@ DJANGO_SIMPLETASK_RABBITMQ_PASSWORD = 'guest'
|
|
|
95
96
|
|
|
96
97
|
### 分布式锁
|
|
97
98
|
|
|
99
|
+
默认使用 Django 缓存中的 Redis 连接,只需配置好 Django 的 `CACHES`:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
CACHES = {
|
|
103
|
+
'default': {
|
|
104
|
+
'BACKEND': 'django_redis.cache.RedisCache',
|
|
105
|
+
'LOCATION': 'redis://127.0.0.1:6379/1',
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
如需自定义锁引擎或连接参数:
|
|
111
|
+
|
|
98
112
|
```python
|
|
99
113
|
DJANGO_SIMPLETASK_LOCK_CONFIG = {
|
|
100
114
|
'global_lock_engine_class': 'globallock.redis_global_lock.RedisGlobalLock',
|
|
@@ -110,17 +124,22 @@ DJANGO_SIMPLETASK_LOCK_TIMEOUT = 600 # 锁超时时间(秒)
|
|
|
110
124
|
### 队列路由
|
|
111
125
|
|
|
112
126
|
```python
|
|
113
|
-
DJANGO_SIMPLETASK_DEFAULT_QUEUE = '
|
|
114
|
-
DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE = '
|
|
127
|
+
DJANGO_SIMPLETASK_DEFAULT_QUEUE = 'myproject:django-simpletask5:queue:default' # 默认队列
|
|
128
|
+
DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE = 'myproject:django-simpletask5:queue:high_priority' # 高优先级队列
|
|
115
129
|
```
|
|
116
130
|
|
|
117
131
|
### 执行与重试
|
|
118
132
|
|
|
119
133
|
```python
|
|
120
|
-
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES = 3
|
|
121
|
-
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = 600
|
|
134
|
+
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES = 3 # 默认最大重试次数
|
|
135
|
+
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = 600 # 默认执行超时(秒),任务开始执行后的超时限制
|
|
136
|
+
DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS = 3600 # 默认 Pickup 超时(秒),等待 Worker 消费的时间限制
|
|
122
137
|
```
|
|
123
138
|
|
|
139
|
+
框架区分两种超时:
|
|
140
|
+
- **执行超时**(`timeout_seconds`)— 任务进入 `running` 状态后,必须在指定时间内完成,否则被 `StatusCheckExecutor` 标记超时。适用于保护实际执行不 hang 死。
|
|
141
|
+
- **Pickup 超时**(`pickup_timeout_seconds`)— 任务创建后等待 Worker 消费的时间上限,也用于被拒绝回退后等待重新调度的保护。通常设得比执行超时更长,因为排队等待时间不可控。
|
|
142
|
+
|
|
124
143
|
### 归档
|
|
125
144
|
|
|
126
145
|
```python
|
|
@@ -196,6 +215,33 @@ class OrderCreateExecutor(BaseExecutor):
|
|
|
196
215
|
return 'ok'
|
|
197
216
|
```
|
|
198
217
|
|
|
218
|
+
执行器可以通过 `executor_class` 属性覆盖超时配置:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
class OrderCreateExecutor(BaseExecutor):
|
|
222
|
+
timeout_seconds = 300 # 执行超时 5 分钟
|
|
223
|
+
pickup_timeout_seconds = 1800 # Pickup 超时 30 分钟
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### 拒绝回退
|
|
227
|
+
|
|
228
|
+
执行器在执行过程中遇到临时条件不满足(如资源上限),可以抛出 `RejectException` 拒绝本次执行:
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
from django_simpletask5.executors.exceptions import RejectException
|
|
232
|
+
|
|
233
|
+
class MyExecutor(BaseExecutor):
|
|
234
|
+
def execute(self, execution: TaskExecution) -> str | None:
|
|
235
|
+
if not self._can_proceed():
|
|
236
|
+
raise RejectException('Resource limit reached, try again later')
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
框架捕获后会:
|
|
240
|
+
- 调用 `message.reject(requeue=True)` 将消息放回队列尾部,下次重新调度
|
|
241
|
+
- `TaskExecution` 状态恢复为 `pending`,`started_at` 清空,等待下次 Worker 消费
|
|
242
|
+
- **不消耗**重试次数
|
|
243
|
+
- 刷新 `expire_time`(使用 Pickup 超时),防止被 `StatusCheckExecutor` 误回收
|
|
244
|
+
|
|
199
245
|
### 3. 启动 Worker
|
|
200
246
|
|
|
201
247
|
默认只监听 `default` 队列,`high_priority` 队列需要单独启动 Worker 处理。
|
|
@@ -224,6 +270,31 @@ order = OrderTask.objects.get(order_id='ORD-001')
|
|
|
224
270
|
order.trigger('refund', extra_context={'refund_amount': '50.00'})
|
|
225
271
|
```
|
|
226
272
|
|
|
273
|
+
### 6. 抑制异步消息
|
|
274
|
+
|
|
275
|
+
创建或更新 Task 时,可通过 `_skip_publish` 机制禁止触发 RabbitMQ 消息通知,不会创建 `TaskExecution` 也不发送异步消息。提供四种使用方式:
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
# 方式一:设置实例属性
|
|
279
|
+
order = OrderTask(order_id='ORD-001', customer_name='Test', amount=Decimal('100.00'))
|
|
280
|
+
order._skip_publish = True
|
|
281
|
+
order.save()
|
|
282
|
+
|
|
283
|
+
# 方式二:调用 skip_publish() 方法(支持链式调用)
|
|
284
|
+
order.skip_publish().save()
|
|
285
|
+
|
|
286
|
+
# 方式三:save 时传入 skip_publish=True
|
|
287
|
+
order.save(skip_publish=True)
|
|
288
|
+
|
|
289
|
+
# 方式四:构造或 create 时直接传入(推荐)
|
|
290
|
+
OrderTask.objects.create(
|
|
291
|
+
order_id='ORD-001', customer_name='Test', amount=Decimal('100.00'),
|
|
292
|
+
skip_publish=True,
|
|
293
|
+
)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
`_skip_publish` 是临时实例属性,不会持久化到数据库,后续正常操作不受影响。
|
|
297
|
+
|
|
227
298
|
## Cron 任务
|
|
228
299
|
|
|
229
300
|
### 在代码中定义 Cron 任务
|
|
@@ -276,11 +347,34 @@ Task 模型变更 → Django 信号 → 创建 TaskExecution 并发布到消息
|
|
|
276
347
|
↓
|
|
277
348
|
Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保存结果
|
|
278
349
|
↓
|
|
279
|
-
|
|
350
|
+
失败时自动重试/拒绝时重新入队,完成后归档
|
|
280
351
|
```
|
|
281
352
|
|
|
282
353
|
## Releases
|
|
283
354
|
|
|
355
|
+
### 0.2.2
|
|
356
|
+
|
|
357
|
+
- **抑制异步消息** — 新增 `_skip_publish` 机制,创建/更新/删除 Task 时可通过 `skip_publish()` 方法、`save(skip_publish=True)`、构造时传入 `skip_publish=True` 等方式禁止触发 RabbitMQ 消息通知
|
|
358
|
+
- **批量创建触发异步消息** — `TaskQuerySet` + `TaskManager` 接管 `bulk_create`,批量创建 Task 后自动生成 TaskExecution 并推送 MQ;子类覆盖 `objects` 时发出警告提示
|
|
359
|
+
- **分布式锁默认引擎** — 默认使用 `DjangoRedisGlobalLock`,复用 Django `CACHES['default']` 配置(需为 Redis 缓存),无需手动配置 `DJANGO_SIMPLETASK_LOCK_CONFIG`
|
|
360
|
+
- **Redis key 命名统一** — 所有 Redis key 格式规范为 `{项目前缀}:django-simpletask5:{用途}:{id}`,项目前缀从 `ROOT_URLCONF` 自动提取,也可通过 `DJANGO_SIMPLETASK_KEY_PREFIX` 显式覆盖
|
|
361
|
+
- **CronJob 管理增强** — 新增 `app_label` 字段(自动检测),列表页展示并支持过滤
|
|
362
|
+
- **内置 CronJob 重命名** — Status Check → Task Timeout Detection,Retry Timeout → Retry Failed Tasks,Ping/Pong → Health Check 等
|
|
363
|
+
- **Dashboard 重做** — 新增今日失败卡片,成功率按 90%/80% 分绿色/橙色/红色三级,卡片颜色统一使用 info 蓝色
|
|
364
|
+
- **Pending/Running 显示修复** — 改为展示 `pending / running` 双值而非仅 pending
|
|
365
|
+
- **成功率显示优化** — `(22/25)` 与 `88%` 同行显示,字号缩小颜色淡化,高度对齐
|
|
366
|
+
- **Redis 不可达降级** — Worker 数量显示 `-` 而非 `0`,区分无数据与异常
|
|
367
|
+
- **按钮优化** — 改用 Font Awesome 图标,统一 `min-width` 对齐,title 改为中文描述
|
|
368
|
+
- **翻译补充** — 补充 Run/Activate/Deactivate 及所有内置 CronJob 名称的中文翻译
|
|
369
|
+
- **新增依赖** — `django-static-fontawesome` 用于按钮图标
|
|
370
|
+
- **示例应用** — 示例 cronjobs 模块在 AppConfig.ready() 中导入确保注册生效
|
|
371
|
+
|
|
372
|
+
### 0.2.1
|
|
373
|
+
|
|
374
|
+
- **拒绝回退机制** — 新增 `RejectException`,执行器可抛出该异常拒绝当前任务,消息自动重新入队等待下次调度,不消耗重试次数
|
|
375
|
+
- **区分两种超时** — 引入 `pickup_timeout_seconds`(等待 Worker 消费超时)与 `timeout_seconds`(执行超时)两种超时配置,`expire_time` 改用 Pickup 超时计算,超时检测更精准
|
|
376
|
+
- **文档更新** — README 补充拒绝回退机制的使用说明和两种超时的详细解释
|
|
377
|
+
|
|
284
378
|
### 0.2.0
|
|
285
379
|
|
|
286
380
|
- **破坏性变更**: 移除 `Task` 模型上的自定义主键,`task_id` 不再是主键字段,改为 `unique=True` 的唯一标识字段。所有 `Task` 子模型将自动获得 Django 默认的自增 `id` 主键。已有数据库需要迁移处理。
|
|
@@ -3,6 +3,7 @@ import json
|
|
|
3
3
|
from django import forms
|
|
4
4
|
from django.contrib import admin
|
|
5
5
|
from django.http import HttpResponseRedirect, Http404
|
|
6
|
+
from django.middleware.csrf import get_token
|
|
6
7
|
from django.urls import path, reverse
|
|
7
8
|
from django.utils.html import format_html, mark_safe
|
|
8
9
|
from django.utils.translation import gettext_lazy as _
|
|
@@ -225,6 +226,7 @@ class CronJobAdmin(DjangoCheckboxNormalizeAdmin, DjangoTabbedChangeformAdmin):
|
|
|
225
226
|
form = CronJobForm
|
|
226
227
|
list_display = [
|
|
227
228
|
"display_name",
|
|
229
|
+
"app_label",
|
|
228
230
|
"cron_expression",
|
|
229
231
|
"is_active",
|
|
230
232
|
"is_modified_by_user",
|
|
@@ -232,9 +234,14 @@ class CronJobAdmin(DjangoCheckboxNormalizeAdmin, DjangoTabbedChangeformAdmin):
|
|
|
232
234
|
"action_buttons",
|
|
233
235
|
]
|
|
234
236
|
readonly_fields = ["created_at", "updated_at"]
|
|
235
|
-
list_filter = ["is_active", "is_modified_by_user"]
|
|
237
|
+
list_filter = ["is_active", "is_modified_by_user", "app_label"]
|
|
236
238
|
search_fields = ["display_name", "uid", "executor_class"]
|
|
237
239
|
|
|
240
|
+
class Media:
|
|
241
|
+
css = {
|
|
242
|
+
"all": ("fontawesome/css/all.min.css",),
|
|
243
|
+
}
|
|
244
|
+
|
|
238
245
|
fieldsets = [
|
|
239
246
|
(None, {
|
|
240
247
|
"fields": ["uid", "display_name", "executor_class", "cron_expression", "description", "created_at", "updated_at"],
|
|
@@ -261,6 +268,10 @@ class CronJobAdmin(DjangoCheckboxNormalizeAdmin, DjangoTabbedChangeformAdmin):
|
|
|
261
268
|
def get_queryset(self, request):
|
|
262
269
|
return super().get_queryset(request)
|
|
263
270
|
|
|
271
|
+
def changelist_view(self, request, extra_context=None):
|
|
272
|
+
self._admin_request = request
|
|
273
|
+
return super().changelist_view(request, extra_context=extra_context)
|
|
274
|
+
|
|
264
275
|
def get_urls(self):
|
|
265
276
|
urls = super().get_urls()
|
|
266
277
|
custom_urls = [
|
|
@@ -311,39 +322,45 @@ class CronJobAdmin(DjangoCheckboxNormalizeAdmin, DjangoTabbedChangeformAdmin):
|
|
|
311
322
|
reverse('admin:django_simpletask5_cronjob_changelist')
|
|
312
323
|
)
|
|
313
324
|
|
|
325
|
+
_btn_base = "padding:3px 8px;border-radius:3px;border:none;cursor:pointer;margin-right:4px;font-size:12px;min-width:68px"
|
|
326
|
+
|
|
327
|
+
def _csrf_token_input(self):
|
|
328
|
+
token = get_token(self._admin_request) if hasattr(self, '_admin_request') else ''
|
|
329
|
+
return f'<input type="hidden" name="csrfmiddlewaretoken" value="{token}">'
|
|
330
|
+
|
|
314
331
|
def action_buttons(self, obj):
|
|
315
332
|
run_url = reverse('admin:django_simpletask5_cronjob_run', args=[obj.pk])
|
|
316
333
|
toggle_url = reverse('admin:django_simpletask5_cronjob_toggle_active', args=[obj.pk])
|
|
317
|
-
|
|
334
|
+
csrf_input = self._csrf_token_input()
|
|
318
335
|
|
|
319
336
|
buttons = []
|
|
320
337
|
buttons.append(
|
|
321
338
|
f'<form style="display:inline" method="post" action="{run_url}">'
|
|
322
|
-
f'
|
|
323
|
-
f'<button type="submit" style="background:#17a2b8;color:#fff;
|
|
324
|
-
f'<i class="
|
|
339
|
+
f'{csrf_input}'
|
|
340
|
+
f'<button type="submit" style="background:#17a2b8;color:#fff;{self._btn_base}" title="{_("Click to run this cronjob")}">'
|
|
341
|
+
f'<i class="fas fa-play"></i> {_("Run")}</button></form>'
|
|
325
342
|
)
|
|
326
343
|
if obj.is_active:
|
|
327
344
|
btn_style = "background:#ffc107;color:#000"
|
|
328
|
-
btn_title = _("
|
|
329
|
-
btn_icon = "
|
|
345
|
+
btn_title = _("Click to deactivate this cronjob")
|
|
346
|
+
btn_icon = "fas fa-pause"
|
|
330
347
|
btn_label = _("Deactivate")
|
|
331
348
|
else:
|
|
332
349
|
btn_style = "background:#28a745;color:#fff"
|
|
333
|
-
btn_title = _("
|
|
334
|
-
btn_icon = "
|
|
350
|
+
btn_title = _("Click to activate this cronjob")
|
|
351
|
+
btn_icon = "fas fa-play-circle"
|
|
335
352
|
btn_label = _("Activate")
|
|
336
353
|
buttons.append(
|
|
337
354
|
f'<form style="display:inline" method="post" action="{toggle_url}">'
|
|
338
|
-
f'
|
|
339
|
-
f'<button type="submit" style="{btn_style};
|
|
355
|
+
f'{csrf_input}'
|
|
356
|
+
f'<button type="submit" style="{btn_style};{self._btn_base}" title="{btn_title}">'
|
|
340
357
|
f'<i class="{btn_icon}"></i> {btn_label}</button></form>'
|
|
341
358
|
)
|
|
342
359
|
delete_url = reverse('admin:django_simpletask5_cronjob_delete', args=[obj.pk])
|
|
343
360
|
buttons.append(
|
|
344
|
-
f'<a class="button" style="background:#dc3545;color:#fff;padding:3px 8px;border-radius:3px;text-decoration:none;margin-right:4px;font-size:12px" '
|
|
345
|
-
f'href="{delete_url}" title="{_("
|
|
346
|
-
f'<i class="
|
|
361
|
+
f'<a class="button" style="background:#dc3545;color:#fff;padding:3px 8px;border-radius:3px;text-decoration:none;margin-right:4px;font-size:12px;min-width:68px;display:inline-block;text-align:center" '
|
|
362
|
+
f'href="{delete_url}" title="{_("Click to delete this cronjob")}">'
|
|
363
|
+
f'<i class="fas fa-trash"></i> {_("Delete")}</a>'
|
|
347
364
|
)
|
|
348
365
|
return mark_safe(''.join(buttons))
|
|
349
366
|
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/core/cronjob_registry.py
RENAMED
|
@@ -10,7 +10,17 @@ logger = logging.getLogger(__name__)
|
|
|
10
10
|
_CRONJOB_REGISTRY = {}
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def register_cronjob(uid, display_name, cron_expression, executor_class, context=None, description='', is_active=True):
|
|
13
|
+
def register_cronjob(uid, display_name, cron_expression, executor_class, context=None, description='', is_active=True, app_label=''):
|
|
14
|
+
if not app_label:
|
|
15
|
+
import inspect
|
|
16
|
+
frame = inspect.currentframe()
|
|
17
|
+
try:
|
|
18
|
+
caller_frame = frame.f_back
|
|
19
|
+
caller_module = inspect.getmodule(caller_frame)
|
|
20
|
+
if caller_module and caller_module.__name__:
|
|
21
|
+
app_label = caller_module.__name__.split('.')[0]
|
|
22
|
+
finally:
|
|
23
|
+
del frame
|
|
14
24
|
_CRONJOB_REGISTRY[uid] = {
|
|
15
25
|
'display_name': display_name,
|
|
16
26
|
'cron_expression': cron_expression,
|
|
@@ -18,6 +28,7 @@ def register_cronjob(uid, display_name, cron_expression, executor_class, context
|
|
|
18
28
|
'context': json.dumps(context) if context else None,
|
|
19
29
|
'description': description,
|
|
20
30
|
'is_active': is_active,
|
|
31
|
+
'app_label': app_label,
|
|
21
32
|
}
|
|
22
33
|
|
|
23
34
|
|
|
@@ -56,6 +67,7 @@ def sync_cronjobs():
|
|
|
56
67
|
cronjob.context = config.get('context')
|
|
57
68
|
cronjob.description = config.get('description', '')
|
|
58
69
|
cronjob.is_active = config.get('is_active', True)
|
|
70
|
+
cronjob.app_label = config.get('app_label', '')
|
|
59
71
|
if cronjob.next_run_time is None:
|
|
60
72
|
cronjob.next_run_time = _compute_next_run_time(config['cron_expression'])
|
|
61
73
|
cronjob.save()
|
|
@@ -68,6 +80,7 @@ def sync_cronjobs():
|
|
|
68
80
|
context=config.get('context'),
|
|
69
81
|
description=config.get('description', ''),
|
|
70
82
|
is_active=config.get('is_active', True),
|
|
83
|
+
app_label=config.get('app_label', ''),
|
|
71
84
|
is_modified_by_user=False,
|
|
72
85
|
next_run_time=_compute_next_run_time(config['cron_expression']),
|
|
73
86
|
)
|
|
@@ -80,35 +93,35 @@ def register_builtin_cronjobs():
|
|
|
80
93
|
|
|
81
94
|
register_cronjob(
|
|
82
95
|
uid='simpletask_status_check',
|
|
83
|
-
display_name=_('
|
|
96
|
+
display_name=_('Task Timeout Detection'),
|
|
84
97
|
cron_expression='* * * * *',
|
|
85
98
|
executor_class='django_simpletask5.executors.status_check.StatusCheckExecutor',
|
|
86
99
|
description='Scan long-running pending/running tasks and mark them as timeout',
|
|
87
100
|
)
|
|
88
101
|
register_cronjob(
|
|
89
102
|
uid='simpletask_retry_timeout',
|
|
90
|
-
display_name=_('Retry
|
|
103
|
+
display_name=_('Retry Failed Tasks'),
|
|
91
104
|
cron_expression='* * * * *',
|
|
92
105
|
executor_class='django_simpletask5.executors.retry_timeout.RetryTimeoutExecutor',
|
|
93
106
|
description='Retry timeout tasks that still have retry attempts remaining',
|
|
94
107
|
)
|
|
95
108
|
register_cronjob(
|
|
96
109
|
uid='simpletask_archive',
|
|
97
|
-
display_name=_('Archive'),
|
|
110
|
+
display_name=_('Execution Archive'),
|
|
98
111
|
cron_expression='0 2 * * *',
|
|
99
112
|
executor_class='django_simpletask5.executors.archive.ArchiveExecutor',
|
|
100
113
|
description='Archive previous day TaskExecution records',
|
|
101
114
|
)
|
|
102
115
|
register_cronjob(
|
|
103
116
|
uid='simpletask_purge_expired',
|
|
104
|
-
display_name=_('Purge Expired'),
|
|
117
|
+
display_name=_('Purge Expired Records'),
|
|
105
118
|
cron_expression='0 3 * * *',
|
|
106
119
|
executor_class='django_simpletask5.executors.archive.PurgeExpiredExecutor',
|
|
107
120
|
description='Purge expired TaskExecution records beyond retention days',
|
|
108
121
|
)
|
|
109
122
|
register_cronjob(
|
|
110
123
|
uid='simpletask_ping_pong',
|
|
111
|
-
display_name=_('
|
|
124
|
+
display_name=_('Health Check'),
|
|
112
125
|
cron_expression='* * * * *',
|
|
113
126
|
executor_class='django_simpletask5.executors.ping_pong.PingPongExecutor',
|
|
114
127
|
description='Ping pong health check',
|
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
from django.conf import settings
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
def _compute_key_prefix() -> str:
|
|
5
|
+
custom = getattr(settings, 'DJANGO_SIMPLETASK_KEY_PREFIX', None)
|
|
6
|
+
if custom is not None:
|
|
7
|
+
return custom
|
|
8
|
+
root_urlconf = getattr(settings, 'ROOT_URLCONF', None)
|
|
9
|
+
if root_urlconf:
|
|
10
|
+
return root_urlconf.split('.')[0]
|
|
11
|
+
return 'default'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
DJANGO_SIMPLETASK_KEY_PREFIX = _compute_key_prefix()
|
|
15
|
+
|
|
3
16
|
DJANGO_SIMPLETASK_LOCK_TIMEOUT = getattr(settings, 'DJANGO_SIMPLETASK_LOCK_TIMEOUT', 600)
|
|
17
|
+
DJANGO_SIMPLETASK_LOCK_RENEW_INTERVAL = getattr(settings, 'DJANGO_SIMPLETASK_LOCK_RENEW_INTERVAL', 60)
|
|
4
18
|
|
|
19
|
+
DJANGO_SIMPLETASK_SHUTDOWN_TIMEOUT = getattr(settings, 'DJANGO_SIMPLETASK_SHUTDOWN_TIMEOUT', 30)
|
|
5
20
|
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES = getattr(settings, 'DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES', 3)
|
|
6
21
|
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = getattr(settings, 'DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS', 600)
|
|
22
|
+
DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS = getattr(settings, 'DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS', 3600)
|
|
7
23
|
|
|
8
24
|
DJANGO_SIMPLETASK_ARCHIVE_PATH = getattr(settings, 'DJANGO_SIMPLETASK_ARCHIVE_PATH', 'django_simpletask5_archives')
|
|
9
25
|
DJANGO_SIMPLETASK_ARCHIVE_SALT = getattr(settings, 'DJANGO_SIMPLETASK_ARCHIVE_SALT', 'django-simpletask5-archive')
|
|
@@ -11,8 +27,8 @@ DJANGO_SIMPLETASK_ARCHIVE_RETENTION_DAYS = getattr(settings, 'DJANGO_SIMPLETASK_
|
|
|
11
27
|
|
|
12
28
|
DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC = getattr(settings, 'DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC', True)
|
|
13
29
|
|
|
14
|
-
DJANGO_SIMPLETASK_DEFAULT_QUEUE = getattr(settings, 'DJANGO_SIMPLETASK_DEFAULT_QUEUE', '
|
|
15
|
-
DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE = getattr(settings, 'DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE', '
|
|
30
|
+
DJANGO_SIMPLETASK_DEFAULT_QUEUE = getattr(settings, 'DJANGO_SIMPLETASK_DEFAULT_QUEUE', f'{DJANGO_SIMPLETASK_KEY_PREFIX}:django-simpletask5:queue:default')
|
|
31
|
+
DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE = getattr(settings, 'DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE', f'{DJANGO_SIMPLETASK_KEY_PREFIX}:django-simpletask5:queue:high_priority')
|
|
16
32
|
|
|
17
33
|
DJANGO_SIMPLETASK_ENABLE_SCRIPT_EXECUTORS = getattr(settings, 'DJANGO_SIMPLETASK_ENABLE_SCRIPT_EXECUTORS', False)
|
|
18
34
|
DJANGO_SIMPLETASK_SCRIPT_WHITELIST = getattr(settings, 'DJANGO_SIMPLETASK_SCRIPT_WHITELIST', [])
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _default_lock_config():
|
|
9
|
+
return {
|
|
10
|
+
'global_lock_engine_class': 'globallock.django_redis_global_lock.DjangoRedisGlobalLock',
|
|
11
|
+
'global_lock_engine_options': {
|
|
12
|
+
'redis-cache-name': 'default',
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_lock_manager():
|
|
18
|
+
from globallock import GlobalLockManager
|
|
19
|
+
config = getattr(settings, 'DJANGO_SIMPLETASK_LOCK_CONFIG', None)
|
|
20
|
+
if config is None:
|
|
21
|
+
config = _default_lock_config()
|
|
22
|
+
return GlobalLockManager(config)
|
{django_simpletask5-0.2.0 → django_simpletask5-0.2.2}/django_simpletask5/core/message_queue.py
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import socket
|
|
3
|
+
import time
|
|
3
4
|
|
|
4
5
|
from django.conf import settings
|
|
5
6
|
from kombu import Connection, Exchange, Queue, Producer, Consumer
|
|
@@ -53,32 +54,29 @@ class KombuQueue:
|
|
|
53
54
|
retry=True,
|
|
54
55
|
)
|
|
55
56
|
|
|
56
|
-
def consume(self, queue_name, callback, auto_ack=True):
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
with Connection(_get_broker_url()) as conn:
|
|
61
|
-
conn.connect()
|
|
62
|
-
queue = Queue(queue_name, self._exchange, routing_key=queue_name, durable=True)
|
|
57
|
+
def consume(self, queue_name, callback, auto_ack=True, poll_callback=None, poll_interval=60):
|
|
58
|
+
with Connection(_get_broker_url()) as conn:
|
|
59
|
+
conn.connect()
|
|
60
|
+
queue = Queue(queue_name, self._exchange, routing_key=queue_name, durable=True)
|
|
63
61
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
62
|
+
with Consumer(
|
|
63
|
+
conn,
|
|
64
|
+
queues=[queue],
|
|
65
|
+
callbacks=[lambda body, msg: self._on_message(body, msg, callback, auto_ack)],
|
|
66
|
+
accept=['json'],
|
|
67
|
+
):
|
|
68
|
+
last_poll = time.time()
|
|
69
|
+
while not self._stopped:
|
|
70
|
+
try:
|
|
71
|
+
conn.drain_events(timeout=0.5)
|
|
72
|
+
except socket.timeout:
|
|
73
|
+
pass
|
|
74
|
+
if poll_callback and time.time() - last_poll >= poll_interval:
|
|
75
|
+
try:
|
|
76
|
+
poll_callback()
|
|
77
|
+
except Exception:
|
|
78
|
+
logger.exception('Poll callback failed')
|
|
79
|
+
last_poll = time.time()
|
|
82
80
|
|
|
83
81
|
@staticmethod
|
|
84
82
|
def _on_message(body, message, callback, auto_ack):
|