关于 frappe-syntax-scheduler
介绍 Frappe 使用 Redis Queue(RQ)配置定时事件和后台任务的语法参考,帮助开发者选择调度方式、队列及任务处理模式,并说明版本差异。
主要功能
- 01配置标准及 cron 调度事件
- 02将独立函数和文档方法入队
- 03选择队列与超时设置
- 04在 v15+ 对任务去重
- 05处理重试、回调和进度更新
- 06监控工作进程与任务日志
适用场景
- 01运行定期维护或报告任务
- 02将耗时操作交给后台工作进程
- 03避免队列中出现重复任务
- 04检查调度器和工作进程故障
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ impertio-studio/ frappe_claude_skill_package --skill frappe-syntax-scheduler安装后在 Claude Code 中输入/frappe-syntax-scheduler确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/anti-patterns.mdenqueue-api.mderror-handling.mdevents.mdexamples.mdhooks.mdmethods.mdmonitoring.mdpatterns.mdqueues.mdscheduler-events.mdsyntax.md详细介绍
Frappe 调度器与后台任务
Frappe 调度器事件和通过 Redis Queue(RQ)处理后台任务的确定性语法参考。
决策树
Need periodic execution?
├─ Fixed interval (hourly/daily/weekly/monthly) → scheduler_events in hooks.py
├─ Custom cron schedule → scheduler_events.cron in hooks.py
├─ User-configurable interval → Scheduled Job Type DocType
└─ No, triggered by user/event
├─ Run method on a specific document → frappe.enqueue_doc()
├─ Run standalone function async → frappe.enqueue()
└─ Run from controller on self → self.queue_action()
快速参考:调度器事件(hooks.py)
# hooks.py — ALWAYS run bench migrate after changes
scheduler_events = {
# Standard events (default queue)
"all": ["myapp.tasks.every_tick"], # Every tick [v14: 240s, v15+: 60s]
"hourly": ["myapp.tasks.hourly_task"],
"daily": ["myapp.tasks.daily_task"],
"weekly": ["myapp.tasks.weekly_task"],
"monthly": ["myapp.tasks.monthly_task"],
# Long queue events (for heavy processing)
"hourly_long": ["myapp.tasks.hourly_heavy"],
"daily_long": ["myapp.tasks.daily_heavy"],
"weekly_long": ["myapp.tasks.weekly_heavy"],
"monthly_long": ["myapp.tasks.monthly_heavy"],
# Cron events (croniter-compatible syntax)
"cron": {
"*/15 * * * *": ["myapp.tasks.every_15_min"],
"0 9 * * 1-5": ["myapp.tasks.weekday_9am"],
"0 0 1 * *": ["myapp.tasks.first_of_month"],
}
}
重要:每次修改 scheduler_events 后,务必运行 bench migrate。否则更改不会生效。
调度器事件类型
| 事件 | 频率 | 队列 | 使用场景 |
|---|---|---|---|
all | 每个周期 [v14:4min,v15+:60s] | default | 频繁轮询 |
hourly | 每小时一次 | default | 同步、清理 |
daily | 每天一次 | default | 报告、摘要 |
weekly | 每周一次 | default | 归档 |
monthly | 每月一次 | default | 计费、账单 |
hourly_long | 每小时一次 | long | 大型同步 |
daily_long | 每天一次 | long | 大型导出 |
weekly_long | 每周一次 | long | 数据仓储 |
monthly_long | 每月一次 | long | 年度报告 |
cron | 自定义计划 | 可配置 | 任意自定义时间 |
Cron 语法
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-6, Sunday=0)
│ │ │ │ │
* * * * *
| 符号 | 含义 | 示例 |
|---|---|---|
* | 任意值 | * * * * * = 每分钟 |
, | 列表 | 1,15 * * * * = 第 1 和第 15 分钟 |
- | 范围 | 0 9-17 * * * = 9 到 17 点 |
/ | 间隔 | */10 * * * * = 每 10 分钟 |
常见模式:
- 每 5 分钟:
*/5 * * * * - 工作日 9:00:
0 9 * * 1-5 - 周一 8:00:
0 8 * * 1 - 工作时间内每小时:
0 9-17 * * 1-5
快速参考:frappe.enqueue()
frappe.enqueue(
method, # REQUIRED: function or "dotted.module.path"
queue="default", # "short", "default", "long", or custom
timeout=None, # Override queue timeout (seconds)
is_async=True, # False = run synchronously (skip worker)
now=False, # True = run via frappe.call() directly
job_id=None, # [v15+] Unique ID for deduplication
enqueue_after_commit=False, # Wait for DB commit before enqueue
at_front=False, # Place at front of queue
on_success=None, # Success callback
on_failure=None, # Failure callback
**kwargs # Arguments passed to method
)
队列类型
| 队列 | 默认超时 | 使用时机 |
|---|---|---|
short | 300s(5 min) | 任务 < 30 秒 |
default | 300s(5 min) | 任务 30 秒 - 5 分钟 |
long | 1500s(25 min) | 任务 5 - 25 分钟 |
long + 自定义超时 | 用户定义 | 任务 > 25 分钟 |
# Short queue — quick status update
frappe.enqueue("myapp.tasks.update_status", queue="short", doc=doc.name)
# Long queue — heavy report generation
frappe.enqueue("myapp.tasks.generate_report", queue="long", timeout=3600)
frappe.enqueue_doc()
将指定文档上的控制器方法加入队列。
frappe.enqueue_doc(
"Sales Invoice", # DocType
"SINV-00001", # Document name
"send_notification", # Controller method name
queue="long",
timeout=600,
recipient="[email protected]" # kwargs passed to method
)
控制器方法必须使用 @frappe.whitelist() 装饰:
class SalesInvoice(Document):
@frappe.whitelist()
def send_notification(self, recipient):
# self is the loaded document
pass
self.queue_action()
在控制器内部使用的替代方式:
class SalesOrder(Document):
def on_submit(self):
self.queue_action("send_emails", emails=email_list)
def send_emails(self, emails):
for email in emails:
send_mail(email)
任务去重
[v15+] 推荐模式
from frappe.utils.background_jobs import is_job_enqueued
job_id = f"import::{doc.name}"
if not is_job_enqueued(job_id):
frappe.enqueue(
"myapp.tasks.import_data",
job_id=job_id,
doc_name=doc.name
)
else:
frappe.msgprint("Import already in progress")
[v14] 旧版模式(新代码中切勿使用)
from frappe.core.page.background_jobs.background_jobs import get_info
enqueued = [d.get("job_name") for d in get_info()]
if name not in enqueued:
frappe.enqueue(..., job_name=name)
错误处理模式
批处理任务中,务必对每条记录使用 try/except,并执行提交/回滚:
def process_records(records):
success, errors = 0, 0
for record in records:
try:
process_single(record)
frappe.db.commit()
success += 1
except Exception:
frappe.db.rollback()
frappe.log_error(
frappe.get_traceback(),
f"Process Error: {record}"
)
errors += 1
return {"success": success, "errors": errors}
重试模式
def task_with_retry(data, retry_count=0, max_retries=3):
try:
external_api_call(data)
except Exception:
if retry_count < max_retries:
frappe.enqueue(
"myapp.tasks.task_with_retry",
queue="default",
data=data,
retry_count=retry_count + 1,
max_retries=max_retries,
enqueue_after_commit=True
)
frappe.log_error(f"Retry {retry_count+1}/{max_retries}", "Task Retry")
else:
frappe.log_error(frappe.get_traceback(), f"Failed after {max_retries} retries")
raise
回调
def on_success_handler(job, connection, result, *args, **kwargs):
frappe.publish_realtime("show_alert", {"message": "Done!"})
def on_failure_handler(job, connection, type, value, traceback):
frappe.log_error(f"Job {job.id} failed: {value}", "Job Error")
frappe.enqueue(
"myapp.tasks.risky_task",
on_success=on_success_handler,
on_failure=on_failure_handler,
)
进度更新
def long_task(items, user):
total = len(items)
for i, item in enumerate(items):
process_item(item)
frappe.publish_realtime(
"task_progress",
{"progress": (i + 1) / total * 100, "current": i + 1, "total": total},
user=user,
)
用户上下文
重要:调度器任务以 Administrator 身份运行。创建文档时,务必明确设置所有权:
def scheduled_task():
doc = frappe.new_doc("ToDo")
doc.owner = "[email protected]"
doc.insert(ignore_permissions=True)
监控
| 工具 | 用途 |
|---|---|
bench doctor | 调度器状态、Worker 健康状况 |
| RQ Worker(DocType) | Worker 状态:忙碌/空闲 |
| RQ Job(DocType) | 任务状态、队列筛选 |
| Scheduled Job Log(DocType) | 执行历史、错误 |
logs/worker.error.log | Worker 异常 |
logs/scheduler.log | 调度器活动 |
版本差异
| 功能 | v14 | v15+ |
|---|---|---|
周期间隔(all 事件) | ~240s(4 min) | ~60s |
| 周期配置键 | scheduler_interval | scheduler_tick_interval |
| 去重 | job_name(已弃用) | job_id + is_job_enqueued() |
在 common_site_config.json 中自定义周期:
{ "scheduler_tick_interval": 120 }
重要规则
- 务必在 hooks.py 中修改
scheduler_events后运行bench migrate - 务必使用
job_id+is_job_enqueued()进行去重 [v15+] - 务必根据任务时长选择正确的队列:short/default/long
- 务必在批处理任务中逐条提交,并在出错时回滚
- 务必牢记调度器任务以 Administrator 身份运行
- 切勿直接在调度器事件中运行繁重逻辑,应将其加入队列
- 新代码中切勿使用
job_name进行去重(v14 旧版方式)
参考文件
- scheduler-events.md:所有事件类型、cron 语法和配置
- enqueue-api.md:完整的 frappe.enqueue / enqueue_doc API
- queues.md:队列类型、超时、自定义队列和工作进程
- monitoring.md:RQ DocType、bench doctor、日志文件和警报
- **error-handling.md****:错误模式、重试和批处理
- examples.md:完整的可运行示例
- anti-patterns.md:常见错误及更正
另请参阅
frappe-syntax-hooks— 完整的 hooks.py 参考frappe-core-background— 后台任务架构frappe-errors-jobs— 任务失败调试
评论
支持 Markdown