pykitool 0.0.1__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.
- pykitool-0.0.1/PKG-INFO +36 -0
- pykitool-0.0.1/README.md +0 -0
- pykitool-0.0.1/pykitool/__init__.py +0 -0
- pykitool-0.0.1/pykitool/base/__init__.py +0 -0
- pykitool-0.0.1/pykitool/base/cache.py +352 -0
- pykitool-0.0.1/pykitool/base/enums.py +102 -0
- pykitool-0.0.1/pykitool/base/exception.py +6 -0
- pykitool-0.0.1/pykitool/base/response.py +82 -0
- pykitool-0.0.1/pykitool/base/tlog.py +230 -0
- pykitool-0.0.1/pykitool/sqliter/__init__.py +0 -0
- pykitool-0.0.1/pykitool/sqliter/exception.py +30 -0
- pykitool-0.0.1/pykitool/sqliter/middleware.py +84 -0
- pykitool-0.0.1/pykitool/sqliter/plus.py +125 -0
- pykitool-0.0.1/pykitool/sqliter/repo.py +188 -0
- pykitool-0.0.1/pykitool/utils/__init__.py +0 -0
- pykitool-0.0.1/pykitool/utils/cbfile.py +697 -0
- pykitool-0.0.1/pykitool/utils/cbrequest.py +473 -0
- pykitool-0.0.1/pykitool/utils/cbruntime.py +870 -0
- pykitool-0.0.1/pykitool/utils/cbutils.py +518 -0
- pykitool-0.0.1/pykitool.egg-info/PKG-INFO +36 -0
- pykitool-0.0.1/pykitool.egg-info/SOURCES.txt +24 -0
- pykitool-0.0.1/pykitool.egg-info/dependency_links.txt +1 -0
- pykitool-0.0.1/pykitool.egg-info/requires.txt +14 -0
- pykitool-0.0.1/pykitool.egg-info/top_level.txt +1 -0
- pykitool-0.0.1/pyproject.toml +68 -0
- pykitool-0.0.1/setup.cfg +4 -0
pykitool-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pykitool
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Author: xiesx
|
|
5
|
+
Project-URL: Homepage, https://github.com/xiesx123/pykitool
|
|
6
|
+
Project-URL: Documentation, https://wgp520.github.io/hutool-python
|
|
7
|
+
Project-URL: Repository, https://github.com/xiesx123/pykitool
|
|
8
|
+
Project-URL: Issues, https://github.com/xiesx123/pykitool/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/xiesx123/pykitool/blob/master/docs/changelog.md
|
|
10
|
+
Keywords: toolkit,tools,utils,helper
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.8
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: asyncio==4.0.0
|
|
25
|
+
Requires-Dist: fake-useragent>=2.0.0
|
|
26
|
+
Requires-Dist: hutool-python>=1.1.1
|
|
27
|
+
Requires-Dist: loguru>=0.7.3
|
|
28
|
+
Requires-Dist: psutil>=7.2.2
|
|
29
|
+
Requires-Dist: pydantic>=2.10.6
|
|
30
|
+
Requires-Dist: requests-cache>=1.3.2
|
|
31
|
+
Requires-Dist: sqlalchemy>=2.0.51
|
|
32
|
+
Requires-Dist: sqlmodel>=0.0.29
|
|
33
|
+
Requires-Dist: starlette>=0.44.0
|
|
34
|
+
Requires-Dist: tqdm>=4.68.3
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
pykitool-0.0.1/README.md
ADDED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
sys.path.insert(0, os.getcwd())
|
|
5
|
+
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
from collections import OrderedDict
|
|
9
|
+
from datetime import timedelta
|
|
10
|
+
from typing import Any, Dict, Optional
|
|
11
|
+
|
|
12
|
+
import requests_cache
|
|
13
|
+
from loguru import logger
|
|
14
|
+
|
|
15
|
+
# ================================ KV 键值存储 ================================
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# 简单的线程安全键值存储类
|
|
19
|
+
class kv:
|
|
20
|
+
|
|
21
|
+
_store: Dict[str, Any] = {} # 共享的存储字典
|
|
22
|
+
_lock: threading.Lock = threading.Lock() # 线程锁
|
|
23
|
+
|
|
24
|
+
# 获取键值
|
|
25
|
+
@classmethod
|
|
26
|
+
def get(cls, key: str, default: Any = None) -> Any:
|
|
27
|
+
with cls._lock:
|
|
28
|
+
return cls._store.get(key, default)
|
|
29
|
+
|
|
30
|
+
# 设置键值
|
|
31
|
+
@classmethod
|
|
32
|
+
def set(cls, key: str, value: Any) -> None:
|
|
33
|
+
with cls._lock:
|
|
34
|
+
cls._store[key] = value
|
|
35
|
+
|
|
36
|
+
# 删除键值
|
|
37
|
+
@classmethod
|
|
38
|
+
def delete(cls, key: str) -> bool:
|
|
39
|
+
with cls._lock:
|
|
40
|
+
if key in cls._store:
|
|
41
|
+
del cls._store[key]
|
|
42
|
+
return True
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
# 清空所有键值
|
|
46
|
+
@classmethod
|
|
47
|
+
def clear(cls) -> None:
|
|
48
|
+
with cls._lock:
|
|
49
|
+
cls._store.clear()
|
|
50
|
+
|
|
51
|
+
# 获取所有键值
|
|
52
|
+
@classmethod
|
|
53
|
+
def get_all(cls) -> Dict[str, Any]:
|
|
54
|
+
with cls._lock:
|
|
55
|
+
return cls._store.copy()
|
|
56
|
+
|
|
57
|
+
# 检查键是否存在
|
|
58
|
+
@classmethod
|
|
59
|
+
def exists(cls, key: str) -> bool:
|
|
60
|
+
with cls._lock:
|
|
61
|
+
return key in cls._store
|
|
62
|
+
|
|
63
|
+
# 获取存储大小
|
|
64
|
+
@classmethod
|
|
65
|
+
def size(cls) -> int:
|
|
66
|
+
with cls._lock:
|
|
67
|
+
return len(cls._store)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ================================ LRU 缓存 ================================
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# LRU 缓存类,支持超时和使用频率统计
|
|
74
|
+
class LruCache:
|
|
75
|
+
|
|
76
|
+
# 初始化 LRU 缓存
|
|
77
|
+
def __init__(self, max_size: int = 5, timeout: int = 3600, cleanup_interval: int = 300) -> None:
|
|
78
|
+
self.cache: OrderedDict[str, Dict[str, Any]] = OrderedDict()
|
|
79
|
+
self.max_size: int = max_size
|
|
80
|
+
self.timeout: int = timeout
|
|
81
|
+
self.lock: threading.RLock = threading.RLock() # 使用可重入锁避免死锁
|
|
82
|
+
self.cleanup_interval: int = cleanup_interval
|
|
83
|
+
self._stop_event: threading.Event = threading.Event()
|
|
84
|
+
self._wakeup_event: threading.Event = threading.Event()
|
|
85
|
+
self._cleanup_thread: Optional[threading.Thread] = None
|
|
86
|
+
self._start_cleanup_thread()
|
|
87
|
+
|
|
88
|
+
# 后台清理线程工作函数
|
|
89
|
+
def _cleanup_thread_worker(self) -> None:
|
|
90
|
+
while not self._stop_event.is_set():
|
|
91
|
+
# 计算下一个到期时间(在锁内读取状态)
|
|
92
|
+
with self.lock:
|
|
93
|
+
now = time.time()
|
|
94
|
+
expire_times = []
|
|
95
|
+
for v in self.cache.values():
|
|
96
|
+
t = v.get("timeout", self.timeout)
|
|
97
|
+
expire_times.append(v["last_used"] + t)
|
|
98
|
+
|
|
99
|
+
if expire_times:
|
|
100
|
+
next_expire = min(expire_times) - now
|
|
101
|
+
# 等待到下一个到期时间或 cleanup_interval(取较小者),至少等待 1 秒
|
|
102
|
+
wait_time = max(1, int(min(self.cleanup_interval, next_expire))) if next_expire > 0 else 0
|
|
103
|
+
else:
|
|
104
|
+
wait_time = self.cleanup_interval
|
|
105
|
+
|
|
106
|
+
# 如果已有到期项应立即清理(wait_time == 0),否则等待唤醒或超时
|
|
107
|
+
if wait_time == 0:
|
|
108
|
+
with self.lock:
|
|
109
|
+
removed = self._cleanup_expired()
|
|
110
|
+
if removed:
|
|
111
|
+
logger.debug(f"LRU cache periodic cleanup removed {removed} items")
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
# 等待:被唤醒(如 set() 调用)或超时到达
|
|
115
|
+
fired = self._wakeup_event.wait(timeout=wait_time)
|
|
116
|
+
# 清除唤醒标志以便下次重新计算
|
|
117
|
+
self._wakeup_event.clear()
|
|
118
|
+
# 循环回到开头重新计算或检查停止条件
|
|
119
|
+
|
|
120
|
+
# 启动后台清理线程
|
|
121
|
+
def _start_cleanup_thread(self) -> None:
|
|
122
|
+
if self._cleanup_thread is None or not self._cleanup_thread.is_alive():
|
|
123
|
+
self._stop_event.clear()
|
|
124
|
+
self._cleanup_thread = threading.Thread(target=self._cleanup_thread_worker, daemon=True)
|
|
125
|
+
self._cleanup_thread.start()
|
|
126
|
+
|
|
127
|
+
# 停止后台清理线程
|
|
128
|
+
def _stop_cleanup_thread(self) -> None:
|
|
129
|
+
if self._cleanup_thread and self._cleanup_thread.is_alive():
|
|
130
|
+
self._stop_event.set()
|
|
131
|
+
self._cleanup_thread.join(timeout=5)
|
|
132
|
+
|
|
133
|
+
# 清理过期缓存
|
|
134
|
+
def _cleanup_expired(self) -> int:
|
|
135
|
+
now = time.time()
|
|
136
|
+
keys_to_remove = [key for key, value in self.cache.items() if now - value["last_used"] > value.get("timeout", self.timeout)]
|
|
137
|
+
for key in keys_to_remove:
|
|
138
|
+
del self.cache[key]
|
|
139
|
+
return len(keys_to_remove)
|
|
140
|
+
|
|
141
|
+
# 清理超出大小
|
|
142
|
+
def _cleanup_size(self) -> None:
|
|
143
|
+
# 先清理过期项
|
|
144
|
+
self._cleanup_expired()
|
|
145
|
+
# 如果仍超出大小限制,删除使用次数最少的项
|
|
146
|
+
while len(self.cache) > self.max_size:
|
|
147
|
+
# 使用 min 的 key 参数找到使用次数最少的项
|
|
148
|
+
least_used_key = min(self.cache.keys(), key=lambda k: (self.cache[k]["usage"], self.cache[k]["last_used"]))
|
|
149
|
+
del self.cache[least_used_key]
|
|
150
|
+
|
|
151
|
+
# 获取缓存项
|
|
152
|
+
def get(self, key: str) -> Optional[Any]:
|
|
153
|
+
with self.lock:
|
|
154
|
+
if key in self.cache:
|
|
155
|
+
now = time.time()
|
|
156
|
+
if now - self.cache[key]["last_used"] > self.cache[key].get("timeout", self.timeout):
|
|
157
|
+
del self.cache[key]
|
|
158
|
+
return None
|
|
159
|
+
self.cache[key]["last_used"] = now
|
|
160
|
+
self.cache[key]["usage"] += 1
|
|
161
|
+
return self.cache[key]["obj"]
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
# 设置缓存项
|
|
165
|
+
def set(self, key: str, obj: Any, timeout: Optional[int] = None) -> None:
|
|
166
|
+
with self.lock:
|
|
167
|
+
# 移动到末尾
|
|
168
|
+
if key in self.cache:
|
|
169
|
+
self.cache.move_to_end(key)
|
|
170
|
+
# 设置缓存
|
|
171
|
+
self.cache[key] = {
|
|
172
|
+
"obj": obj,
|
|
173
|
+
"last_used": time.time(),
|
|
174
|
+
"usage": 1,
|
|
175
|
+
"timeout": timeout or self.timeout,
|
|
176
|
+
}
|
|
177
|
+
# 超出大小限制时清理
|
|
178
|
+
self._cleanup_size()
|
|
179
|
+
|
|
180
|
+
# 获取缓存大小
|
|
181
|
+
def size(self) -> int:
|
|
182
|
+
with self.lock:
|
|
183
|
+
return len(self.cache)
|
|
184
|
+
|
|
185
|
+
# 获取缓存状态
|
|
186
|
+
def status(self, print_log: bool = False) -> Dict[str, Dict[str, Any]]:
|
|
187
|
+
with self.lock:
|
|
188
|
+
result = {
|
|
189
|
+
key: {
|
|
190
|
+
"last_used": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(value["last_used"])),
|
|
191
|
+
"usage": value["usage"],
|
|
192
|
+
"timeout": value.get("timeout", self.timeout),
|
|
193
|
+
}
|
|
194
|
+
for key, value in self.cache.items()
|
|
195
|
+
}
|
|
196
|
+
if print_log:
|
|
197
|
+
logger.info(f"LRU Cache Status: {result}")
|
|
198
|
+
return result
|
|
199
|
+
|
|
200
|
+
# 检查缓存项是否存在
|
|
201
|
+
def exists(self, key: str) -> bool:
|
|
202
|
+
with self.lock:
|
|
203
|
+
return key in self.cache
|
|
204
|
+
|
|
205
|
+
# 删除指定
|
|
206
|
+
def delete(self, key: str) -> bool:
|
|
207
|
+
with self.lock:
|
|
208
|
+
if key in self.cache:
|
|
209
|
+
del self.cache[key]
|
|
210
|
+
return True
|
|
211
|
+
return False
|
|
212
|
+
|
|
213
|
+
# 清理过期和超出限制的缓存项
|
|
214
|
+
def clear(self) -> int:
|
|
215
|
+
with self.lock:
|
|
216
|
+
return self._cleanup_expired()
|
|
217
|
+
|
|
218
|
+
# 清空所有缓存
|
|
219
|
+
def clear_all(self) -> None:
|
|
220
|
+
with self.lock:
|
|
221
|
+
self.cache.clear()
|
|
222
|
+
|
|
223
|
+
# 析构函数,确保线程正确关闭
|
|
224
|
+
def __del__(self) -> None:
|
|
225
|
+
"""析构时停止后台清理线程"""
|
|
226
|
+
self._stop_cleanup_thread()
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# LRU 缓存单例
|
|
230
|
+
_LRU_INSTANCE: Optional[LruCache] = None
|
|
231
|
+
_LRU_LOCK: threading.Lock = threading.Lock()
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
# 获取 LRU 缓存单例
|
|
235
|
+
def get_lru_cache(max_size: int = 100, timeout: int = 3600, clean_interval: int = 1800) -> LruCache:
|
|
236
|
+
global _LRU_INSTANCE
|
|
237
|
+
if _LRU_INSTANCE is None:
|
|
238
|
+
with _LRU_LOCK:
|
|
239
|
+
if _LRU_INSTANCE is None:
|
|
240
|
+
try:
|
|
241
|
+
_LRU_INSTANCE = LruCache(max_size=max_size, timeout=timeout, cleanup_interval=clean_interval)
|
|
242
|
+
except (AttributeError, TypeError) as e:
|
|
243
|
+
logger.warning(f"Failed to load cache config, using defaults: {e}")
|
|
244
|
+
_LRU_INSTANCE = LruCache()
|
|
245
|
+
return _LRU_INSTANCE
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# ================================ 请求缓存会话 ================================
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
_REQUEST_SESSION: Optional[requests_cache.CachedSession] = None
|
|
252
|
+
_REQUEST_LOCK: threading.Lock = threading.Lock()
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# 获取带缓存的请求会话
|
|
256
|
+
def get_request_session(cache_name: requests_cache.StrOrPath = "cache", timeout: int = 3600) -> requests_cache.CachedSession:
|
|
257
|
+
global _REQUEST_SESSION
|
|
258
|
+
if _REQUEST_SESSION is None:
|
|
259
|
+
with _REQUEST_LOCK:
|
|
260
|
+
if _REQUEST_SESSION is None:
|
|
261
|
+
try:
|
|
262
|
+
timeout = timeout
|
|
263
|
+
except (AttributeError, TypeError) as e:
|
|
264
|
+
logger.warning(f"Failed to load cache config, using default timeout: {e}")
|
|
265
|
+
timeout = 3600
|
|
266
|
+
_REQUEST_SESSION = requests_cache.CachedSession(
|
|
267
|
+
cache_name=cache_name,
|
|
268
|
+
backend="filesystem",
|
|
269
|
+
expire_after=timedelta(seconds=timeout),
|
|
270
|
+
ignored_parameters=[
|
|
271
|
+
"Authorization",
|
|
272
|
+
"X-API-KEY",
|
|
273
|
+
"access_token",
|
|
274
|
+
"key",
|
|
275
|
+
"api_key",
|
|
276
|
+
"trustedclienttoken",
|
|
277
|
+
"Ocp-Apim-Subscription-Key",
|
|
278
|
+
],
|
|
279
|
+
)
|
|
280
|
+
return _REQUEST_SESSION
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# ================================ 调用示例 ================================
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
if __name__ == "__main__":
|
|
287
|
+
|
|
288
|
+
# ==================== KV 键值存储示例 ====================
|
|
289
|
+
|
|
290
|
+
# 设置键值
|
|
291
|
+
# kv.set("username", "Alice")
|
|
292
|
+
# print(kv.get("username")) # 输出: Alice
|
|
293
|
+
|
|
294
|
+
# 设置多个键值
|
|
295
|
+
# kv.set("age", 25)
|
|
296
|
+
# print(kv.get_all()) # 输出: {'username': 'Alice', 'age': 25}
|
|
297
|
+
|
|
298
|
+
# 检查键是否存在
|
|
299
|
+
# print(kv.exists("username")) # 输出: True
|
|
300
|
+
|
|
301
|
+
# 获取存储大小
|
|
302
|
+
# print(kv.size()) # 输出: 2
|
|
303
|
+
|
|
304
|
+
# 删除键值
|
|
305
|
+
# kv.delete("username")
|
|
306
|
+
# print(kv.get("username")) # 输出: None
|
|
307
|
+
|
|
308
|
+
# 清空所有键值
|
|
309
|
+
# kv.clear()
|
|
310
|
+
# print(kv.get_all()) # 输出: {}
|
|
311
|
+
|
|
312
|
+
# ==================== LRU 缓存示例 ====================
|
|
313
|
+
|
|
314
|
+
# 获取 LRU 缓存实例
|
|
315
|
+
# cache = get_lru_cache()
|
|
316
|
+
|
|
317
|
+
# 设置缓存
|
|
318
|
+
# cache.set("key1", {"data": "value1"})
|
|
319
|
+
# cache.set("key2", {"data": "value2"}, timeout=60) # 自定义超时时间
|
|
320
|
+
|
|
321
|
+
# 获取缓存
|
|
322
|
+
# result = cache.get("key1")
|
|
323
|
+
# print(result) # 输出: {'data': 'value1'}
|
|
324
|
+
|
|
325
|
+
# 检查缓存是否存在
|
|
326
|
+
# print(cache.exists("key1")) # 输出: True
|
|
327
|
+
|
|
328
|
+
# 获取缓存状态
|
|
329
|
+
# cache.status(print_log=True)
|
|
330
|
+
|
|
331
|
+
# 删除缓存
|
|
332
|
+
# cache.delete("key1")
|
|
333
|
+
|
|
334
|
+
# 清理过期缓存
|
|
335
|
+
# removed_count = cache.clear()
|
|
336
|
+
# print(f"Removed {removed_count} expired items")
|
|
337
|
+
|
|
338
|
+
# 清空所有缓存
|
|
339
|
+
# cache.clear_all()
|
|
340
|
+
|
|
341
|
+
# ==================== 请求缓存会话示例 ====================
|
|
342
|
+
|
|
343
|
+
# 获取带缓存的请求会话
|
|
344
|
+
# session = get_request_session()
|
|
345
|
+
|
|
346
|
+
# 发送请求(会自动缓存)
|
|
347
|
+
# URL = "https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list"
|
|
348
|
+
# params = {"trustedclienttoken": "6A5AA1D4EAFF4E9FB37E23D68491D6F4"}
|
|
349
|
+
# response = session.get(URL, params=params, timeout=30, verify=False)
|
|
350
|
+
# print(response.text)
|
|
351
|
+
|
|
352
|
+
pass
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
sys.path.insert(0, os.getcwd())
|
|
5
|
+
|
|
6
|
+
import platform
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from functools import wraps
|
|
9
|
+
from typing import Any, Callable, Optional, Type, TypeVar
|
|
10
|
+
|
|
11
|
+
from loguru import logger
|
|
12
|
+
|
|
13
|
+
E = TypeVar("E", bound=Enum)
|
|
14
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# 基类
|
|
18
|
+
class AbstractEnum:
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def from_value(cls: Type[E], val: str) -> E:
|
|
22
|
+
for member in cls:
|
|
23
|
+
if member.value == val:
|
|
24
|
+
return member
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def from_name(cls: Type[E], name: str) -> E:
|
|
29
|
+
try:
|
|
30
|
+
return cls[name]
|
|
31
|
+
except KeyError:
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def choices(cls):
|
|
36
|
+
return [(member.name, member.value) for member in cls]
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def names(cls):
|
|
40
|
+
return [member.name for member in cls]
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def values(cls):
|
|
44
|
+
return [member.value for member in cls]
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def has_name(cls, name):
|
|
48
|
+
return name in cls.__members__
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def has_value(cls, val):
|
|
52
|
+
return val in cls._value2member_map_
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# 基础
|
|
56
|
+
class BaseAbstractEnum:
|
|
57
|
+
|
|
58
|
+
# 代理协议
|
|
59
|
+
class Protocol(AbstractEnum, Enum):
|
|
60
|
+
HTTP = "http"
|
|
61
|
+
SOCKS5 = "socks5"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# 平台
|
|
65
|
+
class Platform(AbstractEnum, Enum):
|
|
66
|
+
Window = "windows"
|
|
67
|
+
Linux = "linux"
|
|
68
|
+
Mac = "darwin"
|
|
69
|
+
|
|
70
|
+
# 指定平台装饰器,限制函数只在特定操作系统上执行
|
|
71
|
+
def system(*valid_os, enabled: bool = True) -> Callable[[F], F]:
|
|
72
|
+
|
|
73
|
+
# 装饰器
|
|
74
|
+
def decorator(func: F) -> F:
|
|
75
|
+
|
|
76
|
+
@wraps(func)
|
|
77
|
+
def wrapper(*args, **kwargs) -> Optional[Any]:
|
|
78
|
+
# 获取当前操作系统名称并转为小写
|
|
79
|
+
current_os = platform.system().lower()
|
|
80
|
+
# 转换 valid_os 为小写列表
|
|
81
|
+
os_list = [os_name.value for os_name in valid_os]
|
|
82
|
+
# 如果当前操作系统符合要求,执行函数
|
|
83
|
+
if current_os in os_list:
|
|
84
|
+
return func(*args, **kwargs)
|
|
85
|
+
else:
|
|
86
|
+
if enabled:
|
|
87
|
+
logger.info(f"Function {func.__name__} is not supported on {platform.system()}. Skipping.")
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
return wrapper
|
|
91
|
+
|
|
92
|
+
return decorator
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
print(Platform.from_name(Platform.Window.name))
|
|
97
|
+
print(Platform.from_value("windows"))
|
|
98
|
+
print(Platform.choices())
|
|
99
|
+
print(Platform.names())
|
|
100
|
+
print(Platform.values())
|
|
101
|
+
print(Platform.has_value("windows"))
|
|
102
|
+
print(Platform.has_value("windows2"))
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
sys.path.insert(0, os.getcwd())
|
|
5
|
+
|
|
6
|
+
from enum import IntEnum
|
|
7
|
+
from typing import Any, Generic, TypeVar
|
|
8
|
+
|
|
9
|
+
from loguru import logger
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
MAX_LOG_LENGTH = 500
|
|
13
|
+
LIST_PREVIEW = 3
|
|
14
|
+
DICT_PREVIEW = 5
|
|
15
|
+
|
|
16
|
+
T = TypeVar("T")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# 响应码类型
|
|
20
|
+
class ResponseCode(IntEnum):
|
|
21
|
+
SUCCESS = 0 # 成功
|
|
22
|
+
FAILURE = 1 # 失败
|
|
23
|
+
ERROR = -1 # 错误
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# 通用响应模型
|
|
27
|
+
class ResponseModel(BaseModel, Generic[T]):
|
|
28
|
+
code: int
|
|
29
|
+
message: str
|
|
30
|
+
data: T
|
|
31
|
+
|
|
32
|
+
# 将数据转换为 JSON 字符串,并对列表或字典进行预览截断
|
|
33
|
+
@staticmethod
|
|
34
|
+
def _json_preview(data: Any, max_length: int = MAX_LOG_LENGTH) -> str:
|
|
35
|
+
from pykitool.utils import cbutils
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
if isinstance(data, dict):
|
|
39
|
+
preview_dict = dict(list(data.items())[:DICT_PREVIEW])
|
|
40
|
+
json_str = cbutils.to_json(preview_dict)
|
|
41
|
+
elif isinstance(data, list):
|
|
42
|
+
preview_list = data[:LIST_PREVIEW]
|
|
43
|
+
json_str = cbutils.to_json(preview_list)
|
|
44
|
+
else:
|
|
45
|
+
json_str = str(data)
|
|
46
|
+
|
|
47
|
+
# 全局截断
|
|
48
|
+
if len(json_str) > max_length:
|
|
49
|
+
json_str = json_str[:max_length] + " ... [truncated]"
|
|
50
|
+
|
|
51
|
+
return json_str
|
|
52
|
+
except Exception as e:
|
|
53
|
+
return f"<Failed to serialize data: {e}>"
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def success(code: int = 0, message: str = "success", data: Any = {}) -> "ResponseModel":
|
|
57
|
+
response = ResponseModel(code=code, message=message, data=data)
|
|
58
|
+
if data is not {}:
|
|
59
|
+
logger.info(response._json_preview(data))
|
|
60
|
+
return response
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def failure(code: int = 1, message: str = "failure", data: Any = {}) -> "ResponseModel":
|
|
64
|
+
response = ResponseModel(code=code, message=message, data=data)
|
|
65
|
+
if data is not {}:
|
|
66
|
+
logger.info(response._json_preview(data))
|
|
67
|
+
return response
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def error(code: int = -1, message: str = "error", data: Any = {}) -> "ResponseModel":
|
|
71
|
+
response = ResponseModel(code=code, message=message, data=data)
|
|
72
|
+
if data is not {}:
|
|
73
|
+
logger.info(response._json_preview(data))
|
|
74
|
+
return response
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# 通用分页响应模型
|
|
78
|
+
class ResponsePageModel(BaseModel, Generic[T]):
|
|
79
|
+
code: int = 0
|
|
80
|
+
msg: str = ""
|
|
81
|
+
count: int = 0
|
|
82
|
+
data: list[T] = []
|