tretool 0.2.1__py3-none-any.whl → 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tretool/smartCache.py ADDED
@@ -0,0 +1,569 @@
1
+ import time
2
+ from collections import OrderedDict, defaultdict
3
+ from typing import Any, Callable, Optional, Union, Dict, List, Tuple
4
+ from functools import wraps, update_wrapper
5
+ import pickle
6
+ import hashlib
7
+ import threading
8
+ import json
9
+ import inspect
10
+ import zlib
11
+ from datetime import timedelta
12
+ import logging
13
+
14
+ # 设置日志
15
+ logger = logging.getLogger(__name__)
16
+
17
+ class CachePolicy:
18
+ """缓存策略基类"""
19
+ def __init__(self, maxsize: int = 128):
20
+ self.maxsize = maxsize
21
+
22
+ def on_get(self, key: Any) -> None:
23
+ """当获取缓存项时调用"""
24
+ pass
25
+
26
+ def on_set(self, key: Any) -> None:
27
+ """当设置缓存项时调用"""
28
+ pass
29
+
30
+ def on_delete(self, key: Any) -> None:
31
+ """当删除缓存项时调用"""
32
+ pass
33
+
34
+ def on_miss(self, key: Any) -> None:
35
+ """当缓存未命中时调用"""
36
+ pass
37
+
38
+ def on_evict(self, key: Any) -> None:
39
+ """当淘汰缓存项时调用"""
40
+ pass
41
+
42
+ def evict(self) -> Any:
43
+ """根据策略淘汰一个缓存项,返回被淘汰的键"""
44
+ raise NotImplementedError
45
+
46
+ def clear(self) -> None:
47
+ """清除策略状态"""
48
+ pass
49
+
50
+ class LRUPolicy(CachePolicy):
51
+ """最近最少使用策略"""
52
+ def __init__(self, maxsize: int = 128):
53
+ super().__init__(maxsize)
54
+ self._order = OrderedDict()
55
+
56
+ def on_get(self, key: Any) -> None:
57
+ if key in self._order:
58
+ self._order.move_to_end(key)
59
+
60
+ def on_set(self, key: Any) -> None:
61
+ self._order[key] = True
62
+ self._order.move_to_end(key)
63
+ if len(self._order) > self.maxsize:
64
+ self.evict()
65
+
66
+ def on_delete(self, key: Any) -> None:
67
+ self._order.pop(key, None)
68
+
69
+ def on_evict(self, key: Any) -> None:
70
+ self._order.pop(key, None)
71
+
72
+ def evict(self) -> Any:
73
+ if not self._order:
74
+ raise ValueError("Cache is empty")
75
+ key, _ = self._order.popitem(last=False)
76
+ return key
77
+
78
+ def clear(self) -> None:
79
+ self._order.clear()
80
+
81
+ class LFUPolicy(CachePolicy):
82
+ """最不经常使用策略"""
83
+ def __init__(self, maxsize: int = 128):
84
+ super().__init__(maxsize)
85
+ self._counts = defaultdict(int)
86
+ self._min_heap = []
87
+
88
+ def on_get(self, key: Any) -> None:
89
+ self._counts[key] += 1
90
+
91
+ def on_set(self, key: Any) -> None:
92
+ self._counts[key] = 1
93
+ if len(self._counts) > self.maxsize:
94
+ self.evict()
95
+
96
+ def on_delete(self, key: Any) -> None:
97
+ self._counts.pop(key, None)
98
+
99
+ def on_evict(self, key: Any) -> None:
100
+ self._counts.pop(key, None)
101
+
102
+ def evict(self) -> Any:
103
+ if not self._counts:
104
+ raise ValueError("Cache is empty")
105
+
106
+ # 找到使用次数最少的键
107
+ min_key = min(self._counts.keys(), key=lambda k: self._counts[k])
108
+ del self._counts[min_key]
109
+ return min_key
110
+
111
+ def clear(self) -> None:
112
+ self._counts.clear()
113
+
114
+ class FIFOPolicy(CachePolicy):
115
+ """先进先出策略"""
116
+ def __init__(self, maxsize: int = 128):
117
+ super().__init__(maxsize)
118
+ self._queue = []
119
+
120
+ def on_set(self, key: Any) -> None:
121
+ self._queue.append(key)
122
+ if len(self._queue) > self.maxsize:
123
+ self.evict()
124
+
125
+ def on_delete(self, key: Any) -> None:
126
+ if key in self._queue:
127
+ self._queue.remove(key)
128
+
129
+ def on_evict(self, key: Any) -> None:
130
+ if key in self._queue:
131
+ self._queue.remove(key)
132
+
133
+ def evict(self) -> Any:
134
+ if not self._queue:
135
+ raise ValueError("Cache is empty")
136
+ return self._queue.pop(0)
137
+
138
+ def clear(self) -> None:
139
+ self._queue.clear()
140
+
141
+ class CacheItem:
142
+ """缓存项"""
143
+ __slots__ = ['value', 'expire_time', 'hits', 'last_accessed', 'size']
144
+
145
+ def __init__(self, value: Any, ttl: Optional[float] = None):
146
+ self.value = value
147
+ self.expire_time = time.time() + ttl if ttl else None
148
+ self.hits = 0
149
+ self.last_accessed = time.time()
150
+ self.size = self._calculate_size(value)
151
+
152
+ def is_expired(self) -> bool:
153
+ """检查是否已过期"""
154
+ return self.expire_time is not None and time.time() > self.expire_time
155
+
156
+ def hit(self) -> None:
157
+ """增加命中计数"""
158
+ self.hits += 1
159
+ self.last_accessed = time.time()
160
+
161
+ def _calculate_size(self, value: Any) -> int:
162
+ """估算缓存项大小"""
163
+ try:
164
+ return len(pickle.dumps(value))
165
+ except:
166
+ return 1 # 无法计算大小时返回1
167
+
168
+ class SmartCache:
169
+ """智能缓存"""
170
+ def __init__(
171
+ self,
172
+ policy: Union[str, CachePolicy] = 'lru',
173
+ maxsize: int = 128,
174
+ maxmem: Optional[int] = None,
175
+ ttl: Optional[float] = None,
176
+ serializer: Optional[Callable[[Any], bytes]] = None,
177
+ deserializer: Optional[Callable[[bytes], Any]] = None,
178
+ namespace: str = 'default',
179
+ compression: bool = False
180
+ ):
181
+ """
182
+ 初始化智能缓存
183
+
184
+ 参数:
185
+ policy: 缓存策略 ('lru', 'lfu', 'fifo' 或 CachePolicy 实例)
186
+ maxsize: 最大缓存项数
187
+ maxmem: 最大内存使用量(MB),None表示不限制
188
+ ttl: 默认生存时间(秒),None表示永不过期
189
+ serializer: 自定义序列化函数
190
+ deserializer: 自定义反序列化函数
191
+ namespace: 缓存命名空间
192
+ compression: 是否启用压缩
193
+ """
194
+ self._data: Dict[Any, CacheItem] = {}
195
+ self._lock = threading.RLock()
196
+ self.maxsize = maxsize
197
+ self.maxmem = maxmem * 1024 * 1024 if maxmem else None # 转换为字节
198
+ self.default_ttl = ttl
199
+ self.namespace = namespace
200
+ self.compression = compression
201
+ self.total_size = 0 # 当前总大小(字节)
202
+
203
+ self.stats = {
204
+ 'hits': 0,
205
+ 'misses': 0,
206
+ 'evictions': 0,
207
+ 'expired': 0,
208
+ 'size': 0,
209
+ 'compression_ratio': 0.0
210
+ }
211
+
212
+ # 设置序列化器
213
+ self.serializer = serializer or pickle.dumps
214
+ self.deserializer = deserializer or pickle.loads
215
+
216
+ # 设置缓存策略
217
+ if isinstance(policy, str):
218
+ policy = policy.lower()
219
+ if policy == 'lru':
220
+ self.policy = LRUPolicy(maxsize)
221
+ elif policy == 'lfu':
222
+ self.policy = LFUPolicy(maxsize)
223
+ elif policy == 'fifo':
224
+ self.policy = FIFOPolicy(maxsize)
225
+ else:
226
+ raise ValueError(f"Unknown cache policy: {policy}")
227
+ elif isinstance(policy, CachePolicy):
228
+ self.policy = policy
229
+ else:
230
+ raise TypeError("policy must be str or CachePolicy instance")
231
+
232
+ def __len__(self) -> int:
233
+ """当前缓存项数量"""
234
+ return len(self._data)
235
+
236
+ def __contains__(self, key: Any) -> bool:
237
+ """检查键是否在缓存中(未过期)"""
238
+ try:
239
+ self._get(key, update_stats=False)
240
+ return True
241
+ except KeyError:
242
+ return False
243
+
244
+ def clear(self) -> None:
245
+ """清空缓存"""
246
+ with self._lock:
247
+ self._data.clear()
248
+ self.policy.clear()
249
+ self.total_size = 0
250
+ self.stats = {
251
+ 'hits': 0,
252
+ 'misses': 0,
253
+ 'evictions': 0,
254
+ 'expired': 0,
255
+ 'size': 0,
256
+ 'compression_ratio': 0.0
257
+ }
258
+
259
+ def get(self, key: Any, default: Any = None) -> Any:
260
+ """
261
+ 获取缓存值
262
+
263
+ 参数:
264
+ key: 缓存键
265
+ default: 未找到时返回的默认值
266
+
267
+ 返回:
268
+ 缓存值或默认值
269
+ """
270
+ try:
271
+ return self._get(key)
272
+ except KeyError:
273
+ return default
274
+
275
+ def _get(self, key: Any, update_stats: bool = True) -> Any:
276
+ """内部获取方法,不处理默认值"""
277
+ with self._lock:
278
+ if key not in self._data:
279
+ if update_stats:
280
+ self.stats['misses'] += 1
281
+ self.policy.on_miss(key)
282
+ raise KeyError(key)
283
+
284
+ item = self._data[key]
285
+ if item.is_expired():
286
+ del self._data[key]
287
+ self.total_size -= item.size
288
+ if update_stats:
289
+ self.stats['expired'] += 1
290
+ self.policy.on_miss(key)
291
+ raise KeyError(f"Key {key} expired")
292
+
293
+ item.hit()
294
+ if update_stats:
295
+ self.stats['hits'] += 1
296
+ self.policy.on_get(key)
297
+ return item.value
298
+
299
+ def set(self, key: Any, value: Any, ttl: Optional[float] = None) -> None:
300
+ """
301
+ 设置缓存值
302
+
303
+ 参数:
304
+ key: 缓存键
305
+ value: 缓存值
306
+ ttl: 生存时间(秒),None表示使用默认ttl
307
+ """
308
+ with self._lock:
309
+ # 如果键已存在,先删除
310
+ if key in self._data:
311
+ self._delete(key, update_stats=False)
312
+
313
+ # 检查是否需要淘汰
314
+ while len(self._data) >= self.maxsize or (
315
+ self.maxmem is not None and self.total_size >= self.maxmem
316
+ ):
317
+ self._evict()
318
+
319
+ # 压缩数据
320
+ if self.compression:
321
+ try:
322
+ value = self._compress(value)
323
+ except Exception as e:
324
+ logger.warning(f"Compression failed: {str(e)}")
325
+
326
+ ttl = ttl if ttl is not None else self.default_ttl
327
+ item = CacheItem(value, ttl)
328
+ self._data[key] = item
329
+ self.total_size += item.size
330
+ self.policy.on_set(key)
331
+ self.stats['size'] = self.total_size
332
+
333
+ def _compress(self, data: Any) -> Any:
334
+ """压缩数据"""
335
+ serialized = self.serializer(data)
336
+ compressed = zlib.compress(serialized)
337
+ self.stats['compression_ratio'] = len(compressed) / len(serialized)
338
+ return compressed
339
+
340
+ def _decompress(self, data: Any) -> Any:
341
+ """解压数据"""
342
+ if not self.compression:
343
+ return data
344
+ return self.deserializer(zlib.decompress(data))
345
+
346
+ def delete(self, key: Any) -> bool:
347
+ """
348
+ 删除缓存项
349
+
350
+ 返回:
351
+ 是否成功删除
352
+ """
353
+ with self._lock:
354
+ return self._delete(key)
355
+
356
+ def _delete(self, key: Any, update_stats: bool = True) -> bool:
357
+ """内部删除方法"""
358
+ if key in self._data:
359
+ item = self._data[key]
360
+ del self._data[key]
361
+ self.total_size -= item.size
362
+ if update_stats:
363
+ self.policy.on_delete(key)
364
+ return True
365
+ return False
366
+
367
+ def _evict(self) -> None:
368
+ """根据策略淘汰一个缓存项"""
369
+ with self._lock:
370
+ try:
371
+ key = self.policy.evict()
372
+ if key in self._data:
373
+ item = self._data[key]
374
+ del self._data[key]
375
+ self.total_size -= item.size
376
+ self.stats['evictions'] += 1
377
+ self.policy.on_evict(key)
378
+ except ValueError:
379
+ pass # 缓存为空
380
+
381
+ def keys(self) -> List[Any]:
382
+ """获取所有未过期的键"""
383
+ with self._lock:
384
+ return [k for k in self._data if not self._data[k].is_expired()]
385
+
386
+ def values(self) -> List[Any]:
387
+ """获取所有未过期的值"""
388
+ with self._lock:
389
+ return [self._get(k) for k in self.keys()]
390
+
391
+ def items(self) -> List[Tuple[Any, Any]]:
392
+ """获取所有未过期的键值对"""
393
+ with self._lock:
394
+ return [(k, self._get(k)) for k in self.keys()]
395
+
396
+ def get_stats(self) -> Dict[str, Any]:
397
+ """获取缓存统计信息"""
398
+ with self._lock:
399
+ stats = self.stats.copy()
400
+ stats.update({
401
+ 'current_size': len(self._data),
402
+ 'max_size': self.maxsize,
403
+ 'memory_usage': f"{self.total_size / (1024 * 1024):.2f}MB",
404
+ 'max_memory': f"{self.maxmem / (1024 * 1024):.2f}MB" if self.maxmem else "unlimited",
405
+ 'namespace': self.namespace,
406
+ 'policy': self.policy.__class__.__name__,
407
+ 'compression': self.compression,
408
+ 'avg_hit_rate': stats['hits'] / max(1, stats['hits'] + stats['misses'])
409
+ })
410
+ return stats
411
+
412
+ def persist(self, filepath: str) -> None:
413
+ """持久化缓存到文件"""
414
+ with self._lock:
415
+ data = {
416
+ 'items': {k: (self.serializer(v.value), v.expire_time)
417
+ for k, v in self._data.items()
418
+ if not v.is_expired()},
419
+ 'stats': self.stats,
420
+ 'namespace': self.namespace,
421
+ 'compression': self.compression
422
+ }
423
+ with open(filepath, 'wb') as f:
424
+ pickle.dump(data, f)
425
+
426
+ def load(self, filepath: str) -> None:
427
+ """从文件加载缓存"""
428
+ with self._lock:
429
+ with open(filepath, 'rb') as f:
430
+ data = pickle.load(f)
431
+
432
+ if data.get('namespace') != self.namespace:
433
+ raise ValueError("Namespace mismatch")
434
+
435
+ self.clear()
436
+ self.compression = data.get('compression', False)
437
+
438
+ for k, v in data['items'].items():
439
+ value = self.deserializer(v[0])
440
+ if self.compression:
441
+ value = self._decompress(value)
442
+
443
+ ttl = (v[1] - time.time()) if v[1] is not None else None
444
+ self._data[k] = CacheItem(value, ttl)
445
+
446
+ self.stats = data['stats']
447
+
448
+ def memoize(
449
+ self,
450
+ ttl: Optional[float] = None,
451
+ key_func: Optional[Callable] = None,
452
+ unless: Optional[Callable] = None
453
+ ):
454
+ """
455
+ 装饰器,缓存函数结果
456
+
457
+ 参数:
458
+ ttl: 生存时间(秒)
459
+ key_func: 自定义缓存键生成函数
460
+ unless: 条件函数,返回True时不缓存
461
+ """
462
+ def decorator(func):
463
+ @wraps(func)
464
+ def wrapper(*args, **kwargs):
465
+ # 检查是否应该跳过缓存
466
+ if unless and unless(*args, **kwargs):
467
+ return func(*args, **kwargs)
468
+
469
+ cache_key = self._make_cache_key(func, args, kwargs, key_func)
470
+ try:
471
+ return self.get(cache_key)
472
+ except KeyError:
473
+ result = func(*args, **kwargs)
474
+ self.set(cache_key, result, ttl)
475
+ return result
476
+
477
+ def invalidate(*args, **kwargs):
478
+ """使缓存项失效"""
479
+ cache_key = self._make_cache_key(func, args, kwargs, key_func)
480
+ self.delete(cache_key)
481
+
482
+ wrapper.invalidate = invalidate
483
+ return wrapper
484
+ return decorator
485
+
486
+ def _make_cache_key(
487
+ self,
488
+ func: Callable,
489
+ args: tuple,
490
+ kwargs: dict,
491
+ key_func: Optional[Callable] = None
492
+ ) -> str:
493
+ """生成缓存键"""
494
+ if key_func:
495
+ return key_func(func, args, kwargs)
496
+
497
+ # 获取函数签名
498
+ sig = inspect.signature(func)
499
+ bound_args = sig.bind(*args, **kwargs)
500
+ bound_args.apply_defaults()
501
+
502
+ # 生成键
503
+ key_parts = [
504
+ func.__module__,
505
+ func.__qualname__,
506
+ str(bound_args.arguments)
507
+ ]
508
+ key = "|".join(key_parts)
509
+ return hashlib.sha256(key.encode('utf-8')).hexdigest()
510
+
511
+ def ttl(self, key: Any) -> Optional[float]:
512
+ """
513
+ 获取剩余生存时间(秒)
514
+
515
+ 返回:
516
+ 剩余时间(秒),None表示永不过期,-1表示键不存在或已过期
517
+ """
518
+ with self._lock:
519
+ if key not in self._data:
520
+ return -1
521
+
522
+ item = self._data[key]
523
+ if item.is_expired():
524
+ return -1
525
+
526
+ if item.expire_time is None:
527
+ return None
528
+
529
+ return max(0, item.expire_time - time.time())
530
+
531
+ def expire(self, key: Any, ttl: float) -> bool:
532
+ """
533
+ 设置键的生存时间
534
+
535
+ 参数:
536
+ key: 缓存键
537
+ ttl: 生存时间(秒)
538
+
539
+ 返回:
540
+ 是否设置成功
541
+ """
542
+ with self._lock:
543
+ if key not in self._data or self._data[key].is_expired():
544
+ return False
545
+
546
+ self._data[key].expire_time = time.time() + ttl
547
+ return True
548
+
549
+ def touch(self, key: Any) -> bool:
550
+ """
551
+ 更新键的最后访问时间
552
+
553
+ 返回:
554
+ 是否更新成功
555
+ """
556
+ with self._lock:
557
+ if key not in self._data or self._data[key].is_expired():
558
+ return False
559
+
560
+ self._data[key].last_accessed = time.time()
561
+ self.policy.on_get(key)
562
+ return True
563
+
564
+ def create_cache(namespace: str = 'default', **kwargs) -> SmartCache:
565
+ """创建缓存实例的工厂函数"""
566
+ return SmartCache(namespace=namespace, **kwargs)
567
+
568
+ # 全局默认缓存
569
+ default_cache = SmartCache()