IMPL Specification

Generated at 2026-03-09 22:49:07

1.0 序文 IMPL025

本ドキュメントは、『beautyspot』の実装記録(ジャーナル)を保持する。

2.0 詳細仕様 IMPL026

ソースコードの実装構造、設計判断の根拠、および技術的負債について記録する。

2.1 mark デコレータ実装 IMPL001
CACHE

実装概要

Spot.mark() メソッドが本仕様の中核。二段階デコレータファクトリとして、 オプションを受け取る外側の mark() と、関数をラップする内側の decorator() で構成される。 sync/async の判定は inspect.iscoroutinefunction()デコレーション時に行い、 同期関数は _execute_sync()、非同期関数は _execute_async() に委譲する。 functools.wraps(fn) により元関数のメタデータ(__name__, __doc__, __module__, __qualname__)を保持する。

設計判断

二段階デコレータファクトリの採用

@mark() (括弧あり)と @mark(括弧なし)の両方をサポートするため、 引数の型で分岐するパターンを採用した。第一引数が callable なら括弧なし、 そうでなければ括弧ありと判定する。

sync/async 分岐のタイミング

デコレーション時に判定する方式を採用。呼び出し時に毎回 inspect する方式と比較し、 ランタイムオーバーヘッドがゼロになる利点がある。ただしデコレーション後に 関数の性質が動的に変わるケースには対応できない(実用上問題なし)。

ジェネレータ関数の拒否

inspect.isgeneratorfunction()inspect.isasyncgenfunction() の 両方をチェックし、ConfigurationError を送出する。 ジェネレータの戻り値はイテレータであり、キャッシュの意味論と矛盾するため。

実装メモ

TODO / 技術的負債

Ref: src/beautyspot/core.py
2.2 cached_run 実装 IMPL002
CACHE

実装概要

Spot.cached_run() はコンテキストマネージャとして実装。 渡された関数群を内部で mark() と同等のラッピングを行い、 コンテキスト内で呼び出すとキャッシュが効く一時的なラッパーを返す。

設計判断

スマートリターンの型分岐

単一関数の場合は結果を直接返し、複数関数の場合はタプルで返す。 これにより wrapped_fn = spot.cached_run(fn) のように自然に書ける。 0個の場合は ValidationError で早期失敗させる。

コンテキストマネージャパターン

@contextmanager デコレータを使用。__enter__ でラップ済み関数を返し、 __exit__ で後処理(drain 等)は with spot: に委譲する設計。

実装メモ

Ref: src/beautyspot/core.py
2.3 同期・非同期実行エンジン IMPL003
CACHE

実装概要

Spot._execute_sync()Spot._execute_async() が実行エンジンの中核。 両メソッドは同一のフロー(キー生成→キャッシュ検索→Herd待機→関数実行→保存)を それぞれ同期/非同期のセマンティクスで実装する。 mark() がデコレーション時に inspect.iscoroutinefunction() で判定し、 適切な方を選択する。

設計判断

同期・非同期の並行実装

_execute_sync_execute_async は処理フローが同一だが、 await の有無やロック機構(threading.Event vs asyncio.Future)が異なるため、 共通化せず並行して実装している。DRY 原則よりも明瞭性・デバッグ容易性を優先した。

Herd 結果の保存前共有

Herd protection の結果ボックスへの結果格納は、DB/Blob 保存のに行う。 これにより、保存が失敗しても待機中のスレッド/タスクは結果を受け取れる。 「保存の失敗で実行結果が失われる」ことを防ぐ意図的な設計。

バックグラウンド保存のエラー隔離

save_sync=False 時の保存エラーは on_background_error コールバックに 通知し、ERROR ログを出力するが、例外を再送出しない。 関数の実行自体は成功しているため、ユーザーに例外を見せるのは不適切と判断した。

実装メモ

Ref: src/beautyspot/core.py
2.4 SHA-256 キャッシュキー実装 IMPL004
KEY

実装概要

KeyGen クラスの静的メソッド群がキャッシュキー生成を担う。 _default(args, kwargs) が主要なエントリポイントで、引数を canonicalize() で正規化 → msgpack でシリアライズ → SHA-256 でハッシュする。 最終キーは func_identifier:input_id[:version] 形式の文字列を SHA-256 した値。

設計判断

SHA-256 の採用

MD5 より衝突耐性が高く、Python 標準ライブラリの hashlib で利用可能。 キャッシュキーとして64文字の hex 文字列は十分にコンパクトで、 DB のインデックスとしても効率的。

msgpack 経由のシリアライズ

正規化結果を直接 str() でハッシュする方式と比較し、 msgpack はバイナリ表現が安定しており、Python バージョン間での repr() の差異に影響されない。

ファイルベースの戦略

from_file_content() はファイルを 65KB チャンクで読み取り、 拡張子をハッシュに含める。大きなファイルでもメモリ効率が良い。 from_path_stat() は mtime + size のみでハッシュし、 ファイル内容の読み取りを避ける高速版。ファイル不在時は "MISSING_{filepath}" を返し、不在自体をキーに反映する。

実装メモ

Ref: src/beautyspot/cachekey.py
2.5 再帰的正規化 (canonicalize) IMPL005
KEY

実装概要

canonicalize()functools.singledispatch で実装された再帰的正規化関数。 型ごとにディスパッチハンドラを登録し、ネストされたデータ構造を再帰的に正規化する。 各ハンドラは型タグ付きのタプルを返し、異なるコレクション型が同じ内容でも 区別されるようにする。

設計判断

singledispatch の採用

if/elif チェーンと比較して、新しい型のサポート追加が局所的で、 既存コードへの影響がない。numpy や Pydantic のようなオプショナル依存の 型ハンドラも、条件付きで登録できる。

型タグによるコレクション区別

[1, 2](list)と (1, 2)(tuple)を区別するため、 正規化結果に ("__list__", ...), ("__tuple__", ...) のように 型タグを含める。これにより、構造的に同一でも型が異なるデータが 異なるキャッシュキーを生成する。

bool vs int の区別

Python では True == 1 かつ isinstance(True, int) だが、 キャッシュキーとしては区別が必要。singledispatch は MRO 順でマッチするため、 boolint より先にチェックするインライン処理を base handler に配置した。

実装メモ

Ref: src/beautyspot/cachekey.py
2.6 KeyGenPolicy 実装 IMPL006
KEY

実装概要

Strategy enum(DEFAULT, IGNORE, FILE_CONTENT, PATH_STAT の4種)と KeyGenPolicy クラスで構成。KeyGenPolicy は引数名→戦略のマッピングを保持し、 bind(func)inspect.signature() を使って関数シグネチャにバインドされた キー生成関数を返す。ファクトリメソッド KeyGen.ignore(), KeyGen.map(), KeyGen.file_content(), KeyGen.path_stat() で宣言的にポリシーを構築できる。

設計判断

bind() によるシグネチャバインディング

デコレーション時に inspect.signature(func) を呼び、 位置引数・キーワード引数・デフォルト値を解決する。 これにより、呼び出し時にはシグネチャ解析のオーバーヘッドがなくなる。

Strategy enum による宣言的指定

引数ごとの戦略を enum で宣言する方式を採用。 関数を渡す方式(keygen=lambda ...)と比較して、 シリアライズ可能で、デバッグ時の可読性が高い。

実装メモ

Ref: src/beautyspot/cachekey.py
2.7 SQLiteTaskDB 実装 IMPL007
DB

実装概要

SQLiteTaskDB クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の _writer_thread(デーモン)が _write_queue から書き込みタスクを取り出して 直列処理し、読み取りは threading.local() のスレッドローカル接続で並行実行する。 WAL モードにより読み取りと書き込みが並行可能。

設計判断

専用ライタースレッドの採用

SQLite は単一書き込み接続しかサポートしないため、全書き込みを1つのスレッドに 集約する Producer-Consumer パターンを採用。キュー経由でタスクを受け渡し、 ライタースレッドがコミット/ロールバックを管理する。 マルチスレッドからの並行書き込みによるロック競合を根本的に回避する。

スレッドローカル読み取り接続

threading.local()_ReadConnWrapper を格納し、スレッドごとに 読み取り専用接続(PRAGMA query_only = ON)を保持する。 接続エラー時は自動的に再作成し、リーク防止のため WeakSet で全接続を追跡する。

_WriteTask 状態マシン

書き込みタスクを PENDING → RUNNING → DONE(または CANCELLED)の 状態マシンで管理。タイムアウト時の try_cancel() により、 キュー内で待機中のタスクを安全にキャンセルできる。 既に RUNNING 状態のタスクはキャンセルせず完了を待つ。

実装メモ

Ref: src/beautyspot/db.py
2.8 DB プロトコル階層実装 IMPL008
DB

実装概要

DB 層のプロトコル階層を定義。TaskDBCore(ランタイム必須の CRUD)、 Flushable(書き込み同期)、Shutdownable(安全な停止)、 Maintenable(GC・統計等の管理操作)の4つのプロトコルと、 それらを統合した TaskDBMaintenable を提供する。 TaskDBBase は ABC として Maintenable のデフォルト実装(安全な no-op)を提供する。

設計判断

プロトコルの細分化

単一の巨大インターフェースではなく、責務ごとにプロトコルを分割した。 これにより、カスタム DB 実装は TaskDBCore のみ実装すればランタイムで動作し、 メンテナンス機能は段階的にオプトインできる。

デフォルト実装の安全性

TaskDBBase のメンテナンスメソッド(delete_expired(), prune() 等)は デフォルトで空リスト返却や no-op を行い、サブクラスが未実装でも安全に動作する。 CLI の gc コマンドが未対応の DB バックエンドでもエラーにならない設計。

実装メモ

Ref: src/beautyspot/db.py
2.9 LocalStorage 実装 IMPL009
STORE

実装概要

設計判断

アトミック書き込みパターン

一時ファイルに書き込み → fsync でディスクに確実に反映 → os.replace で アトミックにリネーム。この3段階により、書き込み中のクラッシュや 並行アクセスでファイルが半壊状態になることを防ぐ。 一時ファイルには .spot_tmp 接尾辞を使用し、残存時のクリーンアップを容易にした。

パストラバーサル防止の二重ガード

実装メモ

Ref: src/beautyspot/storage.py
2.10 S3Storage 実装 IMPL010
STORE

実装概要

S3Storage クラスが BlobStorageBase を実装。 s3://bucket/prefix/ 形式の URI を解析し、boto3 の S3 クライアントを使用。 ファイルは {prefix}/{key}.bin のキーで S3 に保存される。

設計判断

オプショナル依存のガード

boto3 は import 時にガードし、未インストール時は クラス定義は成功するがインスタンス化で明確なエラーメッセージを表示する。 beautyspot 全体が boto3 に依存しないよう、遅延インポートパターンを採用。

URI ベースのバックエンド選択

s3://bucket/prefix 形式の URI から bucket と prefix を自動解析する _parse_s3_uri() ヘルパーにより、ファクトリ関数がスキームに基づいて LocalStorage と S3Storage を透過的に切り替えられる。

実装メモ

Ref: src/beautyspot/storage.py
2.11 ストレージポリシー実装 IMPL011
STORE

実装概要

StoragePolicyProtocolshould_save_as_blob(data: bytes) -> bool を定義。 3つの実装を提供: - ThresholdStoragePolicy: len(data) > threshold で判定 - WarningOnlyPolicy: 常に False を返し、閾値超過時に WARNING ログを出力 - AlwaysBlobPolicy: 常に True を返す

設計判断

Strategy パターンによるポリシー分離

保存先判定ロジックを core.Spot から分離し、差し替え可能なポリシーオブジェクトに 委譲する。@mark(save_blob=True/False) の明示指定はポリシーより優先されるが、 save_blob=None(デフォルト)時にポリシーが判定を行う。

WarningOnlyPolicy をデフォルトにした理由

v2.0 との後方互換性を維持するため。v2.0 では全データが DB 直接保存だったため、 デフォルトを ThresholdStoragePolicy にすると既存ユーザーの挙動が変わる。 WARNING ログにより、ユーザーに Blob 分離の恩恵を段階的に周知する。

実装メモ

Ref: src/beautyspot/storage.py
2.12 MsgpackSerializer 実装 IMPL012
SERIAL

実装概要

MsgpackSerializer クラスが SerializerProtocolTypeRegistryProtocol を実装。 dumps(obj) -> bytes / loads(data) -> Any が主要 API。 カスタム型は msgpack の ExtType(code, payload) で拡張する。 _default_packer() がエンコード時のカスタム型ディスパッチ、 _ext_hook() がデコード時の型復元を行う。

設計判断

Copy-on-Write レジストリ

_encoders / _decoders 辞書は register() 時に新しい辞書を作成して アトミックに差し替える(CoW)。読み取り側はスナップショットを参照するため、 ロックなしで安全に読める。GIL に依存せず、PEP 703(free-threading)にも 対応できる設計。世代カウンタ _registry_generationレジストリ差し替えの前に インクリメントすることで、読み取り側が古いキャッシュを使い続けることを防ぐ。

スレッドローカル LRU キャッシュ

MRO スキャンの結果(サブクラス→登録済み親クラスの解決)を threading.local()OrderedDict にキャッシュする。_cache_generation_registry_generation を比較し、 世代が変わったらキャッシュを破棄する。LRU のサイズ上限は max_cache_size で制御。

MRO スキャンによるサブクラス自動解決

Pydantic モデルのサブクラスのように、親クラスが登録済みなら サブクラスも自動的に同じエンコーダで処理できる。type(obj).__mro__ を走査し、 最初にマッチした登録型のエンコーダを使用する。

実装メモ

Ref: src/beautyspot/serializer.py
2.13 カスタム型登録実装 IMPL013
SERIAL

実装概要

2つのレイヤーで構成: - MsgpackSerializer.register(type, code, encoder, decoder): 低レベルの型登録 API - Spot.register() / Spot.register_type(): ユーザー向けの高レベル API

Spot.register() はデコレータ形式、Spot.register_type() は命令形式で、 いずれも内部で serializer.register() に委譲する(TypeRegistryProtocol 準拠時のみ)。

設計判断

二層 API の提供

デコレータ形式 @spot.register(code=1, ...) はクラス定義と同時に登録でき、 宣言的で読みやすい。命令形式 spot.register_type(MyClass, code=1, ...) は 外部ライブラリのクラスなどデコレートできない場合に使用する。

TypeRegistryProtocol による条件分岐

シリアライザが TypeRegistryProtocol を実装していない場合(カスタムシリアライザ)、 register() / register_type()ConfigurationError を送出する。 これにより、型登録非対応のシリアライザを使用する場合のエラーメッセージが明確になる。

実装メモ

Ref: src/beautyspot/serializer.py, src/beautyspot/core.py
2.14 BackgroundLoop 実装 IMPL014
BGIO

実装概要

_BackgroundLoop クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に asyncio.new_event_loop() で新しいイベントループを作成し、 _thread(デーモンスレッド)上で loop.run_forever() を実行する。 外部からは submit() を通じてコルーチンを投入でき、run_coroutine_threadsafe で スレッドセーフにループへ渡される。

設計判断

デーモンスレッドと明示的なシャットダウン

スレッドは daemon=True とし、メインスレッド終了時にプロセスがブロックされないようにしている。 しかし、安全なリソース解放のために drain() メソッドを提供し、 投入された全タスクの完了を drain_timeout の範囲で待機する。

独立したイベントループ

メインスレッドのイベントループと競合しないよう、専用のイベントループを 作成してバックグラウンドスレッドで駆動する。これにより save_sync=False 時の 保存処理などが、呼び出し元の asyncio 環境から完全に隔離される。

実装メモ

Ref: src/beautyspot/core.py
2.15 save_sync / flush / drain 実装 IMPL015
BGIO

実装概要

Spot クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 save_sync=False の場合、キャッシュへの保存処理は _bg_loop.submit() で バックグラウンドに投入される。これら未完了のタスクやDBキューを同期するため、 flush() およびコンテキストマネージャによる drain が実装されている。

設計判断

flush と drain の使い分け

バックグラウンドエラーの隔離

バックグラウンド保存時のエラーは呼び出し元のメインフローを妨げないよう、 on_background_error コールバックに通知され、ログ出力のみ行う。 例外の再送出は行わない。

実装メモ

Ref: src/beautyspot/core.py
2.16 LifecyclePolicy 実装 IMPL016
LIFE

実装概要

LifecyclePolicy クラスは Rule オブジェクトのリストを保持し、 関数名に基づいてキャッシュの保持期間を決定する。 resolve() メソッドが fnmatch.fnmatch() を使って関数名をパターンと照合し、 最初にマッチしたルールの保持期間を返す。

設計判断

First-Match セマンティクス

ルールのリストは順序が意味を持ち、最初にマッチしたものが採用される。 これにより、特定プレフィックスの関数には短い保持期間を設定し、 最後に *(ワイルドカード)でデフォルトポリシーを設定するような フォールバック構造が簡単に記述できる。

resolve_with_fallback の導入

後方互換性と柔軟性のため、まず func_identifier (モジュール名付きの完全修飾名) で マッチングを試み、マッチしなかった場合は func_name (短い関数名) で再度マッチングを 試みる resolve_with_fallback() を提供している。

default_retention の導入

LifecyclePolicy コンストラクタに default_retention パラメータを追加。 どのルールにもマッチしない場合に返す保持期間を指定できる。 LifecyclePolicy.default()default_retention="30d" で生成し、 デフォルトで30日の保持期間を設定する。

実装メモ

Ref: src/beautyspot/lifecycle.py
2.17 Retention 実装 IMPL017
LIFE

実装概要

Retention クラスを名前空間として使用し、保持期間のパースや 特殊な定数(INDEFINITE, FOREVER)を管理する。 parse_retention() 関数は文字列("7d", "12h"等)、timedelta、 または秒数(int/float)を受け取り、標準化された timedelta オブジェクトに変換する。

設計判断

_ForeverSentinel シングルトン

Retention.FOREVER はポリシーを強制的にバイパスするための特殊値。 PEP 703 (free-threading) 環境での安全性を考慮し、 _ForeverSentinelthreading.Lock を用いたダブルチェックロッキングで 厳密なシングルトンとして実装されている。

直感的な文字列表現のサポート

"7d", "12h", "30m", "10s" のような文字列フォーマットを 正規表現 _TIME_PATTERN でパースすることで、設定ファイルや ハードコード時の可読性を高めている。

実装メモ

Ref: src/beautyspot/lifecycle.py
2.18 TokenBucket 実装 IMPL018
LIMIT

実装概要

LimiterProtocol を実装する TokenBucket クラス。 GCRA (Generic Cell Rate Algorithm) に基づき、スレッドセーフおよび 非同期対応のスムーズなレートリミッタを提供する。 内部状態として Theoretical Arrival Time (TAT) を保持し、 consume() で同期的スリープ、consume_async() で非同期的スリープを行う。

設計判断

GCRAアルゴリズムの採用

伝統的なトークンバケットとは異なり、長時間アイドル後に 一気にバーストを許容しない「Strict Pacing」を実現できる。 TATの更新と現在時刻の比較だけで待機時間を計算できるため、 メモリ効率と計算効率に優れる。

モノトニッククロックの利用

時刻の取得に time.monotonic() を使用し、システム時刻の変更(NTP同期など)の 影響を受けない堅牢な設計としている。

実装メモ

Ref: src/beautyspot/limiter.py
2.19 HookBase 実装 IMPL019
HOOK

実装概要

タスク実行ライフサイクルに介入するインターフェース HookBase と、 そのスレッドセーフ版 ThreadSafeHookBase の実装。 pre_execute, on_cache_hit, on_cache_miss の各コールバックが提供される。 ThreadSafeHookBase__init_subclass__ を利用し、サブクラスで定義された フックメソッドを自動的にロックでラップする。

設計判断

init_subclass による透過的ロッキング

ユーザーが手動でロックを書く手間を省くため、メタクラス的なアプローチを採用。 クラス定義時にフックメソッドを抽出し、_wrap_with_lock デコレータで包むことで、 ユーザーコードを汚さずに完全な排他制御を実現している。

RLock(再入可能ロック)の使用

当初の Lock から RLock に変更された。 サブクラスが super().pre_execute(...) のように親のメソッドを呼び出した際、 同一スレッドが再度ロックを取得しようとしてデッドロックする問題を防ぐため。

実装メモ

Ref: src/beautyspot/hooks.py
2.20 MaintenanceService 実装 IMPL020
MAINT

実装概要

MaintenanceService クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出といったガベージコレクション(GC)を担当。 主にCLIの beautyspot gc コマンドから呼び出される。 対象となる DB (TaskDBMaintenable) とストレージ (BlobStorageMaintenable) を受け取り、 複数のフェーズに分けてクリーンアップを実行する。

設計判断

5フェーズのクリーンアッププロセス

clean_garbage() は以下の順序で安全にGCを実行する: 1. 期限切れDBレコードの削除 2. Blobストレージの一時ファイル(.spot_tmp)のクリーンアップ 3. 孤立したBlobファイル(DBに存在しないファイル)の特定 4. 孤立ファイルの削除 5. 空ディレクトリの剪定

Grace Period (猶予期間) の導入

実行中のタスクがBlobを書き込んだ直後で、まだDBにメタデータが保存されていない タイミングでGCが走ると、必要なファイルが孤立と誤認されるリスクがある。 これを防ぐため、orphan_grace_seconds(デフォルト60秒)より新しいファイルは 孤立判定から除外する設計とした。

実装メモ

Ref: src/beautyspot/maintenance.py
2.21 CLI コマンド実装 IMPL021
CLI

実装概要

Typer を利用したコマンドラインインターフェースの実装。 list, show, stats, clear, clean, gc, prune, version 等の コマンドを提供し、rich ライブラリを用いてコンソール出力(テーブル、パネル、 プログレスバー等)をリッチにフォーマットしている。

設計判断

Typer と Rich の組み合わせ

Typer は型ヒントベースでコマンドやオプションを定義でき、コードの記述量と メンテナンスコストを大幅に削減できる。Rich による出力は、単なるテキストではなく 構造化された情報(JSONやMarkdown)の視認性を劇的に向上させ、DXを高める。

安全性を考慮した対話的プロンプト

clearclean など、データを大規模に削除するコマンドについては、 --force オプションが指定されない限り、rich.prompt.Confirm を用いて ユーザーに明示的な確認を求めることで、誤操作によるデータ喪失を防いでいる。

実装メモ

Ref: src/beautyspot/cli.py
2.22 ファクトリ関数実装 IMPL022
DI

実装概要

beautyspot パッケージのメインエントリポイントとなる Spot ファクトリ関数の実装。 引数として渡された各コンポーネント(DB、Serializer、Storage等)を依存性注入(DI)で 解決し、デフォルトのコンポーネント(SQLiteTaskDB, MsgpackSerializer, LocalStorage等)を インスタンス化して CacheManager_Spot コアエンジンを組み立てる。

設計判断

Factory 関数による Composition

_Spot クラス自体のコンストラクタは複雑な依存関係を要求するが、 ユーザー向けに Spot() 関数を提供することで、通常は name を渡すだけで 「ゼロ設定」で動作するようにカプセル化している。 同時に、高度なユーザーは各コンポーネントを自由に差し替え可能な DI アーキテクチャを維持している。

DB ライフサイクルの委譲

Spot() 関数内でデフォルトのDB(SQLiteTaskDB)を自動生成した場合、 spot._owns_db = True フラグを立て、Spotエンジンのシャットダウン時に DBも自動でクローズされるようにする。一方、ユーザーが明示的に db= を 渡した場合は、DBのライフサイクル管理は呼び出し元に委ねる(勝手に閉じない)。

実装メモ

Ref: src/beautyspot/__init__.py
2.23 Thundering Herd 実装 IMPL023
HERD

実装概要

CacheManager クラス内で、Thundering Herd(キャッシュミス時に同一キーへの 大量アクセスが同時に発生する問題)を防止する直列化機構を実装。 _inflight 辞書で実行中のキーを管理し、最初の1スレッド/タスクだけが関数を実行し、 後続の呼び出しは wait_herd_sync / wait_herd_async でその完了を待機する。

設計判断

同期・非同期の統合的保護

_inflight 辞書の値として (threading.Event, list[asyncio.Future], list[result]) の タプルを保持し、スレッドベースの待機 (Event.wait) と asyncio ベースの待機 (Future) の 両方を単一の管理下で混在できるようにしている。

タイムアウトと再試行(自己回復)

関数の実行がハングアップした場合に待機側が永遠にブロックされるのを防ぐため、 HERD_TIMEOUT(300秒)と HERD_MAX_RETRIES(3回)を設定。 タイムアウト時には警告ログを出しつつ再試行し、上限を超えれば TimeoutError で フェイルファストする堅牢な設計とした。

実装メモ

Ref: src/beautyspot/cache.py
2.24 BlobStorageBase 抽象インターフェース実装 IMPL024
STORE

実装概要

storage.py 内の BlobStorageBase ABC が SPEC024 の中核実装。 5つの抽象メソッド(save, load, delete, list_keys, get_mtime)を定義し、 LocalStorageS3Storage が具象実装として継承する。

加えて、ランタイム型チェックのために BlobStorageCoreMaintenableBlobStorageMaintenable の3つの Protocol クラスを定義し、 isinstance() での型判定を可能にしている(@runtime_checkable)。

設計判断

ABC と Protocol の併用

BlobStorageBase は ABC として抽象メソッドを強制する一方、 BlobStorageCore / Maintenable は Protocol として構造的部分型を提供する。 これにより、BlobStorageBase を継承しないサードパーティ実装でも isinstance(obj, BlobStorageCore) で利用可能になる柔軟性を確保した。

3層の Protocol 分割

これにより、save/load/delete のみを実装した軽量バックエンドも受け入れ可能。

ReadableBuffer 型エイリアス

bytes | bytearray | memoryviewReadableBuffer として定義。 memoryview を受け入れることでゼロコピー書き込みが可能になり、 大きなデータを保存する際のメモリ効率を改善している。

実装メモ

Ref: src/beautyspot/storage.py