pytest-dsl 0.3.1__py3-none-any.whl → 0.5.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.
@@ -18,15 +18,15 @@ from pytest_dsl.core.context import TestContext
18
18
 
19
19
  def _process_file_reference(reference: Union[str, Dict[str, Any]], allow_vars: bool = True, test_context: TestContext = None) -> Any:
20
20
  """处理文件引用,加载外部文件内容
21
-
21
+
22
22
  支持两种语法:
23
23
  1. 简单语法: "@file:/path/to/file.json" 或 "@file_template:/path/to/file.json"
24
24
  2. 详细语法: 使用file_ref结构提供更多的配置选项
25
-
25
+
26
26
  Args:
27
27
  reference: 文件引用字符串或配置字典
28
28
  allow_vars: 是否允许在文件内容中替换变量
29
-
29
+
30
30
  Returns:
31
31
  加载并处理后的文件内容
32
32
  """
@@ -35,16 +35,16 @@ def _process_file_reference(reference: Union[str, Dict[str, Any]], allow_vars: b
35
35
  # 匹配简单文件引用语法
36
36
  file_ref_pattern = r'^@file(?:_template)?:(.+)$'
37
37
  match = re.match(file_ref_pattern, reference.strip())
38
-
38
+
39
39
  if match:
40
40
  file_path = match.group(1).strip()
41
41
  is_template = '_template' in reference[:15] # 检查是否为模板
42
42
  return _load_file_content(file_path, is_template, 'auto', 'utf-8', test_context)
43
-
43
+
44
44
  # 处理详细语法
45
45
  elif isinstance(reference, dict) and 'file_ref' in reference:
46
46
  file_ref = reference['file_ref']
47
-
47
+
48
48
  if isinstance(file_ref, str):
49
49
  # 如果file_ref是字符串,使用默认配置
50
50
  return _load_file_content(file_ref, allow_vars, 'auto', 'utf-8', test_context)
@@ -53,44 +53,44 @@ def _process_file_reference(reference: Union[str, Dict[str, Any]], allow_vars: b
53
53
  file_path = file_ref.get('path')
54
54
  if not file_path:
55
55
  raise ValueError("file_ref必须包含path字段")
56
-
56
+
57
57
  template = file_ref.get('template', allow_vars)
58
58
  file_type = file_ref.get('type', 'auto')
59
59
  encoding = file_ref.get('encoding', 'utf-8')
60
-
60
+
61
61
  return _load_file_content(file_path, template, file_type, encoding, test_context)
62
-
62
+
63
63
  # 如果不是文件引用,返回原始值
64
64
  return reference
65
65
 
66
66
 
67
- def _load_file_content(file_path: str, is_template: bool = False,
67
+ def _load_file_content(file_path: str, is_template: bool = False,
68
68
  file_type: str = 'auto', encoding: str = 'utf-8', test_context: TestContext = None) -> Any:
69
69
  """加载文件内容
70
-
70
+
71
71
  Args:
72
72
  file_path: 文件路径
73
73
  is_template: 是否作为模板处理(替换变量引用)
74
74
  file_type: 文件类型 (auto, json, yaml, text)
75
75
  encoding: 文件编码
76
-
76
+
77
77
  Returns:
78
78
  加载并处理后的文件内容
79
79
  """
80
80
  # 验证文件存在
81
81
  if not os.path.exists(file_path):
82
82
  raise FileNotFoundError(f"找不到引用的文件: {file_path}")
83
-
83
+
84
84
  # 读取文件内容
85
85
  with open(file_path, 'r', encoding=encoding) as f:
86
86
  content = f.read()
87
-
87
+
88
88
  # 如果是模板,处理变量替换
89
89
  if is_template:
90
90
  from pytest_dsl.core.variable_utils import VariableReplacer
91
91
  replacer = VariableReplacer(test_context=test_context)
92
92
  content = replacer.replace_in_string(content)
93
-
93
+
94
94
  # 根据文件类型处理内容
95
95
  if file_type == 'auto':
96
96
  # 根据文件扩展名自动检测类型
@@ -101,7 +101,7 @@ def _load_file_content(file_path: str, is_template: bool = False,
101
101
  file_type = 'yaml'
102
102
  else:
103
103
  file_type = 'text'
104
-
104
+
105
105
  # 处理不同类型的文件
106
106
  if file_type == 'json':
107
107
  try:
@@ -120,46 +120,46 @@ def _load_file_content(file_path: str, is_template: bool = False,
120
120
 
121
121
  def _process_request_config(config: Dict[str, Any], test_context: TestContext = None) -> Dict[str, Any]:
122
122
  """处理请求配置,检查并处理文件引用
123
-
123
+
124
124
  Args:
125
125
  config: 请求配置
126
-
126
+
127
127
  Returns:
128
128
  处理后的请求配置
129
129
  """
130
130
  if not isinstance(config, dict):
131
131
  return config
132
-
132
+
133
133
  # 处理request部分
134
134
  if 'request' in config and isinstance(config['request'], dict):
135
135
  request = config['request']
136
-
136
+
137
137
  # 处理json字段
138
138
  if 'json' in request:
139
139
  request['json'] = _process_file_reference(request['json'], test_context=test_context)
140
-
140
+
141
141
  # 处理data字段
142
142
  if 'data' in request:
143
143
  request['data'] = _process_file_reference(request['data'], test_context=test_context)
144
-
144
+
145
145
  # 处理headers字段
146
146
  if 'headers' in request:
147
147
  request['headers'] = _process_file_reference(request['headers'], test_context=test_context)
148
-
148
+
149
149
  return config
150
150
 
151
151
 
152
152
  def _normalize_retry_config(config, assert_retry_count=None, assert_retry_interval=None):
153
153
  """标准化断言重试配置
154
-
154
+
155
155
  将不同来源的重试配置(命令行参数、retry配置、retry_assertions配置)
156
156
  统一转换为标准化的重试配置对象。
157
-
157
+
158
158
  Args:
159
159
  config: 原始配置字典
160
160
  assert_retry_count: 命令行级别的重试次数参数
161
161
  assert_retry_interval: 命令行级别的重试间隔参数
162
-
162
+
163
163
  Returns:
164
164
  标准化的重试配置字典,格式为:
165
165
  {
@@ -180,7 +180,7 @@ def _normalize_retry_config(config, assert_retry_count=None, assert_retry_interv
180
180
  'indices': [], # 默认不指定要重试的断言索引
181
181
  'specific': {} # 默认不指定特定断言的重试配置
182
182
  }
183
-
183
+
184
184
  # 处理命令行参数
185
185
  if assert_retry_count and int(assert_retry_count) > 0:
186
186
  standard_retry_config['enabled'] = True
@@ -188,12 +188,12 @@ def _normalize_retry_config(config, assert_retry_count=None, assert_retry_interv
188
188
  standard_retry_config['all'] = True # 命令行参数会重试所有断言
189
189
  if assert_retry_interval:
190
190
  standard_retry_config['interval'] = float(assert_retry_interval)
191
-
191
+
192
192
  # 处理专用retry_assertions配置
193
193
  if 'retry_assertions' in config and config['retry_assertions']:
194
194
  retry_assertions = config['retry_assertions']
195
195
  standard_retry_config['enabled'] = True
196
-
196
+
197
197
  if 'count' in retry_assertions:
198
198
  standard_retry_config['count'] = retry_assertions['count']
199
199
  if 'interval' in retry_assertions:
@@ -204,7 +204,7 @@ def _normalize_retry_config(config, assert_retry_count=None, assert_retry_interv
204
204
  standard_retry_config['indices'] = retry_assertions['indices']
205
205
  if 'specific' in retry_assertions:
206
206
  standard_retry_config['specific'] = retry_assertions['specific']
207
-
207
+
208
208
  # 处理传统retry配置(如果专用配置不存在)
209
209
  elif 'retry' in config and config['retry']:
210
210
  retry_config = config['retry']
@@ -214,7 +214,7 @@ def _normalize_retry_config(config, assert_retry_count=None, assert_retry_interv
214
214
  standard_retry_config['all'] = True # 传统配置会重试所有断言
215
215
  if 'interval' in retry_config:
216
216
  standard_retry_config['interval'] = retry_config['interval']
217
-
217
+
218
218
  return standard_retry_config
219
219
 
220
220
 
@@ -231,9 +231,9 @@ def _normalize_retry_config(config, assert_retry_count=None, assert_retry_interv
231
231
  ])
232
232
  def http_request(context, **kwargs):
233
233
  """执行HTTP请求
234
-
234
+
235
235
  根据YAML配置发送HTTP请求,支持客户端配置、会话管理、响应捕获和断言。
236
-
236
+
237
237
  Args:
238
238
  context: 测试上下文
239
239
  client: 客户端名称
@@ -245,7 +245,7 @@ def http_request(context, **kwargs):
245
245
  template: 模板名称
246
246
  assert_retry_count: 断言失败时的重试次数
247
247
  assert_retry_interval: 断言重试间隔时间(秒)
248
-
248
+
249
249
  Returns:
250
250
  捕获的变量字典或响应对象
251
251
  """
@@ -258,17 +258,17 @@ def http_request(context, **kwargs):
258
258
  template_name = kwargs.get('template')
259
259
  assert_retry_count = kwargs.get('assert_retry_count')
260
260
  assert_retry_interval = kwargs.get('assert_retry_interval')
261
-
261
+
262
262
  with allure.step(f"发送HTTP请求 (客户端: {client_name}{', 会话: ' + session_name if session_name else ''})"):
263
263
  # 处理模板
264
264
  if template_name:
265
265
  # 从YAML变量中获取模板
266
266
  http_templates = yaml_vars.get_variable("http_templates") or {}
267
267
  template = http_templates.get(template_name)
268
-
268
+
269
269
  if not template:
270
270
  raise ValueError(f"未找到名为 '{template_name}' 的HTTP请求模板")
271
-
271
+
272
272
  # 解析配置并合并模板
273
273
  if isinstance(config, str):
274
274
  # 先进行变量替换,再解析YAML
@@ -277,7 +277,7 @@ def http_request(context, **kwargs):
277
277
  config = replacer.replace_in_string(config)
278
278
  try:
279
279
  user_config = yaml.safe_load(config) if config else {}
280
-
280
+
281
281
  # 深度合并
282
282
  merged_config = _deep_merge(template.copy(), user_config)
283
283
  config = merged_config
@@ -289,7 +289,7 @@ def http_request(context, **kwargs):
289
289
  from pytest_dsl.core.variable_utils import VariableReplacer
290
290
  replacer = VariableReplacer(test_context=context)
291
291
  config = replacer.replace_in_string(config)
292
-
292
+
293
293
  # 解析YAML配置
294
294
  if isinstance(config, str):
295
295
  try:
@@ -299,7 +299,7 @@ def http_request(context, **kwargs):
299
299
 
300
300
  # 统一处理重试配置
301
301
  retry_config = _normalize_retry_config(config, assert_retry_count, assert_retry_interval)
302
-
302
+
303
303
  # 为了兼容性,将标准化后的重试配置写回到配置中
304
304
  if retry_config['enabled']:
305
305
  config['retry_assertions'] = {
@@ -309,22 +309,22 @@ def http_request(context, **kwargs):
309
309
  'indices': retry_config['indices'],
310
310
  'specific': retry_config['specific']
311
311
  }
312
-
312
+
313
313
  config = _process_request_config(config, test_context=context)
314
-
314
+
315
315
  # 创建HTTP请求对象
316
316
  http_req = HTTPRequest(config, client_name, session_name)
317
-
317
+
318
318
  # 执行请求
319
319
  response = http_req.execute()
320
-
320
+
321
321
  # 处理捕获
322
322
  captured_values = http_req.captured_values
323
-
323
+
324
324
  # 将捕获的变量注册到上下文
325
325
  for var_name, value in captured_values.items():
326
326
  context.set(var_name, value)
327
-
327
+
328
328
  # 保存完整响应(如果需要)
329
329
  if save_response:
330
330
  context.set(save_response, response)
@@ -337,18 +337,18 @@ def http_request(context, **kwargs):
337
337
  else:
338
338
  # 不需要重试,直接断言
339
339
  http_req.process_asserts()
340
-
340
+
341
341
  # 返回捕获的变量
342
342
  return captured_values
343
343
 
344
344
 
345
345
  def _deep_merge(dict1, dict2):
346
346
  """深度合并两个字典
347
-
347
+
348
348
  Args:
349
349
  dict1: 基础字典(会被修改)
350
350
  dict2: 要合并的字典(优先级更高)
351
-
351
+
352
352
  Returns:
353
353
  合并后的字典
354
354
  """
@@ -362,7 +362,7 @@ def _deep_merge(dict1, dict2):
362
362
 
363
363
  def _process_assertions_with_unified_retry(http_req, retry_config):
364
364
  """使用统一的重试配置处理断言
365
-
365
+
366
366
  Args:
367
367
  http_req: HTTP请求对象
368
368
  retry_config: 标准化的重试配置
@@ -379,21 +379,21 @@ def _process_assertions_with_unified_retry(http_req, retry_config):
379
379
  name="断言验证失败详情",
380
380
  attachment_type=allure.attachment_type.TEXT
381
381
  )
382
-
382
+
383
383
  # 添加一个特殊的标记到配置中,表示我们只想收集失败的断言而不抛出异常
384
384
  original_config = http_req.config.copy() if isinstance(http_req.config, dict) else {}
385
-
385
+
386
386
  # 创建一个临时副本
387
387
  temp_config = original_config.copy()
388
-
388
+
389
389
  # 添加特殊标记,用于指示http_request.py中的process_asserts在处理fail时不抛出异常
390
390
  # 注意:这需要对应修改HTTPRequest.process_asserts方法
391
391
  temp_config['_collect_failed_assertions_only'] = True
392
-
392
+
393
393
  try:
394
394
  # 临时替换配置
395
395
  http_req.config = temp_config
396
-
396
+
397
397
  # 重新运行断言,这次只收集失败的断言而不抛出异常
398
398
  _, failed_retryable_assertions = http_req.process_asserts()
399
399
  except Exception as collect_err:
@@ -407,23 +407,23 @@ def _process_assertions_with_unified_retry(http_req, retry_config):
407
407
  finally:
408
408
  # 恢复原始配置
409
409
  http_req.config = original_config
410
-
410
+
411
411
  # 有断言失败,判断是否有需要重试的断言
412
412
  if not failed_retryable_assertions:
413
413
  # 没有可重试的断言,重新抛出原始异常
414
414
  raise
415
-
415
+
416
416
  # 过滤需要重试的断言
417
417
  retryable_assertions = []
418
-
418
+
419
419
  for failed_assertion in failed_retryable_assertions:
420
420
  assertion_idx = failed_assertion['index']
421
-
421
+
422
422
  # 判断该断言是否应该重试
423
423
  should_retry = False
424
424
  specific_retry_count = retry_config['count']
425
425
  specific_retry_interval = retry_config['interval']
426
-
426
+
427
427
  # 检查特定断言配置
428
428
  if str(assertion_idx) in retry_config['specific']:
429
429
  should_retry = True
@@ -439,39 +439,39 @@ def _process_assertions_with_unified_retry(http_req, retry_config):
439
439
  # 检查是否重试所有
440
440
  elif retry_config['all']:
441
441
  should_retry = True
442
-
442
+
443
443
  # 如果应该重试,添加到可重试断言列表
444
444
  if should_retry:
445
445
  # 添加重试配置到断言对象
446
446
  failed_assertion['retry_count'] = specific_retry_count
447
447
  failed_assertion['retry_interval'] = specific_retry_interval
448
448
  retryable_assertions.append(failed_assertion)
449
-
449
+
450
450
  # 如果没有可重试的断言,重新抛出异常
451
451
  if not retryable_assertions:
452
452
  raise
453
-
453
+
454
454
  # 记录哪些断言会被重试
455
455
  retry_info = "\n".join([
456
- f"{i+1}. {a['type']} " +
457
- (f"[{a['path']}]" if a['path'] else "") +
458
- f": 重试 {a['retry_count']} 次,间隔 {a['retry_interval']} 秒"
456
+ f"{i+1}. {a['type']} " +
457
+ (f"[{a['path']}]" if a['path'] else "") +
458
+ f": 重试 {a['retry_count']} 次,间隔 {a['retry_interval']} 秒"
459
459
  for i, a in enumerate(retryable_assertions)
460
460
  ])
461
-
461
+
462
462
  allure.attach(
463
463
  f"找到 {len(retryable_assertions)} 个可重试的断言:\n\n{retry_info}",
464
464
  name="重试断言列表",
465
465
  attachment_type=allure.attachment_type.TEXT
466
466
  )
467
-
467
+
468
468
  # 开始重试循环
469
469
  max_retry_count = retry_config['count']
470
-
470
+
471
471
  # 找出所有断言中最大的重试次数
472
472
  for retryable_assertion in retryable_assertions:
473
473
  max_retry_count = max(max_retry_count, retryable_assertion.get('retry_count', 3))
474
-
474
+
475
475
  # 进行断言重试
476
476
  for attempt in range(1, max_retry_count + 1): # 从1开始,因为第0次已经尝试过了
477
477
  # 等待重试间隔
@@ -480,41 +480,41 @@ def _process_assertions_with_unified_retry(http_req, retry_config):
480
480
  retry_interval = retry_config['interval']
481
481
  for assertion in retryable_assertions:
482
482
  retry_interval = max(retry_interval, assertion.get('retry_interval', 1.0))
483
-
483
+
484
484
  allure.attach(
485
485
  f"重试 {len(retryable_assertions)} 个断言\n"
486
486
  f"等待间隔: {retry_interval}秒",
487
487
  name="断言重试信息",
488
488
  attachment_type=allure.attachment_type.TEXT
489
489
  )
490
-
490
+
491
491
  time.sleep(retry_interval)
492
-
492
+
493
493
  # 重新发送请求
494
494
  http_req.execute()
495
-
495
+
496
496
  # 过滤出仍在重试范围内的断言
497
497
  still_retryable_assertions = []
498
498
  for assertion in retryable_assertions:
499
499
  assertion_retry_count = assertion.get('retry_count', 3)
500
-
500
+
501
501
  # 如果断言的重试次数大于当前尝试次数,继续重试该断言
502
502
  if attempt < assertion_retry_count:
503
503
  still_retryable_assertions.append(assertion)
504
-
504
+
505
505
  # 如果没有可以继续重试的断言,跳出循环
506
506
  if not still_retryable_assertions:
507
507
  break
508
-
508
+
509
509
  # 只重试那些仍在重试范围内的断言
510
510
  try:
511
511
  # 从原始断言配置中提取出需要重试的断言
512
512
  retry_assertion_indexes = [a['index'] for a in still_retryable_assertions]
513
513
  retry_assertions = [http_req.config.get('asserts', [])[idx] for idx in retry_assertion_indexes]
514
-
514
+
515
515
  # 只处理需要重试的断言
516
516
  results, new_failed_assertions = http_req.process_asserts(specific_asserts=retry_assertions)
517
-
517
+
518
518
  # 如果所有断言都通过了,检查全部断言
519
519
  if not new_failed_assertions:
520
520
  # 执行一次完整的断言检查,确保所有断言都通过
@@ -534,10 +534,10 @@ def _process_assertions_with_unified_retry(http_req, retry_config):
534
534
  attachment_type=allure.attachment_type.TEXT
535
535
  )
536
536
  continue
537
-
537
+
538
538
  # 更新失败的可重试断言列表
539
539
  retryable_assertions = new_failed_assertions
540
-
540
+
541
541
  except AssertionError as retry_err:
542
542
  # 重试时断言失败,记录后继续重试
543
543
  allure.attach(
@@ -546,7 +546,7 @@ def _process_assertions_with_unified_retry(http_req, retry_config):
546
546
  attachment_type=allure.attachment_type.TEXT
547
547
  )
548
548
  continue
549
-
549
+
550
550
  # 重试次数用完,执行一次完整的断言以获取最终结果和错误
551
551
  # 这会抛出异常,如果仍然有断言失败
552
552
  allure.attach(
@@ -554,7 +554,7 @@ def _process_assertions_with_unified_retry(http_req, retry_config):
554
554
  name="重试完成",
555
555
  attachment_type=allure.attachment_type.TEXT
556
556
  )
557
-
557
+
558
558
  try:
559
559
  results, _ = http_req.process_asserts()
560
560
  return results
@@ -575,7 +575,7 @@ def _process_assertions_with_unified_retry(http_req, retry_config):
575
575
  # 以下函数保留用于向后兼容,但内部逻辑已更改为调用统一重试函数
576
576
  def _process_assertions_with_retry(http_req, retry_count, retry_interval):
577
577
  """处理断言并支持重试(向后兼容函数)
578
-
578
+
579
579
  Args:
580
580
  http_req: HTTP请求对象
581
581
  retry_count: 重试次数
@@ -590,20 +590,20 @@ def _process_assertions_with_retry(http_req, retry_count, retry_interval):
590
590
  'indices': [],
591
591
  'specific': {}
592
592
  }
593
-
593
+
594
594
  # 使用统一的重试处理函数
595
595
  return _process_assertions_with_unified_retry(http_req, retry_config)
596
596
 
597
597
 
598
598
  def _process_config_based_assertions_with_retry(http_req):
599
599
  """基于配置处理断言重试(向后兼容函数)
600
-
600
+
601
601
  Args:
602
602
  http_req: HTTP请求对象
603
603
  """
604
604
  # 从配置中获取重试信息
605
605
  retry_assertions_config = http_req.config.get('retry_assertions', {})
606
-
606
+
607
607
  # 创建统一的重试配置
608
608
  retry_config = {
609
609
  'enabled': True,
@@ -613,6 +613,6 @@ def _process_config_based_assertions_with_retry(http_req):
613
613
  'indices': retry_assertions_config.get('indices', []),
614
614
  'specific': retry_assertions_config.get('specific', {})
615
615
  }
616
-
616
+
617
617
  # 使用统一的重试处理函数
618
- return _process_assertions_with_unified_retry(http_req, retry_config)
618
+ return _process_assertions_with_unified_retry(http_req, retry_config)