funcguard 0.1.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.

Potentially problematic release.


This version of funcguard might be problematic. Click here for more details.

@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 tinycen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,156 @@
1
+ Metadata-Version: 2.1
2
+ Name: funcguard
3
+ Version: 0.1.1
4
+ Summary: A funcguard for Python.
5
+ Home-page: https://github.com/tinycen/funcguard
6
+ Author: tinycen
7
+ Author-email: sky_ruocen@qq.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+
15
+ # FuncGuard
16
+
17
+ FuncGuard是一个Python库,提供了函数执行超时控制和重试机制的实用工具。
18
+
19
+ ## 功能特点
20
+
21
+ - 函数执行超时控制
22
+ - 函数执行失败自动重试
23
+ - HTTP请求封装(支持自动重试)
24
+
25
+ ## 安装/升级
26
+
27
+ ```bash
28
+ pip install funcguard
29
+ ```
30
+
31
+ ```bash
32
+ pip install --upgrade funcguard
33
+ ```
34
+
35
+
36
+ ## 使用方法
37
+
38
+ ### 超时控制
39
+
40
+ 使用`timeout_handler`函数可以控制函数的执行时间,防止函数运行时间过长:
41
+
42
+ ```python
43
+ from funcguard.core import timeout_handler
44
+
45
+ def long_running_function():
46
+ # 模拟一个耗时操作
47
+ import time
48
+ time.sleep(10)
49
+ return "操作完成"
50
+
51
+ try:
52
+ # 设置超时时间为5秒
53
+ result = timeout_handler(long_running_function, execution_timeout=5)
54
+ print(result)
55
+ except TimeoutError as e:
56
+ print(f"捕获到超时错误: {e}")
57
+ ```
58
+
59
+ ### 重试机制
60
+
61
+ 使用`retry_function`函数可以在函数执行失败时自动重试:
62
+
63
+ ```python
64
+ from funcguard.core import retry_function
65
+
66
+ def unstable_function():
67
+ # 模拟一个可能失败的操作
68
+ import random
69
+ if random.random() < 0.7: # 70%的概率失败
70
+ raise Exception("随机错误")
71
+ return "操作成功"
72
+
73
+ try:
74
+ # 最多重试3次,每次执行超时时间为10秒
75
+ result = retry_function(unstable_function, max_retries=3, execute_timeout=10, task_name="测试任务")
76
+ print(result)
77
+ except Exception as e:
78
+ print(f"重试后仍然失败: {e}")
79
+ ```
80
+
81
+ ### HTTP请求
82
+
83
+ 使用`send_request`函数发送HTTP请求,支持自动重试:
84
+
85
+ ```python
86
+ from funcguard.tools import send_request
87
+
88
+ # 不使用重试
89
+ response = send_request(
90
+ method="GET",
91
+ url="https://api.example.com/data",
92
+ headers={"Content-Type": "application/json"},
93
+ timeout=30
94
+ )
95
+ print(response)
96
+
97
+ # 使用重试
98
+ response = send_request(
99
+ method="POST",
100
+ url="https://api.example.com/data",
101
+ headers={"Content-Type": "application/json"},
102
+ data={"key": "value"},
103
+ timeout=30,
104
+ auto_retry={
105
+ "task_name": "API请求",
106
+ "max_retries": 3,
107
+ "execute_timeout": 60
108
+ }
109
+ )
110
+ print(response)
111
+ ```
112
+
113
+ ## API文档
114
+
115
+ ### funcguard.core
116
+
117
+ #### timeout_handler(func, args=(), kwargs=None, execution_timeout=90)
118
+
119
+ - **参数**:
120
+ - `func`: 需要执行的目标函数
121
+ - `args`: 目标函数的位置参数,默认为空元组
122
+ - `kwargs`: 目标函数的关键字参数,默认为None
123
+ - `execution_timeout`: 函数执行的超时时间,单位为秒,默认为90秒
124
+ - **返回值**: 目标函数的返回值
125
+ - **异常**: `TimeoutError` - 当函数执行超过指定时间时抛出
126
+
127
+ #### retry_function(func, max_retries=5, execute_timeout=90, task_name="", *args, **kwargs)
128
+
129
+ - **参数**:
130
+ - `func`: 需要重试的函数
131
+ - `max_retries`: 最大重试次数,默认为5
132
+ - `execute_timeout`: 执行超时时间,默认为90秒
133
+ - `task_name`: 任务名称,用于打印日志
134
+ - `args`: func的位置参数
135
+ - `kwargs`: func的关键字参数
136
+ - **返回值**: func的返回值
137
+ - **异常**: 当重试次数用尽后仍然失败时,抛出最后一次的异常
138
+
139
+ ### funcguard.tools
140
+
141
+ #### send_request(method, url, headers, data=None, return_type="json", timeout=60, auto_retry=None)
142
+
143
+ - **参数**:
144
+ - `method`: HTTP方法(GET, POST等)
145
+ - `url`: 请求URL
146
+ - `headers`: 请求头
147
+ - `data`: 请求数据,默认为None
148
+ - `return_type`: 返回类型,可选"json"、"response"或"text",默认为"json"
149
+ - `timeout`: 请求超时时间,单位为秒,默认为60
150
+ - `auto_retry`: 自动重试配置,格式为`{"task_name": "", "max_retries": 5, "execute_timeout": 90}`,默认为None
151
+ - **返回值**: 根据return_type参数返回不同格式的响应数据
152
+ - **异常**: 当请求失败且重试次数用尽后,抛出相应的异常
153
+
154
+ ## 许可证
155
+
156
+ MIT License
@@ -0,0 +1,142 @@
1
+ # FuncGuard
2
+
3
+ FuncGuard是一个Python库,提供了函数执行超时控制和重试机制的实用工具。
4
+
5
+ ## 功能特点
6
+
7
+ - 函数执行超时控制
8
+ - 函数执行失败自动重试
9
+ - HTTP请求封装(支持自动重试)
10
+
11
+ ## 安装/升级
12
+
13
+ ```bash
14
+ pip install funcguard
15
+ ```
16
+
17
+ ```bash
18
+ pip install --upgrade funcguard
19
+ ```
20
+
21
+
22
+ ## 使用方法
23
+
24
+ ### 超时控制
25
+
26
+ 使用`timeout_handler`函数可以控制函数的执行时间,防止函数运行时间过长:
27
+
28
+ ```python
29
+ from funcguard.core import timeout_handler
30
+
31
+ def long_running_function():
32
+ # 模拟一个耗时操作
33
+ import time
34
+ time.sleep(10)
35
+ return "操作完成"
36
+
37
+ try:
38
+ # 设置超时时间为5秒
39
+ result = timeout_handler(long_running_function, execution_timeout=5)
40
+ print(result)
41
+ except TimeoutError as e:
42
+ print(f"捕获到超时错误: {e}")
43
+ ```
44
+
45
+ ### 重试机制
46
+
47
+ 使用`retry_function`函数可以在函数执行失败时自动重试:
48
+
49
+ ```python
50
+ from funcguard.core import retry_function
51
+
52
+ def unstable_function():
53
+ # 模拟一个可能失败的操作
54
+ import random
55
+ if random.random() < 0.7: # 70%的概率失败
56
+ raise Exception("随机错误")
57
+ return "操作成功"
58
+
59
+ try:
60
+ # 最多重试3次,每次执行超时时间为10秒
61
+ result = retry_function(unstable_function, max_retries=3, execute_timeout=10, task_name="测试任务")
62
+ print(result)
63
+ except Exception as e:
64
+ print(f"重试后仍然失败: {e}")
65
+ ```
66
+
67
+ ### HTTP请求
68
+
69
+ 使用`send_request`函数发送HTTP请求,支持自动重试:
70
+
71
+ ```python
72
+ from funcguard.tools import send_request
73
+
74
+ # 不使用重试
75
+ response = send_request(
76
+ method="GET",
77
+ url="https://api.example.com/data",
78
+ headers={"Content-Type": "application/json"},
79
+ timeout=30
80
+ )
81
+ print(response)
82
+
83
+ # 使用重试
84
+ response = send_request(
85
+ method="POST",
86
+ url="https://api.example.com/data",
87
+ headers={"Content-Type": "application/json"},
88
+ data={"key": "value"},
89
+ timeout=30,
90
+ auto_retry={
91
+ "task_name": "API请求",
92
+ "max_retries": 3,
93
+ "execute_timeout": 60
94
+ }
95
+ )
96
+ print(response)
97
+ ```
98
+
99
+ ## API文档
100
+
101
+ ### funcguard.core
102
+
103
+ #### timeout_handler(func, args=(), kwargs=None, execution_timeout=90)
104
+
105
+ - **参数**:
106
+ - `func`: 需要执行的目标函数
107
+ - `args`: 目标函数的位置参数,默认为空元组
108
+ - `kwargs`: 目标函数的关键字参数,默认为None
109
+ - `execution_timeout`: 函数执行的超时时间,单位为秒,默认为90秒
110
+ - **返回值**: 目标函数的返回值
111
+ - **异常**: `TimeoutError` - 当函数执行超过指定时间时抛出
112
+
113
+ #### retry_function(func, max_retries=5, execute_timeout=90, task_name="", *args, **kwargs)
114
+
115
+ - **参数**:
116
+ - `func`: 需要重试的函数
117
+ - `max_retries`: 最大重试次数,默认为5
118
+ - `execute_timeout`: 执行超时时间,默认为90秒
119
+ - `task_name`: 任务名称,用于打印日志
120
+ - `args`: func的位置参数
121
+ - `kwargs`: func的关键字参数
122
+ - **返回值**: func的返回值
123
+ - **异常**: 当重试次数用尽后仍然失败时,抛出最后一次的异常
124
+
125
+ ### funcguard.tools
126
+
127
+ #### send_request(method, url, headers, data=None, return_type="json", timeout=60, auto_retry=None)
128
+
129
+ - **参数**:
130
+ - `method`: HTTP方法(GET, POST等)
131
+ - `url`: 请求URL
132
+ - `headers`: 请求头
133
+ - `data`: 请求数据,默认为None
134
+ - `return_type`: 返回类型,可选"json"、"response"或"text",默认为"json"
135
+ - `timeout`: 请求超时时间,单位为秒,默认为60
136
+ - `auto_retry`: 自动重试配置,格式为`{"task_name": "", "max_retries": 5, "execute_timeout": 90}`,默认为None
137
+ - **返回值**: 根据return_type参数返回不同格式的响应数据
138
+ - **异常**: 当请求失败且重试次数用尽后,抛出相应的异常
139
+
140
+ ## 许可证
141
+
142
+ MIT License
@@ -0,0 +1,7 @@
1
+ from .core import timeout_handler, retry_function
2
+ from .tools import send_request
3
+
4
+ __author__ = "ruocen"
5
+
6
+ # 暴露主要接口
7
+ __all__ = ["timeout_handler", "retry_function","send_request"]
@@ -0,0 +1,81 @@
1
+ class FuncguardTimeoutError(Exception):
2
+ pass
3
+
4
+ import time
5
+ from concurrent.futures import ThreadPoolExecutor , TimeoutError
6
+
7
+ # 计算函数运行时间
8
+ def timeout_handler( func, args = (), kwargs = None, execution_timeout = 90 ):
9
+ """
10
+ 使用 ThreadPoolExecutor 实现超时控制。
11
+
12
+ :param func: 需要执行的目标函数
13
+ :param args: 目标函数的位置参数,默认为空元组
14
+ :param kwargs: 目标函数的关键字参数,默认为 None
15
+ :param execution_timeout: 函数执行的超时时间,单位为秒,默认为 90 秒
16
+ :return: 目标函数的返回值
17
+ """
18
+ if kwargs is None:
19
+ kwargs = { }
20
+
21
+ with ThreadPoolExecutor( max_workers = 1 ) as executor:
22
+ future = executor.submit( func, *args, **kwargs )
23
+ try:
24
+ return future.result( timeout = execution_timeout )
25
+ except TimeoutError:
26
+ error_message = f"TimeoutError:函数 {func.__name__} 执行时间超过 {execution_timeout} 秒"
27
+ # print( error_message )
28
+ raise FuncguardTimeoutError( error_message )
29
+
30
+
31
+ # 重试函数
32
+ def retry_function( func , max_retries = 5 , execute_timeout = 90 , task_name = "" , *args , **kwargs ) :
33
+ """
34
+ 重试函数的通用封装。
35
+ :param func: 需要重试的函数
36
+ :param max_retries: 最大重试次数
37
+ :param execute_timeout: 执行超时时间
38
+ :param task_name: 任务名称,用于打印日志
39
+ :param args: func的位置参数
40
+ :param kwargs: func的关键字参数
41
+ :return: func的返回值
42
+ """
43
+ retry_count = 0
44
+ current_timeout = execute_timeout # 初始化当前超时时间为传入的execute_timeout
45
+
46
+ # 检查原始kwargs中是否包含timeout参数
47
+ original_timeout = kwargs.get( 'timeout' , 0 )
48
+
49
+ if current_timeout < original_timeout :
50
+ current_timeout = original_timeout + 30
51
+
52
+ last_exception = None
53
+ while retry_count < max_retries :
54
+ try :
55
+ result = timeout_handler( func , args = args , kwargs = kwargs , execution_timeout = current_timeout )
56
+ return result # 如果调用成功,则返回结果
57
+
58
+ except BaseException as e :
59
+ last_exception = e
60
+ retry_count += 1
61
+ print( e )
62
+ if "TimeoutError" in str( e ) :
63
+ # 计划延长的时间
64
+ extend_time = 30 * retry_count
65
+
66
+ # 增加执行超时时间
67
+ current_timeout += extend_time
68
+
69
+ # 如果原始函数有timeout参数,也增加它
70
+ if original_timeout > 0 :
71
+ kwargs[ 'timeout' ] = original_timeout + extend_time
72
+ # print( f"增加timeout参数至: {kwargs[ 'timeout' ]}秒" )
73
+
74
+ print( f"{task_name} : {func.__name__} 请求失败,正在重试... (第{retry_count}次)" )
75
+ if retry_count < max_retries : # 如果不是最后一次重试,则等待一段时间后重试
76
+ time.sleep( 5 * retry_count )
77
+ print( f"请求失败次数达到上限:{max_retries}次,终止请求。重试了{retry_count}次" )
78
+ # 这里可以添加更多的错误处理逻辑,例如记录错误信息
79
+ if last_exception:
80
+ raise last_exception # 重新抛出最后一个异常
81
+ return None
@@ -0,0 +1,46 @@
1
+ import json
2
+ import requests
3
+ from . import core
4
+ # 使用 from .core import retry_function 会导致测试失败
5
+
6
+ # 发起请求
7
+ def send_request( method , url , headers , data = None , return_type = "json" , timeout = 60 , auto_retry = None ) :
8
+ '''
9
+ 发送HTTP请求的通用函数
10
+
11
+ :param method: HTTP方法(GET, POST等)
12
+ :param url: 请求URL
13
+ :param headers: 请求头
14
+ :param data: 请求数据
15
+ :param return_type: 返回类型(json, text, response)
16
+ :param timeout: 请求超时时间
17
+ :param auto_retry: 自动重试配置,格式为:
18
+ {"task_name": "任务名称", "max_retries": 最大重试次数, "execute_timeout": 执行超时时间}
19
+ :return: 请求结果
20
+ '''
21
+ if data is None :
22
+ payload = { }
23
+ else :
24
+ if (isinstance( data , dict ) or isinstance( data , list )) and data != { } :
25
+ payload = json.dumps( data , ensure_ascii = False )
26
+ else :
27
+ payload = data
28
+ if auto_retry is None :
29
+ response = requests.request( method , url , headers = headers , data = payload , timeout = timeout )
30
+ else :
31
+ max_retries = auto_retry.get( "max_retries" , 5 )
32
+ execute_timeout = auto_retry.get( "execute_timeout" , 90 )
33
+ task_name = auto_retry.get( "task_name" , "" )
34
+ response = core.retry_function( requests.request , max_retries , execute_timeout , task_name , method , url ,
35
+ headers = headers , data = payload , timeout = timeout )
36
+
37
+ if response is None:
38
+ raise ValueError("请求返回的响应为None")
39
+
40
+ if return_type == "json" :
41
+ result = response.json()
42
+ elif return_type == "response" :
43
+ return response
44
+ else :
45
+ result = response.text
46
+ return result
@@ -0,0 +1,156 @@
1
+ Metadata-Version: 2.1
2
+ Name: funcguard
3
+ Version: 0.1.1
4
+ Summary: A funcguard for Python.
5
+ Home-page: https://github.com/tinycen/funcguard
6
+ Author: tinycen
7
+ Author-email: sky_ruocen@qq.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+
15
+ # FuncGuard
16
+
17
+ FuncGuard是一个Python库,提供了函数执行超时控制和重试机制的实用工具。
18
+
19
+ ## 功能特点
20
+
21
+ - 函数执行超时控制
22
+ - 函数执行失败自动重试
23
+ - HTTP请求封装(支持自动重试)
24
+
25
+ ## 安装/升级
26
+
27
+ ```bash
28
+ pip install funcguard
29
+ ```
30
+
31
+ ```bash
32
+ pip install --upgrade funcguard
33
+ ```
34
+
35
+
36
+ ## 使用方法
37
+
38
+ ### 超时控制
39
+
40
+ 使用`timeout_handler`函数可以控制函数的执行时间,防止函数运行时间过长:
41
+
42
+ ```python
43
+ from funcguard.core import timeout_handler
44
+
45
+ def long_running_function():
46
+ # 模拟一个耗时操作
47
+ import time
48
+ time.sleep(10)
49
+ return "操作完成"
50
+
51
+ try:
52
+ # 设置超时时间为5秒
53
+ result = timeout_handler(long_running_function, execution_timeout=5)
54
+ print(result)
55
+ except TimeoutError as e:
56
+ print(f"捕获到超时错误: {e}")
57
+ ```
58
+
59
+ ### 重试机制
60
+
61
+ 使用`retry_function`函数可以在函数执行失败时自动重试:
62
+
63
+ ```python
64
+ from funcguard.core import retry_function
65
+
66
+ def unstable_function():
67
+ # 模拟一个可能失败的操作
68
+ import random
69
+ if random.random() < 0.7: # 70%的概率失败
70
+ raise Exception("随机错误")
71
+ return "操作成功"
72
+
73
+ try:
74
+ # 最多重试3次,每次执行超时时间为10秒
75
+ result = retry_function(unstable_function, max_retries=3, execute_timeout=10, task_name="测试任务")
76
+ print(result)
77
+ except Exception as e:
78
+ print(f"重试后仍然失败: {e}")
79
+ ```
80
+
81
+ ### HTTP请求
82
+
83
+ 使用`send_request`函数发送HTTP请求,支持自动重试:
84
+
85
+ ```python
86
+ from funcguard.tools import send_request
87
+
88
+ # 不使用重试
89
+ response = send_request(
90
+ method="GET",
91
+ url="https://api.example.com/data",
92
+ headers={"Content-Type": "application/json"},
93
+ timeout=30
94
+ )
95
+ print(response)
96
+
97
+ # 使用重试
98
+ response = send_request(
99
+ method="POST",
100
+ url="https://api.example.com/data",
101
+ headers={"Content-Type": "application/json"},
102
+ data={"key": "value"},
103
+ timeout=30,
104
+ auto_retry={
105
+ "task_name": "API请求",
106
+ "max_retries": 3,
107
+ "execute_timeout": 60
108
+ }
109
+ )
110
+ print(response)
111
+ ```
112
+
113
+ ## API文档
114
+
115
+ ### funcguard.core
116
+
117
+ #### timeout_handler(func, args=(), kwargs=None, execution_timeout=90)
118
+
119
+ - **参数**:
120
+ - `func`: 需要执行的目标函数
121
+ - `args`: 目标函数的位置参数,默认为空元组
122
+ - `kwargs`: 目标函数的关键字参数,默认为None
123
+ - `execution_timeout`: 函数执行的超时时间,单位为秒,默认为90秒
124
+ - **返回值**: 目标函数的返回值
125
+ - **异常**: `TimeoutError` - 当函数执行超过指定时间时抛出
126
+
127
+ #### retry_function(func, max_retries=5, execute_timeout=90, task_name="", *args, **kwargs)
128
+
129
+ - **参数**:
130
+ - `func`: 需要重试的函数
131
+ - `max_retries`: 最大重试次数,默认为5
132
+ - `execute_timeout`: 执行超时时间,默认为90秒
133
+ - `task_name`: 任务名称,用于打印日志
134
+ - `args`: func的位置参数
135
+ - `kwargs`: func的关键字参数
136
+ - **返回值**: func的返回值
137
+ - **异常**: 当重试次数用尽后仍然失败时,抛出最后一次的异常
138
+
139
+ ### funcguard.tools
140
+
141
+ #### send_request(method, url, headers, data=None, return_type="json", timeout=60, auto_retry=None)
142
+
143
+ - **参数**:
144
+ - `method`: HTTP方法(GET, POST等)
145
+ - `url`: 请求URL
146
+ - `headers`: 请求头
147
+ - `data`: 请求数据,默认为None
148
+ - `return_type`: 返回类型,可选"json"、"response"或"text",默认为"json"
149
+ - `timeout`: 请求超时时间,单位为秒,默认为60
150
+ - `auto_retry`: 自动重试配置,格式为`{"task_name": "", "max_retries": 5, "execute_timeout": 90}`,默认为None
151
+ - **返回值**: 根据return_type参数返回不同格式的响应数据
152
+ - **异常**: 当请求失败且重试次数用尽后,抛出相应的异常
153
+
154
+ ## 许可证
155
+
156
+ MIT License
@@ -0,0 +1,16 @@
1
+ LICENSE
2
+ README.md
3
+ setup.py
4
+ funcguard/__init__.py
5
+ funcguard/core.py
6
+ funcguard/tools.py
7
+ funcguard.egg-info/PKG-INFO
8
+ funcguard.egg-info/SOURCES.txt
9
+ funcguard.egg-info/dependency_links.txt
10
+ funcguard.egg-info/not-zip-safe
11
+ funcguard.egg-info/requires.txt
12
+ funcguard.egg-info/top_level.txt
13
+ tests/__init__.py
14
+ tests/run_test.py
15
+ tests/test_core.py
16
+ tests/test_tools.py
@@ -0,0 +1 @@
1
+ requests
@@ -0,0 +1,2 @@
1
+ funcguard
2
+ tests
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,31 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ # 读取README文件
4
+ try:
5
+ with open('README.md', 'r', encoding='utf-8') as f:
6
+ long_description = f.read()
7
+ except FileNotFoundError:
8
+ long_description = 'A funcguard for Python.'
9
+
10
+ setup(
11
+ name='funcguard',
12
+ version='0.1.1',
13
+ packages=find_packages(),
14
+ install_requires=[
15
+ 'requests',
16
+ ],
17
+ author='tinycen',
18
+ author_email='sky_ruocen@qq.com',
19
+ description='A funcguard for Python.',
20
+ long_description=long_description,
21
+ long_description_content_type='text/markdown',
22
+ url='https://github.com/tinycen/funcguard',
23
+ classifiers=[
24
+ 'Programming Language :: Python :: 3',
25
+ 'License :: OSI Approved :: MIT License',
26
+ 'Operating System :: OS Independent',
27
+ ],
28
+ python_requires='>=3.10',
29
+ include_package_data=True,
30
+ zip_safe=False,
31
+ )
@@ -0,0 +1 @@
1
+ # 测试包初始化文件
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 测试运行脚本
4
+ 运行所有测试用例
5
+ """
6
+
7
+ import unittest
8
+ import sys
9
+ import os
10
+
11
+ # 添加项目根目录到Python路径
12
+ project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
13
+ sys.path.insert(0, project_root)
14
+
15
+ # 导入测试模块
16
+ from tests.test_core import TestTimeoutHandler, TestRetryFunction
17
+ from tests.test_tools import TestSendRequest
18
+
19
+
20
+ def run_all_tests():
21
+ """运行所有测试"""
22
+ # 创建测试套件
23
+ loader = unittest.TestLoader()
24
+ suite = unittest.TestSuite()
25
+
26
+ # 添加测试类
27
+ suite.addTests(loader.loadTestsFromTestCase(TestTimeoutHandler))
28
+ suite.addTests(loader.loadTestsFromTestCase(TestRetryFunction))
29
+ suite.addTests(loader.loadTestsFromTestCase(TestSendRequest))
30
+
31
+ # 运行测试
32
+ runner = unittest.TextTestRunner(verbosity=2)
33
+ result = runner.run(suite)
34
+
35
+ return result.wasSuccessful()
36
+
37
+
38
+ if __name__ == '__main__':
39
+ success = run_all_tests()
40
+ sys.exit(0 if success else 1)
@@ -0,0 +1,109 @@
1
+ import time
2
+ import unittest
3
+ from unittest.mock import patch
4
+
5
+ from funcguard.core import timeout_handler, retry_function
6
+
7
+
8
+ class TestTimeoutHandler(unittest.TestCase):
9
+ """测试timeout_handler函数"""
10
+
11
+ def test_normal_execution(self):
12
+ """测试正常执行的函数"""
13
+ def quick_function():
14
+ time.sleep(0.1)
15
+ return "success"
16
+
17
+ result = timeout_handler(quick_function, execution_timeout=2)
18
+ self.assertEqual(result, "success")
19
+
20
+ def test_timeout_execution(self):
21
+ """测试超时的函数"""
22
+ def slow_function():
23
+ time.sleep(2)
24
+ return "should not reach here"
25
+
26
+ from funcguard.core import FuncguardTimeoutError
27
+ with self.assertRaises(FuncguardTimeoutError) as context:
28
+ timeout_handler(slow_function, execution_timeout=1)
29
+
30
+ self.assertIn("执行时间超过 1 秒", str(context.exception))
31
+
32
+ def test_function_with_args(self):
33
+ """测试带参数的函数"""
34
+ def add_numbers(a, b):
35
+ return a + b
36
+
37
+ result = timeout_handler(add_numbers, args=(3, 4), execution_timeout=2)
38
+ self.assertEqual(result, 7)
39
+
40
+ def test_function_with_kwargs(self):
41
+ """测试带关键字参数的函数"""
42
+ def greet(name, greeting="Hello"):
43
+ return f"{greeting}, {name}!"
44
+
45
+ result = timeout_handler(greet, kwargs={"name": "World", "greeting": "Hi"}, execution_timeout=2)
46
+ self.assertEqual(result, "Hi, World!")
47
+
48
+
49
+ class TestRetryFunction(unittest.TestCase):
50
+ """测试retry_function函数"""
51
+
52
+ def test_successful_first_try(self):
53
+ """测试第一次就成功的函数"""
54
+ def always_success():
55
+ return "success"
56
+
57
+ result = retry_function(always_success, max_retries=2, task_name="test")
58
+ self.assertEqual(result, "success")
59
+
60
+ def test_retry_until_success(self):
61
+ """测试重试后成功的函数"""
62
+ attempts = []
63
+
64
+ def sometimes_fail():
65
+ attempts.append(len(attempts))
66
+ if len(attempts) < 2:
67
+ raise ValueError("Not yet")
68
+ return "finally success"
69
+
70
+ result = retry_function(sometimes_fail, max_retries=2, task_name="test")
71
+ self.assertEqual(result, "finally success")
72
+ self.assertEqual(len(attempts), 2)
73
+
74
+ def test_exhaust_all_retries(self):
75
+ """测试耗尽所有重试次数"""
76
+ def always_fail():
77
+ raise RuntimeError("Always fails")
78
+
79
+ with self.assertRaises(RuntimeError) as context:
80
+ retry_function(always_fail, max_retries=2, task_name="test")
81
+
82
+ self.assertEqual(str(context.exception), "Always fails")
83
+
84
+ def test_timeout_in_retry(self):
85
+ """测试重试中的超时处理"""
86
+ def timeout_function():
87
+ time.sleep(2)
88
+ return "should timeout"
89
+
90
+ # retry_function会在重试耗尽后抛出最后的异常
91
+ from funcguard.core import FuncguardTimeoutError
92
+ with self.assertRaises(FuncguardTimeoutError):
93
+ retry_function(timeout_function, max_retries=1, execute_timeout=1, task_name="test")
94
+
95
+ @patch('time.sleep')
96
+ def test_retry_with_custom_delay(self, mock_sleep):
97
+ """测试重试延迟"""
98
+ def always_fail():
99
+ raise ValueError("test")
100
+
101
+ with self.assertRaises(ValueError):
102
+ retry_function(always_fail, max_retries=2, task_name="test")
103
+
104
+ # 验证sleep被调用了正确的次数
105
+ self.assertEqual(mock_sleep.call_count, 1)
106
+
107
+
108
+ if __name__ == '__main__':
109
+ unittest.main()
@@ -0,0 +1,156 @@
1
+ import json
2
+ import unittest
3
+ from unittest.mock import patch, MagicMock
4
+ import requests
5
+
6
+ from funcguard.tools import send_request
7
+
8
+
9
+ class MockResponse:
10
+ """模拟requests响应"""
11
+
12
+ def __init__(self, json_data=None, text="", status_code=200):
13
+ self.json_data = json_data or {}
14
+ self.text = text
15
+ self.status_code = status_code
16
+
17
+ def json(self):
18
+ return self.json_data
19
+
20
+
21
+ class TestSendRequest(unittest.TestCase):
22
+ """测试send_request函数"""
23
+
24
+ @patch('requests.request')
25
+ def test_get_request_json_response(self, mock_request):
26
+ """测试GET请求返回JSON"""
27
+ mock_response = MockResponse(json_data={"key": "value"})
28
+ mock_request.return_value = mock_response
29
+
30
+ result = send_request(
31
+ method="GET",
32
+ url="https://api.example.com/data",
33
+ headers={"Content-Type": "application/json"}
34
+ )
35
+
36
+ self.assertEqual(result, {"key": "value"})
37
+ mock_request.assert_called_once()
38
+
39
+ @patch('requests.request')
40
+ def test_post_request_with_data(self, mock_request):
41
+ """测试POST请求带数据"""
42
+ mock_response = MockResponse(json_data={"status": "created"})
43
+ mock_request.return_value = mock_response
44
+
45
+ result = send_request(
46
+ method="POST",
47
+ url="https://api.example.com/users",
48
+ headers={"Content-Type": "application/json"},
49
+ data={"name": "test", "email": "test@example.com"}
50
+ )
51
+
52
+ self.assertEqual(result, {"status": "created"})
53
+ mock_request.assert_called_once()
54
+
55
+ @patch('requests.request')
56
+ def test_return_response_object(self, mock_request):
57
+ """测试返回response对象"""
58
+ mock_response = MockResponse(text="raw response")
59
+ mock_request.return_value = mock_response
60
+
61
+ result = send_request(
62
+ method="GET",
63
+ url="https://api.example.com/raw",
64
+ headers={},
65
+ return_type="response"
66
+ )
67
+
68
+ self.assertEqual(result, mock_response)
69
+
70
+ @patch('requests.request')
71
+ def test_return_text_response(self, mock_request):
72
+ """测试返回文本响应"""
73
+ mock_response = MockResponse(text="plain text response")
74
+ mock_request.return_value = mock_response
75
+
76
+ result = send_request(
77
+ method="GET",
78
+ url="https://api.example.com/text",
79
+ headers={},
80
+ return_type="text"
81
+ )
82
+
83
+ self.assertEqual(result, "plain text response")
84
+
85
+ @patch('requests.request')
86
+ def test_custom_timeout(self, mock_request):
87
+ """测试自定义超时时间"""
88
+ mock_response = MockResponse()
89
+ mock_request.return_value = mock_response
90
+
91
+ send_request(
92
+ method="GET",
93
+ url="https://api.example.com/data",
94
+ headers={},
95
+ timeout=30
96
+ )
97
+
98
+ mock_request.assert_called_once()
99
+
100
+ @patch('funcguard.core.retry_function')
101
+ @patch('requests.request')
102
+ def test_auto_retry_enabled(self, mock_request, mock_retry):
103
+ """测试启用自动重试"""
104
+ # 设置retry_function返回一个包含正确json数据的MockResponse对象
105
+ mock_response = MockResponse(json_data={"success": True})
106
+ mock_retry.return_value = mock_response
107
+
108
+ result = send_request(
109
+ method="POST",
110
+ url="https://api.example.com/data",
111
+ headers={"Content-Type": "application/json"},
112
+ data={"key": "value"},
113
+ auto_retry={
114
+ "task_name": "API测试",
115
+ "max_retries": 3,
116
+ "execute_timeout": 60
117
+ }
118
+ )
119
+
120
+ # 验证结果
121
+ if hasattr(result, 'json_data'):
122
+ # 如果result是MockResponse对象,直接比较其json_data
123
+ self.assertEqual(result.json_data, {"success": True})
124
+ else:
125
+ # 否则直接比较result
126
+ self.assertEqual(result, {"success": True})
127
+ mock_retry.assert_called_once()
128
+
129
+ @patch('requests.request')
130
+ def test_none_response_raises_error(self, mock_request):
131
+ """测试None响应抛出错误"""
132
+ mock_request.return_value = None
133
+
134
+ with self.assertRaises(ValueError) as context:
135
+ send_request("GET", "https://api.example.com/data", {})
136
+
137
+ self.assertEqual(str(context.exception), "请求返回的响应为None")
138
+
139
+ def test_invalid_return_type(self):
140
+ """测试无效的返回类型"""
141
+ with patch('requests.request') as mock_request:
142
+ mock_response = MockResponse()
143
+ mock_request.return_value = mock_response
144
+
145
+ # 对于无效的return_type,函数会尝试访问response.text
146
+ result = send_request(
147
+ method="GET",
148
+ url="https://api.example.com/data",
149
+ headers={},
150
+ return_type="invalid"
151
+ )
152
+ self.assertEqual(result, "")
153
+
154
+
155
+ if __name__ == '__main__':
156
+ unittest.main()