mcpField
F
frappe-impl-whitelisted

frappe-impl-whitelisted

github.com/impertio-studio開発198118 回インストール30 週間前に更新
リポジトリ

frappe-impl-whitelisted について

Frappe 開発者向けに、ホワイトリスト登録 API の実装手順をまとめたガイドです。エンドポイント設計、権限、セキュリティ、エラー処理に加え、v14、v15、v16 の違いも扱います。

主な機能

  1. 01エンドポイント種別と権限モデルの選択
  2. 02認証 API とゲスト API の実装手順
  3. 03コントローラーメソッドとファイルアップロード
  4. 04バックグラウンドジョブとエラー処理のパターン
  5. 05クライアント呼び出しと外部連携の例
  6. 06v14~v16 の機能差

活用シーン

  1. 01公開 API またはログインユーザー向け API の設計
  2. 02DocType フォーム用コントローラーメソッドの実装
  3. 03ゲスト API の入力検証とレート制限の確認
  4. 04Server Script API からホワイトリスト登録メソッドへの移行

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/frappe-impl-whitelistedを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
anti-patterns.md
decision-tree.md
examples.md
workflows.md

詳細

Frappe のホワイトリスト登録メソッドの実装ワークフロー

API エンドポイントを構築するためのステップバイステップのワークフローです。デコレーターの構文については、frappe-syntax-whitelistedを参照してください。

バージョン:v14/v15/v16(バージョン固有の機能には注記があります)


基本の判断:どの種類のエンドポイントか?

WHAT ARE YOU BUILDING?
│
├─► Public API (no login required)?
│   └─► allow_guest=True + STRICT input validation + rate limiting
│
├─► Authenticated API for logged-in users?
│   └─► Default @frappe.whitelist() + document permission checks
│
├─► Admin-only API?
│   └─► frappe.only_for("System Manager")
│
├─► Document-specific method (called from form)?
│   └─► Controller method + frm.call() from JS
│
├─► Standalone utility API?
│   └─► Separate api.py + frappe.call() from JS
│
├─► External webhook receiver?
│   └─► allow_guest=True + signature verification
│
└─► Background job trigger?
    └─► Authenticated API that calls frappe.enqueue()

ワークフロー 1:エンドポイントを設計する

ステップ 1:配置場所を選ぶ
WHERE SHOULD THE CODE LIVE?
│
├─► Related to a DocType, called from its form?
│   └─► doctype/xxx/xxx.py (controller method)
│       Client: frm.call('method_name', args)
│
├─► Related to a DocType, standalone?
│   └─► doctype/xxx/xxx_api.py or myapp/api/module.py
│       Client: frappe.call('myapp.api.module.method')
│
├─► General app utility?
│   └─► myapp/api.py (small app) or myapp/api/module.py (large app)
│
└─► External integration?
    └─► myapp/integrations/service_name.py
ステップ 2:権限モデルを選ぶ
WHO CAN CALL THIS API?
│
├─► Anyone (public) → allow_guest=True
│   ⚠️ MUST validate ALL input, sanitize for XSS, rate limit
│
├─► Any logged-in user → Default (no allow_guest)
│   Still check document permissions per record!
│
├─► Specific role(s) → frappe.only_for("Role")
│
└─► Document-level → frappe.has_permission(doctype, ptype, doc)
ステップ 3:HTTP メソッドを選ぶ
WHAT DOES THE API DO?
│
├─► Read-only → methods=["GET"]
├─► Creates/modifies data → methods=["POST"]
└─► Both or default → omit methods parameter (all allowed)

ワークフロー 2:認証済み API を実装する

手順

ステップ 1:関数を作成する

# myapp/api.py
import frappe
from frappe import _

@frappe.whitelist()
def get_customer_balance(customer):
    """Get outstanding balance for a customer."""
    # 1. Permission check
    if not frappe.has_permission("Customer", "read", customer):
        frappe.throw(_("Not permitted"), frappe.PermissionError)

    # 2. Validate input
    if not customer or not frappe.db.exists("Customer", customer):
        frappe.throw(_("Customer not found"), frappe.DoesNotExistError)

    # 3. Fetch and return
    balance = frappe.db.sql("""
        SELECT COALESCE(SUM(outstanding_amount), 0)
        FROM `tabSales Invoice`
        WHERE customer = %s AND docstatus = 1
    """, customer)[0][0]

    return {"customer": customer, "balance": balance}

ステップ 2:クライアントスクリプトから呼び出す

frappe.call({
    method: 'myapp.api.get_customer_balance',
    args: { customer: 'CUST-00001' },
    callback(r) {
        if (r.message) console.log(r.message.balance);
    }
});

ステップ 3:curl でテストする

# Authenticate first
curl -X POST https://site.com/api/method/login \
  -d 'usr=admin&pwd=password'

# Call the API
curl -X POST https://site.com/api/method/myapp.api.get_customer_balance \
  -H "Content-Type: application/json" \
  -d '{"customer": "CUST-00001"}' \
  --cookie cookies.txt

# Or use token auth
curl -X POST https://site.com/api/method/myapp.api.get_customer_balance \
  -H "Authorization: token api_key:api_secret" \
  -H "Content-Type: application/json" \
  -d '{"customer": "CUST-00001"}'

ワークフロー 3:パブリック(ゲスト)API を実装する

手順

ステップ 1:厳格な検証を行う関数を作成する

@frappe.whitelist(allow_guest=True, methods=["POST"])
def submit_inquiry(name, email, phone=None, message=None):
    """Public contact form — strict validation required."""
    # 1. Validate required fields
    if not all([name, email]):
        frappe.throw(_("Name and email are required"))

    # 2. Validate email format
    if not frappe.utils.validate_email_address(email):
        frappe.throw(_("Invalid email address"))

    # 3. Sanitize ALL input
    name = frappe.utils.strip_html(name)[:100]
    email = email.strip().lower()[:200]
    phone = frappe.utils.strip_html(phone)[:20] if phone else None
    message = frappe.utils.strip_html(message)[:2000] if message else None

    # 4. Create record with ignore_permissions
    lead = frappe.get_doc({
        "doctype": "Lead",
        "lead_name": name, "email_id": email,
        "phone": phone, "notes": message, "source": "Website"
    })
    lead.insert(ignore_permissions=True)

    return {"success": True, "message": _("Thank you")}

ステップ 2:レート制限を追加する(v15 以降)

from frappe.rate_limiter import rate_limit

@frappe.whitelist(allow_guest=True, methods=["POST"])
@rate_limit(limit=5, seconds=60)  # 5 calls per minute
def submit_inquiry(name, email, phone=None, message=None):
    ...
ゲスト API の重要なルール
  • 必ずすべての入力パラメーターを検証し、サニタイズしてください
  • 必ずデータを書き込むエンドポイントにはmethods=["POST"]を使用してください
  • 必ずレート制限を追加してください(v15 以降ではデコレーター、v14 ではキャッシュベースの手動スロットリング)
  • 決して内部エラーの詳細を公開しないでください。frappe.log_error()でログに記録し、汎用的なメッセージを表示してください
  • 決して機密データ(内部 ID、ファイルパス、スタックトレース)を返さないでください
  • 決して未加工のユーザー入力をfrappe.get_doc()に渡さないでください。明示的なフィールドマッピングを使用してください

ワークフロー 4:コントローラーメソッドを実装する

手順

ステップ 1:DocType コントローラーにメソッドを追加する

# myapp/doctype/sales_order/sales_order.py
class SalesOrder(Document):
    @frappe.whitelist()
    def calculate_shipping(self, carrier):
        """Called from form via frm.call()."""
        if not self.shipping_address:
            frappe.throw(_("Shipping address required"))
        rate = get_shipping_rate(self.shipping_address, carrier)
        return {"carrier": carrier, "rate": rate}

ステップ 2:フォーム JS から呼び出す

frm.call('calculate_shipping', {
    carrier: 'FedEx'
}).then(r => {
    frm.set_value('shipping_amount', r.message.rate);
});

主な違い:frm.call()経由で呼び出されるコントローラーメソッドでは、Frappe がドキュメントの権限を自動的に確認します。ドキュメント自体に対する手動の権限確認は不要です。


ワークフロー 5:エラー処理を実装する

標準パターン
@frappe.whitelist()
def process_payment(invoice, amount):
    try:
        # Validate
        if not invoice:
            frappe.throw(_("Invoice required"), frappe.ValidationError)
        if not frappe.has_permission("Sales Invoice", "write", invoice):
            frappe.throw(_("Not permitted"), frappe.PermissionError)

        # Process
        result = do_payment(invoice, float(amount))
        return {"success": True, "data": result}

    except (frappe.ValidationError, frappe.PermissionError):
        raise  # Let Frappe handle (417 / 403)
    except frappe.DoesNotExistError:
        frappe.throw(_("Not found"), frappe.DoesNotExistError)  # 404
    except Exception:
        frappe.log_error(frappe.get_traceback(), "Payment Error")
        frappe.local.response["http_status_code"] = 500
        return {"success": False, "error": _("Internal error")}
HTTP ステータスコードのリファレンス
コードFrappe の例外発生する状況
200—成功
403PermissionErrorアクセス拒否
404DoesNotExistError見つからない
409DuplicateEntryError重複
417ValidationError検証失敗
429—レート制限超過(v15 以降)
500Exceptionサーバーエラー

ワークフロー 6:ファイルアップロードのエンドポイント

@frappe.whitelist()
def upload_attachment(doctype, docname):
    """Handle file upload attached to a document."""
    if not frappe.has_permission(doctype, "write", docname):
        frappe.throw(_("Not permitted"), frappe.PermissionError)

    file = frappe.request.files.get('file')
    if not file:
        frappe.throw(_("No file provided"))

    file_doc = frappe.get_doc({
        "doctype": "File",
        "file_name": file.filename,
        "attached_to_doctype": doctype,
        "attached_to_name": docname,
        "content": file.read(),
        "is_private": 1
    })
    file_doc.insert(ignore_permissions=True)
    return {"file_url": file_doc.file_url}

ワークフロー 7:バックグラウンドジョブのエンドポイント

@frappe.whitelist()
def start_heavy_export(doctype, filters=None):
    """Trigger a background job — returns immediately."""
    frappe.only_for("System Manager")

    frappe.enqueue(
        "myapp.tasks.export_data",
        queue="long",
        timeout=1500,
        doctype=doctype,
        filters=filters,
        user=frappe.session.user
    )
    return {"status": "queued", "message": _("Export started")}

クライアント統合パターン

オプションを指定した frappe.call()
frappe.call({
    method: 'myapp.api.my_method',
    args: { param: 'value' },
    freeze: true,
    freeze_message: __('Processing...'),
    callback(r) { console.log(r.message); },
    error(r) { frappe.msgprint(__('Error')); }
});
Async/Await パターン
const r = await frappe.call({
    method: 'myapp.api.get_data',
    args: { id: 123 }
});
console.log(r.message);
外部システムからの REST API
# Token auth (recommended for integrations)
curl -H "Authorization: token api_key:api_secret" \
  https://site.com/api/method/myapp.api.method

# Bearer auth (OAuth)
curl -H "Authorization: Bearer access_token" \
  https://site.com/api/method/myapp.api.method

セキュリティルール(必ず行うこと/決して行わないこと)

  1. 必ずドキュメントにアクセスする前に権限を確認してください
  2. 必ずパラメーター化クエリを使用してください。SQL で f-string を決して使用しないでください
  3. 必ずゲスト API の入力を検証し、サニタイズしてください
  4. ignore_permissions=Trueを使用する前に、必ずfrappe.only_for()でロールを確認してください
  5. 内部エラーを決して公開しないでください。詳細をログに記録し、汎用的なメッセージを返してください
  6. データを変更するエンドポイントでmethods=["GET"]を決して使用しないでください
  7. ゲスト API からのdataディクショナリを決して信頼しないでください。明示的なパラメーター名を使用してください
  8. ブラウザーからの fetch() 呼び出しには、必ずX-Frappe-CSRF-Tokenヘッダーを含めてください

移行:Server Script API からホワイトリスト登録メソッドへ

手順操作
1Server Script のロジックをmyapp/api/module.pyにコピーする
2同じ権限モデルで@frappe.whitelist()デコレーターを追加する
3すべてのfrappe.call()参照を新しいドット区切りパスに更新する
4Server Script を無効化または削除する
5bench --site sitename migrateを実行する

バージョンの違い

機能v14v15v16
@frappe.whitelist()はいはいはい
allow_guestはいはいはい
methods パラメーターはいはいはい
型アノテーションの検証いいえはいはい
@rate_limit デコレーターいいえはいはい
API v2 エンドポイントいいえはいはい
v15 以降の型検証
@frappe.whitelist()
def typed_api(customer: str, limit: int = 10) -> dict:
    """v15+ validates types from annotations automatically."""
    return {"customer": customer, "limit": limit}

参考ファイル

ファイル内容
decision-tree.mdAPI の種類と権限の選択に関する完全なガイド
workflows.md実装パターンのステップバイステップガイド(10 以上のワークフロー)
examples.md本番環境ですぐに使えるコード例

コメント