Coverage for src/beautyspot/__init__.py: 89%

1# src/beautyspot/__init__.py

2

3 import logging

4 import warnings

5 from importlib.metadata import version, PackageNotFoundError

6 from pathlib import Path

7 from typing import Any, Optional, Callable

8

9 from beautyspot.core import Spot as _Spot

10 from beautyspot.cache import CacheManager as _CacheManager

11

12 from beautyspot.types import (

13 SaveErrorContext,

14 PreExecuteContext,

15 CacheHitContext,

16 CacheMissContext,

17)

18 from beautyspot.cachekey import KeyGen

19 from beautyspot.lifecycle import LifecyclePolicy, Rule, Retention

20 from beautyspot.limiter import Gcra, LimiterProtocol

21 from beautyspot.content_types import ContentType

22 from beautyspot.db import TaskDBBase, TaskDBCore, TaskDBMaintenable, SQLiteTaskDB

23 from beautyspot.exceptions import (

24 BeautySpotError,

25 CacheCorruptedError,

26 SerializationError,

27 ConfigurationError,

28 ValidationError,

29 IncompatibleProviderError,

30)

31 from beautyspot.storage import (

32 BlobStorageBase,

33 BlobStorageCore,

34 BlobStorageMaintenable,

35 LocalStorage,

36 StoragePolicyProtocol,

37 WarningOnlyPolicy,

38 ThresholdStoragePolicy,

39 AlwaysBlobPolicy,

40)

41 from beautyspot.serializer import SerializerProtocol, MsgpackSerializer

42 from beautyspot.hooks import HookBase, ThreadSafeHookBase

43

44 try:

45 __version__ = version("beautyspot")

46 except PackageNotFoundError:

47 __version__ = "0.0.0+unknown"

48

49

50 _UNSET: Any = object()

51

52

53 def _resolve_renamed(

54 new_val: Any,

55 old_val: Any,

56 new_name: str,

57 old_name: str,

58 default: Any,

59) -> Any:

60 """新旧パラメータ名を解決するヘルパー。旧名使用時は DeprecationWarning を発行する。"""

61 if new_val is not _UNSET and old_val is not _UNSET: 61 ↛ 62line 61 didn't jump to line 62 because the condition on line 61 was never true

62 raise IncompatibleProviderError(

63 f"Cannot specify both '{new_name}' and deprecated '{old_name}'."

64 )

65 if old_val is not _UNSET: 65 ↛ 66line 65 didn't jump to line 66 because the condition on line 65 was never true

66 warnings.warn(

67 f"'{old_name}' is deprecated, use '{new_name}' instead.",

68 DeprecationWarning,

69 stacklevel=3,

70 )

71 return old_val

72 if new_val is not _UNSET:

73 return new_val

74 return default

75

76

77 def Spot(

78 name: str,

79 db: Optional[TaskDBMaintenable] = None,

80 serializer: Optional[SerializerProtocol] = None,

81 limiter: Optional[LimiterProtocol] = None,

82 storage_backend: Optional[BlobStorageMaintenable] = None,

83 storage_policy: Optional[StoragePolicyProtocol] = None,

84 cache: Optional[_CacheManager] = None,

85 # --- Configuration Options ---

86 lifecycle_policy: Optional[LifecyclePolicy] = None,

87 gc_probability: float = 0.0,

88 blob_warning_threshold: int = 1024 * 1024,

89 save_blob: bool = False,

90 tokens_per_minute: int = 10000,

91 save_sync: bool = True,

92 flush_timeout: float = 5.0,

93 flush_poll_interval: float = 0.5,

94 on_save_error: Optional[Callable[[BaseException, SaveErrorContext], None]] = None,

95 # --- Deprecated aliases (backward compat) ---

96 eviction_rate: Any = _UNSET,

97 tpm: Any = _UNSET,

98 drain_timeout: Any = _UNSET,

99 drain_poll_interval: Any = _UNSET,

100 on_background_error: Any = _UNSET,

101) -> _Spot:

102 """

103 Beautyspotのメインエントリポイント（Factory Function）。

104 依存関係の解決とデフォルト設定の適用を行います。

105 """

106

107 # 0. 非推奨パラメータの解決

108 effective_gc_prob: float = _resolve_renamed(

109 gc_probability if gc_probability != 0.0 else _UNSET,

110 eviction_rate, "gc_probability", "eviction_rate", 0.0,

111 )

112 effective_tpm: int = _resolve_renamed(

113 tokens_per_minute if tokens_per_minute != 10000 else _UNSET,

114 tpm, "tokens_per_minute", "tpm", 10000,

115 )

116 effective_flush_timeout: float = _resolve_renamed(

117 flush_timeout if flush_timeout != 5.0 else _UNSET,

118 drain_timeout, "flush_timeout", "drain_timeout", 5.0,

119 )

120 effective_flush_poll: float = _resolve_renamed(

121 flush_poll_interval if flush_poll_interval != 0.5 else _UNSET,

122 drain_poll_interval, "flush_poll_interval", "drain_poll_interval", 0.5,

123 )

124 effective_on_save_error = _resolve_renamed(

125 on_save_error if on_save_error is not None else _UNSET,

126 on_background_error, "on_save_error", "on_background_error", None,

127 )

128

129 # 1. デフォルトパス使用時のみワークスペースをセットアップ

130 _default_workspace = Path(".beautyspot")

131

132 # 2. コンポーネントの解決 (DI)

133 resolved_db = db or SQLiteTaskDB(_default_workspace / f"{name}.db")

134 resolved_ser = serializer or MsgpackSerializer()

135 resolved_stg = storage_backend or LocalStorage(_default_workspace / "blobs" / name)

136 resolved_limiter = limiter or Gcra(tokens_per_minute=effective_tpm)

137

138 # 3. Storage Policy の解決

139 resolved_policy: StoragePolicyProtocol

140 if storage_policy is not None:

141 resolved_policy = storage_policy

142 elif save_blob:

143 resolved_policy = AlwaysBlobPolicy()

144 else:

145 logger = logging.getLogger("beautyspot")

146 resolved_policy = WarningOnlyPolicy(

147 warning_threshold=blob_warning_threshold, logger=logger

148 )

149

150 # 4. CacheManager の組み立て (Composition)

151 resolved_cache = cache or _CacheManager(

152 db=resolved_db,

153 storage=resolved_stg,

154 serializer=resolved_ser,

155 storage_policy=resolved_policy,

156 lifecycle_policy=lifecycle_policy,

157 )

158

159 # 5. Core Spot の生成

160 # _owns_db: ファクトリ関数で内部的に DB を生成した場合のみ True。

161 # GC 時のファイナライザが DB シャットダウンするかを決定する。

162 # コンストラクタ引数で渡すことで、ファイナライザのキャプチャタイミング問題を防ぐ。

163 spot = _Spot(

164 name=name,

165 cache=resolved_cache,

166 limiter=resolved_limiter,

167 # その他のオプション

168 save_sync=save_sync,

169 gc_probability=effective_gc_prob,

170 flush_timeout=effective_flush_timeout,

171 flush_poll_interval=effective_flush_poll,

172 on_save_error=effective_on_save_error,

173 _owns_db=(db is None),

174 )

175

176 return spot

177

178

179# isinstance(spot, bs.SpotType) のための型エクスポート

180 SpotType: type[_Spot] = _Spot

181

182 __all__ = [

183 # --- Core ---

184 "Spot",

185 "SpotType",

186 "KeyGen",

187 "ContentType",

188 "SaveErrorContext",

189 # --- Exceptions ---

190 "BeautySpotError",

191 "CacheCorruptedError",

192 "SerializationError",

193 "ConfigurationError",

194 "ValidationError",

195 "IncompatibleProviderError",

196 # --- Protocols & Base Classes (for custom implementations) ---

197 "TaskDBCore",

198 "TaskDBMaintenable",

199 "TaskDBBase",

200 "BlobStorageCore",

201 "BlobStorageMaintenable",

202 "BlobStorageBase",

203 "SerializerProtocol",

204 "StoragePolicyProtocol",

205 "LimiterProtocol",

206 # --- Default Implementations ---

207 "SQLiteTaskDB",

208 "LocalStorage",

209 "MsgpackSerializer",

210 "Gcra",

211 "ThresholdStoragePolicy",

212 "WarningOnlyPolicy",

213 "AlwaysBlobPolicy",

214 # --- Lifecycle ---

215 "LifecyclePolicy",

216 "Rule",

217 "Retention",

218 # --- Hooks ---

219 "HookBase",

220 "ThreadSafeHookBase",

221 "PreExecuteContext",

222 "CacheHitContext",

223 "CacheMissContext",

224]