pytest-dsl 0.15.2__py3-none-any.whl → 0.15.4__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 +59 -5
- pytest_dsl/core/custom_keyword_manager.py +14 -8
- pytest_dsl/core/dsl_executor.py +28 -8
- 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/utils.py +34 -27
- pytest_dsl/core/validator.py +71 -1
- 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-0.15.2.dist-info → pytest_dsl-0.15.4.dist-info}/METADATA +1 -1
- {pytest_dsl-0.15.2.dist-info → pytest_dsl-0.15.4.dist-info}/RECORD +22 -20
- {pytest_dsl-0.15.2.dist-info → pytest_dsl-0.15.4.dist-info}/WHEEL +0 -0
- {pytest_dsl-0.15.2.dist-info → pytest_dsl-0.15.4.dist-info}/entry_points.txt +0 -0
- {pytest_dsl-0.15.2.dist-info → pytest_dsl-0.15.4.dist-info}/licenses/LICENSE +0 -0
- {pytest_dsl-0.15.2.dist-info → pytest_dsl-0.15.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,202 @@
|
|
1
|
+
"""变量提供者模块
|
2
|
+
|
3
|
+
定义了变量提供者的接口和具体实现,用于将不同来源的变量注入到TestContext中。
|
4
|
+
这样可以实现解耦,让关键字只需要通过context获取变量,而不需要直接依赖特定的变量源。
|
5
|
+
"""
|
6
|
+
|
7
|
+
from abc import ABC, abstractmethod
|
8
|
+
from typing import Any, Optional, Dict
|
9
|
+
|
10
|
+
|
11
|
+
class VariableProvider(ABC):
|
12
|
+
"""变量提供者接口
|
13
|
+
|
14
|
+
所有的变量提供者都需要实现这个接口,以便可以注册到TestContext中。
|
15
|
+
"""
|
16
|
+
|
17
|
+
@abstractmethod
|
18
|
+
def get_variable(self, key: str) -> Optional[Any]:
|
19
|
+
"""获取变量值
|
20
|
+
|
21
|
+
Args:
|
22
|
+
key: 变量键
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
变量值,如果不存在返回None
|
26
|
+
"""
|
27
|
+
pass
|
28
|
+
|
29
|
+
@abstractmethod
|
30
|
+
def has_variable(self, key: str) -> bool:
|
31
|
+
"""检查变量是否存在
|
32
|
+
|
33
|
+
Args:
|
34
|
+
key: 变量键
|
35
|
+
|
36
|
+
Returns:
|
37
|
+
True如果变量存在,否则False
|
38
|
+
"""
|
39
|
+
pass
|
40
|
+
|
41
|
+
def get_all_variables(self) -> Dict[str, Any]:
|
42
|
+
"""获取所有变量(可选实现)
|
43
|
+
|
44
|
+
Returns:
|
45
|
+
包含所有变量的字典
|
46
|
+
"""
|
47
|
+
return {}
|
48
|
+
|
49
|
+
|
50
|
+
class YAMLVariableProvider(VariableProvider):
|
51
|
+
"""YAML变量提供者
|
52
|
+
|
53
|
+
将yaml_vars包装成变量提供者,使其可以注入到TestContext中。
|
54
|
+
"""
|
55
|
+
|
56
|
+
def __init__(self):
|
57
|
+
# 延迟导入,避免循环依赖
|
58
|
+
from pytest_dsl.core.yaml_vars import yaml_vars
|
59
|
+
self.yaml_vars = yaml_vars
|
60
|
+
|
61
|
+
def get_variable(self, key: str) -> Optional[Any]:
|
62
|
+
"""从YAML变量源获取变量值"""
|
63
|
+
return self.yaml_vars.get_variable(key)
|
64
|
+
|
65
|
+
def has_variable(self, key: str) -> bool:
|
66
|
+
"""检查YAML变量源中是否存在变量"""
|
67
|
+
return self.yaml_vars.has_variable(key)
|
68
|
+
|
69
|
+
def get_all_variables(self) -> Dict[str, Any]:
|
70
|
+
"""获取所有YAML变量"""
|
71
|
+
return self.yaml_vars.get_all_variables()
|
72
|
+
|
73
|
+
|
74
|
+
class GlobalContextVariableProvider(VariableProvider):
|
75
|
+
"""全局上下文变量提供者
|
76
|
+
|
77
|
+
将global_context包装成变量提供者,但需要避免和YAML变量重复。
|
78
|
+
"""
|
79
|
+
|
80
|
+
def __init__(self):
|
81
|
+
# 延迟导入,避免循环依赖
|
82
|
+
from pytest_dsl.core.global_context import global_context
|
83
|
+
self.global_context = global_context
|
84
|
+
|
85
|
+
def get_variable(self, key: str) -> Optional[Any]:
|
86
|
+
"""从全局上下文获取变量值"""
|
87
|
+
# 注意:global_context的get_variable方法内部也会调用yaml_vars
|
88
|
+
# 为了避免重复,这里直接访问存储的变量
|
89
|
+
try:
|
90
|
+
# 直接从全局变量存储中获取,跳过YAML变量
|
91
|
+
from filelock import FileLock
|
92
|
+
with FileLock(self.global_context._lock_file):
|
93
|
+
variables = self.global_context._load_variables()
|
94
|
+
return variables.get(key)
|
95
|
+
except Exception:
|
96
|
+
return None
|
97
|
+
|
98
|
+
def has_variable(self, key: str) -> bool:
|
99
|
+
"""检查全局上下文中是否存在变量"""
|
100
|
+
try:
|
101
|
+
from filelock import FileLock
|
102
|
+
with FileLock(self.global_context._lock_file):
|
103
|
+
variables = self.global_context._load_variables()
|
104
|
+
return key in variables
|
105
|
+
except Exception:
|
106
|
+
return False
|
107
|
+
|
108
|
+
def get_all_variables(self) -> Dict[str, Any]:
|
109
|
+
"""获取所有全局变量"""
|
110
|
+
try:
|
111
|
+
from filelock import FileLock
|
112
|
+
with FileLock(self.global_context._lock_file):
|
113
|
+
return self.global_context._load_variables()
|
114
|
+
except Exception:
|
115
|
+
return {}
|
116
|
+
|
117
|
+
|
118
|
+
class CompositeVariableProvider(VariableProvider):
|
119
|
+
"""组合变量提供者
|
120
|
+
|
121
|
+
可以将多个变量提供者组合在一起,按优先级顺序查找变量。
|
122
|
+
"""
|
123
|
+
|
124
|
+
def __init__(self, providers: list = None):
|
125
|
+
"""初始化组合变量提供者
|
126
|
+
|
127
|
+
Args:
|
128
|
+
providers: 变量提供者列表,按优先级排序(索引越小优先级越高)
|
129
|
+
"""
|
130
|
+
self.providers = providers or []
|
131
|
+
|
132
|
+
def add_provider(self, provider: VariableProvider):
|
133
|
+
"""添加变量提供者"""
|
134
|
+
if provider not in self.providers:
|
135
|
+
self.providers.append(provider)
|
136
|
+
|
137
|
+
def remove_provider(self, provider: VariableProvider):
|
138
|
+
"""移除变量提供者"""
|
139
|
+
if provider in self.providers:
|
140
|
+
self.providers.remove(provider)
|
141
|
+
|
142
|
+
def get_variable(self, key: str) -> Optional[Any]:
|
143
|
+
"""按优先级顺序从提供者中获取变量"""
|
144
|
+
for provider in self.providers:
|
145
|
+
try:
|
146
|
+
value = provider.get_variable(key)
|
147
|
+
if value is not None:
|
148
|
+
return value
|
149
|
+
except Exception:
|
150
|
+
continue
|
151
|
+
return None
|
152
|
+
|
153
|
+
def has_variable(self, key: str) -> bool:
|
154
|
+
"""检查是否有任何提供者包含该变量"""
|
155
|
+
for provider in self.providers:
|
156
|
+
try:
|
157
|
+
if provider.has_variable(key):
|
158
|
+
return True
|
159
|
+
except Exception:
|
160
|
+
continue
|
161
|
+
return False
|
162
|
+
|
163
|
+
def get_all_variables(self) -> Dict[str, Any]:
|
164
|
+
"""获取所有变量,优先级高的覆盖优先级低的"""
|
165
|
+
all_vars = {}
|
166
|
+
|
167
|
+
# 从后往前遍历,让优先级高的覆盖优先级低的
|
168
|
+
for provider in reversed(self.providers):
|
169
|
+
try:
|
170
|
+
vars_dict = provider.get_all_variables()
|
171
|
+
all_vars.update(vars_dict)
|
172
|
+
except Exception:
|
173
|
+
continue
|
174
|
+
|
175
|
+
return all_vars
|
176
|
+
|
177
|
+
|
178
|
+
# 创建默认的变量提供者实例
|
179
|
+
def create_default_variable_providers() -> list:
|
180
|
+
"""创建默认的变量提供者列表
|
181
|
+
|
182
|
+
按优先级排序:YAML变量 > 全局上下文变量
|
183
|
+
|
184
|
+
Returns:
|
185
|
+
变量提供者列表
|
186
|
+
"""
|
187
|
+
providers = [
|
188
|
+
YAMLVariableProvider(),
|
189
|
+
GlobalContextVariableProvider()
|
190
|
+
]
|
191
|
+
return providers
|
192
|
+
|
193
|
+
|
194
|
+
def setup_context_with_default_providers(context):
|
195
|
+
"""为TestContext设置默认的变量提供者
|
196
|
+
|
197
|
+
Args:
|
198
|
+
context: TestContext实例
|
199
|
+
"""
|
200
|
+
providers = create_default_variable_providers()
|
201
|
+
for provider in providers:
|
202
|
+
context.register_external_variable_provider(provider)
|
@@ -1,50 +1,54 @@
|
|
1
|
+
"""变量替换工具模块
|
2
|
+
|
3
|
+
该模块提供了高级的变量替换功能,支持复杂的变量访问语法。
|
4
|
+
"""
|
5
|
+
|
1
6
|
import re
|
2
7
|
import json
|
3
|
-
from typing import Any, Dict, List
|
8
|
+
from typing import Any, Dict, List
|
4
9
|
from pytest_dsl.core.global_context import global_context
|
5
10
|
from pytest_dsl.core.context import TestContext
|
6
|
-
from pytest_dsl.core.yaml_vars import yaml_vars
|
7
11
|
|
8
12
|
|
9
13
|
class VariableReplacer:
|
10
|
-
"""
|
11
|
-
|
12
|
-
提供统一的变量替换功能,支持字符串、字典和列表中的变量替换。
|
13
|
-
变量查找优先级:本地变量 > 测试上下文 > YAML变量 > 全局上下文
|
14
|
-
"""
|
14
|
+
"""变量替换器,支持高级变量访问语法"""
|
15
15
|
|
16
16
|
def __init__(self, local_variables: Dict[str, Any] = None, test_context: TestContext = None):
|
17
17
|
"""初始化变量替换器
|
18
18
|
|
19
19
|
Args:
|
20
20
|
local_variables: 本地变量字典
|
21
|
-
test_context:
|
21
|
+
test_context: 测试上下文
|
22
22
|
"""
|
23
23
|
self.local_variables = local_variables or {}
|
24
|
-
self._test_context = test_context
|
24
|
+
self._test_context = test_context
|
25
25
|
|
26
26
|
@property
|
27
27
|
def test_context(self) -> TestContext:
|
28
|
-
"""
|
28
|
+
"""获取测试上下文,如果没有提供则尝试从关键字管理器获取"""
|
29
|
+
if self._test_context:
|
30
|
+
return self._test_context
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
if hasattr(self._test_context, 'executor') and self._test_context.executor is not None:
|
37
|
-
return self._test_context.executor.test_context
|
38
|
-
return self._test_context
|
32
|
+
# 尝试从关键字管理器获取当前上下文
|
33
|
+
try:
|
34
|
+
from pytest_dsl.core.keyword_manager import keyword_manager
|
35
|
+
return getattr(keyword_manager, 'current_context', None)
|
36
|
+
except ImportError:
|
37
|
+
return None
|
39
38
|
|
40
39
|
def get_variable(self, var_name: str) -> Any:
|
41
|
-
"""
|
40
|
+
"""获取变量值,按优先级顺序查找
|
41
|
+
|
42
|
+
查找顺序:
|
43
|
+
1. 本地变量
|
44
|
+
2. 测试上下文
|
45
|
+
3. 全局上下文(包含YAML变量的访问)
|
42
46
|
|
43
47
|
Args:
|
44
48
|
var_name: 变量名
|
45
49
|
|
46
50
|
Returns:
|
47
|
-
|
51
|
+
变量值
|
48
52
|
|
49
53
|
Raises:
|
50
54
|
KeyError: 当变量不存在时
|
@@ -54,17 +58,13 @@ class VariableReplacer:
|
|
54
58
|
value = self.local_variables[var_name]
|
55
59
|
return self._convert_value(value)
|
56
60
|
|
57
|
-
#
|
58
|
-
|
59
|
-
|
61
|
+
# 从测试上下文中获取(优先级高于YAML变量)
|
62
|
+
context = self.test_context
|
63
|
+
if context and context.has(var_name):
|
64
|
+
value = context.get(var_name)
|
60
65
|
return self._convert_value(value)
|
61
66
|
|
62
|
-
#
|
63
|
-
yaml_value = yaml_vars.get_variable(var_name)
|
64
|
-
if yaml_value is not None:
|
65
|
-
return self._convert_value(yaml_value)
|
66
|
-
|
67
|
-
# 从全局上下文获取
|
67
|
+
# 从全局上下文获取(包含对YAML变量的统一访问)
|
68
68
|
if global_context.has_variable(var_name):
|
69
69
|
value = global_context.get_variable(var_name)
|
70
70
|
return self._convert_value(value)
|
@@ -130,7 +130,8 @@ class VariableReplacer:
|
|
130
130
|
try:
|
131
131
|
var_value = self._parse_variable_path(var_ref)
|
132
132
|
# 替换变量引用
|
133
|
-
result = result[:match.start()] + str(var_value) +
|
133
|
+
result = result[:match.start()] + str(var_value) + \
|
134
|
+
result[match.end():]
|
134
135
|
except (KeyError, IndexError, TypeError) as e:
|
135
136
|
raise KeyError(f"无法解析变量引用 '${{{var_ref}}}': {str(e)}")
|
136
137
|
|
@@ -170,7 +171,8 @@ class VariableReplacer:
|
|
170
171
|
|
171
172
|
# 逐步访问路径
|
172
173
|
for part in path_parts[1:]:
|
173
|
-
current_value = self._access_value(
|
174
|
+
current_value = self._access_value(
|
175
|
+
current_value, part, root_var_name)
|
174
176
|
|
175
177
|
return current_value
|
176
178
|
|
@@ -258,14 +260,16 @@ class VariableReplacer:
|
|
258
260
|
raise KeyError(f"字典中不存在键 '{key}'")
|
259
261
|
return current_value[key]
|
260
262
|
else:
|
261
|
-
raise TypeError(
|
263
|
+
raise TypeError(
|
264
|
+
f"无法在 {type(current_value).__name__} 类型上使用字符串键访问")
|
262
265
|
|
263
266
|
# 处理数字索引
|
264
267
|
try:
|
265
268
|
index = int(key_content)
|
266
269
|
if isinstance(current_value, (list, tuple)):
|
267
270
|
if index >= len(current_value) or index < -len(current_value):
|
268
|
-
raise IndexError(
|
271
|
+
raise IndexError(
|
272
|
+
f"数组索引 {index} 超出范围,数组长度为 {len(current_value)}")
|
269
273
|
return current_value[index]
|
270
274
|
elif isinstance(current_value, dict):
|
271
275
|
# 字典也可以用数字键
|
@@ -274,7 +278,8 @@ class VariableReplacer:
|
|
274
278
|
raise KeyError(f"字典中不存在键 '{index}' 或 '{str_key}'")
|
275
279
|
return current_value.get(index, current_value.get(str_key))
|
276
280
|
else:
|
277
|
-
raise TypeError(
|
281
|
+
raise TypeError(
|
282
|
+
f"无法在 {type(current_value).__name__} 类型上使用索引访问")
|
278
283
|
except ValueError:
|
279
284
|
raise ValueError(f"无效的索引格式: '{key_content}'")
|
280
285
|
|
@@ -283,7 +288,8 @@ class VariableReplacer:
|
|
283
288
|
if isinstance(current_value, dict) and access_token in current_value:
|
284
289
|
return current_value[access_token]
|
285
290
|
else:
|
286
|
-
raise KeyError(
|
291
|
+
raise KeyError(
|
292
|
+
f"无法访问属性 '{access_token}',当前值类型是 {type(current_value).__name__}")
|
287
293
|
|
288
294
|
def replace_in_dict(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
289
295
|
"""递归替换字典中的变量引用
|
@@ -303,7 +309,8 @@ class VariableReplacer:
|
|
303
309
|
result = {}
|
304
310
|
for key, value in data.items():
|
305
311
|
# 替换键中的变量
|
306
|
-
new_key = self.replace_in_string(
|
312
|
+
new_key = self.replace_in_string(
|
313
|
+
key) if isinstance(key, str) else key
|
307
314
|
# 替换值中的变量
|
308
315
|
new_value = self.replace_in_value(value)
|
309
316
|
result[new_key] = new_value
|
@@ -409,4 +416,4 @@ class VariableReplacer:
|
|
409
416
|
return yaml.dump(replaced_data, allow_unicode=True)
|
410
417
|
except:
|
411
418
|
# 如果YAML解析失败,直接作为字符串处理
|
412
|
-
return self.replace_in_string(yaml_str)
|
419
|
+
return self.replace_in_string(yaml_str)
|
pytest_dsl/core/yaml_loader.py
CHANGED
@@ -31,50 +31,91 @@ def add_yaml_options(parser):
|
|
31
31
|
|
32
32
|
|
33
33
|
def load_yaml_variables_from_args(yaml_files=None, yaml_vars_dir=None,
|
34
|
-
project_root=None, environment=None
|
35
|
-
|
34
|
+
project_root=None, environment=None,
|
35
|
+
auto_load_default=None):
|
36
|
+
"""从命令行参数加载YAML变量
|
36
37
|
|
37
38
|
Args:
|
38
39
|
yaml_files: YAML文件列表
|
39
40
|
yaml_vars_dir: YAML变量目录路径
|
40
|
-
project_root:
|
41
|
+
project_root: 项目根目录
|
41
42
|
environment: 环境名称(用于hook加载)
|
43
|
+
auto_load_default: 是否自动加载默认配置
|
44
|
+
None - 根据用户输入智能判断
|
45
|
+
True - 强制加载默认配置
|
46
|
+
False - 不加载默认配置
|
42
47
|
"""
|
43
|
-
#
|
48
|
+
# 智能判断是否应该加载默认配置
|
49
|
+
if auto_load_default is None:
|
50
|
+
# 如果用户指定了具体的YAML文件,不自动加载默认配置
|
51
|
+
# 如果用户指定了具体的目录,也不自动加载默认配置
|
52
|
+
user_specified_files = bool(yaml_files)
|
53
|
+
user_specified_dir = bool(yaml_vars_dir)
|
54
|
+
auto_load_default = not (user_specified_files or user_specified_dir)
|
55
|
+
|
56
|
+
if not auto_load_default:
|
57
|
+
print("🎯 检测到用户指定了配置,跳过默认配置自动加载")
|
58
|
+
else:
|
59
|
+
print("📁 未指定配置,将自动加载默认配置目录")
|
60
|
+
|
61
|
+
# 首先尝试通过hook加载变量(最高优先级)
|
44
62
|
hook_variables = _load_variables_through_hooks(
|
45
63
|
project_root=project_root, environment=environment)
|
46
64
|
|
47
65
|
if hook_variables:
|
48
|
-
print(f"通过Hook加载了 {len(hook_variables)} 个变量")
|
66
|
+
print(f"🔌 通过Hook加载了 {len(hook_variables)} 个变量")
|
49
67
|
# 将hook变量加载到yaml_vars中
|
50
68
|
yaml_vars._variables.update(hook_variables)
|
51
69
|
|
52
|
-
#
|
70
|
+
# 加载用户指定的YAML文件(第二优先级)
|
53
71
|
if yaml_files:
|
54
72
|
yaml_vars.load_yaml_files(yaml_files)
|
55
|
-
print(f"
|
56
|
-
|
57
|
-
#
|
58
|
-
if yaml_vars_dir
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
print(f"已加载YAML变量目录: {yaml_vars_dir}")
|
66
|
-
loaded_files = yaml_vars.get_loaded_files()
|
67
|
-
if loaded_files:
|
68
|
-
# 过滤出当前目录的文件
|
69
|
-
if yaml_vars_dir:
|
73
|
+
print(f"📄 已加载用户指定的YAML文件: {', '.join(yaml_files)}")
|
74
|
+
|
75
|
+
# 加载用户指定的目录中的YAML文件(第三优先级)
|
76
|
+
if yaml_vars_dir:
|
77
|
+
if Path(yaml_vars_dir).exists():
|
78
|
+
yaml_vars.load_from_directory(yaml_vars_dir)
|
79
|
+
print(f"📂 已加载用户指定的YAML目录: {yaml_vars_dir}")
|
80
|
+
loaded_files = yaml_vars.get_loaded_files()
|
81
|
+
if loaded_files:
|
82
|
+
# 过滤出当前目录的文件
|
70
83
|
dir_files = [f for f in loaded_files if Path(
|
71
84
|
f).parent == Path(yaml_vars_dir)]
|
72
85
|
if dir_files:
|
73
|
-
print(f"目录中加载的文件: {', '.join(dir_files)}")
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
86
|
+
print(f" 目录中加载的文件: {', '.join(dir_files)}")
|
87
|
+
else:
|
88
|
+
print(f"⚠️ 用户指定的YAML目录不存在: {yaml_vars_dir}")
|
89
|
+
|
90
|
+
# 自动加载默认配置(最低优先级,仅在用户未指定配置时)
|
91
|
+
if auto_load_default:
|
92
|
+
default_yaml_vars_dir = None
|
93
|
+
if project_root:
|
94
|
+
# 默认使用项目根目录下的config目录
|
95
|
+
default_yaml_vars_dir = str(Path(project_root) / 'config')
|
96
|
+
print(f"🏠 使用默认YAML变量目录: {default_yaml_vars_dir}")
|
97
|
+
|
98
|
+
if default_yaml_vars_dir and Path(default_yaml_vars_dir).exists():
|
99
|
+
yaml_vars.load_from_directory(default_yaml_vars_dir)
|
100
|
+
print(f"📁 已加载默认YAML变量目录: {default_yaml_vars_dir}")
|
101
|
+
loaded_files = yaml_vars.get_loaded_files()
|
102
|
+
if loaded_files:
|
103
|
+
# 过滤出默认目录的文件
|
104
|
+
dir_files = [f for f in loaded_files if Path(
|
105
|
+
f).parent == Path(default_yaml_vars_dir)]
|
106
|
+
if dir_files:
|
107
|
+
print(f" 默认目录中加载的文件: {', '.join(dir_files)}")
|
108
|
+
elif default_yaml_vars_dir:
|
109
|
+
print(f"📁 默认YAML变量目录不存在: {default_yaml_vars_dir}")
|
110
|
+
|
111
|
+
# 显示最终加载的变量汇总
|
112
|
+
all_loaded_files = yaml_vars.get_loaded_files()
|
113
|
+
if all_loaded_files:
|
114
|
+
print(f"✅ 变量加载完成,共加载 {len(all_loaded_files)} 个文件")
|
115
|
+
if len(yaml_vars._variables) > 0:
|
116
|
+
print(f"📊 总共加载了 {len(yaml_vars._variables)} 个变量")
|
117
|
+
else:
|
118
|
+
print("⚠️ 未加载任何YAML变量文件")
|
78
119
|
|
79
120
|
# 加载完YAML变量后,自动连接远程服务器
|
80
121
|
load_remote_servers_from_yaml()
|
@@ -203,37 +244,136 @@ def load_yaml_variables(config):
|
|
203
244
|
)
|
204
245
|
|
205
246
|
|
206
|
-
def load_remote_servers_from_yaml():
|
207
|
-
"""从YAML变量中加载远程服务器配置
|
247
|
+
def load_remote_servers_from_yaml(variable_source=None):
|
248
|
+
"""从YAML变量中加载远程服务器配置
|
249
|
+
|
250
|
+
Args:
|
251
|
+
variable_source: 自定义变量源,如果不提供则使用全局上下文
|
252
|
+
"""
|
208
253
|
try:
|
209
254
|
from pytest_dsl.remote.keyword_client import remote_keyword_manager
|
210
255
|
|
211
|
-
#
|
212
|
-
|
213
|
-
|
214
|
-
|
256
|
+
# 支持自定义变量源
|
257
|
+
if variable_source and callable(variable_source):
|
258
|
+
variables = variable_source()
|
259
|
+
remote_servers = variables.get(
|
260
|
+
'remote_servers') if isinstance(variables, dict) else None
|
261
|
+
else:
|
262
|
+
# 使用默认的全局上下文
|
263
|
+
from pytest_dsl.core.global_context import global_context
|
264
|
+
remote_servers = global_context.get_variable('remote_servers')
|
215
265
|
|
216
|
-
|
266
|
+
if not remote_servers:
|
267
|
+
return []
|
268
|
+
|
269
|
+
# 支持两种格式:数组和字典
|
270
|
+
server_configs = []
|
271
|
+
|
272
|
+
if isinstance(remote_servers, list):
|
273
|
+
# 数组格式: [{'url': '...', 'alias': '...'}, ...]
|
274
|
+
server_configs = remote_servers
|
275
|
+
print(f"发现 {len(remote_servers)} 个远程服务器配置(数组格式)")
|
276
|
+
elif isinstance(remote_servers, dict):
|
277
|
+
# 字典格式: {'server1': {'url': '...', 'alias': '...'}, ...}
|
278
|
+
for server_name, server_config in remote_servers.items():
|
279
|
+
if isinstance(server_config, dict):
|
280
|
+
# 如果没有指定alias,使用键名作为alias
|
281
|
+
if 'alias' not in server_config:
|
282
|
+
server_config = server_config.copy()
|
283
|
+
server_config['alias'] = server_name
|
284
|
+
server_configs.append(server_config)
|
285
|
+
print(f"发现 {len(server_configs)} 个远程服务器配置(字典格式)")
|
286
|
+
else:
|
287
|
+
print(
|
288
|
+
f"警告:remote_servers配置格式不支持,期望数组或字典,得到 {type(remote_servers)}")
|
289
|
+
return []
|
290
|
+
|
291
|
+
results = []
|
217
292
|
|
218
293
|
# 注册远程服务器
|
219
|
-
for server_config in
|
294
|
+
for server_config in server_configs:
|
220
295
|
if isinstance(server_config, dict):
|
221
296
|
url = server_config.get('url')
|
222
297
|
alias = server_config.get('alias')
|
223
298
|
api_key = server_config.get('api_key')
|
299
|
+
sync_config = server_config.get('sync_config')
|
224
300
|
|
225
301
|
if url and alias:
|
226
302
|
print(f"自动连接远程服务器: {alias} -> {url}")
|
227
303
|
success = remote_keyword_manager.register_remote_server(
|
228
|
-
url, alias, api_key=api_key
|
304
|
+
url, alias, api_key=api_key, sync_config=sync_config
|
229
305
|
)
|
230
306
|
if success:
|
231
307
|
print(f"✓ 远程服务器 {alias} 连接成功")
|
232
308
|
else:
|
233
309
|
print(f"✗ 远程服务器 {alias} 连接失败")
|
234
310
|
|
311
|
+
results.append(
|
312
|
+
{'alias': alias, 'url': url, 'success': success})
|
313
|
+
else:
|
314
|
+
print(f"警告:服务器配置缺少必要字段 url 或 alias: {server_config}")
|
315
|
+
|
316
|
+
return results
|
317
|
+
|
235
318
|
except ImportError:
|
236
319
|
# 如果远程功能不可用,跳过
|
237
|
-
|
320
|
+
return []
|
238
321
|
except Exception as e:
|
239
322
|
print(f"自动连接远程服务器时出现警告: {e}")
|
323
|
+
return []
|
324
|
+
|
325
|
+
|
326
|
+
def register_remote_servers_from_config(servers_config, variable_providers=None):
|
327
|
+
"""从配置注册远程服务器的独立函数
|
328
|
+
|
329
|
+
这个函数可以被其他系统独立调用,不依赖YAML配置。
|
330
|
+
|
331
|
+
Args:
|
332
|
+
servers_config: 服务器配置列表或单个配置
|
333
|
+
variable_providers: 变量提供者列表,用于同步自定义变量
|
334
|
+
|
335
|
+
Returns:
|
336
|
+
dict: 注册结果
|
337
|
+
|
338
|
+
Examples:
|
339
|
+
>>> # 单个服务器
|
340
|
+
>>> config = {'url': 'http://server:8270', 'alias': 'test'}
|
341
|
+
>>> result = register_remote_servers_from_config(config)
|
342
|
+
|
343
|
+
>>> # 多个服务器
|
344
|
+
>>> configs = [
|
345
|
+
... {'url': 'http://server1:8270', 'alias': 'server1'},
|
346
|
+
... {'url': 'http://server2:8270', 'alias': 'server2'}
|
347
|
+
... ]
|
348
|
+
>>> results = register_remote_servers_from_config(configs)
|
349
|
+
|
350
|
+
>>> # 带变量提供者
|
351
|
+
>>> def my_vars():
|
352
|
+
... return {'env': 'prod', 'api_key': 'secret'}
|
353
|
+
>>> results = register_remote_servers_from_config(configs, [my_vars])
|
354
|
+
"""
|
355
|
+
try:
|
356
|
+
from pytest_dsl.core.remote_server_registry import remote_server_registry
|
357
|
+
|
358
|
+
# 如果有变量提供者,先添加它们
|
359
|
+
if variable_providers:
|
360
|
+
remote_server_registry.clear_variable_providers()
|
361
|
+
for provider in variable_providers:
|
362
|
+
if callable(provider):
|
363
|
+
remote_server_registry.add_variable_provider(provider)
|
364
|
+
|
365
|
+
# 确保servers_config是列表
|
366
|
+
if isinstance(servers_config, dict):
|
367
|
+
servers_config = [servers_config]
|
368
|
+
elif not isinstance(servers_config, list):
|
369
|
+
raise ValueError("servers_config必须是字典或字典列表")
|
370
|
+
|
371
|
+
# 使用注册器批量注册
|
372
|
+
return remote_server_registry.register_servers_from_config(servers_config)
|
373
|
+
|
374
|
+
except ImportError:
|
375
|
+
print("远程功能不可用,请检查依赖安装")
|
376
|
+
return {}
|
377
|
+
except Exception as e:
|
378
|
+
print(f"注册远程服务器时发生错误: {e}")
|
379
|
+
return {}
|
@@ -14,7 +14,6 @@ from typing import Dict, Any, Union
|
|
14
14
|
|
15
15
|
from pytest_dsl.core.keyword_manager import keyword_manager
|
16
16
|
from pytest_dsl.core.http_request import HTTPRequest
|
17
|
-
from pytest_dsl.core.yaml_vars import yaml_vars
|
18
17
|
from pytest_dsl.core.context import TestContext
|
19
18
|
|
20
19
|
# 配置日志
|
@@ -293,8 +292,8 @@ def http_request(context, **kwargs):
|
|
293
292
|
# 添加调试信息,检查客户端配置是否可用
|
294
293
|
print(f"🌐 HTTP请求 - 客户端: {client_name}")
|
295
294
|
|
296
|
-
#
|
297
|
-
http_clients_config =
|
295
|
+
# 从context获取http_clients配置(统一的变量获取方式)
|
296
|
+
http_clients_config = context.get("http_clients")
|
298
297
|
if http_clients_config:
|
299
298
|
print(f"✓ 找到http_clients配置,包含 {len(http_clients_config)} 个客户端")
|
300
299
|
if client_name in http_clients_config:
|
@@ -310,10 +309,15 @@ def http_request(context, **kwargs):
|
|
310
309
|
|
311
310
|
with allure.step(f"发送HTTP请求 (客户端: {client_name}"
|
312
311
|
f"{', 会话: ' + session_name if session_name else ''})"):
|
312
|
+
|
313
|
+
# 确保http_client_manager有正确的context引用
|
314
|
+
from pytest_dsl.core.http_client import http_client_manager
|
315
|
+
http_client_manager.set_context(context)
|
316
|
+
|
313
317
|
# 处理模板
|
314
318
|
if template_name:
|
315
|
-
# 从
|
316
|
-
http_templates =
|
319
|
+
# 从context获取模板配置(统一的变量获取方式)
|
320
|
+
http_templates = context.get("http_templates") or {}
|
317
321
|
template = http_templates.get(template_name)
|
318
322
|
|
319
323
|
if not template:
|