← 全体レポートに戻る

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

生成日時: 2026-03-13 23:59:51

対象アイテム

15

REQ

1

ARCH

1

SPEC

3

TST

3

IMPL

3

ADR

4

レビュー済

15/15

Suspect

0

グループ

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

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

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

グループREQARCHSPECTSTIMPLADR
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()` と、関数...
ADR012
# Imperative Execution, Smart Defaults, and Workspace Management ## Context and...
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()` と同等のラッピングを行...
ADR012
# Imperative Execution, Smart Defaults, and Workspace Management ## Context and...
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()` が実行エンジンの中核。 両メソッドは同一のフ...
ADR012
# Imperative Execution, Smart Defaults, and Workspace Management ## Context and...
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()` と、関数...
ADR013
# Rename Project to Spot and Task to Mark ## Context and Problem Statement / コン...
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()` と、関数...
ADR015
# Strict Scoping for Imperative Execution (Runtime Guard) ## Context and Proble...
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()` と、関数...
ADR034
# cached_run スコープ制限の廃止 ## Context and Problem Statement / コンテキスト ADR-0014 では、`...
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()` と同等のラッピングを行...
ADR013
# Rename Project to Spot and Task to Mark ## Context and Problem Statement / コン...
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()` と同等のラッピングを行...
ADR015
# Strict Scoping for Imperative Execution (Runtime Guard) ## Context and Proble...
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()` と同等のラッピングを行...
ADR034
# cached_run スコープ制限の廃止 ## Context and Problem Statement / コンテキスト ADR-0014 では、`...
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()` が実行エンジンの中核。 両メソッドは同一のフ...
ADR013
# Rename Project to Spot and Task to Mark ## Context and Problem Statement / コン...
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()` が実行エンジンの中核。 両メソッドは同一のフ...
ADR015
# Strict Scoping for Imperative Execution (Runtime Guard) ## Context and Proble...
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()` が実行エンジンの中核。 両メソッドは同一のフ...
ADR034
# cached_run スコープ制限の廃止 ## Context and Problem Statement / コンテキスト ADR-0014 では、`...

カバレッジ(局所)

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

アイテム詳細

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

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

親:

子: ADR012, ADR013, ADR015, ADR034, 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

子:

ADR012 ADR {h(g)} ✓ レビュー済

Imperative Execution, Smart Defaults, and Workspace Management

Context and Problem Statement / コンテキスト

これまで beautyspot は、関数のキャッシュ化にデコレータ (@project.task) を使用する方法のみを提供していました。しかし、実際の利用シナリオにおいて以下の課題が浮き彫りになりました:

  1. サードパーティライブラリの利用: ソースコードを変更できない関数(例: pandas.read_csv)や、外部APIクライアントのメソッドをキャッシュしたい場合、わざわざラッパー関数を定義する必要があり、記述が冗長になる。
  2. アドホックな解析: ノートブック環境や一時的なスクリプトで試行錯誤する際、関数定義の手間がオーバーヘッドとなり、開発体験(DX)を損ねる。
  3. リソース管理の曖昧さ: Project インスタンスが内部で保持する ThreadPoolExecutor が適切にシャットダウンされない場合、プロセスがハングするリスクがある。
  4. ディレクトリの汚染: 生成されるデータベースファイル (.db) やBlobディレクトリ (blobs/) がプロジェクトルート直下に作成され、ディレクトリ構成が煩雑になる。
  5. 設定の重複: 複数のタスクで「常にBlob保存したい」「特定のバージョンを一律適用したい」といった場合、都度引数を指定するのは DRY (Don't Repeat Yourself) 原則に反する。
  6. None 結果のキャッシュ: time.sleep のように戻り値が None である関数をキャッシュしようとすると、キャッシュミスと誤認され再実行されてしまうバグが存在した。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

以下の包括的なアーキテクチャ変更を行います。

1. 命令的実行メソッド (project.run) の導入

デコレータを使用せず、関数オブジェクトとその引数を渡すことで即座にキャッシュ付き実行を行う run メソッドを Project クラスに追加します。

result = project.run(func, arg1, arg2, _save_blob=True)

2. コンテキストマネージャとリソース管理

Project クラスをコンテキストマネージャとして設計し、with ブロック内での利用を推奨パターンとします。これにより、ブロック終了時に確実に shutdown() が呼ばれ、スレッドプール等のリソースが解放されることを保証します。

3. 設定の階層化とスマートデフォルト

Project 初期化時にデフォルト設定を受け入れ、個別の実行時にそれを継承・上書きできる仕組みを導入します。優先順位は「個別指定 > プロジェクトデフォルト > システムデフォルト」とします。

4. ワークスペースディレクトリによる集約

すべての生成アーティファクト(DB、Blob)を、デフォルトで隠しディレクトリ .beautyspot/ 配下に集約します。また、初期化時に .gitignore を自動生成し、キャッシュファイルがバージョン管理に含まれるのを防ぎます。

5. 番兵オブジェクトによるキャッシュ判定

キャッシュヒットの判定において None を特別視せず、専用の番兵オブジェクト (CACHE_MISS) を導入することで、None を返す関数も正しくキャッシュ可能とします。

Consequences / 決定

親: REQ001

子:

ADR013 ADR {h(g)} ✓ レビュー済

Rename Project to Spot and Task to Mark

Context and Problem Statement / コンテキスト

現在、beautyspot のメインエントリーポイントとして Project クラスが、タスク定義のデコレータとして @project.task が使用されています。しかし、これらの名称には以下の課題があります。

  1. Project の曖昧さ: ユーザーにとって "Project" は、自身のソースコード全体やリポジトリを指す言葉であることが多いです。ライブラリの管理オブジェクトを Project と呼ぶことで、ユーザーのメンタルモデルとの衝突(「プロジェクトの中にプロジェクトがある?」)を招いています。
  2. task の不一致: task は「仕事・課題」を表す名詞ですが、デコレータの役割は「関数を永続化対象として登録・設定する」という動的な作用です。名詞としての命名は、宣言的なデコレータの性質と完全に一致していません。
  3. ブランド・アイデンティティの欠如: 現在の API は汎用的すぎて、このライブラリ特有の「黒子」や「美点(Beauty Spot)」というコンセプトがコード上で表現されていません。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

ライブラリのコアとなる用語を再定義し、以下のリネームを行います。

1. Rename Project class to Spot

管理クラスの名前を Spot に変更します。これにより、「コード上の特定の場所(Spot)を管理する」というニュアンスを持たせ、ライブラリ名 beautyspot との一貫性を持たせます。

2. Rename @task decorator to @mark

デコレータ名を @spot.mark に変更します。これは "Marking a spot"(地点に印を付ける)というイディオムに基づいており、「この関数を管理対象としてマークする」という宣言的な意図を明確にします。

3. Keep run method as spot.run

命令的な実行メソッドである run は、名前を変更せずそのまま維持します。これは、「mark(宣言・静的)」と「run(実行・動的)」という役割分担を明確にするためです。

Consequences / 決定

親: REQ001

子:

ADR015 ADR {h(g)} ✓ レビュー済

Strict Scoping for Imperative Execution (Runtime Guard)

Context and Problem Statement / コンテキスト

spot.cached_run() コンテキストマネージャを使用すると、ユーザーは一時的に関数のキャッシュ挙動を適用できます。しかし、Python の with 文のスコープルールにより、ブロック内でバインドされた変数(例: with ... as task:)はブロック終了後もアクセス可能です。

これにより、ユーザーが特定のコンテキスト設定(version="v1" や一時的なストレージ設定など)を持つ関数ラッパーを、意図しない場所で再利用してしまうリスクがあります。これは微妙なバグやリソースリークの原因となります。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

ScopedMark コンテキストマネージャに Runtime Guard パターンを実装します。

  1. State Tracking: コンテキストマネージャはアクティブ状態のフラグを保持する。
  2. Wrapper Guard: cached_run から返される関数は、実行前にこのフラグをチェックするガードでラップされる。
  3. Fail Fast: ラップされた関数が with ブロックの外で呼び出された場合、即座に RuntimeError を送出する。

Consequences / 決定

親: REQ001

子:

ADR034 ADR {h(g)} ✓ レビュー済

cached_run スコープ制限の廃止

Context and Problem Statement / コンテキスト

ADR-0014 では、cached_run が返すラッパー関数を with ブロック外から呼び出した場合に RuntimeError を送出する Runtime Guard パターンを導入しました。しかし、ADR-0023 で Spot インスタンス自体の再利用性(with spot: はフラッシュのみを目的とし、インスタンスの無効化ではない)が確立されたことにより、cached_run のラッパーについても同様にスコープ外での利用を妨げない方針が自然な帰結となりました。

また、Runtime Guard の実装において ContextVar を使用していましたが、呼び出しごとに新規の ContextVar を作成する構造になっており、コンテキスト分離の恩恵が得られていませんでした。さらに、asyncio の一部のパターンにおいてガードが機能しないバグも内包していました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

cached_run の Runtime Guard を廃止し、返されたラッパー関数は with ブロック外でも呼び出し可能とします。

  1. 簡略化: ContextVar, is_active, make_scoped_guard などの関連ロジックを全て削除します。
  2. 実装の純粋化: cached_run は単に self.mark() を対象関数に適用して返すだけの、直感的な実装に変更します。

Consequences / 決定

親: REQ001

子: