django-simpletask5 0.2.7__tar.gz → 0.2.9__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.
Files changed (84) hide show
  1. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/PKG-INFO +149 -50
  2. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/README.md +146 -46
  3. django_simpletask5-0.2.9/django_simpletask5/__init__.py +13 -0
  4. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/admin.py +150 -24
  5. django_simpletask5-0.2.9/django_simpletask5/apps.py +25 -0
  6. django_simpletask5-0.2.9/django_simpletask5/core/__init__.py +1 -0
  7. django_simpletask5-0.2.9/django_simpletask5/core/cronjob_registry.py +207 -0
  8. django_simpletask5-0.2.9/django_simpletask5/core/defaults.py +86 -0
  9. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/core/executor_scanner.py +47 -0
  10. django_simpletask5-0.2.9/django_simpletask5/core/lock.py +43 -0
  11. django_simpletask5-0.2.9/django_simpletask5/core/logging_utils.py +106 -0
  12. django_simpletask5-0.2.9/django_simpletask5/core/message_queue.py +269 -0
  13. django_simpletask5-0.2.9/django_simpletask5/core/publisher.py +372 -0
  14. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/core/reclaim.py +52 -5
  15. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/core/security.py +10 -0
  16. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/core/signals.py +71 -4
  17. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/core/worker_registry.py +80 -7
  18. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/dashboards.py +22 -2
  19. django_simpletask5-0.2.9/django_simpletask5/executors/__init__.py +1 -0
  20. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/executors/archive.py +15 -0
  21. django_simpletask5-0.2.9/django_simpletask5/executors/base.py +63 -0
  22. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/executors/bash_script.py +22 -0
  23. django_simpletask5-0.2.9/django_simpletask5/executors/exceptions.py +14 -0
  24. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/executors/loader.py +10 -0
  25. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/executors/ping_pong.py +5 -0
  26. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/executors/python_script.py +18 -0
  27. django_simpletask5-0.2.9/django_simpletask5/executors/retry_timeout.py +106 -0
  28. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/executors/simple_request.py +17 -1
  29. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/executors/status_check.py +8 -0
  30. django_simpletask5-0.2.9/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  31. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.po +13 -0
  32. django_simpletask5-0.2.9/django_simpletask5/management/__init__.py +1 -0
  33. django_simpletask5-0.2.9/django_simpletask5/management/commands/__init__.py +1 -0
  34. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/management/commands/django_simpletask_archive.py +16 -0
  35. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/management/commands/django_simpletask_crontab.py +69 -7
  36. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/management/commands/django_simpletask_executor.py +130 -14
  37. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/management/commands/django_simpletask_queues.py +22 -1
  38. django_simpletask5-0.2.9/django_simpletask5/management/commands/django_simpletask_sync_cronjobs.py +24 -0
  39. django_simpletask5-0.2.9/django_simpletask5/migrations/0013_alter_taskexecution_task_id.py +24 -0
  40. django_simpletask5-0.2.9/django_simpletask5/migrations/0014_cronjob_is_active_modified_by_user.py +22 -0
  41. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/models.py +244 -15
  42. django_simpletask5-0.2.9/django_simpletask5/services/__init__.py +1 -0
  43. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/services/archive.py +62 -0
  44. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5.egg-info/PKG-INFO +149 -50
  45. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5.egg-info/SOURCES.txt +2 -0
  46. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5.egg-info/requires.txt +1 -0
  47. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/pyproject.toml +3 -4
  48. django_simpletask5-0.2.7/django_simpletask5/__init__.py +0 -10
  49. django_simpletask5-0.2.7/django_simpletask5/apps.py +0 -13
  50. django_simpletask5-0.2.7/django_simpletask5/core/cronjob_registry.py +0 -130
  51. django_simpletask5-0.2.7/django_simpletask5/core/defaults.py +0 -39
  52. django_simpletask5-0.2.7/django_simpletask5/core/lock.py +0 -22
  53. django_simpletask5-0.2.7/django_simpletask5/core/logging_utils.py +0 -17
  54. django_simpletask5-0.2.7/django_simpletask5/core/message_queue.py +0 -143
  55. django_simpletask5-0.2.7/django_simpletask5/core/publisher.py +0 -184
  56. django_simpletask5-0.2.7/django_simpletask5/executors/__init__.py +0 -0
  57. django_simpletask5-0.2.7/django_simpletask5/executors/base.py +0 -35
  58. django_simpletask5-0.2.7/django_simpletask5/executors/exceptions.py +0 -13
  59. django_simpletask5-0.2.7/django_simpletask5/executors/retry_timeout.py +0 -65
  60. django_simpletask5-0.2.7/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  61. django_simpletask5-0.2.7/django_simpletask5/management/__init__.py +0 -0
  62. django_simpletask5-0.2.7/django_simpletask5/management/commands/__init__.py +0 -0
  63. django_simpletask5-0.2.7/django_simpletask5/management/commands/django_simpletask_sync_cronjobs.py +0 -14
  64. django_simpletask5-0.2.7/django_simpletask5/migrations/__init__.py +0 -0
  65. django_simpletask5-0.2.7/django_simpletask5/services/__init__.py +0 -0
  66. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/LICENSE +0 -0
  67. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0001_initial.py +0 -0
  68. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0002_taskexecution_done_tasks_count_and_more.py +0 -0
  69. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0003_alter_cronjob_options_remove_cronjob_name_and_more.py +0 -0
  70. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0004_alter_cronjob_is_modified_by_user.py +0 -0
  71. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0005_alter_cronjob_is_modified_by_user_help.py +0 -0
  72. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0006_alter_cronjob_options_alter_taskexecution_created_at_and_more.py +0 -0
  73. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0007_rename_task_model_to_executor_class_in_stat.py +0 -0
  74. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0008_taskexecution_pickup_timeout_seconds.py +0 -0
  75. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0009_add_cronjob_app_label.py +0 -0
  76. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0010_taskexecution_id_alter_taskexecution_execution_id.py +0 -0
  77. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0011_taskexecutionstat_canceled_count.py +0 -0
  78. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0012_alter_taskexecution_status.py +0 -0
  79. {django_simpletask5-0.2.7/django_simpletask5/core → django_simpletask5-0.2.9/django_simpletask5/migrations}/__init__.py +0 -0
  80. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/static/django_simpletask5/css/dashboard.css +0 -0
  81. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5/static/django_simpletask5/js/select2_executor.js +0 -0
  82. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5.egg-info/dependency_links.txt +0 -0
  83. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/django_simpletask5.egg-info/top_level.txt +0 -0
  84. {django_simpletask5-0.2.7 → django_simpletask5-0.2.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-simpletask5
3
- Version: 0.2.7
3
+ Version: 0.2.9
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>
@@ -15,14 +15,13 @@ Classifier: Framework :: Django :: 5.1
15
15
  Classifier: Framework :: Django :: 5.2
16
16
  Classifier: License :: OSI Approved :: MIT License
17
17
  Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.8
19
- Classifier: Programming Language :: Python :: 3.9
20
18
  Classifier: Programming Language :: Python :: 3.10
21
19
  Classifier: Programming Language :: Python :: 3.11
22
20
  Classifier: Programming Language :: Python :: 3.12
23
- Requires-Python: >=3.8
21
+ Requires-Python: >=3.10
24
22
  Description-Content-Type: text/markdown
25
23
  License-File: LICENSE
24
+ Requires-Dist: Django>=4.2
26
25
  Requires-Dist: globallock>=0.2.0
27
26
  Requires-Dist: django-safe-fields>=0.2.3
28
27
  Requires-Dist: django-app-requires>=0.3.6
@@ -51,7 +50,7 @@ Dynamic: license-file
51
50
  - **自定义事件** — 通过 `task.trigger('event_name')` 触发任意事件
52
51
  - **灵活的执行器映射** — 不同事件可绑定不同的执行器类
53
52
  - **队列路由** — 不同事件可路由到不同优先级队列
54
- - **重试与超时** — 失败自动重试(指数退避),支持执行超时与 Pickup 超时两种检测
53
+ - **重试与超时** — 失败自动重试(指数退避),支持 Pickup 超时检测;运行中孤儿任务由执行锁回收
55
54
  - **拒绝回退** — 执行器可通过 `RejectException` 拒绝当前任务,消息自动重新入队等待下次调度
56
55
  - **加密字段** — 敏感数据自动加密存储
57
56
  - **Cron 调度** — 内置 crontab 守护进程,支持代码注册与数据库覆盖
@@ -61,10 +60,10 @@ Dynamic: license-file
61
60
 
62
61
  ## 依赖
63
62
 
64
- - Python >= 3.8
65
- - Django >= 3.2(兼容至 5.2.x)
66
- - Kombu >= 5.3.0(消息队列,支持 RabbitMQ/Redis/内存)
67
- - Redis >= 4.0.0(分布式锁、Worker 注册中心)
63
+ - Python >= 3.10
64
+ - Django >= 4.2(兼容至 5.2.x)
65
+ - Kombu >= 5.6.2(消息队列,支持 RabbitMQ/Redis/内存)
66
+ - Redis >= 7.4.0(分布式锁、Worker 注册中心)
68
67
  - 详见 `pyproject.toml`
69
68
 
70
69
  ## 安装
@@ -134,6 +133,8 @@ INSTALLED_APPS = [
134
133
  # ── 消息队列(MQ)────────────────────────────────────────────
135
134
  # 生产使用 RabbitMQ 或 Redis;开发调试可用内存队列
136
135
  DJANGO_SIMPLETASK_BROKER_URL = 'amqp://guest:guest@127.0.0.1:5672//'
136
+ DJANGO_SIMPLETASK_BROKER_HEARTBEAT = 60 # 发布连接池的 broker 心跳(秒)
137
+ DJANGO_SIMPLETASK_MAX_REQUEUE_DELIVERIES = 100 # AMQP 消息最大重投次数,超过后丢弃
137
138
  # 若不设 BROKER_URL,则按以下参数组合:
138
139
  # DJANGO_SIMPLETASK_MESSAGE_QUEUE_TYPE = 'rabbitmq' # 'memory' | 'rabbitmq' | 'redis'
139
140
  # DJANGO_SIMPLETASK_RABBITMQ_HOST = '127.0.0.1'
@@ -172,16 +173,28 @@ DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE = 'myproject:django-simpletask5:queue:high
172
173
 
173
174
  # ── 执行与重试 ───────────────────────────────────────────────
174
175
  DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES = 3
175
- DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = 600 # 执行超时(秒)
176
+ DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = 600 # 执行超时字段(秒,当前不主动中断)
176
177
  DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS = 3600 # Pickup 超时(秒)
177
178
 
179
+ # ── 发布重试 ────────────────────────────────────────────────
180
+ DJANGO_SIMPLETASK_PUBLISH_RETRY_POLICY = {
181
+ 'max_retries': 2,
182
+ 'interval_start': 0.2,
183
+ 'interval_step': 0.3,
184
+ 'interval_max': 1.0,
185
+ }
186
+ DJANGO_SIMPLETASK_PUBLISH_RETRY_BASE_DELAY = 1.0 # 应用层第 1 次重试等待(秒)
187
+ DJANGO_SIMPLETASK_PUBLISH_RETRY_MAX_DELAY = 5.0 # 应用层重试等待上限(秒)
188
+
178
189
  # ── Worker / 日志运维参数 ────────────────────────────────────
179
- DJANGO_SIMPLETASK_WORKER_HEARTBEAT_INTERVAL = 60 # Worker 心跳间隔(秒)
180
- DJANGO_SIMPLETASK_SHUTDOWN_TIMEOUT = 30 # 优雅停机等待(秒)
181
- DJANGO_SIMPLETASK_LOG_INTERVAL = 300 # 存活/节流日志间隔(秒)
190
+ DJANGO_SIMPLETASK_WORKER_HEARTBEAT_INTERVAL = 60 # Worker 心跳/巡检间隔(秒)
191
+ DJANGO_SIMPLETASK_SHUTDOWN_TIMEOUT = 30 # 每个 worker 线程的优雅停机等待(秒)
192
+ DJANGO_SIMPLETASK_LOG_INTERVAL = 300 # 存活/注册失败等日志节流间隔(秒)
193
+ DJANGO_SIMPLETASK_PUBLISH_LOG_INTERVAL = 60 # 发布/回收失败日志节流间隔(秒)
182
194
 
183
195
  # ── 归档与统计 ──────────────────────────────────────────────
184
- DJANGO_SIMPLETASK_ARCHIVE_PATH = os.path.join(BASE_DIR, 'django_simpletask5_archives')
196
+ # 归档文件通过 TaskExecutionArchive.file 写入 MEDIA_ROOT/django_simpletask5_archives/%Y/%m/
197
+ MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
185
198
  DJANGO_SIMPLETASK_ARCHIVE_SALT = 'change-me-archive-salt' # 归档加密盐值
186
199
  DJANGO_SIMPLETASK_ARCHIVE_RETENTION_DAYS = 7
187
200
 
@@ -214,18 +227,24 @@ django_app_requires_patch_all()
214
227
  | `DJANGO_SIMPLETASK_MESSAGE_QUEUE_TYPE` | `'memory'` | `memory` 仅用于开发调试;**生产必须改为 `rabbitmq` 或 `redis`**,否则消息不持久化、跨进程 Worker 无法消费 |
215
228
  | `DJANGO_SIMPLETASK_RABBITMQ_HOST/PORT/USER/PASSWORD` | `127.0.0.1 / 5672 / guest / guest` | 类型为 `rabbitmq` 且未设 `BROKER_URL` 时生效。生产务必更换默认 `guest` 凭据 |
216
229
  | `DJANGO_SIMPLETASK_REDIS_MQ_HOST/PORT/DB/PASSWORD` | `127.0.0.1 / 6379 / 0 / None` | 类型为 `redis` 时生效。建议与锁/缓存使用**不同 db**,避免 key 冲突与互相阻塞。Redis 需认证时设置 `PASSWORD` |
230
+ | `DJANGO_SIMPLETASK_BROKER_HEARTBEAT` | `60` | Kombu 发布连接池的 broker heartbeat(秒)。网络中间设备会断开空闲连接时建议保持开启 |
231
+ | `DJANGO_SIMPLETASK_MAX_REQUEUE_DELIVERIES` | `100` | AMQP 消息被 reject/requeue 的最大次数,超过后 ack 丢弃,防止毒消息无限循环;memory/redis 传输无 `x-death` 时不生效 |
217
232
  | `CACHES['default']` | — | 默认锁引擎 `DjangoRedisGlobalLock` 依赖它,**必须配置为 Redis 缓存** |
218
233
  | `DJANGO_SIMPLETASK_LOCK_CONFIG` | `None`(默认复用缓存锁) | 需要独立连接参数时显式配置(`global_lock_engine_options` 支持 `host/port/db/password`)。**注意**:Worker 注册中心(心跳/状态)也读取该配置的 `host/port/db/password`,未配置时固定连 `127.0.0.1:6379/0`。globallock>=0.2.0 起锁有效期由 lockman/lock 作用域管理(内置看门狗自动续期),**不再需要配置 `LOCK_TIMEOUT` / `LOCK_RENEW_INTERVAL`** |
219
234
  | `DJANGO_SIMPLETASK_KEY_PREFIX` | 自动取 `ROOT_URLCONF` 首段 | 生产建议**显式指定**,多项目共用 Redis 时避免 key 冲突 |
220
235
  | `DJANGO_SIMPLETASK_DEFAULT_QUEUE` | `{prefix}:...:queue:default` | 默认队列名。多环境(dev/staging/prod)建议加环境后缀隔离 |
221
236
  | `DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE` | `{prefix}:...:queue:high_priority` | 高优先级队列,**需单独启动 Worker 消费** |
222
237
  | `DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES` | `3` | 默认最大重试次数。执行器幂等性好的任务可加大 |
223
- | `DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS` | `600` | 执行超时,应**大于任务实际最长执行时间**,否则任务被误判超时 |
238
+ | `DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS` | `600` | 写入 `TaskExecution.timeout_seconds`,并用于计算 running 时的 `expire_time`。当前版本不会因此主动中断 running 任务;严格超时应由执行器自行实现 |
224
239
  | `DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS` | `3600` | Pickup 超时(等待 Worker 消费的上限),**通常大于执行超时**,因为排队等待时间不可控 |
225
- | `DJANGO_SIMPLETASK_WORKER_HEARTBEAT_INTERVAL` | `60` | Worker 心跳间隔,应**小于 Worker 注册 TTL(300 秒)**,否则注册状态被误清理 |
226
- | `DJANGO_SIMPLETASK_SHUTDOWN_TIMEOUT` | `30` | 优雅停机等待时长,应**大于单任务最长执行时长**,否则停机会中断进行中的任务 |
227
- | `DJANGO_SIMPLETASK_LOG_INTERVAL` | `300` | 存活日志与异常节流日志的输出间隔。日志量大时可调大 |
228
- | `DJANGO_SIMPLETASK_ARCHIVE_PATH` | `django_simpletask5_archives` | 归档目录。生产建议指向**持久化/对象存储**挂载目录 |
240
+ | `DJANGO_SIMPLETASK_PUBLISH_RETRY_POLICY` | `{'max_retries': 2, 'interval_start': 0.2, 'interval_step': 0.3, 'interval_max': 1.0}` | Kombu Producer 内层重试策略。应用层还会做最多 3 次发布尝试 |
241
+ | `DJANGO_SIMPLETASK_PUBLISH_RETRY_BASE_DELAY` | `1.0` | 应用层发布重试第 1 次等待(秒),之后按指数退避 |
242
+ | `DJANGO_SIMPLETASK_PUBLISH_RETRY_MAX_DELAY` | `5.0` | 应用层单次发布重试等待上限(秒) |
243
+ | `DJANGO_SIMPLETASK_WORKER_HEARTBEAT_INTERVAL` | `60` | Worker 心跳/巡检间隔,应**小于 Worker 注册 TTL(300 秒)**,否则注册状态被误清理 |
244
+ | `DJANGO_SIMPLETASK_SHUTDOWN_TIMEOUT` | `30` | **每个** worker 线程的优雅停机等待时长;总等待时间可能为 worker 数乘以该值 |
245
+ | `DJANGO_SIMPLETASK_LOG_INTERVAL` | `300` | Worker 注册失败、Crontab 错误等日志的节流间隔。日志量大时可调大 |
246
+ | `DJANGO_SIMPLETASK_PUBLISH_LOG_INTERVAL` | `60` | 发布、重投、孤儿回收失败日志的节流间隔(秒) |
247
+ | `DJANGO_SIMPLETASK_ARCHIVE_PATH` | `django_simpletask5_archives` | **当前版本未读取,仅保留兼容**。归档文件实际写入 `MEDIA_ROOT/django_simpletask5_archives/%Y/%m/` |
229
248
  | `DJANGO_SIMPLETASK_ARCHIVE_SALT` | `django-simpletask5-archive` | 归档加密盐值,**生产必须修改**(结合 `SECRET_KEY` 派生 AES 密钥) |
230
249
  | `DJANGO_SIMPLETASK_ARCHIVE_RETENTION_DAYS` | `7` | 归档保留天数,按合规要求调整 |
231
250
  | `DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC` | `True` | 自动把代码注册的 Cron 同步到数据库;只想手工管理时设为 `False` |
@@ -238,6 +257,8 @@ django_app_requires_patch_all()
238
257
 
239
258
  > 注意:`DJANGO_SIMPLETASK_ENABLE_SCRIPT_EXECUTORS` 设为 `True` 后,还需为用户/组授予 `django_simpletask5 | Cron job | Can use script executors` 权限,非超级管理员无法创建 Python/Shell 脚本执行器类型的定时任务。
240
259
 
260
+ > 发布可靠性说明:应用层 `_publish_message` 最多尝试 3 次(首次 + 2 次重试),失败等待按 `PUBLISH_RETRY_BASE_DELAY` 指数增长并受 `PUBLISH_RETRY_MAX_DELAY` 限制;Kombu Producer 内部还会按 `PUBLISH_RETRY_POLICY` 重试。最终仍失败时,`safe_publish_message()` 会把非终态 execution 标记为 `failed`,不会让调用方因 broker 异常崩溃。`publish_message()` 保留异常上抛语义,供需要自行处理错误的调用方使用。
261
+
241
262
  ## 快速开始(从零到一集成)
242
263
 
243
264
  本框架的集成分三步:**定义任务模型 → 编写执行器 → 启动 Worker**。完整可运行的参考实现见仓库中的 `django_simpletask5_example` 应用,包含 `OrderTask` 模型、创建/更新/删除/退款四个执行器及内置 Cron 任务。
@@ -278,7 +299,7 @@ class OrderTask(Task):
278
299
  ```
279
300
 
280
301
  要点:
281
- - 主键为 Django 默认自增 `id`;内置 `task_id`(UUID,`unique=True`)作为业务唯一标识,**不是**主键
302
+ - 主键为 Django 默认自增 `id`。`Task` 不再内置 `task_id` 字段,业务标识由业务模型自行定义(自定义主键或自然键均可)
282
303
  - 保存 / 删除 / 批量创建自动触发事件,详见「事件处理机制」
283
304
  - 事件上下文、update 字段判定、delete 拦截等行为的完整说明见对应章节
284
305
 
@@ -292,7 +313,7 @@ from django_simpletask5.executors.base import BaseExecutor
292
313
  from django_simpletask5.models import TaskExecution
293
314
 
294
315
  class OrderCreateExecutor(BaseExecutor):
295
- timeout_seconds = 300 # 执行超时 5 分钟(可选)
316
+ timeout_seconds = 300 # 记录预期执行上限(当前不主动中断,可选)
296
317
  pickup_timeout_seconds = 1800 # Pickup 超时 30 分钟(可选)
297
318
 
298
319
  def execute(self, execution: TaskExecution, task=None) -> str | None:
@@ -354,7 +375,7 @@ OrderTask.objects.create(order_id='ORD-001', customer_name='Test', amount=Decima
354
375
  | `update` | 字段值发生变化后保存 | `task_id`、`trigger_event`、`data`、`old_data`、`new_data`、`changed_fields` |
355
376
  | `delete` | 调用 `instance.delete()` | `task_id`、`trigger_event`、`data` |
356
377
 
357
- 执行器通过 `execution.get_context_dict()` 获取以上上下文。
378
+ 执行器通过 `execution.get_context_dict()` 获取以上上下文。其中 `task_id` 是业务对象主键的字符串表示(如 `"42"`、UUID 字符串或自定义键);业务对象反查见「模型与生命周期」。
358
379
 
359
380
  #### update 事件的字段变更判定
360
381
 
@@ -393,6 +414,8 @@ class OrderDeleteExecutor(BaseExecutor):
393
414
 
394
415
  这样实现删除操作与外部系统清理的解耦,且支持失败重试。
395
416
 
417
+ `confirm_delete()` 与 `execution.get_task()` 都会通过 `TaskExecution.task_id`(业务主键字符串)反查业务对象,反查逻辑由业务模型上的 `get_task_instance()` 提供(见「模型与生命周期」)。
418
+
396
419
  ### 事件 → 执行器映射
397
420
 
398
421
  每个事件消息最终要由一个执行器处理。`executor_class` 声明「事件名 → 执行器类路径」的分派表,可以是字符串(所有事件共用同一个执行器)或字典(按事件分派):
@@ -484,7 +507,7 @@ OrderTask.objects.create(..., skip_publish=True) # 方式四:构造参数(
484
507
  | `get_tasks(execution)` | 返回可迭代对象,默认 `yield None`(单任务) |
485
508
  | `get_tasks_count(execution)` | 任务总数(用于进度展示),默认 `1` |
486
509
  | `reject(execution, reason='')` | 拒绝任务:状态复位 `pending`、清空 `started_at`、刷新 `expire_time` |
487
- | `timeout_seconds` / `pickup_timeout_seconds` | 类属性,覆盖该执行器的超时配置 |
510
+ | `timeout_seconds` / `pickup_timeout_seconds` | 类属性。`pickup_timeout_seconds` 用于自动超时判定;`timeout_seconds` 当前仅记录预期执行上限,不主动中断任务 |
488
511
  | `get_parameter_schema()` | 声明执行器参数,用于 Admin 参数配置界面(默认空) |
489
512
 
490
513
  ### execute 签名与返回值
@@ -556,43 +579,75 @@ class MyExecutor(BaseExecutor):
556
579
 
557
580
  ```python
558
581
  class OrderCreateExecutor(BaseExecutor):
559
- timeout_seconds = 300 # 执行超时 5 分钟
582
+ timeout_seconds = 300 # 记录预期执行上限(当前不主动中断)
560
583
  pickup_timeout_seconds = 1800 # Pickup 超时 30 分钟
561
584
  ```
562
585
 
563
586
  未配置时使用全局默认值 `DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS`(600 秒)与 `DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS`(3600 秒)。
564
587
 
588
+ > 注意:当前版本只有 `pickup_timeout_seconds` 会被框架自动判定并触发 `timeout`;`timeout_seconds` 不会中止正在运行的执行器。需要严格执行时限时,请在 `execute()` 内部自行实现超时或使用可取消的子进程。
589
+
565
590
  ## 模型与生命周期
566
591
 
567
592
  ### Task 抽象模型
568
593
 
569
594
  所有业务任务模型继承 `Task`:
570
595
 
571
- - 内置 `task_id`(UUID,`unique=True`);主键为 Django 默认自增 `id`,无需自定义
596
+ - 主键为 Django 默认自增 `id`,无需自定义;**不内置** `task_id` 字段,业务标识由业务模型按需定义
572
597
  - 提供 `trigger(event, extra_context)`、`skip_publish()`、`get_executor_class_path(event)`、`get_queue_name(event)` 等方法
573
598
  - 保存、删除、批量创建自动触发事件(见「事件处理机制」)
574
599
 
600
+ #### 业务对象反查:`get_task_instance()`
601
+
602
+ `TaskExecution.task_id` 存的是业务对象主键的字符串表示(`CharField`)。需要反查业务对象时(`execution.get_task()` / `execution.confirm_delete()`),框架先通过 `task_model` 解析出模型类,再调用该类的 `get_task_instance(task_id)` 完成查询。
603
+
604
+ 默认实现在 `Task` 基类中按主键 `pk` 查询,并通过内置的 `_coerce_task_pk()` 兼容常见主键类型:
605
+
606
+ - `AutoField` / `BigAutoField` / `SmallAutoField` — 字符串转为 `int`
607
+ - `UUIDField` — 字符串转为 `uuid.UUID`
608
+ - `CharField` / `TextField` 等字符串主键 — 原样使用
609
+
610
+ ```python
611
+ task = execution.get_task() # 返回业务对象(反查不到返回 None)
612
+ execution.confirm_delete() # 反查并真实删除业务对象
613
+ ```
614
+
615
+ 业务模型使用自定义主键或非主键自然键时,**重载 `get_task_instance()`** 即可,框架会自动改走你的实现:
616
+
617
+ ```python
618
+ class OrderTask(Task):
619
+ code = models.CharField(max_length=64, unique=True) # 非主键自然键
620
+
621
+ @classmethod
622
+ def get_task_instance(cls, task_id):
623
+ if task_id is None:
624
+ return None
625
+ return cls.objects.filter(code=task_id).first()
626
+ ```
627
+
575
628
  ### TaskExecution 状态机
576
629
 
577
630
  每条事件消息对应一条 `TaskExecution` 记录,状态流转如下:
578
631
 
579
632
  ```
580
633
  pending ──(Worker 消费)──▶ running ──(执行成功)──▶ success
581
- │
582
- ├─(执行异常且重试次数用完)──▶ failed
583
- ├─(执行异常但可重试)────────▶ retry ──(重新入队)──▶ pending
584
- └─(expire_time 超时)────────▶ timeout ──(重试)────▶ retry
585
- pending ──(用户取消)────────▶ canceled(终态)
634
+ │ │
635
+ │ ├─(异常且可重试)────────▶ retry ──(重新入队并被消费)──▶ running
636
+ │ ├─(异常且重试耗尽)──────▶ failed
637
+ │ └─(执行器异常退出/锁丢失)─▶ retry 或 failed(由 reclaim 回收)
638
+ ├─(pickup 超时,从未被领取)──▶ timeout ──(RetryTimeout)──▶ retry
639
+ ├─(用户取消)────────────────▶ canceled(终态)
640
+ └─(发布最终失败)────────────▶ failed
586
641
  ```
587
642
 
588
643
  | 状态 | 含义 |
589
644
  |---|---|
590
- | `pending` | 已发布,等待 Worker 消费 |
591
- | `running` | 正在执行 |
645
+ | `pending` | 已发布,等待 Worker 消费;拒绝回退也会回到该状态 |
646
+ | `running` | 已获取执行锁,正在执行 |
592
647
  | `success` | 执行成功 |
593
- | `failed` | 重试次数耗尽后失败 |
594
- | `retry` | 失败但可重试,消息已重新入队 |
595
- | `timeout` | 超过 `expire_time` 未完成 |
648
+ | `failed` | 重试次数耗尽、发布最终失败,或孤儿回收时已无重试次数 |
649
+ | `retry` | 失败/超时后等待重新投递,消息已重新入队 |
650
+ | `timeout` | **pending** 超过 pickup 期限仍未被领取;running 不会因 `expire_time` 自动变成 timeout |
596
651
  | `canceled` | 被用户主动取消(终态,Worker 不会执行) |
597
652
 
598
653
  ### 取消任务
@@ -612,29 +667,29 @@ TaskExecution.objects.filter(
612
667
  ### 重试与超时
613
668
 
614
669
  - **重试**:`max_retries`(默认 3)控制最大重试次数,`retry_count` 累计已重试次数;重试消息重新发布到原队列
615
- - **两种超时**:
616
- - **执行超时**(`timeout_seconds`):进入 `running` 后必须在指定时间内完成,防止执行 hang 死
617
- - **Pickup 超时**(`pickup_timeout_seconds`):创建后被 Worker 消费的等待上限,也用于拒绝回退后的重新调度保护
618
- - **超时检测与恢复**:内置 `StatusCheckExecutor`(内置 Cron)将超时的 `pending`/`running` 记录标记为 `timeout`;`RetryTimeoutExecutor` 将 `timeout` 记录重新入队重试(执行超时消耗重试次数,Pickup 超时不消耗)
670
+ - **Pickup 超时**(`pickup_timeout_seconds`):**唯一由框架自动判定的超时**。`pending` 超过 `expire_time` 后由 `StatusCheckExecutor` 标记为 `timeout`,也用于拒绝回退后的重新调度保护;Pickup 超时不消耗重试次数
671
+ - **执行超时字段**(`timeout_seconds`):写入 `TaskExecution.timeout_seconds`,并在任务进入 `running` 时用于计算 `expire_time`。当前版本**不会**因该时间到达而主动中断 running 任务;如果执行器 hang 住,需在执行器内部实现超时/取消机制
672
+ - **超时检测与恢复**:`StatusCheckExecutor` 每分钟把超过 pickup 期限的 `pending` 记录标记为 `timeout`;`RetryTimeoutExecutor` 每分钟把 `timeout` 记录重新入队,实际运行过的记录消耗一次重试次数。running 孤儿任务由 crontab 基于执行锁过期回收,而不是按 `expire_time` 判超时
619
673
 
620
674
  ### 其他模型
621
675
 
622
676
  - `CronJob` — 定时任务注册表,支持代码注册与数据库覆盖(见「Cron 任务」)
623
- - `TaskExecutionArchive` — 归档记录,已完成执行归档为加密 JSONL(见配置「归档」)
624
- - `TaskExecutionStat` — 按 `executor_class` 与日期聚合的日统计
677
+ - `TaskExecutionArchive` — 归档记录,终态执行记录归档为加密 JSONL(见配置「归档」)
678
+ - `TaskExecutionStat` — 按日期、`executor_class`、`trigger_event` 聚合的日统计
625
679
 
626
680
  ## Cron 任务
627
681
 
628
682
  ### 在代码中定义 Cron 任务
629
683
 
630
- 在任意 app 的 `cronjobs.py` 文件中使用 `register_cronjob` 注册:
684
+ 在任意 app 的 `cronjobs.py` 文件中使用 `register_cronjob` 注册,并在该 app 的 `AppConfig.ready()` 中导入这个模块;框架不会自动发现各 app 下的 `cronjobs.py`:
631
685
 
632
686
  ```python
633
687
  # myapp/cronjobs.py
634
- from django_simpletask5.cronjob_registry import register_cronjob
688
+ from django_simpletask5.core.cronjob_registry import register_cronjob
635
689
 
636
690
  register_cronjob(
637
- name='health_check',
691
+ uid='health_check',
692
+ display_name='Health Check',
638
693
  cron_expression='*/5 * * * *',
639
694
  executor_class='django_simpletask5.executors.simple_request.SimpleRequestExecutor',
640
695
  context={
@@ -646,15 +701,33 @@ register_cronjob(
646
701
  )
647
702
  ```
648
703
 
704
+ ```python
705
+ # myapp/apps.py
706
+ from django.apps import AppConfig
707
+
708
+ class MyAppConfig(AppConfig):
709
+ name = 'myapp'
710
+
711
+ def ready(self):
712
+ import myapp.cronjobs # noqa: F401
713
+ ```
714
+
649
715
  ### 从代码同步到数据库
650
716
 
651
- 注册的 Cron 任务需要同步到数据库才会生效。框架默认在 `django_simpletask_crontab` 启动时自动同步(可通过 `DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC = False` 关闭),也可以手动执行:
717
+ 注册的 Cron 任务需要同步到数据库才会生效。`django_simpletask_crontab` 运行期间每个 tick 都会自动同步(默认开启,可通过 `DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC = False` 关闭自动同步),也可以手动执行:
652
718
 
653
719
  ```bash
654
720
  python manage.py django_simpletask_sync_cronjobs
655
721
  ```
656
722
 
657
- 同步后,用户可以在 Django Admin 中查看和修改 Cron 任务,被手动修改过的任务不会在后续同步中被覆盖(`is_modified_by_user` 标记保护)。
723
+ 手动同步命令使用 `sync_cronjobs(force=True)`,即使关闭了自动同步也会执行。同步后,用户可以在 Django Admin 中查看和修改 Cron 任务,被手动修改过的任务不会在后续同步中被覆盖(`is_modified_by_user` 标记保护)。单个 CronJob 创建失败只记录日志,不会中断其它任务同步。
724
+
725
+ 启用/停用(`is_active`)的所有权独立跟踪,规则与 `is_modified_by_user` 对称——**手动改过就一直归你,直到显式重置**:
726
+
727
+ - 用户**未**手动启停过的任务,`is_active` 会按代码注册值同步(例如 `simpletask_ping_pong` 注册为 `is_active=False`,默认保持停用)。
728
+ - 用户手动暂停/启用后,会置 `is_active_modified_by_user` 标记,自动同步不再覆盖该状态,但**不影响**其它定义字段(`cron_expression` 等仍照常同步)。
729
+ - 该标记不会自动清除;需要让它重新跟随代码时,使用管理后台的“从代码定义重置”(`reset_from_code`),或在编辑页取消勾选“保留手动启用状态”。
730
+ - 这样手动启停的状态可稳定保持,不会因代码默认值变化而悄悄被改写。
658
731
 
659
732
  > 注意:cron 表达式按 Django 的 `TIME_ZONE` 设置对应的本地时间解释。`USE_TZ=True` 时仍以本地时间计算触发时刻(存储为 UTC),例如 `TIME_ZONE = 'Asia/Shanghai'` 下 `30 14 * * *` 表示每天 14:30 本地时间触发。
660
733
 
@@ -662,13 +735,14 @@ python manage.py django_simpletask_sync_cronjobs
662
735
 
663
736
  | 执行器 | 说明 |
664
737
  |---|---|
665
- | `PingPongExecutor` | 健康检查,返回 `'pong'` |
738
+ | `PingPongExecutor` | 健康检查,返回 `'pong'`;内置 Cron 默认停用 |
666
739
  | `BashScriptExecutor` | 执行 Shell 脚本 |
667
740
  | `PythonScriptExecutor` | 执行 Python 代码 |
668
741
  | `SimpleRequestExecutor` | 发起 HTTP 请求 |
669
- | `StatusCheckExecutor` | 检测卡住的执行并标记超时(每 5 分钟) |
670
- | `RetryTimeoutExecutor` | 重试超时的执行(每 10 分钟) |
671
- | `ArchiveExecutor` | 归档已完成执行并生成统计(每天凌晨 2 点) |
742
+ | `StatusCheckExecutor` | 检测超过 pickup 期限、从未被领取的 `pending` 记录并标记 `timeout`(内置 Cron 每分钟) |
743
+ | `RetryTimeoutExecutor` | 将 `timeout` 记录重新入队(内置 Cron 每分钟;运行过的记录消耗一次重试) |
744
+ | `ArchiveExecutor` | 归档前一天的终态执行并生成统计(内置 Cron 每天 2:00) |
745
+ | `PurgeExpiredExecutor` | 删除超过 `ARCHIVE_RETENTION_DAYS` 的终态执行记录(内置 Cron 每天 3:00;不删除归档文件) |
672
746
 
673
747
  ## 开发与测试
674
748
 
@@ -702,6 +776,31 @@ Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保
702
776
 
703
777
  ## Releases
704
778
 
779
+ ### 0.2.9
780
+
781
+ - **启用状态所有权独立跟踪** — 新增 `CronJob.is_active_modified_by_user` 字段与迁移 `0014`,将“启用/暂停状态”与“定义字段”的所有权彻底分离。用户手动启停(快捷按钮 / 批量操作 / 编辑表单)后置该标记,自动同步(`sync_cronjobs`)不再覆盖 `is_active`;未手动改过的任务仍按代码注册值同步。暂停只锁 `is_active`,`cron_expression` 等定义字段照常同步
782
+ - **显式释放所有权** — 该标记不会自动清除;需要让任务重新跟随代码时,使用管理后台“从代码定义重置”(`reset_from_code`),或在编辑页取消勾选“保留手动启用状态”
783
+ - **保留列表分页/过滤条件** — 修复暂停/运行按钮提交后重定向丢失查询串的问题:按钮表单 `action` 及视图重定向均携带当前 changelist 的 `GET` 参数,操作后刷新仍停留在原分页、过滤器与搜索条件
784
+ - **消息发布可靠性增强** — Kombu 发布复用连接池并支持 broker heartbeat;`publish()` 返回 `queue` / `message_count` / `consumer_count` 队列统计;新增可配置发布重试策略和递增重试延迟;发布异常时自动丢弃损坏连接;新增公开的 `publish_message()` 与兜底的 `safe_publish_message()`,最终发布失败会将 execution 标记为 `failed`
785
+ - **发布日志节流** — 发布、重试投递、孤儿任务回收和 CronJob 触发失败统一接入日志节流,避免 broker/Redis 长时间不可用时刷屏;重投日志同时记录队列积压与消费者数量
786
+ - **Worker 与回收健壮性** — Worker 注册表 Redis 客户端改为公开 `get_redis_client()`;修正 Worker 清理 key 的 glob 为 `worker:{pid}:*`;`reclaim._lock_config()` 与 `get_lock_manager()` 对齐,只有 `None` 才回退默认锁配置;回收扫描前后关闭旧数据库连接,减少长时间运行后的连接失效
787
+ - **日志初始化修复** — `setup_console_logging()` 不再把 `FileHandler` 误判为控制台 handler,避免已有文件日志时跳过控制台输出
788
+ - **CronJob 同步健壮性** — `sync_cronjobs(force=True)` 支持手动同步命令绕过 `DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC`;单条 CronJob 创建失败不再中断其它任务同步;`django_simpletask_sync_cronjobs` 命令改为强制同步
789
+ - **API 命名整理** — `_CRONJOB_REGISTRY` → `CRONJOB_REGISTRY`、`_compute_next_run_time()` → `compute_next_run_time()`、`_get_broker_url()` → `get_broker_url()`、`_model_to_dict()` → `model_to_dict()`、`_DeleteIntercepted` → `DeleteIntercepted`、`_get_redis_client()` → `get_redis_client()`;依赖旧私有名称的扩展或测试需同步调整
790
+ - **可观测性入口公开** — Dashboard 使用的 Worker 统计入口由 `_get_worker_stats` 改为公开的 `get_worker_stats()`;消费者线程心跳改为读取 `DJANGO_SIMPLETASK_WORKER_HEARTBEAT_INTERVAL`,Worker 注册、Redis 降级和日志节流逻辑补充了完整说明
791
+ - **注释与文档校准** — 为整个 `django_simpletask5` 生产包补充中文模块/类/函数/分支注释,并修正归档路径、归档清理、`running/timeout` 状态流转、Admin 只读性、脚本白名单、连接池 heartbeat、优雅退出等待时间等与实际行为不一致的说明
792
+ - **迁移与翻译** — 新增迁移 `django_simpletask5.0014_cronjob_is_active_modified_by_user`,同步更新简体中文翻译与迁移中的字段 `help_text`
793
+ - **测试** — 新增 `test_logging_utils.py`;扩充消息发布、消息队列、CronJob 同步、Admin 启停、Reclaim、Worker 恢复、Redis 锁与集成测试,覆盖最终发布失败、队列积压统计、`force` 同步、启停所有权、查询串保留、空锁配置和 Worker 清理 glob 等场景
794
+ - **环境要求** — 代码使用 `X | None` 联合类型,最低 Python 版本明确为 3.10;同时将 Django 最低版本明确为 4.2,并补充 `Django>=4.2` 依赖声明
795
+ - **版本** — `pyproject.toml` 版本更新为 `0.2.9`
796
+
797
+ ### 0.2.8
798
+
799
+ - **移除 `Task` 冗余的 `task_id` 字段** — `TaskExecution.task_id` 由 UUID 改为 `CharField(max_length=255)`,直接用于存放业务对象主键字符串(`str(instance.pk)`),不再维护一份与业务主键重复的 UUID。消息上下文中的 `task_id` 字段名不变,值语义从 UUID 变为业务主键字符串(如 `"42"`)
800
+ - **业务对象反查下沉到模型层** — 新增 `Task.get_task_instance(task_id)` 类方法作为默认反查实现(按 `pk` 查询,内置 `_coerce_task_pk()` 兼容 `int` / `UUID` / 字符串主键);`TaskExecution.get_task()` / `confirm_delete()` 改为解析 `task_model` 后委托给该方法。业务模型使用自定义主键或自然键时重载 `get_task_instance()` 即可
801
+ - **新增迁移** — `django_simpletask5.0013_alter_taskexecution_task_id`(字段改 CharField)、`django_simpletask5_example.0003_remove_ordertask_task_id`
802
+ - **破坏性变更**: 依赖 `TaskExecution.task_id` 为 UUID 类型的外部消费方需适配为业务主键字符串;业务 `Task` 子类若曾使用内置 `task_id` 字段需移除
803
+
705
804
  ### 0.2.7
706
805
 
707
806
  - **运行中任务回收机制重构** — 取消原先按 `expire_time` 将 running 直接判为超时的语义;`StatusCheckExecutor` 仅兜底回收长期未被 Worker 获取的 pending。新增 `core/reclaim`,由 crontab 每 ~5 秒基于执行锁探测运行中任务:锁已过期(执行器异常退出)即快速重投或直接置失败,降低孤儿任务滞留时间