mcpField
F
frappe-syntax-scheduler

frappe-syntax-scheduler

github.com/impertio-studio开发198109 次安装30 周前更新
代码仓库

关于 frappe-syntax-scheduler

介绍 Frappe 使用 Redis Queue(RQ)配置定时事件和后台任务的语法参考,帮助开发者选择调度方式、队列及任务处理模式,并说明版本差异。

主要功能

  1. 01配置标准及 cron 调度事件
  2. 02将独立函数和文档方法入队
  3. 03选择队列与超时设置
  4. 04在 v15+ 对任务去重
  5. 05处理重试、回调和进度更新
  6. 06监控工作进程与任务日志

适用场景

  1. 01运行定期维护或报告任务
  2. 02将耗时操作交给后台工作进程
  3. 03避免队列中出现重复任务
  4. 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.md
enqueue-api.md
error-handling.md
events.md
examples.md
hooks.md
methods.md
monitoring.md
patterns.md
queues.md
scheduler-events.md
syntax.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
)

队列类型

队列默认超时使用时机
short300s(5 min)任务 < 30 秒
default300s(5 min)任务 30 秒 - 5 分钟
long1500s(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.logWorker 异常
logs/scheduler.log调度器活动

版本差异

功能v14v15+
周期间隔(all 事件)~240s(4 min)~60s
周期配置键scheduler_intervalscheduler_tick_interval
去重job_name(已弃用)job_id + is_job_enqueued()

在 common_site_config.json 中自定义周期:

{ "scheduler_tick_interval": 120 }

重要规则

  1. 务必在 hooks.py 中修改 scheduler_events 后运行 bench migrate
  2. 务必使用 job_id + is_job_enqueued() 进行去重 [v15+]
  3. 务必根据任务时长选择正确的队列:short/default/long
  4. 务必在批处理任务中逐条提交,并在出错时回滚
  5. 务必牢记调度器任务以 Administrator 身份运行
  6. 切勿直接在调度器事件中运行繁重逻辑,应将其加入队列
  7. 新代码中切勿使用 job_name 进行去重(v14 旧版方式)

参考文件

另请参阅

  • frappe-syntax-hooks — 完整的 hooks.py 参考
  • frappe-core-background — 后台任务架构
  • frappe-errors-jobs — 任务失败调试

评论

支持 Markdown