pixelarraylib 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.
@@ -0,0 +1,373 @@
1
+ import traceback
2
+ from typing import List
3
+ import redis
4
+ import redis.asyncio as aioredis
5
+ from arraylib.monitor.feishu import Feishu
6
+
7
+ feishu_alert = Feishu("devtoolkit服务报警")
8
+
9
+
10
+ class RedisUtils:
11
+ def __init__(self, host, port, password, db):
12
+ self.redis_client = redis.Redis(
13
+ host=host,
14
+ port=port,
15
+ password=password,
16
+ db=db,
17
+ )
18
+
19
+ def get_redis_client(self):
20
+ return self.redis_client
21
+
22
+ def set(self, key, value, expire_seconds=None):
23
+ """
24
+ description:
25
+ 设置缓存
26
+ parameters:
27
+ key(str): 缓存键
28
+ value(str): 缓存值
29
+ expire_seconds(int): 缓存过期时间
30
+ return:
31
+ flag(bool): 是否设置成功
32
+ """
33
+ try:
34
+ self.redis_client.set(key, value, ex=expire_seconds)
35
+ return True
36
+ except Exception as e:
37
+ feishu_alert.send(traceback.format_exc())
38
+ return False
39
+
40
+ def get(self, key, default_value=""):
41
+ """
42
+ description:
43
+ 获取缓存
44
+ parameters:
45
+ key(str): 缓存键
46
+ default_value(str): 默认值
47
+ return:
48
+ value(str): 缓存值
49
+ """
50
+ try:
51
+ value = self.redis_client.get(key)
52
+ if value is None:
53
+ return default_value
54
+ return value.decode()
55
+ except Exception as e:
56
+ feishu_alert.send(traceback.format_exc())
57
+ return default_value
58
+
59
+ def delete(self, key):
60
+ """
61
+ description:
62
+ 删除缓存
63
+ parameters:
64
+ key(str): 缓存键
65
+ return:
66
+ flag(bool): 是否删除成功
67
+ """
68
+ try:
69
+ self.redis_client.delete(key)
70
+ return True
71
+ except Exception as e:
72
+ feishu_alert.send(traceback.format_exc())
73
+ return False
74
+
75
+ def delete_many(self, keys: List[str]) -> bool:
76
+ """
77
+ description:
78
+ 删除多个缓存
79
+ parameters:
80
+ keys(list): 缓存键列表
81
+ return:
82
+ flag(bool): 是否删除成功
83
+ """
84
+ try:
85
+ self.redis_client.delete(*keys)
86
+ return True
87
+ except Exception as e:
88
+ feishu_alert.send(traceback.format_exc())
89
+ return False
90
+
91
+ def set_hash(self, key, field, value, expire_seconds=None):
92
+ """
93
+ description:
94
+ 设置哈希表
95
+ parameters:
96
+ key(str): 哈希表键
97
+ field(str): 哈希表字段
98
+ value(str): 哈希表值
99
+ expire_seconds(int, optional): 哈希表过期时间(秒),默认为None表示不过期
100
+ return:
101
+ flag(bool): 是否设置成功
102
+ """
103
+ try:
104
+ self.redis_client.hset(key, field, value)
105
+ if expire_seconds is not None:
106
+ self.redis_client.expire(key, expire_seconds)
107
+ return True
108
+ except Exception as e:
109
+ feishu_alert.send(traceback.format_exc())
110
+ return False
111
+
112
+ def get_hash(self, key, field):
113
+ """
114
+ description:
115
+ 获取哈希表的值
116
+ parameters:
117
+ key(str): 哈希表键
118
+ field(str): 哈希表字段
119
+ return:
120
+ value(str): 哈希表值
121
+ """
122
+ try:
123
+ value = self.redis_client.hget(key, field)
124
+ if value is None:
125
+ return ""
126
+ return value.decode()
127
+ except Exception as e:
128
+ feishu_alert.send(traceback.format_exc())
129
+ return ""
130
+
131
+ def delete_hash(self, key, field):
132
+ """
133
+ description:
134
+ 删除哈希表的值
135
+ parameters:
136
+ key(str): 哈希表键
137
+ field(str): 哈希表字段
138
+ return:
139
+ flag(bool): 是否删除成功
140
+ """
141
+ try:
142
+ self.redis_client.hdel(key, field)
143
+ return True
144
+ except Exception as e:
145
+ feishu_alert.send(traceback.format_exc())
146
+ return False
147
+
148
+ def list_hash_keys(self, key):
149
+ """
150
+ description:
151
+ 获取哈希表的所有键
152
+ parameters:
153
+ key(str): 哈希表键
154
+ return:
155
+ keys(list): 哈希表的键
156
+ """
157
+ try:
158
+ return [key.decode() for key in self.redis_client.hkeys(key)]
159
+ except Exception as e:
160
+ feishu_alert.send(traceback.format_exc())
161
+ return []
162
+
163
+ def list_keys(self, prefix=""):
164
+ """
165
+ description:
166
+ 获取所有以指定前缀开头的键
167
+ parameters:
168
+ prefix(str): 键前缀,默认为空字符串,表示获取所有键
169
+ return:
170
+ keys(list): 键列表
171
+ """
172
+ try:
173
+ # 使用 Redis 的 scan 方法替代 keys,避免大数据量时阻塞
174
+ cursor = 0
175
+ keys = []
176
+ pattern = f"{prefix}*" if prefix else "*"
177
+ while True:
178
+ cursor, batch = self.redis_client.scan(
179
+ cursor=cursor, match=pattern, count=1000
180
+ )
181
+ keys.extend(batch)
182
+ if cursor == 0:
183
+ break
184
+ return [key.decode() for key in keys]
185
+ except Exception as e:
186
+ feishu_alert.send(traceback.format_exc())
187
+ return []
188
+
189
+ def __del__(self):
190
+ self.redis_client.close()
191
+
192
+
193
+ class RedisUtilsAsync:
194
+ def __init__(self, host, port, password, db):
195
+ self.async_redis_client = aioredis.from_url(
196
+ f"redis://:{password}@{host}:{port}/{db}"
197
+ )
198
+
199
+ async def get_async_redis_client(self):
200
+ return self.async_redis_client
201
+
202
+ async def set(self, key, value, expire_seconds=None):
203
+ """
204
+ description:
205
+ 异步设置缓存
206
+ parameters:
207
+ key(str): 缓存键
208
+ value(str): 缓存值
209
+ expire_seconds(int): 缓存过期时间
210
+ return:
211
+ flag(bool): 是否设置成功
212
+ """
213
+ try:
214
+ await self.async_redis_client.set(key, value, ex=expire_seconds)
215
+ return True
216
+ except Exception as e:
217
+ feishu_alert.send(traceback.format_exc())
218
+ return False
219
+
220
+ async def get(self, key, default_value=""):
221
+ """
222
+ description:
223
+ 异步获取缓存
224
+ parameters:
225
+ key(str): 缓存键
226
+ default_value(str): 默认值
227
+ return:
228
+ value(str): 缓存值
229
+ """
230
+ try:
231
+ value = await self.async_redis_client.get(key)
232
+ if value is None:
233
+ return default_value
234
+ return value.decode()
235
+ except Exception as e:
236
+ feishu_alert.send(traceback.format_exc())
237
+ return default_value
238
+
239
+ async def delete(self, key):
240
+ """
241
+ description:
242
+ 异步删除缓存
243
+ parameters:
244
+ key(str): 缓存键
245
+ return:
246
+ flag(bool): 是否删除成功
247
+ """
248
+ try:
249
+ await self.async_redis_client.delete(key)
250
+ return True
251
+ except Exception as e:
252
+ feishu_alert.send(traceback.format_exc())
253
+ return False
254
+
255
+ async def delete_many(self, keys: List[str]) -> bool:
256
+ """
257
+ description:
258
+ 异步删除多个缓存
259
+ parameters:
260
+ keys(list): 缓存键列表
261
+ return:
262
+ flag(bool): 是否删除成功
263
+ """
264
+ try:
265
+ await self.async_redis_client.delete(*keys)
266
+ return True
267
+ except Exception as e:
268
+ feishu_alert.send(traceback.format_exc())
269
+ return False
270
+
271
+ async def set_hash(self, key, field, value, expire_seconds=None):
272
+ """
273
+ description:
274
+ 异步设置哈希表
275
+ parameters:
276
+ key(str): 哈希表键
277
+ field(str): 哈希表字段
278
+ value(str): 哈希表值
279
+ expire_seconds(int, optional): 哈希表过期时间(秒),默认为None表示不过期
280
+ return:
281
+ flag(bool): 是否设置成功
282
+ """
283
+ try:
284
+ await self.async_redis_client.hset(key, field, value)
285
+ if expire_seconds is not None:
286
+ await self.async_redis_client.expire(key, expire_seconds)
287
+ return True
288
+ except Exception as e:
289
+ feishu_alert.send(traceback.format_exc())
290
+ return False
291
+
292
+ async def get_hash(self, key, field):
293
+ """
294
+ description:
295
+ 异步获取哈希表的值
296
+ parameters:
297
+ key(str): 哈希表键
298
+ field(str): 哈希表字段
299
+ return:
300
+ value(str): 哈希表值
301
+ """
302
+ try:
303
+ value = await self.async_redis_client.hget(key, field)
304
+ if value is None:
305
+ return ""
306
+ return value.decode()
307
+ except Exception as e:
308
+ feishu_alert.send(traceback.format_exc())
309
+ return ""
310
+
311
+ async def delete_hash(self, key, field):
312
+ """
313
+ description:
314
+ 异步删除哈希表的值
315
+ parameters:
316
+ key(str): 哈希表键
317
+ field(str): 哈希表字段
318
+ return:
319
+ flag(bool): 是否删除成功
320
+ """
321
+ try:
322
+ await self.async_redis_client.hdel(key, field)
323
+ return True
324
+ except Exception as e:
325
+ feishu_alert.send(traceback.format_exc())
326
+ return False
327
+
328
+ async def list_hash_keys(self, key):
329
+ """
330
+ description:
331
+ 异步获取哈希表的所有键
332
+ parameters:
333
+ key(str): 哈希表键
334
+ return:
335
+ keys(list): 哈希表的键
336
+ """
337
+ try:
338
+ keys = await self.async_redis_client.hkeys(key)
339
+ return [key.decode() for key in keys]
340
+ except Exception as e:
341
+ feishu_alert.send(traceback.format_exc())
342
+ return []
343
+
344
+ async def list_keys(self, prefix=""):
345
+ """
346
+ description:
347
+ 异步获取所有以指定前缀开头的键
348
+ parameters:
349
+ prefix(str): 键前缀,默认为空字符串,表示获取所有键
350
+ return:
351
+ keys(list): 键列表
352
+ """
353
+ try:
354
+ # 使用 Redis 的 scan 方法替代 keys,避免大数据量时阻塞
355
+ cursor = 0
356
+ keys = []
357
+ pattern = f"{prefix}*" if prefix else "*"
358
+ while True:
359
+ cursor, batch = await self.async_redis_client.scan(
360
+ cursor=cursor, match=pattern, count=1000
361
+ )
362
+ keys.extend(batch)
363
+ if cursor == 0:
364
+ break
365
+ return [key.decode() for key in keys]
366
+ except Exception as e:
367
+ feishu_alert.send(traceback.format_exc())
368
+ return []
369
+
370
+ async def close(self):
371
+ """关闭异步Redis连接"""
372
+ if self.async_redis_client:
373
+ await self.async_redis_client.close()
@@ -0,0 +1,13 @@
1
+ from arraylib.decorators.decorators import (
2
+ catch_exception,
3
+ with_retry,
4
+ with_timeout,
5
+ avoid_chinese_filename,
6
+ )
7
+
8
+ __all__ = [
9
+ "catch_exception",
10
+ "with_retry",
11
+ "with_timeout",
12
+ "avoid_chinese_filename",
13
+ ]
@@ -0,0 +1,194 @@
1
+ import functools
2
+ import os
3
+ import re
4
+ import shutil
5
+ import traceback
6
+ import asyncio
7
+ from typing import Callable, Any
8
+ from time import sleep
9
+ from functools import wraps
10
+ from concurrent.futures import ThreadPoolExecutor, TimeoutError
11
+ from arraylib.monitor.feishu import Feishu
12
+ import inspect
13
+
14
+
15
+ def catch_exception(
16
+ exception_return: Any = None,
17
+ alert_params: bool = True,
18
+ addtional_alert: str = "",
19
+ alert_channel: str = "devtoolkit服务报警",
20
+ ):
21
+ feishu_alert = Feishu(alert_channel)
22
+ def get_caller_function_name():
23
+ """
24
+ description:
25
+ 获取调用当前函数的函数名
26
+ return:
27
+ str: 函数名
28
+ """
29
+ return inspect.getframeinfo(inspect.currentframe().f_back.f_back).function
30
+
31
+ def decorator(func: Callable) -> Callable:
32
+ @wraps(func)
33
+ def sync_wrapper(*args, **kwargs) -> Any:
34
+ try:
35
+ return func(*args, **kwargs)
36
+ except Exception as e:
37
+ alert_params_str = f"参数:{args, kwargs}" if alert_params else ""
38
+ alert_message = f"文件{os.path.relpath(inspect.getfile(func))}中的函数{get_caller_function_name()}执行失败,{alert_params_str},错误信息如下:\n {traceback.format_exc()}"
39
+ if addtional_alert:
40
+ alert_message += f"\n{addtional_alert}"
41
+ feishu_alert.send(alert_message)
42
+ return exception_return
43
+
44
+ @wraps(func)
45
+ async def async_wrapper(*args, **kwargs) -> Any:
46
+ try:
47
+ return await func(*args, **kwargs)
48
+ except Exception as e:
49
+ alert_params_str = f"参数:{args, kwargs}" if alert_params else ""
50
+ alert_message = f"文件{os.path.relpath(inspect.getfile(func))}中的函数{get_caller_function_name()}执行失败,{alert_params_str},错误信息如下:\n {traceback.format_exc()}"
51
+ if addtional_alert:
52
+ alert_message += f"\n{addtional_alert}"
53
+ feishu_alert.send(alert_message)
54
+ return exception_return
55
+
56
+ if asyncio.iscoroutinefunction(func):
57
+ return async_wrapper
58
+ else:
59
+ return sync_wrapper
60
+
61
+ return decorator
62
+
63
+
64
+ def with_retry(
65
+ retry_times: int = 3,
66
+ retry_interval: int = 1,
67
+ ) -> Callable:
68
+ def decorator(func: Callable) -> Callable:
69
+ @functools.wraps(func)
70
+ def wrapper(*args, **kwargs) -> Any:
71
+ for attempt in range(retry_times):
72
+ try:
73
+ return func(*args, **kwargs)
74
+ except Exception as e:
75
+ if attempt < retry_times - 1:
76
+ print(f"第{attempt + 1}次尝试失败,{retry_interval}秒后重试...")
77
+ sleep(retry_interval)
78
+ else:
79
+ print(f"操作失败,已重试{retry_times}次,执行失败")
80
+ raise e
81
+
82
+ return wrapper
83
+
84
+ return decorator
85
+
86
+
87
+ def with_timeout(seconds):
88
+ def decorator(func):
89
+ @wraps(func)
90
+ def wrapper(*args, **kwargs):
91
+ with ThreadPoolExecutor(max_workers=1) as executor:
92
+ future = executor.submit(func, *args, **kwargs)
93
+ try:
94
+ return future.result(timeout=seconds)
95
+ except TimeoutError:
96
+ raise TimeoutError(
97
+ f"Function {func.__name__} timed out after {seconds} seconds"
98
+ )
99
+
100
+ return wrapper
101
+
102
+ return decorator
103
+
104
+
105
+ def avoid_chinese_filename(func):
106
+ def contains_chinese(text):
107
+ if not text:
108
+ return False
109
+ chinese_pattern = re.compile(r"[\u4e00-\u9fff]")
110
+ return bool(chinese_pattern.search(text))
111
+
112
+ def create_temp_path(original_path):
113
+ if not contains_chinese(original_path):
114
+ return original_path
115
+
116
+ dir_path = os.path.dirname(original_path)
117
+ filename = os.path.basename(original_path)
118
+ name, ext = os.path.splitext(filename)
119
+
120
+ import uuid
121
+
122
+ temp_name = f"temp_{uuid.uuid4().hex[:8]}{ext}"
123
+ temp_path = os.path.join(dir_path, temp_name)
124
+
125
+ try:
126
+ shutil.copy2(original_path, temp_path)
127
+ return temp_path
128
+ except Exception as e:
129
+ return original_path
130
+
131
+ def restore_original_files(path_mapping):
132
+ for temp_path, original_path in path_mapping.items():
133
+ try:
134
+ if os.path.exists(temp_path):
135
+ os.makedirs(os.path.dirname(original_path), exist_ok=True)
136
+
137
+ if not os.path.exists(original_path):
138
+ os.rename(temp_path, original_path)
139
+ else:
140
+ os.remove(original_path)
141
+ os.rename(temp_path, original_path)
142
+ except Exception as e:
143
+ pass
144
+
145
+ def cleanup_temp_files(temp_files):
146
+ for temp_path in temp_files:
147
+ try:
148
+ if os.path.exists(temp_path):
149
+ os.remove(temp_path)
150
+ except Exception as e:
151
+ pass
152
+
153
+ def wrapper(*args, **kwargs):
154
+ path_mapping = {}
155
+ temp_files = []
156
+
157
+ try:
158
+ new_args = list(args)
159
+ for i, arg in enumerate(args):
160
+ if isinstance(arg, str):
161
+ is_file_path = os.path.exists(arg)
162
+ if is_file_path:
163
+ temp_path = create_temp_path(arg)
164
+ if temp_path != arg:
165
+ path_mapping[temp_path] = arg
166
+ new_args[i] = temp_path
167
+ temp_files.append(temp_path)
168
+
169
+ new_kwargs = kwargs.copy()
170
+ for key, value in kwargs.items():
171
+ if isinstance(value, str):
172
+ if os.path.exists(value):
173
+ temp_path = create_temp_path(value)
174
+ if temp_path != value:
175
+ path_mapping[temp_path] = value
176
+ new_kwargs[key] = temp_path
177
+ temp_files.append(temp_path)
178
+
179
+ result = func(*new_args, **new_kwargs)
180
+
181
+ restore_original_files(path_mapping)
182
+
183
+ return result
184
+
185
+ except Exception as e:
186
+ try:
187
+ restore_original_files(path_mapping)
188
+ except:
189
+ pass
190
+ raise e
191
+ finally:
192
+ cleanup_temp_files(temp_files)
193
+
194
+ return wrapper
File without changes