infomankit 0.3.23__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.
Files changed (143) hide show
  1. infoman/__init__.py +1 -0
  2. infoman/cli/README.md +378 -0
  3. infoman/cli/__init__.py +7 -0
  4. infoman/cli/commands/__init__.py +3 -0
  5. infoman/cli/commands/init.py +312 -0
  6. infoman/cli/scaffold.py +634 -0
  7. infoman/cli/templates/Makefile.template +132 -0
  8. infoman/cli/templates/app/__init__.py.template +3 -0
  9. infoman/cli/templates/app/app.py.template +4 -0
  10. infoman/cli/templates/app/models_base.py.template +18 -0
  11. infoman/cli/templates/app/models_entity_init.py.template +11 -0
  12. infoman/cli/templates/app/models_schemas_init.py.template +11 -0
  13. infoman/cli/templates/app/repository_init.py.template +11 -0
  14. infoman/cli/templates/app/routers_init.py.template +15 -0
  15. infoman/cli/templates/app/services_init.py.template +11 -0
  16. infoman/cli/templates/app/static_index.html.template +39 -0
  17. infoman/cli/templates/app/static_main.js.template +31 -0
  18. infoman/cli/templates/app/static_style.css.template +111 -0
  19. infoman/cli/templates/app/utils_init.py.template +11 -0
  20. infoman/cli/templates/config/.env.dev.template +43 -0
  21. infoman/cli/templates/config/.env.prod.template +43 -0
  22. infoman/cli/templates/config/README.md.template +28 -0
  23. infoman/cli/templates/docker/.dockerignore.template +60 -0
  24. infoman/cli/templates/docker/Dockerfile.template +47 -0
  25. infoman/cli/templates/docker/README.md.template +240 -0
  26. infoman/cli/templates/docker/docker-compose.yml.template +81 -0
  27. infoman/cli/templates/docker/mysql_custom.cnf.template +42 -0
  28. infoman/cli/templates/docker/mysql_init.sql.template +15 -0
  29. infoman/cli/templates/project/.env.example.template +1 -0
  30. infoman/cli/templates/project/.gitignore.template +60 -0
  31. infoman/cli/templates/project/Makefile.template +38 -0
  32. infoman/cli/templates/project/README.md.template +137 -0
  33. infoman/cli/templates/project/deploy.sh.template +97 -0
  34. infoman/cli/templates/project/main.py.template +10 -0
  35. infoman/cli/templates/project/manage.sh.template +97 -0
  36. infoman/cli/templates/project/pyproject.toml.template +47 -0
  37. infoman/cli/templates/project/service.sh.template +203 -0
  38. infoman/config/__init__.py +25 -0
  39. infoman/config/base.py +67 -0
  40. infoman/config/db_cache.py +237 -0
  41. infoman/config/db_relation.py +181 -0
  42. infoman/config/db_vector.py +39 -0
  43. infoman/config/jwt.py +16 -0
  44. infoman/config/llm.py +16 -0
  45. infoman/config/log.py +627 -0
  46. infoman/config/mq.py +26 -0
  47. infoman/config/settings.py +65 -0
  48. infoman/llm/__init__.py +0 -0
  49. infoman/llm/llm.py +297 -0
  50. infoman/logger/__init__.py +57 -0
  51. infoman/logger/context.py +191 -0
  52. infoman/logger/core.py +358 -0
  53. infoman/logger/filters.py +157 -0
  54. infoman/logger/formatters.py +138 -0
  55. infoman/logger/handlers.py +276 -0
  56. infoman/logger/metrics.py +160 -0
  57. infoman/performance/README.md +583 -0
  58. infoman/performance/__init__.py +19 -0
  59. infoman/performance/cli.py +215 -0
  60. infoman/performance/config.py +166 -0
  61. infoman/performance/reporter.py +519 -0
  62. infoman/performance/runner.py +303 -0
  63. infoman/performance/standards.py +222 -0
  64. infoman/service/__init__.py +8 -0
  65. infoman/service/app.py +67 -0
  66. infoman/service/core/__init__.py +0 -0
  67. infoman/service/core/auth.py +105 -0
  68. infoman/service/core/lifespan.py +132 -0
  69. infoman/service/core/monitor.py +57 -0
  70. infoman/service/core/response.py +37 -0
  71. infoman/service/exception/__init__.py +7 -0
  72. infoman/service/exception/error.py +274 -0
  73. infoman/service/exception/exception.py +25 -0
  74. infoman/service/exception/handler.py +238 -0
  75. infoman/service/infrastructure/__init__.py +8 -0
  76. infoman/service/infrastructure/base.py +212 -0
  77. infoman/service/infrastructure/db_cache/__init__.py +8 -0
  78. infoman/service/infrastructure/db_cache/manager.py +194 -0
  79. infoman/service/infrastructure/db_relation/__init__.py +41 -0
  80. infoman/service/infrastructure/db_relation/manager.py +300 -0
  81. infoman/service/infrastructure/db_relation/manager_pro.py +408 -0
  82. infoman/service/infrastructure/db_relation/mysql.py +52 -0
  83. infoman/service/infrastructure/db_relation/pgsql.py +54 -0
  84. infoman/service/infrastructure/db_relation/sqllite.py +25 -0
  85. infoman/service/infrastructure/db_vector/__init__.py +40 -0
  86. infoman/service/infrastructure/db_vector/manager.py +201 -0
  87. infoman/service/infrastructure/db_vector/qdrant.py +322 -0
  88. infoman/service/infrastructure/mq/__init__.py +15 -0
  89. infoman/service/infrastructure/mq/manager.py +178 -0
  90. infoman/service/infrastructure/mq/nats/__init__.py +0 -0
  91. infoman/service/infrastructure/mq/nats/nats_client.py +57 -0
  92. infoman/service/infrastructure/mq/nats/nats_event_router.py +25 -0
  93. infoman/service/launch.py +284 -0
  94. infoman/service/middleware/__init__.py +7 -0
  95. infoman/service/middleware/base.py +41 -0
  96. infoman/service/middleware/logging.py +51 -0
  97. infoman/service/middleware/rate_limit.py +301 -0
  98. infoman/service/middleware/request_id.py +21 -0
  99. infoman/service/middleware/white_list.py +24 -0
  100. infoman/service/models/__init__.py +8 -0
  101. infoman/service/models/base.py +441 -0
  102. infoman/service/models/type/embed.py +70 -0
  103. infoman/service/routers/__init__.py +18 -0
  104. infoman/service/routers/health_router.py +311 -0
  105. infoman/service/routers/monitor_router.py +44 -0
  106. infoman/service/utils/__init__.py +8 -0
  107. infoman/service/utils/cache/__init__.py +0 -0
  108. infoman/service/utils/cache/cache.py +192 -0
  109. infoman/service/utils/module_loader.py +10 -0
  110. infoman/service/utils/parse.py +10 -0
  111. infoman/service/utils/resolver/__init__.py +8 -0
  112. infoman/service/utils/resolver/base.py +47 -0
  113. infoman/service/utils/resolver/resp.py +102 -0
  114. infoman/service/vector/__init__.py +20 -0
  115. infoman/service/vector/base.py +56 -0
  116. infoman/service/vector/qdrant.py +125 -0
  117. infoman/service/vector/service.py +67 -0
  118. infoman/utils/__init__.py +2 -0
  119. infoman/utils/decorators/__init__.py +8 -0
  120. infoman/utils/decorators/cache.py +137 -0
  121. infoman/utils/decorators/retry.py +99 -0
  122. infoman/utils/decorators/safe_execute.py +99 -0
  123. infoman/utils/decorators/timing.py +99 -0
  124. infoman/utils/encryption/__init__.py +8 -0
  125. infoman/utils/encryption/aes.py +66 -0
  126. infoman/utils/encryption/ecc.py +108 -0
  127. infoman/utils/encryption/rsa.py +112 -0
  128. infoman/utils/file/__init__.py +0 -0
  129. infoman/utils/file/handler.py +22 -0
  130. infoman/utils/hash/__init__.py +0 -0
  131. infoman/utils/hash/hash.py +61 -0
  132. infoman/utils/http/__init__.py +8 -0
  133. infoman/utils/http/client.py +62 -0
  134. infoman/utils/http/info.py +94 -0
  135. infoman/utils/http/result.py +19 -0
  136. infoman/utils/notification/__init__.py +8 -0
  137. infoman/utils/notification/feishu.py +35 -0
  138. infoman/utils/text/__init__.py +8 -0
  139. infoman/utils/text/extractor.py +111 -0
  140. infomankit-0.3.23.dist-info/METADATA +632 -0
  141. infomankit-0.3.23.dist-info/RECORD +143 -0
  142. infomankit-0.3.23.dist-info/WHEEL +4 -0
  143. infomankit-0.3.23.dist-info/entry_points.txt +5 -0
@@ -0,0 +1,102 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ # Time :2025/6/25 21:51
6
+ # Author :Maxwell
7
+ # Description:
8
+ """
9
+
10
+ import json
11
+ import traceback
12
+ from typing import Any, Optional, Type, Union
13
+ from pydantic import BaseModel, ValidationError
14
+ from infoman.service.utils.resolver.base import BaseResp
15
+ from infoman.logger import logger
16
+
17
+
18
+ def get_data_from_text(text) -> (bool, Any):
19
+ try:
20
+ result = json.loads(text)
21
+ result_code = result.get("code")
22
+ if result_code != 200:
23
+ return False, result.get("message")
24
+ return True, result.get("data")
25
+ except Exception as e:
26
+ logger.info(f"get_data_from_text error: {traceback.format_exc()}")
27
+ return False, None
28
+
29
+
30
+ def get_data_model_from_text(
31
+ text: str, model: Optional[Type[BaseModel]] = None
32
+ ) -> (bool, Any):
33
+ """
34
+ 从文本中解析JSON数据,并可选地使用模型验证数据。
35
+
36
+ 参数:
37
+ text (str): 要解析的JSON文本
38
+ model (Optional[Type[BaseModel]]): 可选的Pydantic模型,用于验证数据
39
+
40
+ 返回:
41
+ tuple: (bool, Any) 第一个元素表示是否成功,第二个元素是数据或错误信息
42
+ """
43
+ try:
44
+ result = json.loads(text)
45
+
46
+ result_code = result.get("code")
47
+ if result_code != "000000":
48
+ return False, result.get("message")
49
+
50
+ data = result.get("data")
51
+ if model is not None:
52
+ if isinstance(data, dict):
53
+ try:
54
+ validated_data = model.parse_obj(data)
55
+ return True, validated_data
56
+ except ValidationError as e:
57
+ logger.info(f"Data validation error: {e}")
58
+ return False, f"Data validation failed: {e}"
59
+ elif isinstance(data, list):
60
+ data_list = []
61
+ for one in data:
62
+ try:
63
+ validated_data = model.parse_obj(one)
64
+ data_list.append(validated_data)
65
+ except ValidationError as e:
66
+ logger.info(f"Data validation error: {e}")
67
+ return True, data_list
68
+ return True, data
69
+
70
+ except Exception as e:
71
+ logger.info(f"get_data_from_text error: {traceback.format_exc()}")
72
+ return False, None
73
+
74
+
75
+ def get_model_from_text(
76
+ text: str, model: Optional[Type[BaseModel]] = None
77
+ ) -> (bool, Any):
78
+ try:
79
+ result = json.loads(text)
80
+ result_code = result.get("code")
81
+ if result_code != "000000":
82
+ return False, result.get("message")
83
+
84
+ data = result.get("data")
85
+ if isinstance(data, list):
86
+ try:
87
+ validated_data = model.parse_obj(result)
88
+ return True, validated_data
89
+ except ValidationError as e:
90
+ logger.info(f"Data validation error: {e}")
91
+
92
+ if model is not None:
93
+ try:
94
+ validated_data = model.parse_obj(data)
95
+ return True, validated_data
96
+ except ValidationError as e:
97
+ logger.info(f"Data validation error: {e}")
98
+ return False, "data is null"
99
+
100
+ except Exception as e:
101
+ logger.info(f"get_data_from_text error: {traceback.format_exc()}")
102
+ return False, None
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """Vector store integration entry.
5
+
6
+ Exposes a light-weight abstraction and a Qdrant implementation.
7
+ """
8
+
9
+ from .base import VectorClient, VectorPoint, VectorFilter
10
+ from .qdrant import QdrantVectorClient
11
+ from .service import VectorService, get_vector_service
12
+
13
+ __all__ = [
14
+ "VectorClient",
15
+ "VectorPoint",
16
+ "VectorFilter",
17
+ "QdrantVectorClient",
18
+ "VectorService",
19
+ "get_vector_service",
20
+ ]
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from __future__ import annotations
5
+
6
+ from dataclasses import dataclass
7
+ from typing import Any, Iterable, Mapping, Protocol, Sequence
8
+
9
+
10
+ @dataclass
11
+ class VectorPoint:
12
+ id: str | int
13
+ vector: Sequence[float]
14
+ payload: Mapping[str, Any] | None = None
15
+
16
+
17
+ @dataclass
18
+ class VectorFilter:
19
+ # Generic equality filter; concrete impl can translate
20
+ must: dict[str, Any] | None = None
21
+ must_not: dict[str, Any] | None = None
22
+
23
+
24
+ class VectorClient(Protocol):
25
+ async def ensure_collection(
26
+ self,
27
+ name: str,
28
+ vector_size: int,
29
+ distance: str = "cosine",
30
+ on_disk: bool = False,
31
+ ) -> None: ...
32
+
33
+ async def upsert(
34
+ self,
35
+ collection: str,
36
+ points: Iterable[VectorPoint],
37
+ wait: bool = False,
38
+ ) -> None: ...
39
+
40
+ async def delete(
41
+ self,
42
+ collection: str,
43
+ ids: Iterable[str | int] | None = None,
44
+ filt: VectorFilter | None = None,
45
+ wait: bool = False,
46
+ ) -> None: ...
47
+
48
+ async def search(
49
+ self,
50
+ collection: str,
51
+ query_vector: Sequence[float],
52
+ limit: int = 10,
53
+ filt: VectorFilter | None = None,
54
+ with_vectors: bool = False,
55
+ ) -> list[dict[str, Any]]: ...
56
+
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import Any, Iterable, Sequence
7
+
8
+ from .base import VectorClient, VectorPoint, VectorFilter
9
+
10
+ try:
11
+ from qdrant_client import AsyncQdrantClient
12
+ from qdrant_client.models import (
13
+ Distance,
14
+ VectorParams,
15
+ PointStruct,
16
+ Filter as QFilter,
17
+ FieldCondition,
18
+ MatchAny,
19
+ MatchValue,
20
+ )
21
+ except Exception: # pragma: no cover - optional dependency at import time
22
+ AsyncQdrantClient = None # type: ignore
23
+ Distance = VectorParams = PointStruct = object # type: ignore
24
+ QFilter = FieldCondition = MatchAny = MatchValue = object # type: ignore
25
+
26
+
27
+ _DIST_MAP = {
28
+ "cosine": Distance.COSINE if hasattr(Distance, "COSINE") else "cosine",
29
+ "dot": Distance.DOT if hasattr(Distance, "DOT") else "dot",
30
+ "euclid": Distance.EUCLID if hasattr(Distance, "EUCLID") else "euclid",
31
+ }
32
+
33
+
34
+ def _to_qfilter(f: VectorFilter | None) -> QFilter | None:
35
+ if f is None:
36
+ return None
37
+ must = []
38
+ must_not = []
39
+ if f.must:
40
+ for k, v in f.must.items():
41
+ if isinstance(v, (list, tuple, set)):
42
+ must.append(FieldCondition(key=k, match=MatchAny(any=list(v))))
43
+ else:
44
+ must.append(FieldCondition(key=k, match=MatchValue(value=v)))
45
+ if f.must_not:
46
+ for k, v in f.must_not.items():
47
+ if isinstance(v, (list, tuple, set)):
48
+ must_not.append(FieldCondition(key=k, match=MatchAny(any=list(v))))
49
+ else:
50
+ must_not.append(FieldCondition(key=k, match=MatchValue(value=v)))
51
+ return QFilter(must=must or None, must_not=must_not or None)
52
+
53
+
54
+ class QdrantVectorClient(VectorClient):
55
+ def __init__(self, client: Any):
56
+ if AsyncQdrantClient is not None and not isinstance(client, AsyncQdrantClient):
57
+ # Allow duck typing (useful in tests), but warn in docs
58
+ pass
59
+ self.client = client
60
+
61
+ async def ensure_collection(
62
+ self, name: str, vector_size: int, distance: str = "cosine", on_disk: bool = False
63
+ ) -> None:
64
+ dist = _DIST_MAP.get(distance, _DIST_MAP["cosine"])
65
+ exists = await self.client.collection_exists(collection_name=name)
66
+ if exists:
67
+ return
68
+ await self.client.create_collection(
69
+ collection_name=name,
70
+ vectors_config=VectorParams(size=vector_size, distance=dist, on_disk=on_disk),
71
+ )
72
+
73
+ async def upsert(self, collection: str, points: Iterable[VectorPoint], wait: bool = False) -> None:
74
+ payloads = []
75
+ qpoints: list[PointStruct] = []
76
+ for p in points:
77
+ payload = dict(p.payload) if p.payload else None
78
+ payloads.append(payload)
79
+ qpoints.append(PointStruct(id=p.id, vector=list(p.vector), payload=payload))
80
+ if not qpoints:
81
+ return
82
+ await self.client.upsert(collection_name=collection, points=qpoints, wait=wait)
83
+
84
+ async def delete(
85
+ self,
86
+ collection: str,
87
+ ids: Iterable[str | int] | None = None,
88
+ filt: VectorFilter | None = None,
89
+ wait: bool = False,
90
+ ) -> None:
91
+ if ids:
92
+ await self.client.delete(collection_name=collection, points=list(ids), wait=wait)
93
+ return
94
+ qf = _to_qfilter(filt)
95
+ if qf is not None:
96
+ await self.client.delete(collection_name=collection, filter=qf, wait=wait)
97
+
98
+ async def search(
99
+ self,
100
+ collection: str,
101
+ query_vector: Sequence[float],
102
+ limit: int = 10,
103
+ filt: VectorFilter | None = None,
104
+ with_vectors: bool = False,
105
+ ) -> list[dict[str, Any]]:
106
+ qf = _to_qfilter(filt)
107
+ res = await self.client.search(
108
+ collection_name=collection,
109
+ query_vector=list(query_vector),
110
+ limit=limit,
111
+ filter=qf,
112
+ with_vectors=with_vectors,
113
+ )
114
+ results: list[dict[str, Any]] = []
115
+ for r in res:
116
+ results.append(
117
+ {
118
+ "id": r.id,
119
+ "score": r.score,
120
+ "payload": getattr(r, "payload", None),
121
+ "vector": getattr(r, "vector", None) if with_vectors else None,
122
+ }
123
+ )
124
+ return results
125
+
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import Any, Sequence
7
+
8
+ from fastapi import FastAPI
9
+
10
+ from .base import VectorPoint, VectorFilter
11
+ from . import VectorClient
12
+ from infoman.service.models.type.embed import (
13
+ EmbedModel,
14
+ EmbedCollection,
15
+ EmbedDataType,
16
+ )
17
+
18
+
19
+ class VectorService:
20
+ """High-level vector operations bound to Embed* config.
21
+
22
+ This provides convenience methods using `EmbedModel` and `EmbedDataType`
23
+ to compute collection names and dimensions.
24
+ """
25
+
26
+ def __init__(self, vector_client: VectorClient):
27
+ self.vc = vector_client
28
+
29
+ @staticmethod
30
+ def collection_name(data_type: EmbedDataType, model: EmbedModel) -> str:
31
+ return EmbedCollection.collection_name(data_type, model)
32
+
33
+ async def ensure_collection(self, data_type: EmbedDataType, model: EmbedModel) -> str:
34
+ name = self.collection_name(data_type, model)
35
+ await self.vc.ensure_collection(name, model.value.dem, distance="cosine")
36
+ return name
37
+
38
+ async def upsert(
39
+ self,
40
+ data_type: EmbedDataType,
41
+ model: EmbedModel,
42
+ points: list[VectorPoint],
43
+ wait: bool = False,
44
+ ) -> str:
45
+ name = self.collection_name(data_type, model)
46
+ await self.vc.upsert(name, points, wait=wait)
47
+ return name
48
+
49
+ async def search(
50
+ self,
51
+ data_type: EmbedDataType,
52
+ model: EmbedModel,
53
+ query_vector: Sequence[float],
54
+ limit: int = 10,
55
+ filt: VectorFilter | None = None,
56
+ with_vectors: bool = False,
57
+ ) -> list[dict[str, Any]]:
58
+ name = self.collection_name(data_type, model)
59
+ return await self.vc.search(name, query_vector, limit, filt, with_vectors)
60
+
61
+
62
+ def get_vector_service(app: FastAPI) -> VectorService | None:
63
+ vc = getattr(app.state, "vector_client", None)
64
+ if vc is None:
65
+ return None
66
+ return VectorService(vc)
67
+
@@ -0,0 +1,2 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
@@ -0,0 +1,8 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ # Time :2025/6/22 12:06
6
+ # Author :Maxwell
7
+ # Description:
8
+ """
@@ -0,0 +1,137 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ # Time :2025/6/22 12:11
6
+ # Author :Maxwell
7
+ # Description:
8
+ """
9
+ import time
10
+ import functools
11
+ from typing import Callable, TypeVar, Any, Optional, Union, Dict, List, Tuple
12
+ from infoman.logger import logger
13
+ from inspect import iscoroutinefunction
14
+
15
+ T = TypeVar("T")
16
+ F = TypeVar("F", bound=Callable[..., Any])
17
+
18
+
19
+ def cache(
20
+ ttl: Optional[int] = 60, # 缓存生存时间(秒)
21
+ max_size: int = 128, # 最大缓存条目数
22
+ key_func: Optional[Callable] = None, # 自定义缓存键生成函数
23
+ ) -> Callable[[F], F]:
24
+ """
25
+ 缓存装饰器 - 缓存函数结果以提高性能
26
+
27
+ 参数:
28
+ ttl: 缓存生存时间(秒),None表示永不过期
29
+ max_size: 最大缓存条目数
30
+ key_func: 自定义缓存键生成函数
31
+
32
+ 示例:
33
+ @cache(ttl=300) # 缓存5分钟
34
+ def get_user_data(user_id):
35
+ # 获取用户数据的代码
36
+ """
37
+ cache_dict: Dict[str, Tuple[Any, Optional[float]]] = {}
38
+
39
+ def make_key(*args, **kwargs) -> str:
40
+ """生成缓存键"""
41
+ if key_func:
42
+ return str(key_func(*args, **kwargs))
43
+
44
+ # 默认键生成逻辑
45
+ key_parts = [str(arg) for arg in args]
46
+ key_parts.extend(f"{k}={v}" for k, v in sorted(kwargs.items()))
47
+ return ":".join(key_parts)
48
+
49
+ def is_expired(timestamp: Optional[float]) -> bool:
50
+ """检查缓存是否过期"""
51
+ if timestamp is None: # 永不过期
52
+ return False
53
+ return time.time() > timestamp
54
+
55
+ def cleanup_cache() -> None:
56
+ """清理过期缓存"""
57
+ if ttl is None:
58
+ return
59
+
60
+ expired_keys = [
61
+ k
62
+ for k, (_, exp_time) in cache_dict.items()
63
+ if exp_time is not None and is_expired(exp_time)
64
+ ]
65
+
66
+ for k in expired_keys:
67
+ del cache_dict[k]
68
+
69
+ def decorator(func: F) -> F:
70
+ @functools.wraps(func)
71
+ async def async_wrapper(*args, **kwargs):
72
+ # 清理过期缓存
73
+ cleanup_cache()
74
+
75
+ # 生成缓存键
76
+ key = make_key(*args, **kwargs)
77
+
78
+ # 检查缓存
79
+ if key in cache_dict:
80
+ value, exp_time = cache_dict[key]
81
+ if not is_expired(exp_time):
82
+ return value
83
+
84
+ # 缓存未命中,执行函数
85
+ result = await func(*args, **kwargs)
86
+
87
+ # 更新缓存
88
+ if len(cache_dict) >= max_size:
89
+ # 简单的LRU策略:删除第一个条目
90
+ if cache_dict:
91
+ cache_dict.pop(next(iter(cache_dict)))
92
+
93
+ expiration = (time.time() + ttl) if ttl is not None else None
94
+ cache_dict[key] = (result, expiration)
95
+
96
+ return result
97
+
98
+ @functools.wraps(func)
99
+ def sync_wrapper(*args, **kwargs):
100
+ # 清理过期缓存
101
+ cleanup_cache()
102
+
103
+ # 生成缓存键
104
+ key = make_key(*args, **kwargs)
105
+
106
+ # 检查缓存
107
+ if key in cache_dict:
108
+ value, exp_time = cache_dict[key]
109
+ if not is_expired(exp_time):
110
+ return value
111
+
112
+ # 缓存未命中,执行函数
113
+ result = func(*args, **kwargs)
114
+
115
+ # 更新缓存
116
+ if len(cache_dict) >= max_size:
117
+ # 简单的LRU策略:删除第一个条目
118
+ if cache_dict:
119
+ cache_dict.pop(next(iter(cache_dict)))
120
+
121
+ expiration = (time.time() + ttl) if ttl is not None else None
122
+ cache_dict[key] = (result, expiration)
123
+
124
+ return result
125
+
126
+ # 添加清除缓存的辅助方法
127
+ def clear_cache():
128
+ cache_dict.clear()
129
+
130
+ if iscoroutinefunction(func):
131
+ async_wrapper.clear_cache = clear_cache
132
+ return async_wrapper
133
+ else:
134
+ sync_wrapper.clear_cache = clear_cache
135
+ return sync_wrapper
136
+
137
+ return decorator
@@ -0,0 +1,99 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ # Time :2025/6/22 12:10
6
+ # Author :Maxwell
7
+ # Description:
8
+ """
9
+ import time
10
+ import asyncio
11
+ import functools
12
+ from typing import Callable, TypeVar, Any, Optional, Union, Dict, List, Tuple
13
+ from infoman.logger import logger
14
+ from inspect import iscoroutinefunction
15
+
16
+ T = TypeVar("T")
17
+ F = TypeVar("F", bound=Callable[..., Any])
18
+
19
+
20
+ def retry(
21
+ max_attempts: int = 3,
22
+ delay_seconds: float = 1.0,
23
+ backoff_factor: float = 2.0,
24
+ exceptions: Tuple[Exception, ...] = (Exception,),
25
+ logger_name: Optional[str] = None,
26
+ ) -> Callable[[F], F]:
27
+ """
28
+ 重试装饰器 - 在发生特定异常时自动重试函数
29
+
30
+ 参数:
31
+ max_attempts: 最大尝试次数
32
+ delay_seconds: 初始延迟时间(秒)
33
+ backoff_factor: 退避因子(每次重试后延迟时间的乘数)
34
+ exceptions: 触发重试的异常类型
35
+ logger_name: 自定义日志记录器名称
36
+
37
+ 示例:
38
+ @retry(max_attempts=5, exceptions=(ConnectionError, TimeoutError))
39
+ async def connect_to_service():
40
+ # 连接服务的代码
41
+ """
42
+
43
+ def decorator(func: F) -> F:
44
+ @functools.wraps(func)
45
+ async def async_wrapper(*args, **kwargs):
46
+ attempt = 1
47
+ current_delay = delay_seconds
48
+
49
+ while True:
50
+ try:
51
+ return await func(*args, **kwargs)
52
+ except exceptions as e:
53
+ if attempt >= max_attempts:
54
+ logger.error(
55
+ f"Function {func.__name__} failed after {max_attempts} attempts. "
56
+ f"Last error: {str(e)}"
57
+ )
58
+ raise
59
+
60
+ logger.warning(
61
+ f"Attempt {attempt}/{max_attempts} for {func.__name__} failed: {str(e)}. "
62
+ f"Retrying in {current_delay:.2f}s"
63
+ )
64
+
65
+ await asyncio.sleep(current_delay)
66
+ attempt += 1
67
+ current_delay *= backoff_factor
68
+
69
+ @functools.wraps(func)
70
+ def sync_wrapper(*args, **kwargs):
71
+ attempt = 1
72
+ current_delay = delay_seconds
73
+
74
+ while True:
75
+ try:
76
+ return func(*args, **kwargs)
77
+ except exceptions as e:
78
+ if attempt >= max_attempts:
79
+ logger.error(
80
+ f"Function {func.__name__} failed after {max_attempts} attempts. "
81
+ f"Last error: {str(e)}"
82
+ )
83
+ raise
84
+
85
+ logger.warning(
86
+ f"Attempt {attempt}/{max_attempts} for {func.__name__} failed: {str(e)}. "
87
+ f"Retrying in {current_delay:.2f}s"
88
+ )
89
+
90
+ time.sleep(current_delay)
91
+ attempt += 1
92
+ current_delay *= backoff_factor
93
+
94
+ if iscoroutinefunction(func):
95
+ return async_wrapper
96
+ else:
97
+ return sync_wrapper
98
+
99
+ return decorator