pytest-dsl 0.1.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.
Files changed (63) hide show
  1. pytest_dsl/__init__.py +10 -0
  2. pytest_dsl/cli.py +44 -0
  3. pytest_dsl/conftest_adapter.py +4 -0
  4. pytest_dsl/core/__init__.py +0 -0
  5. pytest_dsl/core/auth_provider.py +409 -0
  6. pytest_dsl/core/auto_decorator.py +181 -0
  7. pytest_dsl/core/auto_directory.py +81 -0
  8. pytest_dsl/core/context.py +23 -0
  9. pytest_dsl/core/custom_auth_example.py +425 -0
  10. pytest_dsl/core/dsl_executor.py +329 -0
  11. pytest_dsl/core/dsl_executor_utils.py +84 -0
  12. pytest_dsl/core/global_context.py +103 -0
  13. pytest_dsl/core/http_client.py +411 -0
  14. pytest_dsl/core/http_request.py +810 -0
  15. pytest_dsl/core/keyword_manager.py +109 -0
  16. pytest_dsl/core/lexer.py +139 -0
  17. pytest_dsl/core/parser.py +197 -0
  18. pytest_dsl/core/parsetab.py +76 -0
  19. pytest_dsl/core/plugin_discovery.py +187 -0
  20. pytest_dsl/core/utils.py +146 -0
  21. pytest_dsl/core/variable_utils.py +267 -0
  22. pytest_dsl/core/yaml_loader.py +62 -0
  23. pytest_dsl/core/yaml_vars.py +75 -0
  24. pytest_dsl/docs/custom_keywords.md +140 -0
  25. pytest_dsl/examples/__init__.py +5 -0
  26. pytest_dsl/examples/assert/assertion_example.auto +44 -0
  27. pytest_dsl/examples/assert/boolean_test.auto +34 -0
  28. pytest_dsl/examples/assert/expression_test.auto +49 -0
  29. pytest_dsl/examples/http/__init__.py +3 -0
  30. pytest_dsl/examples/http/builtin_auth_test.auto +79 -0
  31. pytest_dsl/examples/http/csrf_auth_test.auto +64 -0
  32. pytest_dsl/examples/http/custom_auth_test.auto +76 -0
  33. pytest_dsl/examples/http/file_reference_test.auto +111 -0
  34. pytest_dsl/examples/http/http_advanced.auto +91 -0
  35. pytest_dsl/examples/http/http_example.auto +147 -0
  36. pytest_dsl/examples/http/http_length_test.auto +55 -0
  37. pytest_dsl/examples/http/http_retry_assertions.auto +91 -0
  38. pytest_dsl/examples/http/http_retry_assertions_enhanced.auto +94 -0
  39. pytest_dsl/examples/http/http_with_yaml.auto +58 -0
  40. pytest_dsl/examples/http/new_retry_test.auto +22 -0
  41. pytest_dsl/examples/http/retry_assertions_only.auto +52 -0
  42. pytest_dsl/examples/http/retry_config_only.auto +49 -0
  43. pytest_dsl/examples/http/retry_debug.auto +22 -0
  44. pytest_dsl/examples/http/retry_with_fix.auto +21 -0
  45. pytest_dsl/examples/http/simple_retry.auto +20 -0
  46. pytest_dsl/examples/http/vars.yaml +55 -0
  47. pytest_dsl/examples/http_clients.yaml +48 -0
  48. pytest_dsl/examples/keyword_example.py +70 -0
  49. pytest_dsl/examples/test_assert.py +16 -0
  50. pytest_dsl/examples/test_http.py +168 -0
  51. pytest_dsl/keywords/__init__.py +10 -0
  52. pytest_dsl/keywords/assertion_keywords.py +610 -0
  53. pytest_dsl/keywords/global_keywords.py +51 -0
  54. pytest_dsl/keywords/http_keywords.py +430 -0
  55. pytest_dsl/keywords/system_keywords.py +17 -0
  56. pytest_dsl/main_adapter.py +7 -0
  57. pytest_dsl/plugin.py +44 -0
  58. pytest_dsl-0.1.0.dist-info/METADATA +537 -0
  59. pytest_dsl-0.1.0.dist-info/RECORD +63 -0
  60. pytest_dsl-0.1.0.dist-info/WHEEL +5 -0
  61. pytest_dsl-0.1.0.dist-info/entry_points.txt +5 -0
  62. pytest_dsl-0.1.0.dist-info/licenses/LICENSE +21 -0
  63. pytest_dsl-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,187 @@
1
+ """插件发现模块
2
+
3
+ 该模块提供了下列功能:
4
+ 1. 使用entry_points机制发现和加载第三方关键字插件
5
+ 2. 扫描本地内置关键字(向后兼容)
6
+ 3. 自动扫描并导入用户项目中的keywords目录下的关键字模块
7
+
8
+ 自定义关键字目录结构:
9
+ - 项目根目录/
10
+ - keywords/ # 关键字根目录
11
+ - __init__.py # 可选,如果要作为包导入
12
+ - my_keywords.py # 顶层关键字模块
13
+ - another_module.py # 顶层关键字模块
14
+ - web/ # 子目录(可选作为子包)
15
+ - __init__.py # 可选,如果要作为子包导入
16
+ - selenium_keywords.py # 子目录中的关键字模块
17
+
18
+ 每个关键字模块中应使用keyword_manager.register装饰器注册关键字。
19
+ """
20
+
21
+ import importlib
22
+ import importlib.util
23
+ import importlib.metadata
24
+ import pkgutil
25
+ import os
26
+ import sys
27
+ from pathlib import Path
28
+ from typing import List
29
+
30
+ from pytest_dsl.core.keyword_manager import keyword_manager
31
+
32
+
33
+ def discover_installed_plugins() -> List[str]:
34
+ """
35
+ 发现所有已安装的pytest-dsl关键字插件
36
+
37
+ 通过entry_points机制查找所有声明了'pytest_dsl.keywords'入口点的包
38
+
39
+ Returns:
40
+ List[str]: 已安装的插件包名列表
41
+ """
42
+ plugins = []
43
+ try:
44
+ eps = importlib.metadata.entry_points(group='pytest_dsl.keywords')
45
+ for ep in eps:
46
+ plugins.append(ep.module)
47
+ except Exception as e:
48
+ print(f"发现插件时出错: {e}")
49
+ return plugins
50
+
51
+
52
+ def load_plugin_keywords(plugin_name: str) -> None:
53
+ """
54
+ 加载指定插件包中的所有关键字
55
+
56
+ Args:
57
+ plugin_name: 插件包名
58
+ """
59
+ try:
60
+ # 导入插件包
61
+ plugin = importlib.import_module(plugin_name)
62
+
63
+ # 如果插件有register_keywords函数,调用它
64
+ if hasattr(plugin, 'register_keywords') and callable(plugin.register_keywords):
65
+ plugin.register_keywords(keyword_manager)
66
+ return
67
+
68
+ # 否则,遍历包中的所有模块
69
+ if hasattr(plugin, '__path__'):
70
+ for _, name, is_pkg in pkgutil.iter_modules(plugin.__path__, plugin.__name__ + '.'):
71
+ if not is_pkg:
72
+ try:
73
+ module = importlib.import_module(name)
74
+ # 模块已导入,关键字装饰器会自动注册
75
+ except ImportError as e:
76
+ print(f"无法导入模块 {name}: {e}")
77
+ except ImportError as e:
78
+ print(f"无法导入插件 {plugin_name}: {e}")
79
+
80
+
81
+ def load_all_plugins() -> None:
82
+ """
83
+ 发现并加载所有已安装的关键字插件
84
+ """
85
+ plugins = discover_installed_plugins()
86
+ for plugin_name in plugins:
87
+ load_plugin_keywords(plugin_name)
88
+
89
+
90
+ def _load_module_from_file(file_path):
91
+ """
92
+ 从文件路径动态加载Python模块
93
+
94
+ Args:
95
+ file_path: Python文件路径
96
+
97
+ Returns:
98
+ 加载成功返回True,否则返回False
99
+ """
100
+ try:
101
+ module_name = file_path.stem
102
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
103
+ if spec:
104
+ module = importlib.util.module_from_spec(spec)
105
+ spec.loader.exec_module(module)
106
+ print(f"已加载项目关键字模块: {module_name}")
107
+ return True
108
+ except Exception as e:
109
+ print(f"加载项目关键字模块 {file_path.name} 时出错: {e}")
110
+ return False
111
+
112
+
113
+ def scan_local_keywords() -> None:
114
+ """
115
+ 扫描本地keywords目录中的关键字
116
+
117
+ 1. 先尝试导入内置的keywords包(向后兼容)
118
+ 2. 再查找并导入用户项目根目录下的keywords目录中的Python模块
119
+ """
120
+ # 1. 尝试导入内置的keywords包
121
+ try:
122
+ import keywords
123
+ except ImportError:
124
+ print("提示: 未找到内置关键字包")
125
+
126
+ # 2. 查找并导入用户项目中的keywords目录
127
+ try:
128
+ # 获取当前工作目录,通常是用户项目的根目录
129
+ project_root = Path(os.getcwd())
130
+ keywords_dir = project_root / 'keywords'
131
+
132
+ if keywords_dir.exists() and keywords_dir.is_dir():
133
+ print(f"发现项目关键字目录: {keywords_dir}")
134
+
135
+ # 将keywords目录添加到Python路径中,以便能够导入
136
+ if str(keywords_dir) not in sys.path:
137
+ sys.path.insert(0, str(keywords_dir))
138
+
139
+ # 首先尝试作为包导入整个keywords目录
140
+ if (keywords_dir / '__init__.py').exists():
141
+ try:
142
+ importlib.import_module('keywords')
143
+ print("已加载项目keywords包")
144
+ except ImportError as e:
145
+ print(f"导入项目keywords包失败: {e}")
146
+
147
+ # 遍历keywords目录下的所有Python文件(包括子目录)
148
+ loaded_modules = 0
149
+
150
+ # 先加载顶层目录中的模块
151
+ for file_path in keywords_dir.glob('*.py'):
152
+ if file_path.name != '__init__.py':
153
+ if _load_module_from_file(file_path):
154
+ loaded_modules += 1
155
+
156
+ # 然后遍历子目录
157
+ for subdir in [d for d in keywords_dir.iterdir() if d.is_dir()]:
158
+ # 检查子目录是否为Python包
159
+ init_file = subdir / '__init__.py'
160
+ if init_file.exists():
161
+ # 尝试作为包导入
162
+ subdir_name = subdir.name
163
+ try:
164
+ importlib.import_module(f'keywords.{subdir_name}')
165
+ print(f"已加载项目关键字子包: {subdir_name}")
166
+ loaded_modules += 1
167
+ except ImportError as e:
168
+ print(f"导入项目关键字子包 {subdir_name} 失败: {e}")
169
+
170
+ # 无论是否为包,都尝试直接加载其中的Python文件
171
+ for file_path in subdir.glob('*.py'):
172
+ if file_path.name != '__init__.py':
173
+ if _load_module_from_file(file_path):
174
+ loaded_modules += 1
175
+
176
+ if loaded_modules > 0:
177
+ print(f"成功从项目中加载了 {loaded_modules} 个关键字模块")
178
+ else:
179
+ print("未从项目中加载到任何关键字模块")
180
+ else:
181
+ print("提示: 未在项目中找到keywords目录")
182
+ except Exception as e:
183
+ print(f"扫描项目关键字时出错: {e}")
184
+
185
+
186
+ if __name__ == "__main__":
187
+ load_all_plugins()
@@ -0,0 +1,146 @@
1
+ """工具函数模块
2
+
3
+ 该模块提供了各种共享的工具函数。
4
+ """
5
+
6
+ import re
7
+ from typing import Dict, Any, Union, List
8
+
9
+ from pytest_dsl.core.global_context import global_context
10
+
11
+
12
+ def replace_variables_in_string(value: str) -> str:
13
+ """替换字符串中的变量引用,包括嵌套的点号引用
14
+
15
+ Args:
16
+ value: 包含变量引用的字符串
17
+
18
+ Returns:
19
+ 替换后的字符串
20
+ """
21
+ if not isinstance(value, str):
22
+ return value
23
+
24
+ # 基本变量引用模式: ${variable}
25
+ basic_pattern = r'\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}'
26
+
27
+ # 嵌套引用模式: ${variable.field.subfield}
28
+ nested_pattern = r'\$\{([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)+)\}'
29
+
30
+ # 先处理嵌套引用
31
+ matches = list(re.finditer(nested_pattern, value))
32
+ for match in reversed(matches):
33
+ var_ref = match.group(1) # 例如: "api_test_data.user_id"
34
+ parts = var_ref.split('.')
35
+
36
+ # 获取根变量
37
+ root_var_name = parts[0]
38
+ if context_has_variable(root_var_name):
39
+ root_var = get_variable(root_var_name)
40
+
41
+ # 递归访问嵌套属性
42
+ var_value = root_var
43
+ for part in parts[1:]:
44
+ if isinstance(var_value, dict) and part in var_value:
45
+ var_value = var_value[part]
46
+ else:
47
+ # 无法解析的属性路径
48
+ var_value = f"${{{var_ref}}}" # 保持原样
49
+ break
50
+
51
+ # 替换变量引用
52
+ value = value[:match.start()] + str(var_value) + value[match.end():]
53
+
54
+ # 再处理基本引用
55
+ matches = list(re.finditer(basic_pattern, value))
56
+ for match in reversed(matches):
57
+ var_name = match.group(1)
58
+ if context_has_variable(var_name):
59
+ var_value = get_variable(var_name)
60
+ value = value[:match.start()] + str(var_value) + value[match.end():]
61
+
62
+ return value
63
+
64
+
65
+ def replace_variables_in_dict(data: Union[Dict, List, str]) -> Union[Dict, List, str]:
66
+ """递归替换字典中的变量引用
67
+
68
+ Args:
69
+ data: 包含变量引用的字典、列表或字符串
70
+
71
+ Returns:
72
+ 替换变量后的数据
73
+ """
74
+ if isinstance(data, dict):
75
+ return {k: replace_variables_in_dict(v) for k, v in data.items()}
76
+ elif isinstance(data, list):
77
+ return [replace_variables_in_dict(item) for item in data]
78
+ elif isinstance(data, str) and '${' in data:
79
+ return replace_variables_in_string(data)
80
+ else:
81
+ return data
82
+
83
+
84
+ def context_has_variable(var_name: str) -> bool:
85
+ """检查变量是否存在于全局上下文"""
86
+ # 先检查YAML变量
87
+ from pytest_dsl.core.yaml_vars import yaml_vars
88
+ if yaml_vars.get_variable(var_name) is not None:
89
+ return True
90
+
91
+ # 检查测试上下文
92
+ from pytest_dsl.core.keyword_manager import keyword_manager
93
+ current_context = getattr(keyword_manager, 'current_context', None)
94
+ if current_context and current_context.has(var_name):
95
+ return True
96
+
97
+ # 再检查全局上下文
98
+ return global_context.has_variable(var_name)
99
+
100
+
101
+ def get_variable(var_name: str) -> Any:
102
+ """获取变量值,先从YAML变量中获取,再从全局上下文获取"""
103
+ # 先从YAML变量中获取
104
+ from pytest_dsl.core.yaml_vars import yaml_vars
105
+ yaml_value = yaml_vars.get_variable(var_name)
106
+ if yaml_value is not None:
107
+ return yaml_value
108
+
109
+ # 检查测试上下文
110
+ from pytest_dsl.core.keyword_manager import keyword_manager
111
+ current_context = getattr(keyword_manager, 'current_context', None)
112
+ if current_context and current_context.has(var_name):
113
+ return current_context.get(var_name)
114
+
115
+ # 再从全局上下文获取
116
+ if global_context.has_variable(var_name):
117
+ return global_context.get_variable(var_name)
118
+
119
+ # 如果都没有找到,返回变量引用本身
120
+ return f"${{{var_name}}}"
121
+
122
+
123
+ def deep_merge(base: Dict, override: Dict) -> Dict:
124
+ """深度合并两个字典
125
+
126
+ Args:
127
+ base: 基础字典
128
+ override: 覆盖字典
129
+
130
+ Returns:
131
+ 合并后的字典
132
+ """
133
+ result = base.copy()
134
+
135
+ for key, value in override.items():
136
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
137
+ # 递归合并嵌套字典
138
+ result[key] = deep_merge(result[key], value)
139
+ elif key in result and isinstance(result[key], list) and isinstance(value, list):
140
+ # 合并列表
141
+ result[key] = result[key] + value
142
+ else:
143
+ # 覆盖或添加值
144
+ result[key] = value
145
+
146
+ return result
@@ -0,0 +1,267 @@
1
+ import re
2
+ import json
3
+ from typing import Any, Dict, List, Union
4
+ from pytest_dsl.core.global_context import global_context
5
+ from pytest_dsl.core.context import TestContext
6
+ from pytest_dsl.core.yaml_vars import yaml_vars
7
+
8
+
9
+ class VariableReplacer:
10
+ """统一的变量替换工具类
11
+
12
+ 提供统一的变量替换功能,支持字符串、字典和列表中的变量替换。
13
+ 变量查找优先级:本地变量 > 测试上下文 > YAML变量 > 全局上下文
14
+ """
15
+
16
+ def __init__(self, local_variables: Dict[str, Any] = None, test_context: TestContext = None):
17
+ """初始化变量替换器
18
+
19
+ Args:
20
+ local_variables: 本地变量字典
21
+ test_context: 测试上下文对象
22
+ """
23
+ self.local_variables = local_variables or {}
24
+ self._test_context = test_context or TestContext()
25
+
26
+ @property
27
+ def test_context(self) -> TestContext:
28
+ """获取测试上下文,确保始终使用最新的上下文对象
29
+
30
+ 如果上下文对象中包含executor属性,则使用executor的上下文
31
+ (这确保即使上下文被替换也能获取正确的引用)
32
+
33
+ Returns:
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
39
+
40
+ def get_variable(self, var_name: str) -> Any:
41
+ """获取变量值,按照优先级查找
42
+
43
+ Args:
44
+ var_name: 变量名
45
+
46
+ Returns:
47
+ 变量值,如果变量不存在则抛出 KeyError
48
+
49
+ Raises:
50
+ KeyError: 当变量不存在时
51
+ """
52
+ # 从本地变量获取
53
+ if var_name in self.local_variables:
54
+ value = self.local_variables[var_name]
55
+ return self._convert_value(value)
56
+
57
+ # 从测试上下文中获取
58
+ if self.test_context.has(var_name):
59
+ value = self.test_context.get(var_name)
60
+ return self._convert_value(value)
61
+
62
+ # 从YAML变量中获取
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
+ # 从全局上下文获取
68
+ if global_context.has_variable(var_name):
69
+ value = global_context.get_variable(var_name)
70
+ return self._convert_value(value)
71
+
72
+ # 如果变量不存在,抛出异常
73
+ raise KeyError(f"变量 '{var_name}' 不存在")
74
+
75
+ def _convert_value(self, value: Any) -> Any:
76
+ """转换值为正确的类型
77
+
78
+ Args:
79
+ value: 要转换的值
80
+
81
+ Returns:
82
+ 转换后的值
83
+ """
84
+ if isinstance(value, str):
85
+ # 处理布尔值
86
+ if value.lower() in ('true', 'false'):
87
+ return value.lower() == 'true'
88
+ # 处理数字
89
+ try:
90
+ if '.' in value:
91
+ return float(value)
92
+ return int(value)
93
+ except (ValueError, TypeError):
94
+ pass
95
+ return value
96
+
97
+ def replace_in_string(self, value: str) -> str:
98
+ """替换字符串中的变量引用
99
+
100
+ Args:
101
+ value: 包含变量引用的字符串
102
+
103
+ Returns:
104
+ 替换后的字符串
105
+
106
+ Raises:
107
+ KeyError: 当变量不存在时
108
+ """
109
+ if not isinstance(value, str) or '${' not in value:
110
+ return value
111
+
112
+ # 处理变量引用模式: ${variable} 或 ${variable.field.subfield}
113
+ pattern = r'\$\{([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)\}'
114
+
115
+ result = value
116
+ matches = list(re.finditer(pattern, result))
117
+
118
+ # 从后向前替换,避免位置偏移
119
+ for match in reversed(matches):
120
+ var_ref = match.group(1) # 例如: "api_test_data.user_id" 或 "variable"
121
+ parts = var_ref.split('.')
122
+
123
+ # 获取根变量
124
+ root_var_name = parts[0]
125
+ try:
126
+ root_var = self.get_variable(root_var_name)
127
+ except KeyError:
128
+ raise KeyError(f"变量 '{root_var_name}' 不存在")
129
+
130
+ # 递归访问嵌套属性
131
+ var_value = root_var
132
+ for part in parts[1:]:
133
+ if isinstance(var_value, dict) and part in var_value:
134
+ var_value = var_value[part]
135
+ else:
136
+ raise KeyError(f"无法访问属性 '{part}',变量 '{root_var_name}' 的类型是 {type(var_value).__name__}")
137
+
138
+ # 替换变量引用
139
+ result = result[:match.start()] + str(var_value) + result[match.end():]
140
+
141
+ return result
142
+
143
+ def replace_in_dict(self, data: Dict[str, Any]) -> Dict[str, Any]:
144
+ """递归替换字典中的变量引用
145
+
146
+ Args:
147
+ data: 包含变量引用的字典
148
+
149
+ Returns:
150
+ 替换后的字典
151
+
152
+ Raises:
153
+ KeyError: 当变量不存在时
154
+ """
155
+ if not isinstance(data, dict):
156
+ return data
157
+
158
+ result = {}
159
+ for key, value in data.items():
160
+ # 替换键中的变量
161
+ new_key = self.replace_in_string(key) if isinstance(key, str) else key
162
+ # 替换值中的变量
163
+ new_value = self.replace_in_value(value)
164
+ result[new_key] = new_value
165
+
166
+ return result
167
+
168
+ def replace_in_list(self, data: List[Any]) -> List[Any]:
169
+ """递归替换列表中的变量引用
170
+
171
+ Args:
172
+ data: 包含变量引用的列表
173
+
174
+ Returns:
175
+ 替换后的列表
176
+
177
+ Raises:
178
+ KeyError: 当变量不存在时
179
+ """
180
+ if not isinstance(data, list):
181
+ return data
182
+
183
+ return [self.replace_in_value(item) for item in data]
184
+
185
+ def replace_in_value(self, value: Any) -> Any:
186
+ """递归替换任意值中的变量引用
187
+
188
+ Args:
189
+ value: 任意值,可能是字符串、字典、列表等
190
+
191
+ Returns:
192
+ 替换后的值
193
+
194
+ Raises:
195
+ KeyError: 当变量不存在时
196
+ """
197
+ if isinstance(value, str):
198
+ return self.replace_in_string(value)
199
+ elif isinstance(value, dict):
200
+ return self.replace_in_dict(value)
201
+ elif isinstance(value, list):
202
+ return self.replace_in_list(value)
203
+ elif isinstance(value, (int, float, bool, type(None))):
204
+ return value
205
+ else:
206
+ # 对于其他类型,尝试转换为字符串后替换
207
+ try:
208
+ str_value = str(value)
209
+ if '${' in str_value:
210
+ replaced = self.replace_in_string(str_value)
211
+ # 尝试将替换后的字符串转换回原始类型
212
+ if isinstance(value, (int, float)):
213
+ return type(value)(replaced)
214
+ elif isinstance(value, bool):
215
+ return replaced.lower() == 'true'
216
+ return replaced
217
+ return value
218
+ except:
219
+ return value
220
+
221
+ def replace_in_json(self, json_str: str) -> str:
222
+ """替换JSON字符串中的变量引用
223
+
224
+ Args:
225
+ json_str: 包含变量引用的JSON字符串
226
+
227
+ Returns:
228
+ 替换后的JSON字符串
229
+
230
+ Raises:
231
+ KeyError: 当变量不存在时
232
+ json.JSONDecodeError: 当JSON解析失败时
233
+ """
234
+ try:
235
+ # 先解析JSON
236
+ data = json.loads(json_str)
237
+ # 替换变量
238
+ replaced_data = self.replace_in_value(data)
239
+ # 重新序列化为JSON
240
+ return json.dumps(replaced_data, ensure_ascii=False)
241
+ except json.JSONDecodeError:
242
+ # 如果JSON解析失败,直接作为字符串处理
243
+ return self.replace_in_string(json_str)
244
+
245
+ def replace_in_yaml(self, yaml_str: str) -> str:
246
+ """替换YAML字符串中的变量引用
247
+
248
+ Args:
249
+ yaml_str: 包含变量引用的YAML字符串
250
+
251
+ Returns:
252
+ 替换后的YAML字符串
253
+
254
+ Raises:
255
+ KeyError: 当变量不存在时
256
+ """
257
+ try:
258
+ import yaml
259
+ # 先解析YAML
260
+ data = yaml.safe_load(yaml_str)
261
+ # 替换变量
262
+ replaced_data = self.replace_in_value(data)
263
+ # 重新序列化为YAML
264
+ return yaml.dump(replaced_data, allow_unicode=True)
265
+ except:
266
+ # 如果YAML解析失败,直接作为字符串处理
267
+ return self.replace_in_string(yaml_str)
@@ -0,0 +1,62 @@
1
+ """YAML变量加载器模块
2
+
3
+ 该模块负责处理YAML变量文件的加载和管理,支持从命令行参数加载单个文件或目录。
4
+ """
5
+
6
+ import os
7
+ from pathlib import Path
8
+ from pytest_dsl.core.yaml_vars import yaml_vars
9
+
10
+
11
+ def add_yaml_options(parser):
12
+ """添加YAML变量相关的命令行参数选项
13
+
14
+ Args:
15
+ parser: pytest命令行参数解析器
16
+ """
17
+ group = parser.getgroup('yaml-vars')
18
+ group.addoption(
19
+ '--yaml-vars',
20
+ action='append',
21
+ default=[],
22
+ help='YAML变量文件路径,可以指定多个文件 (例如: --yaml-vars vars1.yaml --yaml-vars vars2.yaml)'
23
+ )
24
+ group.addoption(
25
+ '--yaml-vars-dir',
26
+ action='store',
27
+ default=None,
28
+ help='YAML变量文件目录路径,将加载该目录下所有.yaml文件,默认为项目根目录下的config目录'
29
+ )
30
+
31
+
32
+ def load_yaml_variables(config):
33
+ """加载YAML变量文件
34
+
35
+ 从命令行参数指定的文件和目录加载YAML变量。
36
+
37
+ Args:
38
+ config: pytest配置对象
39
+ """
40
+ # 加载单个YAML文件
41
+ yaml_files = config.getoption('--yaml-vars')
42
+ if yaml_files:
43
+ yaml_vars.load_yaml_files(yaml_files)
44
+ print(f"已加载YAML变量文件: {', '.join(yaml_files)}")
45
+
46
+ # 加载目录中的YAML文件
47
+ yaml_vars_dir = config.getoption('--yaml-vars-dir')
48
+ if yaml_vars_dir is None:
49
+ # 默认使用使用者项目根目录下的config目录
50
+ # 通过pytest的rootdir获取使用者的项目根目录
51
+ project_root = config.rootdir
52
+ yaml_vars_dir = str(project_root / 'config')
53
+ print(f"使用默认YAML变量目录: {yaml_vars_dir}")
54
+
55
+ if Path(yaml_vars_dir).exists():
56
+ yaml_vars.load_from_directory(yaml_vars_dir)
57
+ print(f"已加载YAML变量目录: {yaml_vars_dir}")
58
+ loaded_files = yaml_vars.get_loaded_files()
59
+ if loaded_files:
60
+ print(f"目录中加载的文件: {', '.join(loaded_files)}")
61
+ else:
62
+ print(f"YAML变量目录不存在: {yaml_vars_dir}")