nb-cache 0.2__tar.gz → 0.4__tar.gz

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.
Files changed (38) hide show
  1. {nb_cache-0.2 → nb_cache-0.4}/PKG-INFO +207 -12
  2. {nb_cache-0.2 → nb_cache-0.4}/README.md +203 -3
  3. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/decorators/cache.py +19 -4
  4. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/key.py +27 -12
  5. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/wrapper.py +10 -3
  6. {nb_cache-0.2 → nb_cache-0.4}/nb_cache.egg-info/PKG-INFO +208 -13
  7. {nb_cache-0.2 → nb_cache-0.4}/pyproject.toml +3 -2
  8. {nb_cache-0.2 → nb_cache-0.4}/LICENSE +0 -0
  9. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/__init__.py +0 -0
  10. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/_compat.py +0 -0
  11. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/backends/__init__.py +0 -0
  12. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/backends/base.py +0 -0
  13. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/backends/dual.py +0 -0
  14. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/backends/memory.py +0 -0
  15. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/backends/redis.py +0 -0
  16. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/condition.py +0 -0
  17. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/decorators/__init__.py +0 -0
  18. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/decorators/bloom.py +0 -0
  19. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/decorators/circuit_breaker.py +0 -0
  20. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/decorators/early.py +0 -0
  21. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/decorators/failover.py +0 -0
  22. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/decorators/hit.py +0 -0
  23. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/decorators/iterator.py +0 -0
  24. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/decorators/locked.py +0 -0
  25. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/decorators/rate_limit.py +0 -0
  26. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/decorators/soft.py +0 -0
  27. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/exceptions.py +0 -0
  28. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/helpers.py +0 -0
  29. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/middleware.py +0 -0
  30. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/serialize.py +0 -0
  31. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/tags.py +0 -0
  32. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/transaction.py +0 -0
  33. {nb_cache-0.2 → nb_cache-0.4}/nb_cache/ttl.py +0 -0
  34. {nb_cache-0.2 → nb_cache-0.4}/nb_cache.egg-info/SOURCES.txt +0 -0
  35. {nb_cache-0.2 → nb_cache-0.4}/nb_cache.egg-info/dependency_links.txt +0 -0
  36. {nb_cache-0.2 → nb_cache-0.4}/nb_cache.egg-info/requires.txt +0 -0
  37. {nb_cache-0.2 → nb_cache-0.4}/nb_cache.egg-info/top_level.txt +0 -0
  38. {nb_cache-0.2 → nb_cache-0.4}/setup.cfg +0 -0
@@ -1,7 +1,7 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: nb_cache
3
- Version: 0.2
4
- Summary: 更强的缓存装饰器,支持同步和异步函数,支持加锁防止缓存击穿,支持内存和Redis作为缓存器,支持Redis+内存双缓存提高性能
3
+ Version: 0.4
4
+ Summary: `nb_cache` 不仅是一个基础的缓存装饰器,它在彻底抹平 Python 同步与异步代码差异的同时,开箱即用地提供了内存/Redis双层缓存、防击穿、防雪崩、限流与熔断等企业级高可用特性。
5
5
  Author: ydf0509
6
6
  Project-URL: Homepage, https://github.com/ydf0509/nb_cache
7
7
  Project-URL: Repository, https://github.com/ydf0509/nb_cache
@@ -18,22 +18,56 @@ Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
21
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
23
  Requires-Python: >=3.6
23
24
  Description-Content-Type: text/markdown
24
25
  Provides-Extra: redis
25
- Requires-Dist: redis>=3.0; extra == "redis"
26
26
  Provides-Extra: speedup
27
- Requires-Dist: xxhash; extra == "speedup"
28
- Requires-Dist: hiredis; extra == "speedup"
29
27
  Provides-Extra: all
30
- Requires-Dist: redis>=3.0; extra == "all"
31
- Requires-Dist: xxhash; extra == "all"
32
- Requires-Dist: hiredis; extra == "all"
33
28
 
34
- # nb_cache
35
29
 
36
- 更强的缓存装饰器,支持同步和异步函数,支持加锁防止缓存击穿,支持内存和Redis作为缓存器,支持Redis+内存双缓存提高性能。
30
+ # 🚀 nb_cache: Python 缓存界的“瑞士军刀”
31
+
32
+ [![Python Versions](https://img.shields.io/badge/python-3.6+-blue.svg)](https://pypi.org/project/nb-cache/)
33
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
34
+
35
+ > **`nb_cache` 不仅是一个基础的缓存装饰器,它在彻底抹平 Python 同步与异步代码差异的同时,开箱即用地提供了内存/Redis双层缓存、防击穿、防雪崩、限流与熔断等企业级高可用特性。**
36
+
37
+ 在如今混杂着 Sync 和 Async 的 Python 项目中,开发者往往需要为同步代码和异步代码寻找不同的缓存解决方案。`nb_cache` 彻底抹平了这一差异——**只需同一个装饰器,即可完美兼容同步与异步函数**。
38
+
39
+ 如果你曾经使用过 `cashews`,你会对 `nb_cache` 感到非常亲切。`nb_cache` 吸收了其优秀的特性,并弥补了其最大的短板:**全面支持同/异步场景,且所有底层操作兼具 Sync 和 Async 两种 API**。
40
+
41
+ ## ✨ 核心特性
42
+
43
+ - ☯️ **同/异步无缝统一**:无需区分 `@cache` 或 `@acache`,同一个装饰器自动识别普通函数与协程,内部自动路由。同一套上下文管理器同时支持 `with` 和 `async with`。
44
+ - 🚀 **丰富的高级后端**:
45
+ - **Memory (`mem://`)**: 极速的本地 LRU 内存缓存。
46
+ - **Redis (`redis://`)**: 分布式 Redis 缓存,支持连接池。
47
+ - **双层缓存 (`dual://`)**: **(杀手级特性)** 本地内存(L1) + Redis(L2) 的透明双重缓存。读取时优先击中内存,写入时双写,彻底释放 Redis 压力。
48
+ - 🛡️ **企业级高可用防护**:
49
+ - **防缓存击穿 (Stampede)**:只需一个参数 `lock=True`,即可在并发未命中时让多余请求等待,只放行一个请求去查库。
50
+ - **防缓存雪崩 (Avalanche)**:提供 `@cache.early` (提前后台刷新) 和 `@cache.soft` (软过期,返回旧值并异步刷新) 完美解决雪崩。
51
+ - **服务降级与失败回退**:提供 `@cache.failover`,当数据库或下游接口挂掉时,自动返回缓存的旧值兜底。
52
+ - 🔧 **极其丰富的“微服务”级装饰器**:除了缓存,还内置了**限流** (`rate_limit`, `slice_rate_limit`)、**熔断** (`circuit_breaker`)、**并发防抖** (`thunder_protection`)、**布隆过滤器** (`bloom`, `dual_bloom`)。
53
+ - 🔑 **智能 Key 路由与模板**:告别繁琐的 key 拼接。支持 `{user_id}`、`{user.name}` (直接读取对象属性)、`{data:hash}` (自动对大字典算md5) 等高级格式化模板。
54
+ - 🔒 **数据安全与压缩**:自带序列化流水线。一行配置即可开启 JSON/Pickle 序列化、Gzip/Zlib 压缩,以及 HMAC 签名(防止缓存数据被恶意篡改)。
55
+ - 🏷️ **标签系统与事务**:支持给缓存打标签 (Tags) 实现按业务模块批量失效,支持类似数据库的缓存事务 (Transaction) 自动回滚。
56
+
57
+ ## 💡 为什么选择 nb_cache?
58
+
59
+ | 特性 | 传统自带 `lru_cache` | `redis-py` 原生 | `cashews` | 🏆 `nb_cache` |
60
+ | :--- | :---: | :---: | :---: | :---: |
61
+ | **同步函数支持** | ✅ | ✅ | ❌ | **✅ 完美支持** |
62
+ | **异步函数 (Asyncio) 支持** | ❌ | ✅ | ✅ | **✅ 完美支持** |
63
+ | **内存/Redis 双重缓存** | ❌ | ❌ | ✅ | **✅ 开箱即用** |
64
+ | **防击穿 (分布式锁合并请求)** | ❌ | 需手写代码 | ✅ | **✅ `lock=True`** |
65
+ | **防雪崩/后台自动刷新** | ❌ | 需手写代码 | ✅ | **✅ `@cache.early`** |
66
+ | **限流与熔断** | ❌ | 需手写代码 | ✅ | **✅ 内置支持** |
67
+
68
+ ---
69
+
70
+ ### 👉 接下来,请看下方的【安装】与【快速开始】,体验一行代码带来的架构升级:
37
71
 
38
72
  ## 安装
39
73
 
@@ -66,12 +100,20 @@ def get_user(user_id):
66
100
  async def get_user_async(user_id):
67
101
  return await db.query_async(user_id)
68
102
 
69
- # 加锁防止缓存击穿
103
+ # 加锁防止缓存击穿,如果123这个入参没有缓存,但是同一秒请求123这个入参1万次,
104
+ # 加上lock=True后,只有第一次请求会真正执行函数,其余请求等待并复用第一次请求的结果,避免"击穿"。
70
105
  @cache.cache(ttl=60, lock=True)
71
106
  def get_hot_data(key):
107
+ time.sleep(20)
72
108
  return expensive_query(key)
73
109
  ```
74
110
 
111
+ ## 不想吃苦,如何使用ai掌握nb_cache?
112
+
113
+ `nb_cache_all_docs_and_codes.md` 这个文件包含了nb_cache 的教程和全部源码。
114
+ 你把这个文件发送给deepseek ai [https://chat.deepseek.com/](https://chat.deepseek.com/) ,ai就能自动帮你掌握 `nb_cache` 的用法。
115
+
116
+
75
117
  ## 对比 cashews
76
118
 
77
119
  如果你不懂 `nb_cache` 用法,可以参考 `cashews` 的用法。ai很熟练 `cashews`的用法。
@@ -665,6 +707,94 @@ def check_permission(user, action):
665
707
  return db.query_permission(user['id'], action)
666
708
  ```
667
709
 
710
+ ### key_include_func 参数说明
711
+
712
+ 默认情况下,`nb_cache` 会把 **模块路径 + 函数名** 自动拼入 cache key,以确保不同模块的同名函数不会冲突:
713
+
714
+ ```
715
+ # 默认生成的 key(含函数信息)
716
+ testp2:myapp.services:get_user:user_id:42
717
+ ```
718
+
719
+ 如果你已经通过 `key=` 参数自己指定了业务 key 模板,这段模块+函数前缀往往是多余的噪音。
720
+ 设置 `key_include_func=False` 后,key 只保留业务部分:
721
+
722
+ ```
723
+ # key_include_func=False 后生成的 key
724
+ testp2:user:42
725
+ ```
726
+
727
+ #### 设置级别
728
+
729
+ `key_include_func` 支持两个层级,**装饰器上的值优先于 `setup()` 的默认值**。
730
+
731
+ **1. `setup()` 级别 —— 影响该实例下所有装饰器**
732
+
733
+ ```python
734
+ from nb_cache import Cache
735
+
736
+ cache = Cache().setup("redis://localhost:6379/0", prefix="myapp", key_include_func=False)
737
+
738
+ @cache.cache(ttl=300, key="user:{user_id}")
739
+ def get_user(user_id):
740
+ return db.query(user_id)
741
+ # final_key → myapp:user:42
742
+
743
+ @cache.cache(ttl=60, key="order:{order_id}")
744
+ def get_order(order_id):
745
+ return db.query_order(order_id)
746
+ # final_key → myapp:order:100
747
+ ```
748
+
749
+ **2. 装饰器级别 —— 覆盖 `setup()` 的默认值,只影响当前函数**
750
+
751
+ ```python
752
+ cache = Cache().setup("redis://localhost:6379/0", prefix="myapp")
753
+ # 默认 key_include_func=True
754
+
755
+ @cache.cache(ttl=300, key="user:{user_id}")
756
+ def get_user(user_id):
757
+ ...
758
+ # final_key → myapp:mymodule:get_user:user:{user_id} → myapp:mymodule:get_user:user:42
759
+
760
+ @cache.cache(ttl=60, key="order:{order_id}", key_include_func=False)
761
+ def get_order(order_id):
762
+ ...
763
+ # final_key → myapp:order:100 (单独关闭,不含函数名)
764
+ ```
765
+
766
+ #### 不指定 key= 时的行为
767
+
768
+ 当不传 `key=` 参数,`nb_cache` 会根据函数签名自动生成 key。
769
+ 此时 `key_include_func=False` 意味着 key 只由参数值组成,**极易碰撞**,不推荐在此场景下使用:
770
+
771
+ ```python
772
+ # 不推荐:不指定 key= 且 key_include_func=False
773
+ @cache.cache(ttl=60, key_include_func=False)
774
+ def get_user(user_id):
775
+ ...
776
+ # final_key → myapp:user_id=42 ← 与其他相同签名函数会冲突
777
+ ```
778
+
779
+ #### 如何预览生成的 key(不调用函数)
780
+
781
+ ```python
782
+ from nb_cache.key import get_cache_key, get_cache_key_template
783
+
784
+ # 方式1:从已装饰函数取模板(推荐)
785
+ template = get_user._cache_key_template
786
+ logic_key = get_cache_key(get_user, template, (42,), {})
787
+ final_key = cache._backend._make_key(logic_key)
788
+ print(final_key) # myapp:user:42
789
+
790
+ # 方式2:直接用工具函数生成(不需要先装饰)
791
+ tpl = get_cache_key_template(get_user, key="user:{user_id}", key_include_func=False)
792
+ key = get_cache_key(get_user, tpl, (42,), {})
793
+ print(key) # user:42
794
+ ```
795
+
796
+ ---
797
+
668
798
  ## 锁(上下文管理器)
669
799
 
670
800
  ```python
@@ -775,6 +905,71 @@ def get_data():
775
905
  return {"name": "test"}
776
906
  ```
777
907
 
908
+ ## 常见问题解答
909
+
910
+ ### 问题1:如何查看缓存最终生成的key是什么?
911
+
912
+
913
+ #### 方式一:通过日志查看
914
+ ```
915
+ 因为nb_cache 已经在 nb_cache.cache 日志命名空间,用debug 日志级别打印了最终生成的key。
916
+
917
+ 所以你可以通过nb_log来查看:
918
+ nb_log.get_logger('nb_cache.cache')
919
+
920
+ 也可以通过 原生logging 来查看:
921
+ logger = logging.getLogger("nb_cache.cache")
922
+ logger.setLevel(logging.DEBUG)
923
+ logger.addHandler(logging.StreamHandler())
924
+ ```
925
+
926
+ 日志例子:
927
+ ```
928
+ 2026-02-28 18:46:01 - nb_cache.cache - "D:\codes\nb_cache\nb_cache\decorators\cache.py:61" - async_wrapper - DEBUG - [nb_cache] func=__main__:aio_fun final_key=testp2:__main__:aio_fun:aiof:3_4 ttl=700.0
929
+ ```
930
+
931
+ #### 方式二:不调用函数,直接预览 cache key
932
+
933
+ ```python
934
+ # -*- coding: utf-8 -*-
935
+ """nb_cache key 生成 Demo"""
936
+ import sys
937
+ import asyncio
938
+ from nb_cache import Cache
939
+ from nb_cache.key import get_cache_key, get_cache_key_template
940
+ import nb_log
941
+
942
+ nb_log.get_logger("nb_cache.cache")
943
+
944
+ if sys.platform == "win32":
945
+ asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
946
+
947
+ cache = Cache()
948
+ cache.setup("redis://", prefix="testp2")
949
+
950
+
951
+ @cache.cache(ttl=700,key='sf:{a}_{b}')
952
+ def simple_func(a, b):
953
+ return a + b
954
+
955
+ print(simple_func(1, 2))
956
+
957
+ # --- 不调用函数,直接预览 cache key ---
958
+ # 方式1:从装饰后的函数取模板属性(推荐)
959
+ template = simple_func._cache_key_template
960
+ logic_key = get_cache_key(simple_func, template, (1, 2), {})
961
+ final_key = cache._backend._make_key(logic_key)
962
+ print("logic_key:", logic_key) # 不含 prefix
963
+ print("final_key:", final_key) # 含 prefix,与 Redis 中一致
964
+
965
+ # 方式2:用工具函数直接生成,不需要先装饰
966
+ def raw_func(a, b):
967
+ return a + b
968
+ tpl = get_cache_key_template(raw_func, key='sf:{a}_{b}')
969
+ key2 = get_cache_key(raw_func, tpl, (1, 2), {})
970
+ print("key2 (无prefix):", key2)
971
+ ```
972
+
778
973
  ## 许可证
779
974
 
780
975
  MIT License
@@ -1,6 +1,45 @@
1
- # nb_cache
2
1
 
3
- 更强的缓存装饰器,支持同步和异步函数,支持加锁防止缓存击穿,支持内存和Redis作为缓存器,支持Redis+内存双缓存提高性能。
2
+ # 🚀 nb_cache: Python 缓存界的“瑞士军刀”
3
+
4
+ [![Python Versions](https://img.shields.io/badge/python-3.6+-blue.svg)](https://pypi.org/project/nb-cache/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ > **`nb_cache` 不仅是一个基础的缓存装饰器,它在彻底抹平 Python 同步与异步代码差异的同时,开箱即用地提供了内存/Redis双层缓存、防击穿、防雪崩、限流与熔断等企业级高可用特性。**
8
+
9
+ 在如今混杂着 Sync 和 Async 的 Python 项目中,开发者往往需要为同步代码和异步代码寻找不同的缓存解决方案。`nb_cache` 彻底抹平了这一差异——**只需同一个装饰器,即可完美兼容同步与异步函数**。
10
+
11
+ 如果你曾经使用过 `cashews`,你会对 `nb_cache` 感到非常亲切。`nb_cache` 吸收了其优秀的特性,并弥补了其最大的短板:**全面支持同/异步场景,且所有底层操作兼具 Sync 和 Async 两种 API**。
12
+
13
+ ## ✨ 核心特性
14
+
15
+ - ☯️ **同/异步无缝统一**:无需区分 `@cache` 或 `@acache`,同一个装饰器自动识别普通函数与协程,内部自动路由。同一套上下文管理器同时支持 `with` 和 `async with`。
16
+ - 🚀 **丰富的高级后端**:
17
+ - **Memory (`mem://`)**: 极速的本地 LRU 内存缓存。
18
+ - **Redis (`redis://`)**: 分布式 Redis 缓存,支持连接池。
19
+ - **双层缓存 (`dual://`)**: **(杀手级特性)** 本地内存(L1) + Redis(L2) 的透明双重缓存。读取时优先击中内存,写入时双写,彻底释放 Redis 压力。
20
+ - 🛡️ **企业级高可用防护**:
21
+ - **防缓存击穿 (Stampede)**:只需一个参数 `lock=True`,即可在并发未命中时让多余请求等待,只放行一个请求去查库。
22
+ - **防缓存雪崩 (Avalanche)**:提供 `@cache.early` (提前后台刷新) 和 `@cache.soft` (软过期,返回旧值并异步刷新) 完美解决雪崩。
23
+ - **服务降级与失败回退**:提供 `@cache.failover`,当数据库或下游接口挂掉时,自动返回缓存的旧值兜底。
24
+ - 🔧 **极其丰富的“微服务”级装饰器**:除了缓存,还内置了**限流** (`rate_limit`, `slice_rate_limit`)、**熔断** (`circuit_breaker`)、**并发防抖** (`thunder_protection`)、**布隆过滤器** (`bloom`, `dual_bloom`)。
25
+ - 🔑 **智能 Key 路由与模板**:告别繁琐的 key 拼接。支持 `{user_id}`、`{user.name}` (直接读取对象属性)、`{data:hash}` (自动对大字典算md5) 等高级格式化模板。
26
+ - 🔒 **数据安全与压缩**:自带序列化流水线。一行配置即可开启 JSON/Pickle 序列化、Gzip/Zlib 压缩,以及 HMAC 签名(防止缓存数据被恶意篡改)。
27
+ - 🏷️ **标签系统与事务**:支持给缓存打标签 (Tags) 实现按业务模块批量失效,支持类似数据库的缓存事务 (Transaction) 自动回滚。
28
+
29
+ ## 💡 为什么选择 nb_cache?
30
+
31
+ | 特性 | 传统自带 `lru_cache` | `redis-py` 原生 | `cashews` | 🏆 `nb_cache` |
32
+ | :--- | :---: | :---: | :---: | :---: |
33
+ | **同步函数支持** | ✅ | ✅ | ❌ | **✅ 完美支持** |
34
+ | **异步函数 (Asyncio) 支持** | ❌ | ✅ | ✅ | **✅ 完美支持** |
35
+ | **内存/Redis 双重缓存** | ❌ | ❌ | ✅ | **✅ 开箱即用** |
36
+ | **防击穿 (分布式锁合并请求)** | ❌ | 需手写代码 | ✅ | **✅ `lock=True`** |
37
+ | **防雪崩/后台自动刷新** | ❌ | 需手写代码 | ✅ | **✅ `@cache.early`** |
38
+ | **限流与熔断** | ❌ | 需手写代码 | ✅ | **✅ 内置支持** |
39
+
40
+ ---
41
+
42
+ ### 👉 接下来,请看下方的【安装】与【快速开始】,体验一行代码带来的架构升级:
4
43
 
5
44
  ## 安装
6
45
 
@@ -33,12 +72,20 @@ def get_user(user_id):
33
72
  async def get_user_async(user_id):
34
73
  return await db.query_async(user_id)
35
74
 
36
- # 加锁防止缓存击穿
75
+ # 加锁防止缓存击穿,如果123这个入参没有缓存,但是同一秒请求123这个入参1万次,
76
+ # 加上lock=True后,只有第一次请求会真正执行函数,其余请求等待并复用第一次请求的结果,避免"击穿"。
37
77
  @cache.cache(ttl=60, lock=True)
38
78
  def get_hot_data(key):
79
+ time.sleep(20)
39
80
  return expensive_query(key)
40
81
  ```
41
82
 
83
+ ## 不想吃苦,如何使用ai掌握nb_cache?
84
+
85
+ `nb_cache_all_docs_and_codes.md` 这个文件包含了nb_cache 的教程和全部源码。
86
+ 你把这个文件发送给deepseek ai [https://chat.deepseek.com/](https://chat.deepseek.com/) ,ai就能自动帮你掌握 `nb_cache` 的用法。
87
+
88
+
42
89
  ## 对比 cashews
43
90
 
44
91
  如果你不懂 `nb_cache` 用法,可以参考 `cashews` 的用法。ai很熟练 `cashews`的用法。
@@ -632,6 +679,94 @@ def check_permission(user, action):
632
679
  return db.query_permission(user['id'], action)
633
680
  ```
634
681
 
682
+ ### key_include_func 参数说明
683
+
684
+ 默认情况下,`nb_cache` 会把 **模块路径 + 函数名** 自动拼入 cache key,以确保不同模块的同名函数不会冲突:
685
+
686
+ ```
687
+ # 默认生成的 key(含函数信息)
688
+ testp2:myapp.services:get_user:user_id:42
689
+ ```
690
+
691
+ 如果你已经通过 `key=` 参数自己指定了业务 key 模板,这段模块+函数前缀往往是多余的噪音。
692
+ 设置 `key_include_func=False` 后,key 只保留业务部分:
693
+
694
+ ```
695
+ # key_include_func=False 后生成的 key
696
+ testp2:user:42
697
+ ```
698
+
699
+ #### 设置级别
700
+
701
+ `key_include_func` 支持两个层级,**装饰器上的值优先于 `setup()` 的默认值**。
702
+
703
+ **1. `setup()` 级别 —— 影响该实例下所有装饰器**
704
+
705
+ ```python
706
+ from nb_cache import Cache
707
+
708
+ cache = Cache().setup("redis://localhost:6379/0", prefix="myapp", key_include_func=False)
709
+
710
+ @cache.cache(ttl=300, key="user:{user_id}")
711
+ def get_user(user_id):
712
+ return db.query(user_id)
713
+ # final_key → myapp:user:42
714
+
715
+ @cache.cache(ttl=60, key="order:{order_id}")
716
+ def get_order(order_id):
717
+ return db.query_order(order_id)
718
+ # final_key → myapp:order:100
719
+ ```
720
+
721
+ **2. 装饰器级别 —— 覆盖 `setup()` 的默认值,只影响当前函数**
722
+
723
+ ```python
724
+ cache = Cache().setup("redis://localhost:6379/0", prefix="myapp")
725
+ # 默认 key_include_func=True
726
+
727
+ @cache.cache(ttl=300, key="user:{user_id}")
728
+ def get_user(user_id):
729
+ ...
730
+ # final_key → myapp:mymodule:get_user:user:{user_id} → myapp:mymodule:get_user:user:42
731
+
732
+ @cache.cache(ttl=60, key="order:{order_id}", key_include_func=False)
733
+ def get_order(order_id):
734
+ ...
735
+ # final_key → myapp:order:100 (单独关闭,不含函数名)
736
+ ```
737
+
738
+ #### 不指定 key= 时的行为
739
+
740
+ 当不传 `key=` 参数,`nb_cache` 会根据函数签名自动生成 key。
741
+ 此时 `key_include_func=False` 意味着 key 只由参数值组成,**极易碰撞**,不推荐在此场景下使用:
742
+
743
+ ```python
744
+ # 不推荐:不指定 key= 且 key_include_func=False
745
+ @cache.cache(ttl=60, key_include_func=False)
746
+ def get_user(user_id):
747
+ ...
748
+ # final_key → myapp:user_id=42 ← 与其他相同签名函数会冲突
749
+ ```
750
+
751
+ #### 如何预览生成的 key(不调用函数)
752
+
753
+ ```python
754
+ from nb_cache.key import get_cache_key, get_cache_key_template
755
+
756
+ # 方式1:从已装饰函数取模板(推荐)
757
+ template = get_user._cache_key_template
758
+ logic_key = get_cache_key(get_user, template, (42,), {})
759
+ final_key = cache._backend._make_key(logic_key)
760
+ print(final_key) # myapp:user:42
761
+
762
+ # 方式2:直接用工具函数生成(不需要先装饰)
763
+ tpl = get_cache_key_template(get_user, key="user:{user_id}", key_include_func=False)
764
+ key = get_cache_key(get_user, tpl, (42,), {})
765
+ print(key) # user:42
766
+ ```
767
+
768
+ ---
769
+
635
770
  ## 锁(上下文管理器)
636
771
 
637
772
  ```python
@@ -742,6 +877,71 @@ def get_data():
742
877
  return {"name": "test"}
743
878
  ```
744
879
 
880
+ ## 常见问题解答
881
+
882
+ ### 问题1:如何查看缓存最终生成的key是什么?
883
+
884
+
885
+ #### 方式一:通过日志查看
886
+ ```
887
+ 因为nb_cache 已经在 nb_cache.cache 日志命名空间,用debug 日志级别打印了最终生成的key。
888
+
889
+ 所以你可以通过nb_log来查看:
890
+ nb_log.get_logger('nb_cache.cache')
891
+
892
+ 也可以通过 原生logging 来查看:
893
+ logger = logging.getLogger("nb_cache.cache")
894
+ logger.setLevel(logging.DEBUG)
895
+ logger.addHandler(logging.StreamHandler())
896
+ ```
897
+
898
+ 日志例子:
899
+ ```
900
+ 2026-02-28 18:46:01 - nb_cache.cache - "D:\codes\nb_cache\nb_cache\decorators\cache.py:61" - async_wrapper - DEBUG - [nb_cache] func=__main__:aio_fun final_key=testp2:__main__:aio_fun:aiof:3_4 ttl=700.0
901
+ ```
902
+
903
+ #### 方式二:不调用函数,直接预览 cache key
904
+
905
+ ```python
906
+ # -*- coding: utf-8 -*-
907
+ """nb_cache key 生成 Demo"""
908
+ import sys
909
+ import asyncio
910
+ from nb_cache import Cache
911
+ from nb_cache.key import get_cache_key, get_cache_key_template
912
+ import nb_log
913
+
914
+ nb_log.get_logger("nb_cache.cache")
915
+
916
+ if sys.platform == "win32":
917
+ asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
918
+
919
+ cache = Cache()
920
+ cache.setup("redis://", prefix="testp2")
921
+
922
+
923
+ @cache.cache(ttl=700,key='sf:{a}_{b}')
924
+ def simple_func(a, b):
925
+ return a + b
926
+
927
+ print(simple_func(1, 2))
928
+
929
+ # --- 不调用函数,直接预览 cache key ---
930
+ # 方式1:从装饰后的函数取模板属性(推荐)
931
+ template = simple_func._cache_key_template
932
+ logic_key = get_cache_key(simple_func, template, (1, 2), {})
933
+ final_key = cache._backend._make_key(logic_key)
934
+ print("logic_key:", logic_key) # 不含 prefix
935
+ print("final_key:", final_key) # 含 prefix,与 Redis 中一致
936
+
937
+ # 方式2:用工具函数直接生成,不需要先装饰
938
+ def raw_func(a, b):
939
+ return a + b
940
+ tpl = get_cache_key_template(raw_func, key='sf:{a}_{b}')
941
+ key2 = get_cache_key(raw_func, tpl, (1, 2), {})
942
+ print("key2 (无prefix):", key2)
943
+ ```
944
+
745
945
  ## 许可证
746
946
 
747
947
  MIT License
@@ -2,18 +2,27 @@
2
2
  """Basic cache decorator with sync/async support and optional locking."""
3
3
  import asyncio
4
4
  import functools
5
- import time
5
+ import logging
6
6
 
7
7
  from nb_cache._compat import is_coroutine_function
8
8
  from nb_cache.condition import get_cache_condition
9
- from nb_cache.key import get_cache_key, get_cache_key_template
9
+ from nb_cache.key import get_cache_key, get_cache_key_template, get_func_name
10
10
  from nb_cache.serialize import default_serializer, _SENTINEL
11
11
  from nb_cache.ttl import ttl_to_seconds
12
12
 
13
+ logger = logging.getLogger("nb_cache.cache")
14
+
15
+
16
+ def _final_key(be, cache_key):
17
+ """通过 backend 的 _make_key 方法获取最终写入存储的完整 key。"""
18
+ if hasattr(be, '_make_key'):
19
+ return be._make_key(cache_key)
20
+ return cache_key
21
+
13
22
 
14
23
  def cache(ttl, key=None, condition=None, prefix="", lock=False,
15
24
  lock_ttl=None, tags=(), backend=None, serializer=None,
16
- tag_registry=None):
25
+ tag_registry=None, key_include_func=True):
17
26
  """Basic cache decorator.
18
27
 
19
28
  Supports both sync and async functions transparently.
@@ -29,6 +38,8 @@ def cache(ttl, key=None, condition=None, prefix="", lock=False,
29
38
  backend: Cache backend instance. If None, uses the global default.
30
39
  serializer: Serializer instance. If None, uses default.
31
40
  tag_registry: TagRegistry instance for tag-based invalidation.
41
+ key_include_func: If False, module path and function name are excluded
42
+ from the generated key. Default True.
32
43
  """
33
44
  _condition = get_cache_condition(condition)
34
45
  _serializer = serializer or default_serializer
@@ -36,7 +47,7 @@ def cache(ttl, key=None, condition=None, prefix="", lock=False,
36
47
  _lock_ttl = ttl_to_seconds(lock_ttl) if lock_ttl else _ttl_seconds
37
48
 
38
49
  def decorator(func):
39
- _key_template = get_cache_key_template(func, key, prefix)
50
+ _key_template = get_cache_key_template(func, key, prefix, key_include_func=key_include_func)
40
51
  _backend_ref = [backend]
41
52
  _registry = tag_registry
42
53
 
@@ -49,6 +60,8 @@ def cache(ttl, key=None, condition=None, prefix="", lock=False,
49
60
  async def async_wrapper(*args, **kwargs):
50
61
  be = _get_backend()
51
62
  cache_key = get_cache_key(func, _key_template, args, kwargs)
63
+ logger.debug("[nb_cache] func=%s final_key=%s ttl=%s",
64
+ get_func_name(func), _final_key(be, cache_key), _ttl_seconds)
52
65
 
53
66
  raw = await be.get(cache_key)
54
67
  if raw is not None:
@@ -91,6 +104,8 @@ def cache(ttl, key=None, condition=None, prefix="", lock=False,
91
104
  def sync_wrapper(*args, **kwargs):
92
105
  be = _get_backend()
93
106
  cache_key = get_cache_key(func, _key_template, args, kwargs)
107
+ logger.debug("[nb_cache] func=%s final_key=%s ttl=%s",
108
+ get_func_name(func), _final_key(be, cache_key), _ttl_seconds)
94
109
 
95
110
  raw = be.get_sync(cache_key)
96
111
  if raw is not None:
@@ -2,14 +2,11 @@
2
2
  """Cache key generation and template utilities."""
3
3
  import hashlib
4
4
  import inspect
5
- import logging
6
5
  import re
7
6
 
8
7
  # Matches {param}, {param:fmt}, {param.attr}, {param.attr:fmt}
9
8
  _TEMPLATE_PARAM_RE = re.compile(r'\{([\w.]+)(?::(\w+))?\}')
10
9
 
11
- logger = logging.getLogger("nb_cache.key")
12
-
13
10
 
14
11
  def get_func_name(func):
15
12
  """Get a stable, qualified name for a function."""
@@ -35,24 +32,37 @@ def get_cache_key(func, key_template, args, kwargs):
35
32
  else:
36
33
  key = _render_template(func, key_template, args, kwargs)
37
34
 
38
- logger.debug("[nb_cache] func=%s key=%s", get_func_name(func), key)
39
35
  return key
40
36
 
41
37
 
42
- def get_cache_key_template(func, key=None, prefix=""):
38
+ def get_cache_key_template(func, key=None, prefix="", key_include_func=True):
43
39
  """Build a key template (string or callable) for a function.
44
40
 
45
41
  When key is a callable, it is stored as-is and will be called at cache time.
46
- When key is a string template, it is prefixed with func_name.
42
+ When key is a string template, it is prefixed with func_name (if include_func_name=True).
47
43
  When key is None, auto-generate a template from the function signature.
44
+
45
+ Args:
46
+ func: The decorated function.
47
+ key: Key template string or callable. None means auto-generate.
48
+ prefix: Key prefix string (from decorator or setup).
49
+ key_include_func: If False, the module path and function name are NOT
50
+ included in the generated key. Useful when you want a short,
51
+ purely business-logic key (e.g. ``aiof:3_4`` instead of
52
+ ``__main__:aio_fun:aiof:3_4``).
48
53
  """
49
54
  func_name = get_func_name(func)
50
55
  if key is not None:
51
56
  if callable(key):
52
57
  return key
53
- if prefix:
54
- return "{}:{}:{}".format(prefix, func_name, key)
55
- return "{}:{}".format(func_name, key)
58
+ if key_include_func:
59
+ if prefix:
60
+ return "{}:{}:{}".format(prefix, func_name, key)
61
+ return "{}:{}".format(func_name, key)
62
+ else:
63
+ if prefix:
64
+ return "{}:{}".format(prefix, key)
65
+ return key
56
66
 
57
67
  sig = inspect.signature(func)
58
68
  parts = []
@@ -62,9 +72,14 @@ def get_cache_key_template(func, key=None, prefix=""):
62
72
  parts.append("{{{name}}}".format(name=name))
63
73
 
64
74
  template = ":".join(parts) if parts else ""
65
- if prefix:
66
- return "{}:{}:{}".format(prefix, func_name, template)
67
- return "{}:{}".format(func_name, template)
75
+ if key_include_func:
76
+ if prefix:
77
+ return "{}:{}:{}".format(prefix, func_name, template)
78
+ return "{}:{}".format(func_name, template)
79
+ else:
80
+ if prefix:
81
+ return "{}:{}".format(prefix, template) if template else prefix
82
+ return template
68
83
 
69
84
 
70
85
  def _resolve_attr(val, attr_path):
@@ -148,6 +148,7 @@ class Cache(object):
148
148
  self._tag_registry = get_default_tag_registry()
149
149
  self._serializer = default_serializer
150
150
  self._is_setup = False
151
+ self._key_include_func = True
151
152
 
152
153
  @property
153
154
  def is_setup(self):
@@ -159,13 +160,16 @@ class Cache(object):
159
160
 
160
161
  # --- Setup ---
161
162
 
162
- def setup(self, settings_url, middlewares=None, prefix="", **kwargs):
163
+ def setup(self, settings_url, middlewares=None, prefix="", key_include_func=True, **kwargs):
163
164
  """Configure the cache backend from a URL.
164
165
 
165
166
  Args:
166
167
  settings_url: URL like 'mem://', 'redis://host:port/db'.
167
168
  middlewares: List of Middleware instances.
168
169
  prefix: Global key prefix.
170
+ key_include_func: If False, the module path and function name are NOT
171
+ included in auto-generated cache keys. Useful for short, business-logic-only
172
+ keys. Default is True.
169
173
  **kwargs: Extra arguments passed to the backend.
170
174
 
171
175
  Supported URL schemes: mem://, redis://, rediss://, dual://
@@ -200,6 +204,7 @@ class Cache(object):
200
204
  comp = NullCompressor()
201
205
  self._serializer = Serializer(serializer=ser, compressor=comp, signer=signer)
202
206
 
207
+ self._key_include_func = key_include_func
203
208
  self._backend.init_sync()
204
209
  self._is_setup = True
205
210
  return self
@@ -480,12 +485,14 @@ class Cache(object):
480
485
  # --- Decorator shortcuts ---
481
486
 
482
487
  def cache(self, ttl, key=None, condition=None, prefix="", lock=False,
483
- lock_ttl=None, tags=(), serializer=None):
488
+ lock_ttl=None, tags=(), serializer=None, key_include_func:bool=None):
484
489
  from nb_cache.decorators.cache import cache as _cache
490
+ _kif = self._key_include_func if key_include_func is None else key_include_func
485
491
  return _cache(ttl, key=key, condition=condition, prefix=prefix,
486
492
  lock=lock, lock_ttl=lock_ttl, tags=tags,
487
493
  backend=self._backend, serializer=serializer or self._serializer,
488
- tag_registry=self._tag_registry if tags else None)
494
+ tag_registry=self._tag_registry if tags else None,
495
+ key_include_func=_kif)
489
496
 
490
497
  def failover(self, ttl, key=None, exceptions=None, condition=None,
491
498
  prefix="fail", tags=(), serializer=None):
@@ -1,7 +1,7 @@
1
- Metadata-Version: 2.4
2
- Name: nb_cache
3
- Version: 0.2
4
- Summary: 更强的缓存装饰器,支持同步和异步函数,支持加锁防止缓存击穿,支持内存和Redis作为缓存器,支持Redis+内存双缓存提高性能
1
+ Metadata-Version: 2.1
2
+ Name: nb-cache
3
+ Version: 0.4
4
+ Summary: `nb_cache` 不仅是一个基础的缓存装饰器,它在彻底抹平 Python 同步与异步代码差异的同时,开箱即用地提供了内存/Redis双层缓存、防击穿、防雪崩、限流与熔断等企业级高可用特性。
5
5
  Author: ydf0509
6
6
  Project-URL: Homepage, https://github.com/ydf0509/nb_cache
7
7
  Project-URL: Repository, https://github.com/ydf0509/nb_cache
@@ -18,22 +18,56 @@ Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
21
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
23
  Requires-Python: >=3.6
23
24
  Description-Content-Type: text/markdown
24
25
  Provides-Extra: redis
25
- Requires-Dist: redis>=3.0; extra == "redis"
26
26
  Provides-Extra: speedup
27
- Requires-Dist: xxhash; extra == "speedup"
28
- Requires-Dist: hiredis; extra == "speedup"
29
27
  Provides-Extra: all
30
- Requires-Dist: redis>=3.0; extra == "all"
31
- Requires-Dist: xxhash; extra == "all"
32
- Requires-Dist: hiredis; extra == "all"
33
28
 
34
- # nb_cache
35
29
 
36
- 更强的缓存装饰器,支持同步和异步函数,支持加锁防止缓存击穿,支持内存和Redis作为缓存器,支持Redis+内存双缓存提高性能。
30
+ # 🚀 nb_cache: Python 缓存界的“瑞士军刀”
31
+
32
+ [![Python Versions](https://img.shields.io/badge/python-3.6+-blue.svg)](https://pypi.org/project/nb-cache/)
33
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
34
+
35
+ > **`nb_cache` 不仅是一个基础的缓存装饰器,它在彻底抹平 Python 同步与异步代码差异的同时,开箱即用地提供了内存/Redis双层缓存、防击穿、防雪崩、限流与熔断等企业级高可用特性。**
36
+
37
+ 在如今混杂着 Sync 和 Async 的 Python 项目中,开发者往往需要为同步代码和异步代码寻找不同的缓存解决方案。`nb_cache` 彻底抹平了这一差异——**只需同一个装饰器,即可完美兼容同步与异步函数**。
38
+
39
+ 如果你曾经使用过 `cashews`,你会对 `nb_cache` 感到非常亲切。`nb_cache` 吸收了其优秀的特性,并弥补了其最大的短板:**全面支持同/异步场景,且所有底层操作兼具 Sync 和 Async 两种 API**。
40
+
41
+ ## ✨ 核心特性
42
+
43
+ - ☯️ **同/异步无缝统一**:无需区分 `@cache` 或 `@acache`,同一个装饰器自动识别普通函数与协程,内部自动路由。同一套上下文管理器同时支持 `with` 和 `async with`。
44
+ - 🚀 **丰富的高级后端**:
45
+ - **Memory (`mem://`)**: 极速的本地 LRU 内存缓存。
46
+ - **Redis (`redis://`)**: 分布式 Redis 缓存,支持连接池。
47
+ - **双层缓存 (`dual://`)**: **(杀手级特性)** 本地内存(L1) + Redis(L2) 的透明双重缓存。读取时优先击中内存,写入时双写,彻底释放 Redis 压力。
48
+ - 🛡️ **企业级高可用防护**:
49
+ - **防缓存击穿 (Stampede)**:只需一个参数 `lock=True`,即可在并发未命中时让多余请求等待,只放行一个请求去查库。
50
+ - **防缓存雪崩 (Avalanche)**:提供 `@cache.early` (提前后台刷新) 和 `@cache.soft` (软过期,返回旧值并异步刷新) 完美解决雪崩。
51
+ - **服务降级与失败回退**:提供 `@cache.failover`,当数据库或下游接口挂掉时,自动返回缓存的旧值兜底。
52
+ - 🔧 **极其丰富的“微服务”级装饰器**:除了缓存,还内置了**限流** (`rate_limit`, `slice_rate_limit`)、**熔断** (`circuit_breaker`)、**并发防抖** (`thunder_protection`)、**布隆过滤器** (`bloom`, `dual_bloom`)。
53
+ - 🔑 **智能 Key 路由与模板**:告别繁琐的 key 拼接。支持 `{user_id}`、`{user.name}` (直接读取对象属性)、`{data:hash}` (自动对大字典算md5) 等高级格式化模板。
54
+ - 🔒 **数据安全与压缩**:自带序列化流水线。一行配置即可开启 JSON/Pickle 序列化、Gzip/Zlib 压缩,以及 HMAC 签名(防止缓存数据被恶意篡改)。
55
+ - 🏷️ **标签系统与事务**:支持给缓存打标签 (Tags) 实现按业务模块批量失效,支持类似数据库的缓存事务 (Transaction) 自动回滚。
56
+
57
+ ## 💡 为什么选择 nb_cache?
58
+
59
+ | 特性 | 传统自带 `lru_cache` | `redis-py` 原生 | `cashews` | 🏆 `nb_cache` |
60
+ | :--- | :---: | :---: | :---: | :---: |
61
+ | **同步函数支持** | ✅ | ✅ | ❌ | **✅ 完美支持** |
62
+ | **异步函数 (Asyncio) 支持** | ❌ | ✅ | ✅ | **✅ 完美支持** |
63
+ | **内存/Redis 双重缓存** | ❌ | ❌ | ✅ | **✅ 开箱即用** |
64
+ | **防击穿 (分布式锁合并请求)** | ❌ | 需手写代码 | ✅ | **✅ `lock=True`** |
65
+ | **防雪崩/后台自动刷新** | ❌ | 需手写代码 | ✅ | **✅ `@cache.early`** |
66
+ | **限流与熔断** | ❌ | 需手写代码 | ✅ | **✅ 内置支持** |
67
+
68
+ ---
69
+
70
+ ### 👉 接下来,请看下方的【安装】与【快速开始】,体验一行代码带来的架构升级:
37
71
 
38
72
  ## 安装
39
73
 
@@ -66,12 +100,20 @@ def get_user(user_id):
66
100
  async def get_user_async(user_id):
67
101
  return await db.query_async(user_id)
68
102
 
69
- # 加锁防止缓存击穿
103
+ # 加锁防止缓存击穿,如果123这个入参没有缓存,但是同一秒请求123这个入参1万次,
104
+ # 加上lock=True后,只有第一次请求会真正执行函数,其余请求等待并复用第一次请求的结果,避免"击穿"。
70
105
  @cache.cache(ttl=60, lock=True)
71
106
  def get_hot_data(key):
107
+ time.sleep(20)
72
108
  return expensive_query(key)
73
109
  ```
74
110
 
111
+ ## 不想吃苦,如何使用ai掌握nb_cache?
112
+
113
+ `nb_cache_all_docs_and_codes.md` 这个文件包含了nb_cache 的教程和全部源码。
114
+ 你把这个文件发送给deepseek ai [https://chat.deepseek.com/](https://chat.deepseek.com/) ,ai就能自动帮你掌握 `nb_cache` 的用法。
115
+
116
+
75
117
  ## 对比 cashews
76
118
 
77
119
  如果你不懂 `nb_cache` 用法,可以参考 `cashews` 的用法。ai很熟练 `cashews`的用法。
@@ -665,6 +707,94 @@ def check_permission(user, action):
665
707
  return db.query_permission(user['id'], action)
666
708
  ```
667
709
 
710
+ ### key_include_func 参数说明
711
+
712
+ 默认情况下,`nb_cache` 会把 **模块路径 + 函数名** 自动拼入 cache key,以确保不同模块的同名函数不会冲突:
713
+
714
+ ```
715
+ # 默认生成的 key(含函数信息)
716
+ testp2:myapp.services:get_user:user_id:42
717
+ ```
718
+
719
+ 如果你已经通过 `key=` 参数自己指定了业务 key 模板,这段模块+函数前缀往往是多余的噪音。
720
+ 设置 `key_include_func=False` 后,key 只保留业务部分:
721
+
722
+ ```
723
+ # key_include_func=False 后生成的 key
724
+ testp2:user:42
725
+ ```
726
+
727
+ #### 设置级别
728
+
729
+ `key_include_func` 支持两个层级,**装饰器上的值优先于 `setup()` 的默认值**。
730
+
731
+ **1. `setup()` 级别 —— 影响该实例下所有装饰器**
732
+
733
+ ```python
734
+ from nb_cache import Cache
735
+
736
+ cache = Cache().setup("redis://localhost:6379/0", prefix="myapp", key_include_func=False)
737
+
738
+ @cache.cache(ttl=300, key="user:{user_id}")
739
+ def get_user(user_id):
740
+ return db.query(user_id)
741
+ # final_key → myapp:user:42
742
+
743
+ @cache.cache(ttl=60, key="order:{order_id}")
744
+ def get_order(order_id):
745
+ return db.query_order(order_id)
746
+ # final_key → myapp:order:100
747
+ ```
748
+
749
+ **2. 装饰器级别 —— 覆盖 `setup()` 的默认值,只影响当前函数**
750
+
751
+ ```python
752
+ cache = Cache().setup("redis://localhost:6379/0", prefix="myapp")
753
+ # 默认 key_include_func=True
754
+
755
+ @cache.cache(ttl=300, key="user:{user_id}")
756
+ def get_user(user_id):
757
+ ...
758
+ # final_key → myapp:mymodule:get_user:user:{user_id} → myapp:mymodule:get_user:user:42
759
+
760
+ @cache.cache(ttl=60, key="order:{order_id}", key_include_func=False)
761
+ def get_order(order_id):
762
+ ...
763
+ # final_key → myapp:order:100 (单独关闭,不含函数名)
764
+ ```
765
+
766
+ #### 不指定 key= 时的行为
767
+
768
+ 当不传 `key=` 参数,`nb_cache` 会根据函数签名自动生成 key。
769
+ 此时 `key_include_func=False` 意味着 key 只由参数值组成,**极易碰撞**,不推荐在此场景下使用:
770
+
771
+ ```python
772
+ # 不推荐:不指定 key= 且 key_include_func=False
773
+ @cache.cache(ttl=60, key_include_func=False)
774
+ def get_user(user_id):
775
+ ...
776
+ # final_key → myapp:user_id=42 ← 与其他相同签名函数会冲突
777
+ ```
778
+
779
+ #### 如何预览生成的 key(不调用函数)
780
+
781
+ ```python
782
+ from nb_cache.key import get_cache_key, get_cache_key_template
783
+
784
+ # 方式1:从已装饰函数取模板(推荐)
785
+ template = get_user._cache_key_template
786
+ logic_key = get_cache_key(get_user, template, (42,), {})
787
+ final_key = cache._backend._make_key(logic_key)
788
+ print(final_key) # myapp:user:42
789
+
790
+ # 方式2:直接用工具函数生成(不需要先装饰)
791
+ tpl = get_cache_key_template(get_user, key="user:{user_id}", key_include_func=False)
792
+ key = get_cache_key(get_user, tpl, (42,), {})
793
+ print(key) # user:42
794
+ ```
795
+
796
+ ---
797
+
668
798
  ## 锁(上下文管理器)
669
799
 
670
800
  ```python
@@ -775,6 +905,71 @@ def get_data():
775
905
  return {"name": "test"}
776
906
  ```
777
907
 
908
+ ## 常见问题解答
909
+
910
+ ### 问题1:如何查看缓存最终生成的key是什么?
911
+
912
+
913
+ #### 方式一:通过日志查看
914
+ ```
915
+ 因为nb_cache 已经在 nb_cache.cache 日志命名空间,用debug 日志级别打印了最终生成的key。
916
+
917
+ 所以你可以通过nb_log来查看:
918
+ nb_log.get_logger('nb_cache.cache')
919
+
920
+ 也可以通过 原生logging 来查看:
921
+ logger = logging.getLogger("nb_cache.cache")
922
+ logger.setLevel(logging.DEBUG)
923
+ logger.addHandler(logging.StreamHandler())
924
+ ```
925
+
926
+ 日志例子:
927
+ ```
928
+ 2026-02-28 18:46:01 - nb_cache.cache - "D:\codes\nb_cache\nb_cache\decorators\cache.py:61" - async_wrapper - DEBUG - [nb_cache] func=__main__:aio_fun final_key=testp2:__main__:aio_fun:aiof:3_4 ttl=700.0
929
+ ```
930
+
931
+ #### 方式二:不调用函数,直接预览 cache key
932
+
933
+ ```python
934
+ # -*- coding: utf-8 -*-
935
+ """nb_cache key 生成 Demo"""
936
+ import sys
937
+ import asyncio
938
+ from nb_cache import Cache
939
+ from nb_cache.key import get_cache_key, get_cache_key_template
940
+ import nb_log
941
+
942
+ nb_log.get_logger("nb_cache.cache")
943
+
944
+ if sys.platform == "win32":
945
+ asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
946
+
947
+ cache = Cache()
948
+ cache.setup("redis://", prefix="testp2")
949
+
950
+
951
+ @cache.cache(ttl=700,key='sf:{a}_{b}')
952
+ def simple_func(a, b):
953
+ return a + b
954
+
955
+ print(simple_func(1, 2))
956
+
957
+ # --- 不调用函数,直接预览 cache key ---
958
+ # 方式1:从装饰后的函数取模板属性(推荐)
959
+ template = simple_func._cache_key_template
960
+ logic_key = get_cache_key(simple_func, template, (1, 2), {})
961
+ final_key = cache._backend._make_key(logic_key)
962
+ print("logic_key:", logic_key) # 不含 prefix
963
+ print("final_key:", final_key) # 含 prefix,与 Redis 中一致
964
+
965
+ # 方式2:用工具函数直接生成,不需要先装饰
966
+ def raw_func(a, b):
967
+ return a + b
968
+ tpl = get_cache_key_template(raw_func, key='sf:{a}_{b}')
969
+ key2 = get_cache_key(raw_func, tpl, (1, 2), {})
970
+ print("key2 (无prefix):", key2)
971
+ ```
972
+
778
973
  ## 许可证
779
974
 
780
975
  MIT License
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nb_cache"
7
- version = "0.2"
8
- description = "更强的缓存装饰器,支持同步和异步函数,支持加锁防止缓存击穿,支持内存和Redis作为缓存器,支持Redis+内存双缓存提高性能"
7
+ version = "0.4"
8
+ description = "`nb_cache` 不仅是一个基础的缓存装饰器,它在彻底抹平 Python 同步与异步代码差异的同时,开箱即用地提供了内存/Redis双层缓存、防击穿、防雪崩、限流与熔断等企业级高可用特性。"
9
9
  readme = "README.md"
10
10
  # license = {text = "MIT"}
11
11
  requires-python = ">=3.6"
@@ -26,6 +26,7 @@ classifiers = [
26
26
  "Programming Language :: Python :: 3.11",
27
27
  "Programming Language :: Python :: 3.12",
28
28
  "Programming Language :: Python :: 3.13",
29
+ "Programming Language :: Python :: 3.14",
29
30
  "Topic :: Software Development :: Libraries :: Python Modules",
30
31
  # "Topic :: System :: Caching",
31
32
  ]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes