pytest-dsl 0.5.0__py3-none-any.whl → 0.7.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 (40) hide show
  1. pytest_dsl/cli.py +28 -33
  2. pytest_dsl/core/auto_decorator.py +72 -53
  3. pytest_dsl/core/auto_directory.py +8 -5
  4. pytest_dsl/core/dsl_executor.py +91 -23
  5. pytest_dsl/core/http_request.py +272 -221
  6. pytest_dsl/core/lexer.py +17 -17
  7. pytest_dsl/core/parser.py +52 -4
  8. pytest_dsl/core/parsetab.py +81 -70
  9. pytest_dsl/core/plugin_discovery.py +1 -8
  10. pytest_dsl/core/utils.py +43 -23
  11. pytest_dsl/core/variable_utils.py +215 -70
  12. pytest_dsl/core/yaml_loader.py +96 -19
  13. pytest_dsl/examples/assert/assertion_example.auto +1 -1
  14. pytest_dsl/examples/assert/boolean_test.auto +2 -2
  15. pytest_dsl/examples/assert/expression_test.auto +1 -1
  16. pytest_dsl/examples/custom/test_advanced_keywords.auto +2 -2
  17. pytest_dsl/examples/custom/test_custom_keywords.auto +2 -2
  18. pytest_dsl/examples/custom/test_default_values.auto +2 -2
  19. pytest_dsl/examples/http/file_reference_test.auto +1 -1
  20. pytest_dsl/examples/http/http_advanced.auto +1 -1
  21. pytest_dsl/examples/http/http_example.auto +1 -1
  22. pytest_dsl/examples/http/http_length_test.auto +1 -1
  23. pytest_dsl/examples/http/http_retry_assertions.auto +1 -1
  24. pytest_dsl/examples/http/http_retry_assertions_enhanced.auto +2 -2
  25. pytest_dsl/examples/http/http_with_yaml.auto +1 -1
  26. pytest_dsl/examples/quickstart/api_basics.auto +1 -1
  27. pytest_dsl/examples/quickstart/assertions.auto +1 -1
  28. pytest_dsl/examples/quickstart/loops.auto +2 -2
  29. pytest_dsl/keywords/assertion_keywords.py +76 -62
  30. pytest_dsl/keywords/global_keywords.py +43 -4
  31. pytest_dsl/keywords/http_keywords.py +58 -56
  32. pytest_dsl-0.7.0.dist-info/METADATA +1040 -0
  33. pytest_dsl-0.7.0.dist-info/RECORD +67 -0
  34. {pytest_dsl-0.5.0.dist-info → pytest_dsl-0.7.0.dist-info}/WHEEL +1 -1
  35. pytest_dsl/parsetab.py +0 -69
  36. pytest_dsl-0.5.0.dist-info/METADATA +0 -596
  37. pytest_dsl-0.5.0.dist-info/RECORD +0 -68
  38. {pytest_dsl-0.5.0.dist-info → pytest_dsl-0.7.0.dist-info}/entry_points.txt +0 -0
  39. {pytest_dsl-0.5.0.dist-info → pytest_dsl-0.7.0.dist-info}/licenses/LICENSE +0 -0
  40. {pytest_dsl-0.5.0.dist-info → pytest_dsl-0.7.0.dist-info}/top_level.txt +0 -0
@@ -89,6 +89,6 @@
89
89
  all: true
90
90
  ''',步骤名称:"纯配置式重试示例"
91
91
 
92
- @teardown do
92
+ teardown do
93
93
  [打印],内容:'增强断言重试测试完成!'
94
- end
94
+ end
@@ -53,6 +53,6 @@
53
53
 
54
54
  [打印],内容:'请求参数: ${args}'
55
55
 
56
- @teardown do
56
+ teardown do
57
57
  [打印],内容:'YAML变量和模板测试完成!'
58
58
  end
@@ -50,6 +50,6 @@
50
50
  - ["jsonpath", "$.title", "eq", "测试标题"]
51
51
  ''',步骤名称:'创建新文章'
52
52
 
53
- @teardown do
53
+ teardown do
54
54
  [打印],内容:'API测试完成!'
55
55
  end
@@ -26,6 +26,6 @@ result = 53
26
26
  [打印],内容:'结果: ${result}'
27
27
  [断言],条件:'${result} == 53',消息:'结果不正确'
28
28
 
29
- @teardown do
29
+ teardown do
30
30
  [打印],内容:'断言测试完成!'
31
31
  end
@@ -19,6 +19,6 @@ end
19
19
 
20
20
  [打印],内容:'循环结束'
21
21
 
22
- @teardown do
22
+ teardown do
23
23
  [打印],内容:'变量和循环测试完成!'
24
- end
24
+ end
@@ -14,24 +14,24 @@ from pytest_dsl.core.keyword_manager import keyword_manager
14
14
 
15
15
  def _extract_jsonpath(json_data: Union[Dict, List], path: str) -> Any:
16
16
  """使用JSONPath从JSON数据中提取值
17
-
17
+
18
18
  Args:
19
19
  json_data: 要提取数据的JSON对象或数组
20
20
  path: JSONPath表达式
21
-
21
+
22
22
  Returns:
23
23
  提取的值或值列表
24
-
24
+
25
25
  Raises:
26
26
  ValueError: 如果JSONPath表达式无效或找不到匹配项
27
27
  """
28
28
  try:
29
29
  if isinstance(json_data, str):
30
30
  json_data = json.loads(json_data)
31
-
31
+
32
32
  jsonpath_expr = jsonpath.parse(path)
33
33
  matches = [match.value for match in jsonpath_expr.find(json_data)]
34
-
34
+
35
35
  if not matches:
36
36
  return None
37
37
  elif len(matches) == 1:
@@ -44,12 +44,12 @@ def _extract_jsonpath(json_data: Union[Dict, List], path: str) -> Any:
44
44
 
45
45
  def _compare_values(actual: Any, expected: Any, operator: str = "==") -> bool:
46
46
  """比较两个值
47
-
47
+
48
48
  Args:
49
49
  actual: 实际值
50
50
  expected: 预期值
51
51
  operator: 比较运算符 (==, !=, >, <, >=, <=, contains, not_contains, matches, and, or, not)
52
-
52
+
53
53
  Returns:
54
54
  比较结果 (True/False)
55
55
  """
@@ -101,32 +101,32 @@ def _compare_values(actual: Any, expected: Any, operator: str = "==") -> bool:
101
101
  ])
102
102
  def assert_condition(**kwargs):
103
103
  """执行表达式断言
104
-
104
+
105
105
  Args:
106
106
  condition: 断言条件表达式
107
107
  message: 断言失败时的错误消息
108
-
108
+
109
109
  Returns:
110
110
  断言结果 (True/False)
111
-
111
+
112
112
  Raises:
113
113
  AssertionError: 如果断言失败
114
114
  """
115
115
  condition = kwargs.get('condition')
116
116
  message = kwargs.get('message', '断言失败')
117
117
  context = kwargs.get('context')
118
-
118
+
119
119
  # 简单解析表达式,支持 ==, !=, >, <, >=, <=, contains, not_contains, matches, in, and, or, not
120
120
  # 格式: "left_value operator right_value" 或 "boolean_expression"
121
121
  operators = ["==", "!=", ">", "<", ">=", "<=", "contains", "not_contains", "matches", "in", "and", "or", "not"]
122
-
122
+
123
123
  # 先检查是否包含这些操作符
124
124
  operator_used = None
125
125
  for op in operators:
126
126
  if f" {op} " in condition:
127
127
  operator_used = op
128
128
  break
129
-
129
+
130
130
  if not operator_used:
131
131
  # 如果没有找到操作符,尝试作为布尔表达式直接求值
132
132
  try:
@@ -142,36 +142,36 @@ def assert_condition(**kwargs):
142
142
  return True
143
143
  except Exception as e:
144
144
  raise AssertionError(f"{message}. 无法解析条件表达式: {condition}. 错误: {str(e)}")
145
-
145
+
146
146
  # 解析左值和右值
147
147
  left_value, right_value = condition.split(f" {operator_used} ", 1)
148
148
  left_value = left_value.strip()
149
149
  right_value = right_value.strip()
150
-
150
+
151
151
  # 移除引号(如果有)
152
152
  if left_value.startswith('"') and left_value.endswith('"'):
153
153
  left_value = left_value[1:-1]
154
154
  elif left_value.startswith("'") and left_value.endswith("'"):
155
155
  left_value = left_value[1:-1]
156
-
156
+
157
157
  if right_value.startswith('"') and right_value.endswith('"'):
158
158
  right_value = right_value[1:-1]
159
159
  elif right_value.startswith("'") and right_value.endswith("'"):
160
160
  right_value = right_value[1:-1]
161
-
161
+
162
162
  # 记录原始值(用于调试)
163
163
  allure.attach(
164
164
  f"原始左值: {left_value}\n原始右值: {right_value}\n操作符: {operator_used}",
165
165
  name="表达式解析",
166
166
  attachment_type=allure.attachment_type.TEXT
167
167
  )
168
-
168
+
169
169
  # 对左值进行变量替换和表达式计算
170
170
  try:
171
171
  # 如果左值包含变量引用,先进行变量替换
172
172
  if '${' in left_value:
173
173
  left_value = context.executor.variable_replacer.replace_in_string(left_value)
174
-
174
+
175
175
  # 检查是否需要计算表达式
176
176
  if any(op in str(left_value) for op in ['+', '-', '*', '/', '%', '(', ')']):
177
177
  try:
@@ -187,7 +187,7 @@ def assert_condition(**kwargs):
187
187
  attachment_type=allure.attachment_type.TEXT
188
188
  )
189
189
  raise ValueError(f"表达式计算错误: {str(e)}")
190
-
190
+
191
191
  # 处理布尔值字符串和数字字符串
192
192
  if isinstance(left_value, str):
193
193
  if left_value.lower() in ('true', 'false'):
@@ -210,13 +210,13 @@ def assert_condition(**kwargs):
210
210
  attachment_type=allure.attachment_type.TEXT
211
211
  )
212
212
  raise
213
-
213
+
214
214
  # 对右值进行变量替换和表达式计算
215
215
  try:
216
216
  # 如果右值包含变量引用,先进行变量替换
217
217
  if '${' in right_value:
218
218
  right_value = context.executor.variable_replacer.replace_in_string(right_value)
219
-
219
+
220
220
  # 检查是否需要计算表达式
221
221
  if any(op in str(right_value) for op in ['+', '-', '*', '/', '%', '(', ')']):
222
222
  try:
@@ -232,7 +232,7 @@ def assert_condition(**kwargs):
232
232
  attachment_type=allure.attachment_type.TEXT
233
233
  )
234
234
  raise ValueError(f"表达式计算错误: {str(e)}")
235
-
235
+
236
236
  # 处理布尔值字符串
237
237
  if isinstance(right_value, str):
238
238
  if right_value.lower() in ('true', 'false'):
@@ -246,7 +246,7 @@ def assert_condition(**kwargs):
246
246
  attachment_type=allure.attachment_type.TEXT
247
247
  )
248
248
  raise
249
-
249
+
250
250
  # 类型转换和特殊处理
251
251
  if operator_used == "contains":
252
252
  # 特殊处理字符串包含操作
@@ -285,7 +285,7 @@ def assert_condition(**kwargs):
285
285
  # 尝试将右值解析为列表或字典
286
286
  if isinstance(right_value, str):
287
287
  right_value = eval(right_value)
288
-
288
+
289
289
  # 如果是字典,检查键
290
290
  if isinstance(right_value, dict):
291
291
  result = left_value in right_value.keys()
@@ -310,17 +310,17 @@ def assert_condition(**kwargs):
310
310
  right_value = float(right_value)
311
311
  except:
312
312
  pass
313
-
313
+
314
314
  # 记录类型转换后的值(用于调试)
315
315
  allure.attach(
316
316
  f"类型转换后左值: {left_value} ({type(left_value).__name__})\n类型转换后右值: {right_value} ({type(right_value).__name__})",
317
317
  name="类型转换",
318
318
  attachment_type=allure.attachment_type.TEXT
319
319
  )
320
-
320
+
321
321
  # 执行比较
322
322
  result = _compare_values(left_value, right_value, operator_used)
323
-
323
+
324
324
  # 记录和处理断言结果
325
325
  if not result:
326
326
  error_details = f"""
@@ -337,7 +337,7 @@ def assert_condition(**kwargs):
337
337
  attachment_type=allure.attachment_type.TEXT
338
338
  )
339
339
  raise AssertionError(error_details)
340
-
340
+
341
341
  # 记录成功的断言
342
342
  allure.attach(
343
343
  f"实际值: {left_value}\n预期值: {right_value}\n操作符: {operator_used}",
@@ -356,17 +356,17 @@ def assert_condition(**kwargs):
356
356
  ])
357
357
  def assert_json(**kwargs):
358
358
  """执行JSON断言
359
-
359
+
360
360
  Args:
361
361
  json_data: JSON数据(字符串或对象)
362
362
  jsonpath: JSONPath表达式
363
363
  expected_value: 预期的值
364
364
  operator: 比较操作符,默认为"=="
365
365
  message: 断言失败时的错误消息
366
-
366
+
367
367
  Returns:
368
368
  断言结果 (True/False)
369
-
369
+
370
370
  Raises:
371
371
  AssertionError: 如果断言失败
372
372
  ValueError: 如果JSONPath无效或找不到匹配项
@@ -376,27 +376,27 @@ def assert_json(**kwargs):
376
376
  expected_value = kwargs.get('expected_value')
377
377
  operator = kwargs.get('operator', '==')
378
378
  message = kwargs.get('message', 'JSON断言失败')
379
-
379
+
380
380
  # 解析JSON(如果需要)
381
381
  if isinstance(json_data, str):
382
382
  try:
383
383
  json_data = json.loads(json_data)
384
384
  except json.JSONDecodeError as e:
385
385
  raise ValueError(f"无效的JSON数据: {str(e)}")
386
-
386
+
387
387
  # 使用JSONPath提取值
388
388
  actual_value = _extract_jsonpath(json_data, path)
389
-
389
+
390
390
  # 记录提取的值
391
391
  allure.attach(
392
392
  f"JSONPath: {path}\n提取值: {actual_value}",
393
393
  name="JSONPath提取结果",
394
394
  attachment_type=allure.attachment_type.TEXT
395
395
  )
396
-
396
+
397
397
  # 比较值
398
398
  result = _compare_values(actual_value, expected_value, operator)
399
-
399
+
400
400
  # 记录和处理断言结果
401
401
  if not result:
402
402
  allure.attach(
@@ -405,7 +405,7 @@ def assert_json(**kwargs):
405
405
  attachment_type=allure.attachment_type.TEXT
406
406
  )
407
407
  raise AssertionError(message)
408
-
408
+
409
409
  # 记录成功的断言
410
410
  allure.attach(
411
411
  f"实际值: {actual_value}\n预期值: {expected_value}\n操作符: {operator}",
@@ -422,21 +422,23 @@ def assert_json(**kwargs):
422
422
  ])
423
423
  def extract_json(**kwargs):
424
424
  """从JSON数据中提取值并保存到变量
425
-
425
+
426
426
  Args:
427
427
  json_data: JSON数据(字符串或对象)
428
428
  jsonpath: JSONPath表达式
429
429
  variable: 存储提取值的变量名
430
-
430
+ context: 测试上下文
431
+
431
432
  Returns:
432
- 提取的值
433
-
433
+ 提取的值或包含提取值的字典(远程模式)
434
+
434
435
  Raises:
435
436
  ValueError: 如果JSONPath无效或找不到匹配项
436
437
  """
437
438
  json_data = kwargs.get('json_data')
438
439
  path = kwargs.get('jsonpath')
439
440
  variable = kwargs.get('variable')
441
+ context = kwargs.get('context')
440
442
 
441
443
  # 解析JSON(如果需要)
442
444
  if isinstance(json_data, str):
@@ -444,19 +446,31 @@ def extract_json(**kwargs):
444
446
  json_data = json.loads(json_data)
445
447
  except json.JSONDecodeError as e:
446
448
  raise ValueError(f"无效的JSON数据: {str(e)}")
447
-
449
+
448
450
  # 使用JSONPath提取值
449
451
  value = _extract_jsonpath(json_data, path)
450
-
452
+
453
+ # 将提取的值设置到上下文中(本地模式)
454
+ if context and variable:
455
+ context.set(variable, value)
456
+
451
457
  # 记录提取的值
452
458
  allure.attach(
453
459
  f"JSONPath: {path}\n提取值: {value}\n保存到变量: {variable}",
454
460
  name="JSON数据提取",
455
461
  attachment_type=allure.attachment_type.TEXT
456
462
  )
457
-
458
- # 返回提取的值用于变量赋值
459
- return value
463
+
464
+ # 统一返回格式 - 支持远程关键字模式
465
+ return {
466
+ "result": value, # 主要返回值保持兼容
467
+ "captures": {variable: value} if variable else {}, # 明确的捕获变量
468
+ "session_state": {},
469
+ "metadata": {
470
+ "jsonpath": path,
471
+ "variable_name": variable
472
+ }
473
+ }
460
474
 
461
475
 
462
476
  @keyword_manager.register('类型断言', [
@@ -466,22 +480,22 @@ def extract_json(**kwargs):
466
480
  ])
467
481
  def assert_type(**kwargs):
468
482
  """断言值的类型
469
-
483
+
470
484
  Args:
471
485
  value: 要检查的值
472
486
  type: 预期的类型 (string, number, boolean, list, object, null)
473
487
  message: 断言失败时的错误消息
474
-
488
+
475
489
  Returns:
476
490
  断言结果 (True/False)
477
-
491
+
478
492
  Raises:
479
493
  AssertionError: 如果断言失败
480
494
  """
481
495
  value = kwargs.get('value')
482
496
  expected_type = kwargs.get('type')
483
497
  message = kwargs.get('message', '类型断言失败')
484
-
498
+
485
499
  # 检查类型
486
500
  if expected_type == 'string':
487
501
  result = isinstance(value, str)
@@ -508,7 +522,7 @@ def assert_type(**kwargs):
508
522
  result = value is None
509
523
  else:
510
524
  raise ValueError(f"不支持的类型: {expected_type}")
511
-
525
+
512
526
  # 记录和处理断言结果
513
527
  if not result:
514
528
  actual_type = type(value).__name__
@@ -518,7 +532,7 @@ def assert_type(**kwargs):
518
532
  attachment_type=allure.attachment_type.TEXT
519
533
  )
520
534
  raise AssertionError(message)
521
-
535
+
522
536
  # 记录成功的断言
523
537
  allure.attach(
524
538
  f"值: {value}\n类型: {expected_type}",
@@ -536,16 +550,16 @@ def assert_type(**kwargs):
536
550
  ])
537
551
  def compare_values(**kwargs):
538
552
  """比较两个值
539
-
553
+
540
554
  Args:
541
555
  actual: 实际值
542
556
  expected: 预期值
543
557
  operator: 比较操作符,默认为"=="
544
558
  message: 断言失败时的错误消息
545
-
559
+
546
560
  Returns:
547
561
  比较结果 (True/False)
548
-
562
+
549
563
  Raises:
550
564
  AssertionError: 如果比较失败
551
565
  """
@@ -553,7 +567,7 @@ def compare_values(**kwargs):
553
567
  expected = kwargs.get('expected')
554
568
  operator = kwargs.get('operator', '==')
555
569
  message = kwargs.get('message', '数据比较失败')
556
-
570
+
557
571
  # 处理布尔值字符串和表达式
558
572
  if isinstance(actual, str):
559
573
  # 检查是否需要计算表达式
@@ -571,7 +585,7 @@ def compare_values(**kwargs):
571
585
  actual = actual.lower() == 'true'
572
586
  elif actual.lower() in ('yes', 'no', '1', '0', 't', 'f', 'y', 'n'):
573
587
  actual = actual.lower() in ('yes', '1', 't', 'y')
574
-
588
+
575
589
  if isinstance(expected, str):
576
590
  # 检查是否需要计算表达式
577
591
  if any(op in expected for op in ['+', '-', '*', '/', '%', '(', ')']):
@@ -588,10 +602,10 @@ def compare_values(**kwargs):
588
602
  expected = expected.lower() == 'true'
589
603
  elif expected.lower() in ('yes', 'no', '1', '0', 't', 'f', 'y', 'n'):
590
604
  expected = expected.lower() in ('yes', '1', 't', 'y')
591
-
605
+
592
606
  # 比较值
593
607
  result = _compare_values(actual, expected, operator)
594
-
608
+
595
609
  # 记录和处理比较结果
596
610
  if not result:
597
611
  allure.attach(
@@ -600,11 +614,11 @@ def compare_values(**kwargs):
600
614
  attachment_type=allure.attachment_type.TEXT
601
615
  )
602
616
  raise AssertionError(message)
603
-
617
+
604
618
  # 记录成功的比较
605
619
  allure.attach(
606
620
  f"实际值: {actual}\n预期值: {expected}\n操作符: {operator}",
607
621
  name="数据比较成功",
608
622
  attachment_type=allure.attachment_type.TEXT
609
623
  )
610
- return result
624
+ return result
@@ -12,7 +12,17 @@ from pytest_dsl.core.global_context import global_context
12
12
  def set_global_variable(name, value, context):
13
13
  """设置全局变量"""
14
14
  global_context.set_variable(name, value)
15
- return value
15
+
16
+ # 统一返回格式 - 支持远程关键字模式
17
+ return {
18
+ "result": value, # 主要返回值保持兼容
19
+ "captures": {},
20
+ "session_state": {},
21
+ "metadata": {
22
+ "variable_name": name,
23
+ "operation": "set_global_variable"
24
+ }
25
+ }
16
26
 
17
27
 
18
28
  @keyword_manager.register(
@@ -26,7 +36,17 @@ def get_global_variable(name, context):
26
36
  value = global_context.get_variable(name)
27
37
  if value is None:
28
38
  raise Exception(f"全局变量未定义: {name}")
29
- return value
39
+
40
+ # 统一返回格式 - 支持远程关键字模式
41
+ return {
42
+ "result": value, # 主要返回值保持兼容
43
+ "captures": {},
44
+ "session_state": {},
45
+ "metadata": {
46
+ "variable_name": name,
47
+ "operation": "get_global_variable"
48
+ }
49
+ }
30
50
 
31
51
 
32
52
  @keyword_manager.register(
@@ -38,7 +58,17 @@ def get_global_variable(name, context):
38
58
  def delete_global_variable(name, context):
39
59
  """删除全局变量"""
40
60
  global_context.delete_variable(name)
41
- return True
61
+
62
+ # 统一返回格式 - 支持远程关键字模式
63
+ return {
64
+ "result": True, # 主要返回值保持兼容
65
+ "captures": {},
66
+ "session_state": {},
67
+ "metadata": {
68
+ "variable_name": name,
69
+ "operation": "delete_global_variable"
70
+ }
71
+ }
42
72
 
43
73
 
44
74
  @keyword_manager.register(
@@ -48,4 +78,13 @@ def delete_global_variable(name, context):
48
78
  def clear_all_global_variables(context):
49
79
  """清除所有全局变量"""
50
80
  global_context.clear_all()
51
- return True
81
+
82
+ # 统一返回格式 - 支持远程关键字模式
83
+ return {
84
+ "result": True, # 主要返回值保持兼容
85
+ "captures": {},
86
+ "session_state": {},
87
+ "metadata": {
88
+ "operation": "clear_all_global_variables"
89
+ }
90
+ }