✓=レビュー済 ○=未レビュー ⚠=Suspect(複数同時表示あり。IDクリックで詳細へ)
| グループ | REQ | ARCH | SPEC | TST | IMPL |
|---|---|---|---|---|---|
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン... | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... |
| 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)... |
| リンク方向 | カバー数 | カバー率 | 未カバー |
|---|---|---|---|
| ARCH → REQ | 1 / 1 | 100.0% | — |
| SPEC → ARCH | 1 / 1 | 100.0% | — |
| TST → SPEC | 2 / 2 | 100.0% | — |
| IMPL → SPEC | 2 / 2 | 100.0% | — |
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
親: —
子: 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 で元の値と一致すること親: SPEC012
子: —
カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は SerializationError によるキャッシュ保存失敗を引き起こし、ユーザーの既存コードとの統合を阻害する。デコレータ方式と命令方式の両方の登録パスを検証する。
@spot.register(code=N, encoder=..., decoder=...) でクラスが登録され、そのインスタンスのシリアライズ/デシリアライズが正常に動作することspot.register_type(MyClass, code=N, ...) でも同等の登録が行われることmodel_validate をデコーダとして使用する場合に、バリデーション付きの復元が正しく動作すること親: 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
子: —