Python API リファレンス

bootstrap()、open_model()、コンパイル版で自動的に働くゲート、そしてコードで処理が必要になりうる例外について。

あなたが実際に使うランタイム API は、トップレベルの関数 2 つだけです — 有効化やライセンス処理、ゲートそのものといった残りのすべては、その背後で行われます。インポートしたり使ったりするデコレータはありません — magiclock build がライセンスゲートのチェックをコンパイル後のすべてのモジュールへ自動で挿入します(コンパイルビルドを参照)。

magiclock.bootstrap()

python
magiclock.bootstrap(vault_dir=None, *, app_version="1.0.0") -> MagicLockRuntime

現在のプロセスに対して、このマシンの有効化状態に基づきライセンスゲートを起動します。magiclock build の成果物では、この呼び出しはエントリモジュールに自動で挿入されます — 自分で呼び出す必要があるのは、コンパイルしない通常のスクリプトで、最初の open_model() 呼び出しより前に呼ぶ場合のみです。

  • vault_dir — ローカルの有効化状態の保存場所を上書きします。通常は必要ありません。
  • app_version — ライセンスが設定する max_app_version と照合されます。リリースをバージョン単位でロックする場合に関係します。
  • ランタイムオブジェクトを返します(直接必要になることはほとんどなく、大半のコードは副作用のためだけに bootstrap() を呼び出します)。
  • このマシンがまだ有効化されていない場合、または有効化情報は存在するがこのマシンに対して無効な場合に例外を送出します(下記の「処理が必要になりうる例外」を参照)。

magiclock.open_model()

python
magiclock.open_model(path, *, vault_dir=None, passphrase=None, key=None) -> bytes

protect-model で生成された .enc エンベロープを復号し、平文のバイト列を返します — メモリ上にのみ存在し、ディスクに書き込まれることはありません。

  • path.enc ファイルへのパス。
  • passphrase / key — パスフレーズ・ポータブル(--passphrase)、キー・ポータブル(--emit-key)、または二要素(--bind-machine --passphrase)の成果物向け。鍵なしポータブル(デフォルト)や単純なマシンロック形式の場合は省略します。
  • マシンロック形式(--bind-machine)の成果物では、事前に bootstrap() が実行されている必要があります。ポータブル形式のエンベロープは bootstrap() なしで復号できます。
python
import magiclock

magiclock.bootstrap()
weights = magiclock.open_model("model.onnx.enc")

コンパイル版のゲート

呼び出すべき関数やデコレータはありません — マシンロック(--bind-machine)またはクラウド制御(--web-gate)ビルドでは、magiclock build がソースツリーを走査し、コンパイル前にすべてのモジュールへゲートチェック(python_protect ケーパビリティの確認)を 1 つずつ挿入します(デフォルトのポータブルビルドにはゲートは組み込まれません — その保護はネイティブコンパイル自体です)。

python
# あなたのソースコード。あなた自身は変更しません:
def export_report(data: str) -> bytes:
    ...

# `magiclock build` が実際にコンパイルする内容(概念的には):
current_runtime().require_feature("python_protect")

def export_report(data: str) -> bytes:
    ...

このチェックは各モジュールにつき一度だけ、プロセスがそのモジュールを最初にインポートしたタイミングで実行されます — 呼び出しごとではありません。1 つの共有された削除可能なデコレータの背後に集約されるのではなく、コンパイルされたすべてのモジュールへ織り込まれているため、攻撃者はモジュールを 1 つずつ見つけて patch する必要があります。エントリではないモジュールの失敗は通常の例外として伝播します(ライブラリとしてインポートされる可能性があるため)。エントリモジュールの失敗は、それがないと他の何も動かないため、簡潔なメッセージを表示してから終了します。

magiclock.revalidate()

python
magiclock.revalidate() -> None

保護チェックをその場で再実行します。ゲートはインポート時にモジュールごとに一度だけ走ります。スクリプトには妥当なコストですが、長時間動き続けるプロセス(サーバー、ワーカー、デーモン)では、起動時に下した判断を信じ続けることになります。適切な周期で呼び出してください。1 時間ごとが一般的です。

python
import magiclock

def periodic_check():
    magiclock.revalidate()   # ライセンスや許可が成立しなくなっていれば例外

ローカルのライセンスゲート一式を再実行し、さらにこのプロセスで読み込まれているクラウド制御(--web-gate)成果物については、サーバーから新しい署名付きの許可を強制的に取得します。この後半が重要です。ポータルでの無効化が、長時間稼働するサービスに対して直ちに効くのか、次の自然なチェックポイントまで待つのかを決めます。クラウド制御成果物を長時間稼働プロセスに載せるなら、この呼び出しの有無が「数秒で止まる」と「いずれ止まる」の差になります。

インポート時のゲートと同じ型付きエラーを送出し、問題がなければ None を返します。

magiclock.revalidate_every()

python
magiclock.revalidate_every(seconds, *, on_error=None)

バックグラウンドのデーモンスレッドで revalidate() を定期実行し続けます。bootstrap() の近くで一度だけ呼び出してください。

python
import magiclock

magiclock.bootstrap()
magiclock.revalidate_every(3600)     # 1 時間ごとに再確認

チェックが失敗した場合、既定ではプロセスを終了します。 バックグラウンドスレッドが失敗を握りつぶすと、成立しなくなったライセンスのままサービスを提供し続けることになり、しかもデーモンスレッドのトレースバックは誰も読みません。on_error を渡せば自分で引き取れます — 接続をドレインし、readiness プローブを落としてから終了してください。

python
magiclock.revalidate_every(3600, on_error=lambda exc: my_graceful_shutdown(exc))

返されるスレッドには stop() があり、正常終了に使えます。on_error 自体が例外を送出した場合もプロセスは終了します — 壊れたハンドラが動き続ける手段になってはなりません。

処理が必要になりうる例外

bootstrap()open_model() は、「まだ有効化されていない」から「この成果物は期限切れ」まで、さまざまな状況で例外を送出します。多くの統合コードでは、広く例外を捕捉し、例外のクラス名で分岐します。

python
try:
    magiclock.bootstrap()
    data = magiclock.open_model("model.onnx.enc")
except Exception as exc:
    name = type(exc).__name__
    if name == "NotActivatedError":
        ...  # this machine hasn't been activated — run `magiclock activate`
    elif name == "TrialExpiredError":
        ...  # the artifact's expiry window has passed
    else:
        raise
例外発生する状況
NotActivatedErrorこのマシンで有効化が見つからない — magiclock activate を実行してください(または CLI に初回暗号化時の自動有効化を任せてください)。
VaultLockedErrorここではローカルの有効化状態を開けない — マシンが違うか、ボルトファイルが移動/改変されています。この 2 つのケースは設計上区別できません。セキュリティモデルを参照してください。
TrialExpiredError--trial--expires-in--expires-at のいずれかを指定した成果物が、復号可能な期間を過ぎています。
SubscriptionExpiredErrorプランの期限が切れています。これは新規の暗号化のみをブロックし、復号側で送出されることは一切ありません。
SubscriptionInvalidErrorサブスクリプションの認証情報が、必要な箇所で欠落している、形式が不正である、または署名検証に失敗しています。
PortableFormatErrorポータブルエンベロープ(デフォルト、または --passphrase/--emit-key)の形式が不正、またはサポート対象外のバージョンです。
PortableKeyErroropen_model()/run --key に渡したパスフレーズまたはキーが、ポータブル成果物と一致しません。
DebuggerDetectedError復号処理の境界でデバッガのアタッチが検出されました。
RuntimeNotInitializedErrorfrom magiclock_host.errors import RuntimeNotInitializedErrorこのプロセスで bootstrap() が実行される前に、ゲートされたモジュールのチェックが実行されました。

互換性に関する注意

.pya/.enc エンベロープには、それを生成した CPython のメジャー・マイナーバージョンが埋め込まれています。3.12 で暗号化した成果物は 3.13 では読み込めません — 1 つの成果物を複数の Python バージョンにまたがって出荷するのではなく、インタプリタをアップグレードしたら protect/protect-model を再実行してください。