Python
actql パッケージは、ActQLをPythonのアプリの中で動かします。動かしておくサーバーはなく、同じAPIでリモートのHostやActQL Cloudにもつながります。TypeScriptとNodeをPythonの流儀で写したもので、メソッドは snake_case、状態は型付きのdataclass(dictが必要なら dataclasses.asdict())、同期と async の両方のインターフェースがあります。
uv add actql
# or
pip install actqlPython 3.11以上が必要です。
短いクエリ
actql.open() が最短の道です。状態をディレクトリに置くローカルのエンジンと、1回限りのクエリのための sql() / aql() が手に入ります。
import actql
with actql.open(state_dir=".actql") as db:
rows = db.sql(
"SELECT id, name FROM crm.accounts WHERE owner = @owner",
{"owner": "kim"},
).all()
print(rows)
with db.aql("FROM crm.accounts |> LIMIT 1000").rows() as reader:
for row in reader:
print(row["id"])sql() は普通の SELECT を、aql() はPipe構文を受け取ります。第2引数はパラメーターのマップです。all() で全件を読み、rows() で順に流し読みします。どちらも with ブロックの中で実行します。ブロックを抜けるとハンドルは閉じられます。
同じ sql() と aql() は、connect() で作ったクライアントでも使えます。
短いクエリは読むだけです。Effectのあるプログラムは start_source を通します。
接続する
client = actql.connect(
mode="local",
installation=".actql", # Storeのディレクトリ
profile="mock", # 省略すると設定した接続を使う
)リモートのHostには:
client = actql.connect(
mode="remote",
endpoint=os.environ["ACTQL_ENDPOINT"], # actql://host.acme.internal
token=os.environ["ACTQL_TOKEN"],
)token には文字列のほか、文字列を返す呼び出し可能オブジェクトも渡せます。endpoint には、actql:// の接続文字列か、Hostの https:// のURLを渡します。クライアントはコンテキストマネージャーとして使うか、自分で close() を呼んでください。閉じてもRunは取り消されません。
イベントループの中では、非同期のインターフェースを使います。同じメソッドを await するだけです。
async with await actql.AsyncClient.connect(mode="local", installation=".actql") as client:
rows = await client.sql("SELECT 1 AS id").all()client.sql(…) はクエリのハンドルを返し、.all() を await する(または .rows() を順に読む)ことでクエリが実行されます。読み取り1回につき await は1つです。
プログラムを実行する
source = open("churn-risk.aql").read()
execution = client.start_source(
source,
format="aql",
parameters={"owner": "kim"},
stop_before="commit", # 任意:最初の書き込みの前で止める
)
state = execution.wait()
print(state.status) # "succeeded", "waiting_checkpoint", "failed", …start_source はプログラムを検査し、永続的なRunを始めます。wait() は次の安定した状態、つまり判断待ち、突き合わせ待ち、または最終状態で、status、checkpoint、effects、revision を持つ RunState のdataclassを返します。Runがすでに安定した状態なら、wait() はその状態をすぐに返します。その次の状態を待つには wait(since=state.revision) を使います。succeeded は、すべての計算とEffectに結果が出たという意味です。非同期のEffectは accepted のまま、外部システム側の処理が続いていることがあります。execution.ref はRunの参照です。別のプロセスがこのRunを必要とするなら保存しておきます。
成功したあとに、名前付きのResultを読みます。
rows = execution.result("updates").all(max_rows=10_000)読み取りには上限があります。max_rows を渡すか、rows() で流し読みしてください。レコードを検証して型を付けるには、schema= にpydanticのモデル、またはPython SDKが受け付ける検証オブジェクトを渡します。合わないレコードがあれば例外になり、値が変換されることはありません。
Checkpoint
ポリシーが書き込みの判断を保留したとき、または stop_before="commit" を渡したとき:
state = execution.wait()
if state.status == "waiting_checkpoint":
ckpt = state.checkpoint
print(ckpt.id, ckpt.effect.target, len(ckpt.items))
for change in ckpt.preview.changes:
print(change.id, change.field, change.before, "→", change.after)コードから判断するか、人に渡してこのプロセスを終えるかを選べます。RunはStoreの中で待ちます。
if len(ckpt.items) <= 100:
client.approve(ckpt.id)
final = execution.wait(since=state.revision)
else:
notify_reviewer(run_ref=execution.ref, checkpoint=ckpt.id)
# ここで待つものはない。レビュー担当者がCLIか別の場所から承認し、
# あとのプロセスが execution.ref にアタッチして結果を読む。approve() と reject() は、クライアントに設定した承認者で判断に署名し、そのプレビューに結び付けます。承認すると同じRunが続きます。却下すると、Runは何も送らずに cancelled で終わります。
コードで判断する
クライアント自身を委譲先にすることもできます。connect() に decide 関数を渡すと、Hostは止まる前にプレビューを渡してそれを呼びます。関数は "permit"、"reject"、"defer" のどれかを返し、"defer" のときだけCheckpointが作られます。
def decide(decision):
if decision.kind != "effect_set": # 読み取り、LLMの呼び出し、Run自体
return "permit"
return "permit" if len(decision.preview.items) <= 100 else "defer"
client = actql.connect(mode="local", installation=".actql", decide=decide)decision.kind は "run"、"read"、"infer"、"egress"、"effect_set" のどれかです。preview を持つのは effect_set の判断だけで、その items は変更されるレコード、fields は書き込まれる項目、target は対象のリソースです。関数ができるのは判断だけで、プレビューを変えることはできません。関数が例外を送出すれば、何も送られません。
レビュー用のツール向けには、client.checkpoints()、client.review_checkpoint(item)、client.resolve_checkpoints(items, signer=...) が、明示的な署名とページングを伴う同じ操作を提供します。
Runにアタッチする
execution = client.attach(saved_ref)
state = execution.status() # 現在の状態。待たない再起動のあとも、Runは止まった場所にそのままあります。応答が失われた書き込みは needs_reconciliation として現れます。
if state.status == "needs_reconciliation":
after = execution.reconcile() # 外部システムに何が起きたかを確認する。再送はしない
print(after.status, after.effects)execution.cancel() はRunに停止を求めます。同じプログラムを同じ request_id で始めると、もう一度実行する代わりに既存のRunが返ります。ただし、プログラム、パラメーター、入力、認可のスコープが同じときに限ります。同じ request_id に違う内容を渡すと RequestConflict が送出されます。
execution = client.start_source(source, format="aql", request_id="nightly-2026-10-15")実行せずに検査する
outcome = client.check(source, kind="text", format="aql", scope="template_only")
if outcome.report.verdict != "valid":
for reason in outcome.report.reasons:
print(reason.code, reason.location)client.plan(source, format="aql") は、actql explain が表示する内容をデータとして返します。
ファイルの内容を読むこと、SERIALIZE でファイルを作ること、Resultを受け取ることは、別々のものです。ファイルを扱うがその違いを説明しています。
エラー
失敗は、安定した code を持つ actql.ClientError のサブクラスとして送出されます。
| 例外 | いつ |
|---|---|
CheckFailed | プログラムが無効。報告が添付される |
RequestHeld | Runが始まる前にポリシーが判断を保留した(Run前のCheckpoint)。ほかのCheckpointと同じように解決してから、Runに attach する |
RequestConflict | その request_id が、別のプログラム、パラメーター、入力、またはスコープで使われていた |
SubmissionUnknown | 開始への応答が失われた。リクエストを照会し、新しいIDで始め直さない |
ResolutionUnknown | 承認への応答が失われた。同じ項目を再送すれば、Hostは繰り返しだと認識し、二重に適用しない |
ClientClosed | クライアントが閉じられている |
応答が失われたことは、何も起きなかったことを意味しません。リクエストやRunはその識別情報で照会し、書き込みの再試行として再送はしないでください。
次に読む
- クイックスタート:解約リスクのプログラムを最初から最後まで
- ローカルとリモートのHost:接続、認証情報、ポリシー
- Runのライフサイクルと復旧:すべての状態と、そのときにすること
Source: actql/actql/docs/guides/python.md at d904416674e4 · Edit on GitHub