frappe-syntax-serverscripts について
RestrictedPython サンドボックスで Frappe Server Scripts を作成するための実用リファレンススキルです。スクリプトの種類、利用可能な API、ライフサイクルイベント、制約を解説します。
主な機能
- 01Document Event、API、Scheduler、Permission Query スクリプト
- 02RestrictedPython のサンドボックス規則と利用可能なメソッド
- 03ドキュメントのライフサイクルイベント対応表
- 04バージョンごとの有効化に関する説明
- 05セキュリティと安全なクエリの指針
活用シーン
- 01ドキュメントイベントへの検証処理の追加
- 02Frappe での API または定期実行スクリプトの作成
- 03ユーザー権限に応じたドキュメント一覧の絞り込み
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ impertio-studio/ frappe_claude_skill_package --skill frappe-syntax-serverscriptsインストール後、Claude Code で/frappe-syntax-serverscriptsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/anti-patterns.mdevents.mdexamples.mdhooks.mdmethods.mdpatterns.mdsyntax.md詳細
Frappe Server Scripts — 完全リファレンス
Server Scripts は、Frappe/ERPNext の UI で Setup > Server Script を通じて管理する Python スクリプトです。 RestrictedPython サンドボックス内で実行されます。
重要:サンドボックスのルール
┌──────────────────────────────────────────────────────────────────┐
│ ALL import STATEMENTS ARE BLOCKED │
│ │
│ import json → ImportError: __import__ not found │
│ from datetime import * → ImportError: __import__ not found │
│ import frappe → ImportError (even frappe itself!) │
│ │
│ EVERYTHING you need is pre-loaded in the frappe namespace. │
│ NEVER write an import line. ALWAYS use frappe.utils.*, etc. │
└──────────────────────────────────────────────────────────────────┘
インポートの代わりに、あらかじめ読み込まれている名前空間を必ず使用してください。
| ブロックされるインポート | 代わりに使用するもの |
|---|---|
import json | frappe.parse_json() / frappe.as_json() |
from datetime import date | frappe.utils.today() / frappe.utils.now_datetime() |
from frappe.utils import cint | frappe.utils.cint()(すでに読み込み済み) |
import requests | frappe.make_get_request() / frappe.make_post_request() |
import re | 使用不可 — 正規表現を使わずにロジックを組み立て直してください |
import os / import sys | 使用不可 — 代わりにカスタムアプリを使用してください |
Server Scripts の有効化
# v14: enabled by default
# v15+: DISABLED by default — you MUST enable explicitly:
bench set-config -g server_script_enabled 1
# Or set server_script_enabled: true in site_config.json
Frappe Cloud の共有ベンチでは Server Scripts が動作することを絶対に想定しないでください。プライベートベンチが必要です。
スクリプトの種類
| 種類 | トリガー | 主な変数 |
|---|---|---|
| Document Event | ドキュメントのライフサイクル(保存、送信、キャンセル) | doc |
| API | /api/method/{name} への HTTP リクエスト | frappe.form_dict |
| Scheduler Event | Cron スケジュール | (なし) |
| Permission Query | ドキュメント一覧のフィルタリング | user, conditions |
イベント名の対応関係(Document Events)
重要:UI 上の名前は内部のフック名とは異なります。
| Server Script UI | 内部フック | 実行されるタイミング |
|---|---|---|
| Before Insert | before_insert | 新しいドキュメントが DB に保存される前 |
| After Insert | after_insert | 最初の DB 挿入後 |
| Before Validate | before_validate | フレームワークによる検証の前 |
| Before Save | validate | 保存前(新規作成・更新) |
| After Save | on_update | 保存成功後 |
| Before Submit | before_submit | 送信前(docstatus 0→1) |
| After Submit | on_submit | 送信完了後 |
| Before Cancel | before_cancel | キャンセル前(docstatus 1→2) |
| After Cancel | on_cancel | キャンセル完了後 |
| Before Delete | on_trash | 完全削除の前 |
| After Delete | after_delete | 完全削除後 |
「Before Save」と before_save を絶対に混同しないでください。UI ラベルの「Before Save」は validate フックに対応します。実際の before_save フックは validate の後に実行されます。
判断フロー:Server Script と Document Controller の使い分け
Need custom Python logic for a DocType?
│
├─► Can you install a custom Frappe app?
│ ├─► YES: Use a Document Controller when you need:
│ │ • import statements (any Python library)
│ │ • File system access
│ │ • Complex class inheritance
│ │ • autoname / before_naming hooks
│ │ • Unit-testable code
│ │
│ └─► NO: Use a Server Script when:
│ • You only have UI access (no bench CLI)
│ • Logic is simple validation / field calculation
│ • You need a quick API endpoint
│ • You need dynamic permission filtering
│
└─► Is logic > 50 lines or needs external libraries?
├─► YES → Document Controller in a custom app
└─► NO → Server Script is fine
クイックリファレンス:サンドボックスで利用可能なもの
あらかじめ読み込まれているオブジェクト
doc # Current document (Document Event only)
frappe # Core namespace — ALWAYS available
frappe.db # Database operations
frappe.utils # Date, number, string utilities
frappe.session # Current session (user, csrf_token)
frappe.form_dict # Request parameters (API scripts)
frappe.response # Response object (API scripts)
frappe.request # Werkzeug request object
frappe.qb # Query Builder (v14+)
json # Python json module (pre-loaded)
主なメソッド
# Documents
frappe.get_doc(doctype, name) # Fetch document
frappe.new_doc(doctype) # Create new document
frappe.get_cached_doc(doctype, name) # Cached fetch (read-only)
frappe.get_last_doc(doctype) # Most recent document
frappe.get_mapped_doc(...) # Map fields between DocTypes
frappe.delete_doc(doctype, name) # Delete document
frappe.rename_doc(doctype, old, new) # Rename document
# Querying
frappe.get_all(doctype, filters, fields, order_by, limit) # No permission check
frappe.get_list(doctype, filters, fields, order_by, limit) # With permission check
frappe.db.get_value(doctype, name, fieldname)
frappe.db.get_single_value(doctype, fieldname)
frappe.db.set_value(doctype, name, fieldname, value)
frappe.db.exists(doctype, name_or_filters)
frappe.db.count(doctype, filters)
frappe.db.sql(query, values, as_dict) # ALWAYS parameterize!
frappe.db.escape(value) # SQL escape
frappe.db.commit() # ONLY in Scheduler scripts
frappe.db.rollback() # ONLY in Scheduler scripts
# Messaging
frappe.throw(msg, exc, title) # Stop execution + show error
frappe.msgprint(msg, title, indicator) # User notification
frappe.log_error(message, title) # Error Log entry
# HTTP (yes, these work in sandbox!)
frappe.make_get_request(url, params, headers)
frappe.make_post_request(url, data, headers)
frappe.make_put_request(url, data, headers)
# Email
frappe.sendmail(recipients, sender, subject, message)
# Utilities
frappe.utils.today() # "2024-01-15"
frappe.utils.now() # "2024-01-15 10:30:00"
frappe.utils.now_datetime() # datetime object
frappe.utils.add_days(date, n) # Date arithmetic
frappe.utils.add_months(date, n)
frappe.utils.date_diff(d1, d2) # Days between dates
frappe.utils.flt(val) # Safe float (None → 0.0)
frappe.utils.cint(val) # Safe int (None → 0)
frappe.utils.cstr(val) # Safe string (None → "")
frappe.parse_json(string) # JSON string → dict/list
frappe.as_json(obj) # dict/list → JSON string
frappe.render_template(template, ctx) # Jinja rendering
frappe.get_url() # Site URL
frappe.get_hooks(hook) # Read app hooks
run_script(script_name, **kwargs) # Call another Server Script
# Session / Permissions
frappe.session.user # Current user email
frappe.get_roles(user) # User's roles list
frappe.has_permission(doctype, ptype, doc)
frappe.get_fullname(user) # User's display name
_("translatable string") # Translation function
使用可能な Python 組み込み関数
str, int, float, bool, list, dict, tuple, set # Types
range, enumerate, zip, map, filter # Iteration
sum, min, max, len, sorted, reversed # Aggregation
isinstance, type, hasattr, getattr # Introspection
all, any, abs, round, divmod # Math/logic
print # → server log
True, False, None # Constants
ブロックされる Python 組み込み関数
open, file # No file I/O
eval, exec, compile # No dynamic code execution
__import__ # No imports (this is the root cause)
globals, locals # No scope introspection
スクリプトの種類ごとの構文
Document Event
# Config: Reference DocType = Sales Invoice, Event = Before Save
if doc.grand_total < 0:
frappe.throw("Total MUST NOT be negative")
doc.requires_approval = 1 if doc.grand_total > 10000 else 0
API
# Config: API Method = get_customer_orders, Allow Guest = No
# Endpoint: /api/method/get_customer_orders
customer = frappe.form_dict.get("customer")
if not customer:
frappe.throw("Parameter 'customer' is required")
orders = frappe.get_all("Sales Order",
filters={"customer": customer, "docstatus": 1},
fields=["name", "grand_total", "status"],
order_by="creation desc",
limit=20
)
frappe.response["message"] = {"orders": orders, "count": len(orders)}
Scheduler Event
# Config: Event Frequency = Cron, Cron Format = 0 9 * * *
overdue = frappe.get_all("Sales Invoice",
filters={"status": "Unpaid", "due_date": ["<", frappe.utils.today()], "docstatus": 1},
fields=["name", "customer", "grand_total"]
)
for inv in overdue:
frappe.log_error(f"Overdue: {inv.name} ({inv.customer})", "Invoice Reminder")
frappe.db.commit() # ALWAYS commit in Scheduler scripts
Permission Query
# Config: Reference DocType = Sales Invoice
# Variables available: user, conditions
roles = frappe.get_roles(user)
if "System Manager" in roles:
conditions = ""
elif "Sales User" in roles:
conditions = f"`tabSales Invoice`.owner = {frappe.db.escape(user)}"
else:
conditions = "1=0"
バージョン間の違い
| 機能 | v14 | v15 | v16 |
|---|---|---|---|
| Server Scripts の有効化 | デフォルトで有効 | デフォルトで無効 | デフォルトで無効 |
| 有効化コマンド | 不要 | bench set-config -g server_script_enabled 1 | v15 と同じ |
frappe.qb(Query Builder) | 利用可能 | 利用可能 | 利用可能 |
ライブラリ向けの run_script() | v13+ | 利用可能 | 利用可能 |
frappe.make_get_request() | 利用可能 | 利用可能 | 利用可能 |
| Frappe Cloud の共有ベンチ | サポート対象 | サポート対象外 | サポート対象外 |
重要なルール 5 つ
importは絶対に記述しないでください。すべてfrappe名前空間にあります。- Before Save スクリプト内で
doc.save()を絶対に呼び出さないでください。無限ループの原因になります。 - Document Event スクリプト内で
frappe.db.commit()を絶対に呼び出さないでください。フレームワークが処理します。 - Scheduler スクリプトの最後では、
frappe.db.commit()を必ず呼び出してください。 - パラメーター化クエリを必ず使用してください。辞書と
%(var)sを使い、SQL 内で f-string は絶対に使用しないでください。
参考資料
- references/methods.md — サンドボックス API の完全なリファレンス
- references/events.md — ドキュメントのライフサイクルと実行順序
- references/examples.md — スクリプトの種類ごとの動作する例
- references/anti-patterns.md — サンドボックス違反とよくある間違い
- references/syntax.md — 構文のクイックリファレンス
- references/patterns.md — よくあるパターン(検証、自動入力、API)
- references/hooks.md — Server Scripts と hooks.py の相互作用
相互参照
- frappe-syntax-api — Frappe REST API とホワイトリスト登録されたメソッド
- frappe-syntax-doctype — DocType のフィールド型とスキーマ
- frappe-core-database — frappe.db の詳細解説
- frappe-core-permissions — 権限システムのアーキテクチャ
- frappe-errors-common — エラー処理のパターン
コメント
Markdown 対応