← 全体レポートに戻る

局所トレーサビリティビュー グループ: CACHE

生成日時: 2026-03-11 00:06:09

対象アイテム

11

REQ

1

ARCH

1

SPEC

3

TST

3

IMPL

3

レビュー済

11/11

Suspect

0

グループ

CACHE
アイテム: REQ001 ARCH001 SPEC001 SPEC002 SPEC003 TST001 TST002 TST003 IMPL001 IMPL002 IMPL003

トレーサビリティマトリクス

✓=レビュー済 ○=未レビュー ⚠=Suspect(複数同時表示あり。IDクリックで詳細へ)

グループREQARCHSPECTSTIMPL
CACHEREQ001
関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。
ARCH001
## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor...
SPEC001
## インターフェース ```python @spot.mark( save_blob: Optional[bool] = None, key...
TST001
## 目的 `@spot.mark()` はユーザーが最も頻繁に使う公開APIであり、キャッシュの正確性・透過性・拡張性の基盤となる。デコレーションによって元...
IMPL001
## 実装概要 `Spot.mark()` メソッドが本仕様の中核。二段階デコレータファクトリとして、 オプションを受け取る外側の `mark()` と、関数...
CACHEREQ001
関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。
ARCH001
## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor...
SPEC002
## インターフェース ```python @contextmanager def cached_run( self, *funcs: Any...
TST002
## 目的 `cached_run` はデコレータを直接付与できない外部ライブラリ関数にキャッシュを適用する唯一の手段であり、このAPIの不具合はサードパーテ...
IMPL002
## 実装概要 `Spot.cached_run()` はコンテキストマネージャとして実装。 渡された関数群を内部で `mark()` と同等のラッピングを行...
CACHEREQ001
関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。
ARCH001
## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor...
SPEC003
## 概要 `@spot.mark()` デコレータは同期関数と非同期(`async def`)関数の両方をサポートし、関数定義に応じて適切な実行・保存・キャ...
TST003
## 目的 beautyspot は同期・非同期の両方の関数を透過的にキャッシュするが、asyncio 固有のイベントループ制約(スレッドをまたぐコルーチン実...
IMPL003
## 実装概要 `Spot._execute_sync()` と `Spot._execute_async()` が実行エンジンの中核。 両メソッドは同一のフ...

カバレッジ(局所)

リンク方向カバー数カバー率未カバー
ARCH → REQ 1 / 1 100.0%
SPEC → ARCH 1 / 1 100.0%
TST → SPEC 3 / 3 100.0%
IMPL → SPEC 3 / 3 100.0%

アイテム詳細

REQ001 REQ {h(g)} ✓ レビュー済

関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。

親:

子: ARCH001

ARCH001 ARCH {h(g)} ✓ レビュー済

コンポーネント構成

graph TD
  A[bs.Spot Factory] -->|DI/Composition| B[core.Spot]
  B --> C[TaskDBBase]
  B --> D[SerializerProtocol]
  B --> E[BlobStorageBase]
  B --> F[StoragePolicyProtocol]
  B --> G[LimiterProtocol]
  B --> H[LifecyclePolicy]

コンポーネント責務

コンポーネント 責務 インターフェース
core.Spot キャッシュエンジン。キー生成→検索→実行→保存の制御 mark(), cached_run()
TaskDBBase メタデータ永続化。キャッシュキーによる検索と保存 find_by_key(), insert()
SerializerProtocol データのシリアライズ/デシリアライズ pack(), unpack()
BlobStorageBase 大規模データの外部ストレージ保存 save(), load(), delete()
StoragePolicyProtocol Blob保存の判定ポリシー should_save_as_blob()

データフロー

sequenceDiagram
  participant User as ユーザーコード
  participant Spot as core.Spot
  participant DB as TaskDB
  participant Ser as Serializer
  participant Blob as BlobStorage

  User->>Spot: fn(args)
  Spot->>DB: find_by_key(cache_key)
  alt キャッシュヒット
    DB-->>Spot: cached_data
    Spot->>Ser: unpack(data)
  else キャッシュミス
    Spot->>Spot: fn(args) 実行
    Spot->>Ser: pack(result)
    Spot->>DB: insert(metadata)
    opt Blob保存
      Spot->>Blob: save(key, data)
    end
  end
  Spot-->>User: result

技術選定

技術領域 選定 理由
メタデータDB SQLite ゼロ設定、組み込み可能、十分な性能
シリアライズ MessagePack JSONより高速・コンパクト、バイナリ対応
ストレージ ローカルファイル / クラウド 小規模はローカル、大規模は外部ストレージ等で拡張可能

非機能要件方針

親: REQ001

子: SPEC001, SPEC002, SPEC003

SPEC001 SPEC {h(g)} ✓ レビュー済

インターフェース

@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 関数単位のフックリスト(実行前後やキャッシュヒット時に発火)

エラーハンドリング

エッジケース

親: ARCH001

子: IMPL001, TST001

SPEC002 SPEC {h(g)} ✓ レビュー済

インターフェース

@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)の仕様

エラーハンドリング

エッジケース

親: ARCH001

子: IMPL002, TST002

SPEC003 SPEC {h(g)} ✓ レビュー済

概要

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

振る舞い

sync/async の自動判定

実行フロー

エラーハンドリング

エッジケース

親: ARCH001

子: IMPL003, TST003

TST001 TST {h(g)} ✓ レビュー済

目的

@spot.mark() はユーザーが最も頻繁に使う公開APIであり、キャッシュの正確性・透過性・拡張性の基盤となる。デコレーションによって元の関数の挙動やメタデータが損なわれると、ユーザーコードのデバッグや型チェックに支障をきたすため、ラッパーの透過性を厳密に検証する。

検証観点

references: tests/integration/core/test_basic.py

親: SPEC001

子:

TST002 TST {h(g)} ✓ レビュー済

目的

cached_run はデコレータを直接付与できない外部ライブラリ関数にキャッシュを適用する唯一の手段であり、このAPIの不具合はサードパーティ統合シナリオ全体に影響する。戻り値の型が関数の数で変わるスマートリターン仕様は、誤実装すると型安全性を損なうため重点的に検証する。

検証観点

references: tests/integration/core/test_cached_run.py

親: SPEC002

子:

TST003 TST {h(g)} ✓ レビュー済

目的

beautyspot は同期・非同期の両方の関数を透過的にキャッシュするが、asyncio 固有のイベントループ制約(スレッドをまたぐコルーチン実行、例外伝播のタイミング差異)により、同期版とは異なる不具合が発生しやすい。非同期パスの堅牢性を独立して検証する。

検証観点

references: tests/integration/core/test_async_save.py

親: SPEC003

子:

IMPL001 IMPL {h(g)} ✓ レビュー済

実装概要

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 / 技術的負債

references: src/beautyspot/core.py

親: SPEC001

子:

IMPL002 IMPL {h(g)} ✓ レビュー済

実装概要

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

設計判断

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

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

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

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

実装メモ

references: src/beautyspot/core.py

親: SPEC002

子:

IMPL003 IMPL {h(g)} ✓ レビュー済

実装概要

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 ログを出力するが、例外を再送出しない。 関数の実行自体は成功しているため、ユーザーに例外を見せるのは不適切と判断した。

実装メモ

references: src/beautyspot/core.py

親: SPEC003

子: