pytest-dsl 0.12.0__tar.gz → 0.13.0__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.
- {pytest_dsl-0.12.0/pytest_dsl.egg-info → pytest_dsl-0.13.0}/PKG-INFO +1 -1
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pyproject.toml +1 -1
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/cli.py +25 -2
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/core/custom_keyword_manager.py +136 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/core/dsl_executor.py +37 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/core/http_request.py +135 -61
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/core/parser.py +7 -2
- pytest_dsl-0.13.0/pytest_dsl/core/parsetab.py +131 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/http/http_example.auto +1 -1
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/http/http_retry_assertions.auto +4 -4
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/http/http_retry_assertions_enhanced.auto +2 -2
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/http/new_retry_test.auto +1 -1
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/http/retry_assertions_only.auto +3 -3
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/http/retry_config_only.auto +3 -3
- pytest_dsl-0.13.0/pytest_dsl/examples/http/retry_debug.auto +33 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/http/retry_with_fix.auto +1 -1
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/keywords/http_keywords.py +42 -14
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/plugin.py +22 -4
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0/pytest_dsl.egg-info}/PKG-INFO +1 -1
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl.egg-info/SOURCES.txt +3 -0
- pytest_dsl-0.13.0/tests/mock_config.yaml +11 -0
- pytest_dsl-0.13.0/tests/test_mock_server.py +396 -0
- pytest_dsl-0.13.0/tests/test_retry_runner.py +59 -0
- pytest_dsl-0.12.0/pytest_dsl/core/parsetab.py +0 -130
- pytest_dsl-0.12.0/pytest_dsl/examples/http/retry_debug.auto +0 -22
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/LICENSE +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/MANIFEST.in +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/README.md +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/__init__.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/conftest_adapter.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/core/__init__.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/core/auth_provider.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/core/auto_decorator.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/core/auto_directory.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/core/context.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/core/dsl_executor_utils.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/core/global_context.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/core/http_client.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/core/keyword_manager.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/core/lexer.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/core/plugin_discovery.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/core/utils.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/core/variable_utils.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/core/yaml_loader.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/core/yaml_vars.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/docs/custom_keywords.md +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/__init__.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/assert/assertion_example.auto +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/assert/boolean_test.auto +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/assert/expression_test.auto +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/custom/test_advanced_keywords.auto +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/custom/test_custom_keywords.auto +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/custom/test_default_values.auto +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/http/__init__.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/http/builtin_auth_test.auto +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/http/file_reference_test.auto +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/http/http_advanced.auto +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/http/http_length_test.auto +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/http/http_with_yaml.auto +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/http/simple_retry.auto +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/http/vars.yaml +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/quickstart/api_basics.auto +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/quickstart/assertions.auto +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/quickstart/loops.auto +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/test_assert.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/test_custom_keyword.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/test_http.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/examples/test_quickstart.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/keywords/__init__.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/keywords/assertion_keywords.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/keywords/global_keywords.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/keywords/system_keywords.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/main_adapter.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/remote/__init__.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/remote/hook_manager.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/remote/keyword_client.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/remote/keyword_server.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/remote/variable_bridge.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl/templates/keywords_report.html +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl.egg-info/dependency_links.txt +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl.egg-info/entry_points.txt +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl.egg-info/requires.txt +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/pytest_dsl.egg-info/top_level.txt +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/setup.cfg +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/setup.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/tests/test_end_to_end_seamless.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/tests/test_enhanced_variable_access.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/tests/test_http_assertions_extractors.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/tests/test_seamless_variable_sync.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/tests/test_variable_sync.py +0 -0
- {pytest_dsl-0.12.0 → pytest_dsl-0.13.0}/tests/test_variable_sync_demo.py +0 -0
@@ -78,7 +78,7 @@ def parse_args():
|
|
78
78
|
list_parser.add_argument(
|
79
79
|
'--category',
|
80
80
|
choices=[
|
81
|
-
'builtin', 'plugin', 'custom',
|
81
|
+
'builtin', 'plugin', 'custom',
|
82
82
|
'project_custom', 'remote', 'all'
|
83
83
|
],
|
84
84
|
default='all',
|
@@ -108,7 +108,7 @@ def parse_args():
|
|
108
108
|
parser.add_argument(
|
109
109
|
'--category',
|
110
110
|
choices=[
|
111
|
-
'builtin', 'plugin', 'custom',
|
111
|
+
'builtin', 'plugin', 'custom',
|
112
112
|
'project_custom', 'remote', 'all'
|
113
113
|
],
|
114
114
|
default='all'
|
@@ -157,6 +157,29 @@ def load_all_keywords(include_remote=False):
|
|
157
157
|
# 扫描本地关键字
|
158
158
|
scan_local_keywords()
|
159
159
|
|
160
|
+
# 自动导入项目中的resources目录
|
161
|
+
try:
|
162
|
+
from pytest_dsl.core.custom_keyword_manager import (
|
163
|
+
custom_keyword_manager)
|
164
|
+
|
165
|
+
# 尝试从多个可能的项目根目录位置导入resources
|
166
|
+
possible_roots = [
|
167
|
+
os.getcwd(), # 当前工作目录
|
168
|
+
os.path.dirname(os.getcwd()), # 上级目录
|
169
|
+
]
|
170
|
+
|
171
|
+
# 尝试每个可能的根目录
|
172
|
+
for project_root in possible_roots:
|
173
|
+
if project_root and os.path.exists(project_root):
|
174
|
+
resources_dir = os.path.join(project_root, "resources")
|
175
|
+
if (os.path.exists(resources_dir) and
|
176
|
+
os.path.isdir(resources_dir)):
|
177
|
+
custom_keyword_manager.auto_import_resources_directory(
|
178
|
+
project_root)
|
179
|
+
break
|
180
|
+
except Exception as e:
|
181
|
+
print(f"自动导入resources目录时出现警告: {str(e)}")
|
182
|
+
|
160
183
|
# 扫描项目中的自定义关键字(.resource文件中定义的)
|
161
184
|
project_custom_keywords = scan_project_custom_keywords()
|
162
185
|
if project_custom_keywords:
|
@@ -15,6 +15,7 @@ class CustomKeywordManager:
|
|
15
15
|
"""初始化自定义关键字管理器"""
|
16
16
|
self.resource_cache = {} # 缓存已加载的资源文件
|
17
17
|
self.resource_paths = [] # 资源文件搜索路径
|
18
|
+
self.auto_imported_resources = set() # 记录已自动导入的资源文件
|
18
19
|
|
19
20
|
def add_resource_path(self, path: str) -> None:
|
20
21
|
"""添加资源文件搜索路径
|
@@ -25,6 +26,141 @@ class CustomKeywordManager:
|
|
25
26
|
if path not in self.resource_paths:
|
26
27
|
self.resource_paths.append(path)
|
27
28
|
|
29
|
+
def auto_import_resources_directory(
|
30
|
+
self, project_root: str = None) -> None:
|
31
|
+
"""自动导入项目中的resources目录
|
32
|
+
|
33
|
+
Args:
|
34
|
+
project_root: 项目根目录,默认为当前工作目录
|
35
|
+
"""
|
36
|
+
if project_root is None:
|
37
|
+
project_root = os.getcwd()
|
38
|
+
|
39
|
+
# 查找resources目录
|
40
|
+
resources_dir = os.path.join(project_root, "resources")
|
41
|
+
|
42
|
+
if (not os.path.exists(resources_dir) or
|
43
|
+
not os.path.isdir(resources_dir)):
|
44
|
+
# 如果没有resources目录,静默返回
|
45
|
+
return
|
46
|
+
|
47
|
+
print(f"发现resources目录: {resources_dir}")
|
48
|
+
|
49
|
+
# 递归查找所有.resource文件
|
50
|
+
resource_files = []
|
51
|
+
for root, dirs, files in os.walk(resources_dir):
|
52
|
+
for file in files:
|
53
|
+
if file.endswith('.resource'):
|
54
|
+
resource_files.append(os.path.join(root, file))
|
55
|
+
|
56
|
+
if not resource_files:
|
57
|
+
print("resources目录中没有找到.resource文件")
|
58
|
+
return
|
59
|
+
|
60
|
+
print(f"在resources目录中发现 {len(resource_files)} 个资源文件")
|
61
|
+
|
62
|
+
# 按照依赖关系排序并加载资源文件
|
63
|
+
sorted_files = self._sort_resources_by_dependencies(resource_files)
|
64
|
+
|
65
|
+
for resource_file in sorted_files:
|
66
|
+
try:
|
67
|
+
# 检查是否已经自动导入过
|
68
|
+
absolute_path = os.path.abspath(resource_file)
|
69
|
+
if absolute_path not in self.auto_imported_resources:
|
70
|
+
self.load_resource_file(resource_file)
|
71
|
+
self.auto_imported_resources.add(absolute_path)
|
72
|
+
print(f"自动导入资源文件: {resource_file}")
|
73
|
+
except Exception as e:
|
74
|
+
print(f"自动导入资源文件失败 {resource_file}: {e}")
|
75
|
+
|
76
|
+
def _sort_resources_by_dependencies(self, resource_files):
|
77
|
+
"""根据依赖关系对资源文件进行排序
|
78
|
+
|
79
|
+
Args:
|
80
|
+
resource_files: 资源文件列表
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
list: 按依赖关系排序后的资源文件列表
|
84
|
+
"""
|
85
|
+
# 简单的拓扑排序实现
|
86
|
+
dependencies = {}
|
87
|
+
all_files = set()
|
88
|
+
|
89
|
+
# 分析每个文件的依赖关系
|
90
|
+
for file_path in resource_files:
|
91
|
+
all_files.add(file_path)
|
92
|
+
dependencies[file_path] = self._extract_dependencies(file_path)
|
93
|
+
|
94
|
+
# 拓扑排序
|
95
|
+
sorted_files = []
|
96
|
+
visited = set()
|
97
|
+
temp_visited = set()
|
98
|
+
|
99
|
+
def visit(file_path):
|
100
|
+
if file_path in temp_visited:
|
101
|
+
# 检测到循环依赖,跳过
|
102
|
+
return
|
103
|
+
if file_path in visited:
|
104
|
+
return
|
105
|
+
|
106
|
+
temp_visited.add(file_path)
|
107
|
+
|
108
|
+
# 访问依赖的文件
|
109
|
+
for dep in dependencies.get(file_path, []):
|
110
|
+
if dep in all_files: # 只处理在当前文件列表中的依赖
|
111
|
+
visit(dep)
|
112
|
+
|
113
|
+
temp_visited.remove(file_path)
|
114
|
+
visited.add(file_path)
|
115
|
+
sorted_files.append(file_path)
|
116
|
+
|
117
|
+
# 访问所有文件
|
118
|
+
for file_path in resource_files:
|
119
|
+
if file_path not in visited:
|
120
|
+
visit(file_path)
|
121
|
+
|
122
|
+
return sorted_files
|
123
|
+
|
124
|
+
def _extract_dependencies(self, file_path):
|
125
|
+
"""提取资源文件的依赖关系
|
126
|
+
|
127
|
+
Args:
|
128
|
+
file_path: 资源文件路径
|
129
|
+
|
130
|
+
Returns:
|
131
|
+
list: 依赖的文件路径列表
|
132
|
+
"""
|
133
|
+
dependencies = []
|
134
|
+
|
135
|
+
try:
|
136
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
137
|
+
content = f.read()
|
138
|
+
|
139
|
+
# 解析文件获取导入信息
|
140
|
+
lexer = get_lexer()
|
141
|
+
parser = get_parser()
|
142
|
+
ast = parser.parse(content, lexer=lexer)
|
143
|
+
|
144
|
+
if ast.type == 'Start' and ast.children:
|
145
|
+
metadata_node = ast.children[0]
|
146
|
+
if metadata_node.type == 'Metadata':
|
147
|
+
for item in metadata_node.children:
|
148
|
+
if item.type == '@import':
|
149
|
+
imported_file = item.value
|
150
|
+
# 处理相对路径
|
151
|
+
if not os.path.isabs(imported_file):
|
152
|
+
imported_file = os.path.join(
|
153
|
+
os.path.dirname(file_path), imported_file)
|
154
|
+
# 规范化路径
|
155
|
+
imported_file = os.path.normpath(imported_file)
|
156
|
+
dependencies.append(imported_file)
|
157
|
+
|
158
|
+
except Exception as e:
|
159
|
+
# 如果解析失败,返回空依赖列表
|
160
|
+
print(f"解析资源文件依赖失败 {file_path}: {e}")
|
161
|
+
|
162
|
+
return dependencies
|
163
|
+
|
28
164
|
def load_resource_file(self, file_path: str) -> None:
|
29
165
|
"""加载资源文件
|
30
166
|
|
@@ -300,6 +300,9 @@ class DSLExecutor:
|
|
300
300
|
metadata = {}
|
301
301
|
teardown_node = None
|
302
302
|
|
303
|
+
# 自动导入项目中的resources目录
|
304
|
+
self._auto_import_resources()
|
305
|
+
|
303
306
|
# 先处理元数据和找到teardown节点
|
304
307
|
for child in node.children:
|
305
308
|
if child.type == 'Metadata':
|
@@ -334,6 +337,40 @@ class DSLExecutor:
|
|
334
337
|
# 测试用例执行完成后清空上下文
|
335
338
|
self.test_context.clear()
|
336
339
|
|
340
|
+
def _auto_import_resources(self):
|
341
|
+
"""自动导入项目中的resources目录"""
|
342
|
+
try:
|
343
|
+
from pytest_dsl.core.custom_keyword_manager import custom_keyword_manager
|
344
|
+
|
345
|
+
# 尝试从多个可能的项目根目录位置导入resources
|
346
|
+
possible_roots = [
|
347
|
+
os.getcwd(), # 当前工作目录
|
348
|
+
os.path.dirname(os.getcwd()), # 上级目录
|
349
|
+
]
|
350
|
+
|
351
|
+
# 如果在pytest环境中,尝试获取pytest的根目录
|
352
|
+
try:
|
353
|
+
import pytest
|
354
|
+
if hasattr(pytest, 'config') and pytest.config:
|
355
|
+
pytest_root = pytest.config.rootdir
|
356
|
+
if pytest_root:
|
357
|
+
possible_roots.insert(0, str(pytest_root))
|
358
|
+
except:
|
359
|
+
pass
|
360
|
+
|
361
|
+
# 尝试每个可能的根目录
|
362
|
+
for project_root in possible_roots:
|
363
|
+
if project_root and os.path.exists(project_root):
|
364
|
+
resources_dir = os.path.join(project_root, "resources")
|
365
|
+
if os.path.exists(resources_dir) and os.path.isdir(resources_dir):
|
366
|
+
custom_keyword_manager.auto_import_resources_directory(
|
367
|
+
project_root)
|
368
|
+
break
|
369
|
+
|
370
|
+
except Exception as e:
|
371
|
+
# 自动导入失败不应该影响测试执行,只记录警告
|
372
|
+
print(f"自动导入resources目录时出现警告: {str(e)}")
|
373
|
+
|
337
374
|
def _handle_import(self, file_path):
|
338
375
|
"""处理导入指令
|
339
376
|
|