Skip to content

Data Serialization & Registry

beautyspot におけるデータの変換処理には、明確に異なる2つの役割があります。ここを混同しないことが、正しくライブラリを使いこなすコツです。

  1. Cache Key Generation (Input): 関数の「引数」をハッシュ化し、キャッシュの ID (Key) を決める。
  2. Result Serialization (Output): 関数の「戻り値」をバイナリ化し、DBやストレージに 保存 (Persist) する。

このページでは、後者の「戻り値の保存」について解説します。

Input vs Output の違い

機能 対象 役割 設定方法
KeyGen 引数 (Inputs) 「前回と同じ入力か?」を判定する input_key_fn=...
(ローカル設定)
Registry 戻り値 (Outputs) データをディスクに保存・復元する spot.register(...)
(グローバル設定)
graph LR
    subgraph "Input Flow (KeyGen)"
    A[Arguments] --> B{Canonicalize}
    B --> C[SHA-256 Hash]
    C --> D[Cache Key]
    end

    subgraph "Output Flow (Registry)"
    E[Return Value] --> F{Serializer}
    F --> G[Msgpack Bytes]
    G --> H[Storage / DB]
    end

標準のシリアライズ (Msgpack)

beautyspot はデフォルトで Msgpack を使用しています。以下の型は設定なしで保存・復元が可能です。

  • Python プリミティブ (int, float, str, bool, bytes, None)
  • コレクション (dict, list, tuple)

カスタム型の登録 (spot.register)

独自のクラスや、標準でサポートされていない型を戻り値として返したい場合、register デコレータを使います。

Point: dictlist など、Msgpackで扱える基本的な型を返せば、beautyspot が自動的にバイナリへ変換(パック)します。

基本的な使い方

クラス定義にデコレータを添えるだけです。

@spot.register(
    code=10,  # 0-127 の間でユニークなIDを指定
    encoder=lambda obj: obj.to_dict(),          # Object -> Dict (or any serializable)
    decoder=lambda data: MyClass.from_dict(data) # Dict -> Object
)
class MyClass:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def to_dict(self):
        # 単に辞書を返すだけ!
        return {"x": self.x, "y": self.y}

    @staticmethod
    def from_dict(data):
        # data は自動的に unpack された状態で渡されます
        return MyClass(data["x"], data["y"])

既存のライブラリ型を登録する

自分で定義していないクラス(例: numpy.ndarray)を登録する場合は、spot.register_type 関数を使用します。

import numpy as np
import io

def encode_numpy(arr):
    # メモリ上のバイナリとして書き出し
    with io.BytesIO() as f:
        np.save(f, arr)
        return f.getvalue()

def decode_numpy(data):
    with io.BytesIO(data) as f:
        return np.load(f)

# アプリケーション起動時に登録
spot.register_type(
    type_=np.ndarray,
    code=20,
    encoder=encode_numpy,
    decoder=decode_numpy
)

タスク単位でのシリアライザの上書き (Advanced)

通常、@spot.register で登録した設定はワークスペース全体で共有されますが、特定のタスクでのみ異なるシリアライズ挙動をさせたい場合があります。 markcached_runserializer 引数に、SerializerProtocol を満たすオブジェクト(MsgpackSerializer のインスタンスなど)を渡すことで、その実行コンテキストのシリアライザを上書きできます。

これを利用すると、以下のようなケースに対応できます。

  1. 一時的な型登録: グローバル環境を汚さずに、特定のタスク専用のカスタム型を登録する。
  2. Pickleの利用: Msgpackではどうしても扱えないオブジェクトに対し、標準の pickle を使用する(※セキュリティリスクと互換性に注意)。
import pickle

class PickleSerializer:
    def dumps(self, obj) -> bytes:
        return pickle.dumps(obj)

    def loads(self, data) -> Any:
        return pickle.loads(data)

# このタスクだけ Pickle を使用して保存・復元される
@spot.mark(serializer=PickleSerializer())
def complex_task():
    ...

よくある間違い

引数にカスタム型を渡す場合

関数の 引数 としてカスタム型を渡すだけであれば、register不要 です。 KeyGen はオブジェクトの内部構造(__dict__など)を自動的に検査してハッシュを作るからです。

register が必要なのは、あくまでそのオブジェクトを 「キャッシュとして保存(永続化)」 したい場合のみです。