erpnext-errors-api

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

ERPNext API Error Handling

ERPNext API错误处理

Patterns for handling errors in API development. For syntax details, see
erpnext-api-patterns
.
Version: v14/v15/v16 compatible
API开发中的错误处理模式。语法细节请参考
erpnext-api-patterns
版本:兼容v14/v15/v16

API Error Handling Overview

API错误处理概览

┌─────────────────────────────────────────────────────────────────────┐
│ API ERROR HANDLING DECISION                                         │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│ Where is the error occurring?                                      │
│                                                                     │
│ Server-side (Python)?                                              │
│ ├── Validation error → frappe.throw() with clear message          │
│ ├── Permission error → frappe.throw() + PermissionError           │
│ ├── Not found        → frappe.throw() + DoesNotExistError         │
│ └── Unexpected       → Log + generic error to client              │
│                                                                     │
│ Client-side (JavaScript)?                                          │
│ ├── frappe.call      → Use error callback or .catch()             │
│ └── frappe.xcall     → Use try/catch with async/await             │
│                                                                     │
│ External integration?                                              │
│ └── requests library → try/except with specific exceptions         │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ API 错误处理决策树                                                 │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│ 错误发生在哪个环节?                                                │
│                                                                     │
│ 服务端(Python)?                                                  │
│ ├── 验证错误 → 使用frappe.throw()返回清晰提示信息                  │
│ ├── 权限错误 → frappe.throw() + PermissionError异常                │
│ ├── 资源不存在 → frappe.throw() + DoesNotExistError异常            │
│ └── 意外错误 → 记录日志并向客户端返回通用错误信息                  │
│                                                                     │
│ 客户端(JavaScript)?                                              │
│ ├── frappe.call → 使用错误回调函数或.catch()方法                    │
│ └── frappe.xcall → 结合async/await使用try/catch捕获异常             │
│                                                                     │
│ 外部集成场景?                                                      │
│ └── requests库 → 使用try/except捕获特定异常                         │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

HTTP Status Codes Reference

HTTP状态码参考

CodeMeaningWhen Frappe Uses
200SuccessNormal response
400Bad RequestValidation error
403ForbiddenPermission denied
404Not FoundDocument doesn't exist
417Expectation Failedfrappe.throw() called
500Server ErrorUnhandled exception
状态码含义Frappe使用场景
200成功正常响应
400错误请求验证失败
403禁止访问权限不足
404未找到文档不存在
417预期失败调用frappe.throw()时
500服务器错误未处理的异常

Server-Side Patterns

服务端处理模式

Basic Whitelisted Method

基础白名单方法

python
@frappe.whitelist()
def update_status(docname, status):
    # Validate input
    if not docname:
        frappe.throw(_("Document name is required"), frappe.ValidationError)
    
    if status not in ["Draft", "Submitted", "Cancelled"]:
        frappe.throw(_("Invalid status: {0}").format(status))
    
    try:
        doc = frappe.get_doc("My DocType", docname)
        doc.status = status
        doc.save()
        return {"success": True, "name": doc.name}
    except frappe.DoesNotExistError:
        frappe.throw(_("Document {0} not found").format(docname))
    except frappe.PermissionError:
        frappe.throw(_("Permission denied"), frappe.PermissionError)
python
@frappe.whitelist()
def update_status(docname, status):
    # 验证输入
    if not docname:
        frappe.throw(_("文档名称为必填项"), frappe.ValidationError)
    
    if status not in ["Draft", "Submitted", "Cancelled"]:
        frappe.throw(_("无效状态:{0}").format(status))
    
    try:
        doc = frappe.get_doc("My DocType", docname)
        doc.status = status
        doc.save()
        return {"success": True, "name": doc.name}
    except frappe.DoesNotExistError:
        frappe.throw(_("文档{0}不存在").format(docname))
    except frappe.PermissionError:
        frappe.throw(_("权限不足"), frappe.PermissionError)

Bulk Operation with Partial Failure

批量操作的部分失败处理

python
@frappe.whitelist()
def bulk_update(items):
    items = frappe.parse_json(items)
    results = {"success": [], "failed": []}
    
    for item in items:
        try:
            doc = frappe.get_doc("Item", item["name"])
            doc.update(item)
            doc.save()
            results["success"].append(item["name"])
        except Exception as e:
            results["failed"].append({
                "name": item["name"],
                "error": str(e)
            })
    
    frappe.db.commit()
    return results
python
@frappe.whitelist()
def bulk_update(items):
    items = frappe.parse_json(items)
    results = {"success": [], "failed": []}
    
    for item in items:
        try:
            doc = frappe.get_doc("Item", item["name"])
            doc.update(item)
            doc.save()
            results["success"].append(item["name"])
        except Exception as e:
            results["failed"].append({
                "name": item["name"],
                "error": str(e)
            })
    
    frappe.db.commit()
    return results

Client-Side Patterns

客户端处理模式

frappe.call Error Handling

frappe.call错误处理

javascript
frappe.call({
    method: "myapp.api.update_status",
    args: { docname: "DOC-001", status: "Submitted" },
    callback: function(r) {
        if (r.message && r.message.success) {
            frappe.show_alert({message: __("Updated"), indicator: "green"});
        }
    },
    error: function(r) {
        // Called on HTTP error or frappe.throw
        frappe.msgprint({
            title: __("Error"),
            message: r.message || __("Operation failed"),
            indicator: "red"
        });
    }
});
javascript
frappe.call({
    method: "myapp.api.update_status",
    args: { docname: "DOC-001", status: "Submitted" },
    callback: function(r) {
        if (r.message && r.message.success) {
            frappe.show_alert({message: __("更新成功"), indicator: "green"});
        }
    },
    error: function(r) {
        // HTTP错误或frappe.throw触发时调用
        frappe.msgprint({
            title: __("错误"),
            message: r.message || __("操作失败"),
            indicator: "red"
        });
    }
});

async/await Pattern

async/await模式

javascript
async function updateDocument(docname, status) {
    try {
        const result = await frappe.xcall("myapp.api.update_status", {
            docname: docname,
            status: status
        });
        return result;
    } catch (error) {
        console.error("API Error:", error);
        frappe.throw(__("Failed to update document"));
    }
}
javascript
async function updateDocument(docname, status) {
    try {
        const result = await frappe.xcall("myapp.api.update_status", {
            docname: docname,
            status: status
        });
        return result;
    } catch (error) {
        console.error("API Error:", error);
        frappe.throw(__("文档更新失败"));
    }
}

External API Pattern

外部API调用模式

python
import requests

def call_external_api(endpoint, data):
    try:
        response = requests.post(
            endpoint,
            json=data,
            timeout=30,
            headers={"Authorization": f"Bearer {get_api_key()}"}
        )
        response.raise_for_status()
        return response.json()
    except requests.Timeout:
        frappe.log_error("External API timeout", "API Integration")
        frappe.throw(_("External service timeout. Please try again."))
    except requests.HTTPError as e:
        frappe.log_error(f"HTTP {e.response.status_code}", "API Integration")
        frappe.throw(_("External service error"))
    except requests.RequestException as e:
        frappe.log_error(str(e), "API Integration")
        frappe.throw(_("Connection failed"))
python
import requests

def call_external_api(endpoint, data):
    try:
        response = requests.post(
            endpoint,
            json=data,
            timeout=30,
            headers={"Authorization": f"Bearer {get_api_key()}"}
        )
        response.raise_for_status()
        return response.json()
    except requests.Timeout:
        frappe.log_error("外部API超时", "API集成")
        frappe.throw(_("外部服务超时,请稍后重试。"))
    except requests.HTTPError as e:
        frappe.log_error(f"HTTP {e.response.status_code}", "API集成")
        frappe.throw(_("外部服务错误"))
    except requests.RequestException as e:
        frappe.log_error(str(e), "API集成")
        frappe.throw(_("连接失败"))

Critical Rules

核心规则

✅ ALWAYS

✅ 必须遵守

  • Validate input before processing
  • Use
    frappe.throw()
    for user-facing errors
  • Log unexpected errors with
    frappe.log_error()
  • Return structured responses from APIs
  • Handle both success and error in callbacks
  • 处理前先验证输入
  • 面向用户的错误使用
    frappe.throw()
    返回
  • 使用
    frappe.log_error()
    记录意外错误
  • API返回结构化响应
  • 回调中同时处理成功和错误场景

❌ NEVER

❌ 禁止操作

  • Expose internal error details to users
  • Catch exceptions without logging
  • Return raw exception messages
  • Assume API calls will succeed
  • Skip input validation
  • 向用户暴露内部错误细节
  • 捕获异常但不记录日志
  • 返回原始异常信息
  • 假设API调用一定会成功
  • 跳过输入验证

Quick Reference: Error Responses

错误响应速查

python
undefined
python
undefined

User-facing error (shows alert)

面向用户的错误(会显示弹窗提示)

frappe.throw(_("Clear error message"))
frappe.throw(_("清晰的错误提示"))

Permission error (403)

权限错误(返回403状态码)

frappe.throw(_("Not allowed"), frappe.PermissionError)
frappe.throw(_("无权限操作"), frappe.PermissionError)

Validation error (400)

验证错误(返回400状态码)

frappe.throw(_("Invalid input"), frappe.ValidationError)
frappe.throw(_("无效输入"), frappe.ValidationError)

Log error (no user message)

记录错误(不向用户返回提示)

frappe.log_error(frappe.get_traceback(), "Error Title")

---
frappe.log_error(frappe.get_traceback(), "错误标题")

---

Reference Files

参考文件

FileContents
patterns.mdDetailed error handling patterns
examples.mdComplete working examples
anti-patterns.mdCommon mistakes to avoid

文件内容
patterns.md详细的错误处理模式
examples.md完整的可运行示例
anti-patterns.md需要避免的常见错误

See Also

相关参考

  • erpnext-api-patterns
    - API implementation patterns
  • erpnext-syntax-whitelisted
    - Whitelisted method syntax
  • erpnext-errors-serverscripts
    - Server Script error handling
  • erpnext-api-patterns
    - API实现模式
  • erpnext-syntax-whitelisted
    - 白名单方法语法
  • erpnext-errors-serverscripts
    - 服务器脚本错误处理