django-simpletask5 0.2.1__tar.gz → 0.2.3__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.1 → django_simpletask5-0.2.3}/PKG-INFO +65 -3
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/README.md +63 -2
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/__init__.py +1 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/admin.py +31 -14
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/core/cronjob_registry.py +21 -7
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/core/defaults.py +17 -2
- django_simpletask5-0.2.3/django_simpletask5/core/lock.py +22 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/core/message_queue.py +23 -25
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/core/publisher.py +47 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/core/worker_registry.py +5 -2
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/dashboards.py +28 -22
- django_simpletask5-0.2.3/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.po +68 -36
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/management/commands/django_simpletask_crontab.py +5 -3
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/management/commands/django_simpletask_executor.py +142 -70
- django_simpletask5-0.2.3/django_simpletask5/migrations/0009_add_cronjob_app_label.py +20 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/models.py +45 -0
- django_simpletask5-0.2.3/django_simpletask5/static/django_simpletask5/css/dashboard.css +16 -0
- django_simpletask5-0.2.3/django_simpletask5/static/django_simpletask5/js/select2_executor.js +89 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5.egg-info/PKG-INFO +65 -3
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5.egg-info/SOURCES.txt +4 -1
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5.egg-info/requires.txt +1 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/pyproject.toml +3 -2
- django_simpletask5-0.2.1/django_simpletask5/core/lock.py +0 -24
- django_simpletask5-0.2.1/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/LICENSE +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/apps.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/core/__init__.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/core/executor_scanner.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/core/signals.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/executors/__init__.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/executors/archive.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/executors/base.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/executors/bash_script.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/executors/exceptions.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/executors/loader.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/executors/ping_pong.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/executors/python_script.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/executors/retry_timeout.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/executors/simple_request.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/executors/status_check.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/management/__init__.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/management/commands/__init__.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/management/commands/django_simpletask_archive.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/management/commands/django_simpletask_sync_cronjobs.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/migrations/0001_initial.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/migrations/0002_taskexecution_done_tasks_count_and_more.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/migrations/0003_alter_cronjob_options_remove_cronjob_name_and_more.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/migrations/0004_alter_cronjob_is_modified_by_user.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/migrations/0005_alter_cronjob_is_modified_by_user_help.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/migrations/0006_alter_cronjob_options_alter_taskexecution_created_at_and_more.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/migrations/0007_rename_task_model_to_executor_class_in_stat.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/migrations/0008_taskexecution_pickup_timeout_seconds.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/migrations/__init__.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/services/__init__.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/services/archive.py +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5.egg-info/dependency_links.txt +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5.egg-info/top_level.txt +0 -0
- {django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/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.3
|
|
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
|
|
@@ -135,6 +136,19 @@ DJANGO_SIMPLETASK_RABBITMQ_PASSWORD = 'guest'
|
|
|
135
136
|
|
|
136
137
|
### 分布式锁
|
|
137
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
|
+
|
|
138
152
|
```python
|
|
139
153
|
DJANGO_SIMPLETASK_LOCK_CONFIG = {
|
|
140
154
|
'global_lock_engine_class': 'globallock.redis_global_lock.RedisGlobalLock',
|
|
@@ -150,8 +164,8 @@ DJANGO_SIMPLETASK_LOCK_TIMEOUT = 600 # 锁超时时间(秒)
|
|
|
150
164
|
### 队列路由
|
|
151
165
|
|
|
152
166
|
```python
|
|
153
|
-
DJANGO_SIMPLETASK_DEFAULT_QUEUE = '
|
|
154
|
-
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' # 高优先级队列
|
|
155
169
|
```
|
|
156
170
|
|
|
157
171
|
### 执行与重试
|
|
@@ -296,6 +310,31 @@ order = OrderTask.objects.get(order_id='ORD-001')
|
|
|
296
310
|
order.trigger('refund', extra_context={'refund_amount': '50.00'})
|
|
297
311
|
```
|
|
298
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
|
+
|
|
299
338
|
## Cron 任务
|
|
300
339
|
|
|
301
340
|
### 在代码中定义 Cron 任务
|
|
@@ -329,6 +368,8 @@ python manage.py django_simpletask_sync_cronjobs
|
|
|
329
368
|
|
|
330
369
|
同步后,用户可以在 Django Admin 中查看和修改 Cron 任务,被手动修改过的任务不会在后续同步中被覆盖(`is_modified_by_user` 标记保护)。
|
|
331
370
|
|
|
371
|
+
> 注意:cron 表达式按 Django 的 `TIME_ZONE` 设置对应的本地时间解释。`USE_TZ=True` 时仍以本地时间计算触发时刻(存储为 UTC),例如 `TIME_ZONE = 'Asia/Shanghai'` 下 `30 14 * * *` 表示每天 14:30 本地时间触发。
|
|
372
|
+
|
|
332
373
|
## 内置执行器
|
|
333
374
|
|
|
334
375
|
| 执行器 | 说明 |
|
|
@@ -353,6 +394,27 @@ Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保
|
|
|
353
394
|
|
|
354
395
|
## Releases
|
|
355
396
|
|
|
397
|
+
### 0.2.3
|
|
398
|
+
|
|
399
|
+
- **Cron 时区修复** — cron 表达式改为按 Django `TIME_ZONE` 配置的本地时间解释,不再固定按 UTC 计算。`USE_TZ=True` 时使用 `timezone.localtime(now())` 作为 croniter 基准时间,非 UTC 时区下定时任务不再偏移;`USE_TZ=False` 保持原有本地时间行为不变
|
|
400
|
+
|
|
401
|
+
### 0.2.2
|
|
402
|
+
|
|
403
|
+
- **抑制异步消息** — 新增 `_skip_publish` 机制,创建/更新/删除 Task 时可通过 `skip_publish()` 方法、`save(skip_publish=True)`、构造时传入 `skip_publish=True` 等方式禁止触发 RabbitMQ 消息通知
|
|
404
|
+
- **批量创建触发异步消息** — `TaskQuerySet` + `TaskManager` 接管 `bulk_create`,批量创建 Task 后自动生成 TaskExecution 并推送 MQ;子类覆盖 `objects` 时发出警告提示
|
|
405
|
+
- **分布式锁默认引擎** — 默认使用 `DjangoRedisGlobalLock`,复用 Django `CACHES['default']` 配置(需为 Redis 缓存),无需手动配置 `DJANGO_SIMPLETASK_LOCK_CONFIG`
|
|
406
|
+
- **Redis key 命名统一** — 所有 Redis key 格式规范为 `{项目前缀}:django-simpletask5:{用途}:{id}`,项目前缀从 `ROOT_URLCONF` 自动提取,也可通过 `DJANGO_SIMPLETASK_KEY_PREFIX` 显式覆盖
|
|
407
|
+
- **CronJob 管理增强** — 新增 `app_label` 字段(自动检测),列表页展示并支持过滤
|
|
408
|
+
- **内置 CronJob 重命名** — Status Check → Task Timeout Detection,Retry Timeout → Retry Failed Tasks,Ping/Pong → Health Check 等
|
|
409
|
+
- **Dashboard 重做** — 新增今日失败卡片,成功率按 90%/80% 分绿色/橙色/红色三级,卡片颜色统一使用 info 蓝色
|
|
410
|
+
- **Pending/Running 显示修复** — 改为展示 `pending / running` 双值而非仅 pending
|
|
411
|
+
- **成功率显示优化** — `(22/25)` 与 `88%` 同行显示,字号缩小颜色淡化,高度对齐
|
|
412
|
+
- **Redis 不可达降级** — Worker 数量显示 `-` 而非 `0`,区分无数据与异常
|
|
413
|
+
- **按钮优化** — 改用 Font Awesome 图标,统一 `min-width` 对齐,title 改为中文描述
|
|
414
|
+
- **翻译补充** — 补充 Run/Activate/Deactivate 及所有内置 CronJob 名称的中文翻译
|
|
415
|
+
- **新增依赖** — `django-static-fontawesome` 用于按钮图标
|
|
416
|
+
- **示例应用** — 示例 cronjobs 模块在 AppConfig.ready() 中导入确保注册生效
|
|
417
|
+
|
|
356
418
|
### 0.2.1
|
|
357
419
|
|
|
358
420
|
- **拒绝回退机制** — 新增 `RejectException`,执行器可抛出该异常拒绝当前任务,消息自动重新入队等待下次调度,不消耗重试次数
|
|
@@ -96,6 +96,19 @@ DJANGO_SIMPLETASK_RABBITMQ_PASSWORD = 'guest'
|
|
|
96
96
|
|
|
97
97
|
### 分布式锁
|
|
98
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
|
+
|
|
99
112
|
```python
|
|
100
113
|
DJANGO_SIMPLETASK_LOCK_CONFIG = {
|
|
101
114
|
'global_lock_engine_class': 'globallock.redis_global_lock.RedisGlobalLock',
|
|
@@ -111,8 +124,8 @@ DJANGO_SIMPLETASK_LOCK_TIMEOUT = 600 # 锁超时时间(秒)
|
|
|
111
124
|
### 队列路由
|
|
112
125
|
|
|
113
126
|
```python
|
|
114
|
-
DJANGO_SIMPLETASK_DEFAULT_QUEUE = '
|
|
115
|
-
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' # 高优先级队列
|
|
116
129
|
```
|
|
117
130
|
|
|
118
131
|
### 执行与重试
|
|
@@ -257,6 +270,31 @@ order = OrderTask.objects.get(order_id='ORD-001')
|
|
|
257
270
|
order.trigger('refund', extra_context={'refund_amount': '50.00'})
|
|
258
271
|
```
|
|
259
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
|
+
|
|
260
298
|
## Cron 任务
|
|
261
299
|
|
|
262
300
|
### 在代码中定义 Cron 任务
|
|
@@ -290,6 +328,8 @@ python manage.py django_simpletask_sync_cronjobs
|
|
|
290
328
|
|
|
291
329
|
同步后,用户可以在 Django Admin 中查看和修改 Cron 任务,被手动修改过的任务不会在后续同步中被覆盖(`is_modified_by_user` 标记保护)。
|
|
292
330
|
|
|
331
|
+
> 注意:cron 表达式按 Django 的 `TIME_ZONE` 设置对应的本地时间解释。`USE_TZ=True` 时仍以本地时间计算触发时刻(存储为 UTC),例如 `TIME_ZONE = 'Asia/Shanghai'` 下 `30 14 * * *` 表示每天 14:30 本地时间触发。
|
|
332
|
+
|
|
293
333
|
## 内置执行器
|
|
294
334
|
|
|
295
335
|
| 执行器 | 说明 |
|
|
@@ -314,6 +354,27 @@ Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保
|
|
|
314
354
|
|
|
315
355
|
## Releases
|
|
316
356
|
|
|
357
|
+
### 0.2.3
|
|
358
|
+
|
|
359
|
+
- **Cron 时区修复** — cron 表达式改为按 Django `TIME_ZONE` 配置的本地时间解释,不再固定按 UTC 计算。`USE_TZ=True` 时使用 `timezone.localtime(now())` 作为 croniter 基准时间,非 UTC 时区下定时任务不再偏移;`USE_TZ=False` 保持原有本地时间行为不变
|
|
360
|
+
|
|
361
|
+
### 0.2.2
|
|
362
|
+
|
|
363
|
+
- **抑制异步消息** — 新增 `_skip_publish` 机制,创建/更新/删除 Task 时可通过 `skip_publish()` 方法、`save(skip_publish=True)`、构造时传入 `skip_publish=True` 等方式禁止触发 RabbitMQ 消息通知
|
|
364
|
+
- **批量创建触发异步消息** — `TaskQuerySet` + `TaskManager` 接管 `bulk_create`,批量创建 Task 后自动生成 TaskExecution 并推送 MQ;子类覆盖 `objects` 时发出警告提示
|
|
365
|
+
- **分布式锁默认引擎** — 默认使用 `DjangoRedisGlobalLock`,复用 Django `CACHES['default']` 配置(需为 Redis 缓存),无需手动配置 `DJANGO_SIMPLETASK_LOCK_CONFIG`
|
|
366
|
+
- **Redis key 命名统一** — 所有 Redis key 格式规范为 `{项目前缀}:django-simpletask5:{用途}:{id}`,项目前缀从 `ROOT_URLCONF` 自动提取,也可通过 `DJANGO_SIMPLETASK_KEY_PREFIX` 显式覆盖
|
|
367
|
+
- **CronJob 管理增强** — 新增 `app_label` 字段(自动检测),列表页展示并支持过滤
|
|
368
|
+
- **内置 CronJob 重命名** — Status Check → Task Timeout Detection,Retry Timeout → Retry Failed Tasks,Ping/Pong → Health Check 等
|
|
369
|
+
- **Dashboard 重做** — 新增今日失败卡片,成功率按 90%/80% 分绿色/橙色/红色三级,卡片颜色统一使用 info 蓝色
|
|
370
|
+
- **Pending/Running 显示修复** — 改为展示 `pending / running` 双值而非仅 pending
|
|
371
|
+
- **成功率显示优化** — `(22/25)` 与 `88%` 同行显示,字号缩小颜色淡化,高度对齐
|
|
372
|
+
- **Redis 不可达降级** — Worker 数量显示 `-` 而非 `0`,区分无数据与异常
|
|
373
|
+
- **按钮优化** — 改用 Font Awesome 图标,统一 `min-width` 对齐,title 改为中文描述
|
|
374
|
+
- **翻译补充** — 补充 Run/Activate/Deactivate 及所有内置 CronJob 名称的中文翻译
|
|
375
|
+
- **新增依赖** — `django-static-fontawesome` 用于按钮图标
|
|
376
|
+
- **示例应用** — 示例 cronjobs 模块在 AppConfig.ready() 中导入确保注册生效
|
|
377
|
+
|
|
317
378
|
### 0.2.1
|
|
318
379
|
|
|
319
380
|
- **拒绝回退机制** — 新增 `RejectException`,执行器可抛出该异常拒绝当前任务,消息自动重新入队等待下次调度,不消耗重试次数
|
|
@@ -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.1 → django_simpletask5-0.2.3}/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
|
|
|
@@ -27,7 +38,8 @@ def _compute_next_run_time(cron_expression, base=None):
|
|
|
27
38
|
except ImportError:
|
|
28
39
|
return None
|
|
29
40
|
if base is None:
|
|
30
|
-
|
|
41
|
+
now = timezone.now()
|
|
42
|
+
base = timezone.localtime(now) if timezone.is_aware(now) else now
|
|
31
43
|
try:
|
|
32
44
|
cron = croniter(cron_expression, base)
|
|
33
45
|
return cron.get_next(datetime)
|
|
@@ -56,6 +68,7 @@ def sync_cronjobs():
|
|
|
56
68
|
cronjob.context = config.get('context')
|
|
57
69
|
cronjob.description = config.get('description', '')
|
|
58
70
|
cronjob.is_active = config.get('is_active', True)
|
|
71
|
+
cronjob.app_label = config.get('app_label', '')
|
|
59
72
|
if cronjob.next_run_time is None:
|
|
60
73
|
cronjob.next_run_time = _compute_next_run_time(config['cron_expression'])
|
|
61
74
|
cronjob.save()
|
|
@@ -68,6 +81,7 @@ def sync_cronjobs():
|
|
|
68
81
|
context=config.get('context'),
|
|
69
82
|
description=config.get('description', ''),
|
|
70
83
|
is_active=config.get('is_active', True),
|
|
84
|
+
app_label=config.get('app_label', ''),
|
|
71
85
|
is_modified_by_user=False,
|
|
72
86
|
next_run_time=_compute_next_run_time(config['cron_expression']),
|
|
73
87
|
)
|
|
@@ -80,35 +94,35 @@ def register_builtin_cronjobs():
|
|
|
80
94
|
|
|
81
95
|
register_cronjob(
|
|
82
96
|
uid='simpletask_status_check',
|
|
83
|
-
display_name=_('
|
|
97
|
+
display_name=_('Task Timeout Detection'),
|
|
84
98
|
cron_expression='* * * * *',
|
|
85
99
|
executor_class='django_simpletask5.executors.status_check.StatusCheckExecutor',
|
|
86
100
|
description='Scan long-running pending/running tasks and mark them as timeout',
|
|
87
101
|
)
|
|
88
102
|
register_cronjob(
|
|
89
103
|
uid='simpletask_retry_timeout',
|
|
90
|
-
display_name=_('Retry
|
|
104
|
+
display_name=_('Retry Failed Tasks'),
|
|
91
105
|
cron_expression='* * * * *',
|
|
92
106
|
executor_class='django_simpletask5.executors.retry_timeout.RetryTimeoutExecutor',
|
|
93
107
|
description='Retry timeout tasks that still have retry attempts remaining',
|
|
94
108
|
)
|
|
95
109
|
register_cronjob(
|
|
96
110
|
uid='simpletask_archive',
|
|
97
|
-
display_name=_('Archive'),
|
|
111
|
+
display_name=_('Execution Archive'),
|
|
98
112
|
cron_expression='0 2 * * *',
|
|
99
113
|
executor_class='django_simpletask5.executors.archive.ArchiveExecutor',
|
|
100
114
|
description='Archive previous day TaskExecution records',
|
|
101
115
|
)
|
|
102
116
|
register_cronjob(
|
|
103
117
|
uid='simpletask_purge_expired',
|
|
104
|
-
display_name=_('Purge Expired'),
|
|
118
|
+
display_name=_('Purge Expired Records'),
|
|
105
119
|
cron_expression='0 3 * * *',
|
|
106
120
|
executor_class='django_simpletask5.executors.archive.PurgeExpiredExecutor',
|
|
107
121
|
description='Purge expired TaskExecution records beyond retention days',
|
|
108
122
|
)
|
|
109
123
|
register_cronjob(
|
|
110
124
|
uid='simpletask_ping_pong',
|
|
111
|
-
display_name=_('
|
|
125
|
+
display_name=_('Health Check'),
|
|
112
126
|
cron_expression='* * * * *',
|
|
113
127
|
executor_class='django_simpletask5.executors.ping_pong.PingPongExecutor',
|
|
114
128
|
description='Ping pong health check',
|
|
@@ -1,7 +1,22 @@
|
|
|
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)
|
|
7
22
|
DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS = getattr(settings, 'DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS', 3600)
|
|
@@ -12,8 +27,8 @@ DJANGO_SIMPLETASK_ARCHIVE_RETENTION_DAYS = getattr(settings, 'DJANGO_SIMPLETASK_
|
|
|
12
27
|
|
|
13
28
|
DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC = getattr(settings, 'DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC', True)
|
|
14
29
|
|
|
15
|
-
DJANGO_SIMPLETASK_DEFAULT_QUEUE = getattr(settings, 'DJANGO_SIMPLETASK_DEFAULT_QUEUE', '
|
|
16
|
-
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')
|
|
17
32
|
|
|
18
33
|
DJANGO_SIMPLETASK_ENABLE_SCRIPT_EXECUTORS = getattr(settings, 'DJANGO_SIMPLETASK_ENABLE_SCRIPT_EXECUTORS', False)
|
|
19
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.1 → django_simpletask5-0.2.3}/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):
|
|
@@ -33,6 +33,8 @@ def _resolve_pickup_timeout_seconds(executor_class_path: str) -> int:
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def create_and_publish(task, trigger_event, extra_context=None):
|
|
36
|
+
if getattr(task, '_skip_publish', False):
|
|
37
|
+
return None
|
|
36
38
|
executor_class_path = task.get_executor_class_path(event=trigger_event)
|
|
37
39
|
queue_name = task.get_queue_name(event=trigger_event)
|
|
38
40
|
|
|
@@ -63,6 +65,51 @@ def create_and_publish(task, trigger_event, extra_context=None):
|
|
|
63
65
|
return execution
|
|
64
66
|
|
|
65
67
|
|
|
68
|
+
def bulk_create_and_publish(tasks, trigger_event, extra_context=None):
|
|
69
|
+
executions = []
|
|
70
|
+
for task in tasks:
|
|
71
|
+
if getattr(task, '_skip_publish', False):
|
|
72
|
+
continue
|
|
73
|
+
executor_class_path = task.get_executor_class_path(event=trigger_event)
|
|
74
|
+
queue_name = task.get_queue_name(event=trigger_event)
|
|
75
|
+
|
|
76
|
+
context = {
|
|
77
|
+
'task_id': str(task.task_id),
|
|
78
|
+
'trigger_event': trigger_event,
|
|
79
|
+
'data': _model_to_dict(task),
|
|
80
|
+
}
|
|
81
|
+
if extra_context:
|
|
82
|
+
context.update(extra_context)
|
|
83
|
+
|
|
84
|
+
serialized_context = _serialize_context(context)
|
|
85
|
+
|
|
86
|
+
execution = TaskExecution(
|
|
87
|
+
task_id=task.task_id,
|
|
88
|
+
task_model=task._meta.label,
|
|
89
|
+
trigger_event=trigger_event,
|
|
90
|
+
executor_class=executor_class_path,
|
|
91
|
+
status='pending',
|
|
92
|
+
timeout_seconds=_resolve_timeout_seconds(executor_class_path),
|
|
93
|
+
pickup_timeout_seconds=_resolve_pickup_timeout_seconds(executor_class_path),
|
|
94
|
+
context=json.dumps(serialized_context, default=str) if serialized_context else None,
|
|
95
|
+
)
|
|
96
|
+
executions.append((execution, queue_name))
|
|
97
|
+
|
|
98
|
+
if not executions:
|
|
99
|
+
return []
|
|
100
|
+
|
|
101
|
+
TaskExecution.objects.bulk_create([e for e, _ in executions])
|
|
102
|
+
|
|
103
|
+
transaction.on_commit(lambda: _bulk_publish(executions))
|
|
104
|
+
|
|
105
|
+
return [e for e, _ in executions]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _bulk_publish(executions):
|
|
109
|
+
for execution, routing_key in executions:
|
|
110
|
+
_publish_message(execution, routing_key=routing_key)
|
|
111
|
+
|
|
112
|
+
|
|
66
113
|
def _publish_message(execution, routing_key=None):
|
|
67
114
|
message = {
|
|
68
115
|
'execution_id': str(execution.execution_id),
|
{django_simpletask5-0.2.1 → django_simpletask5-0.2.3}/django_simpletask5/core/worker_registry.py
RENAMED
|
@@ -46,11 +46,13 @@ def _safe_redis_call(fn, default=None, log_level=logging.WARNING):
|
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def _worker_key(pid, tid):
|
|
49
|
-
|
|
49
|
+
from django_simpletask5.core.defaults import DJANGO_SIMPLETASK_KEY_PREFIX
|
|
50
|
+
return f'{DJANGO_SIMPLETASK_KEY_PREFIX}:django-simpletask5:worker:{pid}:{tid}'
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
def _workers_pattern():
|
|
53
|
-
|
|
54
|
+
from django_simpletask5.core.defaults import DJANGO_SIMPLETASK_KEY_PREFIX
|
|
55
|
+
return f'{DJANGO_SIMPLETASK_KEY_PREFIX}:django-simpletask5:worker:*'
|
|
54
56
|
|
|
55
57
|
|
|
56
58
|
def register_worker(queue_name, services=None, pid=None, tid=None):
|
|
@@ -133,4 +135,5 @@ def get_worker_stats():
|
|
|
133
135
|
'by_queue': by_queue,
|
|
134
136
|
'by_status': by_status,
|
|
135
137
|
'workers': workers,
|
|
138
|
+
'redis_available': not _REDIS_UNAVAILABLE,
|
|
136
139
|
}
|