✓=レビュー済 ○=未レビュー ⚠=Suspect(複数同時表示あり。IDクリックで詳細へ)
| グループ | REQ | ARCH | SPEC | TST | IMPL | ADR |
|---|---|---|---|---|---|---|
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン... | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... | ADR004 ✓ # Resilient Deserialization and Explicit Versioning ## Context and Problem Stat... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... | ADR004 ✓ # Resilient Deserialization and Explicit Versioning ## Context and Problem Stat... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン... | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... | ADR008 ✓ # Customizable Serialization Strategy with Msgpack Default ## Context and Probl... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン... | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... | ADR014 ✓ # Decorator-based Type Registration with Late Binding ## Context and Problem St... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン... | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... | ADR017 ✓ title: Nested Msgpack Protocol via Serializer Wrapping status: Accepted date: 20... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン... | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... | ADR019 ✓ # Per-Task Serializer Override ## Context and Problem Statement / コンテキスト プロジェク... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン... | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... | ADR025 ✓ # Protocol-based Serializer Registration ## Context and Problem Statement / コンテ... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン... | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... | ADR039 ✓ # Bounded LRU Cache for Subclass Resolution in Serializer ## Context and Proble... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン... | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... | ADR042 ✓ # Thread-safe LRU Cache for Serializer ## Context and Problem Statement / コンテキス... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... | ADR008 ✓ # Customizable Serialization Strategy with Msgpack Default ## Context and Probl... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... | ADR014 ✓ # Decorator-based Type Registration with Late Binding ## Context and Problem St... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... | ADR017 ✓ title: Nested Msgpack Protocol via Serializer Wrapping status: Accepted date: 20... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... | ADR019 ✓ # Per-Task Serializer Override ## Context and Problem Statement / コンテキスト プロジェク... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... | ADR025 ✓ # Protocol-based Serializer Registration ## Context and Problem Statement / コンテ... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... | ADR039 ✓ # Bounded LRU Cache for Subclass Resolution in Serializer ## Context and Proble... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... | ADR042 ✓ # Thread-safe LRU Cache for Serializer ## Context and Problem Statement / コンテキス... |
| リンク方向 | カバー数 | カバー率 | 未カバー |
|---|---|---|---|
| ARCH → REQ | 1 / 1 | 100.0% | — |
| SPEC → ARCH | 1 / 1 | 100.0% | — |
| TST → SPEC | 2 / 2 | 100.0% | — |
| IMPL → SPEC | 2 / 2 | 100.0% | — |
| ADR → REQ | 1 / 1 | 100.0% | — |
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
親: —
子: ADR004, ADR008, ADR014, ADR017, ADR019, ADR025, ADR039, ADR042, ARCH005
graph TD
A[MsgpackSerializer] -->|Registry| B[TypeRegistryProtocol]
A -->|LRU Cache| C[Thread-Local Storage]
A -->|Core| D[msgpack-python]
| コンポーネント | 責務 | インターフェース |
|---|---|---|
| MsgpackSerializer | MessagePackベースの高速・コンパクトなバイナリ変換 | dumps(), loads() |
| TypeRegistryProtocol | ユーザー定義型(ExtType)のエンコーダ・デコーダ登録 | register() |
| Thread-Local Cache | free-threading環境でのロック競合を回避するMRO解決キャッシュ | OrderedDict (LRU) |
sequenceDiagram
participant User as ユーザーコード
participant Ser as MsgpackSerializer
participant Reg as TypeRegistry
participant MP as msgpack
User->>Ser: dumps(obj)
Ser->>Ser: 型チェック & MRO解決
opt カスタム型
Ser->>Reg: エンコーダ取得
Reg-->>Ser: encoder_fn
Ser->>MP: ExtType(code, encoder_fn(obj))
end
MP-->>Ser: binary
Ser-->>User: bytes
| 技術領域 | 選定 | 理由 |
|---|---|---|
| フォーマット | MessagePack | JSONより高速・コンパクト。バイナリをネイティブにサポート |
| スレッド安全 | Copy-on-Write (CoW) | 読み取りパスからロックを排除し、マルチスレッド性能を最大化 |
| 型拡張 | ExtType (0-127) | カスタム型を1バイトのオーバーヘッドで表現可能 |
ExtCode 未登録エラーを検知し、互換性問題を明示的に報告親: REQ005
class MsgpackSerializer(SerializerProtocol, TypeRegistryProtocol):
def dumps(self, obj: Any) -> bytes: ...
def loads(self, data: bytes) -> Any: ...
dumps)type(obj) で取得ExtType としてパックSerializationError)loads)ExtType を発見した場合、コードに対応するカスタムデコーダを呼び出す| 設定 | デフォルト | 説明 |
|---|---|---|
max_cache_size |
1024 |
スレッドごとの型解決LRUキャッシュの最大サイズ |
SerializationError を送出SerializationError を送出registry_generation カウンタを使用して、スレッドローカルキャッシュの整合性を維持親: ARCH005
def register(
code: int,
encoder: Callable[[Any], Any],
decoder: Callable[[Any], Any],
) -> Callable: ... # デコレータ形式
def register_type(
type_class: Type,
code: int,
encoder: Callable[[Any], Any],
decoder: Callable[[Any], Any],
) -> None: ...
_registry_generation をインクリメントし、全スレッドのLRUキャッシュを無効化対象とする| パラメータ | 制限 | 説明 |
|---|---|---|
code |
0 〜 127 |
MessagePack ExtType のユーザー定義領域コード |
encoder |
引数1、戻り値シリアライズ可能 | オブジェクトをプリミティブ構造へ変換する関数 |
decoder |
引数1、戻り値オブジェクト | プリミティブ構造からオブジェクトを復元する関数 |
ValueError を送出し、レジストリの破損を防ぐValueError親: ARCH005
MsgpackSerializer はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレントなデータ劣化(精度低下等)に直結する。スレッドセーフ性の欠如はマルチスレッド環境での競合状態を引き起こす。
dumps → loads で元の値と一致することreferences: tests/unit/test_serializer.py
親: SPEC012
子: —
カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は SerializationError によるキャッシュ保存失敗を引き起こし、ユーザーの既存コードとの統合を阻害する。デコレータ方式と命令方式の両方の登録パスを検証する。
@spot.register(code=N, encoder=..., decoder=...) でクラスが登録され、そのインスタンスのシリアライズ/デシリアライズが正常に動作することspot.register_type(MyClass, code=N, ...) でも同等の登録が行われることmodel_validate をデコーダとして使用する場合に、バリデーション付きの復元が正しく動作することreferences: tests/unit/test_type_registry.py
親: SPEC013
子: —
MsgpackSerializer クラスが SerializerProtocol と TypeRegistryProtocol を実装。
dumps(obj) -> bytes / loads(data) -> Any が主要 API。
カスタム型は msgpack の ExtType(code, payload) で拡張する。
_default_packer() がエンコード時のカスタム型ディスパッチ、
_ext_hook() がデコード時の型復元を行う。
_encoders / _decoders 辞書は register() 時に新しい辞書を作成して
アトミックに差し替える(CoW)。読み取り側はスナップショットを参照するため、
ロックなしで安全に読める。GIL に依存せず、PEP 703(free-threading)にも
対応できる設計。世代カウンタ _registry_generation をレジストリ差し替えの前に
インクリメントすることで、読み取り側が古いキャッシュを使い続けることを防ぐ。
MRO スキャンの結果(サブクラス→登録済み親クラスの解決)を threading.local() の
OrderedDict にキャッシュする。_cache_generation と _registry_generation を比較し、
世代が変わったらキャッシュを破棄する。LRU のサイズ上限は max_cache_size で制御。
Pydantic モデルのサブクラスのように、親クラスが登録済みなら
サブクラスも自動的に同じエンコーダで処理できる。type(obj).__mro__ を走査し、
最初にマッチした登録型のエンコーダを使用する。
register() の code は 0-127(msgpack ExtType の制約)ConfigurationError で拒否_ext_hook 内のデシリアライズは再帰的に msgpack.unpackb(ext_hook=self._ext_hook) を
呼ぶことで、ネストされたカスタム型も復元できるSerializationError のメッセージには未登録型のヒント(spot.register() の使い方)を含めるreferences: src/beautyspot/serializer.py
親: SPEC012
子: —
2つのレイヤーで構成:
- MsgpackSerializer.register(type, code, encoder, decoder): 低レベルの型登録 API
- Spot.register() / Spot.register_type(): ユーザー向けの高レベル API
Spot.register() はデコレータ形式、Spot.register_type() は命令形式で、
いずれも内部で serializer.register() に委譲する(TypeRegistryProtocol 準拠時のみ)。
デコレータ形式 @spot.register(code=1, ...) はクラス定義と同時に登録でき、
宣言的で読みやすい。命令形式 spot.register_type(MyClass, code=1, ...) は
外部ライブラリのクラスなどデコレートできない場合に使用する。
シリアライザが TypeRegistryProtocol を実装していない場合(カスタムシリアライザ)、
register() / register_type() は ConfigurationError を送出する。
これにより、型登録非対応のシリアライザを使用する場合のエラーメッセージが明確になる。
register() の内部で _write_lock を取得してから CoW で辞書を差し替えるmodel_validate(data) を使用し、
バリデーション付きの復元が可能references: src/beautyspot/serializer.py, src/beautyspot/core.py
親: SPEC013
子: —
Pythonの pickle は、保存されたオブジェクトのクラス定義と現在のコードが一致していることを前提とします。
開発中はクラス定義が頻繁に変更されるため、過去のキャッシュを読み込む際に AttributeError 等が発生し、アプリケーションがクラッシュする問題がありました。
version 指定による無効化を可能にする。Chosen option: Option 2.
Storage.load() でデシリアライズエラー(クラス不整合や破損)が発生した場合、これを CacheCorruptedError として捕捉し、core.py 側で 「キャッシュミス」 として扱う。これによりアプリをクラッシュさせず、自動的に再計算を行う。@task デコレータに version 引数を追加する。これを変更するとキャッシュキーが変わり、古い(互換性のない)キャッシュを参照しなくなる。version の更新を検討してね」というヒントを出力し、ベストプラクティスへ誘導する。version を使うことで安全なデプロイ(キャッシュ切り替え)が可能になる。親: REQ005
子: —
現在、beautyspot はキャッシュデータのシリアライズ(保存)に Python 標準の pickle を使用している。
これには以下の重大な課題がある:
pickle は信頼できないデータを読み込む際に任意のコード実行(RCE)を引き起こす可能性があり、「共有キャッシュ」としての利用にリスクがある。json は安全だが、画像などのバイナリデータを効率的に扱えず(Base64化でサイズ増)、タプルなどのPython固有型が失われる。v1.0.0 リリースにあたり、「デフォルトで安全(Secure by Default)」 かつ 「拡張性(Extensibility)」 のあるシリアライズ戦略が必要である。
pandas.DataFrame)を、ライブラリ側の変更なしに保存・復元できるようにしたい。pickle のまま)。警告文で責任をユーザーに委ねる。json に一本化する。バイナリは Base64 エンコードを強制する。msgpack を採用し、標準型のみサポートする。msgpack を採用し、ExtType 機能を用いた「カスタム型登録システム」を実装する。Chosen option: Option 4.
シリアライザのバックエンドとして msgpack (MessagePack) を採用し、さらにユーザーが任意の型変換ロジックを注入できる TypeRegistry パターンを導入する。
msgpack>=1.0.0 を pyproject.toml に追加する。pickle はデフォルトから廃止(またはオプトイン化)する。msgpack を使用し、安全な型のみをシリアライズする。Project クラスに register_type(type, code, encoder, decoder) メソッドを追加する。code) と変換関数を登録することで対応できる。TypeError ではなく、具体的なオブジェクト情報と register_type の利用を促す親切なエラーメッセージ (SerializationError) を送出する。msgpack の default (pack時) と ext_hook (unpack時) を活用し、拡張型を msgpack.ExtType としてラップする。
# Implementation Sketch
class Project:
def __init__(self, ...):
# ...
self.serializer = MsgpackSerializer()
def register_type(self, type_: type, code: int, encoder: Callable, decoder: Callable):
"""
Register a custom serializer for a specific type.
Args:
type_: The class to handle (e.g. MyClass)
code: Unique integer ID (0-127) for this type
encoder: Function that converts obj -> bytes
decoder: Function that converts bytes -> obj
"""
self.serializer.register(type_, code, encoder, decoder)
# --- Internal ---
class MsgpackSerializer:
def dump(self, obj):
return msgpack.packb(obj, default=self._default_packer, use_bin_type=True)
def load(self, data):
return msgpack.unpackb(data, ext_hook=self._ext_hook, raw=False)
def _default_packer(self, obj):
# 登録済みの型なら ExtType に変換
if type(obj) in self._encoders:
code, encoder = self._encoders[type(obj)]
return msgpack.ExtType(code, encoder(obj))
# 未登録なら詳細なエラーを出す
raise SerializationError(f"Object of type '{type(obj).__name__}' is not serializable...")
pickle を排除することで、デフォルトでの脆弱性を解消できる。json + Base64 よりも高速かつ省サイズでバイナリデータを扱える。msgpack パッケージへの依存が発生する(ただし軽量であるため許容範囲とする)。pickle で保存されたキャッシュデータ (.pkl) との互換性がなくなるため、v1.0.0 アップデート時にキャッシュクリア(DBリセット)が必要になる。See ADR-0009 for further refinements regarding save_blob=False behavior (Msgpack + Base64) and size guardrails.
親: REQ005
子: —
これまでの spot.register_type() は命令的であり、クラス定義と登録ロジックが分離してしまうため、凝集度が低いという課題がありました。
また、Pydantic モデルのようなクラスメソッド(例: cls.model_validate_json)をデコーダとして登録したい場合、デコレータ評価時にはまだクラスが未定義(NameError)であるため、綺麗に記述できないという技術的な制約がありました。
register_type() メソッドのみを提供する。register メソッドを追加し、decoder_factory による遅延バインディングを導入する。Chosen option: Option 2.
Spot クラスに register デコレータメソッドを追加し、decoder_factory による遅延バインディング を導入します。
@spot.register(
code=10,
encoder=lambda obj: ...,
decoder_factory=lambda cls: cls.deserialize # Class is passed here after definition
)
class MyModel:
...
register デコレータは、対象クラスの定義完了後(デコレート時)に実行される。decoder_factory が指定されている場合、生成された cls オブジェクトを引数としてファクトリを実行し、実際の decoder 関数を取得する。decoder を用いて、既存の register_type バックエンドに登録する。decoder と decoder_factory という2つの引数が増え、APIが少し複雑になる(ドキュメントでの補完が必要)。親: REQ005
子: —
title: Nested Msgpack Protocol via Serializer Wrapping status: Accepted date: 2026-02-02 context: Improving DX for Custom Types
これまでは、カスタム型のエンコーダは ExtType の仕様に合わせて「生のバイト列」を返す必要がありました。
これにより、ユーザーは msgpack ライブラリを直接操作する必要があり、Pydantic モデルなどの扱いが煩雑になっていました。
bytes)を返し、デコーダがそれを受け取る(旧仕様)。Chosen option: Option 2.
Serializer Wrapping (Nested Protocol) を採用します。
MsgpackSerializer は、ユーザー定義のエンコーダ/デコーダに対するラッパー(Wrapper)として機能する。packb して ExtType に格納する。ExtType のデータを、ライブラリが自動的に unpackb してからデコーダに渡す。import msgpack をする必要がなくなり、直感的な実装が可能になる。親: REQ005
子: —
プロジェクト全体としては安全性と互換性の観点から msgpack を標準シリアライザとしています。
しかし、探索的データ分析(EDA)やプロトタイピング、あるいは msgpack でのシリアライズが困難なサードパーティ製オブジェクトを扱う特定のタスクにおいては、Python標準の pickle のような柔軟なシリアライザを局所的に使用したいというニーズがあります。
Spot.mark() および Spot.cached_run() に serializer 引数を追加し、局所的なオーバーライドを可能にする。Chosen option: Option 2.
Spot.mark() および Spot.cached_run() に、オプショナル引数 serializer を追加します。dumps(obj) -> bytes および loads(bytes) -> obj メソッドを持つ任意のオブジェクトを渡すことができます。pickle 等の機能を利用できる。pickle を使用したタスクは、異なる環境間や長期間の保存において互換性が保証されない。Spot.register() で登録したカスタム型変換は、オーバーライドされたシリアライザには適用されない。親: REQ005
子: —
beautyspot では、@spot.register デコレータを使用してカスタム型を登録できます。
当初の実装では、Spot クラス内でシリアライザが MsgpackSerializer のインスタンスであるかどうかを明示的にチェックしていました。
if isinstance(self.serializer, MsgpackSerializer):
self.serializer.register(...)
これは、高レベルの Spot クラスを特定の具象クラス (MsgpackSerializer) に結合させており、依存関係逆転の原則 (Dependency Inversion Principle) に違反していました。その結果、カスタム型登録をサポートする他のシリアライザ(将来的な JsonSerializer やカスタムラッパーなど)を使用することが不可能でした。
Spot クラスを特定のライブラリ(msgpack)や実装から切り離すこと。isinstance チェックを維持する。Chosen option: Option 2.
beautyspot.serializer に TypeRegistryProtocol を導入します。
register メソッドのシグネチャを規定するプロトコルを定義します。Spot クラスは、具象クラスではなくこのプロトコルに対して適合性チェックを行うように変更します。SerializerProtocol (dump/load) と TypeRegistryProtocol を分離し、型登録をサポートしないシリアライザも許容できるようにします。msgpack から解放され、TypeRegistryProtocol に従う任意のシリアライザを使用可能になった。register メソッドのシグネチャを正確に遵守する必要がある。親: REQ005
子: —
MsgpackSerializer の _default_packer において、クラスの解決結果を保存するキャッシュ辞書が、動的に型が生成される環境(動的な Pydantic モデル等)で無制限に肥大化し、メモリリークを引き起こす懸念がありました。
また、この辞書へのアクセスは非常に高頻度であるため、厳密なスレッドセーフティ(ロック)を導入すると、シリアライズ性能のボトルネック(ロック競合)を招く恐れがありました。
dict)。メモリリークのリスクがある。OrderedDict を利用した上限付き LRU キャッシュを、楽観的(ロックフリー)なアプローチで実装する。Chosen option: Option 3.
標準ライブラリの collections.OrderedDict を利用し、上限サイズ(デフォルト 1024)を持つ LRU (Least Recently Used) キャッシュ を導入します。マルチスレッド環境下でのアクセスに対しては、意図的にロックフリーなアプローチを維持します。
親: REQ005
子: —
MsgpackSerializer は動的型生成時のメモリリークを防ぐために、内部で OrderedDict を用いた LRU キャッシュを運用しています。しかし、バックグラウンド保存や Web フレームワークでの利用など、マルチスレッド環境からの並行アクセスが日常的に発生します。ロックを持たない OrderedDict への並行操作は、RuntimeError やデータの破損を引き起こす致命的なバグの温床となっていました。
threading.Lock() を導入し、キャッシュ操作を排他的に行う。Chosen option: Option 2.
MsgpackSerializer の内部状態(キャッシュおよびレジストリ)に対するすべての読み書き操作を threading.Lock() で保護します。
MsgpackSerializer をシングルトンとして安全にマルチスレッド環境で使い回せるようになった。親: REQ005
子: —