✓=レビュー済 ○=未レビュー ⚠=Suspect(複数同時表示あり。アイテムIDをクリックで詳細へ)
| グループ▲▼ | REQ▲▼ | ARCH▲▼ | SPEC▲▼ | TST▲▼ | IMPL▲▼ | ADR▲▼ |
|---|---|---|---|---|---|---|
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC007 ✓ ## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db... | TST007 ✓ ## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす... tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py | IMPL007 ✓ ## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri... src/beautyspot/db.py | ADR007 ✓ # Semantic Content Type Support ## Context and Problem Statement / コンテキスト 生成AI... |
| DB | REQ019 ✓ データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLite... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC007 ✓ ## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db... | TST007 ✓ ## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす... tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py | IMPL007 ✓ ## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri... src/beautyspot/db.py | ADR051 ✓ # Thread-Safe SQLite Connection Closing and Lock Management ## Context and Prob... |
| CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | 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であり、キャッシュの正確性・透過性・拡張性の基盤となる。デコレーションによって元... tests/integration/core/test_basic.py | IMPL001 ✓ ## 実装概要 `Spot.mark()` メソッドが本仕様の中核。二段階デコレータファクトリとして、 オプションを受け取る外側の `mark()` と、関数... src/beautyspot/core.py | ADR012 ✓ # Imperative Execution, Smart Defaults, and Workspace Management ## Context and... |
| KEY | REQ002 ✓ 関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。 | ARCH002 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy... | SPEC004 ✓ ## インターフェース ```python class KeyGen: @staticmethod def _default(args: tu... | TST004 ✓ ## 目的 キャッシュキーはキャッシュの同一性判定の基盤であり、ハッシュが不安定だとキャッシュミスの頻発(性能劣化)や誤ヒット(データ不整合)を引き起こす。P... tests/unit/test_cachekey.py | IMPL004 ✓ ## 実装概要 `KeyGen` クラスの静的メソッド群がキャッシュキー生成を担う。 `_default(args, kwargs)` が主要なエントリポイン... src/beautyspot/cachekey.py | ADR002 ✓ # Stable Hashing for Function Arguments ## Context and Problem Statement / コンテキ... |
| STORE | REQ004 ✓ 大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。 | ARCH004 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol... | SPEC009 ✓ ## インターフェース ```python class LocalStorage(BlobStorageMaintenable): def __ini... | TST009 ✓ ## 目的 `LocalStorage` はBlobデータのファイルシステム永続化を担い、並行書き込み時のデータ破損やパストラバーサルによるセキュリティ脆弱性... tests/integration/storage/test_local.py | IMPL009 ✓ ## 実装概要 - `LocalStorage` クラスが `BlobStorageBase` を実装。 ファイルは `{base_dir}/{key}.... src/beautyspot/storage.py | ADR030 ✓ # Declarative Storage Policy ## Context and Problem Statement / コンテキスト これまでの `... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン... tests/unit/test_serializer.py | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... src/beautyspot/serializer.py | ADR004 ✓ # Resilient Deserialization and Explicit Versioning ## Context and Problem Stat... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... tests/integration/core/test_background_loop.py | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... src/beautyspot/core.py | ADR005 ✓ # Manage Executor Lifecycle via Instance Ownership and Weak References ## Conte... |
| LIFE | REQ007 ✓ キャッシュの有効期限を関数名パターンに基づくルールで設定できること。個別の関数に対して保持期間を指定でき、期限切れのキャッシュを自動的に無効化できること。 | ARCH007 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|マッチング要求| B[LifecyclePolicy]... | SPEC016 ✓ ## インターフェース ```python class LifecyclePolicy: def add_rule(self, pattern: st... | TST016 ✓ ## 目的 `LifecyclePolicy` はキャッシュデータの保持期間を関数名パターンで制御する。パターンマッチングの不備は、重要なキャッシュの意図しな... tests/unit/test_lifecycle.py | IMPL016 ✓ ## 実装概要 `LifecyclePolicy` クラスは `Rule` オブジェクトのリストを保持し、 関数名に基づいてキャッシュの保持期間を決定する。 ... src/beautyspot/lifecycle.py | ADR032 ✓ # Declarative Lifecycle Policy ## Context and Problem Statement / コンテキスト 機械学習や... |
| LIMIT | REQ008 ✓ 関数の実行頻度をトークンバケット方式で制限し、外部APIの呼び出しレートを制御できること。同期・非同期の両方に対応すること。 | ARCH008 ✓ ## コンポーネント構成 ```mermaid graph TD A["@spot.consume"] -->|宣言的適用| B[LimiterProto... | SPEC018 ✓ ## インターフェース ```python class TokenBucket(LimiterProtocol): def consume(self,... | TST018 ✓ ## 目的 `TokenBucket` はAPI呼び出しやリソースアクセスのレート制限を実現する。レートリミッターの不具合はAPIの過負荷(制限が甘い場合)や... tests/unit/test_limiter.py | IMPL018 ✓ ## 実装概要 `LimiterProtocol` を実装する `TokenBucket` クラス。 GCRA (Generic Cell Rate Algo... src/beautyspot/limiter.py | ADR003 ✓ # Smooth Rate Limiter (GCRA) ## Context and Problem Statement / コンテキスト `beauty... |
| HOOK | REQ009 ✓ 関数の実行前、キャッシュヒット時、キャッシュミス時にカスタムコールバック(フック)を実行できること。スレッドセーフなフック実装を提供すること。 | ARCH009 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|イベント通知| B[HookBase] C[Thr... | SPEC019 ✓ ## インターフェース ```python class HookBase: def pre_execute(self, ctx: PreExecute... | TST019 ✓ ## 目的 Hook システムはキャッシュライフサイクルへのユーザー拡張ポイント(ロギング、メトリクス収集、監査等)であり、フック内の不具合がメインの関数実行... tests/integration/core/test_hooks.py | IMPL019 ✓ ## 実装概要 タスク実行ライフサイクルに介入するインターフェース `HookBase` と、 そのスレッドセーフ版 `ThreadSafeHookBase`... src/beautyspot/hooks.py | ADR035 ✓ # クラスベース・フックシステムと並列実行サポート ## Context and Problem Statement / コンテキスト `beautyspo... |
| MAINT | REQ010 ✓ 期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除(prune)等のメンテナンス操作ができること。 | ARCH010 ✓ ## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase... | SPEC020 ✓ ## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra... | TST020 ✓ ## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用... tests/unit/test_maintenance.py | IMPL020 ✓ ## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と... src/beautyspot/maintenance.py | ADR016 ✓ # Interactive Deletion and Cleanup Policy ## Context and Problem Statement / コン... |
| CLI | REQ011 ✓ コマンドラインインターフェースからキャッシュの一覧表示、詳細確認、統計表示、削除、GC等の管理操作ができること。 | ARCH011 ✓ ## コンポーネント構成 ```mermaid graph TD A[beautyspot CLI] -->|Command| B[Typer App] ... | SPEC021 ✓ ## インターフェース ```bash beautyspot list [--db DB_PATH] beautyspot gc [--name PROJEC... | TST021 ✓ ## 目的 CLI はユーザーがキャッシュの状況確認・管理を行う主要インターフェースであり、コマンドの不具合はユーザー体験と運用効率に直接影響する。`CliR... tests/integration/cli/test_cli.py | IMPL021 ✓ ## 実装概要 `Typer` を利用したコマンドラインインターフェースの実装。 `list`, `show`, `stats`, `clear`, `cle... src/beautyspot/cli.py | ADR006 ✓ # Dashboard Interaction Model ## Context and Problem Statement / コンテキスト 現状のダッシ... |
| DI | REQ012 ✓ 全コンポーネント(DB、ストレージ、シリアライザ、リミッター等)が `Protocol/ABC` に基づく依存注入により差し替え可能であること。ファクトリ関数が... | ARCH012 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|構築| B[core.Spot] A ... | SPEC022 ✓ ## インターフェース ```python def Spot( name: str = "default", storage_path: st... | TST022 ✓ ## 目的 `bs.Spot()` ファクトリ関数は全コンポーネントの DI 配線を担う唯一のパブリックエントリポイントであり、デフォルト構成の正確性とカスタ... tests/integration/core/test_dependency_injection.py | IMPL022 ✓ ## 実装概要 `beautyspot` パッケージのメインエントリポイントとなる `Spot` ファクトリ関数の実装。 引数として渡された各コンポーネント(... src/beautyspot/__init__.py | ADR009 ✓ # Database Dependency Injection and Abstraction ## Context and Problem Statemen... |
| HERD | REQ013 ✓ 同一キャッシュキーに対する並行リクエストを直列化し、1つのリクエストのみが関数を実行し、他のリクエストはその結果を共有することで重複実行を防止できること。 | ARCH013 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|管理要求| B[CacheManager] B -... | SPEC023 ✓ ## インターフェース ```python class CacheManager: def herd_sync( self, key:... | TST023 ✓ ## 目的 Thundering Herd Protection は、同一キーへの並行リクエストが一斉に関数を実行する「Thundering Herd」問題を... tests/integration/core/test_thundering_herd.py | IMPL023 ✓ ## 実装概要 `CacheManager` クラス内で、Thundering Herd(キャッシュミス時に同一キーへの 大量アクセスが同時に発生する問題)を... src/beautyspot/cache.py | — |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC008 ✓ ## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge... | TST008 ✓ ## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL... tests/unit/test_db_writer_queue.py | IMPL008 ✓ ## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd... src/beautyspot/db.py | ADR007 ✓ # Semantic Content Type Support ## Context and Problem Statement / コンテキスト 生成AI... |
| DB | REQ019 ✓ データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLite... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC008 ✓ ## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge... | TST008 ✓ ## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL... tests/unit/test_db_writer_queue.py | IMPL008 ✓ ## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd... src/beautyspot/db.py | ADR051 ✓ # Thread-Safe SQLite Connection Closing and Lock Management ## Context and Prob... |
| CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | 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の不具合はサードパーテ... tests/integration/core/test_cached_run.py | IMPL002 ✓ ## 実装概要 `Spot.cached_run()` はコンテキストマネージャとして実装。 渡された関数群を内部で `mark()` と同等のラッピングを行... src/beautyspot/core.py | ADR012 ✓ # Imperative Execution, Smart Defaults, and Workspace Management ## Context and... |
| CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | ARCH001 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor... | SPEC003 ✓ ## 概要 `@spot.mark()` デコレータは同期関数と非同期(`async def`)関数の両方をサポートし、関数定義に応じて適切な実行・保存・キャ... | TST003 ✓ ## 目的 beautyspot は同期・非同期の両方の関数を透過的にキャッシュするが、asyncio 固有のイベントループ制約(スレッドをまたぐコルーチン実... tests/integration/core/test_async_save.py | IMPL003 ✓ ## 実装概要 `Spot._execute_sync()` と `Spot._execute_async()` が実行エンジンの中核。 両メソッドは同一のフ... src/beautyspot/core.py | ADR012 ✓ # Imperative Execution, Smart Defaults, and Workspace Management ## Context and... |
| KEY | REQ002 ✓ 関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。 | ARCH002 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy... | SPEC005 ✓ ## インターフェース ```python @singledispatch def canonicalize(obj: Any) -> Any: ... ``... | TST005 ✓ ## 目的 `canonicalize()` は引数の型ごとに正規化ルールを適用し、論理的に同値な入力が同じキャッシュキーを生成することを保証する。正規化の不... tests/unit/test_cachekey.py | IMPL005 ✓ ## 実装概要 `canonicalize()` は `functools.singledispatch` で実装された再帰的正規化関数。 型ごとにディスパッ... src/beautyspot/cachekey.py | ADR002 ✓ # Stable Hashing for Function Arguments ## Context and Problem Statement / コンテキ... |
| KEY | REQ002 ✓ 関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。 | ARCH002 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy... | SPEC006 ✓ ## インターフェース ```python class Strategy(Enum): DEFAULT = auto() # 再帰的正規化 ... | TST006 ✓ ## 目的 `KeyGenPolicy` は引数ごとのキー生成戦略をカスタマイズする機構であり、ログレベルやデバッグフラグのような非決定的引数をキーから除外し... tests/unit/test_cachekey.py | IMPL006 ✓ ## 実装概要 `Strategy` enum(DEFAULT, IGNORE, FILE_CONTENT, PATH_STAT の4種)と `KeyGenP... src/beautyspot/cachekey.py | ADR002 ✓ # Stable Hashing for Function Arguments ## Context and Problem Statement / コンテキ... |
| STORE | REQ004 ✓ 大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。 | ARCH004 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol... | SPEC010 ✓ ## インターフェース ```python class S3Storage(BlobStorageMaintenable): def __init__... | TST010 ✓ ## 目的 `S3Storage` はクラウド環境での大規模Blob保存を担い、ネットワーク障害・認証エラー・バケット不在などオンプレミスにはない障害モードが... tests/integration/storage/test_s3.py | IMPL010 ✓ ## 実装概要 `S3Storage` クラスが `BlobStorageBase` を実装。 `s3://bucket/prefix/` 形式の URI を... src/beautyspot/storage.py | ADR030 ✓ # Declarative Storage Policy ## Context and Problem Statement / コンテキスト これまでの `... |
| STORE | REQ004 ✓ 大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。 | ARCH004 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol... | SPEC011 ✓ ## インターフェース ```python class StoragePolicyProtocol(Protocol): def should_sav... | TST011 ✓ ## 目的 ストレージポリシーは「データをDB直接保存するか、Blobストレージに分離するか」を決定する戦略レイヤーである。閾値判定の誤りはDBの肥大化(性能... tests/unit/test_storage_policy.py | IMPL011 ✓ ## 実装概要 `StoragePolicyProtocol` が `should_save_as_blob(data: bytes) -> bool` を定... src/beautyspot/storage.py | ADR030 ✓ # Declarative Storage Policy ## Context and Problem Statement / コンテキスト これまでの `... |
| STORE | REQ004 ✓ 大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。 | ARCH004 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol... | SPEC024 ✓ ## インターフェース ```python class BlobStorageBase(ABC): @abstractmethod def s... | TST024 ✓ ## 目的 `BlobStorageBase` は全ストレージバックエンド(LocalStorage, S3Storage, サードパーティ)が 準拠すべき抽... tests/integration/storage/test_blob_storage_base.py | IMPL024 ✓ ## 実装概要 `storage.py` 内の `BlobStorageBase` ABC が SPEC024 の中核実装。 5つの抽象メソッド(`save`... src/beautyspot/storage.py | ADR030 ✓ # Declarative Storage Policy ## Context and Problem Statement / コンテキスト これまでの `... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... tests/unit/test_type_registry.py | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... src/beautyspot/serializer.py, src/beautyspot/core.py | ADR004 ✓ # Resilient Deserialization and Explicit Versioning ## Context and Problem Stat... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... tests/integration/core/test_exit_drain.py | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... src/beautyspot/core.py | ADR005 ✓ # Manage Executor Lifecycle via Instance Ownership and Weak References ## Conte... |
| LIFE | REQ007 ✓ キャッシュの有効期限を関数名パターンに基づくルールで設定できること。個別の関数に対して保持期間を指定でき、期限切れのキャッシュを自動的に無効化できること。 | ARCH007 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|マッチング要求| B[LifecyclePolicy]... | SPEC017 ✓ ## インターフェース ```python def parse_retention(val: Any) -> float | None: ... ``` #... | TST017 ✓ ## 目的 `Retention` はキャッシュの有効期間をユーザーフレンドリーな形式で指定する値オブジェクトである。パースの不備は意図しない保持期間(例: ... tests/unit/test_lifecycle_extended.py | IMPL017 ✓ ## 実装概要 `Retention` クラスを名前空間として使用し、保持期間のパースや 特殊な定数(`INDEFINITE`, `FOREVER`)を管理す... src/beautyspot/lifecycle.py | ADR032 ✓ # Declarative Lifecycle Policy ## Context and Problem Statement / コンテキスト 機械学習や... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC007 ✓ ## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db... | TST007 ✓ ## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす... tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py | IMPL007 ✓ ## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri... src/beautyspot/db.py | ADR010 ✓ # Msgpack Everywhere with Native BLOB Support ## Context and Problem Statement ... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC007 ✓ ## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db... | TST007 ✓ ## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす... tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py | IMPL007 ✓ ## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri... src/beautyspot/db.py | ADR043 ✓ # Serialize SQLite Writes with a Writer Queue ## Context and Problem Statement ... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC007 ✓ ## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db... | TST007 ✓ ## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす... tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py | IMPL007 ✓ ## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri... src/beautyspot/db.py | ADR045 ✓ # Introduce Explicit DB Queue Flushing ## Context and Problem Statement / コンテキス... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC007 ✓ ## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db... | TST007 ✓ ## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす... tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py | IMPL007 ✓ ## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri... src/beautyspot/db.py | ADR048 ✓ # SQLite ライタースレッドの死活監視におけるポーリング間隔の維持 ## Context and Problem Statement / コンテキスト ... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC007 ✓ ## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db... | TST007 ✓ ## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす... tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py | IMPL007 ✓ ## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri... src/beautyspot/db.py | ADR050 ✓ # Delegate Database Lifecycle Management to Caller ## Context and Problem State... |
| DB | REQ019 ✓ データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLite... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC007 ✓ ## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db... | TST007 ✓ ## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす... tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py | IMPL007 ✓ ## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri... src/beautyspot/db.py | ADR052 ✓ # SQLite Write Task Cancellation Strategy ## Context and Problem Statement / コン... |
| CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | 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であり、キャッシュの正確性・透過性・拡張性の基盤となる。デコレーションによって元... tests/integration/core/test_basic.py | IMPL001 ✓ ## 実装概要 `Spot.mark()` メソッドが本仕様の中核。二段階デコレータファクトリとして、 オプションを受け取る外側の `mark()` と、関数... src/beautyspot/core.py | ADR013 ✓ # Rename Project to Spot and Task to Mark ## Context and Problem Statement / コン... |
| CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | 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であり、キャッシュの正確性・透過性・拡張性の基盤となる。デコレーションによって元... tests/integration/core/test_basic.py | IMPL001 ✓ ## 実装概要 `Spot.mark()` メソッドが本仕様の中核。二段階デコレータファクトリとして、 オプションを受け取る外側の `mark()` と、関数... src/beautyspot/core.py | ADR015 ✓ # Strict Scoping for Imperative Execution (Runtime Guard) ## Context and Proble... |
| CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | 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であり、キャッシュの正確性・透過性・拡張性の基盤となる。デコレーションによって元... tests/integration/core/test_basic.py | IMPL001 ✓ ## 実装概要 `Spot.mark()` メソッドが本仕様の中核。二段階デコレータファクトリとして、 オプションを受け取る外側の `mark()` と、関数... src/beautyspot/core.py | ADR034 ✓ # cached_run スコープ制限の廃止 ## Context and Problem Statement / コンテキスト ADR-0014 では、`... |
| KEY | REQ002 ✓ 関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。 | ARCH002 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy... | SPEC004 ✓ ## インターフェース ```python class KeyGen: @staticmethod def _default(args: tu... | TST004 ✓ ## 目的 キャッシュキーはキャッシュの同一性判定の基盤であり、ハッシュが不安定だとキャッシュミスの頻発(性能劣化)や誤ヒット(データ不整合)を引き起こす。P... tests/unit/test_cachekey.py | IMPL004 ✓ ## 実装概要 `KeyGen` クラスの静的メソッド群がキャッシュキー生成を担う。 `_default(args, kwargs)` が主要なエントリポイン... src/beautyspot/cachekey.py | ADR011 ✓ # Canonical Input Serialization with Msgpack and SHA-256 ## Context and Problem... |
| KEY | REQ002 ✓ 関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。 | ARCH002 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy... | SPEC004 ✓ ## インターフェース ```python class KeyGen: @staticmethod def _default(args: tu... | TST004 ✓ ## 目的 キャッシュキーはキャッシュの同一性判定の基盤であり、ハッシュが不安定だとキャッシュミスの頻発(性能劣化)や誤ヒット(データ不整合)を引き起こす。P... tests/unit/test_cachekey.py | IMPL004 ✓ ## 実装概要 `KeyGen` クラスの静的メソッド群がキャッシュキー生成を担う。 `_default(args, kwargs)` が主要なエントリポイン... src/beautyspot/cachekey.py | ADR022 ✓ # Refactoring Complex Modules with Single Dispatch ## Context and Problem State... |
| STORE | REQ004 ✓ 大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。 | ARCH004 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol... | SPEC009 ✓ ## インターフェース ```python class LocalStorage(BlobStorageMaintenable): def __ini... | TST009 ✓ ## 目的 `LocalStorage` はBlobデータのファイルシステム永続化を担い、並行書き込み時のデータ破損やパストラバーサルによるセキュリティ脆弱性... tests/integration/storage/test_local.py | IMPL009 ✓ ## 実装概要 - `LocalStorage` クラスが `BlobStorageBase` を実装。 ファイルは `{base_dir}/{key}.... src/beautyspot/storage.py | ADR044 ✓ # Drop Absolute Path Support in LocalStorage ## Context and Problem Statement /... |
| STORE | REQ004 ✓ 大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。 | ARCH004 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol... | SPEC009 ✓ ## インターフェース ```python class LocalStorage(BlobStorageMaintenable): def __ini... | TST009 ✓ ## 目的 `LocalStorage` はBlobデータのファイルシステム永続化を担い、並行書き込み時のデータ破損やパストラバーサルによるセキュリティ脆弱性... tests/integration/storage/test_local.py | IMPL009 ✓ ## 実装概要 - `LocalStorage` クラスが `BlobStorageBase` を実装。 ファイルは `{base_dir}/{key}.... src/beautyspot/storage.py | ADR046 ✓ # Delegate Workspace Initialization to Storage Backends ## Context and Problem ... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン... tests/unit/test_serializer.py | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... src/beautyspot/serializer.py | ADR008 ✓ # Customizable Serialization Strategy with Msgpack Default ## Context and Probl... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン... tests/unit/test_serializer.py | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... src/beautyspot/serializer.py | ADR014 ✓ # Decorator-based Type Registration with Late Binding ## Context and Problem St... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン... tests/unit/test_serializer.py | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... src/beautyspot/serializer.py | ADR017 ✓ title: Nested Msgpack Protocol via Serializer Wrapping status: Accepted date: 20... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン... tests/unit/test_serializer.py | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... src/beautyspot/serializer.py | ADR019 ✓ # Per-Task Serializer Override ## Context and Problem Statement / コンテキスト プロジェク... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン... tests/unit/test_serializer.py | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... src/beautyspot/serializer.py | ADR025 ✓ # Protocol-based Serializer Registration ## Context and Problem Statement / コンテ... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン... tests/unit/test_serializer.py | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... src/beautyspot/serializer.py | ADR039 ✓ # Bounded LRU Cache for Subclass Resolution in Serializer ## Context and Proble... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン... tests/unit/test_serializer.py | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... src/beautyspot/serializer.py | ADR042 ✓ # Thread-safe LRU Cache for Serializer ## Context and Problem Statement / コンテキス... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... tests/integration/core/test_background_loop.py | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... src/beautyspot/core.py | ADR024 ✓ # Non-blocking Cache Persistence and Task Tracking ## Context and Problem State... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... tests/integration/core/test_background_loop.py | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... src/beautyspot/core.py | ADR033 ✓ # 非同期保存処理におけるエラー可視化とコンテキストの導入 ## Context and Problem Statement / コンテキスト beauty... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... tests/integration/core/test_background_loop.py | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... src/beautyspot/core.py | ADR036 ✓ # バックグラウンドループのライフサイクル管理とGC時のデータロスト対策 ## Context and Problem Statement / コンテキスト ... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... tests/integration/core/test_background_loop.py | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... src/beautyspot/core.py | ADR037 ✓ # GC時のデータロスト防止とバックグラウンドループのシャットダウン戦略 ## Context and Problem Statement / コンテキスト ... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... tests/integration/core/test_background_loop.py | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... src/beautyspot/core.py | ADR040 ✓ # Background Loop Shutdown Strategy and Threading Model ## Context and Problem ... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... tests/integration/core/test_background_loop.py | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... src/beautyspot/core.py | ADR047 ✓ # Background Loop Shutdown Strategy on Garbage Collection ## Context and Proble... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... tests/integration/core/test_background_loop.py | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... src/beautyspot/core.py | ADR049 ✓ # 実行中タスクの強参照セットによる追跡 ## Context and Problem Statement / コンテキスト `Spot` インスタンスは、... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... tests/integration/core/test_background_loop.py | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... src/beautyspot/core.py | ADR053 ✓ # Background Loop and Executor Shutdown Sequence ## Context and Problem Stateme... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... tests/integration/core/test_background_loop.py | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... src/beautyspot/core.py | ADR001 ✓ # Manage Executor Lifecycle via Instance Ownership and Weak References ## Conte... |
| LIFE | REQ007 ✓ キャッシュの有効期限を関数名パターンに基づくルールで設定できること。個別の関数に対して保持期間を指定でき、期限切れのキャッシュを自動的に無効化できること。 | ARCH007 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|マッチング要求| B[LifecyclePolicy]... | SPEC016 ✓ ## インターフェース ```python class LifecyclePolicy: def add_rule(self, pattern: st... | TST016 ✓ ## 目的 `LifecyclePolicy` はキャッシュデータの保持期間を関数名パターンで制御する。パターンマッチングの不備は、重要なキャッシュの意図しな... tests/unit/test_lifecycle.py | IMPL016 ✓ ## 実装概要 `LifecyclePolicy` クラスは `Rule` オブジェクトのリストを保持し、 関数名に基づいてキャッシュの保持期間を決定する。 ... src/beautyspot/lifecycle.py | ADR038 ✓ # Probabilistic Auto-Eviction for Storage Maintenance ## Context and Problem St... |
| LIMIT | REQ008 ✓ 関数の実行頻度をトークンバケット方式で制限し、外部APIの呼び出しレートを制御できること。同期・非同期の両方に対応すること。 | ARCH008 ✓ ## コンポーネント構成 ```mermaid graph TD A["@spot.consume"] -->|宣言的適用| B[LimiterProto... | SPEC018 ✓ ## インターフェース ```python class TokenBucket(LimiterProtocol): def consume(self,... | TST018 ✓ ## 目的 `TokenBucket` はAPI呼び出しやリソースアクセスのレート制限を実現する。レートリミッターの不具合はAPIの過負荷(制限が甘い場合)や... tests/unit/test_limiter.py | IMPL018 ✓ ## 実装概要 `LimiterProtocol` を実装する `TokenBucket` クラス。 GCRA (Generic Cell Rate Algo... src/beautyspot/limiter.py | ADR031 ✓ # Limiter Dependency Injection ## Context and Problem Statement / コンテキスト 以前、`c... |
| MAINT | REQ010 ✓ 期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除(prune)等のメンテナンス操作ができること。 | ARCH010 ✓ ## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase... | SPEC020 ✓ ## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra... | TST020 ✓ ## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用... tests/unit/test_maintenance.py | IMPL020 ✓ ## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と... src/beautyspot/maintenance.py | ADR018 ✓ # Expansion of Testing Strategy: Introduction of High-Level Integration Tests #... |
| MAINT | REQ010 ✓ 期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除(prune)等のメンテナンス操作ができること。 | ARCH010 ✓ ## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase... | SPEC020 ✓ ## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra... | TST020 ✓ ## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用... tests/unit/test_maintenance.py | IMPL020 ✓ ## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と... src/beautyspot/maintenance.py | ADR020 ✓ # Tolerant Deletion Policy for Task Cleanup ## Context and Problem Statement / ... |
| MAINT | REQ010 ✓ 期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除(prune)等のメンテナンス操作ができること。 | ARCH010 ✓ ## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase... | SPEC020 ✓ ## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra... | TST020 ✓ ## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用... tests/unit/test_maintenance.py | IMPL020 ✓ ## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と... src/beautyspot/maintenance.py | ADR021 ✓ # Code Visualization and Quality Metrics Strategy ## Context and Problem Statem... |
| MAINT | REQ010 ✓ 期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除(prune)等のメンテナンス操作ができること。 | ARCH010 ✓ ## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase... | SPEC020 ✓ ## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra... | TST020 ✓ ## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用... tests/unit/test_maintenance.py | IMPL020 ✓ ## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と... src/beautyspot/maintenance.py | ADR023 ✓ # Service Layer for Maintenance Operations ## Context and Problem Statement / コ... |
| MAINT | REQ010 ✓ 期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除(prune)等のメンテナンス操作ができること。 | ARCH010 ✓ ## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase... | SPEC020 ✓ ## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra... | TST020 ✓ ## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用... tests/unit/test_maintenance.py | IMPL020 ✓ ## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と... src/beautyspot/maintenance.py | ADR029 ✓ # Recursive Storage Cleanup and Zombie Project Collection ## Context and Proble... |
| MAINT | REQ010 ✓ 期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除(prune)等のメンテナンス操作ができること。 | ARCH010 ✓ ## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase... | SPEC020 ✓ ## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra... | TST020 ✓ ## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用... tests/unit/test_maintenance.py | IMPL020 ✓ ## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と... src/beautyspot/maintenance.py | ADR041 ✓ # Garbage Collection Strategy for Abandoned Temporary Files ## Context and Prob... |
| CLI | REQ011 ✓ コマンドラインインターフェースからキャッシュの一覧表示、詳細確認、統計表示、削除、GC等の管理操作ができること。 | ARCH011 ✓ ## コンポーネント構成 ```mermaid graph TD A[beautyspot CLI] -->|Command| B[Typer App] ... | SPEC021 ✓ ## インターフェース ```bash beautyspot list [--db DB_PATH] beautyspot gc [--name PROJEC... | TST021 ✓ ## 目的 CLI はユーザーがキャッシュの状況確認・管理を行う主要インターフェースであり、コマンドの不具合はユーザー体験と運用効率に直接影響する。`CliR... tests/integration/cli/test_cli.py | IMPL021 ✓ ## 実装概要 `Typer` を利用したコマンドラインインターフェースの実装。 `list`, `show`, `stats`, `clear`, `cle... src/beautyspot/cli.py | ADR027 ✓ # Pragmatic CLI Refactoring Policy ## Context and Problem Statement / コンテキスト `... |
| CLI | REQ011 ✓ コマンドラインインターフェースからキャッシュの一覧表示、詳細確認、統計表示、削除、GC等の管理操作ができること。 | ARCH011 ✓ ## コンポーネント構成 ```mermaid graph TD A[beautyspot CLI] -->|Command| B[Typer App] ... | SPEC021 ✓ ## インターフェース ```bash beautyspot list [--db DB_PATH] beautyspot gc [--name PROJEC... | TST021 ✓ ## 目的 CLI はユーザーがキャッシュの状況確認・管理を行う主要インターフェースであり、コマンドの不具合はユーザー体験と運用効率に直接影響する。`CliR... tests/integration/cli/test_cli.py | IMPL021 ✓ ## 実装概要 `Typer` を利用したコマンドラインインターフェースの実装。 `list`, `show`, `stats`, `clear`, `cle... src/beautyspot/cli.py | ADR028 ✓ # CLI Scope Definition and Explicit Storage Linkage ## Context and Problem Stat... |
| DI | REQ012 ✓ 全コンポーネント(DB、ストレージ、シリアライザ、リミッター等)が `Protocol/ABC` に基づく依存注入により差し替え可能であること。ファクトリ関数が... | ARCH012 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|構築| B[core.Spot] A ... | SPEC022 ✓ ## インターフェース ```python def Spot( name: str = "default", storage_path: st... | TST022 ✓ ## 目的 `bs.Spot()` ファクトリ関数は全コンポーネントの DI 配線を担う唯一のパブリックエントリポイントであり、デフォルト構成の正確性とカスタ... tests/integration/core/test_dependency_injection.py | IMPL022 ✓ ## 実装概要 `beautyspot` パッケージのメインエントリポイントとなる `Spot` ファクトリ関数の実装。 引数として渡された各コンポーネント(... src/beautyspot/__init__.py | ADR026 ✓ # Factory Function for Default Dependency Injection ## Context and Problem Stat... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC008 ✓ ## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge... | TST008 ✓ ## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL... tests/unit/test_db_writer_queue.py | IMPL008 ✓ ## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd... src/beautyspot/db.py | ADR010 ✓ # Msgpack Everywhere with Native BLOB Support ## Context and Problem Statement ... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC008 ✓ ## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge... | TST008 ✓ ## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL... tests/unit/test_db_writer_queue.py | IMPL008 ✓ ## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd... src/beautyspot/db.py | ADR043 ✓ # Serialize SQLite Writes with a Writer Queue ## Context and Problem Statement ... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC008 ✓ ## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge... | TST008 ✓ ## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL... tests/unit/test_db_writer_queue.py | IMPL008 ✓ ## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd... src/beautyspot/db.py | ADR045 ✓ # Introduce Explicit DB Queue Flushing ## Context and Problem Statement / コンテキス... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC008 ✓ ## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge... | TST008 ✓ ## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL... tests/unit/test_db_writer_queue.py | IMPL008 ✓ ## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd... src/beautyspot/db.py | ADR048 ✓ # SQLite ライタースレッドの死活監視におけるポーリング間隔の維持 ## Context and Problem Statement / コンテキスト ... |
| DB | REQ003 ✓ キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC008 ✓ ## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge... | TST008 ✓ ## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL... tests/unit/test_db_writer_queue.py | IMPL008 ✓ ## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd... src/beautyspot/db.py | ADR050 ✓ # Delegate Database Lifecycle Management to Caller ## Context and Problem State... |
| DB | REQ019 ✓ データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLite... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC008 ✓ ## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge... | TST008 ✓ ## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL... tests/unit/test_db_writer_queue.py | IMPL008 ✓ ## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd... src/beautyspot/db.py | ADR052 ✓ # SQLite Write Task Cancellation Strategy ## Context and Problem Statement / コン... |
| CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | 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の不具合はサードパーテ... tests/integration/core/test_cached_run.py | IMPL002 ✓ ## 実装概要 `Spot.cached_run()` はコンテキストマネージャとして実装。 渡された関数群を内部で `mark()` と同等のラッピングを行... src/beautyspot/core.py | ADR013 ✓ # Rename Project to Spot and Task to Mark ## Context and Problem Statement / コン... |
| CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | 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の不具合はサードパーテ... tests/integration/core/test_cached_run.py | IMPL002 ✓ ## 実装概要 `Spot.cached_run()` はコンテキストマネージャとして実装。 渡された関数群を内部で `mark()` と同等のラッピングを行... src/beautyspot/core.py | ADR015 ✓ # Strict Scoping for Imperative Execution (Runtime Guard) ## Context and Proble... |
| CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | 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の不具合はサードパーテ... tests/integration/core/test_cached_run.py | IMPL002 ✓ ## 実装概要 `Spot.cached_run()` はコンテキストマネージャとして実装。 渡された関数群を内部で `mark()` と同等のラッピングを行... src/beautyspot/core.py | ADR034 ✓ # cached_run スコープ制限の廃止 ## Context and Problem Statement / コンテキスト ADR-0014 では、`... |
| CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | ARCH001 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor... | SPEC003 ✓ ## 概要 `@spot.mark()` デコレータは同期関数と非同期(`async def`)関数の両方をサポートし、関数定義に応じて適切な実行・保存・キャ... | TST003 ✓ ## 目的 beautyspot は同期・非同期の両方の関数を透過的にキャッシュするが、asyncio 固有のイベントループ制約(スレッドをまたぐコルーチン実... tests/integration/core/test_async_save.py | IMPL003 ✓ ## 実装概要 `Spot._execute_sync()` と `Spot._execute_async()` が実行エンジンの中核。 両メソッドは同一のフ... src/beautyspot/core.py | ADR013 ✓ # Rename Project to Spot and Task to Mark ## Context and Problem Statement / コン... |
| CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | ARCH001 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor... | SPEC003 ✓ ## 概要 `@spot.mark()` デコレータは同期関数と非同期(`async def`)関数の両方をサポートし、関数定義に応じて適切な実行・保存・キャ... | TST003 ✓ ## 目的 beautyspot は同期・非同期の両方の関数を透過的にキャッシュするが、asyncio 固有のイベントループ制約(スレッドをまたぐコルーチン実... tests/integration/core/test_async_save.py | IMPL003 ✓ ## 実装概要 `Spot._execute_sync()` と `Spot._execute_async()` が実行エンジンの中核。 両メソッドは同一のフ... src/beautyspot/core.py | ADR015 ✓ # Strict Scoping for Imperative Execution (Runtime Guard) ## Context and Proble... |
| CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | ARCH001 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor... | SPEC003 ✓ ## 概要 `@spot.mark()` デコレータは同期関数と非同期(`async def`)関数の両方をサポートし、関数定義に応じて適切な実行・保存・キャ... | TST003 ✓ ## 目的 beautyspot は同期・非同期の両方の関数を透過的にキャッシュするが、asyncio 固有のイベントループ制約(スレッドをまたぐコルーチン実... tests/integration/core/test_async_save.py | IMPL003 ✓ ## 実装概要 `Spot._execute_sync()` と `Spot._execute_async()` が実行エンジンの中核。 両メソッドは同一のフ... src/beautyspot/core.py | ADR034 ✓ # cached_run スコープ制限の廃止 ## Context and Problem Statement / コンテキスト ADR-0014 では、`... |
| KEY | REQ002 ✓ 関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。 | ARCH002 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy... | SPEC005 ✓ ## インターフェース ```python @singledispatch def canonicalize(obj: Any) -> Any: ... ``... | TST005 ✓ ## 目的 `canonicalize()` は引数の型ごとに正規化ルールを適用し、論理的に同値な入力が同じキャッシュキーを生成することを保証する。正規化の不... tests/unit/test_cachekey.py | IMPL005 ✓ ## 実装概要 `canonicalize()` は `functools.singledispatch` で実装された再帰的正規化関数。 型ごとにディスパッ... src/beautyspot/cachekey.py | ADR011 ✓ # Canonical Input Serialization with Msgpack and SHA-256 ## Context and Problem... |
| KEY | REQ002 ✓ 関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。 | ARCH002 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy... | SPEC005 ✓ ## インターフェース ```python @singledispatch def canonicalize(obj: Any) -> Any: ... ``... | TST005 ✓ ## 目的 `canonicalize()` は引数の型ごとに正規化ルールを適用し、論理的に同値な入力が同じキャッシュキーを生成することを保証する。正規化の不... tests/unit/test_cachekey.py | IMPL005 ✓ ## 実装概要 `canonicalize()` は `functools.singledispatch` で実装された再帰的正規化関数。 型ごとにディスパッ... src/beautyspot/cachekey.py | ADR022 ✓ # Refactoring Complex Modules with Single Dispatch ## Context and Problem State... |
| KEY | REQ002 ✓ 関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。 | ARCH002 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy... | SPEC006 ✓ ## インターフェース ```python class Strategy(Enum): DEFAULT = auto() # 再帰的正規化 ... | TST006 ✓ ## 目的 `KeyGenPolicy` は引数ごとのキー生成戦略をカスタマイズする機構であり、ログレベルやデバッグフラグのような非決定的引数をキーから除外し... tests/unit/test_cachekey.py | IMPL006 ✓ ## 実装概要 `Strategy` enum(DEFAULT, IGNORE, FILE_CONTENT, PATH_STAT の4種)と `KeyGenP... src/beautyspot/cachekey.py | ADR011 ✓ # Canonical Input Serialization with Msgpack and SHA-256 ## Context and Problem... |
| KEY | REQ002 ✓ 関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。 | ARCH002 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy... | SPEC006 ✓ ## インターフェース ```python class Strategy(Enum): DEFAULT = auto() # 再帰的正規化 ... | TST006 ✓ ## 目的 `KeyGenPolicy` は引数ごとのキー生成戦略をカスタマイズする機構であり、ログレベルやデバッグフラグのような非決定的引数をキーから除外し... tests/unit/test_cachekey.py | IMPL006 ✓ ## 実装概要 `Strategy` enum(DEFAULT, IGNORE, FILE_CONTENT, PATH_STAT の4種)と `KeyGenP... src/beautyspot/cachekey.py | ADR022 ✓ # Refactoring Complex Modules with Single Dispatch ## Context and Problem State... |
| STORE | REQ004 ✓ 大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。 | ARCH004 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol... | SPEC010 ✓ ## インターフェース ```python class S3Storage(BlobStorageMaintenable): def __init__... | TST010 ✓ ## 目的 `S3Storage` はクラウド環境での大規模Blob保存を担い、ネットワーク障害・認証エラー・バケット不在などオンプレミスにはない障害モードが... tests/integration/storage/test_s3.py | IMPL010 ✓ ## 実装概要 `S3Storage` クラスが `BlobStorageBase` を実装。 `s3://bucket/prefix/` 形式の URI を... src/beautyspot/storage.py | ADR044 ✓ # Drop Absolute Path Support in LocalStorage ## Context and Problem Statement /... |
| STORE | REQ004 ✓ 大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。 | ARCH004 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol... | SPEC010 ✓ ## インターフェース ```python class S3Storage(BlobStorageMaintenable): def __init__... | TST010 ✓ ## 目的 `S3Storage` はクラウド環境での大規模Blob保存を担い、ネットワーク障害・認証エラー・バケット不在などオンプレミスにはない障害モードが... tests/integration/storage/test_s3.py | IMPL010 ✓ ## 実装概要 `S3Storage` クラスが `BlobStorageBase` を実装。 `s3://bucket/prefix/` 形式の URI を... src/beautyspot/storage.py | ADR046 ✓ # Delegate Workspace Initialization to Storage Backends ## Context and Problem ... |
| STORE | REQ004 ✓ 大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。 | ARCH004 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol... | SPEC011 ✓ ## インターフェース ```python class StoragePolicyProtocol(Protocol): def should_sav... | TST011 ✓ ## 目的 ストレージポリシーは「データをDB直接保存するか、Blobストレージに分離するか」を決定する戦略レイヤーである。閾値判定の誤りはDBの肥大化(性能... tests/unit/test_storage_policy.py | IMPL011 ✓ ## 実装概要 `StoragePolicyProtocol` が `should_save_as_blob(data: bytes) -> bool` を定... src/beautyspot/storage.py | ADR044 ✓ # Drop Absolute Path Support in LocalStorage ## Context and Problem Statement /... |
| STORE | REQ004 ✓ 大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。 | ARCH004 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol... | SPEC011 ✓ ## インターフェース ```python class StoragePolicyProtocol(Protocol): def should_sav... | TST011 ✓ ## 目的 ストレージポリシーは「データをDB直接保存するか、Blobストレージに分離するか」を決定する戦略レイヤーである。閾値判定の誤りはDBの肥大化(性能... tests/unit/test_storage_policy.py | IMPL011 ✓ ## 実装概要 `StoragePolicyProtocol` が `should_save_as_blob(data: bytes) -> bool` を定... src/beautyspot/storage.py | ADR046 ✓ # Delegate Workspace Initialization to Storage Backends ## Context and Problem ... |
| STORE | REQ004 ✓ 大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。 | ARCH004 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol... | SPEC024 ✓ ## インターフェース ```python class BlobStorageBase(ABC): @abstractmethod def s... | TST024 ✓ ## 目的 `BlobStorageBase` は全ストレージバックエンド(LocalStorage, S3Storage, サードパーティ)が 準拠すべき抽... tests/integration/storage/test_blob_storage_base.py | IMPL024 ✓ ## 実装概要 `storage.py` 内の `BlobStorageBase` ABC が SPEC024 の中核実装。 5つの抽象メソッド(`save`... src/beautyspot/storage.py | ADR044 ✓ # Drop Absolute Path Support in LocalStorage ## Context and Problem Statement /... |
| STORE | REQ004 ✓ 大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。 | ARCH004 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol... | SPEC024 ✓ ## インターフェース ```python class BlobStorageBase(ABC): @abstractmethod def s... | TST024 ✓ ## 目的 `BlobStorageBase` は全ストレージバックエンド(LocalStorage, S3Storage, サードパーティ)が 準拠すべき抽... tests/integration/storage/test_blob_storage_base.py | IMPL024 ✓ ## 実装概要 `storage.py` 内の `BlobStorageBase` ABC が SPEC024 の中核実装。 5つの抽象メソッド(`save`... src/beautyspot/storage.py | ADR046 ✓ # Delegate Workspace Initialization to Storage Backends ## Context and Problem ... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... tests/unit/test_type_registry.py | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... src/beautyspot/serializer.py, src/beautyspot/core.py | ADR008 ✓ # Customizable Serialization Strategy with Msgpack Default ## Context and Probl... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... tests/unit/test_type_registry.py | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... src/beautyspot/serializer.py, src/beautyspot/core.py | ADR014 ✓ # Decorator-based Type Registration with Late Binding ## Context and Problem St... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... tests/unit/test_type_registry.py | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... src/beautyspot/serializer.py, src/beautyspot/core.py | ADR017 ✓ title: Nested Msgpack Protocol via Serializer Wrapping status: Accepted date: 20... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... tests/unit/test_type_registry.py | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... src/beautyspot/serializer.py, src/beautyspot/core.py | ADR019 ✓ # Per-Task Serializer Override ## Context and Problem Statement / コンテキスト プロジェク... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... tests/unit/test_type_registry.py | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... src/beautyspot/serializer.py, src/beautyspot/core.py | ADR025 ✓ # Protocol-based Serializer Registration ## Context and Problem Statement / コンテ... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... tests/unit/test_type_registry.py | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... src/beautyspot/serializer.py, src/beautyspot/core.py | ADR039 ✓ # Bounded LRU Cache for Subclass Resolution in Serializer ## Context and Proble... |
| SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... tests/unit/test_type_registry.py | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... src/beautyspot/serializer.py, src/beautyspot/core.py | ADR042 ✓ # Thread-safe LRU Cache for Serializer ## Context and Problem Statement / コンテキス... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... tests/integration/core/test_exit_drain.py | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... src/beautyspot/core.py | ADR024 ✓ # Non-blocking Cache Persistence and Task Tracking ## Context and Problem State... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... tests/integration/core/test_exit_drain.py | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... src/beautyspot/core.py | ADR033 ✓ # 非同期保存処理におけるエラー可視化とコンテキストの導入 ## Context and Problem Statement / コンテキスト beauty... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... tests/integration/core/test_exit_drain.py | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... src/beautyspot/core.py | ADR036 ✓ # バックグラウンドループのライフサイクル管理とGC時のデータロスト対策 ## Context and Problem Statement / コンテキスト ... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... tests/integration/core/test_exit_drain.py | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... src/beautyspot/core.py | ADR037 ✓ # GC時のデータロスト防止とバックグラウンドループのシャットダウン戦略 ## Context and Problem Statement / コンテキスト ... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... tests/integration/core/test_exit_drain.py | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... src/beautyspot/core.py | ADR040 ✓ # Background Loop Shutdown Strategy and Threading Model ## Context and Problem ... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... tests/integration/core/test_exit_drain.py | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... src/beautyspot/core.py | ADR047 ✓ # Background Loop Shutdown Strategy on Garbage Collection ## Context and Proble... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... tests/integration/core/test_exit_drain.py | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... src/beautyspot/core.py | ADR049 ✓ # 実行中タスクの強参照セットによる追跡 ## Context and Problem Statement / コンテキスト `Spot` インスタンスは、... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... tests/integration/core/test_exit_drain.py | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... src/beautyspot/core.py | ADR053 ✓ # Background Loop and Executor Shutdown Sequence ## Context and Problem Stateme... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... tests/integration/core/test_exit_drain.py | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... src/beautyspot/core.py | ADR001 ✓ # Manage Executor Lifecycle via Instance Ownership and Weak References ## Conte... |
| LIFE | REQ007 ✓ キャッシュの有効期限を関数名パターンに基づくルールで設定できること。個別の関数に対して保持期間を指定でき、期限切れのキャッシュを自動的に無効化できること。 | ARCH007 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|マッチング要求| B[LifecyclePolicy]... | SPEC017 ✓ ## インターフェース ```python def parse_retention(val: Any) -> float | None: ... ``` #... | TST017 ✓ ## 目的 `Retention` はキャッシュの有効期間をユーザーフレンドリーな形式で指定する値オブジェクトである。パースの不備は意図しない保持期間(例: ... tests/unit/test_lifecycle_extended.py | IMPL017 ✓ ## 実装概要 `Retention` クラスを名前空間として使用し、保持期間のパースや 特殊な定数(`INDEFINITE`, `FOREVER`)を管理す... src/beautyspot/lifecycle.py | ADR038 ✓ # Probabilistic Auto-Eviction for Storage Maintenance ## Context and Problem St... |
| リンク方向 | グループ | カバー数 | カバー率 | 未カバーアイテム |
|---|---|---|---|---|
| ARCH → REQ | — | 14 / 14 | 100.0% | — |
| ARCH → REQ | BGIO | 1 / 1 | 100.0% | — |
| ARCH → REQ | CACHE | 1 / 1 | 100.0% | — |
| ARCH → REQ | CLI | 1 / 1 | 100.0% | — |
| ARCH → REQ | DB | 2 / 2 | 100.0% | — |
| ARCH → REQ | DI | 1 / 1 | 100.0% | — |
| ARCH → REQ | HERD | 1 / 1 | 100.0% | — |
| ARCH → REQ | HOOK | 1 / 1 | 100.0% | — |
| ARCH → REQ | KEY | 1 / 1 | 100.0% | — |
| ARCH → REQ | LIFE | 1 / 1 | 100.0% | — |
| ARCH → REQ | LIMIT | 1 / 1 | 100.0% | — |
| ARCH → REQ | MAINT | 1 / 1 | 100.0% | — |
| ARCH → REQ | SERIAL | 1 / 1 | 100.0% | — |
| ARCH → REQ | STORE | 1 / 1 | 100.0% | — |
| SPEC → ARCH | — | 13 / 13 | 100.0% | — |
| SPEC → ARCH | BGIO | 1 / 1 | 100.0% | — |
| SPEC → ARCH | CACHE | 1 / 1 | 100.0% | — |
| SPEC → ARCH | CLI | 1 / 1 | 100.0% | — |
| SPEC → ARCH | DB | 1 / 1 | 100.0% | — |
| SPEC → ARCH | DI | 1 / 1 | 100.0% | — |
| SPEC → ARCH | HERD | 1 / 1 | 100.0% | — |
| SPEC → ARCH | HOOK | 1 / 1 | 100.0% | — |
| SPEC → ARCH | KEY | 1 / 1 | 100.0% | — |
| SPEC → ARCH | LIFE | 1 / 1 | 100.0% | — |
| SPEC → ARCH | LIMIT | 1 / 1 | 100.0% | — |
| SPEC → ARCH | MAINT | 1 / 1 | 100.0% | — |
| SPEC → ARCH | SERIAL | 1 / 1 | 100.0% | — |
| SPEC → ARCH | STORE | 1 / 1 | 100.0% | — |
| TST → SPEC | — | 24 / 24 | 100.0% | — |
| TST → SPEC | BGIO | 2 / 2 | 100.0% | — |
| TST → SPEC | CACHE | 3 / 3 | 100.0% | — |
| TST → SPEC | CLI | 1 / 1 | 100.0% | — |
| TST → SPEC | DB | 2 / 2 | 100.0% | — |
| TST → SPEC | DI | 1 / 1 | 100.0% | — |
| TST → SPEC | HERD | 1 / 1 | 100.0% | — |
| TST → SPEC | HOOK | 1 / 1 | 100.0% | — |
| TST → SPEC | KEY | 3 / 3 | 100.0% | — |
| TST → SPEC | LIFE | 2 / 2 | 100.0% | — |
| TST → SPEC | LIMIT | 1 / 1 | 100.0% | — |
| TST → SPEC | MAINT | 1 / 1 | 100.0% | — |
| TST → SPEC | SERIAL | 2 / 2 | 100.0% | — |
| TST → SPEC | STORE | 4 / 4 | 100.0% | — |
| IMPL → SPEC | — | 24 / 24 | 100.0% | — |
| IMPL → SPEC | BGIO | 2 / 2 | 100.0% | — |
| IMPL → SPEC | CACHE | 3 / 3 | 100.0% | — |
| IMPL → SPEC | CLI | 1 / 1 | 100.0% | — |
| IMPL → SPEC | DB | 2 / 2 | 100.0% | — |
| IMPL → SPEC | DI | 1 / 1 | 100.0% | — |
| IMPL → SPEC | HERD | 1 / 1 | 100.0% | — |
| IMPL → SPEC | HOOK | 1 / 1 | 100.0% | — |
| IMPL → SPEC | KEY | 3 / 3 | 100.0% | — |
| IMPL → SPEC | LIFE | 2 / 2 | 100.0% | — |
| IMPL → SPEC | LIMIT | 1 / 1 | 100.0% | — |
| IMPL → SPEC | MAINT | 1 / 1 | 100.0% | — |
| IMPL → SPEC | SERIAL | 2 / 2 | 100.0% | — |
| IMPL → SPEC | STORE | 4 / 4 | 100.0% | — |
| ADR → REQ | — | 13 / 14 | 92.9% | REQ013 |
| ADR → REQ | BGIO | 1 / 1 | 100.0% | — |
| ADR → REQ | CACHE | 1 / 1 | 100.0% | — |
| ADR → REQ | CLI | 1 / 1 | 100.0% | — |
| ADR → REQ | DB | 2 / 2 | 100.0% | — |
| ADR → REQ | DI | 1 / 1 | 100.0% | — |
| ADR → REQ | HERD | 0 / 1 | 0.0% | REQ013 |
| ADR → REQ | HOOK | 1 / 1 | 100.0% | — |
| ADR → REQ | KEY | 1 / 1 | 100.0% | — |
| ADR → REQ | LIFE | 1 / 1 | 100.0% | — |
| ADR → REQ | LIMIT | 1 / 1 | 100.0% | — |
| ADR → REQ | MAINT | 1 / 1 | 100.0% | — |
| ADR → REQ | SERIAL | 1 / 1 | 100.0% | — |
| ADR → REQ | STORE | 1 / 1 | 100.0% | — |
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。
抽象インターフェースにより、DBやストレージなどのバックエンド実装を差し替え可能であること。
親: —
子: ADR007, ADR010, ADR043, ADR045, ADR048, ADR050, ARCH003
データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLiteの書き込みスレッドがスタックした場合でも、フェイルファストによって制御を戻せること。
親: —
子: ADR051, ADR052, ARCH003, TST007
関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。
親: —
子: ADR012, ADR013, ADR015, ADR034, ARCH001
関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。
親: —
子: ADR002, ADR011, ADR022, ARCH002
大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。
DB直接保存とBlob保存を自動判定できること。
親: —
子: ADR030, ADR044, ADR046, ARCH004
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
親: —
子: ADR004, ADR008, ADR014, ADR017, ADR019, ADR025, ADR039, ADR042, ARCH005
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
親: —
子: ADR001, ADR005, ADR024, ADR033, ADR036, ADR037, ADR040, ADR047, ADR049, ADR053, ARCH006
キャッシュの有効期限を関数名パターンに基づくルールで設定できること。個別の関数に対して保持期間を指定でき、期限切れのキャッシュを自動的に無効化できること。
親: —
関数の実行前、キャッシュヒット時、キャッシュミス時にカスタムコールバック(フック)を実行できること。スレッドセーフなフック実装を提供すること。
親: —
期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除(prune)等のメンテナンス操作ができること。
親: —
子: ADR016, ADR018, ADR020, ADR021, ADR023, ADR029, ADR041, ARCH010
コマンドラインインターフェースからキャッシュの一覧表示、詳細確認、統計表示、削除、GC等の管理操作ができること。
親: —
子: ADR006, ADR027, ADR028, ARCH011
全コンポーネント(DB、ストレージ、シリアライザ、リミッター等)が Protocol/ABC に基づく依存注入により差し替え可能であること。ファクトリ関数がデフォルトの組み立てを提供すること。
親: —
同一キャッシュキーに対する並行リクエストを直列化し、1つのリクエストのみが関数を実行し、他のリクエストはその結果を共有することで重複実行を防止できること。
親: —
子: ARCH013
データパイプラインや機械学習の実験、外部API呼び出し等において、同一入力での不要な再実行を防ぐことで、実行時間の短縮とリソースの節約を実現する。開発者が最小限のコード変更で強力なキャッシュ・レート制限機能を導入できることを目指す。
親: —
子: —
Beautyspot: 本キャッシュライブラリの名称。
Spot: 本ライブラリにおけるキャッシュ・レート制限のコアエンジンの名称。
キャッシュキー (Cache Key): 関数の入力引数等から一意に生成される識別子。
キャッシュヒット: 保存された結果を再利用し、元の関数を実行しないこと。
キャッシュミス: 結果が未保存または無効のため、元の関数を実行すること。
メタデータDB (Metadata DB): キャッシュの有効期限、サイズ、関数名などの付帯情報を保存するデータストア。
Blobストレージ (Blob Storage): キャッシュの実際のペイロード(戻り値のシリアライズ結果)を保存するストレージ。
シリアライザ (Serializer): 関数の戻り値をストレージに保存可能なバイト列等に変換、および復元するコンポーネント。
バックグラウンドIO (Background IO): メインスレッド(またはタスク)の実行をブロックせず、非同期にキャッシュ保存などを行う仕組み。
レート制限 (Rate Limiting): 外部API呼び出し等の頻度を制御する仕組み。トークンバケット方式を採用。
Thundering Herd: キャッシュミス時に、多数の並行リクエストが同時に同一関数を実行しようとして過負荷になる現象。
フック (Hook): キャッシュのライフサイクル(ヒット、ミス、保存前後等)に介入できるコールバック機構。
依存注入 (Dependency Injection / DI): 各コンポーネント(DB、ストレージ等)を外部から差し替え可能にする設計パターン。
仕様書アイテムに付与される groups 属性の意味は以下の通りです。
グループ名 | 意味 / 対象領域 |
|---|---|
| 関数キャッシュの基本機能(同期・非同期のデコレータなど) |
| キャッシュキーの生成ロジック(引数のハッシュ化など) |
| メタデータDB(有効期限、メタ情報の管理) |
| Blobストレージ(ペイロードの保存) |
| シリアライゼーション(データの変換処理) |
| バックグラウンドIO(非同期書き込み処理) |
| ライフサイクル管理(有効期限の判定、無効化ルールなど) |
| レート制限(トークンバケット方式等) |
| コールバックフックの仕組み |
| メンテナンス(キャッシュの削除、ガベージコレクション) |
| コマンドラインインターフェース |
| 依存注入の仕組みとデフォルトファクトリ |
| Thundering Herd対策(直列化、重複実行防止) |
| 共通定義(用語集など、特定の機能に縛られないもの) |
親: —
子: —
classDiagram
class TaskDBCore {
<<Protocol>>
+init_schema() void
+get(cache_key) TaskRecord
+save(cache_key, ...) void
+delete(cache_key) bool
}
class Maintenable {
<<Protocol>>
+delete_expired() int
+prune(older_than) int
+delete_all() int
}
class TaskDBMaintenable {
<<Protocol>>
}
TaskDBCore <|-- TaskDBMaintenable
Maintenable <|-- TaskDBMaintenable
class SQLiteTaskDB {
-db_path: Path
-_write_queue: Queue
+init_schema() void
+get(cache_key) TaskRecord
+save(cache_key, ...) void
}
TaskDBMaintenable <|-- SQLiteTaskDB
| コンポーネント | 責務 | インターフェース |
|---|---|---|
TaskDBCore |
キャッシュ実行時に必要な最小限のメタデータDBアクセス | init_schema(), get(), save(), delete() |
Maintenable |
GCやCLI等、運用・保守に必要な拡張操作 | delete_expired(), prune(), delete_all() |
TaskDBMaintenable |
実行用と保守用の両方を備えた上位Protocol | TaskDBCore + Maintenable |
SQLiteTaskDB |
SQLiteを用いたデフォルト実装。非同期書き込みキューを内包 | TaskDBMaintenable 実装 |
sequenceDiagram
participant Spot as core.Spot
participant DB as SQLiteTaskDB
participant Q as WriteQueue
participant Thread as WriterThread
participant SQLite as SQLiteDB
Spot->>DB: get(cache_key)
DB->>SQLite: SELECT
SQLite-->>DB: TaskRecord
DB-->>Spot: TaskRecord
Spot->>DB: save(metadata)
DB->>Q: put(WriteTask)
DB-->>Spot: void (Non-blocking)
Thread->>Q: get()
Thread->>SQLite: INSERT / UPDATE
| 技術領域 | 選定 | 理由 |
|---|---|---|
| デフォルトメタデータDB | SQLite | ゼロ設定、組み込み可能、ローカルキャッシュとして十分な性能 |
| バックグラウンド書き込み | キュー + 専用スレッド | SQLiteの排他制御によるメインスレッドのブロックを防ぐため |
| 抽象化 | ProtocolベースのDI |
TaskDBCoreを満たせばPostgreSQL等に容易に差し替え可能 |
TaskDBCore, Maintenable) で定義され、利用側(core.Spotや_CacheManager)は実装に依存しない。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
graph TD
A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy]
B -->|バインド| C[bound_keygen]
C -->|引数別戦略| D[Strategy]
C -->|正規化| E[canonicalize]
C -->|ハッシュ計算| F[KeyGen.hash_items]
E --> F
| コンポーネント | 責務 | インターフェース |
|---|---|---|
KeyGenPolicy |
関数の引数に対するハッシュ化戦略(無視、ファイル内容等)の宣言的定義と保持。 | bind(func) |
Strategy |
引数ごとの処理方法(DEFAULT, IGNORE, FILE_CONTENT, PATH_STAT)の定義。 | - |
canonicalize |
Pythonオブジェクトを再帰的に正規化し、Msgpackで安定してシリアライズ可能な状態に変換。 | @singledispatch canonicalize(obj) |
KeyGen |
正規化されたリストからSHA-256ハッシュを生成。レガシーデフォルトのハッシュ生成も担う。 | hash_items(items), from_file_content(path) |
sequenceDiagram
participant Spot as core.Spot
participant KP as KeyGenPolicy
participant Sig as inspect.signature
participant Can as canonicalize
participant KG as KeyGen
Spot->>KP: bind(func)
KP->>Sig: signature(func)
Sig-->>KP: bound_keygen 生成
KP-->>Spot: bound_keygen
Spot->>Spot: bound_keygen(*args, **kwargs)
loop 各引数
alt Strategy.IGNORE
Spot->>Spot: スキップ
else Strategy.FILE_CONTENT
Spot->>KG: from_file_content(path)
else Strategy.PATH_STAT
Spot->>KG: from_path_stat(path)
else Strategy.DEFAULT
Spot->>Can: canonicalize(val)
Can-->>Spot: 正規化済みデータ
end
end
Spot->>KG: hash_items(items_to_hash)
KG-->>Spot: SHA-256 ハッシュ文字列 (キャッシュキー)
| 技術領域 | 選定 | 理由 |
|---|---|---|
| 引数のバインド | inspect.signature |
argsとkwargsを定義順に正確にマッピングし、デフォルト値も適用するため |
| 正規化 | functools.singledispatch |
型ごとの正規化ロジックを拡張可能かつクリーンに実装するため |
| シリアライズ | msgpack |
高速でバイナリセーフであり、一貫したバイト列表現を得るため |
| ハッシュ関数 | SHA-256 | 衝突確率が極めて低く、暗号学的に安全かつ広くサポートされているため |
KeyGenPolicy を定義可能にし、柔軟なキャッシュ戦略をサポートする。親: REQ002
graph TD
A[core.Spot] -->|判定| B[StoragePolicyProtocol];
A -->|保存/読込| C[BlobStorageBase];
D[LocalStorage] --> C;
E[S3Storage] --> C;
A -->|メタデータ| F[TaskDBBase];
| コンポーネント | 責務 | インターフェース |
|---|---|---|
| StoragePolicyProtocol | データのサイズ等に基づき、DB保存かBlob保存かを判定する | should_save_as_blob() |
| BlobStorageBase | 大規模データの外部ストレージ保存を抽象化する | save(), load(), delete() |
| LocalStorage | ローカルファイルシステムへのアトミックな保存と検証 | base_dir, os.replace |
| S3Storage | AWS S3互換オブジェクトストレージへの保存 | s3:// URI, boto3 |
sequenceDiagram
participant Spot as core.Spot
participant Policy as StoragePolicy
participant DB as TaskDB
participant Blob as BlobStorage
Spot->>Policy: should_save_as_blob(data)
alt Policy: True (Blob保存)
Spot->>Blob: save(key, data)
Blob-->>Spot: location (path/URI)
Spot->>DB: insert(metadata, storage_type=FILE, blob_key=location)
else Policy: False (Inline保存)
Spot->>DB: insert(metadata, storage_type=DIRECT_BLOB, result_data=data)
end
| 技術領域 | 選定 | 理由 |
|---|---|---|
| 保存判定 | 閾値ベース (Threshold) | DBの肥大化を防ぎつつ、小容量データのI/Oを最適化 |
| アトミック性 | 一時ファイル + Rename | 保存中のクラッシュや並行書き込みによる破損を完全に防止 |
| クラウド対応 | Boto3 (S3) | 業界標準のプロトコルによる高い互換性とスケーラビリティ |
安全性: LocalStorage では is_relative_to によるパストラバーサル防止を徹底
信頼性: 保存時は fsync を実行し、OSレベルでのデータ到達を確認
効率性: S3 保存時は upload_fileobj を使用し、5GB超のマルチパートアップロードに自動対応
親: REQ004
子: SPEC009, SPEC010, SPEC011, SPEC024
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
graph TD
A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop]
B -->|Daemon Thread| C[asyncio.AbstractEventLoop]
C -->|I/O| D[TaskDB / BlobStorage]
A -->|ライフサイクル制御| E[atexit / ContextManager]
| コンポーネント | 責務 | インターフェース |
|---|---|---|
| _BackgroundLoop | デーモンスレッドでのイベントループ管理とタスク投入 | submit(), run_forever() |
| core.Spot | 非同期保存のトリガーとエラーハンドリング | _save_metadata_async() |
| ContextManager | 処理終了時のタスク完了待機(Flush) | __exit__, flush() |
sequenceDiagram
participant Main as メインスレッド
participant BGLoop as _BackgroundLoop
participant Store as ストレージ
Main->>BGLoop: submit(save_coro)
Note over Main: 関数は即座に結果を返す (save_sync=False)
BGLoop->>Store: 書き込み実行 (I/O)
alt 成功
Store-->>BGLoop: OK
else 失敗
BGLoop->>Main: on_background_error(error)
end
| 技術領域 | 選定 | 理由 |
|---|---|---|
| 並行処理 | asyncio in Thread | GILを解放しつつ、多数のI/Oタスクを効率的に多重化 |
| スレッド種類 | Daemon Thread | アプリケーション終了時にプロセスをブロックしない |
| 終了制御 | atexit / drain | 強制終了時も、可能な限り保留中の書き込みを完了させる |
透過性: 保存失敗時もユーザーコードの例外にはせず、コールバック経由で通知
一貫性: flush() により、クリティカルな処理の直後での保存完了を保証
安全性: シャットダウンシーケンス中に新規タスクを受け付けない排他制御を導入
親: REQ006
graph TD
A[core.Spot] -->|マッチング要求| B[LifecyclePolicy]
B -->|ルールリスト| C[Rule]
C -->|パターン照合| D[fnmatch]
B -->|パース| E[Retention]
| コンポーネント | 責務 | インターフェース |
|---|---|---|
| LifecyclePolicy | 複数のルールを管理し、関数名に最適な有効期限を決定する | resolve_with_fallback() |
| Rule | 1つのパターンとそれに対応する保持期間のペア | match(func_name) |
| Retention | 多様な時間表現(文字列/数値)を秒数に正規化する | parse_retention() |
sequenceDiagram
participant Spot as core.Spot
participant Policy as LifecyclePolicy
participant DB as TaskDB
Spot->>Policy: resolve_with_fallback("module.func")
Policy->>Policy: ルール順次照合 (fnmatch)
Policy-->>Spot: expires_at (timestamp)
Spot->>DB: save(..., expires_at)
Note over DB: get() 時に現在時刻と比較判定
| 技術領域 | 選定 | 理由 |
|---|---|---|
| パターンマッチ | fnmatch (Glob) | 正規表現よりも直感的で、開発者にとって馴染みのある指定方法 |
| 保持期間指定 | 文字列パーサー ("30d"等) | 設定ファイルやデコレータでの可読性を向上 |
| 判定タイミング | 遅延判定 (Lazy) | 書き込み・読み込み時のオーバーヘッドを最小化 |
Retention.FOREVER 等の定数により、グローバルポリシーの個別上書きに対応親: REQ007
graph TD
A["@spot.consume"] -->|宣言的適用| B[LimiterProtocol]
C[core.Spot] -->|DI| B
D[TokenBucket] --> B
D -->|アルゴリズム| E[GCRA]
D -->|時刻同期| F[monotonic clock]
| コンポーネント | 責務 | インターフェース |
|---|---|---|
| TokenBucket | GCRAアルゴリズムによるスムースなトラフィック制御 | consume(), consume_async() |
| @spot.consume | 関数実行前のトークン消費を透過的に行うデコレータ | cost (int or callable) |
| LimiterProtocol | レートリミッターの差し替えを可能にするインターフェース | LimiterProtocol |
sequenceDiagram
participant User as ユーザーコード
participant Dec as @spot.consume
participant Bucket as TokenBucket
User->>Dec: call func()
Dec->>Bucket: consume(cost)
Bucket->>Bucket: 次回実行許可時刻の計算 (GCRA)
alt 許可
Bucket-->>Dec: OK
Dec->>User: execute func()
else 拒否 (Over rate)
Bucket-->>Dec: raise ValueError/RateLimitError
end
| 技術領域 | 選定 | 理由 |
|---|---|---|
| アルゴリズム | GCRA (Generic Cell Rate Algorithm) | 固定ウィンドウと違い「スムースな」流量制御が可能 |
| 待機制御 | asyncio.sleep / time.sleep | 同期・非同期の両方のコンテキストで正確なスロットリングを実現 |
| コスト計算 | ダイナミックコスト | 引数に応じて消費トークン数を動的に変更可能 |
正確性: time.monotonic() を使用し、システム時刻の変更(NTP同期等)の影響を受けない
バースト制御: max_burst 設定により、アイドル後の過度なバーストを防止
スレッド安全: threading.Lock により、マルチスレッド環境での二重消費を防止
親: REQ008
子: SPEC018
graph TD
A[core.Spot] -->|イベント通知| B[HookBase]
C[ThreadSafeHookBase] --> B
B -->|コンテキスト| D[Context Objects]
E[PreExecuteContext] --> D
F[CacheHitContext] --> D
G[CacheMissContext] --> D
| コンポーネント | 責務 | インターフェース |
|---|---|---|
| HookBase | 実行ライフサイクルの各段階で呼び出される抽象基底クラス | pre_execute, on_cache_hit, on_cache_miss |
| ThreadSafeHookBase | RLockを用いて各フックメソッドの実行を直列化する | __init_subclass__ による自動ラップ |
| Context Objects | フックに渡される実行時の引数、結果、例外、メタデータのコンテナ | ctx.args, ctx.result 等 |
sequenceDiagram
participant Spot as core.Spot
participant Hook as HookBase
participant Fn as Target Function
Spot->>Hook: pre_execute(ctx)
alt キャッシュヒット
Spot->>Hook: on_cache_hit(ctx)
else キャッシュミス
Spot->>Fn: execute()
Fn-->>Spot: result
Spot->>Hook: on_cache_miss(ctx)
end
| 技術領域 | 選定 | 理由 |
|---|---|---|
| パターン | オブザーバー | コアロジックを汚さずに、ログ出力や統計取得等の横断的関心を分離 |
| スレッド安全 | 自動装飾 (Decorator) | ユーザーが意識せずに、サブクラス化するだけで安全なフックを実装可能 |
| 実行制御 | 準同期実行 | フック内の例外をキャッチしログ出力に留めることで、主処理を保護 |
透過性: フックの実行失敗(例外)は主処理の結果に影響を与えない
柔軟性: 関数ごとに異なるフックリストを hooks=[...] で指定可能
パフォーマンス: フック未登録時のオーバーヘッドを最小化するガード条件を実装
親: REQ009
子: SPEC019
graph TD
A[MaintenanceService] -->|操作集約| B[TaskDBBase]
A -->|操作集約| C[BlobStorageBase]
A -->|自動実行| D[Probabilistic Eviction]
| コンポーネント | 責務 | インターフェース |
|---|---|---|
| MaintenanceService | DBとストレージを跨ぐクリーンアップ操作のファサード | clean_garbage(), prune() |
| TaskDBBase | 参照カウントや期限切れレコードの提供 | get_blob_refs(), delete_expired() |
| BlobStorageBase | 物理ファイルの列挙と削除 | list_keys(), delete() |
sequenceDiagram
participant Service as MaintenanceService
participant DB as TaskDB
participant Storage as BlobStorage
Service->>DB: get_blob_refs()
DB-->>Service: 有効な参照リスト (Whitelist)
Service->>Storage: list_keys()
Storage-->>Service: 全ファイルリスト
Service->>Service: 差分抽出 (孤立ファイルの特定)
Service->>Storage: delete(orphan_keys)
Note over Service: 猶予期間 (grace_period) を考慮して削除
| 技術領域 | 選定 | 理由 |
|---|---|---|
| 削除ポリシー | ホワイトリスト方式 | DBに記録がないBlobを削除対象とすることで、データの整合性を担保 |
| 競合防止 | 更新時刻確認 (mtime) | 保存中のファイルを誤って消さないよう、一定時間経過した孤立のみ削除 |
| 実行頻度 | 確率的オートエビクション | 書き込み時に低確率でGCをキックし、手動メンテなしでの健康度を維持 |
親: REQ010
子: SPEC020
graph TD
A[beautyspot CLI] -->|Command| B[Typer App]
B -->|Business Logic| C[MaintenanceService]
B -->|Output| D[Rich Console]
B -->|Dashboard| E[Dashboard App]
| コンポーネント | 責務 | インターフェース |
|---|---|---|
| Typer App | コマンドライン引数のパースとサブコマンドのディスパッチ | main(), gc(), list() |
| MaintenanceService | 実際のキャッシュ管理ロジックの実行 | clean_garbage(), clear() |
| Rich Console | ターミナルでの視覚的に分かりやすいテーブルやパネルの描画 | Console, Table |
| Dashboard App | キャッシュ状態をインタラクティブに閲覧するためのTUI | dashboard.py |
sequenceDiagram
participant User as ユーザー
participant CLI as CLI App
participant Service as MaintenanceService
participant Output as Rich Console
User->>CLI: beautyspot gc --name my-project
CLI->>Service: clean_garbage()
Service-->>CLI: result (removed count, etc.)
CLI->>Output: print summary table
Output-->>User: formatted output
| 技術領域 | 選定 | 理由 |
|---|---|---|
| フレームワーク | Typer | 型ヒントに基づいた堅牢なコマンドラインインターフェースを迅速に構築 |
| 出力装飾 | Rich | プログレスバーやステータス表示により、長時間処理の進捗を可視化 |
| 連携 | Factory DI | カレントディレクトリや設定から自動的に Spot インスタンスを組み立て |
clear等)には対話的な確認プロンプトを表示親: REQ011
子: SPEC021
graph TD
A[bs.Spot Factory] -->|構築| B[core.Spot]
A -->|DI| C[TaskDBBase]
A -->|DI| D[BlobStorageBase]
A -->|DI| E[SerializerProtocol]
A -->|DI| F[StoragePolicyProtocol]
A -->|DI| G[LimiterProtocol]
| コンポーネント | 責務 | インターフェース |
|---|---|---|
| bs.Spot (Factory) | 環境(名前、パス等)に応じたデフォルトコンポーネントの選定と組み立て | bs.Spot() |
| core.Spot | 注入された依存関係を使用して、キャッシュロジックをオーケストレーションする | mark(), cached_run() |
| Protocols / ABCs | 各コンポーネントが満たすべき契約(インターフェース)の定義 | SerializerProtocol 等 |
| 技術領域 | 選定 | 理由 |
|---|---|---|
| パターン | Constructor Injection | 依存関係を明示的に渡し、テスト時のMock差し替えを容易にする |
| インターフェース | Protocol (Duck Typing) | 厳密な継承を強制せず、構造的部分型によりサードパーティ実装を受け入れ |
| デフォルト設定 | 規約より構成 (CoC) | .beautyspot/ ディレクトリを基点とした標準パスを自動生成 |
core.Spot は具体的なクラス名(SQLite等)を知らず、Protocolのみに依存する親: REQ012
子: SPEC022
graph TD
A[core.Spot] -->|管理要求| B[CacheManager]
B -->|In-flight追跡| C[_inflight Dict]
C -->|同期待機| D[threading.Event]
C -->|非同期待機| E[asyncio.Future]
コンポーネント責務インターフェースCacheManagerキャッシュキーごとの実行状態管理と実行のシリアライズget_or_create_inflight()_inflight実行中のタスクを保持し、後続リクエストをイベントで待機させるExecutionStatecore.Spot待機結果の受け取りとエラーの伝播cached_run()
sequenceDiagram
participant T1 as Thread 1 (First)
participant T2 as Thread 2 (Second)
participant CM as CacheManager
participant Fn as Target Function
T1->>CM: get_or_create(key)
Note over CM: 新規作成 (Executor)
T2->>CM: get_or_create(key)
Note over CM: 既存あり (Waiter)
T1->>Fn: execute()
Fn-->>T1: result
T1->>CM: set_result(key, result)
Note over CM: Eventをセット
CM-->>T2: notify result
T1-->>T1: return result
T2-->>T2: return result
技術領域選定理由待機機構Event / FutureOS/ランタイムレベルの待機を使用し、CPU負荷(ビジーループ)を回避スコープキャッシュキー単位異なる関数の実行は妨げず、同一入力のみを直列化安全策タイムアウト & リトライ実行者がハングした場合に、待機側がデッドロックしないよう保護
一貫性: 実行者が成功すれば全員に結果を、失敗すれば全員に同じ例外を伝播
効率性: メモリリークを防ぐため、結果配布後に _inflight エントリを即座に削除
信頼性: 強参照 (StrongReference) で実行中のタスクを保持し、GCによる消失を防止
親: REQ013
子: SPEC023
class SQLiteTaskDB(TaskDBBase):
def __init__(self, db_path: str | Path, timeout: float = 30.0): ...
def save(self, task: TaskRecord) -> None: ...
def get(self, cache_key: str, *, include_expired: bool = False) -> TaskRecord | None: ...
_WriteTask キューに投入され、専用のバックグラウンドスレッドで直列に実行される。これにより SQLite の database is locked エラーを回避する。PRAGMA query_only = ON 状態で並行して実行される。tasks テーブルの存在を確認し、必要に応じて自動的に ALTER TABLE によるカラム追加(マイグレーション)を行う。timeout 秒以内に完了する必要がある。timeout を超えた場合、タスクはキューから破棄(キャンセル)され、呼び出し元に TimeoutError を返す。timeout を超えた場合、sqlite3.Connection.interrupt() を用いて実行中のクエリを中断し、呼び出し元に TimeoutError を返す。これにより、ライタースレッドの恒久的なハングを防ぎ、システムの可用性を維持する。| 設定 | デフォルト | 説明 |
|---|---|---|
journal_mode |
WAL |
書き込みと読み取りの並行性を高める設定 |
synchronous |
NORMAL |
パフォーマンスと耐久性のバランス設定 |
timeout |
30.0 |
データベースロック待機およびクエリ実行のタイムアウト閾値 |
TimeoutError を送出する。sqlite3.Error をキャッチし、適切にラップして再送出する。:memory: 指定時は、プロセスの生存期間中のみキャッシュが保持される。親: ARCH003
class CacheManager:
def herd_sync(
self, key: str, serializer: Optional[SerializerProtocol] = None
) -> Generator[HerdWaitResult, None, None]: ...
async def herd_async(
self,
key: str,
serializer: Optional[SerializerProtocol],
loop: asyncio.AbstractEventLoop,
executor: Any,
) -> AsyncGenerator[HerdWaitResult, None]: ...
_inflight 辞書をチェックするHerdWaitResult を新規作成し、自分が「実行者 (Executor)」となるHerdWaitResult を返し、自分は「待機者 (Waiter)」となるHerdWaitResult の Event (または Future) をセットし、結果を共有するwith または async with ブロックを抜ける際、実行者は自動的に notify_and_cleanup_inflight を呼び出し、インフライト状態を解除する。これにより例外発生時も状態が残留しないことが保証される。実行中のタスク状態(イベント・結果・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)] 形式 |
herd_sync / herd_async コンテキスト開始時に _inflight にキーが存在しない場合、
_inflight_lock を保持した状態で新規タプルを挿入する_inflight 辞書がタプルへの唯一の管理参照を保持する。
待機者はロック取得時にタプル要素への参照を取得するが、_inflight エントリが正規の所有者である__exit__ / __aexit__)時に notify_and_cleanup_inflight で以下の順序で解放する:
a. _inflight_lock を取得し、エントリの同一性を event の identity (is) で確認する
b. 辞書からエントリを削除する(del _inflight[cache_key])
c. ロックを解放した後、event.set() で同期待機者に通知する
d. futures リスト内の各 asyncio.Future に結果またはエラーを伝播する_inflight 辞書への挿入・削除は常に _inflight_lock の保護下で行う_inflight 内には高々1つのエントリしか存在しないevent と同一オブジェクト(is 比較)を持つスレッドのみが行えるresult_box への書き込みは実行者スレッドのみが行い、待機者は読み取り専用とするfutures リストへの追加は _inflight_lock の保護下でのみ行う_inflight 辞書は CacheManager インスタンスの属性であり、CacheManager が
生存している限り、全てのインフライトエントリは GC の対象にならない。
CacheManager 自体は Spot インスタンスが所有するため、with spot: ブロック内での
安全性が保証される。
| 内部定数 | 値 | 説明 |
|---|---|---|
HERD_TIMEOUT |
300.0 |
待機者が実行者の完了を待つ最大秒数 |
HERD_MAX_RETRIES |
3 |
実行者が失敗またはタイムアウトした際のリトライ回数 |
親: ARCH013
@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): ...
inspect.iscoroutinefunction(fn) で判定する_execute_sync() に委譲する同期ラッパーを返す_execute_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 |
関数単位のフックリスト(実行前後やキャッシュヒット時に発火) |
isgeneratorfunction または isasyncgenfunction)を渡した場合 → デコレーション時に ConfigurationError を送出するcached_run 利用時に引数として関数が一つも指定されなかった場合 → ValidationError を送出する__name__, __doc__ は functools.wraps によって保持される@mark (括弧なし) と @mark() (括弧あり) の両方の記法をサポートする親: ARCH001
@contextmanager
def cached_run(
self,
*funcs: Any,
**kwargs: Any
) -> Iterator[Any]: ...
Spot インスタンスからコンテキストマネージャとして呼び出される。funcs に渡された関数が0個の場合はエラーを送出する。Spot.mark(**kwargs) で生成したキャッシュデコレータを適用し、ラップされた関数を生成する。yield してコンテキストブロック内のユーザーコードに提供する。@spot.mark 適用済み関数と同様にキャッシュ機構を経由する。| パラメータ | 必須 | 説明 |
|---|---|---|
*funcs |
はい | キャッシュ機能を適用したい対象の関数。外部ライブラリの関数なども指定可能。複数指定可能。 |
**kwargs |
いいえ | Spot.mark デコレータに渡すオプション引数(save_blob, keygen, version, content_type, serializer, retention, save_sync, hooks 等)。 |
funcs が空(0個)の場合、beautyspot.exceptions.ValidationError を送出し、コンテキストに入らない。mark デコレータの制約に完全に依存する。KeyGen のスコープ規則に従う。親: ARCH001
@spot.mark() デコレータは同期関数と非同期(async def)関数の両方をサポートし、関数定義に応じて適切な実行・保存・キャッシュ検索フローを透過的に切り替える。
inspect.iscoroutinefunction(func) を用いて判定する。_execute_sync() に委譲する。ThreadPoolExecutor) にタスクを投げて非同期化される(save_sync=False の場合)。_execute_async() に委譲する。inspect.isgeneratorfunction または inspect.isasyncgenfunction で真となるジェネレータ・非同期ジェネレータが渡された場合、サポート対象外として即座に ConfigurationError を送出する。on_background_error コールバックが設定されていればそこに処理が委譲される。親: ARCH001
class KeyGen:
@staticmethod
def _default(args: tuple, kwargs: dict) -> str: ...
@staticmethod
def hash_items(items: list) -> str: ...
_default)args と kwargs を canonicalize() により再帰的に正規化する[args, kwargs] を MessagePack でバイナリにシリアライズするhash_items)KeyGenPolicy で使用される。| パラメータ | 型 | 説明 |
|---|---|---|
args |
tuple |
関数の位置引数 |
kwargs |
dict |
関数のキーワード引数 |
items |
list |
バインド済み引数の正規化値リスト |
RecursionError 発生時は、安全のため str((args, kwargs)) のハッシュをフォールバックとして使用するstr() ベースのハッシュを返す[(), {}] の構造として一定のハッシュ値を生成するstr() はオブジェクトによっては実行ごとに変わる可能性があるため、明示的な正規化が推奨される親: ARCH002
@singledispatch
def canonicalize(obj: Any) -> Any: ...
singledispatch を使用し、型に応じた最適な正規化形式へ再帰的に変換する。
("__bool__", value) のタプルに変換(1 と True の衝突を防止)[[k1, v1], [k2, v2], ...] のリストに変換"__list__") を付与し、要素を再帰的に正規化。集合型はソートを行う("__enum__", module, qualname, value) に変換__dict__ または __slots__ から属性を取得し、型名と共に正規化| オブジェクト型 | 正規化後の形式 | 備考 |
|---|---|---|
numpy.ndarray |
("__numpy__", shape, dtype, bytes) |
高速かつ正確なハッシュを保証 |
OrderedDict |
("__ordered_dict__", items) |
挿入順序を保持したまま正規化 |
Pydantic Model |
("__pydantic_v2__", schema) |
スキーマ情報をベースに判定 |
str(obj) にフォールバックするRecursionError をキャッチし、その階層で str() による正規化に切り替える_safe_sort_key により、Python 3 で比較不可能な型同士(例: int と str)が辞書のキーに含まれていても安定したソート順を保証する親: ARCH002
class Strategy(Enum):
DEFAULT = auto() # 再帰的正規化
IGNORE = auto() # キー計算から除外
FILE_CONTENT = auto() # ファイルの内容をハッシュ
PATH_STAT = auto() # パス+サイズ+更新時刻をハッシュ
class KeyGenPolicy:
def bind(self, func: Callable) -> Callable[..., str]: ...
bind(func) 時に inspect.signature を解析し、引数名とデフォルト値を把握するStrategy を適用し、正規化された値のリストを作成するKeyGen.hash_items() で一つのハッシュにまとめる| 戦略 | 内容 | 用途 |
|---|---|---|
IGNORE |
キャッシュキーに含めない | verbose や logger 等、結果に影響しない引数 |
FILE_CONTENT |
ファイルを全読込してハッシュ化 | 入力ファイルの厳密な変更検知 |
PATH_STAT |
os.stat 情報を使用 |
大容量ファイルの高速な変更検知 |
FILE_CONTENT / PATH_STAT 指定時にファイルがない場合、"MISSING_{path}" という固定文字列をハッシュ対象とする"ERROR_{path}" を返すTypeError を送出する(実行前バリデーション)親: ARCH002
@runtime_checkable
class TaskDBCore(Protocol):
def get(self, cache_key: str, *, include_expired: bool = False) -> TaskRecord | None: ...
def save(self, record: TaskRecord) -> None: ...
class TaskDBBase(ABC):
# TaskDBCore を満たしつつ、メンテナンスのデフォルト実装を提供
| メソッド | 役割 |
|---|---|
get_blob_refs() |
全レコードの blob_key を列挙する(GC用) |
delete_expired() |
expires_at を経過したレコードを一括削除する |
get_history() |
実行履歴(統計)を取得する |
NotImplementedError を送出する代わりに、runtime_checkable による事前チェックを推奨する。TaskDBMaintenable 構成プロトコルとして扱う。親: ARCH003
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)tempfile.mkstemp で一時ファイルを作成flush() および os.fsync() でディスク到達を保証os.replace で最終的なパスへリネーム(アトミックな置換)load / delete)base_dir の配下にあることを is_relative_to で厳密にチェックし、範囲外へのアクセスを遮断する。| パラメータ | 説明 | 制限 |
|---|---|---|
key |
保存ファイル名のベース | .. や / を含む場合は ValidationError |
location |
save が返した識別子 |
相対パス形式を推奨 |
load 時にファイルがない場合 CacheCorruptedError を送出PermissionError 発生時は、GCでの回収に委ねるため警告ログのみ出力OSError を送出/ と \ の両方を検証対象とする親: ARCH004
class S3Storage(BlobStorageMaintenable):
def __init__(self, s3_uri: str, s3_opts: dict | None = None): ...
s3://bucket/prefix/key.bin 形式のURIをロケーション識別子として使用する。save)boto3.upload_fileobj を使用。5GBを超えるデータに対しては、自動的にマルチパートアップロードに切り替えられ、PutObjectの制限を回避する。get_mtime)head_object を使用し、データ本体をダウンロードせずに最終更新時刻(LastModified)を取得する。| 設定 | 内容 |
|---|---|
s3_uri |
s3://my-bucket/my-prefix 形式 |
s3_opts |
boto3.client('s3', **s3_opts) に渡される設定 |
boto3 がインポートできない環境でインスタンス化を試みると ImportError を送出botocore.exceptions.ClientError をキャッチし、状況に応じて CacheCorruptedError に変換親: ARCH004
class StoragePolicyProtocol(Protocol):
def should_save_as_blob(self, data: bytes) -> bool: ...
len(data) が threshold を超えた場合に True を返す。False を返すが、閾値を超えた場合にログ出力のみ行う(互換モード)。True を返す。| ポリシー型 | パラメータ | デフォルト | 説明 |
|---|---|---|---|
Threshold |
threshold |
なし | バイト単位の閾値。10MB等を推奨 |
Warning |
warning_threshold |
なし | 警告を出すサイズ閾値 |
False (DB保存) とみなし、エラーをログに記録する。len(data) > threshold のため、閾値と等しい場合はDB保存となる。親: ARCH004
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
class _BackgroundLoop:
def submit(self, coro: Coroutine) -> None: ...
def stop(self) -> None: ...
def is_running(self) -> bool: ...
submit 時にデーモンスレッドを作成し、asyncio.run() を開始するrun_coroutine_threadsafe を使用して、メインスレッドからコルーチンをイベントループへ投入| 内部属性 | 型 | 説明 |
|---|---|---|
_loop |
AbstractEventLoop |
スレッド内で稼働するループ本体 |
_thread |
Thread |
daemon=True 設定のスレッド |
on_background_error コールバックで処理される親: ARCH006
def flush(timeout: float | None = None) -> bool: ...
@spot.mark(save_sync=False)
def my_func(): ...
save_sync=True)save_sync=False)BackgroundLoop に委譲し、即座に関数の戻り値を返す。flush)_inflight タスク(保存中)のリストを確認し、それら全てが完了するまで、またはタイムアウトまで待機する。| パラメータ | デフォルト | 説明 |
|---|---|---|
timeout |
None |
flush の最大待機時間(秒)。None は無限待機 |
on_background_error |
logger.error |
非同期保存失敗時のエラーハンドラ |
flush がタイムアウトした場合、False を返し、一部のデータが未保存である可能性を通知する。with spot: 終了時に自動的に flush が呼ばれ、プロセス終了前のデータ保存を確実にする。親: ARCH006
class LifecyclePolicy:
def add_rule(self, pattern: str, retention: Any) -> None: ...
def resolve_with_fallback(self, func_name: str) -> float | None: ...
順次照合: 登録された順にルールを走査する
パターンマッチ: fnmatch.fnmatch を使用し、関数名がパターンに合致するか判定
二段階解決:
最初に「完全修飾名 (module.qualname)」で照合
マッチしなければ「短縮名 (qualname)」で照合
結果返却: 最初にマッチしたルールの保持期間を秒数に変換して返す
パラメータ例説明pattern"my_mod.*", "*_test"Glob形式のパターンretention"30d", 3600保持期間の定義
default_retention パラメータで、どのルールにもマッチしない場合のデフォルト保持期間を指定できるLifecyclePolicy.default() のデフォルト値は "30d"(30日)None を指定すると無期限保持(従来互換)default_retention で指定された保持期間を返す(デフォルト: 30日)親: ARCH007
def parse_retention(val: Any) -> float | None: ...
"7d" (7日), "12h" (12時間), "30m" (30分), "10s" (10秒) 形式を秒数に変換する。大文字小文字は区別しない。int, float: 秒数としてそのまま扱う。timedelta: total_seconds() を取得。None: ポリシーに従う(または無期限)を意味する。| 単位 | 倍率 (秒) | 備考 |
|---|---|---|
d |
86400 | 日数 |
h |
3600 | 時間 |
m |
60 | 分 |
s |
1 | 秒 |
ValidationError を送出する。ValidationError となる。Retention.FOREVER (無限大の秒数) を提供し、ポリシーに関わらず永続化を指示できる。親: ARCH007
class TokenBucket(LimiterProtocol):
def consume(self, cost: float = 1.0) -> None: ...
async def consume_async(self, cost: float = 1.0) -> None: ...
TAT - 許容バースト 以前の場合、リクエストを拒否または待機させる。consume: 待機が必要な場合は time.sleep() でブロック。consume_async: 待機が必要な場合は asyncio.sleep() で非占有待機。| 設定 | デフォルト | 説明 |
|---|---|---|
tokens_per_minute |
必須 | 1分間に許可する平均リクエスト数(コスト合計) |
max_burst |
1.0 |
許容されるバースト数(トークン単位) |
RateLimitError (ValueErrorのサブクラス) を送出する。max_burst を超えるコストを持つ単一リクエストは、常に拒否される。TAT は現在時刻より過去に一定以上離れないよう補正される(過度なバーストの防止)。親: ARCH008
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: ...
ThreadSafeHookBase を継承した場合、メタクラスが自動的に各メソッドを with self._lock: で包む| コンテキスト | 保持データ |
|---|---|
PreExecuteContext |
func, args, kwargs, cache_key |
CacheHitContext |
cache_key, result, metadata |
CacheMissContext |
cache_key, result, execution_time |
hooks=[h1, h2] と指定した場合、h1 -> h2 の順で実行される。親: ARCH009
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)delete_expired() でDBから古いタスクを除去blob_key とストレージ内の全ファイルを比較grace_period 以上経過したものを削除| パラメータ | デフォルト | 説明 |
|---|---|---|
grace_period |
3600 |
孤立Blobを削除するまでの猶予期間(秒)。並行実行中の保存を保護する |
days |
なし | prune で削除対象とする経過日数 |
clear(func_name) は前方一致ではなく、完全一致(または glob)で判定する。親: ARCH010
beautyspot list [--db DB_PATH]
beautyspot gc [--name PROJECT_NAME] [--force]
beautyspot stats
beautyspot ui
--db 指定がない場合、デフォルトの .beautyspot/ ディレクトリを探索するMaintenanceService または Spot インスタンスを生成し、対応するメソッドを呼び出すRich ライブラリを使用して、結果をテーブルやプログレスバーで表示する| コマンド | 説明 |
|---|---|
list |
保存されているキャッシュキーと関数名の一覧を表示 |
gc |
期限切れタスクと孤立Blobのクリーンアップを実行 |
stats |
キャッシュヒット率やストレージ使用量の統計を表示 |
ui |
ターミナルベースのインタラクティブダッシュボードを起動 |
--force)に対応する。親: ARCH011
def Spot(
name: str = "default",
storage_path: str | Path | None = None,
serializer: SerializerProtocol | None = None,
limiter: LimiterProtocol | None = None,
# ...その他の依存
) -> core.Spot: ...
storage_path が未指定の場合、.beautyspot/ 以下のプロジェクト名ディレクトリを使用するSQLiteTaskDB を生成し、マイグレーションを実行s3://等)を判定し、適切な BlobStorage 実装を生成core.Spot のコンストラクタに注入して返す| パラメータ | デフォルト値の導出 |
|---|---|
db |
.beautyspot/{name}.db |
blobs |
.beautyspot/blobs/{name}/ |
serializer |
MsgpackSerializer() |
policy |
WarningOnlyPolicy (閾値10MB) |
OSError を送出する。name で複数回呼び出した場合も、原則として新しいインスタンスを返すが、DBファイルへの接続は共有される(SQLiteのWALモードを活用)。親: ARCH012
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等) |
CacheCorruptedError を送出すべきである。ReadableBuffer を受け入れることで、memoryview 等を使用したゼロコピー書き込みを可能にする。親: ARCH004
本ドキュメントは、『beautyspot』の各機能における詳細な仕様、インターフェース、およびアルゴリズムを定義する。
親: —
子: —
SQLiteTaskDB はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす。WALモード・専用ライタースレッドという独自アーキテクチャの信頼性を保証する。
references: tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py
子: —
Thundering Herd Protection は、同一キーへの並行リクエストが一斉に関数を実行する「Thundering Herd」問題を防ぐ。この保護が不完全だと、重い計算やAPI呼び出しが不要に多重実行され、リソース浪費やレートリミット超過を招く。並行性バグはテスト困難であるため、複数の観点から網羅的に検証する。
asyncio.Task が同一キーで同時にキャッシュミスした場合も、関数が1回のみ実行されること_inflight エントリがクリーンアップされることHERD_TIMEOUT(300秒)を超過した場合にタイムアウト処理が発動し、無限待機にならないことHERD_MAX_RETRIES(3回)の範囲でリトライが行われ、上限超過で最終エラーが返ること_inflight 辞書の状態遷移(エントリ追加→結果セット→エントリ削除)が正しい順序で行われることreferences: tests/integration/core/test_thundering_herd.py
親: SPEC023
子: —
@spot.mark() はユーザーが最も頻繁に使う公開APIであり、キャッシュの正確性・透過性・拡張性の基盤となる。デコレーションによって元の関数の挙動やメタデータが損なわれると、ユーザーコードのデバッグや型チェックに支障をきたすため、ラッパーの透過性を厳密に検証する。
save_blob=True 指定時にDB直接保存ではなくBlobストレージ経由で保存されること__name__, __doc__, inspect.signature が元の関数と一致することserializer= パラメータでデフォルトの MsgpackSerializer をカスタム実装に置換できることversion= パラメータを変更するとキャッシュキーが変わり、既存キャッシュがヒットしなくなることreferences: tests/integration/core/test_basic.py
親: SPEC001
子: —
cached_run はデコレータを直接付与できない外部ライブラリ関数にキャッシュを適用する唯一の手段であり、このAPIの不具合はサードパーティ統合シナリオ全体に影響する。戻り値の型が関数の数で変わるスマートリターン仕様は、誤実装すると型安全性を損なうため重点的に検証する。
ValidationError が送出されることreferences: tests/integration/core/test_cached_run.py
親: SPEC002
子: —
beautyspot は同期・非同期の両方の関数を透過的にキャッシュするが、asyncio 固有のイベントループ制約(スレッドをまたぐコルーチン実行、例外伝播のタイミング差異)により、同期版とは異なる不具合が発生しやすい。非同期パスの堅牢性を独立して検証する。
inspect.iscoroutinefunction() により同期/非同期が正しく識別され、適切なラッパーが適用されることawait が1回の実行結果を共有することsave_sync=False 時に _BackgroundLoop 経由でキャッシュ保存が非同期実行されることreferences: tests/integration/core/test_async_save.py
親: SPEC003
子: —
キャッシュキーはキャッシュの同一性判定の基盤であり、ハッシュが不安定だとキャッシュミスの頻発(性能劣化)や誤ヒット(データ不整合)を引き起こす。Python バージョンやプラットフォームに依存しないキー生成の安定性を保証する。
func_identifier:input_id[:version] 入力に対して、複数回の呼び出しで常に同じ SHA-256 ハッシュが生成されることmodule.qualname 形式の func_identifier を含む正しい構造であることreferences: tests/unit/test_cachekey.py
親: SPEC004
子: —
canonicalize() は引数の型ごとに正規化ルールを適用し、論理的に同値な入力が同じキャッシュキーを生成することを保証する。正規化の不備は「同じ入力なのにキャッシュミス」または「異なる入力なのに誤ヒット」という致命的なバグに直結する。
{"a": 1, "b": 2} と {"b": 2, "a": 1} が同一キーを生成すること[1, 2] (list) と (1, 2) (tuple) が異なるキーを生成すること(型タグ付き正規化)型名.メンバー名 で正規化されることshape + dtype + tobytes による正規化で、同値配列が同一キーを生成することTrue と 1 が異なるキーを生成すること(Python では True == 1 だが区別が必要)references: tests/unit/test_cachekey.py
親: SPEC005
子: —
KeyGenPolicy は引数ごとのキー生成戦略をカスタマイズする機構であり、ログレベルやデバッグフラグのような非決定的引数をキーから除外したり、ファイルパスの代わりにファイル内容でキーを生成するユースケースを支える。戦略の誤適用はキャッシュの正確性を根本から損なう。
KeyGenPolicy.bind(func) が関数シグネチャ(位置引数・キーワード引数・デフォルト値)を正しく解釈すること@spot.mark(keygen=KeyGen.ignore("verbose")) のようにデコレータ経由で戦略が正しく適用されることreferences: tests/unit/test_cachekey.py
親: SPEC006
子: —
TaskDBCore / TaskDBBase のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL 等)の差し替えを可能にする拡張ポイントである。インターフェース契約が不明確だと、カスタム実装が実行時に予期しないエラーを起こす。
TaskDBCore の必須メソッド(save, load, delete 等)を実装したクラスが isinstance チェックをパスすることTaskDBBase がメンテナンス系メソッド(prune, stats 等)のデフォルト実装を提供し、サブクラスが必須メソッドのみの実装で動作することbs.Spot(db=CustomDB()) でプロトコル準拠のカスタムDBが正常に動作することreferences: tests/unit/test_db_writer_queue.py
親: SPEC008
子: —
LocalStorage はBlobデータのファイルシステム永続化を担い、並行書き込み時のデータ破損やパストラバーサルによるセキュリティ脆弱性が発生しうる。アトミック書き込みとセキュリティバリデーションの正確性を保証する。
base_dir が存在しない場合に自動作成され、.gitignore が配置されることtempfile.mkstemp → fsync → os.replace のフローで、書き込み中のクラッシュや並行アクセスでファイルが破損しないこと.. や / を含む場合に ValidationError が送出され、base_dir 外へのアクセスが不可能であること。load() でも is_relative_to 検証が機能することclean_temp_files() が猶予期間超過の .spot_tmp ファイルを削除し、猶予期間内のファイルは保持することdelete() が存在しないキーに対してもエラーを送出しないことreferences: tests/integration/storage/test_local.py
親: SPEC009
子: —
S3Storage はクラウド環境での大規模Blob保存を担い、ネットワーク障害・認証エラー・バケット不在などオンプレミスにはない障害モードが存在する。boto3 オプショナル依存のガード処理も含め、クラウドストレージ統合の信頼性を検証する。
save / load / delete / list_keys が正しく動作することimport 時にわかりやすいエラーメッセージが表示されることreferences: tests/integration/storage/test_s3.py
親: SPEC010
子: —
ストレージポリシーは「データをDB直接保存するか、Blobストレージに分離するか」を決定する戦略レイヤーである。閾値判定の誤りはDBの肥大化(性能劣化)や不要なBlob分離(オーバーヘッド増加)を招く。3種のポリシー実装がそれぞれの契約を正しく満たすことを検証する。
False、閾値以上のデータで True を返すこと。境界値(ちょうど閾値サイズ)での挙動が明確であることFalse を返しDB直接保存を選択すること。ただし閾値超過時に WARNING レベルのログが出力されることTrue を返すこと@spot.mark(save_blob=True/False) の明示指定がポリシー判定より優先されること。save_blob=None(デフォルト)時にポリシーの should_save_as_blob() が呼ばれることreferences: tests/unit/test_storage_policy.py
親: SPEC011
子: —
MsgpackSerializer はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレントなデータ劣化(精度低下等)に直結する。スレッドセーフ性の欠如はマルチスレッド環境での競合状態を引き起こす。
dumps → loads で元の値と一致することreferences: tests/unit/test_serializer.py
親: SPEC012
子: —
カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は SerializationError によるキャッシュ保存失敗を引き起こし、ユーザーの既存コードとの統合を阻害する。デコレータ方式と命令方式の両方の登録パスを検証する。
@spot.register(code=N, encoder=..., decoder=...) でクラスが登録され、そのインスタンスのシリアライズ/デシリアライズが正常に動作することspot.register_type(MyClass, code=N, ...) でも同等の登録が行われることmodel_validate をデコーダとして使用する場合に、バリデーション付きの復元が正しく動作することreferences: tests/unit/test_type_registry.py
親: SPEC013
子: —
_BackgroundLoop はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコルーチン受け渡しは競合状態やデッドロックが発生しやすく、シャットダウン時のタスク消失はデータロスに直結する。
submit(coro) で投入したコルーチンが正しく実行され、結果がDBとストレージに反映されることdrain_timeout 秒以内に完了すること。タイムアウト後は強制終了されることsubmit() 呼び出しが受け付けられないことreferences: tests/integration/core/test_background_loop.py
親: SPEC014
子: —
save_sync パラメータと flush / drain メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユーザーが制御する手段である。save_sync=False 使用時にデータが失われないことと、コンテキストマネージャによる確実なフラッシュを保証する。
spot.flush(timeout) 呼び出しで全ペンディング保存が完了するまで待機すること。タイムアウト時の挙動が明確であることwith spot: ブロック終了時に未完了の保存が自動的に drain されることwith ブロックで再利用でき、各ブロック終了時に適切に drain されることon_background_error コールバックが呼ばれ、メインスレッドには影響しないことreferences: tests/integration/core/test_exit_drain.py
親: SPEC015
子: —
LifecyclePolicy はキャッシュデータの保持期間を関数名パターンで制御する。パターンマッチングの不備は、重要なキャッシュの意図しない削除(データロス)や不要なキャッシュの蓄積(ディスク圧迫)を引き起こす。
"train_*", "*.preprocess" 等)がルールの func_pattern として正しく機能し、マッチした関数に対して指定の Retention が適用されることmodule.qualname)でルールが見つからない場合に、短縮名(qualname のみ)でフォールバック検索が行われることdefault_retention で指定された保持期間(デフォルト: 30日)が適用されることdefault_retention=None を指定した場合に無期限保持(None)が返されることreferences: tests/unit/test_lifecycle.py
親: SPEC016
子: —
Retention はキャッシュの有効期間をユーザーフレンドリーな形式で指定する値オブジェクトである。パースの不備は意図しない保持期間(例: "7d" を 7秒と誤解釈)を招き、重要なキャッシュの早期削除やディスクの際限ない肥大化に繋がる。
"7d" → 7日、"12h" → 12時間、"30m" → 30分、"10s" → 10秒として正しく解釈されることtimedelta(days=7) がそのまま保持期間として受け入れられることRetention.FOREVER が無期限保持を表し、シングルトンであること(is 比較が成立)"-1d")やゼロ("0s")が ValidationError を送出すること。不正な形式("abc", "" 等)も拒否されることreferences: tests/unit/test_lifecycle_extended.py
親: SPEC017
子: —
TokenBucket はAPI呼び出しやリソースアクセスのレート制限を実現する。レートリミッターの不具合はAPIの過負荷(制限が甘い場合)やスループットの不必要な低下(制限が厳しすぎる場合)を招く。GCRA アルゴリズムのスムーズなレート制御を検証する。
references: tests/unit/test_limiter.py
親: SPEC018
子: —
Hook システムはキャッシュライフサイクルへのユーザー拡張ポイント(ロギング、メトリクス収集、監査等)であり、フック内の不具合がメインの関数実行に影響を与えないことが最重要の契約である。また ThreadSafeHookBase の自動ロック機構の正確性を保証する。
pre_execute → 関数実行 → on_cache_miss の順でフックが呼ばれること。キャッシュヒット時に pre_execute → on_cache_hit の順で呼ばれることPreExecuteContext, CacheHitContext, CacheMissContext)が正しい情報(func_name, args, result 等)を含むこと__init_subclass__ によりユーザー定義メソッドが自動的に RLock でラップされ、複数スレッドからの同時呼び出しで競合状態が発生しないことreferences: tests/integration/core/test_hooks.py
親: SPEC019
子: —
MaintenanceService は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用者の誤判断を招き、メンテナンス操作の不備はデータの意図しない削除に繋がる。
None が返却され、例外が送出されないことreferences: tests/unit/test_maintenance.py
親: SPEC020
子: —
CLI はユーザーがキャッシュの状況確認・管理を行う主要インターフェースであり、コマンドの不具合はユーザー体験と運用効率に直接影響する。CliRunner によるコマンド実行と出力の正確性を E2E で検証する。
--force フラグでプロンプトがスキップされること@mark でキャッシュ保存 → beautyspot list で確認 → beautyspot clear で削除、という一連のワークフローが正常に動作することreferences: tests/integration/cli/test_cli.py
親: SPEC021
子: —
bs.Spot() ファクトリ関数は全コンポーネントの DI 配線を担う唯一のパブリックエントリポイントであり、デフォルト構成の正確性とカスタム実装の差し替え可能性がシステム全体の柔軟性と正確性を決定する。
SQLiteTaskDB, LocalStorage, MsgpackSerializer, TokenBucket, WarningOnlyPolicy が自動注入されること。ワークスペースディレクトリ(.beautyspot/)が自動作成されることTaskDBBase / TaskDBCore プロトコル準拠のカスタムDBを db= で注入し、正常にキャッシュ操作が行えることBlobStorageBase 準拠のモック Storage を storage_backend= で注入し、Blob保存が委譲されることSerializerProtocol 準拠のカスタム Serializer を注入し、シリアライズ処理が差し替わることbs.Spot() の戻り値型が pyright / mypy で正しく推論されることreferences: tests/integration/core/test_dependency_injection.py
親: SPEC022
子: —
BlobStorageBase は全ストレージバックエンド(LocalStorage, S3Storage, サードパーティ)が
準拠すべき抽象契約を定義する。この契約が正しく機能しないと、キャッシュデータの
保存・復元・削除・メンテナンス(GC)の全てが破綻する。
LocalStorage を具象実装として使い、インターフェース契約を網羅的に検証する。
bytes, bytearray, memoryview の全てを受け入れること(ゼロコピー書き込み)b"") を正常に保存・復元できることCacheCorruptedError を送出することreferences: tests/integration/storage/test_blob_storage_base.py
親: SPEC024
子: —
SQLiteTaskDB クラスが中核。Writer Thread + Reader Threads パターンで実装。
単一の _writer_thread(デーモン)が _write_queue から書き込みタスクを取り出して直列処理し、読み取りは threading.local() のスレッドローカル接続で並行実行する。
WAL モードにより読み取りと書き込みの並行性を確保している。
_enqueue_write 内で task.event.wait(timeout=0.5) をループさせ、self.timeout を超えた場合にフェイルファスト処理を行う。task.try_cancel() でキュー内キャンセル。self._writer_conn.interrupt() を呼び出して実行中の SQLite クエリを強制終了させ、呼び出し元に TimeoutError を返す。references: src/beautyspot/db.py
親: SPEC007
子: —
CacheManager クラス内で、Thundering Herd(キャッシュミス時に同一キーへの
大量アクセスが同時に発生する問題)を防止する直列化機構を実装。
_inflight 辞書で実行中のキーを管理し、最初の1スレッド/タスクだけが関数を実行し、
後続の呼び出しは herd_sync / herd_async コンテキスト内でその完了を待機する。
実行者 (Executor) が処理中に例外(KeyboardInterrupt や asyncio.CancelledError を含む)で
中断された場合でも、_inflight 状態が残留して後続タスクを永続的にブロックすることを防ぐため、
herd_sync / herd_async をコンテキストマネージャとして提供する。
クリーンアップ処理(notify_and_cleanup_inflight)は finally ブロック内で
自動的に実行される。
_inflight 辞書の値として (threading.Event, list[asyncio.Future], list[result]) の
タプルを保持し、スレッドベースの待機 (Event.wait) と asyncio ベースの待機 (Future) の
両方を単一の管理下で混在できるようにしている。
関数の実行がハングアップした場合に待機側が永遠にブロックされるのを防ぐため、
HERD_TIMEOUT(300秒)と HERD_MAX_RETRIES(3回)を設定。
タイムアウト時には警告ログを出しつつ再試行し、上限を超えれば TimeoutError で
フェイルファストする堅牢な設計とした。
notify_and_cleanup_inflight() で待機タスクへの結果伝播と _inflight からの削除を
_inflight_lock によりアトミックに行うresult_box(要素数1のリスト)をリファレンス渡しで共有することで、
Event が set された際に安全に結果を取得できるようにしている_notify_future() は call_soon_threadsafe を使い、非同期Futureに対して
別スレッドから安全に結果(または例外)をセットするreferences: src/beautyspot/cache.py
親: SPEC023
子: —
Spot.mark() メソッドが本仕様の中核。二段階デコレータファクトリとして、
オプションを受け取る外側の mark() と、関数をラップする内側の decorator() で構成される。
sync/async の判定は inspect.iscoroutinefunction() でデコレーション時に行い、
同期関数は _execute_sync()、非同期関数は _execute_async() に委譲する。
functools.wraps(fn) により元関数のメタデータ(__name__, __doc__, __module__, __qualname__)を保持する。
@mark() (括弧あり)と @mark(括弧なし)の両方をサポートするため、
引数の型で分岐するパターンを採用した。第一引数が callable なら括弧なし、
そうでなければ括弧ありと判定する。
デコレーション時に判定する方式を採用。呼び出し時に毎回 inspect する方式と比較し、
ランタイムオーバーヘッドがゼロになる利点がある。ただしデコレーション後に
関数の性質が動的に変わるケースには対応できない(実用上問題なし)。
inspect.isgeneratorfunction() と inspect.isasyncgenfunction() の
両方をチェックし、ConfigurationError を送出する。
ジェネレータの戻り値はイテレータであり、キャッシュの意味論と矛盾するため。
inspect.signature の保持は functools.wraps が設定する __wrapped__ 属性に依存する_resolve_settings() で Spot レベルのデフォルト → 関数レベルのオーバーライドの順で行われるkeygen パラメータが指定された場合、KeyGenPolicy.bind(fn) でシグネチャにバインドされたキー生成関数に変換される@mark を2回付ける)時の検出・警告が未実装references: src/beautyspot/core.py
親: SPEC001
子: —
Spot.cached_run() はコンテキストマネージャとして実装。
渡された関数群を内部で mark() と同等のラッピングを行い、
コンテキスト内で呼び出すとキャッシュが効く一時的なラッパーを返す。
単一関数の場合は結果を直接返し、複数関数の場合はタプルで返す。
これにより wrapped_fn = spot.cached_run(fn) のように自然に書ける。
0個の場合は ValidationError で早期失敗させる。
@contextmanager デコレータを使用。__enter__ でラップ済み関数を返し、
__exit__ で後処理(drain 等)は with spot: に委譲する設計。
@overload を使用しているfunctools.wraps が元関数に適用されるreferences: src/beautyspot/core.py
親: SPEC002
子: —
Spot._execute_sync() と Spot._execute_async() が実行エンジンの中核。
両メソッドは同一のフロー(キー生成→キャッシュ検索→Herd待機→関数実行→保存)を
それぞれ同期/非同期のセマンティクスで実装する。
mark() がデコレーション時に inspect.iscoroutinefunction() で判定し、
適切な方を選択する。
_execute_sync と _execute_async は処理フローが同一だが、
await の有無やロック機構(threading.Event vs asyncio.Future)が異なるため、
共通化せず並行して実装している。DRY 原則よりも明瞭性・デバッグ容易性を優先した。
Herd protection の結果ボックスへの結果格納は、DB/Blob 保存の前に行う。 これにより、保存が失敗しても待機中のスレッド/タスクは結果を受け取れる。 「保存の失敗で実行結果が失われる」ことを防ぐ意図的な設計。
save_sync=False 時の保存エラーは on_background_error コールバックに
通知し、ERROR ログを出力するが、例外を再送出しない。
関数の実行自体は成功しているため、ユーザーに例外を見せるのは不適切と判断した。
pre_execute, on_cache_hit, on_cache_miss)は
try/except で囲み、フック内の例外が実行結果に影響しないようにしている_eviction_guard_lock で非ブロッキングにガードし、
並行エビクションの重複実行を防止している_execute_async 内の保存処理は _BackgroundLoop.submit() でコルーチンとして投入されるreferences: src/beautyspot/core.py
親: SPEC003
子: —
KeyGen クラスの静的メソッド群がキャッシュキー生成を担う。
_default(args, kwargs) が主要なエントリポイントで、引数を
canonicalize() で正規化 → msgpack でシリアライズ → SHA-256 でハッシュする。
最終キーは func_identifier:input_id[:version] 形式の文字列を SHA-256 した値。
MD5 より衝突耐性が高く、Python 標準ライブラリの hashlib で利用可能。
キャッシュキーとして64文字の hex 文字列は十分にコンパクトで、
DB のインデックスとしても効率的。
正規化結果を直接 str() でハッシュする方式と比較し、
msgpack はバイナリ表現が安定しており、Python バージョン間での
repr() の差異に影響されない。
from_file_content() はファイルを 65KB チャンクで読み取り、
拡張子をハッシュに含める。大きなファイルでもメモリ効率が良い。
from_path_stat() は mtime + size のみでハッシュし、
ファイル内容の読み取りを避ける高速版。ファイル不在時は
"MISSING_{filepath}" を返し、不在自体をキーに反映する。
hash_items() は汎用リストハッシュ。内部でも _default でも使われるreferences: src/beautyspot/cachekey.py
親: SPEC004
子: —
canonicalize() は functools.singledispatch で実装された再帰的正規化関数。
型ごとにディスパッチハンドラを登録し、ネストされたデータ構造を再帰的に正規化する。
各ハンドラは型タグ付きのタプルを返し、異なるコレクション型が同じ内容でも
区別されるようにする。
if/elif チェーンと比較して、新しい型のサポート追加が局所的で、 既存コードへの影響がない。numpy や Pydantic のようなオプショナル依存の 型ハンドラも、条件付きで登録できる。
[1, 2](list)と (1, 2)(tuple)を区別するため、
正規化結果に ("__list__", ...), ("__tuple__", ...) のように
型タグを含める。これにより、構造的に同一でも型が異なるデータが
異なるキャッシュキーを生成する。
Python では True == 1 かつ isinstance(True, int) だが、
キャッシュキーとしては区別が必要。singledispatch は MRO 順でマッチするため、
bool を int より先にチェックするインライン処理を base handler に配置した。
__dict__ と __slots__ の両方を MRO 走査で収集する。
__dict__ スロット自体はスキップするOrderedDict は順序が意味を持つため、キーのソートを行わない(dict とは異なる)defaultdict は default_factory を無視し、内容のみで正規化するEnum は 型名.メンバー名 + value で正規化し、モジュール・qualname も含めるstr() にフォールバックし、警告を出力する(非決定的)str() にフォールバックするが、安定性は保証されないreferences: src/beautyspot/cachekey.py
親: SPEC005
子: —
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() で宣言的にポリシーを構築できる。
デコレーション時に inspect.signature(func) を呼び、
位置引数・キーワード引数・デフォルト値を解決する。
これにより、呼び出し時にはシグネチャ解析のオーバーヘッドがなくなる。
引数ごとの戦略を enum で宣言する方式を採用。
関数を渡す方式(keygen=lambda ...)と比較して、
シリアライズ可能で、デバッグ時の可読性が高い。
bind() 内で RecursionError を catch し、警告を出力してフォールバックするFILE_CONTENT 戦略は KeyGen.from_file_content() に委譲し、65KB チャンク読み取りを行うPATH_STAT 戦略は KeyGen.from_path_stat() に委譲し、mtime + size でハッシュするIGNORE 戦略の引数はキー計算から完全に除外される(値に関係なくキャッシュヒット)references: src/beautyspot/cachekey.py
親: SPEC006
子: —
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 バックエンドでもエラーにならない設計。
Flushable.flush(timeout) のデフォルトは no-op。SQLiteTaskDB のみがキュー同期を実装Shutdownable.shutdown(wait) のデフォルトは no-op。リソースを持たない軽量実装に配慮isinstance(db, Maintenable) で機能の有無を判定できるreferences: src/beautyspot/db.py
親: SPEC008
子: —
LocalStorage クラスが BlobStorageBase を実装。 ファイルは {base_dir}/{key}.bin 形式で保存される。
save() は tempfile.mkstemp() → fsync() → os.replace() の アトミック書き込みパターンで実装。
一時ファイルに書き込み → fsync でディスクに確実に反映 → os.replace で アトミックにリネーム。この3段階により、書き込み中のクラッシュや 並行アクセスでファイルが半壊状態になることを防ぐ。 一時ファイルには .spot_tmp 接尾辞を使用し、残存時のクリーンアップを容易にした。
save() / _validate_key(): キーに .., /, \ を含む場合に ValidationError
load() / delete(): 解決済みパスが base_dir.is_relative_to() を満たすか検証 二重のチェックにより、キー経由とロケーション経由の両方のパストラバーサルを防ぐ。
初期化時に base_dir を絶対パスに正規化し、ディレクトリと .gitignore を自動作成
delete() は冪等。ファイル不在時もエラーを送出しない。パーミッションエラーは警告のみ
list_keys() は rglob("*.bin") でレガシーサブディレクトリ構造にも対応
clean_temp_files() は猶予期間(デフォルト24時間)超過の .spot_tmp ファイルのみ削除。 ファイルロック(アンチウイルス等)のエラーは安全に無視する
prune_empty_dirs() はボトムアップで走査し、.DS_Store 等のシステムファイルのみの ディレクトリも空とみなして削除する。base_dir 自体は保持する
references: src/beautyspot/storage.py
親: SPEC009
子: —
S3Storage クラスが BlobStorageBase を実装。
s3://bucket/prefix/ 形式の URI を解析し、boto3 の S3 クライアントを使用。
ファイルは {prefix}/{key}.bin のキーで S3 に保存される。
boto3 は import 時にガードし、未インストール時は
クラス定義は成功するがインスタンス化で明確なエラーメッセージを表示する。
beautyspot 全体が boto3 に依存しないよう、遅延インポートパターンを採用。
s3://bucket/prefix 形式の URI から bucket と prefix を自動解析する
_parse_s3_uri() ヘルパーにより、ファクトリ関数がスキームに基づいて
LocalStorage と S3Storage を透過的に切り替えられる。
save() は upload_fileobj() を使用し、5GB 超のデータにも
マルチパートアップロードで対応するs3://bucket/prefix/key.bin)を返すlist_keys() は paginator で全キーを列挙し、prefix を除いた相対キーを返すget_mtime() は head_object で LastModified を取得(GC の grace period 判定用)references: src/beautyspot/storage.py
親: SPEC010
子: —
StoragePolicyProtocol が should_save_as_blob(data: bytes) -> bool を定義。
3つの実装を提供:
- ThresholdStoragePolicy: len(data) > threshold で判定
- WarningOnlyPolicy: 常に False を返し、閾値超過時に WARNING ログを出力
- AlwaysBlobPolicy: 常に True を返す
保存先判定ロジックを core.Spot から分離し、差し替え可能なポリシーオブジェクトに
委譲する。@mark(save_blob=True/False) の明示指定はポリシーより優先されるが、
save_blob=None(デフォルト)時にポリシーが判定を行う。
v2.0 との後方互換性を維持するため。v2.0 では全データが DB 直接保存だったため、
デフォルトを ThresholdStoragePolicy にすると既存ユーザーの挙動が変わる。
WARNING ログにより、ユーザーに Blob 分離の恩恵を段階的に周知する。
threshold パラメータを属性として保持WarningOnlyPolicy のログは warnings.warn ではなく logging.warning を使用。
ユーザーコードの警告フィルタに影響されないためreferences: src/beautyspot/storage.py
親: SPEC011
子: —
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
子: —
_BackgroundLoop クラスが非同期タスクのバックグラウンド実行を管理する。
初期化時に asyncio.new_event_loop() で新しいイベントループを作成し、
_thread(デーモンスレッド)上で loop.run_forever() を実行する。
外部からは submit() を通じてコルーチンを投入でき、run_coroutine_threadsafe で
スレッドセーフにループへ渡される。
スレッドは daemon=True とし、メインスレッド終了時にプロセスがブロックされないようにしている。
しかし、安全なリソース解放のために drain() メソッドを提供し、
投入された全タスクの完了を drain_timeout の範囲で待機する。
メインスレッドのイベントループと競合しないよう、専用のイベントループを
作成してバックグラウンドスレッドで駆動する。これにより save_sync=False 時の
保存処理などが、呼び出し元の asyncio 環境から完全に隔離される。
submit() は投入されたタスクを _tasks 集合に登録し、add_done_callback で
完了時に自身を削除することで、未完了タスクの追跡を可能にしている_lock による排他制御を行っているatexit ハンドラとしてもシャットダウンが登録されるreferences: src/beautyspot/core.py
親: SPEC014
子: —
Spot クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。
save_sync=False の場合、キャッシュへの保存処理は _bg_loop.submit() で
バックグラウンドに投入される。これら未完了のタスクやDBキューを同期するため、
flush() およびコンテキストマネージャによる drain が実装されている。
flush(): DBライタースレッドのキュー (self.db.flush()) を空になるまで待機する__exit__: コンテキストマネージャ終了時に flush() を呼び出した上で、
さらに _bg_loop.drain() を呼び出し、非同期タスクの完了も待つ
これにより、スクリプト終了時やバッチ処理の区切りで、データの完全な永続化を保証する。バックグラウンド保存時のエラーは呼び出し元のメインフローを妨げないよう、
on_background_error コールバックに通知され、ログ出力のみ行う。
例外の再送出は行わない。
flush() にはタイムアウトを設定でき、ハングアップを防止しているon_background_error は SaveErrorContext を引数に取り、失敗時の
キーや関数の詳細情報を提供するreferences: src/beautyspot/core.py
親: SPEC015
子: —
LifecyclePolicy クラスは Rule オブジェクトのリストを保持し、
関数名に基づいてキャッシュの保持期間を決定する。
resolve() メソッドが fnmatch.fnmatch() を使って関数名をパターンと照合し、
最初にマッチしたルールの保持期間を返す。
ルールのリストは順序が意味を持ち、最初にマッチしたものが採用される。
これにより、特定プレフィックスの関数には短い保持期間を設定し、
最後に *(ワイルドカード)でデフォルトポリシーを設定するような
フォールバック構造が簡単に記述できる。
後方互換性と柔軟性のため、まず func_identifier (モジュール名付きの完全修飾名) で
マッチングを試み、マッチしなかった場合は func_name (短い関数名) で再度マッチングを
試みる resolve_with_fallback() を提供している。
LifecyclePolicy コンストラクタに default_retention パラメータを追加。
どのルールにもマッチしない場合に返す保持期間を指定できる。
LifecyclePolicy.default() は default_retention="30d" で生成し、
デフォルトで30日の保持期間を設定する。
LifecyclePolicy.default() は空のルールリスト + default_retention="30d" を持つdefault_retention=None を指定すれば従来通り無期限保持となるreferences: src/beautyspot/lifecycle.py
親: SPEC016
子: —
Retention クラスを名前空間として使用し、保持期間のパースや
特殊な定数(INDEFINITE, FOREVER)を管理する。
parse_retention() 関数は文字列("7d", "12h"等)、timedelta、
または秒数(int/float)を受け取り、標準化された timedelta オブジェクトに変換する。
Retention.FOREVER はポリシーを強制的にバイパスするための特殊値。
PEP 703 (free-threading) 環境での安全性を考慮し、
_ForeverSentinel は threading.Lock を用いたダブルチェックロッキングで
厳密なシングルトンとして実装されている。
"7d", "12h", "30m", "10s" のような文字列フォーマットを
正規表現 _TIME_PATTERN でパースすることで、設定ファイルや
ハードコード時の可読性を高めている。
parse_retention() は無効なフォーマットや負の値に対して ValidationError を送出する_ForeverSentinel は __bool__() で True を返すようオーバーライドされているreferences: src/beautyspot/lifecycle.py
親: SPEC017
子: —
LimiterProtocol を実装する TokenBucket クラス。
GCRA (Generic Cell Rate Algorithm) に基づき、スレッドセーフおよび
非同期対応のスムーズなレートリミッタを提供する。
内部状態として Theoretical Arrival Time (TAT) を保持し、
consume() で同期的スリープ、consume_async() で非同期的スリープを行う。
伝統的なトークンバケットとは異なり、長時間アイドル後に 一気にバーストを許容しない「Strict Pacing」を実現できる。 TATの更新と現在時刻の比較だけで待機時間を計算できるため、 メモリ効率と計算効率に優れる。
時刻の取得に time.monotonic() を使用し、システム時刻の変更(NTP同期など)の
影響を受けない堅牢な設計としている。
_consume_reservation() メソッドが待機時間の計算と TAT 更新を
threading.Lock でアトミックに行うcost > max_cost(バケット容量超過)の場合は、どれだけ待っても
処理できないため即座に ValueError を送出するtime.sleep()、非同期パスは asyncio.sleep() で待機するreferences: src/beautyspot/limiter.py
親: SPEC018
子: —
タスク実行ライフサイクルに介入するインターフェース HookBase と、
そのスレッドセーフ版 ThreadSafeHookBase の実装。
pre_execute, on_cache_hit, on_cache_miss の各コールバックが提供される。
ThreadSafeHookBase は __init_subclass__ を利用し、サブクラスで定義された
フックメソッドを自動的にロックでラップする。
ユーザーが手動でロックを書く手間を省くため、メタクラス的なアプローチを採用。
クラス定義時にフックメソッドを抽出し、_wrap_with_lock デコレータで包むことで、
ユーザーコードを汚さずに完全な排他制御を実現している。
当初の Lock から RLock に変更された。
サブクラスが super().pre_execute(...) のように親のメソッドを呼び出した際、
同一スレッドが再度ロックを取得しようとしてデッドロックする問題を防ぐため。
_wrap_with_lock は functools.wraps を使い、元のメタデータを保持するThreadSafeHookBase は __getattr__ をオーバーライドし、
super().__init__() の呼び出し忘れに対して親切なエラーメッセージを返すreferences: src/beautyspot/hooks.py
親: SPEC019
子: —
MaintenanceService クラスがDBとBlobストレージ間の整合性チェック、
期限切れキャッシュの削除、孤立ファイルの検出といったガベージコレクション(GC)を担当。
主にCLIの beautyspot gc コマンドから呼び出される。
対象となる DB (TaskDBMaintenable) とストレージ (BlobStorageMaintenable) を受け取り、
複数のフェーズに分けてクリーンアップを実行する。
clean_garbage() は以下の順序で安全にGCを実行する:
1. 期限切れDBレコードの削除
2. Blobストレージの一時ファイル(.spot_tmp)のクリーンアップ
3. 孤立したBlobファイル(DBに存在しないファイル)の特定
4. 孤立ファイルの削除
5. 空ディレクトリの剪定
実行中のタスクがBlobを書き込んだ直後で、まだDBにメタデータが保存されていない
タイミングでGCが走ると、必要なファイルが孤立と誤認されるリスクがある。
これを防ぐため、orphan_grace_seconds(デフォルト60秒)より新しいファイルは
孤立判定から除外する設計とした。
scan_garbage() は、ストレージの全キーを列挙し、
DBの get_blob_keys() との差分を取ることで行うscan_orphan_projects() も提供されるreferences: src/beautyspot/maintenance.py
親: SPEC020
子: —
Typer を利用したコマンドラインインターフェースの実装。
list, show, stats, clear, clean, gc, prune, version 等の
コマンドを提供し、rich ライブラリを用いてコンソール出力(テーブル、パネル、
プログレスバー等)をリッチにフォーマットしている。
Typer は型ヒントベースでコマンドやオプションを定義でき、コードの記述量と メンテナンスコストを大幅に削減できる。Rich による出力は、単なるテキストではなく 構造化された情報(JSONやMarkdown)の視認性を劇的に向上させ、DXを高める。
clear や clean など、データを大規模に削除するコマンドについては、
--force オプションが指定されない限り、rich.prompt.Confirm を用いて
ユーザーに明示的な確認を求めることで、誤操作によるデータ喪失を防いでいる。
MaintenanceService などの内部 API を利用して処理を行う--project / -p) に対して操作を行うが、
一部のコマンド(list 等)はワークスペース全体の走査もサポートするreferences: src/beautyspot/cli.py
親: SPEC021
子: —
beautyspot パッケージのメインエントリポイントとなる Spot ファクトリ関数の実装。
引数として渡された各コンポーネント(DB、Serializer、Storage等)を依存性注入(DI)で
解決し、デフォルトのコンポーネント(SQLiteTaskDB, MsgpackSerializer, LocalStorage等)を
インスタンス化して CacheManager と _Spot コアエンジンを組み立てる。
_Spot クラス自体のコンストラクタは複雑な依存関係を要求するが、
ユーザー向けに Spot() 関数を提供することで、通常は name を渡すだけで
「ゼロ設定」で動作するようにカプセル化している。
同時に、高度なユーザーは各コンポーネントを自由に差し替え可能な DI アーキテクチャを維持している。
Spot() 関数内でデフォルトのDB(SQLiteTaskDB)を自動生成した場合、
spot._owns_db = True フラグを立て、Spotエンジンのシャットダウン時に
DBも自動でクローズされるようにする。一方、ユーザーが明示的に db= を
渡した場合は、DBのライフサイクル管理は呼び出し元に委ねる(勝手に閉じない)。
save_blob フラグや引数によって WarningOnlyPolicy,
AlwaysBlobPolicy 等に解決される__all__ に各種プロトコルやデフォルト実装、例外クラスをエクスポートし、
ライブラリとしての公開API境界を明確に定義しているreferences: src/beautyspot/__init__.py
親: SPEC022
子: —
storage.py 内の BlobStorageBase ABC が SPEC024 の中核実装。
5つの抽象メソッド(save, load, delete, list_keys, get_mtime)を定義し、
LocalStorage と S3Storage が具象実装として継承する。
加えて、ランタイム型チェックのために BlobStorageCore・Maintenable・
BlobStorageMaintenable の3つの Protocol クラスを定義し、
isinstance() での型判定を可能にしている(@runtime_checkable)。
BlobStorageBase は ABC として抽象メソッドを強制する一方、
BlobStorageCore / Maintenable は Protocol として構造的部分型を提供する。
これにより、BlobStorageBase を継承しないサードパーティ実装でも
isinstance(obj, BlobStorageCore) で利用可能になる柔軟性を確保した。
BlobStorageCore: 実行時に必要な最小限(save/load/delete)Maintenable: メンテナンス(GC)に必要な拡張(list_keys/get_mtime)BlobStorageMaintenable: 両方を兼備する完全版これにより、save/load/delete のみを実装した軽量バックエンドも受け入れ可能。
bytes | bytearray | memoryview を ReadableBuffer として定義。
memoryview を受け入れることでゼロコピー書き込みが可能になり、
大きなデータを保存する際のメモリ効率を改善している。
BlobStorageBase 自体はロジックを持たず、インターフェース定義のみ@abstractmethod にはdocstringで契約を明記(冪等性、エラー時の挙動など)delete は冪等であるべきことを docstring で規定list_keys は delete と同じフォーマットの識別子を yield すべきことを規定references: src/beautyspot/storage.py
親: SPEC024
子: —
beautyspot のコア機能である「関数のメモ化(キャッシュ)」において、関数の引数 (args, kwargs) から一意なキャッシュキーを生成する必要があります。
初期実装 (v0.1.0) では、json.dumps が失敗した場合のフォールバックとして str((args, kwargs)) のハッシュ値を使用していました。
しかし、Pythonのデフォルトの __str__ / __repr__ 実装は、オブジェクトのメモリアドレス(例: <MyObject at 0x10a...>)を含むことが多くあります。
これにより以下の問題が発生していました:
外部ライブラリ(joblib 等)を使えば解決しますが、beautyspot は軽量な「黒子」ライブラリを目指しており、依存関係を増やしたくありません。
pydantic モデルや dataclass 等、一般的なPythonオブジェクトを透過的に扱えること。str() によるフォールバック)。joblib 等)の導入。json を拡張した、独自の安定化シリアライザの実装。Chosen option: Option 3.
標準ライブラリの json モジュールを使用し、独自の default シリアライザ (_stable_serialize_default) を実装することで、依存関係なしで 堅牢なハッシュ生成を実現します。
具体的には以下の戦略を採用します:
sorted(list(obj)) でリスト化し、順序を保証します。__dict__ または __slots__ を参照し、オブジェクトの「中身の値」をシリアライズ対象とします。これにより、メモリアドレスへの依存を排除します。hex) に変換します。str() を使用します(この場合のみ不安定になるリスクを許容します)。pydantic モデルや dataclass も設定なしでキャッシュ可能になる。numpy, joblib 等)を増やさずに済む。str() 変換よりも、シリアライズ処理のオーバーヘッド(CPUコスト)がわずかに増加する。親: REQ002
子: —
beautyspot のレート制限機能 (limiter) において、以下の課題があった。
capacity)を小さくすると、今度は巨大なコストを持つタスクが実行できなくなる(デッドロック)問題が発生する。tokens_per_minute を単発タスクのコスト上限とする。consume() に渡された場合、即座に ValueError を送出する。Chosen option: Option 2.
アルゴリズムをトークンバケットから GCRA (Generic Cell Rate Algorithm) に変更する。
現在時刻 < TAT ならば、その差分だけ待機(sleep)してから実行する。現在時刻 >= TAT (アイドル状態)ならば、TAT を現在時刻にリセットしてから計算する(過去の貯金は捨てる)。親: REQ008
子: —
Pythonの pickle は、保存されたオブジェクトのクラス定義と現在のコードが一致していることを前提とします。
開発中はクラス定義が頻繁に変更されるため、過去のキャッシュを読み込む際に AttributeError 等が発生し、アプリケーションがクラッシュする問題がありました。
version 指定による無効化を可能にする。Chosen option: Option 2.
Storage.load() でデシリアライズエラー(クラス不整合や破損)が発生した場合、これを CacheCorruptedError として捕捉し、core.py 側で 「キャッシュミス」 として扱う。これによりアプリをクラッシュさせず、自動的に再計算を行う。@task デコレータに version 引数を追加する。これを変更するとキャッシュキーが変わり、古い(互換性のない)キャッシュを参照しなくなる。version の更新を検討してね」というヒントを出力し、ベストプラクティスへ誘導する。version を使うことで安全なデプロイ(キャッシュ切り替え)が可能になる。親: REQ005
子: —
src/beautyspot/core.py において、非同期タスクのIOオフロード用に ThreadPoolExecutor がグローバル変数 _io_executor として定義されている。
この実装には以下の課題がある:
max_workers=4)がハードコードされており、ユーザーが実行環境(AWS Lambda、強力なサーバー等)に合わせて調整できない。代替案として with 文(Context Manager)による管理も検討されたが、beautyspot の「デコレータを付与するだけで動作する」という簡易な利用体験(DX)を損なうため、採用には至らなかった。ユーザーコードを変更させずに、安全にリソースをクリーンアップする仕組みが必要である。
shutdown() の呼び出しや with 文を強制したくない。Project を Context Manager 化し、__exit__ で shutdown() を呼ぶ。atexit モジュールを使い、終了時に shutdown() を呼ぶ。weakref.finalize で自動クリーンアップを行う。Chosen option: Option 4.
Project クラスの設計を以下のように変更する:
Project インスタンスごとに ThreadPoolExecutor を保持する。Executor の注入を許可する。executor 引数あり): Project はそれを借用するのみ。シャットダウンの責任はユーザー(呼び出し元)にあるため、自動クリーンアップは行わない。executor 引数なし): Project がライフサイクルを管理する責任を持つ。weakref.finalize を使用してシャットダウンを自動化する。weakref.finalize instead of atexit?単純な atexit.register(self.shutdown) を採用しなかった理由は、メモリリーク(循環参照)のリスク である。
atexit レジストリにインスタンスメソッド(self.shutdown)を登録すると、atexit が self への強参照(Strong Reference)を保持し続ける。これにより、アプリケーション実行中に Project インスタンスが不要になってもガベージコレクション(GC)されず、メモリリークを引き起こす。これを防ぐには atexit.unregister が必要になるが、管理が複雑になる。weakref.finalize(self, func, *args) を採用する。self がGCされた時点で、即座に func(クリーンアップ処理)が実行される。self が生存していた場合も、atexit 相当のタイミングで func が実行される。_shutdown_executor は staticmethod とし、引数には self ではなく self.executor のみを渡す。これにより、ファイナライザが self を参照し続けて寿命を延ばしてしまう事故を防ぐ。# Implementation Sketch
class Project:
def __init__(self, ...):
# ...
self.executor = ThreadPoolExecutor(...)
# self を参照しないよう、executor オブジェクトだけを渡す
self._finalizer = weakref.finalize(self, self._shutdown_executor, self.executor)
@staticmethod
def _shutdown_executor(executor):
executor.shutdown(wait=True)
Project インスタンスがGCされたタイミングでスレッドプールも回収されるため、リソース効率が良い。core.py の実装において、GCの挙動と weakref の仕様を理解したコーディングが必要になり、内部的な複雑性が若干増す。Project インスタンスを大量に生成・破棄するような特殊な使い方をした場合、スレッドプールの生成コストがオーバーヘッドになる可能性がある(ドキュメントでシングルトン的な利用を推奨することで緩和する)。親: REQ006
子: —
現状のダッシュボード (dashboard.py) では、タスク一覧の表示と詳細データの復元操作が分離しています。
ユーザーは一覧テーブルで対象を確認した後、別途ドロップダウンメニューから対応する cache_key (ハッシュ値) を手動で探し出す必要があり、認知負荷が高い状態です。
プロジェクトの依存関係として streamlit>=1.51.0 が確保されており、インタラクティブなデータフレーム機能が利用可能となっています。
st.dataframe の on_select 機能を使用し、テーブル行のクリックによって詳細ビュー(Restore Data)のコンテキストを切り替える方式。Chosen option: Option 2.
st.dataframe の on_select 機能を使用し、テーブル行のクリックによって詳細ビューのコンテキストを切り替える方式を採用します。これにより、ユーザーはテーブル上のレコードを直接クリックするだけで、そのタスクの詳細や保存されたデータを復元できるようになります。
pyproject.toml でバージョン指定済みのため許容)。親: REQ011
子: —
生成AIタスクの出力は、テキストだけでなく、画像、構造化データ、ダイアグラム(Mermaid, Graphviz/DOT, HTML)など多岐にわたります。
現状のデータベーススキーマ(result_type = FILE | DIRECT)は「データの保存形式」しか保持しておらず、「データの意味的種類(Semantic Type)」が不明であるため、ダッシュボードでの復元時に適切な可視化(レンダリング)ができません。
また、beautyspot はライブラリとしてユーザーの手元で動作するため、複雑なマイグレーション手順や重量級の依存関係(Alembicなど)を強制することはUXを損なうという課題があります。
content_type カラムを追加し、開発者が @task で明示的に指定できるようにする。あわせて自動マイグレーション機能を実装する。Chosen option: Option 2.
tasks テーブルに content_type (TEXT) カラムを追加する。Project 初期化時(_init_db)に PRAGMA table_info でカラムの存在を確認し、不足していれば標準ライブラリのみで ALTER TABLE を自動実行する。src/beautyspot/types.py を新設し、ContentType クラスで定数を管理する。ContentType.GRAPHVIZ (text/vnd.graphviz)ContentType.MERMAID (text/vnd.mermaid)@project.task デコレータに content_type 引数を追加し、開発者が戻り値の型を宣言できるようにする。graphviz ライブラリと st.graphviz_chart を使用する。st.components.v1.html を使用して Mermaid.js (CDN) を注入し、ブラウザ側でレンダリングする。pyproject.toml に graphviz>=0.20.1 を追加する。dot コマンド(OSレベルのパッケージ)がインストールされている必要がある。親: REQ003
子: —
現在、beautyspot はキャッシュデータのシリアライズ(保存)に Python 標準の pickle を使用している。
これには以下の重大な課題がある:
pickle は信頼できないデータを読み込む際に任意のコード実行(RCE)を引き起こす可能性があり、「共有キャッシュ」としての利用にリスクがある。json は安全だが、画像などのバイナリデータを効率的に扱えず(Base64化でサイズ増)、タプルなどのPython固有型が失われる。v1.0.0 リリースにあたり、「デフォルトで安全(Secure by Default)」 かつ 「拡張性(Extensibility)」 のあるシリアライズ戦略が必要である。
pandas.DataFrame)を、ライブラリ側の変更なしに保存・復元できるようにしたい。pickle のまま)。警告文で責任をユーザーに委ねる。json に一本化する。バイナリは Base64 エンコードを強制する。msgpack を採用し、標準型のみサポートする。msgpack を採用し、ExtType 機能を用いた「カスタム型登録システム」を実装する。Chosen option: Option 4.
シリアライザのバックエンドとして msgpack (MessagePack) を採用し、さらにユーザーが任意の型変換ロジックを注入できる TypeRegistry パターンを導入する。
msgpack>=1.0.0 を pyproject.toml に追加する。pickle はデフォルトから廃止(またはオプトイン化)する。msgpack を使用し、安全な型のみをシリアライズする。Project クラスに register_type(type, code, encoder, decoder) メソッドを追加する。code) と変換関数を登録することで対応できる。TypeError ではなく、具体的なオブジェクト情報と register_type の利用を促す親切なエラーメッセージ (SerializationError) を送出する。msgpack の default (pack時) と ext_hook (unpack時) を活用し、拡張型を msgpack.ExtType としてラップする。
# Implementation Sketch
class Project:
def __init__(self, ...):
# ...
self.serializer = MsgpackSerializer()
def register_type(self, type_: type, code: int, encoder: Callable, decoder: Callable):
"""
Register a custom serializer for a specific type.
Args:
type_: The class to handle (e.g. MyClass)
code: Unique integer ID (0-127) for this type
encoder: Function that converts obj -> bytes
decoder: Function that converts bytes -> obj
"""
self.serializer.register(type_, code, encoder, decoder)
# --- Internal ---
class MsgpackSerializer:
def dump(self, obj):
return msgpack.packb(obj, default=self._default_packer, use_bin_type=True)
def load(self, data):
return msgpack.unpackb(data, ext_hook=self._ext_hook, raw=False)
def _default_packer(self, obj):
# 登録済みの型なら ExtType に変換
if type(obj) in self._encoders:
code, encoder = self._encoders[type(obj)]
return msgpack.ExtType(code, encoder(obj))
# 未登録なら詳細なエラーを出す
raise SerializationError(f"Object of type '{type(obj).__name__}' is not serializable...")
pickle を排除することで、デフォルトでの脆弱性を解消できる。json + Base64 よりも高速かつ省サイズでバイナリデータを扱える。msgpack パッケージへの依存が発生する(ただし軽量であるため許容範囲とする)。pickle で保存されたキャッシュデータ (.pkl) との互換性がなくなるため、v1.0.0 アップデート時にキャッシュクリア(DBリセット)が必要になる。See ADR-0009 for further refinements regarding save_blob=False behavior (Msgpack + Base64) and size guardrails.
親: REQ005
子: —
これまでの beautyspot (v0.x) は、Project クラス内部で TaskDB (SQLite実装) をハードコードしてインスタンス化していた。
# v0.x implementation
self.db = TaskDB(self.db_path)
この設計には以下の課題がある:
v1.0.0 では、ユーザー体験(DX)としての「手軽さ(パスを指定するだけ)」を維持しつつ、アーキテクチャレベルでの柔軟性を確保する必要がある。
TaskDB 実装を注入 (Inject) できるようにする。db_path は維持しつつ、内部のファクトリで実装を切り替える。Project に DB インスタンスを直接渡す Dependency Injection (DI) パターン。Chosen option: Option 3.
src/beautyspot/db.py に抽象基底クラス TaskDB を定義し、インターフェース(save, get, init_schema 等)を強制する。SQLiteTaskDB として再定義する。Project クラスのコンストラクタ引数を db_path: str から db: Union[str, TaskDB] に変更する。str が渡された場合: 内部で SQLiteTaskDB(path) を生成する(Convenience)。TaskDB が渡された場合: そのインスタンスをそのまま使用する(Injection)。Project(..., db=PostgresDB(...)) のように利用可能になる。MemoryTaskDB などを差し込むことで、高速かつクリーンなテストが可能になる。db_path 引数が廃止(または db に統合)されるため、既存コードの引数名変更が必要になる(v1.0.0 での破壊的変更)。親: REQ012
子: —
ADR-0007 で MsgpackSerializer を導入しましたが、SQLiteへの保存方式について以下の課題が残っていました。
TEXT カラム(JSON)ではバイナリデータを扱えず、Numpy配列などが保存できない。TEXT カラムに保存するために Msgpack を Base64 エンコードする案がありましたが、データサイズが約33%増加し、CPUコストもかかる。TEXT カラムに保存する。BLOB カラムを追加し、Msgpack バイナリをそのまま保存する。Chosen option: Option 2.
tasks テーブルに、バイナリデータをそのまま格納するための result_data (BLOB) カラムを追加します。
result_value (TEXT) カラムは、ファイルパスやレガシーなJSONデータの保持用に継続利用する。Project 初期化時に ALTER TABLE で自動的に行われる。データの保存先に関わらず、常に MsgpackSerializer を通過させます。
save_blob=False (Direct Mode):result_data (BLOB) カラム にそのまま保存する。result_type は DIRECT_BLOB とする。save_blob=True (Blob Mode):.bin)として保存。result_value (TEXT) カラム に保存する。result_type は FILE とする。save_blob=False であっても、閾値(デフォルト: 1MB)を超えるデータが渡された場合は、警告ログ (WARNING) を出力して save_blob=True の利用を促します。
result_value カラムを維持するため、旧バージョンのキャッシュデータも読み込み可能。sqlite3 コマンドラインツール等で SELECT した際、中身がバイナリのため視認できない(ダッシュボード等のツールが必要)。親: REQ003
子: —
beautyspot はこれまで、キャッシュキーの生成に json.dumps(sort_keys=True) と MD5 を使用していました。
しかし、以下の課題が顕在化していました:
tolist() や str())による巨大なオーバーヘッドとメモリ消費が発生する。str() に依存したフォールバックでは、巨大なNumpy配列が省略表示(...)された際にハッシュが衝突し、誤ったキャッシュヒットを引き起こす危険性がある。MD5 は現代のセキュリティ基準では非推奨とされており、コンプライアンス上の懸念がある。msgpack エコシステムで統一したい。json.dumps(sort_keys=True) と MD5 を継続利用する。msgpack を用いた独自正規化ロジックと SHA-256 を採用する。Chosen option: Option 2.
キャッシュキー生成ロジックを以下のように刷新します:
canonicalize(obj) を実装する。[[k, v], ...]」に変換し、順序を固定する。numpy をインポートせず、Duck Typing(shape, dtype, tobytes 属性の確認)により検知し、生のバイト列を含むタプルに変換する。これにより完全な一意性を保証する。msgpack でシリアライズする。SHA-256 に変更する。pickle を使用しないため、安全性(RCEフリー)が維持される。親: REQ002
子: —
これまで beautyspot は、関数のキャッシュ化にデコレータ (@project.task) を使用する方法のみを提供していました。しかし、実際の利用シナリオにおいて以下の課題が浮き彫りになりました:
pandas.read_csv)や、外部APIクライアントのメソッドをキャッシュしたい場合、わざわざラッパー関数を定義する必要があり、記述が冗長になる。Project インスタンスが内部で保持する ThreadPoolExecutor が適切にシャットダウンされない場合、プロセスがハングするリスクがある。.db) やBlobディレクトリ (blobs/) がプロジェクトルート直下に作成され、ディレクトリ構成が煩雑になる。None 結果のキャッシュ: time.sleep のように戻り値が None である関数をキャッシュしようとすると、キャッシュミスと誤認され再実行されてしまうバグが存在した。None を返す関数も含め、あらゆる戻り値を正しくキャッシュできること。run メソッドの導入、ワークスペースディレクトリ(.beautyspot/)による集約、コンテキストマネージャによるリソース管理を統合的に実装する。Chosen option: Option 2.
以下の包括的なアーキテクチャ変更を行います。
project.run) の導入デコレータを使用せず、関数オブジェクトとその引数を渡すことで即座にキャッシュ付き実行を行う run メソッドを Project クラスに追加します。
result = project.run(func, arg1, arg2, _save_blob=True)
kwargs) との衝突を避けるため、プレフィックス _ を付与します(例: _save_blob, _version, _content_type)。Project クラスをコンテキストマネージャとして設計し、with ブロック内での利用を推奨パターンとします。これにより、ブロック終了時に確実に shutdown() が呼ばれ、スレッドプール等のリソースが解放されることを保証します。
Project 初期化時にデフォルト設定を受け入れ、個別の実行時にそれを継承・上書きできる仕組みを導入します。優先順位は「個別指定 > プロジェクトデフォルト > システムデフォルト」とします。
すべての生成アーティファクト(DB、Blob)を、デフォルトで隠しディレクトリ .beautyspot/ 配下に集約します。また、初期化時に .gitignore を自動生成し、キャッシュファイルがバージョン管理に含まれるのを防ぎます。
キャッシュヒットの判定において None を特別視せず、専用の番兵オブジェクト (CACHE_MISS) を導入することで、None を返す関数も正しくキャッシュ可能とします。
with 文の使用により、プロセスハングのリスクが大幅に低減した。project.run の引数に特殊なプレフィックスルール(_)が導入されたため、ドキュメントでの周知が必要。親: REQ001
子: —
現在、beautyspot のメインエントリーポイントとして Project クラスが、タスク定義のデコレータとして @project.task が使用されています。しかし、これらの名称には以下の課題があります。
Project の曖昧さ:
ユーザーにとって "Project" は、自身のソースコード全体やリポジトリを指す言葉であることが多いです。ライブラリの管理オブジェクトを Project と呼ぶことで、ユーザーのメンタルモデルとの衝突(「プロジェクトの中にプロジェクトがある?」)を招いています。task の不一致:
task は「仕事・課題」を表す名詞ですが、デコレータの役割は「関数を永続化対象として登録・設定する」という動的な作用です。名詞としての命名は、宣言的なデコレータの性質と完全に一致していません。beautyspot と API の命名に一貫性を持たせること。Project および task)。Project を Spot に、task を mark にリネームする。Chosen option: Option 2.
ライブラリのコアとなる用語を再定義し、以下のリネームを行います。
Project class to Spot管理クラスの名前を Spot に変更します。これにより、「コード上の特定の場所(Spot)を管理する」というニュアンスを持たせ、ライブラリ名 beautyspot との一貫性を持たせます。
@task decorator to @markデコレータ名を @spot.mark に変更します。これは "Marking a spot"(地点に印を付ける)というイディオムに基づいており、「この関数を管理対象としてマークする」という宣言的な意図を明確にします。
run method as spot.run命令的な実行メソッドである run は、名前を変更せずそのまま維持します。これは、「mark(宣言・静的)」と「run(実行・動的)」という役割分担を明確にするためです。
Spot と mark の組み合わせにより、「コード上の重要な箇所に印を付けて管理する」という思想が伝わりやすくなる。spot を使用することで、それが beautyspot のインスタンスであることが一目で分かる。親: REQ001
子: —
これまでの spot.register_type() は命令的であり、クラス定義と登録ロジックが分離してしまうため、凝集度が低いという課題がありました。
また、Pydantic モデルのようなクラスメソッド(例: cls.model_validate_json)をデコーダとして登録したい場合、デコレータ評価時にはまだクラスが未定義(NameError)であるため、綺麗に記述できないという技術的な制約がありました。
register_type() メソッドのみを提供する。register メソッドを追加し、decoder_factory による遅延バインディングを導入する。Chosen option: Option 2.
Spot クラスに register デコレータメソッドを追加し、decoder_factory による遅延バインディング を導入します。
@spot.register(
code=10,
encoder=lambda obj: ...,
decoder_factory=lambda cls: cls.deserialize # Class is passed here after definition
)
class MyModel:
...
register デコレータは、対象クラスの定義完了後(デコレート時)に実行される。decoder_factory が指定されている場合、生成された cls オブジェクトを引数としてファクトリを実行し、実際の decoder 関数を取得する。decoder を用いて、既存の register_type バックエンドに登録する。decoder と decoder_factory という2つの引数が増え、APIが少し複雑になる(ドキュメントでの補完が必要)。親: REQ005
子: —
spot.cached_run() コンテキストマネージャを使用すると、ユーザーは一時的に関数のキャッシュ挙動を適用できます。しかし、Python の with 文のスコープルールにより、ブロック内でバインドされた変数(例: with ... as task:)はブロック終了後もアクセス可能です。
これにより、ユーザーが特定のコンテキスト設定(version="v1" や一時的なストレージ設定など)を持つ関数ラッパーを、意図しない場所で再利用してしまうリスクがあります。これは微妙なバグやリソースリークの原因となります。
spot インスタンスや注入されたインスタンスをより安全に使用できるようにすること。Chosen option: Option 2.
ScopedMark コンテキストマネージャに Runtime Guard パターンを実装します。
cached_run から返される関数は、実行前にこのフラグをチェックするガードでラップされる。with ブロックの外で呼び出された場合、即座に RuntimeError を送出する。ScopedMark の実装がわずかに複雑になる。親: REQ001
子: —
beautyspot は「試行錯誤の高速化」を掲げていますが、ユーザーが失敗した実験結果(キャッシュ)を即座に破棄する手段が Dashboard 上に存在しませんでした。
また、キャッシュ削除のロジックが cli.py に直接実装されており、Core API (Spot クラス) として提供されていないため、プログラムからの制御やテストが困難な状態でした。
加えて、削除機能を実現するには BlobStorageBase (Storage backend) に物理ファイルを削除するインターフェースが必要ですが、これまでの定義には save と load しか存在しませんでした。
delete メソッドを追加し、Storage インターフェースを拡張して、Dashboard からも操作可能にする。Chosen option: Option 2.
Core API への delete メソッドの追加
Spot.delete(cache_key) を正式な API として追加します。このメソッドは、「DBレコードの削除」と「Blobファイルの削除」をアトミック(ベストエフォート)に行う責任を持ちます。
Storage Interface への拡張
BlobStorageBase 抽象基底クラスに delete(location) メソッドを追加します。
delete メソッドの実装が必要となります。Idempotency: ファイルが既に存在しない場合でもエラーを送出せず、静かに終了する(冪等な挙動)ことを推奨実装とします。
Dashboard への削除機能の露出
Dashboard に削除ボタンを配置します。誤操作を防ぐため、確認ダイアログを経由する UI とし、Spot.delete を呼び出します。
BlobStorageBase を継承したカスタムクラスを持つ既存ユーザーコードは、delete メソッドの実装が必要になる。親: REQ010
子: —
title: Nested Msgpack Protocol via Serializer Wrapping status: Accepted date: 2026-02-02 context: Improving DX for Custom Types
これまでは、カスタム型のエンコーダは ExtType の仕様に合わせて「生のバイト列」を返す必要がありました。
これにより、ユーザーは msgpack ライブラリを直接操作する必要があり、Pydantic モデルなどの扱いが煩雑になっていました。
bytes)を返し、デコーダがそれを受け取る(旧仕様)。Chosen option: Option 2.
Serializer Wrapping (Nested Protocol) を採用します。
MsgpackSerializer は、ユーザー定義のエンコーダ/デコーダに対するラッパー(Wrapper)として機能する。packb して ExtType に格納する。ExtType のデータを、ライブラリが自動的に unpackb してからデコーダに渡す。import msgpack をする必要がなくなり、直感的な実装が可能になる。親: REQ005
子: —
現在、beautyspot のテストスイート(tests/)は、主にユニットテストや特定の機能(シリアライザー、リミッターなど)に焦点を当てたテストで構成されている。これらは個々のコンポーネントの正確性を保証する上では有効だが、以下の課題がある:
CliRunner やモックオブジェクトを使用しているため、実際のファイルシステムや SQLite データベースに対する副作用(ファイルの生成、削除、ロック競合など)を検証できていない。stats, clean など)と Python API による実行結果の整合性を、ユーザーの一連の操作フローとして検証する仕組みがない。「個々の部品は正しいが、組み合わせると意図した通りに動かない」という統合レベルのバグを防ぐため、テスト戦略を見直す必要がある。
tests/integration/ ディレクトリを新設し、E2E(End-to-End)シナリオテストを導入する。Chosen option: Option 3.
テストピラミッドの上層として、以下の戦略でインテグレーションテストを導入する:
tests/integration/ を新設し、ユニットテストとは明確に分離する。beautyspot CLI コマンド(stats, list 等)を実行し、出力が正しいことを検証する。tmp_path フィクスチャを活用)。beautyspot.Spot クラスを実際にインスタンス化し、ユーザーコードと同じインターフェース経由で操作を行う。def test_ml_pipeline_lifecycle(spot_env):
# 1. Pipeline Definition (Load -> Process -> Train)
# 2. Initial Run (Assert all executed)
# 3. Cache Hit Run (Assert none executed)
# 4. Version Upgrade of Middle Task (Assert downstream re-executed)
# 5. CLI "stats" command check (Assert DB integrity)
親: REQ010
子: —
プロジェクト全体としては安全性と互換性の観点から msgpack を標準シリアライザとしています。
しかし、探索的データ分析(EDA)やプロトタイピング、あるいは msgpack でのシリアライズが困難なサードパーティ製オブジェクトを扱う特定のタスクにおいては、Python標準の pickle のような柔軟なシリアライザを局所的に使用したいというニーズがあります。
Spot.mark() および Spot.cached_run() に serializer 引数を追加し、局所的なオーバーライドを可能にする。Chosen option: Option 2.
Spot.mark() および Spot.cached_run() に、オプショナル引数 serializer を追加します。dumps(obj) -> bytes および loads(bytes) -> obj メソッドを持つ任意のオブジェクトを渡すことができます。pickle 等の機能を利用できる。pickle を使用したタスクは、異なる環境間や長期間の保存において互換性が保証されない。Spot.register() で登録したカスタム型変換は、オーバーライドされたシリアライザには適用されない。親: REQ005
子: —
spot.delete(key) 機能は、特定のタスクに関連する「キャッシュレコード(DB)」と「実データ(Blob/File)」の両方を削除することを目的としています。
ローカルファイルシステムやS3などの外部ストレージにおいて、Blobの削除操作は様々な理由(ネットワーク障害、一時的な権限エラー、ファイルが既に手動で削除されている等)で失敗する可能性があります。
このとき、厳密な整合性を求めて「Blob削除に失敗したらDBレコードの削除もロールバック(中断)する」という実装にすると、以下の問題が発生します:
Chosen option: Option 2.
削除操作において 「メタデータの削除を優先する」 ポリシーを採用します。
WARNING レベルのログを出力してエラーを捕捉する。beautyspot の管理下からタスクを確実に削除できる。親: REQ010
子: —
プロジェクトの規模拡大に伴い、以下の課題が発生しています:
Chosen option: Option 2.
以下のツールを導入し、開発フローに統合します。
tools/ に配置する。graphviz (システムパッケージ) のインストールが必須となる。親: REQ010
子: —
quality_report により、src/beautyspot/cachekey.py の canonicalize 関数が非常に高い循環的複雑度(ランク D)を持っていることが判明しました。この関数は、ハッシュ化のためのオブジェクト正規化において、dict, list, set, numpy, type など多岐にわたる型をチェックするために長い if-elif-else チェーンを使用していました。
この構造は開放閉鎖の原則 (Open-Closed Principle) に違反しており、新しい型のサポートを追加するたびにコア関数を修正する必要があるため、退行(デグレード)のリスクを高めていました。
if-elif チェーンを維持する。functools.singledispatch を使用してリファクタリングする。Chosen option: Option 2.
canonicalize 関数を functools.singledispatch を用いて刷新します。
str)、およびダックタイピングによるチェック(Numpy 配列や __dict__ を持つオブジェクト等)を処理する。dict, list, tuple, set, frozenset, type 等の特定のロジックは、装飾された独立した関数に移動する。singledispatch は厳密な型に基づいているため、Numpy のようなダックタイピングによるチェックは依然としてデフォルトハンドラや基底層で行う必要がある。親: REQ002
子: —
現在、src/beautyspot/cli.py には以下のビジネスロジックが直接記述されています:
1. Pruning: タイムスタンプに基づく古いタスクの削除。
2. Cleaning: 孤立した Blob ファイルの特定と削除(ガベージコレクション)。
これらのロジックは sqlite3 ドライバに直接アクセスしたり、ファイルパスを操作したりしており、TaskDB や BlobStorageBase の抽象化をバイパスしています。これにより、CLI が SQLite やローカルファイルストレージの詳細に密結合し、他のインターフェース(スクリプトや Web UI)から同じロジックを再利用できないという問題が発生しています。また、S3 などの外部ストレージに対する「掃除」機能の実装も困難になっています。
Spot クラスに追加する。MaintenanceService を新設し、管理ロジックを分離する。Chosen option: Option 3.
メンテナンスロジックを cli.py から抽出し、専用の MaintenanceService (src/beautyspot/maintenance.py) に移動します。
Spot クラスはアプリケーションの実行コンテキストとキャッシュ制御に集中させ、メンテナンス(行政的タスク)は別の関心事として分離します。
MaintenanceService: レコードの整理、孤立した Blob の掃除、履歴の照会などを担当。TaskDB と BlobStorageBase を調整(Orchestrate)する。Spot: 実行時の登録 (mark) と実行 (run) に専念する。Spot) を軽量に保ちつつ、管理ロジックを独立して進化させることができる。list_keys 等を実装していれば、S3 等でも自動的に GC がサポートされる。TaskDB や BlobStorageBase に新たなメソッド(list_keys, get_blob_refs 等)を追加する必要がある。親: REQ010
子: —
大規模なデータのシリアライズや、S3 等のリモートストレージへの保存処理は、ユーザーのメインロジックの実行を妨げる(ブロッキング)要因となっていました。「インフラとしてのキャッシュ処理がユーザーを待たせない」という設計思想を強化する必要があります。
Spot インスタンスを何度も再利用でき、かつリソースの適切なクリーンアップ(フラッシュ)が行えること。with ブロックによる自動フラッシュの仕組みを導入する。Chosen option: Option 2.
キャッシュの保存処理をバックグラウンドで実行し、かつリソースの整合性を保つためのライフサイクル管理を導入します。
wait=False モードの導入: @spot.mark や cached_run に wait オプションを追加し、保存完了を待たずにメインロジックに結果を返すことを可能にします。with spot: ブロックを抜ける際に、全てのバックグラウンドタスクの完了を待機する(Flush)処理を実行します。Spot インスタンスを繰り返し安全に利用可能にします。with ブロックやプロセス終了時に未完了タスクを必ず待機するため、データの整合性が保たれる。親: REQ006
子: —
beautyspot では、@spot.register デコレータを使用してカスタム型を登録できます。
当初の実装では、Spot クラス内でシリアライザが MsgpackSerializer のインスタンスであるかどうかを明示的にチェックしていました。
if isinstance(self.serializer, MsgpackSerializer):
self.serializer.register(...)
これは、高レベルの Spot クラスを特定の具象クラス (MsgpackSerializer) に結合させており、依存関係逆転の原則 (Dependency Inversion Principle) に違反していました。その結果、カスタム型登録をサポートする他のシリアライザ(将来的な JsonSerializer やカスタムラッパーなど)を使用することが不可能でした。
Spot クラスを特定のライブラリ(msgpack)や実装から切り離すこと。isinstance チェックを維持する。Chosen option: Option 2.
beautyspot.serializer に TypeRegistryProtocol を導入します。
register メソッドのシグネチャを規定するプロトコルを定義します。Spot クラスは、具象クラスではなくこのプロトコルに対して適合性チェックを行うように変更します。SerializerProtocol (dump/load) と TypeRegistryProtocol を分離し、型登録をサポートしないシリアライザも許容できるようにします。msgpack から解放され、TypeRegistryProtocol に従う任意のシリアライザを使用可能になった。register メソッドのシグネチャを正確に遵守する必要がある。親: REQ005
子: —
v2.0 以降、beautyspot は依存性注入 (DI) アーキテクチャを採用しました。Spot クラス(core.py)の初期化には、TaskDB, Serializer, Storage の各インスタンスを明示的に渡す必要があります。
これはテスト容易性と柔軟性の観点では優れていますが、単に「すぐに使い始めたい」だけのユーザーにとっては、初期化が非常に冗長になってしまうという課題がありました。
# 一般的なスクリプトには冗長すぎる
db = SQLiteTaskDB(...)
storage = LocalStorage(...)
serializer = MsgpackSerializer()
spot = Spot(name="app", db=db, storage=storage, serializer=serializer)
core.py の Spot クラスを具象クラス(SQLite 等)の詳細から保護し、ピュアな状態に保つこと。Spot.__init__ の引数にデフォルト値(具象クラス)を設定する。__init__.py でファクトリ関数 Spot を提供し、そこでデフォルトの組み立てを行う。Chosen option: Option 3.
beautyspot/__init__.py において、具象クラスのインスタンス化を伴うファクトリ関数 Spot を公開します。
# beautyspot/__init__.py
def Spot(name: str, db=None, ...):
resolved_db = db or SQLiteTaskDB(...)
# ... 他の解決ロジック ...
return _Spot(name, db=resolved_db, ...)
これにより、ライブラリのトップレベルからインポートされる Spot は関数となり、内部で core.py の _Spot クラスを組み立てて返します。
_Spot クラスが、SQLiteTaskDB などの具象実装をインポートする必要がなくなる(関心の分離)。Spot がクラスではなく関数になるため、Spot を直接継承することが難しくなる。継承が必要な場合は core から _Spot をインポートする必要があるが、本ライブラリでは継承よりもコンポジションを推奨するため、許容範囲とする。親: REQ012
子: —
quality_report.md において、src/beautyspot/cli.py 内の複数の関数(show_cmd, prune_cmd, stats_cmd)が循環的複雑度(CC)で Rank C の警告を受けています。一般的にプロジェクトでは CC の低減を推奨していますが、CLI モジュールに関してはその優先度と費用対効果を再検討する必要があります。
Chosen option: Option 2.
CLI モジュールの Rank C スコアについては、以下の理由から「即時の抜本的なリファクタリングは行わず、現状の構造を維持する」ことを決定しました。
cli.py の複雑さは、データ取得後の表示フォーマット分岐(Rich ライブラリによる出力制御)に集中しており、純粋なビジネスロジックの複雑さとは性質が異なります。cli.py は依存関係の末端に位置しており、他モジュールへの波及効果がありません。cachekey.py など)のリファクタリングにリソースを集中できる。親: REQ011
子: —
beautyspot の clean コマンドやガベージコレクション機能は、DBファイルとBlobストレージディレクトリの対応関係が「自明」であることを前提としています。標準構成(.beautyspot/ 配下)ではこの前提は成立しますが、ユーザーがカスタムパスを設定したり、外部バックエンド(S3等)を使用した場合、CLIツールは安全に依存関係を特定できません。この状態で推測に基づく削除を行うと、誤って無関係なデータを削除するリスクがあります。
Chosen option: Option 2.
CLIが提供する「ストレージの自動クリーニング・削除機能」のサポート範囲を、標準ディレクトリ構成(.beautyspot/ 配下)を使用しているプロジェクト に限定します。カスタム構成を利用している場合は、CLI による自動削除は保証されず、ユーザー自身の責任で管理を行う必要がある旨を明記します。
「対応関係が自明でない」問題を根本解決するため、将来のバージョンで TaskDB内に使用しているStorageの情報をメタデータとして記録する 仕組みを導入します。DB初期化時に storage_uri を保存し、メンテナンス時に「操作対象のDBが参照しているストレージと、今操作しようとしている実体が一致するか」を検証可能にします。
beautyspot clean などの恩恵をフルには受けられない場合がある。親: REQ011
子: —
これまでの beautyspot clean コマンドの実装には、以下の課題がありました。
clean コマンドは個別のファイル削除のみを行い、空になったディレクトリを削除していませんでした。.DS_Store などのシステムファイルが存在する場合、ディレクトリが空とみなされず、削除できないケースがありました。.db) のみを削除した場合、対応する Blob ディレクトリが管理外のゴミ(ゾンビプロジェクト)として残り続け、削除する手段がありませんでした。.DS_Store 等)に惑わされず、意図した通りにクリーンアップを実行できること。gc コマンドを導入する。Chosen option: Option 2.
ストレージのクリーンアップ戦略を以下のように刷新します。
clean コマンドを2段階に分割します。
* Phase 1 (File Deletion): DB参照のない孤立ファイルを削除。
* Phase 2 (Directory Pruning): LocalStorage に prune_empty_dirs() メソッドを追加し、再帰的に空ディレクトリを一括削除。
システム生成ファイル(.DS_Store, Thumbs.db, desktop.ini)のみが存在する場合、それらを「実質的な空」とみなし、強制的に削除した上で親ディレクトリを削除します。
DBファイルが失われたプロジェクトを回収するための gc コマンドを実装します。対応する .db ファイルが存在しないディレクトリを「ゾンビプロジェクト」と判定し、shutil.rmtree で強制的に一括削除します。
Storage クラス内に隠蔽できた。os.walk による再帰探索を行うため、ファイル数が膨大な場合にオーバーヘッドが発生する。親: REQ010
子: —
これまでの beautyspot では、関数の実行結果を Blob ストレージ(ファイル)に保存するか、DB のレコードに直接埋め込むかは、ユーザーが save_blob=True フラグで明示的に指定する必要がありました。
また、データサイズが大きい場合に警告を出す機能はありましたが、自動的に対処する機能はありませんでした。これにより、ユーザーはデータのサイズを予測してフラグを管理するという負担を強いられていました。
save_blob フラグ管理に任せる。StoragePolicy プロトコルを導入し、データ内容に基づいて自動的に保存方式を決定する仕組みを構築する。Chosen option: Option 2.
ストレージ保存方式の決定ロジックを抽象化した StoragePolicy プロトコルを導入します。
StoragePolicy Interface:
should_save_as_blob(data: bytes) -> bool を持つプロトコルを定義します。src/beautyspot/storage.py とし、ストレージ関連の責務を凝集させます。Standard Implementations:
ThresholdStoragePolicy: 指定したバイト数を超えた場合に Blob 保存を選択する(推奨デフォルト)。WarningOnlyPolicy: 従来動作互換。Blob 化はせず、閾値超えでログ警告のみ行う。Precedence (優先順位):
@mark(save_blob=...))。Spot クラスのコンストラクタ引数が増え、旧引数(blob_warning_threshold 等)との互換性管理が必要になる。親: REQ004
子: —
以前、core.py の Spot クラスは TokenBucket 実装と密結合していました。Spot は tpm (tokens per minute) 整数引数を受け取り、内部で TokenBucket をインスタンス化していました。
この設計にはいくつかの制限がありました:
1. Testing: ユニットテストが TokenBucket 内の本物の time.sleep 呼び出しに依存するため、実行が遅くなっていました。
2. Extensibility: ユーザーがカスタムレートリミッター(例:Redisベースの分散リミッター)や異なるアルゴリズムを提供できませんでした。
3. Separation of Concerns: core.Spot がリミッターのライフサイクルと設定を管理しており、単一責任の原則に違反していました。
MockLimiter や NoOpLimiter を注入できるようにすること。core.Spot を簡素化し、リミッターの初期化処理を排除すること。Spot クラス内でリミッターの実装をハードコードする。LimiterProtocol を導入し、依存性注入 (DI) を使用してリミッターを外部から提供する。Chosen option: Option 2.
レートリミッターを core.Spot から切り離し、依存性注入 (DI) を使用します。
beautyspot.limiter に consume(cost: int) と consume_async(cost: int) メソッドを規定する LimiterProtocol を定義します。TokenBucket 実装は、型安全性と明確さのために LimiterProtocol を明示的に継承します。core.Spot.__init__ を、tpm の代わりに limiter: LimiterProtocol インスタンスを受け取るように変更します。__init__.py の Spot ファクトリ関数が、カスタムリミッターが提供されない場合のデフォルトの TokenBucket 作成を担当します。MockLimiter などを注入することで、テストの高速化が可能になった。Spot クラスの責務が削減され、コードが簡素化された。_Spot のシグネチャが変更されるため、直接インスタンス化していたコードに影響がある(公開されている Spot ファクトリ経由であれば影響なし)。親: REQ008
子: —
機械学習や生成AIの実験プロセスにおいて、生成されるデータの重要度は均一ではありません。数ヶ月保持すべき「最終モデル」もあれば、数時間で不要になる「一時的なデバッグ出力」もあります。
現在、これらの古いデータを削除するには、ユーザーが MaintenanceService.prune(older_than=...) を呼び出すスクリプトを自作し、定期実行する必要があります。これはユーザーにとって負担であり、設定を忘れるとディスク容量を圧迫する原因となります。また、beautyspot は常駐プロセスを持たないため、クリーンアップのトリガー設計が課題となります。
retention パラメータとデータベースでの有効期限管理を導入し、アクセス時のチェック(Lazy Expiration)と CLI による一括削除を組み合わせる。Chosen option: Option 2.
ユーザーが「データの寿命(What)」を宣言するだけで済むよう、以下の仕組みを導入します。
retention Parameter for @markデコレータおよび run メソッドに retention 引数を追加します(例: "7d", "1h", None)。
expires_at)タスク作成時に寿命を計算し、tasks テーブルの expires_at カラムに保存します。
キャッシュ取得時(spot.db.get)に、expires_at < current_time であれば「キャッシュミス」とみなして None を返します。この時点では物理削除は行わず、レイテンシへの影響を最小限にします。
期限切れデータの物理削除は、CLI コマンド $ beautyspot gc --expired によって一括で行います。
親: REQ007
子: —
beautyspot では、関数の実行結果をキャッシュに保存する際、wait=False を指定することでバックグラウンドスレッドでの非同期保存を行うことができます。しかし、バックグラウンドでの保存処理中にエラーが発生した場合、これまではログが出力されるのみ(サイレントフェイル)でした。
メインスレッドをクラッシュさせないという点では安全ですが、ユーザーからすると「なぜかキャッシュが効かない」という原因究明が困難な状態に陥るリスクがありました。また、監視ツールへのエラー通知や、カスタムのリカバリ処理を行うためのフックが存在しませんでした。
on_background_error) の導入。エラー発生時に任意の処理を実行可能にする。Chosen option: Option 3.
以下の設計方針で実装します。
on_background_error 引数の追加: Spot.__init__ にコールバックを受け付ける引数を追加します。SaveErrorContext) の導入: ハンドラーには、エラー時の詳細情報(対象の関数名、キャッシュキー、戻り値等)を含む専用のデータクラスを渡します。result (評価済みオブジェクト) の包含: デバッグを容易にするため、キャッシュしようとした実際のオブジェクトを含めます。ただしメモリリークのリスクがあるため、Docstring にて警告を記載します。try-except で保護します。SaveErrorContext で巨大なオブジェクトの参照を保持し続けた場合、メモリを圧迫する可能性がある。親: REQ006
子: —
ADR-0014 では、cached_run が返すラッパー関数を with ブロック外から呼び出した場合に RuntimeError を送出する Runtime Guard パターンを導入しました。しかし、ADR-0023 で Spot インスタンス自体の再利用性(with spot: はフラッシュのみを目的とし、インスタンスの無効化ではない)が確立されたことにより、cached_run のラッパーについても同様にスコープ外での利用を妨げない方針が自然な帰結となりました。
また、Runtime Guard の実装において ContextVar を使用していましたが、呼び出しごとに新規の ContextVar を作成する構造になっており、コンテキスト分離の恩恵が得られていませんでした。さらに、asyncio の一部のパターンにおいてガードが機能しないバグも内包していました。
Spot インスタンスのライフサイクル管理方針と、cached_run ラッパーの挙動に一貫性を持たせること。ContextVar によるガードロジックを排除し、コードを簡素化すること。ContextVar の実装を修正してガードを強化する。Chosen option: Option 2.
cached_run の Runtime Guard を廃止し、返されたラッパー関数は with ブロック外でも呼び出し可能とします。
ContextVar, is_active, make_scoped_guard などの関連ロジックを全て削除します。cached_run は単に self.mark() を対象関数に適用して返すだけの、直感的な実装に変更します。ContextVar の誤用や非同期タスクでの不整合リスクが解消された。Spot インスタンスと同様、ラッパーもスコープに縛られず柔軟に利用可能になった。with ブロック外でのラッパー使用がランタイムで検出されなくなる。ただし、ラッパー自体は @spot.mark 相当の正当な関数であり、副作用は生じないため問題視しない。親: REQ001
子: —
beautyspot のコアロジックを汚染することなく、ユーザーが「トークン計算」や「レイテンシ計測」などのカスタムメトリクスを収集できる仕組みが必要です。また、並列実行環境においても、競合状態を避けつつ安全に状態を共有・更新できることが求められます。
HookBase) と、フェーズ別に分離された専用コンテキストオブジェクトの導入。Chosen option: Option 2.
HookBase)状態(開始時間や累計カウンタ)を保持しやすいよう、クラスベースのインターフェースを採用します。
ThreadSafeHookBase)サブクラスでオーバーライドされたメソッドを自動でロックラッパーで包む仕組みを導入します。これにより、ユーザーは明示的なロック制御なしでスレッドセーフな集計が可能になります。
実行フェーズごとに最適化された 3つの専用コンテキストクラス を提供します。
* PreExecuteContext: 関数実行前。
* CacheHitContext: キャッシュ取得成功時。
* CacheMissContext: 関数実行完了時。
フックが登録されていない場合は、コンテキストオブジェクトの生成を完全にスキップします。
親: REQ009
子: —
Spot クラスは非同期のキャッシュ保存を構造的に直列化するため、内部で専用の asyncio ループを実行するスレッド (_BackgroundLoop) を保持しています。インスタンスがガベージコレクション (GC) によって破棄される際、未保存のキャッシュデータを厳密に待機してしまうと、メインスレッドを予測不能なタイミングでフリーズさせたり、デッドロックを引き起こす危険性があります。
そのため、これまでは安全を優先し、GC 時にはタスクをキャンセルしてリソースを解放する設計となっていました。しかし、この設計では一時的なスコープで Spot を使用した場合、GC のタイミングによってキャッシュデータが失われるという課題がありました。
atexit によるグローバルな安全網を導入し、プロセス終了時に未完了タスクをドレインする。あわせて明示的な管理を推奨する。Chosen option: Option 3.
atexit によるグローバルな安全網の追加:
weakref.WeakSet を用いて起動中のバックグラウンドループを追跡し、プロセス終了時 (atexit) に wait=True でドレイン(残存タスクの処理)を行うフックを実装します。プロセス終了時であれば、メインスレッドをブロックしてもデッドロックの危険性が低いためです。Spot インスタンスは長寿命なオブジェクトとして扱うか、コンテキストマネージャ (with) または shutdown(wait=True) による明示的な管理を行うべきであることを周知します。Spot を使った場合でも、アプリ終了時まではデータが救済される可能性が高まる。atexit 時に大量の未完了タスクがあると、プロセス終了に時間がかかる場合がある。親: REQ006
子: —
beautyspot の Spot クラスは、非同期でのキャッシュ保存タスクを構造的に直列化するため、内部で専用の asyncio ループを実行するバックグラウンドスレッド (_BackgroundLoop) を保持しています。インスタンスのライフサイクル終了時、これらのバックグラウンドタスクを安全に終了させる必要がありますが、シャットダウンのトリガー(明示的呼び出し、アプリ終了、GC)によって、メインスレッドへの影響とプロセスの振る舞いが大きく異なるという課題がありました。
特に、GC は予測不能なタイミングで発生します。ここでタスク完了を待機(スレッドを join)してしまうと、メインスレッドがフリーズしたりデッドロックを引き起こす危険性があります。一方で、待機せずにタスクを強制キャンセルすると、保存待ちだったキャッシュデータが失われてしまいます。
Chosen option: Option 3.
以下の3つの方式を実装し、状況に応じて使い分けます。
stop(wait=True) (完全同期シャットダウン)shutdown()、with ブロックの終了、atexit。stop_gracefully_no_wait() (非同期ドレイン / スレッド切り離し)weakref.finalize による GC 時。stop(wait=False) (強制停止)Spot が一時的なスコープでインスタンス化され GC された場合でも、データロストのリスクがほぼ解消された。親: REQ006
子: —
beautyspot はキャッシュの TTL をサポートしており、期限切れデータは論理的に無効化されます。しかし、ユーザーが明示的にクリーンアップを呼び出さない限り、有効期限切れのメタデータや巨大な孤立 Blob ファイルは物理的に削除されず、ストレージが肥大化し続けるという問題がありました。
ユーザーにインフラ管理を意識させない「黒子」としての哲学を維持しつつ、自動でガベージコレクション(エビクション)を行う仕組みが必要になりました。
Chosen option: Option 3.
確率的自動エビクションを採用します。
Spot の初期化パラメータに eviction_rate: float = 0.0 を追加し、オプトイン方式とします。_BackgroundLoop にオフロードします。親: REQ007
子: —
MsgpackSerializer の _default_packer において、クラスの解決結果を保存するキャッシュ辞書が、動的に型が生成される環境(動的な Pydantic モデル等)で無制限に肥大化し、メモリリークを引き起こす懸念がありました。
また、この辞書へのアクセスは非常に高頻度であるため、厳密なスレッドセーフティ(ロック)を導入すると、シリアライズ性能のボトルネック(ロック競合)を招く恐れがありました。
dict)。メモリリークのリスクがある。OrderedDict を利用した上限付き LRU キャッシュを、楽観的(ロックフリー)なアプローチで実装する。Chosen option: Option 3.
標準ライブラリの collections.OrderedDict を利用し、上限サイズ(デフォルト 1024)を持つ LRU (Least Recently Used) キャッシュ を導入します。マルチスレッド環境下でのアクセスに対しては、意図的にロックフリーなアプローチを維持します。
親: REQ005
子: —
バックグラウンドでの非同期 IO タスクを処理するイベントループにおいて、単純に daemon=True スレッドを使用すると、メインスレッド終了時にプロセスが即座に強制終了され、データ破損やキャッシュのロストが発生する危険がありました。一方で、daemon=False にして atexit で待機を試みるアプローチでは、タスクがハングした場合にプロセス全体が永遠に終了しなくなるデッドロックの罠が存在していました。
daemon=False スレッドを使用し、atexit で待機する。デッドロックやハングのリスクが高い。daemon=True スレッドを維持しつつ、明示的なタスク追跡と猶予期間(Grace Period)付きの atexit ハンドラを実装する。Chosen option: Option 2.
真の耐障害性を持つ Graceful Shutdown を実現するため、以下のアーキテクチャを採用します。
daemon=True で起動し、プロセス終了時の無限ハングを OS の力で防ぎます。submit 時と atexit 時の競合状態を完全に排除します。atexit フック内でメインスレッドをブロックし、タイムアウト付き(デフォルト5秒)で _thread.join() を実行することで、IO タスクが完了するための猶予を与えます。親: REQ006
子: —
Blob データの保存時、アトミックな書き込みを実現するために一時ファイルを作成し、完了後にリネームする設計を採用しています。しかし、アンチウイルスソフトやバックアッププロセスの介入によりリネームが失敗するエッジケースが存在します。この際、一時ファイルの削除もロックにより失敗すると、一時ファイルがストレージ上に永久に残留し続ける(ストレージリーク)という問題がありました。
MaintenanceService による遅延ガベージコレクション (GC) を導入する。Chosen option: Option 3.
アトミック書き込みのフェイルセーフとして、以下の機構を導入します。
.spot_tmp という専用のサフィックスを付与し、追跡を容易にします。MaintenanceService.clean_garbage に、一時ファイルの削除処理を統合します。親: REQ010
子: —
MsgpackSerializer は動的型生成時のメモリリークを防ぐために、内部で OrderedDict を用いた LRU キャッシュを運用しています。しかし、バックグラウンド保存や Web フレームワークでの利用など、マルチスレッド環境からの並行アクセスが日常的に発生します。ロックを持たない OrderedDict への並行操作は、RuntimeError やデータの破損を引き起こす致命的なバグの温床となっていました。
threading.Lock() を導入し、キャッシュ操作を排他的に行う。Chosen option: Option 2.
MsgpackSerializer の内部状態(キャッシュおよびレジストリ)に対するすべての読み書き操作を threading.Lock() で保護します。
MsgpackSerializer をシングルトンとして安全にマルチスレッド環境で使い回せるようになった。親: REQ005
子: —
beautyspot は wait=False のバックグラウンド保存や async 経路で、
複数スレッドから同時に SQLite へ書き込みを行う可能性がある。
WAL モードを有効化していても SQLite の書き込みは単一ライター制約があり、
高並行時に database is locked が発生し得る。
一方で、アプリケーションの関数実行自体は失敗させたくない。 また、シャットダウン時には必ず書き込みをフラッシュしたい。
shutdown(wait=True) で書き込みを確実にフラッシュする。save/delete を直列化(単一接続は持たない)。Chosen option: Option 4.
SQLiteTaskDB に writer スレッドとキューを実装し、
書き込み系操作を単一接続で直列化する。
シャットダウン時は shutdown(wait=True) によりキューを drain し、
未処理の書き込みが残らないようにする。
さらに、保存失敗は関数実行を失敗させず、
ログと on_background_error で通知する。
SQLiteTaskDB 内部に queue.Queue と writer スレッドを保持。_enqueue_write() 経由でシリアライズ。TaskDBBase.shutdown() を追加し、SQLiteTaskDB.shutdown(wait=True) でフラッシュ。Spot.shutdown(wait=True) から db.shutdown(wait=True) を呼び出す。wait=True 保存時の失敗も例外として外に出さず、ログとコールバックで通知。database is locked の発生率が下がる。shutdown(wait=True) で書き込みを確実にフラッシュできる。保存失敗がアプリケーションの関数実行に影響しない。
Negative:
shutdown(wait=True) がブロックし続ける。本 ADR はプロセス内の直列化を対象とする。複数プロセスが同一 SQLite を共有する 運用では、依然としてロック競合が起こり得るため注意する。
親: REQ003
子: —
v1.x 系の LocalStorage では、ファイルパス(location)として絶対パスを許容・解決する挙動が含まれていました。v2.0 の開発において、パストラバーサル脆弱性を防ぐために base_dir に対する厳密なセキュリティチェックを導入しましたが、これが旧来の絶対パスの挙動と競合し、意図せぬクラッシュを引き起こす可能性がありました。
base_dir の管理下に限定されることを保証すること。base_dir からの相対パスとして厳密に解釈する。Chosen option: Option 2.
LocalStorage.load() および関連するファイルアクセスにおいて、絶対パスによる後方互換性の維持を公式に 放棄 します。
すべての location パラメータは base_dir に対する相対パスとしてのみ解釈されます。絶対パスの形式であっても、それが base_dir のサブディレクトリ内に解決されない限り、セキュリティチェックにより ValueError として処理されます。
親: REQ004
子: —
SQLiteTaskDB は、ライタースレッドとキューを用いた非同期書き込みを行っています。しかし、メインプロセスの終了時や Spot.flush() 実行時に、ストレージへの I/O は待機するものの、DB への書き込みキューのフラッシュ(全タスクの完了)を明示的に待機する仕組みがありませんでした。これにより、短命なスクリプト等で DB へのデータ書き込みが完了する前にプロセスが終了し、メタデータが消失するリスクがありました。
flush() 実行時に、全ての DB 書き込みタスクが完了していることを保証すること。TaskDBBase に flush(timeout) を追加する。SQLiteTaskDB では「No-op タスク」をキューに投入し、その完了を待機する方式を採用する。Chosen option: Option 2.
TaskDBBase に flush(timeout) インターフェースを追加し、SQLiteTaskDB で実装します。
SQLiteTaskDB の実装では、「何もしない (No-op) 書き込みタスク」をエンキューし、そのタスクの完了イベントを待機するというアプローチを採用します。この flush メソッドを Spot.flush() から呼び出すことで、ストレージと DB の両方の完了を確実に待機させます。
flush() 呼び出し時に、キューに積まれた既存タスクの量に応じて待機時間が発生する。timeout を超えた場合の挙動(ログ出力等)を適切に定義・管理する必要がある。親: REQ003
子: —
これまで beautyspot では、 Factory 関数 Spot() の初期化時において、デフォルトのキャッシュディレクトリ(.beautyspot/)の作成と .gitignore の配置を一律で行っていました。しかし、ユーザーがカスタムのパスを指定した場合でも、意図せずカレントディレクトリに .beautyspot/ が作成されてしまうという課題がありました。また、コンポーネント(DB、ストレージ)が自身の永続化先の詳細を自己管理できておらず、関心の分離の観点で不完全な設計となっていました。
Spot() 内で、全てのコンポーネントの設定を読み取り、一括でディレクトリを作成する。Chosen option: Option 2.
__init__.py および core.py からワークスペース初期化ロジック(_setup_workspace)を完全に削除します。代わりに、ローカルファイルシステムに依存する各バックエンドコンポーネント(LocalStorage および SQLiteTaskDB)の初期化処理内で、自身が使用するディレクトリの作成と .gitignore の配置を行うように責務を委譲します。
.gitignore 配置のロジックを書く必要がある。親: REQ004
子: —
beautyspot はキャッシュの永続化をバックグラウンドスレッドにオフロードしています。コンテキストマネージャを使用した場合は安全にタスクがドレインされますが、インスタンスをグローバルに生成して使い捨てた場合など、ガベージコレクション (GC) によってインスタンスが破棄される際の振る舞いが課題となっていました。GC のタイミングで未完了の I/O タスクを待機すべきか、それとも破棄すべきかを決定する必要があります。
Chosen option: Option 2.
ガベージコレクション (weakref.finalize) によるインスタンス破棄時には、未完了のタスクを待機せず、即座に破棄する(Fail-fast & Non-blocking) ことを決定しました。
同時に、タスクが破棄された場合は logger.warning および ResourceWarning を発行し、ユーザーに対してコンテキストマネージャや明示的な shutdown() の使用を強く促します。
beautyspot が原因でアプリケーションの GC やシャットダウンがハングアップしないことが保証される。親: REQ006
子: —
SQLiteTaskDB は、専用のライタースレッドを用いて書き込みを直列化しています。メインスレッドが書き込み完了を待機する際(または flush 実行時)、ライタースレッドが予期せず死亡(セグメンテーションフォールトや深刻なエラー等)した場合に、メインスレッドが永久にハングアップするのを防ぐ必要があります。
is_alive())を組み合わせる。Chosen option: Option 3.
0.5 秒というポーリング間隔を維持し、ループ内で task.event.wait(timeout=0.5) とスレッドの生存確認を行う設計を継続します。
threading.Event.wait はイベントがセットされた瞬間に即座に復帰するため、正常な書き込み時には 0.5 秒という数値はレイテンシに一切影響しません。親: REQ003
子: —
Spot インスタンスは、バックグラウンドで実行中の保存タスク(Future)を内部のセットで管理しています。これはシャットダウン時や flush() 呼び出し時に、すべてのタスクが完了したかを確認するために必要です。この追跡において、強参照を使用すべきか、それとも GC を妨げない弱参照を使用すべきかを決定する必要があります。
Future オブジェクトへの参照を即座に破棄した場合でも、タスクが途中で GC されることなく確実に完了まで実行されること。flush() 呼び出し時に、全ての未完了タスクを漏れなく検知し、待機できること。weakref.WeakSet を使用する。参照がなくなると自動的に削除されるが、待機すべきタスクが GC のタイミングで勝手に消えてしまうリスクがある。set(強参照)を使用し、タスク完了のコールバックによって明示的にセットから削除する。Chosen option: Option 2.
タスクの追跡には通常の set を使用し、強参照によってタスクの生存を保証します。
Spot が強参照を保持することで、タスクが確実に完了まで実行されることを保証します。WeakSet で発生しうる「待機すべきタスクが勝手に消える」という不透明な挙動を排除します。flush() の挙動が正確になり、確実なドレインが可能になる。atexit 等のシャットダウン機構によってプロセスレベルでの救済策が講じられているため、許容可能である。親: REQ006
子: —
Spot クラスは TaskDBBase インスタンスを DI で受け取っているにもかかわらず、シャットダウン処理内で強制的に db.shutdown() を呼び出していました。これにより、複数の Spot インスタンスで 1 つの DB を共有する場合、一方の Spot が破棄されると他の Spot も DB にアクセスできなくなるという重大なバグが生じていました。これは DI の導入と、GC 時の強制破棄戦略が複雑に絡み合った結果の技術的負債でした。
Spot インスタンス間で単一の DB インスタンスを安全に共有できるようにすること。Spot が引き続き DB のシャットダウンを担当する。インスタンス共有時にクラッシュするリスクがある。Spot クラスから db.shutdown() の呼び出しを削除し、DB を生成・注入した側の責任とする。Chosen option: Option 2.
Spot クラス内部から db.shutdown() の呼び出しを完全に削除します。リソース(DB インスタンス)を生成して注入した側が、そのライフサイクルの全責任を負うという原則を厳格に適用します。
Spot インスタンスでの DB 共有が安全に行えるようになり、関心の分離が明確になった。親: REQ003
子: —
beautyspot の SQLiteTaskDB は、並行読み取り性能を高めるため、各スレッドごとに専用のコネクションを保持する設計となっています。シャットダウン時には WAL のチェックポイントを妨げないよう、これらの接続を一括で閉じようとしていましたが、以下の深刻な問題がありました。
1. check_same_thread の制約: 別スレッドから close() を呼ぶとエラーが発生する。
2. クラッシュの危険性: クエリ実行中に強制クローズされるとセグメンテーションフォールトを引き起こす。
3. リカバリ時のデッドロック: エラー発生時の再接続処理で、再帰的なロック取得によりハングする。
4. GC 時のブロック: ロック解放を待機する設計では、GC 時にメインスレッドがフリーズし、ADR-0045 の「GC 時は絶対にメインスレッドをブロックさせない」という原則に違反する。
RLock) とノンブロッキングクローズ (blocking=False) を組み合わせたフェイルファストなクリーンアップ。Chosen option: Option 2.
相反する要件を解決するため、以下の機構を導入します。
threading.RLock) の導入: 同一スレッド内でのリカバリ処理によるデッドロックを防ぎます。check_same_thread=False を指定し、別スレッドからのクローズを許可します。close() 呼び出しは、ロック取得を ノンブロッキング (blocking=False) で試行します。親: REQ019
子: —
db.py (SQLiteTaskDB) では、バックグラウンドでの書き込みを直列化するために専用の Writer スレッドとキューを使用しています。シャットダウン時 (shutdown(wait=True)) には、キューに積まれた全てのタスクの完了を待機しますが、タイムアウトが発生した場合の振る舞いが問題となります。特に、現在実行中 (RUNNING) の書き込みタスクを強制的にキャンセルすべきかどうかの設計判断が求められました。
Chosen option: Option 2.
実行中の書き込みタスク (RUNNING) は、タイムアウト時間を超過した場合でも強制的にキャンセルしない設計としました。
SQLite のトランザクション実行中にスレッドを強制終了させたり、非同期的に接続を閉じたりすると、データベースファイルが破損する(Corruption)、あるいはWALファイルが不適切な状態になるリスクがあります。
キューの処理ロジックにおいて、すでにキューから取り出され実行状態に入ったタスクは「不可分なアトミック操作」と見なします。シャットダウンのタイムアウトは「新たなタスクの取り出し」を停止するためには機能しますが、実行中のI/O処理を途中で引き裂くことはしません。これにより、システムの安全性とデータ一貫性を最優先しています。
親: REQ019
子: —
beautyspot では、バックグラウンドでのI/Oタスク処理に専用のスレッド(_BackgroundLoop)と、ブロッキング操作を委譲する ThreadPoolExecutor の2つを組み合わせて使用しています。
Spot.shutdown(save_sync=False) またはプロセスのシャットダウン時に、これら2つのリソースを破棄する順序やタイミングの不整合が問題となりました。
具体的には、バックグラウンドループ内の非同期タスクが run_in_executor に依存している状態(I/O待ち)で、先に Executor が cancel_futures=True で強制終了されると、タスク側で予期せぬ CancelledError や RuntimeError が発生し、クラッシュやログのノイズ、正常な後処理(一時ファイルの削除やDB切断など)の阻害を引き起こすリスクがありました。
save_sync=False)であっても、システムがクラッシュせず、残存タスクが安全に破棄されること。run_in_executor を呼び出すバックグラウンドタスク内で CancelledError および RuntimeError を明示的に捕捉・ハンドリングする。Chosen option: Option 2.
シャットダウン手順の順序自体(Loopの停止 -> Executorの停止)は論理的に正しいものの、強制停止(save_sync=False または Loop のドレインタイムアウト)時には、Executor内のタスクがキャンセルされることを前提とした防御的プログラミングを採用します。
具体的には、_save_result_async 内で run_in_executor を呼び出す際に try...except (asyncio.CancelledError, RuntimeError) ブロックを設け、シャットダウンによる強制キャンセルを安全に捕捉します。捕捉した場合は、on_background_error コールバックを呼び出すか、適切に警告を記録することで、クラッシュを防ぎつつ通知を行います。
save_sync=False による明示的な要求動作として許容される。親: REQ006
子: —
src/beautyspot/core.py において、非同期タスクのIOオフロード用に ThreadPoolExecutor がグローバル変数 _io_executor として定義されている。
この実装には以下の課題がある:
max_workers=4)がハードコードされており、ユーザーが実行環境(AWS Lambda、強力なサーバー等)に合わせて調整できない。代替案として with 文(Context Manager)による管理も検討されたが、beautyspot の「デコレータを付与するだけで動作する」という簡易な利用体験(DX)を損なうため、採用には至らなかった。ユーザーコードを変更させずに、安全にリソースをクリーンアップする仕組みが必要である。
shutdown() の呼び出しや with 文を強制したくない。Project を Context Manager 化し、__exit__ で shutdown() を呼ぶ。atexit モジュールを使い、終了時に shutdown() を呼ぶ。weakref.finalize で自動クリーンアップを行う。Chosen option: Option 4.
Project クラスの設計を以下のように変更する:
Project インスタンスごとに ThreadPoolExecutor を保持する。Executor の注入を許可する。executor 引数あり): Project はそれを借用するのみ。シャットダウンの責任はユーザー(呼び出し元)にあるため、自動クリーンアップは行わない。executor 引数なし): Project がライフサイクルを管理する責任を持つ。weakref.finalize を使用してシャットダウンを自動化する。weakref.finalize instead of atexit?単純な atexit.register(self.shutdown) を採用しなかった理由は、メモリリーク(循環参照)のリスク である。
atexit レジストリにインスタンスメソッド(self.shutdown)を登録すると、atexit が self への強参照(Strong Reference)を保持し続ける。これにより、アプリケーション実行中に Project インスタンスが不要になってもガベージコレクション(GC)されず、メモリリークを引き起こす。これを防ぐには atexit.unregister が必要になるが、管理が複雑になる。weakref.finalize(self, func, *args) を採用する。self がGCされた時点で、即座に func(クリーンアップ処理)が実行される。self が生存していた場合も、atexit 相当のタイミングで func が実行される。_shutdown_executor は staticmethod とし、引数には self ではなく self.executor のみを渡す。これにより、ファイナライザが self を参照し続けて寿命を延ばしてしまう事故を防ぐ。# Implementation Sketch
class Project:
def __init__(self, ...):
# ...
self.executor = ThreadPoolExecutor(...)
# self を参照しないよう、executor オブジェクトだけを渡す
self._finalizer = weakref.finalize(self, self._shutdown_executor, self.executor)
@staticmethod
def _shutdown_executor(executor):
executor.shutdown(wait=True)
Project インスタンスがGCされたタイミングでスレッドプールも回収されるため、リソース効率が良い。core.py の実装において、GCの挙動と weakref の仕様を理解したコーディングが必要になり、内部的な複雑性が若干増す。Project インスタンスを大量に生成・破棄するような特殊な使い方をした場合、スレッドプールの生成コストがオーバーヘッドになる可能性がある(ドキュメントでシングルトン的な利用を推奨することで緩和する)。親: REQ006
子: —