mcpField
F
frappe-errors-serverscripts

frappe-errors-serverscripts

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

关于 frappe-errors-serverscripts

面向 Frappe 开发者的 Server Script 错误诊断指南,涵盖沙箱限制、脚本类型选择及常见运行问题的处理方法。

主要功能

  1. 01按错误信息定位原因与修复方式
  2. 02说明受限 Python 沙箱规则
  3. 03区分各脚本类型的上下文
  4. 04提供调度脚本与 API 防错要点
  5. 05涵盖 SQL 注入防范

适用场景

  1. 01排查 Server Script 导入或名称错误
  2. 02修正调度脚本未提交或超时问题
  3. 03诊断 API 响应为空的原因
  4. 04检查 SQL 查询中的用户输入风险

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/impertio-studio/frappe_claude_skill_package --skill frappe-errors-serverscripts

安装后在 Claude Code 中输入/frappe-errors-serverscripts确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
anti-patterns.md
examples.md
patterns.md

详细介绍

服务端脚本错误 — 诊断与解决

交叉引用:frappe-syntax-serverscripts(语法)、frappe-impl-serverscripts(工作流)、frappe-errors-clientscripts(客户端)。


严重:默认禁用服务端脚本 [v15+]

从 Frappe v15 开始,服务端脚本默认禁用。你必须启用它们:

# In site_config.json
{ "server_script_enabled": 1 }

在 Frappe Cloud 上:服务端脚本仅适用于私有 bench,不适用于共享 bench。


错误诊断流程图

ERROR IN SERVER SCRIPT
│
├─► ImportError / NameError
│   ├─► "import json" → BLOCKED. Use frappe.parse_json()
│   ├─► "import datetime" → BLOCKED. Use frappe.utils
│   ├─► "import os/sys/subprocess" → BLOCKED. Security restriction
│   └─► "NameError: name 'dict' is not defined" → Some builtins restricted
│
├─► SyntaxError: not allowed
│   ├─► "try/except" → BLOCKED by RestrictedPython [v14-v15]
│   ├─► "raise ValueError" → BLOCKED. Use frappe.throw()
│   └─► "exec/eval" → BLOCKED. Security restriction
│
├─► Script runs but nothing happens
│   ├─► Wrong Script Type selected → Check Document Event vs API vs Scheduler
│   ├─► Wrong DocType selected → Verify exact DocType name
│   ├─► Wrong Event selected → Before Save ≠ After Save
│   └─► Script disabled → Check "Enabled" checkbox
│
├─► 403 Permission Denied
│   ├─► Scheduler script → Runs as Administrator, check role permissions
│   ├─► API script → Check Allow Guest setting
│   └─► doc_event → User lacks DocType permission
│
├─► Data not saved in Scheduler
│   └─► Missing frappe.db.commit() → REQUIRED in scheduler scripts
│
└─► API script returns empty/wrong response
    └─► Not setting frappe.response["message"] → ALWAYS set response

错误消息 → 原因 → 修复方法表

错误消息原因修复方法
ImportError: import not allowed沙盒中的任何 import 语句使用 frappe.utils、frappe.parse_json() 等
NameError: name 'dict' is not definedRestrictedPython 禁用了部分 Python 内置函数使用 frappe._dict() 或字面量 {}
SyntaxError: try/except not allowedRestrictedPython 禁用了异常处理 [v14-v15]改用条件检查(if/else)
SyntaxError: raise not allowedRestrictedPython 禁用了 raise使用 frappe.throw()
Script not executing选择了错误的脚本类型或事件确认类型匹配:Document Event、API 或 Scheduler
doc is not defined在 API 或 Scheduler 脚本中使用了 doc(没有文档上下文)doc 仅在 Document Event 脚本中可用
Scheduler 中出现 PermissionErrorScheduler 以 Administrator 身份运行,但脚本访问了受限资源适当使用 ignore_permissions=True
Scheduler 中的更改未保存缺少 frappe.db.commit()Scheduler 脚本中必须调用 frappe.db.commit()
API returns empty response忘记设置 frappe.response["message"]必须设置 frappe.response["message"] = result
Timeout / killed无限循环或处理的记录过多查询必须始终添加 limit,并始终使用批处理
ValidationError: qty is required在 Before Save 中调用了 doc.save()(递归)切勿在 Before Save 中调用 doc.save();只需设置值
SQL injection via string formatSQL 中的用户输入未转义必须使用 frappe.db.escape() 或参数化查询

第一大错误:ImportError

每个初学者都会遇到这个问题。 服务端脚本沙盒会阻止所有导入,json 除外。

# ❌ BLOCKED — These ALL fail with ImportError
import json                    # Use frappe.parse_json() / frappe.as_json()
from datetime import datetime  # Use frappe.utils.now(), frappe.utils.today()
import re                      # Not available in sandbox
import os                      # Security: blocked
import requests                # Use frappe.make_get_request(), frappe.make_post_request()

# ✅ CORRECT — Sandbox equivalents
data = frappe.parse_json(doc.json_field)         # Instead of json.loads()
today = frappe.utils.today()                      # Instead of datetime.date.today()
now = frappe.utils.now()                          # Instead of datetime.now()
diff = frappe.utils.date_diff(date1, date2)       # Instead of timedelta
resp = frappe.make_get_request("https://api.com") # Instead of requests.get()
resp = frappe.make_post_request("https://api.com", data=payload)
可用沙盒 API(完整参考)
类别可用方法
文档frappe.get_doc()、frappe.new_doc()、frappe.get_last_doc()、frappe.get_cached_doc()、frappe.get_mapped_doc()、frappe.rename_doc()、frappe.delete_doc()
数据库frappe.db.get_list()、frappe.db.get_all()、frappe.db.get_value()、frappe.db.get_single_value()、frappe.db.set_value()、frappe.db.exists()、frappe.db.sql()、frappe.db.commit()、frappe.db.rollback()、frappe.db.escape()
查询构建器frappe.qb(完整查询构建器)
HTTPfrappe.make_get_request()、frappe.make_post_request()、frappe.make_put_request()
实用工具frappe.utils.*(所有实用工具函数)、frappe.parse_json()、frappe.as_json()
用户/会话frappe.session.user、frappe.get_roles()、frappe.has_permission()
消息frappe.throw()、frappe.msgprint()、frappe.log_error()、frappe.sendmail()
模块json(唯一可导入的模块)

脚本类型选择错误

务必确认选择了正确的脚本类型:

脚本类型触发条件有 doc?有 frappe.form_dict?自动提交?
Document EventDocType 生命周期(Before Save、After Save 等)是否是
API对 /api/method/{method_name} 的 HTTP 请求否是是
Scheduler EventCron 计划否否否 — 必须调用 frappe.db.commit()
Permission Query对 DocType 的每次列表查询否否(有 user)不适用
常见错误:事件类型不正确
# ❌ WRONG — "After Save" cannot prevent save
# Script Type: Document Event, Event: After Save
if not doc.customer:
    frappe.throw("Customer is required")  # Document already saved!

# ✅ CORRECT — Use "Before Save" or "Before Validate"
# Script Type: Document Event, Event: Before Save
if not doc.customer:
    frappe.throw("Customer is required")  # Prevents save

沙箱应对方法

try/except 被禁用:改用条件检查
# ❌ BLOCKED in sandbox
try:
    customer = frappe.get_doc("Customer", doc.customer)
except Exception:
    frappe.throw("Customer not found")

# ✅ CORRECT — Check first, then access
if not frappe.db.exists("Customer", doc.customer):
    frappe.throw(f"Customer '{doc.customer}' not found")
customer = frappe.get_doc("Customer", doc.customer)
raise 被禁用:改用 frappe.throw()
# ❌ BLOCKED
if amount < 0:
    raise ValueError("Amount cannot be negative")

# ✅ CORRECT
if amount < 0:
    frappe.throw("Amount cannot be negative")
API 脚本中 frappe.throw() 的异常类型
异常HTTP 代码适用场景
frappe.ValidationError417输入验证失败
frappe.PermissionError403拒绝访问
frappe.DoesNotExistError404未找到记录
frappe.AuthenticationError401尚未登录
(默认,无 exc)417一般验证错误
# API Script — Correct exception types
if not customer:
    frappe.throw("Customer param required", exc=frappe.ValidationError)  # 417
if not frappe.db.exists("Customer", customer):
    frappe.throw("Customer not found", exc=frappe.DoesNotExistError)    # 404
if not frappe.has_permission("Customer", "read", customer):
    frappe.throw("Access denied", exc=frappe.PermissionError)           # 403

Scheduler 脚本:关键错误

# ❌ WRONG — No limit, no commit, no error logging
invoices = frappe.get_all("Sales Invoice", filters={"status": "Unpaid"})
for inv in invoices:
    frappe.db.set_value("Sales Invoice", inv.name, "reminder_sent", 1)

# ✅ CORRECT — Limit, batch commit, error logging
BATCH_SIZE = 50
invoices = frappe.get_all(
    "Sales Invoice",
    filters={"status": "Unpaid", "docstatus": 1},
    fields=["name", "customer"],
    limit=500  # ALWAYS limit
)

errors = []
for i in range(0, len(invoices), BATCH_SIZE):
    batch = invoices[i:i + BATCH_SIZE]
    for inv in batch:
        if not frappe.db.exists("Customer", inv.customer):
            errors.append(f"{inv.name}: Customer not found")
            continue
        frappe.db.set_value("Sales Invoice", inv.name, "reminder_sent", 1)
    frappe.db.commit()  # REQUIRED

if errors:
    frappe.log_error("\n".join(errors), "Reminder Errors")
frappe.db.commit()

防止 SQL 注入

# ❌ VULNERABLE — String interpolation with user input
territory = frappe.form_dict.get("territory")
conditions = f"`tabCustomer`.territory = '{territory}'"  # SQL INJECTION!

# ✅ SAFE — Use frappe.db.escape()
territory = frappe.form_dict.get("territory")
conditions = f"`tabCustomer`.territory = {frappe.db.escape(territory)}"

# ✅ SAFEST — Use parameterized query or Query Builder
results = frappe.db.get_all("Customer", filters={"territory": territory})

必须遵守 / 绝对禁止的规则

必须遵守
  1. 使用 frappe.utils.*,而不是导入 Python 模块 — 只有 json 模块可以导入
  2. 使用 frappe.throw(),而不是 raise — raise 在沙箱中被禁用
  3. 使用条件检查,而不是 try/except — 异常处理被禁用 [v14-v15]
  4. 在 Scheduler 脚本中调用 frappe.db.commit() — 更改不会自动提交
  5. 在 Scheduler 脚本的所有查询中添加 limit — 防止内存耗尽
  6. 在 API 脚本中设置 frappe.response["message"] — 否则响应为空
  7. 在 SQL 中使用 frappe.db.escape() 处理用户输入 — 防止 SQL 注入
  8. 在 Scheduler 脚本中使用 frappe.log_error() 记录错误 — 没有用户能看到错误
  9. 确认 Script Type 符合你的意图 — Document Event、API 或 Scheduler
绝对禁止
  1. 绝对不要使用 import 语句(json 除外)— RestrictedPython 会阻止
  2. 绝对不要使用 try/except 或 raise — 沙箱会阻止 [v14-v15]
  3. 绝对不要在 Before Save 中调用 doc.save() — 会导致无限递归
  4. 绝对不要使用字符串格式化将用户输入拼入 SQL — 有 SQL 注入风险
  5. 绝对不要在 Scheduler 中处理不限量的记录 — 始终使用 limit
  6. 绝对不要假设 API/Scheduler 脚本中存在 doc — 它仅在 Document Events 中可用
  7. 绝对不要忘记在 Scheduler 中调用 frappe.db.commit() — 所有更改都会丢失

参考文件

文件内容
references/examples.md带诊断说明的真实错误场景
references/anti-patterns.md常见沙箱错误及修复方法
references/patterns.md按脚本类型划分的防御性错误处理模式

评论

支持 Markdown