SPEC Specification

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

1.0 序文 SPEC025

本ドキュメントは、『beautyspot』の各機能における詳細な仕様、インターフェース、およびアルゴリズムを定義する。

2.0 詳細仕様 SPEC026

各コンポーネントが提供するAPI、内部状態遷移、および例外ハンドリングの詳細を定義する。

2.1 mark デコレータ SPEC001
CACHE

インターフェース

@spot.mark(
    save_blob: Optional[bool] = None,
    keygen: Optional[Union[Callable, KeyGenPolicy]] = None,
    input_key_fn: Optional[Union[Callable, KeyGenPolicy]] = None,
    version: str | None = None,
    content_type: Optional[str | ContentType] = None,
    serializer: Optional[SerializerProtocol] = None,
    save_sync: Optional[bool] = None,
    retention: RetentionSpec = None,
    hooks: Optional[Sequence[HookBase]] = None,
)
def my_func(x, y): ...

振る舞い

基本フロー

  1. デコレータが対象関数をラップする
  2. 呼び出し時にキャッシュキーを生成する
  3. DBからキャッシュを検索する
  4. ヒット時: デシリアライズして結果を返す(関数は実行しない)
  5. ミス時: 関数を実行し、結果をシリアライズしてDB/Blobに保存する

sync/async 自動判定

パラメータ詳細

パラメータ デフォルト 説明
save_blob None None: StoragePolicy に委譲、True: 強制Blob保存、False: DB内保存
keygen None キャッシュキー生成のカスタマイズ。引数やポリシーを指定可能
input_key_fn None (非推奨)keygen を使用すること
version None バージョン文字列。変更するとキャッシュキーが変わり無効化される
content_type None 戻り値のMIMEタイプ
serializer None None: Spotのデフォルトを使用。関数単位でオーバーライド可能
save_sync None 保存処理を同期的に行うかどうか(Noneの場合はデフォルトに従う)
retention None キャッシュの保持ポリシー("30d" などの文字列や期間オブジェクト等)
hooks None 関数単位のフックリスト(実行前後やキャッシュヒット時に発火)

エラーハンドリング

エッジケース

2.2 cached_run コンテキストマネージャ SPEC002
CACHE

インターフェース

@contextmanager
def cached_run(
    self,
    *funcs: Any,
    **kwargs: Any
) -> Iterator[Any]: ...

振る舞い

基本フロー

  1. Spot インスタンスからコンテキストマネージャとして呼び出される。
  2. 引数 funcs に渡された関数が0個の場合はエラーを送出する。
  3. 各関数に対して、Spot.mark(**kwargs) で生成したキャッシュデコレータを適用し、ラップされた関数を生成する。
  4. ラップされた関数を yield してコンテキストブロック内のユーザーコードに提供する。
  5. ユーザーコード内での実行は、通常の @spot.mark 適用済み関数と同様にキャッシュ機構を経由する。
  6. コンテキストブロック終了時のクリーンアップ処理は特に行わない(一時的なラッパーはスコープを抜けると破棄される)。

パラメータ詳細

パラメータ 必須 説明
*funcs はい キャッシュ機能を適用したい対象の関数。外部ライブラリの関数なども指定可能。複数指定可能。
**kwargs いいえ Spot.mark デコレータに渡すオプション引数(save_blob, keygen, version, content_type, serializer, retention, save_sync, hooks 等)。

戻り値(Yields)の仕様

エラーハンドリング

エッジケース

2.3 同期・非同期関数サポート SPEC003
CACHE

概要

@spot.mark() デコレータは同期関数と非同期(async def)関数の両方をサポートし、関数定義に応じて適切な実行・保存・キャッシュ検索フローを透過的に切り替える。

振る舞い

sync/async の自動判定

実行フロー

エラーハンドリング

エッジケース

2.4 SHA-256キャッシュキー SPEC004
KEY

インターフェース

class KeyGen:
    @staticmethod
    def _default(args: tuple, kwargs: dict) -> str: ...

    @staticmethod
    def hash_items(items: list) -> str: ...

振る舞い

基本フロー (_default)

  1. 正規化: argskwargscanonicalize() により再帰的に正規化する
  2. パック: 正規化された構造 [args, kwargs] を MessagePack でバイナリにシリアライズする
  3. ハッシュ: バイナリデータに対して SHA-256 ハッシュを計算し、16進数文字列を返す

リストハッシュ (hash_items)

パラメータ詳細

パラメータ 説明
args tuple 関数の位置引数
kwargs dict 関数のキーワード引数
items list バインド済み引数の正規化値リスト

エラーハンドリング

エッジケース

2.5 引数の再帰的正規化 SPEC005
KEY

インターフェース

@singledispatch
def canonicalize(obj: Any) -> Any: ...

振る舞い

singledispatch を使用し、型に応じた最適な正規化形式へ再帰的に変換する。

主要な型別の処理

パラメータ詳細

オブジェクト型 正規化後の形式 備考
numpy.ndarray ("__numpy__", shape, dtype, bytes) 高速かつ正確なハッシュを保証
OrderedDict ("__ordered_dict__", items) 挿入順序を保持したまま正規化
Pydantic Model ("__pydantic_v2__", schema) スキーマ情報をベースに判定

エラーハンドリング

エッジケース

2.6 KeyGenPolicy 戦略 SPEC006
KEY

インターフェース

class Strategy(Enum):
    DEFAULT = auto()      # 再帰的正規化
    IGNORE = auto()       # キー計算から除外
    FILE_CONTENT = auto() # ファイルの内容をハッシュ
    PATH_STAT = auto()    # パス+サイズ+更新時刻をハッシュ

class KeyGenPolicy:
    def bind(self, func: Callable) -> Callable[..., str]: ...

振る舞い

  1. バインド: bind(func) 時に inspect.signature を解析し、引数名とデフォルト値を把握する
  2. 適用: 呼び出し時に引数を実値にバインドし、デフォルト値を補填する
  3. 戦略実行: 引数名ごとに指定された Strategy を適用し、正規化された値のリストを作成する
  4. 集約: 全ての値を KeyGen.hash_items() で一つのハッシュにまとめる

パラメータ詳細

戦略 内容 用途
IGNORE キャッシュキーに含めない verboselogger 等、結果に影響しない引数
FILE_CONTENT ファイルを全読込してハッシュ化 入力ファイルの厳密な変更検知
PATH_STAT os.stat 情報を使用 大容量ファイルの高速な変更検知

エラーハンドリング

エッジケース

2.7 SQLiteTaskDB SPEC007
DB

インターフェース

class SQLiteTaskDB(TaskDBBase):
    def __init__(self, db_path: str | Path, timeout: float = 30.0): ...
    def save(self, task: TaskRecord) -> None: ...
    def get(self, input_key: str) -> TaskRecord | None: ...

振る舞い

ライタースレッド方式

読み取り並行性

スキーマ管理

パラメータ詳細

設定 デフォルト 説明
journal_mode WAL 書き込みと読み取りの並行性を高める設定
synchronous NORMAL パフォーマンスと耐久性のバランス設定
timeout 30.0 データベースロック待機時間の閾値

エラーハンドリング

エッジケース

2.8 DB プロトコル階層 SPEC008
DB

インターフェース

@runtime_checkable
class TaskDBCore(Protocol):
    def get(self, input_key: str) -> TaskRecord | None: ...
    def save(self, record: TaskRecord) -> None: ...

class TaskDBBase(ABC):
    # TaskDBCore を満たしつつ、メンテナンスのデフォルト実装を提供

振る舞い

階層構造

  1. TaskDBCore: キャッシュの実行に必要な最小限のメソッド定義
  2. Maintenable: GCや統計取得に必要な管理用メソッドの定義
  3. TaskDBBase: 共通のバリデーションや例外処理を実装する基底クラス

パラメータ詳細 (主要メソッド)

メソッド 役割
get_blob_refs() 全レコードの blob_key を列挙する(GC用)
delete_expired() expires_at を経過したレコードを一括削除する
get_history() 実行履歴(統計)を取得する

エラーハンドリング

エッジケース

2.9 LocalStorage SPEC009
STORE

インターフェース

class LocalStorage(BlobStorageMaintenable):
    def __init__(self, base_dir: str | Path): ...
    def save(self, key: str, data: bytes) -> str: ...
    def load(self, location: str) -> bytes: ...

振る舞い

アトミック書き込み (save)

  1. 指定ディレクトリ内に tempfile.mkstemp で一時ファイルを作成
  2. データを書き込み、flush() および os.fsync() でディスク到達を保証
  3. os.replace で最終的なパスへリネーム(アトミックな置換)

パストラバーサル対策 (load / delete)

パラメータ詳細

パラメータ 説明 制限
key 保存ファイル名のベース ../ を含む場合は ValidationError
location save が返した識別子 相対パス形式を推奨

エラーハンドリング

エッジケース

2.10 S3Storage SPEC010
STORE

インターフェース

class S3Storage(BlobStorageMaintenable):
    def __init__(self, s3_uri: str, s3_opts: dict | None = None): ...

振る舞い

URIベースの管理

大容量対応 (save)

高速なメタデータ取得 (get_mtime)

パラメータ詳細

設定 内容
s3_uri s3://my-bucket/my-prefix 形式
s3_opts boto3.client('s3', **s3_opts) に渡される設定

エラーハンドリング

エッジケース

2.11 ストレージポリシー SPEC011
STORE

インターフェース

class StoragePolicyProtocol(Protocol):
    def should_save_as_blob(self, data: bytes) -> bool: ...

振る舞い

判定ロジック

パラメータ詳細

ポリシー型 パラメータ デフォルト 説明
Threshold threshold なし バイト単位の閾値。10MB等を推奨
Warning warning_threshold なし 警告を出すサイズ閾値

エラーハンドリング

エッジケース

2.12 MsgpackSerializer SPEC012
SERIAL

インターフェース

class MsgpackSerializer(SerializerProtocol, TypeRegistryProtocol):
    def dumps(self, obj: Any) -> bytes: ...
    def loads(self, data: bytes) -> Any: ...

振る舞い

シリアライズ (dumps)

  1. オブジェクトの型を type(obj) で取得
  2. 登録済み型またはそのサブクラス(MRO順)を探索
  3. ヒットした場合、カスタムエンコーダを実行し ExtType としてパック
  4. ヒットしない場合、MessagePack のデフォルト処理(または SerializationError

デシリアライズ (loads)

  1. MessagePack ストリームをパース
  2. ExtType を発見した場合、コードに対応するカスタムデコーダを呼び出す

パラメータ詳細

設定 デフォルト 説明
max_cache_size 1024 スレッドごとの型解決LRUキャッシュの最大サイズ

エラーハンドリング

エッジケース

2.13 カスタム型登録 SPEC013
SERIAL

インターフェース

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: ...

振る舞い

  1. 重複チェック: 同一コードまたは同一クラスが既に登録されていないか確認
  2. CoW更新: 現在のレジストリ辞書をコピーし、新しいエントリを追加した後に参照を差し替える
  3. 世代更新: _registry_generation をインクリメントし、全スレッドのLRUキャッシュを無効化対象とする

パラメータ詳細

パラメータ 制限 説明
code 0127 MessagePack ExtType のユーザー定義領域コード
encoder 引数1、戻り値シリアライズ可能 オブジェクトをプリミティブ構造へ変換する関数
decoder 引数1、戻り値オブジェクト プリミティブ構造からオブジェクトを復元する関数

エラーハンドリング

エッジケース

2.14 BackgroundLoop SPEC014
BGIO

インターフェース

class _BackgroundLoop:
    def submit(self, coro: Coroutine) -> None: ...
    def stop(self) -> None: ...
    def is_running(self) -> bool: ...

振る舞い

  1. 起動: 初回の submit 時にデーモンスレッドを作成し、asyncio.run() を開始する
  2. 投入: run_coroutine_threadsafe を使用して、メインスレッドからコルーチンをイベントループへ投入
  3. 待機: 投入されたタスクの完了は待たず、制御を即座に呼び出し元へ返す

パラメータ詳細

内部属性 説明
_loop AbstractEventLoop スレッド内で稼働するループ本体
_thread Thread daemon=True 設定のスレッド

エラーハンドリング

エッジケース

2.15 save_sync / flush / drain SPEC015
BGIO

インターフェース

def flush(timeout: float | None = None) -> bool: ...

@spot.mark(save_sync=False)
def my_func(): ...

振る舞い

同期保存 (save_sync=True)

非同期保存 (save_sync=False)

フラッシュ (flush)

パラメータ詳細

パラメータ デフォルト 説明
timeout None flush の最大待機時間(秒)。None は無限待機
on_background_error logger.error 非同期保存失敗時のエラーハンドラ

エラーハンドリング

エッジケース

2.16 LifecyclePolicy SPEC016
LIFE

インターフェース

class LifecyclePolicy:
    def add_rule(self, pattern: str, retention: Any) -> None: ...
    def resolve_with_fallback(self, func_name: str) -> float | None: ...

振る舞い

  1. 順次照合: 登録された順にルールを走査する

  2. パターンマッチ: fnmatch.fnmatch を使用し、関数名がパターンに合致するか判定

  3. 二段階解決:

    1. 最初に「完全修飾名 (module.qualname)」で照合

    2. マッチしなければ「短縮名 (qualname)」で照合

  4. 結果返却: 最初にマッチしたルールの保持期間を秒数に変換して返す

パラメータ詳細

パラメータ例説明pattern"my_mod.*", "*_test"Glob形式のパターンretention"30d", 3600保持期間の定義

デフォルト保持期間

エラーハンドリング

エッジケース

2.17 Retention 仕様 SPEC017
LIFE

インターフェース

def parse_retention(val: Any) -> float | None: ...

振る舞い

文字列パース

数値・オブジェクト

パラメータ詳細 (単位)

単位 倍率 (秒) 備考
d 86400 日数
h 3600 時間
m 60
s 1

エラーハンドリング

エッジケース

2.18 TokenBucket (GCRA) SPEC018
LIMIT

インターフェース

class TokenBucket(LimiterProtocol):
    def consume(self, cost: float = 1.0) -> None: ...
    async def consume_async(self, cost: float = 1.0) -> None: ...

振る舞い

GCRAアルゴリズム

同期 vs 非同期

パラメータ詳細

設定 デフォルト 説明
tokens_per_minute 必須 1分間に許可する平均リクエスト数(コスト合計)
max_burst 1.0 許容されるバースト数(トークン単位)

エラーハンドリング

エッジケース

2.19 HookBase / ThreadSafeHookBase SPEC019
HOOK

インターフェース

class HookBase:
    def pre_execute(self, ctx: PreExecuteContext) -> None: ...
    def on_cache_hit(self, ctx: CacheHitContext) -> None: ...
    def on_cache_miss(self, ctx: CacheMissContext) -> None: ...

振る舞い

  1. フック呼出: キャッシュエンジンの各ステート(実行前、ヒット、ミス)で登録されたフックを順番に実行する
  2. スレッド安全化: ThreadSafeHookBase を継承した場合、メタクラスが自動的に各メソッドを with self._lock: で包む

パラメータ詳細 (Context)

コンテキスト 保持データ
PreExecuteContext func, args, kwargs, cache_key
CacheHitContext cache_key, result, metadata
CacheMissContext cache_key, result, execution_time

エラーハンドリング

エッジケース

2.20 MaintenanceService SPEC020
MAINT

インターフェース

class MaintenanceService:
    def clean_garbage(self, grace_period: int = 3600) -> GarbageStats: ...
    def prune(self, days: int, func_name: str | None = None) -> int: ...
    def clear(self, func_name: str | None = None) -> int: ...

振る舞い

ガベージコレクション (clean_garbage)

  1. DB Flush: 保存中のタスクをDBへ反映させる
  2. 期限切れ削除: delete_expired() でDBから古いタスクを除去
  3. 孤立Blobスキャン: DB内の全 blob_key とストレージ内の全ファイルを比較
  4. 物理削除: 参照のないBlobのうち、作成から grace_period 以上経過したものを削除
  5. 構造清掃: ストレージ内の空ディレクトリを削除

パラメータ詳細

パラメータ デフォルト 説明
grace_period 3600 孤立Blobを削除するまでの猶予期間(秒)。並行実行中の保存を保護する
days なし prune で削除対象とする経過日数

エラーハンドリング

エッジケース

2.21 CLIコマンド SPEC021
CLI

インターフェース

beautyspot list [--db DB_PATH]
beautyspot gc [--name PROJECT_NAME] [--force]
beautyspot stats
beautyspot ui

振る舞い

  1. コンテキスト解決: --db 指定がない場合、デフォルトの .beautyspot/ ディレクトリを探索する
  2. コマンド実行: MaintenanceService または Spot インスタンスを生成し、対応するメソッドを呼び出す
  3. フォーマット出力: Rich ライブラリを使用して、結果をテーブルやプログレスバーで表示する

サブコマンド詳細

コマンド 説明
list 保存されているキャッシュキーと関数名の一覧を表示
gc 期限切れタスクと孤立Blobのクリーンアップを実行
stats キャッシュヒット率やストレージ使用量の統計を表示
ui ターミナルベースのインタラクティブダッシュボードを起動

エラーハンドリング

エッジケース

2.22 ファクトリ関数と Protocol SPEC022
DI

インターフェース

def Spot(
    name: str = "default",
    storage_path: str | Path | None = None,
    serializer: SerializerProtocol | None = None,
    limiter: LimiterProtocol | None = None,
    # ...その他の依存
) -> core.Spot: ...

振る舞い

  1. パス解決: storage_path が未指定の場合、.beautyspot/ 以下のプロジェクト名ディレクトリを使用する
  2. DB初期化: SQLiteTaskDB を生成し、マイグレーションを実行
  3. ストレージ構成: URI(s3://等)を判定し、適切な BlobStorage 実装を生成
  4. インスタンス化: 全てのコンポーネントを core.Spot のコンストラクタに注入して返す

パラメータ詳細

パラメータ デフォルト値の導出
db .beautyspot/{name}.db
blobs .beautyspot/blobs/{name}/
serializer MsgpackSerializer()
policy WarningOnlyPolicy (閾値10MB)

エラーハンドリング

エッジケース

2.23 Thundering Herd Protection SPEC023
HERD

インターフェース

class CacheManager:
    def get_or_create_inflight(
        self, key: str, is_async: bool
    ) -> ExecutionState: ...

振る舞い

同時実行制御

  1. キャッシュキーをキーとした _inflight 辞書をチェックする
  2. 存在しない場合: ExecutionState を新規作成し、自分が「実行者 (Executor)」となる
  3. 存在する場合: 既存の ExecutionState を返し、自分は「待機者 (Waiter)」となる
  4. 完了通知: 実行者が処理を終えたら、ExecutionState の Event (または Future) をセットし、結果を共有する

強参照によるインフライト状態の保持

目的

実行中のタスク状態(イベント・結果・Future)が GC によって消失しないよう、 _inflight 辞書を通じて強参照で保持する。WeakRef は使用しない。

データ構造

_inflight 辞書はキャッシュキーをキーとし、以下の3要素タプルを値として保持する:

_inflight: dict[str, tuple[threading.Event, list[asyncio.Future], list]]
要素 役割
event threading.Event 同期待機者への完了通知シグナル
futures list[asyncio.Future] 非同期待機者への結果配信チャネル
result_box list 結果の共有ボックス。[(success: bool, value: Any)] 形式

ライフサイクル

  1. 作成: wait_herd_sync / wait_herd_async_inflight にキーが存在しない場合、 _inflight_lock を保持した状態で新規タプルを挿入する
  2. 保持: 実行者が処理を完了するまで、_inflight 辞書がタプルへの唯一の管理参照を保持する。 待機者はロック取得時にタプル要素への参照を取得するが、_inflight エントリが正規の所有者である
  3. 解放: notify_and_cleanup_inflight で以下の順序で解放する: a. _inflight_lock を取得し、エントリの同一性を event の identity (is) で確認する b. 辞書からエントリを削除する(del _inflight[cache_key]) c. ロックを解放した後、event.set() で同期待機者に通知する d. futures リスト内の各 asyncio.Future に結果またはエラーを伝播する

不変条件

GC安全性

_inflight 辞書は CacheManager インスタンスの属性であり、CacheManager が 生存している限り、全てのインフライトエントリは GC の対象にならない。 CacheManager 自体は Spot インスタンスが所有するため、with spot: ブロック内での 安全性が保証される。

パラメータ詳細

内部定数 説明
HERD_TIMEOUT 300.0 待機者が実行者の完了を待つ最大秒数
HERD_MAX_RETRIES 3 実行者が失敗またはタイムアウトした際のリトライ回数

エラーハンドリング

エッジケース

2.24 Blobストレージ抽象インターフェース SPEC024
STORE

インターフェース

class BlobStorageBase(ABC):
    @abstractmethod
    def save(self, key: str, data: ReadableBuffer) -> str: ...
    @abstractmethod
    def load(self, location: str) -> bytes: ...
    @abstractmethod
    def delete(self, location: str) -> None: ...
    @abstractmethod
    def list_keys(self) -> Iterator[str]: ...
    @abstractmethod
    def get_mtime(self, location: str) -> float: ...

振る舞い

パラメータ詳細

パラメータ 説明
key str キャッシュキー(通常はハッシュ値)
data bytes / memoryview シリアライズ済みバイナリ
location str 実体へのポインタ(ファイルパス、S3 URI等)

エラーハンドリング

エッジケース