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

53 statements  

« prev     ^ index     » next       coverage.py v7.13.2, created at 2026-03-18 18:18 +0900

1# src/beautyspot/__init__.py 

2 

3import logging 

4import warnings 

5from importlib.metadata import version, PackageNotFoundError 

6from pathlib import Path 

7from typing import Any, Optional, Callable 

8 

9from beautyspot.core import Spot as _Spot 

10from beautyspot.cache import CacheManager as _CacheManager 

11 

12from beautyspot.types import ( 

13 SaveErrorContext, 

14 PreExecuteContext, 

15 CacheHitContext, 

16 CacheMissContext, 

17) 

18from beautyspot.cachekey import KeyGen 

19from beautyspot.lifecycle import LifecyclePolicy, Rule, Retention 

20from beautyspot.limiter import Gcra, LimiterProtocol 

21from beautyspot.content_types import ContentType 

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

23from beautyspot.exceptions import ( 

24 BeautySpotError, 

25 CacheCorruptedError, 

26 SerializationError, 

27 ConfigurationError, 

28 ValidationError, 

29 IncompatibleProviderError, 

30) 

31from beautyspot.storage import ( 

32 BlobStorageBase, 

33 BlobStorageCore, 

34 BlobStorageMaintenable, 

35 LocalStorage, 

36 StoragePolicyProtocol, 

37 WarningOnlyPolicy, 

38 ThresholdStoragePolicy, 

39 AlwaysBlobPolicy, 

40) 

41from beautyspot.serializer import SerializerProtocol, MsgpackSerializer 

42from beautyspot.hooks import HookBase, ThreadSafeHookBase 

43 

44try: 

45 __version__ = version("beautyspot") 

46except PackageNotFoundError: 

47 __version__ = "0.0.0+unknown" 

48 

49 

50_UNSET: Any = object() 

51 

52 

53def _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 

77def 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) のための型エクスポート 

180SpotType: 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]