pytest-dsl 0.15.3__py3-none-any.whl → 0.15.5__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.
- pytest_dsl/__init__.py +52 -3
- pytest_dsl/cli.py +8 -1
- pytest_dsl/core/context.py +82 -5
- pytest_dsl/core/custom_keyword_manager.py +14 -8
- pytest_dsl/core/dsl_executor.py +159 -35
- pytest_dsl/core/global_context.py +38 -8
- pytest_dsl/core/http_client.py +29 -5
- pytest_dsl/core/keyword_utils.py +7 -0
- pytest_dsl/core/remote_server_registry.py +333 -0
- pytest_dsl/core/serialization_utils.py +231 -0
- pytest_dsl/core/utils.py +34 -27
- pytest_dsl/core/variable_providers.py +202 -0
- pytest_dsl/core/variable_utils.py +45 -38
- pytest_dsl/core/yaml_loader.py +176 -36
- pytest_dsl/keywords/http_keywords.py +9 -5
- pytest_dsl/remote/__init__.py +63 -1
- pytest_dsl/remote/keyword_client.py +139 -55
- pytest_dsl/remote/keyword_server.py +32 -16
- {pytest_dsl-0.15.3.dist-info → pytest_dsl-0.15.5.dist-info}/METADATA +1 -1
- {pytest_dsl-0.15.3.dist-info → pytest_dsl-0.15.5.dist-info}/RECORD +24 -21
- {pytest_dsl-0.15.3.dist-info → pytest_dsl-0.15.5.dist-info}/WHEEL +0 -0
- {pytest_dsl-0.15.3.dist-info → pytest_dsl-0.15.5.dist-info}/entry_points.txt +0 -0
- {pytest_dsl-0.15.3.dist-info → pytest_dsl-0.15.5.dist-info}/licenses/LICENSE +0 -0
- {pytest_dsl-0.15.3.dist-info → pytest_dsl-0.15.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,333 @@
|
|
1
|
+
"""
|
2
|
+
远程服务器注册模块
|
3
|
+
|
4
|
+
提供独立的远程服务器注册功能,方便其他系统集成pytest-dsl时使用自己的变量系统。
|
5
|
+
这个模块不依赖于YAML配置,完全通过编程方式进行服务器注册。
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Dict, List, Optional, Any, Callable
|
9
|
+
import logging
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
class RemoteServerRegistry:
|
15
|
+
"""远程服务器注册器
|
16
|
+
|
17
|
+
提供灵活的API用于注册和管理远程关键字服务器,
|
18
|
+
支持自定义变量获取方式,方便第三方系统集成。
|
19
|
+
"""
|
20
|
+
|
21
|
+
def __init__(self):
|
22
|
+
self._variable_providers = [] # 变量提供者列表
|
23
|
+
self._server_configs = [] # 服务器配置列表
|
24
|
+
self._connection_callbacks = [] # 连接成功后的回调
|
25
|
+
|
26
|
+
def add_variable_provider(self, provider: Callable[[], Dict[str, Any]]):
|
27
|
+
"""添加变量提供者
|
28
|
+
|
29
|
+
变量提供者是一个无参数的可调用对象,返回字典形式的变量。
|
30
|
+
这允许第三方系统提供自己的变量获取逻辑。
|
31
|
+
|
32
|
+
Args:
|
33
|
+
provider: 返回变量字典的可调用对象
|
34
|
+
|
35
|
+
Examples:
|
36
|
+
>>> def my_vars():
|
37
|
+
... return {'api_key': 'secret', 'env': 'prod'}
|
38
|
+
>>> registry.add_variable_provider(my_vars)
|
39
|
+
"""
|
40
|
+
if callable(provider):
|
41
|
+
self._variable_providers.append(provider)
|
42
|
+
else:
|
43
|
+
raise ValueError("变量提供者必须是可调用对象")
|
44
|
+
|
45
|
+
def add_connection_callback(self, callback: Callable[[str, bool], None]):
|
46
|
+
"""添加连接回调
|
47
|
+
|
48
|
+
连接回调会在每次连接远程服务器后被调用,
|
49
|
+
无论连接成功还是失败。
|
50
|
+
|
51
|
+
Args:
|
52
|
+
callback: 接受(alias, success)参数的回调函数
|
53
|
+
"""
|
54
|
+
if callable(callback):
|
55
|
+
self._connection_callbacks.append(callback)
|
56
|
+
else:
|
57
|
+
raise ValueError("连接回调必须是可调用对象")
|
58
|
+
|
59
|
+
def register_server(self,
|
60
|
+
url: str,
|
61
|
+
alias: str,
|
62
|
+
api_key: Optional[str] = None,
|
63
|
+
sync_global_vars: bool = True,
|
64
|
+
sync_custom_vars: bool = True,
|
65
|
+
exclude_patterns: Optional[List[str]] = None) -> bool:
|
66
|
+
"""注册单个远程服务器
|
67
|
+
|
68
|
+
Args:
|
69
|
+
url: 服务器URL
|
70
|
+
alias: 服务器别名
|
71
|
+
api_key: API密钥
|
72
|
+
sync_global_vars: 是否同步全局变量
|
73
|
+
sync_custom_vars: 是否同步自定义变量(通过变量提供者)
|
74
|
+
exclude_patterns: 要排除的变量名模式列表
|
75
|
+
|
76
|
+
Returns:
|
77
|
+
bool: 是否连接成功
|
78
|
+
"""
|
79
|
+
# 构建同步配置
|
80
|
+
sync_config = {
|
81
|
+
'sync_global_vars': sync_global_vars,
|
82
|
+
'sync_yaml_vars': False, # 不使用YAML变量
|
83
|
+
'sync_custom_vars': sync_custom_vars,
|
84
|
+
'exclude_patterns': exclude_patterns or ['password', 'secret', 'token']
|
85
|
+
}
|
86
|
+
|
87
|
+
# 收集要同步的变量
|
88
|
+
variables_to_sync = {}
|
89
|
+
|
90
|
+
if sync_custom_vars:
|
91
|
+
variables_to_sync.update(self._collect_custom_variables())
|
92
|
+
|
93
|
+
# 尝试连接
|
94
|
+
success = self._connect_to_server(
|
95
|
+
url, alias, api_key, sync_config, variables_to_sync)
|
96
|
+
|
97
|
+
# 调用连接回调
|
98
|
+
for callback in self._connection_callbacks:
|
99
|
+
try:
|
100
|
+
callback(alias, success)
|
101
|
+
except Exception as e:
|
102
|
+
logger.warning(f"连接回调执行失败: {e}")
|
103
|
+
|
104
|
+
if success:
|
105
|
+
# 保存配置以便后续使用
|
106
|
+
self._server_configs.append({
|
107
|
+
'url': url,
|
108
|
+
'alias': alias,
|
109
|
+
'api_key': api_key,
|
110
|
+
'sync_config': sync_config
|
111
|
+
})
|
112
|
+
|
113
|
+
return success
|
114
|
+
|
115
|
+
def register_servers_from_config(self, servers: List[Dict[str, Any]]) -> Dict[str, Dict[str, Any]]:
|
116
|
+
"""从配置列表批量注册服务器
|
117
|
+
|
118
|
+
Args:
|
119
|
+
servers: 服务器配置列表,每个配置包含url、alias等字段
|
120
|
+
|
121
|
+
Returns:
|
122
|
+
dict: 注册结果,键为alias,值为结果详情字典
|
123
|
+
|
124
|
+
Examples:
|
125
|
+
>>> servers = [
|
126
|
+
... {'url': 'http://server1:8270', 'alias': 'server1'},
|
127
|
+
... {'url': 'http://server2:8270', 'alias': 'server2', 'api_key': 'secret'}
|
128
|
+
... ]
|
129
|
+
>>> results = registry.register_servers_from_config(servers)
|
130
|
+
"""
|
131
|
+
results = {}
|
132
|
+
|
133
|
+
for server_config in servers:
|
134
|
+
if not isinstance(server_config, dict):
|
135
|
+
continue
|
136
|
+
|
137
|
+
url = server_config.get('url')
|
138
|
+
alias = server_config.get('alias')
|
139
|
+
|
140
|
+
if not url or not alias:
|
141
|
+
logger.warning(f"服务器配置缺少必要字段: {server_config}")
|
142
|
+
results[alias or 'unknown'] = {
|
143
|
+
'success': False,
|
144
|
+
'url': url or 'unknown',
|
145
|
+
'alias': alias or 'unknown',
|
146
|
+
'error': '缺少必要字段'
|
147
|
+
}
|
148
|
+
continue
|
149
|
+
|
150
|
+
api_key = server_config.get('api_key')
|
151
|
+
sync_global_vars = server_config.get('sync_global_vars', True)
|
152
|
+
sync_custom_vars = server_config.get('sync_custom_vars', True)
|
153
|
+
exclude_patterns = server_config.get('exclude_patterns')
|
154
|
+
|
155
|
+
success = self.register_server(
|
156
|
+
url=url,
|
157
|
+
alias=alias,
|
158
|
+
api_key=api_key,
|
159
|
+
sync_global_vars=sync_global_vars,
|
160
|
+
sync_custom_vars=sync_custom_vars,
|
161
|
+
exclude_patterns=exclude_patterns
|
162
|
+
)
|
163
|
+
|
164
|
+
results[alias] = {
|
165
|
+
'success': success,
|
166
|
+
'url': url,
|
167
|
+
'alias': alias
|
168
|
+
}
|
169
|
+
|
170
|
+
return results
|
171
|
+
|
172
|
+
def _collect_custom_variables(self) -> Dict[str, Any]:
|
173
|
+
"""收集自定义变量"""
|
174
|
+
variables = {}
|
175
|
+
|
176
|
+
for provider in self._variable_providers:
|
177
|
+
try:
|
178
|
+
provider_vars = provider()
|
179
|
+
if isinstance(provider_vars, dict):
|
180
|
+
variables.update(provider_vars)
|
181
|
+
else:
|
182
|
+
logger.warning(f"变量提供者返回了非字典类型: {type(provider_vars)}")
|
183
|
+
except Exception as e:
|
184
|
+
logger.warning(f"变量提供者执行失败: {e}")
|
185
|
+
|
186
|
+
return variables
|
187
|
+
|
188
|
+
def _connect_to_server(self,
|
189
|
+
url: str,
|
190
|
+
alias: str,
|
191
|
+
api_key: Optional[str],
|
192
|
+
sync_config: Dict[str, Any],
|
193
|
+
variables: Dict[str, Any]) -> bool:
|
194
|
+
"""连接到远程服务器"""
|
195
|
+
try:
|
196
|
+
# 导入远程关键字管理器
|
197
|
+
from pytest_dsl.remote import remote_keyword_manager
|
198
|
+
|
199
|
+
# 创建扩展的同步配置
|
200
|
+
extended_sync_config = sync_config.copy()
|
201
|
+
extended_sync_config['custom_variables'] = variables
|
202
|
+
|
203
|
+
# 注册服务器
|
204
|
+
success = remote_keyword_manager.register_remote_server(
|
205
|
+
url=url,
|
206
|
+
alias=alias,
|
207
|
+
api_key=api_key,
|
208
|
+
sync_config=extended_sync_config
|
209
|
+
)
|
210
|
+
|
211
|
+
if success:
|
212
|
+
logger.info(f"成功连接到远程服务器: {alias} ({url})")
|
213
|
+
else:
|
214
|
+
logger.error(f"连接远程服务器失败: {alias} ({url})")
|
215
|
+
|
216
|
+
return success
|
217
|
+
|
218
|
+
except ImportError:
|
219
|
+
logger.error("远程功能不可用,请检查依赖安装")
|
220
|
+
return False
|
221
|
+
except Exception as e:
|
222
|
+
logger.error(f"连接远程服务器时发生错误: {e}")
|
223
|
+
return False
|
224
|
+
|
225
|
+
def get_registered_servers(self) -> List[Dict[str, Any]]:
|
226
|
+
"""获取已注册的服务器列表"""
|
227
|
+
return self._server_configs.copy()
|
228
|
+
|
229
|
+
def clear_variable_providers(self):
|
230
|
+
"""清空所有变量提供者"""
|
231
|
+
self._variable_providers.clear()
|
232
|
+
|
233
|
+
def clear_connection_callbacks(self):
|
234
|
+
"""清空所有连接回调"""
|
235
|
+
self._connection_callbacks.clear()
|
236
|
+
|
237
|
+
|
238
|
+
# 创建全局注册器实例
|
239
|
+
remote_server_registry = RemoteServerRegistry()
|
240
|
+
|
241
|
+
|
242
|
+
# 便捷函数
|
243
|
+
def register_remote_server_with_variables(url: str,
|
244
|
+
alias: str,
|
245
|
+
variables: Dict[str, Any],
|
246
|
+
api_key: Optional[str] = None) -> bool:
|
247
|
+
"""使用指定变量注册远程服务器的便捷函数
|
248
|
+
|
249
|
+
Args:
|
250
|
+
url: 服务器URL
|
251
|
+
alias: 服务器别名
|
252
|
+
variables: 要同步的变量字典
|
253
|
+
api_key: API密钥
|
254
|
+
|
255
|
+
Returns:
|
256
|
+
bool: 是否连接成功
|
257
|
+
"""
|
258
|
+
# 创建临时变量提供者
|
259
|
+
def temp_provider():
|
260
|
+
return variables
|
261
|
+
|
262
|
+
# 临时添加变量提供者
|
263
|
+
original_providers = remote_server_registry._variable_providers.copy()
|
264
|
+
remote_server_registry._variable_providers = [temp_provider]
|
265
|
+
|
266
|
+
try:
|
267
|
+
return remote_server_registry.register_server(url, alias, api_key)
|
268
|
+
finally:
|
269
|
+
# 恢复原来的变量提供者
|
270
|
+
remote_server_registry._variable_providers = original_providers
|
271
|
+
|
272
|
+
|
273
|
+
def create_database_variable_provider(connection_string: str):
|
274
|
+
"""创建数据库变量提供者示例
|
275
|
+
|
276
|
+
这是一个示例函数,展示如何创建从数据库获取变量的提供者。
|
277
|
+
实际使用时需要根据具体的数据库类型进行调整。
|
278
|
+
|
279
|
+
Args:
|
280
|
+
connection_string: 数据库连接字符串
|
281
|
+
|
282
|
+
Returns:
|
283
|
+
callable: 变量提供者函数
|
284
|
+
"""
|
285
|
+
def database_provider():
|
286
|
+
# 这里是示例代码,实际需要根据数据库类型实现
|
287
|
+
# import sqlite3
|
288
|
+
# conn = sqlite3.connect(connection_string)
|
289
|
+
# cursor = conn.cursor()
|
290
|
+
# cursor.execute("SELECT key, value FROM variables")
|
291
|
+
# variables = dict(cursor.fetchall())
|
292
|
+
# conn.close()
|
293
|
+
# return variables
|
294
|
+
|
295
|
+
return {
|
296
|
+
'db_host': 'localhost',
|
297
|
+
'db_port': '5432',
|
298
|
+
'db_name': 'test_db'
|
299
|
+
}
|
300
|
+
|
301
|
+
return database_provider
|
302
|
+
|
303
|
+
|
304
|
+
def create_config_file_variable_provider(config_file_path: str):
|
305
|
+
"""创建配置文件变量提供者
|
306
|
+
|
307
|
+
从JSON或其他配置文件读取变量。
|
308
|
+
|
309
|
+
Args:
|
310
|
+
config_file_path: 配置文件路径
|
311
|
+
|
312
|
+
Returns:
|
313
|
+
callable: 变量提供者函数
|
314
|
+
"""
|
315
|
+
import json
|
316
|
+
import os
|
317
|
+
|
318
|
+
def config_file_provider():
|
319
|
+
if not os.path.exists(config_file_path):
|
320
|
+
return {}
|
321
|
+
|
322
|
+
try:
|
323
|
+
with open(config_file_path, 'r', encoding='utf-8') as f:
|
324
|
+
if config_file_path.endswith('.json'):
|
325
|
+
return json.load(f)
|
326
|
+
else:
|
327
|
+
# 可以扩展支持其他格式
|
328
|
+
return {}
|
329
|
+
except Exception as e:
|
330
|
+
logger.warning(f"读取配置文件失败: {e}")
|
331
|
+
return {}
|
332
|
+
|
333
|
+
return config_file_provider
|
@@ -0,0 +1,231 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
"""
|
4
|
+
序列化工具模块
|
5
|
+
|
6
|
+
提供统一的XML-RPC序列化检查和转换功能,避免代码重复。
|
7
|
+
"""
|
8
|
+
|
9
|
+
import datetime
|
10
|
+
from typing import Any, Dict, List, Optional
|
11
|
+
|
12
|
+
|
13
|
+
class XMLRPCSerializer:
|
14
|
+
"""XML-RPC序列化工具类
|
15
|
+
|
16
|
+
统一处理XML-RPC序列化检查、转换和过滤逻辑,
|
17
|
+
避免在多个类中重复实现相同的序列化代码。
|
18
|
+
"""
|
19
|
+
|
20
|
+
# 敏感信息过滤模式
|
21
|
+
DEFAULT_EXCLUDE_PATTERNS = [
|
22
|
+
'password', 'secret', 'token', 'credential', 'auth', 'private'
|
23
|
+
]
|
24
|
+
|
25
|
+
@staticmethod
|
26
|
+
def is_serializable(value: Any) -> bool:
|
27
|
+
"""检查值是否可以被XML-RPC序列化
|
28
|
+
|
29
|
+
XML-RPC支持的类型:
|
30
|
+
- None (需要allow_none=True)
|
31
|
+
- bool, int, float, str, bytes
|
32
|
+
- datetime.datetime
|
33
|
+
- list (元素也必须可序列化)
|
34
|
+
- dict (键必须是字符串,值必须可序列化)
|
35
|
+
|
36
|
+
Args:
|
37
|
+
value: 要检查的值
|
38
|
+
|
39
|
+
Returns:
|
40
|
+
bool: 是否可序列化
|
41
|
+
"""
|
42
|
+
# 基本类型
|
43
|
+
if value is None:
|
44
|
+
return True
|
45
|
+
if isinstance(value, (bool, int, float, str, bytes)):
|
46
|
+
return True
|
47
|
+
if isinstance(value, datetime.datetime):
|
48
|
+
return True
|
49
|
+
|
50
|
+
# 严格检查:只允许内置的list和dict类型,不允许自定义类
|
51
|
+
value_type = type(value)
|
52
|
+
|
53
|
+
# 检查是否为内置list类型(不是子类)
|
54
|
+
if value_type is list:
|
55
|
+
try:
|
56
|
+
for item in value:
|
57
|
+
if not XMLRPCSerializer.is_serializable(item):
|
58
|
+
return False
|
59
|
+
return True
|
60
|
+
except Exception:
|
61
|
+
return False
|
62
|
+
|
63
|
+
# 检查是否为内置tuple类型
|
64
|
+
if value_type is tuple:
|
65
|
+
try:
|
66
|
+
for item in value:
|
67
|
+
if not XMLRPCSerializer.is_serializable(item):
|
68
|
+
return False
|
69
|
+
return True
|
70
|
+
except Exception:
|
71
|
+
return False
|
72
|
+
|
73
|
+
# 检查是否为内置dict类型(不是子类,如DotAccessDict)
|
74
|
+
if value_type is dict:
|
75
|
+
try:
|
76
|
+
for k, v in value.items():
|
77
|
+
# XML-RPC要求字典的键必须是字符串
|
78
|
+
if not isinstance(k, str):
|
79
|
+
return False
|
80
|
+
if not XMLRPCSerializer.is_serializable(v):
|
81
|
+
return False
|
82
|
+
return True
|
83
|
+
except Exception:
|
84
|
+
return False
|
85
|
+
|
86
|
+
# 其他类型都不可序列化
|
87
|
+
return False
|
88
|
+
|
89
|
+
@staticmethod
|
90
|
+
def convert_to_serializable(value: Any) -> Optional[Any]:
|
91
|
+
"""尝试将值转换为XML-RPC可序列化的格式
|
92
|
+
|
93
|
+
Args:
|
94
|
+
value: 要转换的值
|
95
|
+
|
96
|
+
Returns:
|
97
|
+
转换后的值,如果无法转换则返回None
|
98
|
+
"""
|
99
|
+
# 如果已经可序列化,直接返回
|
100
|
+
if XMLRPCSerializer.is_serializable(value):
|
101
|
+
return value
|
102
|
+
|
103
|
+
# 尝试转换类字典对象为标准字典
|
104
|
+
if hasattr(value, 'keys') and hasattr(value, 'items'):
|
105
|
+
try:
|
106
|
+
converted_dict = {}
|
107
|
+
for k, v in value.items():
|
108
|
+
# 键必须是字符串
|
109
|
+
if not isinstance(k, str):
|
110
|
+
k = str(k)
|
111
|
+
|
112
|
+
# 递归转换值
|
113
|
+
converted_value = XMLRPCSerializer.convert_to_serializable(v)
|
114
|
+
if converted_value is not None or v is None:
|
115
|
+
converted_dict[k] = converted_value
|
116
|
+
else:
|
117
|
+
# 如果无法转换子值,跳过这个键值对
|
118
|
+
print(f"跳过无法转换的字典项: {k} "
|
119
|
+
f"(类型: {type(v).__name__})")
|
120
|
+
continue
|
121
|
+
|
122
|
+
return converted_dict
|
123
|
+
except Exception as e:
|
124
|
+
print(f"转换类字典对象失败: {e}")
|
125
|
+
return None
|
126
|
+
|
127
|
+
# 尝试转换类列表对象为标准列表
|
128
|
+
if hasattr(value, '__iter__') and not isinstance(value, (str, bytes)):
|
129
|
+
try:
|
130
|
+
converted_list = []
|
131
|
+
for item in value:
|
132
|
+
converted_item = XMLRPCSerializer.convert_to_serializable(item)
|
133
|
+
if converted_item is not None or item is None:
|
134
|
+
converted_list.append(converted_item)
|
135
|
+
else:
|
136
|
+
# 如果无法转换子项,跳过
|
137
|
+
print(f"跳过无法转换的列表项: "
|
138
|
+
f"(类型: {type(item).__name__})")
|
139
|
+
continue
|
140
|
+
|
141
|
+
return converted_list
|
142
|
+
except Exception as e:
|
143
|
+
print(f"转换类列表对象失败: {e}")
|
144
|
+
return None
|
145
|
+
|
146
|
+
# 尝试转换为字符串表示
|
147
|
+
try:
|
148
|
+
str_value = str(value)
|
149
|
+
# 避免转换过长的字符串或包含敏感信息的对象
|
150
|
+
if (len(str_value) < 1000 and
|
151
|
+
not any(pattern in str_value.lower()
|
152
|
+
for pattern in XMLRPCSerializer.DEFAULT_EXCLUDE_PATTERNS)):
|
153
|
+
return str_value
|
154
|
+
except Exception:
|
155
|
+
pass
|
156
|
+
|
157
|
+
# 无法转换
|
158
|
+
return None
|
159
|
+
|
160
|
+
@staticmethod
|
161
|
+
def filter_variables(variables: Dict[str, Any],
|
162
|
+
exclude_patterns: Optional[List[str]] = None) -> Dict[str, Any]:
|
163
|
+
"""过滤变量字典,移除敏感变量和不可序列化的变量
|
164
|
+
|
165
|
+
Args:
|
166
|
+
variables: 原始变量字典
|
167
|
+
exclude_patterns: 排除模式列表,如果为None则使用默认模式
|
168
|
+
|
169
|
+
Returns:
|
170
|
+
Dict[str, Any]: 过滤后的变量字典
|
171
|
+
"""
|
172
|
+
if exclude_patterns is None:
|
173
|
+
exclude_patterns = XMLRPCSerializer.DEFAULT_EXCLUDE_PATTERNS
|
174
|
+
|
175
|
+
filtered_variables = {}
|
176
|
+
|
177
|
+
for var_name, var_value in variables.items():
|
178
|
+
# 检查是否需要排除
|
179
|
+
should_exclude = False
|
180
|
+
var_name_lower = var_name.lower()
|
181
|
+
|
182
|
+
# 检查变量名
|
183
|
+
for pattern in exclude_patterns:
|
184
|
+
if pattern.lower() in var_name_lower:
|
185
|
+
should_exclude = True
|
186
|
+
break
|
187
|
+
|
188
|
+
# 如果值是字符串,也检查是否包含敏感信息
|
189
|
+
if not should_exclude and isinstance(var_value, str):
|
190
|
+
value_lower = var_value.lower()
|
191
|
+
for pattern in exclude_patterns:
|
192
|
+
if (pattern.lower() in value_lower and
|
193
|
+
len(var_value) < 100): # 只检查短字符串
|
194
|
+
should_exclude = True
|
195
|
+
break
|
196
|
+
|
197
|
+
if not should_exclude:
|
198
|
+
# 尝试转换为可序列化的格式
|
199
|
+
serializable_value = XMLRPCSerializer.convert_to_serializable(var_value)
|
200
|
+
# 注意:None值转换后仍然是None,但这是有效的结果
|
201
|
+
if serializable_value is not None or var_value is None:
|
202
|
+
filtered_variables[var_name] = serializable_value
|
203
|
+
else:
|
204
|
+
print(f"跳过不可序列化的变量: {var_name} "
|
205
|
+
f"(类型: {type(var_value).__name__})")
|
206
|
+
else:
|
207
|
+
print(f"跳过敏感变量: {var_name}")
|
208
|
+
|
209
|
+
return filtered_variables
|
210
|
+
|
211
|
+
@staticmethod
|
212
|
+
def validate_xmlrpc_data(data: Any) -> bool:
|
213
|
+
"""验证数据是否可以通过XML-RPC传输
|
214
|
+
|
215
|
+
Args:
|
216
|
+
data: 要验证的数据
|
217
|
+
|
218
|
+
Returns:
|
219
|
+
bool: 是否可以传输
|
220
|
+
"""
|
221
|
+
try:
|
222
|
+
import xmlrpc.client
|
223
|
+
# 尝试序列化数据
|
224
|
+
xmlrpc.client.dumps((data,), allow_none=True)
|
225
|
+
return True
|
226
|
+
except Exception:
|
227
|
+
return False
|
228
|
+
|
229
|
+
|
230
|
+
# 创建全局序列化器实例,方便直接使用
|
231
|
+
xmlrpc_serializer = XMLRPCSerializer()
|
pytest_dsl/core/utils.py
CHANGED
@@ -69,7 +69,8 @@ def _legacy_replace_variables_in_string(value: str) -> str:
|
|
69
69
|
break
|
70
70
|
|
71
71
|
# 替换变量引用
|
72
|
-
value = value[:match.start()] + str(var_value) +
|
72
|
+
value = value[:match.start()] + str(var_value) + \
|
73
|
+
value[match.end():]
|
73
74
|
|
74
75
|
# 再处理基本引用
|
75
76
|
matches = list(re.finditer(basic_pattern, value))
|
@@ -77,7 +78,8 @@ def _legacy_replace_variables_in_string(value: str) -> str:
|
|
77
78
|
var_name = match.group(1)
|
78
79
|
if context_has_variable(var_name):
|
79
80
|
var_value = get_variable(var_name)
|
80
|
-
value = value[:match.start()] + str(var_value) +
|
81
|
+
value = value[:match.start()] + str(var_value) + \
|
82
|
+
value[match.end():]
|
81
83
|
|
82
84
|
return value
|
83
85
|
|
@@ -102,37 +104,42 @@ def replace_variables_in_dict(data: Union[Dict, List, str]) -> Union[Dict, List,
|
|
102
104
|
|
103
105
|
|
104
106
|
def context_has_variable(var_name: str) -> bool:
|
105
|
-
"""
|
106
|
-
# 先检查YAML变量
|
107
|
-
from pytest_dsl.core.yaml_vars import yaml_vars
|
108
|
-
if yaml_vars.get_variable(var_name) is not None:
|
109
|
-
return True
|
107
|
+
"""检查变量是否存在于上下文中
|
110
108
|
|
109
|
+
检查顺序:
|
110
|
+
1. 测试上下文
|
111
|
+
2. 全局上下文(包含YAML变量)
|
112
|
+
"""
|
111
113
|
# 检查测试上下文
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
114
|
+
try:
|
115
|
+
from pytest_dsl.core.keyword_manager import keyword_manager
|
116
|
+
current_context = getattr(keyword_manager, 'current_context', None)
|
117
|
+
if current_context and current_context.has(var_name):
|
118
|
+
return True
|
119
|
+
except ImportError:
|
120
|
+
pass
|
121
|
+
|
122
|
+
# 检查全局上下文(包含YAML变量的统一访问)
|
118
123
|
return global_context.has_variable(var_name)
|
119
124
|
|
120
125
|
|
121
126
|
def get_variable(var_name: str) -> Any:
|
122
|
-
"""
|
123
|
-
# 先从YAML变量中获取
|
124
|
-
from pytest_dsl.core.yaml_vars import yaml_vars
|
125
|
-
yaml_value = yaml_vars.get_variable(var_name)
|
126
|
-
if yaml_value is not None:
|
127
|
-
return yaml_value
|
127
|
+
"""获取变量值
|
128
128
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
129
|
+
获取顺序:
|
130
|
+
1. 测试上下文
|
131
|
+
2. 全局上下文(包含YAML变量)
|
132
|
+
"""
|
133
|
+
# 先从测试上下文获取
|
134
|
+
try:
|
135
|
+
from pytest_dsl.core.keyword_manager import keyword_manager
|
136
|
+
current_context = getattr(keyword_manager, 'current_context', None)
|
137
|
+
if current_context and current_context.has(var_name):
|
138
|
+
return current_context.get(var_name)
|
139
|
+
except ImportError:
|
140
|
+
pass
|
141
|
+
|
142
|
+
# 再从全局上下文获取(包含对YAML变量的统一访问)
|
136
143
|
if global_context.has_variable(var_name):
|
137
144
|
return global_context.get_variable(var_name)
|
138
145
|
|
@@ -163,4 +170,4 @@ def deep_merge(base: Dict, override: Dict) -> Dict:
|
|
163
170
|
# 覆盖或添加值
|
164
171
|
result[key] = value
|
165
172
|
|
166
|
-
return result
|
173
|
+
return result
|