pytest-dsl 0.11.0__py3-none-any.whl → 0.12.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.
@@ -185,7 +185,8 @@ def generate_random_number(**kwargs):
185
185
 
186
186
  @keyword_manager.register('字符串操作', [
187
187
  {'name': '操作', 'mapping': 'operation',
188
- 'description': '操作类型:拼接(concat)、替换(replace)、分割(split)、大写(upper)、小写(lower)、去空格(strip)',
188
+ 'description': '操作类型:拼接(concat)、替换(replace)、分割(split)'
189
+ '大写(upper)、小写(lower)、去空格(strip)',
189
190
  'default': 'strip'},
190
191
  {'name': '字符串', 'mapping': 'string', 'description': '要操作的字符串'},
191
192
  {'name': '参数1', 'mapping': 'param1',
@@ -366,3 +367,421 @@ def execute_command(**kwargs):
366
367
  'stdout': '',
367
368
  'stderr': str(e)
368
369
  }
370
+
371
+
372
+ @keyword_manager.register('求和', [
373
+ {'name': '数据', 'mapping': 'data', 'description': '要求和的数字列表或可迭代对象'},
374
+ {'name': '起始值', 'mapping': 'start', 'description': '求和的起始值', 'default': 0}
375
+ ])
376
+ def sum_values(**kwargs):
377
+ """计算数字列表的总和
378
+
379
+ Args:
380
+ data: 要求和的数字列表
381
+ start: 求和的起始值,默认为0
382
+
383
+ Returns:
384
+ 数字总和
385
+ """
386
+ data = kwargs.get('data', [])
387
+ start = kwargs.get('start', 0)
388
+
389
+ # 确保data是可迭代的
390
+ if not hasattr(data, '__iter__') or isinstance(data, str):
391
+ raise ValueError("数据必须是可迭代的数字列表")
392
+
393
+ try:
394
+ result = sum(data, start)
395
+
396
+ with allure.step(f"求和计算: 数据长度={len(data)}, 起始值={start}"):
397
+ allure.attach(
398
+ f"输入数据: {data}\n起始值: {start}\n结果: {result}",
399
+ name="求和结果",
400
+ attachment_type=allure.attachment_type.TEXT
401
+ )
402
+
403
+ return result
404
+ except Exception as e:
405
+ allure.attach(
406
+ f"求和计算失败: {str(e)}",
407
+ name="求和错误",
408
+ attachment_type=allure.attachment_type.TEXT
409
+ )
410
+ raise
411
+
412
+
413
+ @keyword_manager.register('获取长度', [
414
+ {'name': '对象', 'mapping': 'obj', 'description': '要获取长度的对象(字符串、列表、字典等)'}
415
+ ])
416
+ def get_length(**kwargs):
417
+ """获取对象的长度
418
+
419
+ Args:
420
+ obj: 要获取长度的对象
421
+
422
+ Returns:
423
+ int: 对象的长度
424
+ """
425
+ obj = kwargs.get('obj')
426
+
427
+ if obj is None:
428
+ return 0
429
+
430
+ try:
431
+ result = len(obj)
432
+
433
+ with allure.step(f"获取长度: 对象类型={type(obj).__name__}"):
434
+ allure.attach(
435
+ f"对象: {obj}\n类型: {type(obj).__name__}\n长度: {result}",
436
+ name="长度计算结果",
437
+ attachment_type=allure.attachment_type.TEXT
438
+ )
439
+
440
+ return result
441
+ except Exception as e:
442
+ allure.attach(
443
+ f"获取长度失败: {str(e)}",
444
+ name="长度计算错误",
445
+ attachment_type=allure.attachment_type.TEXT
446
+ )
447
+ raise
448
+
449
+
450
+ @keyword_manager.register('获取最大值', [
451
+ {'name': '数据', 'mapping': 'data', 'description': '要比较的数据列表或多个参数'},
452
+ {'name': '默认值', 'mapping': 'default',
453
+ 'description': '当数据为空时的默认值', 'default': None}
454
+ ])
455
+ def get_max_value(**kwargs):
456
+ """获取最大值
457
+
458
+ Args:
459
+ data: 要比较的数据
460
+ default: 当数据为空时的默认值
461
+
462
+ Returns:
463
+ 最大值
464
+ """
465
+ data = kwargs.get('data')
466
+ default = kwargs.get('default')
467
+
468
+ if data is None:
469
+ if default is not None:
470
+ return default
471
+ raise ValueError("数据不能为空")
472
+
473
+ try:
474
+ # 如果data不是可迭代的,将其转换为列表
475
+ if not hasattr(data, '__iter__') or isinstance(data, str):
476
+ data = [data]
477
+
478
+ if len(data) == 0:
479
+ if default is not None:
480
+ return default
481
+ raise ValueError("数据列表为空")
482
+
483
+ result = max(data)
484
+
485
+ with allure.step(f"获取最大值: 数据长度={len(data)}"):
486
+ allure.attach(
487
+ f"输入数据: {data}\n最大值: {result}",
488
+ name="最大值计算结果",
489
+ attachment_type=allure.attachment_type.TEXT
490
+ )
491
+
492
+ return result
493
+ except Exception as e:
494
+ allure.attach(
495
+ f"获取最大值失败: {str(e)}",
496
+ name="最大值计算错误",
497
+ attachment_type=allure.attachment_type.TEXT
498
+ )
499
+ raise
500
+
501
+
502
+ @keyword_manager.register('获取最小值', [
503
+ {'name': '数据', 'mapping': 'data', 'description': '要比较的数据列表或多个参数'},
504
+ {'name': '默认值', 'mapping': 'default',
505
+ 'description': '当数据为空时的默认值', 'default': None}
506
+ ])
507
+ def get_min_value(**kwargs):
508
+ """获取最小值
509
+
510
+ Args:
511
+ data: 要比较的数据
512
+ default: 当数据为空时的默认值
513
+
514
+ Returns:
515
+ 最小值
516
+ """
517
+ data = kwargs.get('data')
518
+ default = kwargs.get('default')
519
+
520
+ if data is None:
521
+ if default is not None:
522
+ return default
523
+ raise ValueError("数据不能为空")
524
+
525
+ try:
526
+ # 如果data不是可迭代的,将其转换为列表
527
+ if not hasattr(data, '__iter__') or isinstance(data, str):
528
+ data = [data]
529
+
530
+ if len(data) == 0:
531
+ if default is not None:
532
+ return default
533
+ raise ValueError("数据列表为空")
534
+
535
+ result = min(data)
536
+
537
+ with allure.step(f"获取最小值: 数据长度={len(data)}"):
538
+ allure.attach(
539
+ f"输入数据: {data}\n最小值: {result}",
540
+ name="最小值计算结果",
541
+ attachment_type=allure.attachment_type.TEXT
542
+ )
543
+
544
+ return result
545
+ except Exception as e:
546
+ allure.attach(
547
+ f"获取最小值失败: {str(e)}",
548
+ name="最小值计算错误",
549
+ attachment_type=allure.attachment_type.TEXT
550
+ )
551
+ raise
552
+
553
+
554
+ @keyword_manager.register('绝对值', [
555
+ {'name': '数值', 'mapping': 'number', 'description': '要计算绝对值的数字'}
556
+ ])
557
+ def get_absolute_value(**kwargs):
558
+ """计算数字的绝对值
559
+
560
+ Args:
561
+ number: 要计算绝对值的数字
562
+
563
+ Returns:
564
+ 数字的绝对值
565
+ """
566
+ number = kwargs.get('number')
567
+
568
+ if number is None:
569
+ raise ValueError("数值不能为空")
570
+
571
+ try:
572
+ result = abs(number)
573
+
574
+ with allure.step(f"计算绝对值: {number}"):
575
+ allure.attach(
576
+ f"输入数值: {number}\n绝对值: {result}",
577
+ name="绝对值计算结果",
578
+ attachment_type=allure.attachment_type.TEXT
579
+ )
580
+
581
+ return result
582
+ except Exception as e:
583
+ allure.attach(
584
+ f"计算绝对值失败: {str(e)}",
585
+ name="绝对值计算错误",
586
+ attachment_type=allure.attachment_type.TEXT
587
+ )
588
+ raise
589
+
590
+
591
+ @keyword_manager.register('四舍五入', [
592
+ {'name': '数值', 'mapping': 'number', 'description': '要四舍五入的数字'},
593
+ {'name': '小数位数', 'mapping': 'ndigits',
594
+ 'description': '保留的小数位数', 'default': 0}
595
+ ])
596
+ def round_number(**kwargs):
597
+ """对数字进行四舍五入
598
+
599
+ Args:
600
+ number: 要四舍五入的数字
601
+ ndigits: 保留的小数位数,默认为0(整数)
602
+
603
+ Returns:
604
+ 四舍五入后的数字
605
+ """
606
+ number = kwargs.get('number')
607
+ ndigits = kwargs.get('ndigits', 0)
608
+
609
+ if number is None:
610
+ raise ValueError("数值不能为空")
611
+
612
+ try:
613
+ if ndigits == 0:
614
+ result = round(number)
615
+ else:
616
+ result = round(number, int(ndigits))
617
+
618
+ with allure.step(f"四舍五入: {number} -> {ndigits}位小数"):
619
+ allure.attach(
620
+ f"输入数值: {number}\n小数位数: {ndigits}\n结果: {result}",
621
+ name="四舍五入结果",
622
+ attachment_type=allure.attachment_type.TEXT
623
+ )
624
+
625
+ return result
626
+ except Exception as e:
627
+ allure.attach(
628
+ f"四舍五入失败: {str(e)}",
629
+ name="四舍五入错误",
630
+ attachment_type=allure.attachment_type.TEXT
631
+ )
632
+ raise
633
+
634
+
635
+ @keyword_manager.register('转换为字符串', [
636
+ {'name': '值', 'mapping': 'value', 'description': '要转换为字符串的值'}
637
+ ])
638
+ def convert_to_string(**kwargs):
639
+ """将值转换为字符串
640
+
641
+ Args:
642
+ value: 要转换的值
643
+
644
+ Returns:
645
+ str: 转换后的字符串
646
+ """
647
+ value = kwargs.get('value')
648
+
649
+ try:
650
+ result = str(value)
651
+
652
+ with allure.step(f"转换为字符串: {type(value).__name__} -> str"):
653
+ allure.attach(
654
+ f"原始值: {value}\n原始类型: {type(value).__name__}\n"
655
+ f"转换结果: {result}\n结果类型: {type(result).__name__}",
656
+ name="字符串转换结果",
657
+ attachment_type=allure.attachment_type.TEXT
658
+ )
659
+
660
+ return result
661
+ except Exception as e:
662
+ allure.attach(
663
+ f"转换为字符串失败: {str(e)}",
664
+ name="字符串转换错误",
665
+ attachment_type=allure.attachment_type.TEXT
666
+ )
667
+ raise
668
+
669
+
670
+ @keyword_manager.register('转换为整数', [
671
+ {'name': '值', 'mapping': 'value', 'description': '要转换为整数的值'},
672
+ {'name': '进制', 'mapping': 'base',
673
+ 'description': '数字进制(当值为字符串时)', 'default': 10}
674
+ ])
675
+ def convert_to_integer(**kwargs):
676
+ """将值转换为整数
677
+
678
+ Args:
679
+ value: 要转换的值
680
+ base: 数字进制(当值为字符串时),默认为10
681
+
682
+ Returns:
683
+ int: 转换后的整数
684
+ """
685
+ value = kwargs.get('value')
686
+ base = kwargs.get('base', 10)
687
+
688
+ if value is None:
689
+ raise ValueError("值不能为空")
690
+
691
+ try:
692
+ # 如果指定了非10进制,将值转换为字符串再进行进制转换
693
+ if int(base) != 10:
694
+ value_str = str(value)
695
+ result = int(value_str, int(base))
696
+ else:
697
+ result = int(value)
698
+
699
+ with allure.step(f"转换为整数: {type(value).__name__} -> int"):
700
+ allure.attach(
701
+ f"原始值: {value}\n原始类型: {type(value).__name__}\n"
702
+ f"进制: {base}\n转换结果: {result}\n结果类型: {type(result).__name__}",
703
+ name="整数转换结果",
704
+ attachment_type=allure.attachment_type.TEXT
705
+ )
706
+
707
+ return result
708
+ except Exception as e:
709
+ allure.attach(
710
+ f"转换为整数失败: {str(e)}",
711
+ name="整数转换错误",
712
+ attachment_type=allure.attachment_type.TEXT
713
+ )
714
+ raise
715
+
716
+
717
+ @keyword_manager.register('转换为浮点数', [
718
+ {'name': '值', 'mapping': 'value', 'description': '要转换为浮点数的值'}
719
+ ])
720
+ def convert_to_float(**kwargs):
721
+ """将值转换为浮点数
722
+
723
+ Args:
724
+ value: 要转换的值
725
+
726
+ Returns:
727
+ float: 转换后的浮点数
728
+ """
729
+ value = kwargs.get('value')
730
+
731
+ if value is None:
732
+ raise ValueError("值不能为空")
733
+
734
+ try:
735
+ result = float(value)
736
+
737
+ with allure.step(f"转换为浮点数: {type(value).__name__} -> float"):
738
+ allure.attach(
739
+ f"原始值: {value}\n原始类型: {type(value).__name__}\n"
740
+ f"转换结果: {result}\n结果类型: {type(result).__name__}",
741
+ name="浮点数转换结果",
742
+ attachment_type=allure.attachment_type.TEXT
743
+ )
744
+
745
+ return result
746
+ except Exception as e:
747
+ allure.attach(
748
+ f"转换为浮点数失败: {str(e)}",
749
+ name="浮点数转换错误",
750
+ attachment_type=allure.attachment_type.TEXT
751
+ )
752
+ raise
753
+
754
+
755
+ @keyword_manager.register('转换为布尔值', [
756
+ {'name': '值', 'mapping': 'value', 'description': '要转换为布尔值的值'}
757
+ ])
758
+ def convert_to_boolean(**kwargs):
759
+ """将值转换为布尔值
760
+
761
+ Args:
762
+ value: 要转换的值
763
+
764
+ Returns:
765
+ bool: 转换后的布尔值
766
+ """
767
+ value = kwargs.get('value')
768
+
769
+ try:
770
+ result = bool(value)
771
+
772
+ with allure.step(f"转换为布尔值: {type(value).__name__} -> bool"):
773
+ allure.attach(
774
+ f"原始值: {value}\n原始类型: {type(value).__name__}\n"
775
+ f"转换结果: {result}\n结果类型: {type(result).__name__}",
776
+ name="布尔值转换结果",
777
+ attachment_type=allure.attachment_type.TEXT
778
+ )
779
+
780
+ return result
781
+ except Exception as e:
782
+ allure.attach(
783
+ f"转换为布尔值失败: {str(e)}",
784
+ name="布尔值转换错误",
785
+ attachment_type=allure.attachment_type.TEXT
786
+ )
787
+ raise
@@ -7,16 +7,19 @@ from pytest_dsl.core.keyword_manager import keyword_manager, Parameter
7
7
  # 配置日志
8
8
  logger = logging.getLogger(__name__)
9
9
 
10
+
10
11
  class RemoteKeywordClient:
11
12
  """远程关键字客户端,用于连接远程关键字服务器并执行关键字"""
12
13
 
13
- def __init__(self, url='http://localhost:8270/', api_key=None, alias=None, sync_config=None):
14
+ def __init__(self, url='http://localhost:8270/', api_key=None, alias=None,
15
+ sync_config=None):
14
16
  self.url = url
15
17
  self.server = xmlrpc.client.ServerProxy(url, allow_none=True)
16
18
  self.keyword_cache = {}
17
19
  self.param_mappings = {} # 存储每个关键字的参数映射
18
20
  self.api_key = api_key
19
- self.alias = alias or url.replace('http://', '').replace('https://', '').split(':')[0]
21
+ self.alias = alias or url.replace('http://', '').replace(
22
+ 'https://', '').split(':')[0]
20
23
 
21
24
  # 变量传递配置(简化版)
22
25
  self.sync_config = sync_config or {
@@ -24,7 +27,6 @@ class RemoteKeywordClient:
24
27
  'sync_yaml_vars': True, # 连接时传递YAML配置变量
25
28
  'yaml_sync_keys': None, # 指定要同步的YAML键列表,None表示同步所有(除了排除的)
26
29
  'yaml_exclude_patterns': [ # 排除包含这些模式的YAML变量
27
- 'password', 'secret', 'key', 'token', 'credential', 'auth',
28
30
  'private', 'remote_servers' # 排除远程服务器配置避免循环
29
31
  ]
30
32
  }
@@ -42,7 +44,8 @@ class RemoteKeywordClient:
42
44
  self._send_initial_variables()
43
45
 
44
46
  logger.info(f"已连接到远程关键字服务器: {self.url}, 别名: {self.alias}")
45
- print(f"RemoteKeywordClient: 成功连接到远程服务器 {self.url}, 别名: {self.alias}")
47
+ print(f"RemoteKeywordClient: 成功连接到远程服务器 {self.url}, "
48
+ f"别名: {self.alias}")
46
49
  return True
47
50
  except Exception as e:
48
51
  error_msg = f"连接远程关键字服务器失败: {str(e)}"
@@ -81,7 +84,8 @@ class RemoteKeywordClient:
81
84
  for param_detail in param_details:
82
85
  param_name = param_detail['name']
83
86
  param_mapping_name = param_detail.get('mapping', param_name)
84
- param_desc = param_detail.get('description', f'远程关键字参数: {param_name}')
87
+ param_desc = param_detail.get('description',
88
+ f'远程关键字参数: {param_name}')
85
89
  param_default = param_detail.get('default')
86
90
 
87
91
  # 确保参数名称正确映射
@@ -114,7 +118,8 @@ class RemoteKeywordClient:
114
118
  'func': remote_func,
115
119
  'mapping': {p['name']: p['mapping'] for p in parameters},
116
120
  'parameters': [Parameter(**p) for p in parameters],
117
- 'defaults': {p['mapping']: p['default'] for p in parameters if p['default'] is not None}, # 添加默认值支持
121
+ 'defaults': {p['mapping']: p['default'] for p in parameters
122
+ if p['default'] is not None}, # 添加默认值支持
118
123
  'remote': True, # 标记为远程关键字
119
124
  'alias': self.alias,
120
125
  'original_name': name
@@ -161,7 +166,7 @@ class RemoteKeywordClient:
161
166
  else:
162
167
  # 如果没有任何映射,使用原始参数名
163
168
  param_mapping = None
164
- print(f"没有找到参数映射,使用原始参数名")
169
+ print("没有找到参数映射,使用原始参数名")
165
170
 
166
171
  # 映射参数名称
167
172
  mapped_kwargs = {}
@@ -209,10 +214,11 @@ class RemoteKeywordClient:
209
214
 
210
215
  # 处理响应数据
211
216
  if 'response' in return_data and return_data['response']:
212
- print(f"远程关键字响应数据: 已接收")
217
+ print("远程关键字响应数据: 已接收")
213
218
 
214
219
  # 检查是否为新的统一返回格式(包含captures等字段)
215
- if 'captures' in return_data or 'session_state' in return_data or 'metadata' in return_data:
220
+ if ('captures' in return_data or 'session_state' in return_data or
221
+ 'metadata' in return_data):
216
222
  # 返回完整的新格式,让DSL执行器处理变量捕获
217
223
  return return_data
218
224
  elif 'result' in return_data:
@@ -243,11 +249,13 @@ class RemoteKeywordClient:
243
249
  if variables_to_send:
244
250
  try:
245
251
  # 调用远程服务器的变量接收接口
246
- result = self.server.sync_variables_from_client(variables_to_send, self.api_key)
252
+ result = self.server.sync_variables_from_client(
253
+ variables_to_send, self.api_key)
247
254
  if result.get('status') == 'success':
248
255
  print(f"成功传递 {len(variables_to_send)} 个变量到远程服务器")
249
256
  else:
250
- print(f"传递变量到远程服务器失败: {result.get('error', '未知错误')}")
257
+ print(f"传递变量到远程服务器失败: "
258
+ f"{result.get('error', '未知错误')}")
251
259
  except Exception as e:
252
260
  print(f"调用远程变量接口失败: {str(e)}")
253
261
  else:
@@ -295,10 +303,12 @@ class RemoteKeywordClient:
295
303
  # 获取所有YAML变量
296
304
  yaml_data = yaml_vars._variables
297
305
  if yaml_data:
306
+ print(f"客户端YAML变量总数: {len(yaml_data)}")
307
+
298
308
  # 检查同步配置中是否指定了特定的键
299
309
  sync_keys = self.sync_config.get('yaml_sync_keys', None)
300
310
  exclude_patterns = self.sync_config.get('yaml_exclude_patterns', [
301
- 'password', 'secret', 'key', 'token', 'credential', 'auth',
311
+ 'password', 'secret', 'token', 'credential', 'auth',
302
312
  'private', 'remote_servers' # 排除远程服务器配置避免循环
303
313
  ])
304
314
 
@@ -307,6 +317,7 @@ class RemoteKeywordClient:
307
317
  for key in sync_keys:
308
318
  if key in yaml_data:
309
319
  variables[key] = yaml_data[key]
320
+ print(f"传递指定YAML变量: {key}")
310
321
  else:
311
322
  # 传递所有YAML变量,但排除敏感信息
312
323
  for key, value in yaml_data.items():
@@ -323,7 +334,8 @@ class RemoteKeywordClient:
323
334
  if not should_exclude and isinstance(value, str):
324
335
  value_lower = value.lower()
325
336
  for pattern in exclude_patterns:
326
- if pattern.lower() in value_lower and len(value) < 100: # 只检查短字符串
337
+ if (pattern.lower() in value_lower and
338
+ len(value) < 100): # 只检查短字符串
327
339
  should_exclude = True
328
340
  break
329
341
 
@@ -336,11 +348,11 @@ class RemoteKeywordClient:
336
348
 
337
349
  except Exception as e:
338
350
  logger.warning(f"收集YAML变量失败: {str(e)}")
351
+ print(f"收集YAML变量失败: {str(e)}")
339
352
 
340
353
  return variables
341
354
 
342
355
 
343
-
344
356
  # 远程关键字客户端管理器
345
357
  class RemoteKeywordManager:
346
358
  """远程关键字客户端管理器,管理多个远程服务器连接"""
@@ -348,7 +360,8 @@ class RemoteKeywordManager:
348
360
  def __init__(self):
349
361
  self.clients = {} # 别名 -> 客户端实例
350
362
 
351
- def register_remote_server(self, url, alias, api_key=None, sync_config=None):
363
+ def register_remote_server(self, url, alias, api_key=None,
364
+ sync_config=None):
352
365
  """注册远程关键字服务器
353
366
 
354
367
  Args:
@@ -361,7 +374,8 @@ class RemoteKeywordManager:
361
374
  bool: 是否成功连接
362
375
  """
363
376
  print(f"RemoteKeywordManager: 正在注册远程服务器 {url} 别名 {alias}")
364
- client = RemoteKeywordClient(url=url, api_key=api_key, alias=alias, sync_config=sync_config)
377
+ client = RemoteKeywordClient(url=url, api_key=api_key, alias=alias,
378
+ sync_config=sync_config)
365
379
  success = client.connect()
366
380
 
367
381
  if success:
@@ -394,6 +408,5 @@ class RemoteKeywordManager:
394
408
  return client._execute_remote_keyword(name=keyword_name, **kwargs)
395
409
 
396
410
 
397
-
398
411
  # 创建全局远程关键字管理器实例
399
412
  remote_keyword_manager = RemoteKeywordManager()
@@ -403,9 +403,26 @@ class RemoteKeywordServer:
403
403
  self.shared_variables[name] = value
404
404
  print(f"接收到客户端变量: {name}")
405
405
 
406
+ # 将所有同步的变量直接注入到yaml_vars中,实现无缝访问
407
+ from pytest_dsl.core.yaml_vars import yaml_vars
408
+
409
+ for name, value in variables.items():
410
+ # 直接设置到yaml_vars中,确保所有关键字都能无缝访问
411
+ yaml_vars._variables[name] = value
412
+ print(f"✓ 变量 {name} 已注入到yaml_vars,实现无缝访问")
413
+
414
+ # 同时处理全局变量到global_context
415
+ from pytest_dsl.core.global_context import global_context
416
+ for name, value in variables.items():
417
+ if name.startswith('g_'):
418
+ global_context.set_variable(name, value)
419
+ print(f"✓ 全局变量 {name} 已注入到global_context")
420
+
421
+ print(f"✅ 总共同步了 {len(variables)} 个变量,全部实现无缝访问")
422
+
406
423
  return {
407
424
  'status': 'success',
408
- 'message': f'成功同步 {len(variables)} 个变量'
425
+ 'message': f'成功同步 {len(variables)} 个变量,全部实现无缝访问'
409
426
  }
410
427
  except Exception as e:
411
428
  return {