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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,423 @@
1
+ """
2
+ 装饰器工具集,包含函数调用信息记录、弃用标记、重试机制和频率限制等功能。
3
+ """
4
+
5
+ import functools
6
+ import warnings
7
+ import inspect
8
+ import time
9
+ import logging
10
+ import sys
11
+ import threading
12
+ import asyncio
13
+ from typing import Optional, Callable, Any, TypeVar, Union, Tuple
14
+ from pathlib import Path
15
+
16
+ T = TypeVar('T')
17
+ FuncType = Callable[..., T]
18
+ F = TypeVar('F', bound=FuncType[Any])
19
+
20
+
21
+ def info(
22
+ func: Optional[F] = None,
23
+ *,
24
+ show_args: bool = False,
25
+ show_kwargs: bool = False,
26
+ show_return: bool = False,
27
+ show_time: bool = False,
28
+ log_file: Optional[Union[str, Path]] = None,
29
+ indent: int = 0,
30
+ log_level: int = logging.INFO,
31
+ ) -> Union[F, Callable[[F], F]]:
32
+ """
33
+ 装饰器:输出函数的详细调用信息
34
+
35
+ 参数:
36
+ func: 被装饰的函数(自动传入,无需手动指定)
37
+ show_args: 是否显示位置参数,默认False
38
+ show_kwargs: 是否显示关键字参数,默认False
39
+ show_return: 是否显示返回值,默认False
40
+ show_time: 是否显示执行时间,默认False
41
+ log_file: 日志文件路径,不指定则输出到stdout
42
+ indent: 缩进空格数,用于嵌套调用时格式化输出
43
+ log_level: 日志级别,默认为logging.INFO
44
+
45
+ 返回:
46
+ 包装后的函数,调用时会输出详细信息
47
+ """
48
+ def setup_logger() -> logging.Logger:
49
+ """配置并返回logger实例"""
50
+ logger = logging.getLogger(f"func_logger.{indent}")
51
+ if logger.handlers:
52
+ return logger
53
+
54
+ logger.setLevel(log_level)
55
+ formatter = logging.Formatter(
56
+ "%(asctime)s - %(levelname)s - %(message)s",
57
+ datefmt="%Y-%m-%d %H:%M:%S"
58
+ )
59
+
60
+ if log_file:
61
+ handler = logging.FileHandler(log_file, encoding='utf-8')
62
+ else:
63
+ handler = logging.StreamHandler(sys.stdout)
64
+
65
+ handler.setFormatter(formatter)
66
+ logger.addHandler(handler)
67
+ logger.propagate = False
68
+ return logger
69
+
70
+ def decorator(f: F) -> F:
71
+ @functools.wraps(f)
72
+ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
73
+ logger = setup_logger()
74
+ indent_str = ' ' * indent
75
+ sig = inspect.signature(f)
76
+
77
+ log_lines = [f"{indent_str}┌── 调用函数: {f.__name__}"]
78
+
79
+ if show_args and args:
80
+ bound_args = sig.bind(*args, **kwargs)
81
+ bound_args.apply_defaults()
82
+ arg_details = [
83
+ f"{name}={repr(value)}"
84
+ for name, value in bound_args.arguments.items()
85
+ if name in sig.parameters and
86
+ name not in kwargs and
87
+ (len(args) > list(sig.parameters.keys()).index(name))
88
+ ]
89
+ if arg_details:
90
+ log_lines.append(f"{indent_str}├── 位置参数: {', '.join(arg_details)}")
91
+
92
+ if show_kwargs and kwargs:
93
+ kwargs_details = [f"{k}={repr(v)}" for k, v in kwargs.items()]
94
+ log_lines.append(f"{indent_str}├── 关键字参数: {', '.join(kwargs_details)}")
95
+
96
+ start_time = time.perf_counter()
97
+ result = f(*args, **kwargs)
98
+ elapsed = time.perf_counter() - start_time
99
+
100
+ if show_return:
101
+ log_lines.append(f"{indent_str}├── 返回值: {repr(result)}")
102
+
103
+ if show_time:
104
+ log_lines.append(f"{indent_str}└── 执行时间: {elapsed:.6f}秒")
105
+ else:
106
+ log_lines[-1] = log_lines[-1].replace('├──', '└──')
107
+
108
+ logger.log(log_level, "\n".join(log_lines))
109
+ return result
110
+
111
+ @functools.wraps(f)
112
+ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
113
+ logger = setup_logger()
114
+ indent_str = ' ' * indent
115
+ sig = inspect.signature(f)
116
+
117
+ log_lines = [f"{indent_str}┌── 调用异步函数: {f.__name__}"]
118
+
119
+ if show_args and args:
120
+ bound_args = sig.bind(*args, **kwargs)
121
+ bound_args.apply_defaults()
122
+ arg_details = [
123
+ f"{name}={repr(value)}"
124
+ for name, value in bound_args.arguments.items()
125
+ if name in sig.parameters and
126
+ name not in kwargs and
127
+ (len(args) > list(sig.parameters.keys()).index(name))
128
+ ]
129
+ if arg_details:
130
+ log_lines.append(f"{indent_str}├── 位置参数: {', '.join(arg_details)}")
131
+
132
+ if show_kwargs and kwargs:
133
+ kwargs_details = [f"{k}={repr(v)}" for k, v in kwargs.items()]
134
+ log_lines.append(f"{indent_str}├── 关键字参数: {', '.join(kwargs_details)}")
135
+
136
+ start_time = time.perf_counter()
137
+ result = await f(*args, **kwargs)
138
+ elapsed = time.perf_counter() - start_time
139
+
140
+ if show_return:
141
+ log_lines.append(f"{indent_str}├── 返回值: {repr(result)}")
142
+
143
+ if show_time:
144
+ log_lines.append(f"{indent_str}└── 执行时间: {elapsed:.6f}秒")
145
+ else:
146
+ log_lines[-1] = log_lines[-1].replace('├──', '└──')
147
+
148
+ logger.log(log_level, "\n".join(log_lines))
149
+ return result
150
+
151
+ return async_wrapper if inspect.iscoroutinefunction(f) else sync_wrapper
152
+
153
+ if func is None:
154
+ return decorator
155
+ else:
156
+ return decorator(func)
157
+
158
+
159
+ def deprecated_func(
160
+ func: Optional[F] = None,
161
+ *,
162
+ reason: Optional[str] = None,
163
+ version: Optional[str] = None,
164
+ alternative: Optional[str] = None,
165
+ since: Optional[str] = None,
166
+ ) -> Union[F, Callable[[F], F]]:
167
+ """
168
+ 装饰器:标记函数已弃用,并在调用时发出警告
169
+
170
+ 参数:
171
+ func: 被装饰的函数(自动传入,无需手动指定)
172
+ reason: 弃用的原因说明
173
+ version: 计划移除的版本号
174
+ alternative: 推荐的替代函数或方法
175
+ since: 从哪个版本开始弃用
176
+
177
+ 返回:
178
+ 包装后的函数,调用时会发出弃用警告
179
+ """
180
+ def decorator(f: F) -> F:
181
+ @functools.wraps(f)
182
+ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
183
+ message_parts = [f"函数 {f.__name__} 已被弃用"]
184
+
185
+ if since:
186
+ message_parts.append(f"(自版本 {since})")
187
+
188
+ if reason:
189
+ message_parts.append(f",原因: {reason}")
190
+
191
+ if version:
192
+ message_parts.append(f",将在 {version} 版本中移除")
193
+
194
+ if alternative:
195
+ message_parts.append(f",请使用 {alternative} 替代")
196
+
197
+ message = "".join(message_parts)
198
+ warnings.warn(message, category=DeprecationWarning, stacklevel=2)
199
+ return f(*args, **kwargs)
200
+
201
+ @functools.wraps(f)
202
+ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
203
+ message = (
204
+ f"异步函数 {f.__name__} 已被弃用"
205
+ + (f"(自版本 {since})" if since else "")
206
+ + (f",原因: {reason}" if reason else "")
207
+ + (f",将在 {version} 版本中移除" if version else "")
208
+ + (f",请使用 {alternative} 替代" if alternative else "")
209
+ )
210
+ warnings.warn(message, category=DeprecationWarning, stacklevel=2)
211
+ return await f(*args, **kwargs)
212
+
213
+ wrapper = async_wrapper if inspect.iscoroutinefunction(f) else sync_wrapper
214
+
215
+ wrapper._is_deprecated = True # type: ignore
216
+ wrapper._deprecation_info = { # type: ignore
217
+ 'reason': reason,
218
+ 'version': version,
219
+ 'alternative': alternative,
220
+ 'since': since,
221
+ }
222
+
223
+ return wrapper # type: ignore
224
+
225
+ if func is None:
226
+ return decorator
227
+ else:
228
+ return decorator(func)
229
+
230
+
231
+ def retry(
232
+ max_attempts: int = 3,
233
+ delay: float = 1.0,
234
+ backoff: float = 2.0,
235
+ exceptions: Union[Tuple[Exception], Exception] = Exception,
236
+ logger: Optional[logging.Logger] = None,
237
+ ):
238
+ """
239
+ 装饰器:函数执行失败时自动重试
240
+
241
+ 参数:
242
+ max_attempts: 最大尝试次数 (默认: 3)
243
+ delay: 初始延迟时间(秒) (默认: 1.0)
244
+ backoff: 延迟时间倍增系数 (默认: 2.0)
245
+ exceptions: 需要捕获的异常类型 (默认: Exception)
246
+ logger: 自定义logger (默认: 使用print)
247
+
248
+ 返回:
249
+ 包装后的函数
250
+ """
251
+ def decorator(f: F) -> F:
252
+ @functools.wraps(f)
253
+ def sync_wrapper(*args, **kwargs):
254
+ current_delay = delay
255
+ last_exception = None
256
+
257
+ for attempt in range(1, max_attempts + 1):
258
+ try:
259
+ return f(*args, **kwargs)
260
+ except exceptions as e:
261
+ last_exception = e
262
+ if attempt == max_attempts:
263
+ break
264
+
265
+ msg = (f"函数 {f.__name__} 第 {attempt} 次失败,"
266
+ f"{current_delay:.1f}秒后重试... 错误: {str(e)}")
267
+ if logger:
268
+ logger.warning(msg)
269
+ else:
270
+ print(msg)
271
+
272
+ time.sleep(current_delay)
273
+ current_delay *= backoff
274
+
275
+ raise RuntimeError(f"函数 {f.__name__} 在 {max_attempts} 次尝试后仍失败") from last_exception
276
+
277
+ @functools.wraps(f)
278
+ async def async_wrapper(*args, **kwargs):
279
+ current_delay = delay
280
+ last_exception = None
281
+
282
+ for attempt in range(1, max_attempts + 1):
283
+ try:
284
+ return await f(*args, **kwargs)
285
+ except exceptions as e:
286
+ last_exception = e
287
+ if attempt == max_attempts:
288
+ break
289
+
290
+ msg = (f"异步函数 {f.__name__} 第 {attempt} 次失败,"
291
+ f"{current_delay:.1f}秒后重试... 错误: {str(e)}")
292
+ if logger:
293
+ logger.warning(msg)
294
+ else:
295
+ print(msg)
296
+
297
+ await asyncio.sleep(current_delay)
298
+ current_delay *= backoff
299
+
300
+ raise RuntimeError(f"异步函数 {f.__name__} 在 {max_attempts} 次尝试后仍失败") from last_exception
301
+
302
+ return async_wrapper if inspect.iscoroutinefunction(f) else sync_wrapper
303
+ return decorator
304
+
305
+
306
+ def rate_limited(
307
+ calls: int = 1,
308
+ period: float = 1.0,
309
+ raise_on_limit: bool = False
310
+ ):
311
+ """
312
+ 装饰器:限制函数调用频率
313
+
314
+ 参数:
315
+ calls: 时间段内允许的最大调用次数 (默认: 1)
316
+ period: 时间段的长度(秒) (默认: 1.0)
317
+ raise_on_limit: 超出限制时是否抛出异常 (默认: False, 会阻塞等待)
318
+
319
+ 返回:
320
+ 包装后的函数
321
+ """
322
+ def decorator(f: F) -> F:
323
+ call_times = []
324
+ lock = threading.Lock()
325
+
326
+ @functools.wraps(f)
327
+ def sync_wrapper(*args, **kwargs):
328
+ nonlocal call_times
329
+
330
+ with lock:
331
+ now = time.time()
332
+ call_times = [t for t in call_times if t > now - period]
333
+
334
+ if len(call_times) >= calls:
335
+ if raise_on_limit:
336
+ raise RuntimeError(f"调用频率限制: {calls}次/{period}秒")
337
+ else:
338
+ oldest = call_times[0]
339
+ wait_time = max(0, oldest + period - now)
340
+ if wait_time > 0:
341
+ time.sleep(wait_time)
342
+
343
+ call_times.append(time.time())
344
+ return f(*args, **kwargs)
345
+
346
+ @functools.wraps(f)
347
+ async def async_wrapper(*args, **kwargs):
348
+ nonlocal call_times
349
+
350
+ with lock:
351
+ now = time.time()
352
+ call_times = [t for t in call_times if t > now - period]
353
+
354
+ if len(call_times) >= calls:
355
+ if raise_on_limit:
356
+ raise RuntimeError(f"调用频率限制: {calls}次/{period}秒")
357
+ else:
358
+ oldest = call_times[0]
359
+ wait_time = max(0, oldest + period - now)
360
+ if wait_time > 0:
361
+ await asyncio.sleep(wait_time)
362
+
363
+ call_times.append(time.time())
364
+ return await f(*args, **kwargs)
365
+
366
+ return async_wrapper if inspect.iscoroutinefunction(f) else sync_wrapper
367
+ return decorator
368
+
369
+
370
+ def timeout(
371
+ seconds: float,
372
+ timeout_handler: Optional[Callable[[], Any]] = None,
373
+ exception: type = TimeoutError
374
+ ):
375
+ """
376
+ 装饰器:为函数添加执行超时限制
377
+
378
+ 参数:
379
+ seconds: 超时时间(秒)
380
+ timeout_handler: 超时时的处理函数 (默认: 抛出异常)
381
+ exception: 超时时抛出的异常类型 (默认: TimeoutError)
382
+
383
+ 返回:
384
+ 包装后的函数
385
+ """
386
+ def decorator(f: F) -> F:
387
+ @functools.wraps(f)
388
+ def sync_wrapper(*args, **kwargs):
389
+ result = None
390
+ timed_out = False
391
+
392
+ def worker():
393
+ nonlocal result
394
+ result = f(*args, **kwargs)
395
+
396
+ thread = threading.Thread(target=worker)
397
+ thread.start()
398
+ thread.join(timeout=seconds)
399
+
400
+ if thread.is_alive():
401
+ timed_out = True
402
+ thread.join(0.1)
403
+
404
+ if timed_out:
405
+ if timeout_handler is not None:
406
+ return timeout_handler()
407
+ raise exception(f"函数 {f.__name__} 执行超时 ({seconds}秒)")
408
+ return result
409
+
410
+ @functools.wraps(f)
411
+ async def async_wrapper(*args, **kwargs):
412
+ try:
413
+ return await asyncio.wait_for(
414
+ f(*args, **kwargs),
415
+ timeout=seconds
416
+ )
417
+ except asyncio.TimeoutError:
418
+ if timeout_handler is not None:
419
+ return timeout_handler()
420
+ raise exception(f"异步函数 {f.__name__} 执行超时 ({seconds}秒)")
421
+
422
+ return async_wrapper if inspect.iscoroutinefunction(f) else sync_wrapper
423
+ return decorator