mobile-mcp-ai 2.1.2__py3-none-any.whl → 2.5.8__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 (65) hide show
  1. mobile_mcp/__init__.py +34 -0
  2. mobile_mcp/config.py +142 -0
  3. mobile_mcp/core/basic_tools_lite.py +3266 -0
  4. {core → mobile_mcp/core}/device_manager.py +2 -2
  5. mobile_mcp/core/dynamic_config.py +272 -0
  6. mobile_mcp/core/ios_client_wda.py +569 -0
  7. mobile_mcp/core/ios_device_manager_wda.py +306 -0
  8. {core → mobile_mcp/core}/mobile_client.py +279 -39
  9. mobile_mcp/core/template_matcher.py +429 -0
  10. mobile_mcp/core/templates/close_buttons/auto_x_0112_151217.png +0 -0
  11. mobile_mcp/core/templates/close_buttons/auto_x_0112_152037.png +0 -0
  12. mobile_mcp/core/templates/close_buttons/auto_x_0112_152840.png +0 -0
  13. mobile_mcp/core/templates/close_buttons/auto_x_0112_153256.png +0 -0
  14. mobile_mcp/core/templates/close_buttons/auto_x_0112_154847.png +0 -0
  15. mobile_mcp/core/templates/close_buttons/gray_x_stock_ad.png +0 -0
  16. {core → mobile_mcp/core}/utils/smart_wait.py +3 -3
  17. mobile_mcp/mcp_tools/__init__.py +10 -0
  18. mobile_mcp/mcp_tools/mcp_server.py +1071 -0
  19. mobile_mcp_ai-2.5.8.dist-info/METADATA +469 -0
  20. mobile_mcp_ai-2.5.8.dist-info/RECORD +32 -0
  21. mobile_mcp_ai-2.5.8.dist-info/entry_points.txt +2 -0
  22. mobile_mcp_ai-2.5.8.dist-info/licenses/LICENSE +201 -0
  23. mobile_mcp_ai-2.5.8.dist-info/top_level.txt +1 -0
  24. core/ai/__init__.py +0 -11
  25. core/ai/ai_analyzer.py +0 -197
  26. core/ai/ai_config.py +0 -116
  27. core/ai/ai_platform_adapter.py +0 -399
  28. core/ai/smart_test_executor.py +0 -520
  29. core/ai/test_generator.py +0 -365
  30. core/ai/test_generator_from_history.py +0 -391
  31. core/ai/test_generator_standalone.py +0 -293
  32. core/assertion/__init__.py +0 -9
  33. core/assertion/smart_assertion.py +0 -341
  34. core/basic_tools.py +0 -377
  35. core/h5/__init__.py +0 -10
  36. core/h5/h5_handler.py +0 -548
  37. core/ios_client.py +0 -219
  38. core/ios_device_manager.py +0 -252
  39. core/locator/__init__.py +0 -10
  40. core/locator/cursor_ai_auto_analyzer.py +0 -119
  41. core/locator/cursor_vision_helper.py +0 -414
  42. core/locator/mobile_smart_locator.py +0 -1640
  43. core/locator/position_analyzer.py +0 -813
  44. core/locator/script_updater.py +0 -157
  45. core/nl_test_runner.py +0 -585
  46. core/smart_app_launcher.py +0 -334
  47. core/smart_tools.py +0 -311
  48. mcp/__init__.py +0 -8
  49. mcp/mcp_server.py +0 -1919
  50. mcp/mcp_server_simple.py +0 -476
  51. mobile_mcp_ai-2.1.2.dist-info/METADATA +0 -567
  52. mobile_mcp_ai-2.1.2.dist-info/RECORD +0 -45
  53. mobile_mcp_ai-2.1.2.dist-info/entry_points.txt +0 -2
  54. mobile_mcp_ai-2.1.2.dist-info/top_level.txt +0 -4
  55. vision/__init__.py +0 -10
  56. vision/vision_locator.py +0 -404
  57. {core → mobile_mcp/core}/__init__.py +0 -0
  58. {core → mobile_mcp/core}/utils/__init__.py +0 -0
  59. {core → mobile_mcp/core}/utils/logger.py +0 -0
  60. {core → mobile_mcp/core}/utils/operation_history_manager.py +0 -0
  61. {utils → mobile_mcp/utils}/__init__.py +0 -0
  62. {utils → mobile_mcp/utils}/logger.py +0 -0
  63. {utils → mobile_mcp/utils}/xml_formatter.py +0 -0
  64. {utils → mobile_mcp/utils}/xml_parser.py +0 -0
  65. {mobile_mcp_ai-2.1.2.dist-info → mobile_mcp_ai-2.5.8.dist-info}/WHEEL +0 -0
core/nl_test_runner.py DELETED
@@ -1,585 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- 自然语言驱动的测试执行器
5
-
6
- 功能:
7
- 1. 解析自然语言测试步骤
8
- 2. 调用MCP工具执行
9
- 3. Cursor AI分析XML定位元素
10
- 4. 失败时自动视觉识别
11
- 5. 生成Python测试模板
12
- """
13
- import asyncio
14
- import json
15
- import re
16
- from pathlib import Path
17
- from typing import List, Dict, Optional, Any
18
- from datetime import datetime
19
-
20
-
21
- class NLTestRunner:
22
- """自然语言测试执行器"""
23
-
24
- def __init__(self, mcp_tools=None, locator=None, mobile_client=None, script_path=None):
25
- """
26
- 初始化自然语言测试执行器
27
-
28
- Args:
29
- mcp_tools: MCP工具字典(mobile_click, mobile_input等)
30
- locator: MobileSmartLocator实例(用于XML分析)
31
- mobile_client: MobileClient实例(用于视觉识别)
32
- script_path: 脚本路径(用于更新用例)
33
- """
34
- self.mcp_tools = mcp_tools or {}
35
- self.locator = locator
36
- self.mobile_client = mobile_client
37
- self.script_path = script_path
38
- self.execution_log = [] # 执行日志
39
- self.steps = [] # 解析后的步骤
40
- self.current_step_index = 0
41
-
42
- def parse_natural_language(self, nl_text: str) -> List[Dict]:
43
- """
44
- 解析自然语言测试步骤
45
-
46
- Args:
47
- nl_text: 自然语言描述,如:
48
- "启动应用com.im30.way,点击底部第四个图标,点击设置,点击语言,点击English,点击保存"
49
-
50
- Returns:
51
- 步骤列表
52
- """
53
- steps = []
54
-
55
- # 简单规则匹配(可以后续用Cursor AI增强)
56
- # 匹配模式:
57
- # - 启动应用xxx
58
- # - 点击xxx
59
- # - 输入xxx为xxx
60
- # - 滑动xxx
61
- # - 等待x秒
62
- # - 断言xxx
63
-
64
- # 分割步骤(按逗号、句号、换行)
65
- parts = re.split(r'[,,。.\n]', nl_text.strip())
66
-
67
- for part in parts:
68
- part = part.strip()
69
- if not part:
70
- continue
71
-
72
- # 启动应用
73
- if '启动应用' in part or '启动' in part or '打开应用' in part or '打开' in part:
74
- package_match = re.search(r'com\.\w+(?:\.\w+)*', part)
75
- if package_match:
76
- steps.append({
77
- 'action': 'launch_app',
78
- 'package': package_match.group(),
79
- 'original_text': part
80
- })
81
- continue # 找到后继续下一个
82
-
83
- # 点击
84
- elif '点击' in part:
85
- # 提取元素描述
86
- element = part.replace('点击', '').strip()
87
- steps.append({
88
- 'action': 'click',
89
- 'element': element,
90
- 'original_text': part
91
- })
92
-
93
- # 输入
94
- elif '输入' in part:
95
- # 匹配:输入xxx为xxx 或 输入xxx xxx
96
- input_match = re.search(r'输入(.+?)(?:为|:|:)(.+)', part)
97
- if input_match:
98
- element = input_match.group(1).strip()
99
- text = input_match.group(2).strip()
100
- steps.append({
101
- 'action': 'input',
102
- 'element': element,
103
- 'text': text,
104
- 'original_text': part
105
- })
106
- else:
107
- # 简单匹配:输入xxx
108
- element = part.replace('输入', '').strip()
109
- steps.append({
110
- 'action': 'input',
111
- 'element': element,
112
- 'text': '', # 需要后续补充
113
- 'original_text': part
114
- })
115
-
116
- # 滑动
117
- elif '滑动' in part:
118
- direction = None
119
- if '上' in part or 'up' in part.lower():
120
- direction = 'up'
121
- elif '下' in part or 'down' in part.lower():
122
- direction = 'down'
123
- elif '左' in part or 'left' in part.lower():
124
- direction = 'left'
125
- elif '右' in part or 'right' in part.lower():
126
- direction = 'right'
127
-
128
- if direction:
129
- steps.append({
130
- 'action': 'swipe',
131
- 'direction': direction,
132
- 'original_text': part
133
- })
134
-
135
- # 等待
136
- elif '等待' in part:
137
- wait_match = re.search(r'(\d+)', part)
138
- if wait_match:
139
- seconds = int(wait_match.group(1))
140
- steps.append({
141
- 'action': 'wait',
142
- 'seconds': seconds,
143
- 'original_text': part
144
- })
145
-
146
- # 断言
147
- elif '断言' in part or '验证' in part or '检查' in part:
148
- text_match = re.search(r'["\'](.+?)["\']', part)
149
- if text_match:
150
- text = text_match.group(1)
151
- steps.append({
152
- 'action': 'assert_text',
153
- 'text': text,
154
- 'original_text': part
155
- })
156
-
157
- self.steps = steps
158
- return steps
159
-
160
- async def execute_step(self, step: Dict, step_index: int) -> Dict:
161
- """
162
- 执行单个步骤
163
-
164
- Args:
165
- step: 步骤信息
166
- step_index: 步骤索引
167
-
168
- Returns:
169
- 执行结果
170
- """
171
- action = step.get('action')
172
- original_text = step.get('original_text', '')
173
-
174
- print(f"\n{'='*60}")
175
- print(f"📋 步骤 {step_index + 1}: {original_text}")
176
- print(f"{'='*60}")
177
-
178
- result = {
179
- 'step_index': step_index,
180
- 'action': action,
181
- 'original_text': original_text,
182
- 'success': False,
183
- 'method': None,
184
- 'element_ref': None,
185
- 'coordinate': None,
186
- 'error': None,
187
- 'timestamp': datetime.now().isoformat()
188
- }
189
-
190
- try:
191
- if action == 'launch_app':
192
- package = step.get('package')
193
- print(f"🚀 启动应用: {package}")
194
- if 'mobile_launch_app' in self.mcp_tools:
195
- await self.mcp_tools['mobile_launch_app'](package_name=package, wait_time=3)
196
- result['success'] = True
197
- result['method'] = 'mcp_launch_app'
198
- else:
199
- raise ValueError("mobile_launch_app工具不可用")
200
-
201
- elif action == 'click':
202
- element = step.get('element')
203
- print(f"🖱️ 点击: {element}")
204
-
205
- # 先获取XML
206
- print(f" 📋 步骤1: 获取页面XML...")
207
- xml_result = await self._get_xml_snapshot()
208
-
209
- # Cursor AI分析XML(这里需要调用Cursor AI)
210
- print(f" 🤖 步骤2: Cursor AI分析XML定位元素...")
211
- locate_result = await self._cursor_ai_analyze_xml(xml_result, element)
212
-
213
- if locate_result and locate_result.get('found'):
214
- # 找到元素,直接使用定位结果执行点击(避免重复定位)
215
- element_ref = locate_result.get('ref')
216
- result['element_ref'] = element_ref
217
- result['method'] = locate_result.get('method', 'xml_analysis')
218
-
219
- print(f" ✅ 定位成功: {element_ref}")
220
- print(f" 🖱️ 步骤3: 执行点击...")
221
-
222
- # 直接使用client点击,避免MCP工具重复定位
223
- if self.mobile_client:
224
- try:
225
- click_result = await self.mobile_client.click(element, ref=element_ref, verify=False)
226
- if click_result.get('success'):
227
- result['success'] = True
228
- else:
229
- result['error'] = click_result.get('reason', '点击失败')
230
- except Exception as e:
231
- result['error'] = f"点击异常: {e}"
232
- else:
233
- # 降级:使用MCP工具
234
- if 'mobile_click' in self.mcp_tools:
235
- click_result = await self.mcp_tools['mobile_click'](element_desc=element)
236
- if click_result and click_result.get('success'):
237
- result['success'] = True
238
- else:
239
- result['error'] = click_result.get('error', '点击失败')
240
- else:
241
- raise ValueError("mobile_click工具不可用")
242
- else:
243
- # XML分析失败,使用视觉识别
244
- print(f" ⚠️ XML分析失败,使用视觉识别...")
245
- vision_result = await self._cursor_ai_vision_recognize(element)
246
-
247
- if vision_result and vision_result.get('coordinate'):
248
- coord = vision_result['coordinate']
249
- x, y = coord['x'], coord['y']
250
- result['coordinate'] = coord
251
- result['method'] = 'vision_recognition'
252
-
253
- print(f" ✅ 视觉识别成功: ({x}, {y})")
254
- print(f" 🖱️ 步骤3: 使用坐标点击...")
255
-
256
- # 直接使用坐标点击(不通过MCP工具,避免重复定位)
257
- if self.mobile_client:
258
- try:
259
- self.mobile_client.u2.click(x, y)
260
- result['success'] = True
261
- print(f" ✅ 坐标点击成功")
262
- except Exception as e:
263
- result['error'] = f"坐标点击失败: {e}"
264
- else:
265
- result['error'] = "mobile_client不可用,无法执行坐标点击"
266
- else:
267
- result['error'] = "无法定位元素(XML分析和视觉识别都失败)"
268
-
269
- elif action == 'input':
270
- element = step.get('element')
271
- text = step.get('text')
272
- print(f"⌨️ 输入: {element} = {text}")
273
-
274
- # 获取XML并分析
275
- xml_result = await self._get_xml_snapshot()
276
- locate_result = await self._cursor_ai_analyze_xml(xml_result, element)
277
-
278
- if locate_result and locate_result.get('found'):
279
- element_ref = locate_result.get('ref')
280
- result['element_ref'] = element_ref
281
- result['method'] = locate_result.get('method', 'xml_analysis')
282
-
283
- if 'mobile_input' in self.mcp_tools:
284
- await self.mcp_tools['mobile_input'](element_desc=element, text=text)
285
- result['success'] = True
286
- else:
287
- raise ValueError("mobile_input工具不可用")
288
- else:
289
- result['error'] = "无法定位输入框"
290
-
291
- elif action == 'swipe':
292
- direction = step.get('direction')
293
- print(f"👆 滑动: {direction}")
294
-
295
- if 'mobile_swipe' in self.mcp_tools:
296
- swipe_result = await self.mcp_tools['mobile_swipe'](direction=direction)
297
- if swipe_result and swipe_result.get('success'):
298
- result['success'] = True
299
- else:
300
- result['error'] = swipe_result.get('error', '滑动失败')
301
- else:
302
- raise ValueError("mobile_swipe工具不可用")
303
-
304
- elif action == 'wait':
305
- seconds = step.get('seconds', 1)
306
- print(f"⏳ 等待: {seconds}秒")
307
- await asyncio.sleep(seconds)
308
- result['success'] = True
309
-
310
- elif action == 'assert_text':
311
- text = step.get('text')
312
- print(f"✅ 断言: 检查文本 '{text}'")
313
-
314
- if 'mobile_assert_text' in self.mcp_tools:
315
- assert_result = await self.mcp_tools['mobile_assert_text'](text=text)
316
- if assert_result and assert_result.get('found'):
317
- result['success'] = True
318
- else:
319
- result['error'] = f"未找到文本: {text}"
320
- else:
321
- raise ValueError("mobile_assert_text工具不可用")
322
-
323
- except Exception as e:
324
- result['error'] = str(e)
325
- print(f" ❌ 执行失败: {e}")
326
- import traceback
327
- traceback.print_exc()
328
-
329
- # 记录执行日志
330
- self.execution_log.append(result)
331
-
332
- if result['success']:
333
- print(f" ✅ 步骤执行成功")
334
- else:
335
- print(f" ❌ 步骤执行失败: {result.get('error')}")
336
-
337
- return result
338
-
339
- async def _get_xml_snapshot(self) -> Dict:
340
- """获取XML快照"""
341
- if 'mobile_snapshot' in self.mcp_tools:
342
- snapshot_result = await self.mcp_tools['mobile_snapshot']()
343
- if snapshot_result and snapshot_result.get('success'):
344
- return {
345
- 'xml': snapshot_result.get('snapshot', ''),
346
- 'success': True
347
- }
348
- return {'success': False, 'xml': ''}
349
-
350
- async def _cursor_ai_analyze_xml(self, xml_result: Dict, element_desc: str) -> Optional[Dict]:
351
- """
352
- Cursor AI分析XML定位元素
353
-
354
- 使用现有的MobileSmartLocator进行定位
355
- """
356
- if not xml_result.get('success'):
357
- return None
358
-
359
- # 🎯 使用现有的locator进行定位
360
- # 注意:这里需要传入locator实例
361
- if hasattr(self, 'locator') and self.locator:
362
- try:
363
- result = await self.locator.locate(element_desc)
364
- if result:
365
- return {
366
- 'found': True,
367
- 'ref': result.get('ref', ''),
368
- 'method': result.get('method', 'xml_analysis'),
369
- 'confidence': result.get('confidence', 80)
370
- }
371
- except Exception as e:
372
- print(f" ⚠️ XML分析异常: {e}")
373
-
374
- return None
375
-
376
- async def _cursor_ai_vision_recognize(self, element_desc: str) -> Optional[Dict]:
377
- """
378
- Cursor AI视觉识别
379
-
380
- 使用现有的视觉识别功能
381
- """
382
- try:
383
- from mobile_mcp.core.locator.cursor_vision_helper import CursorVisionHelper
384
-
385
- if hasattr(self, 'mobile_client') and self.mobile_client:
386
- cursor_helper = CursorVisionHelper(self.mobile_client)
387
- script_path = getattr(self, 'script_path', None)
388
- result = await cursor_helper.analyze_with_cursor(
389
- element_desc,
390
- script_path=script_path,
391
- auto_analyze=True
392
- )
393
-
394
- if result and result.get('status') == 'completed':
395
- coord = result.get('coordinate')
396
- if coord:
397
- return {
398
- 'coordinate': coord,
399
- 'screenshot_path': result.get('screenshot_path'),
400
- 'confidence': coord.get('confidence', 80)
401
- }
402
- except Exception as e:
403
- print(f" ⚠️ 视觉识别异常: {e}")
404
- import traceback
405
- traceback.print_exc()
406
-
407
- return None
408
-
409
- async def execute(self, nl_text: str) -> Dict:
410
- """
411
- 执行自然语言测试
412
-
413
- Args:
414
- nl_text: 自然语言测试描述
415
-
416
- Returns:
417
- 执行结果
418
- """
419
- print("=" * 60)
420
- print("🚀 自然语言测试执行器")
421
- print("=" * 60)
422
- print(f"\n📝 输入: {nl_text}\n")
423
-
424
- # 解析自然语言
425
- steps = self.parse_natural_language(nl_text)
426
- print(f"✅ 解析完成,共 {len(steps)} 个步骤\n")
427
-
428
- # 执行步骤
429
- success_count = 0
430
- fail_count = 0
431
-
432
- for i, step in enumerate(steps):
433
- result = await self.execute_step(step, i)
434
-
435
- if result['success']:
436
- success_count += 1
437
- else:
438
- fail_count += 1
439
- # 可以选择是否继续执行
440
- # break
441
-
442
- # 生成Python模板
443
- python_code = self.generate_python_template()
444
-
445
- return {
446
- 'total_steps': len(steps),
447
- 'success_count': success_count,
448
- 'fail_count': fail_count,
449
- 'execution_log': self.execution_log,
450
- 'python_template': python_code
451
- }
452
-
453
- def generate_python_template(self) -> str:
454
- """
455
- 生成Python测试模板
456
-
457
- 基于执行日志生成可复用的测试代码
458
- """
459
- lines = [
460
- "#!/usr/bin/env python3",
461
- "# -*- coding: utf-8 -*-",
462
- '"""',
463
- "自动生成的测试用例",
464
- f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
465
- '"""',
466
- "import asyncio",
467
- "import sys",
468
- "from pathlib import Path",
469
- "",
470
- "sys.path.insert(0, str(Path(__file__).parent.parent.parent))",
471
- "from mobile_mcp.core.mobile_client import MobileClient",
472
- "from mobile_mcp.core.locator.mobile_smart_locator import MobileSmartLocator",
473
- "",
474
- "",
475
- "async def main():",
476
- " client = MobileClient()",
477
- " locator = MobileSmartLocator(client)",
478
- "",
479
- ]
480
-
481
- # 添加步骤
482
- for i, log in enumerate(self.execution_log):
483
- action = log['action']
484
- original_text = log.get('original_text', '')
485
-
486
- if action == 'launch_app':
487
- package = log.get('package', '')
488
- lines.append(f" # 步骤 {i+1}: {original_text}")
489
- lines.append(f" await client.launch_app('{package}', wait_time=3)")
490
- lines.append(f" await asyncio.sleep(1)")
491
- lines.append("")
492
-
493
- elif action == 'click':
494
- element = log.get('element', '')
495
- method = log.get('method', '')
496
- element_ref = log.get('element_ref')
497
- coordinate = log.get('coordinate')
498
-
499
- lines.append(f" # 步骤 {i+1}: {original_text}")
500
- lines.append(f" # 定位方法: {method}")
501
-
502
- if coordinate:
503
- x, y = coordinate['x'], coordinate['y']
504
- lines.append(f" # Cursor AI坐标: ({x}, {y})")
505
- lines.append(f" client.u2.click({x}, {y})")
506
- elif element_ref:
507
- lines.append(f" result = await locator.locate('{element}')")
508
- lines.append(f" if result:")
509
- lines.append(f" await client.click('{element}', ref=result['ref'])")
510
- else:
511
- lines.append(f" result = await locator.locate('{element}')")
512
- lines.append(f" if result:")
513
- lines.append(f" await client.click('{element}', ref=result['ref'])")
514
-
515
- lines.append(f" await asyncio.sleep(0.5)")
516
- lines.append("")
517
-
518
- elif action == 'input':
519
- element = log.get('element', '')
520
- text = log.get('text', '')
521
- lines.append(f" # 步骤 {i+1}: {original_text}")
522
- lines.append(f" result = await locator.locate('{element}')")
523
- lines.append(f" if result:")
524
- lines.append(f" await client.type_text('{element}', '{text}', ref=result['ref'])")
525
- lines.append("")
526
-
527
- elif action == 'swipe':
528
- direction = log.get('direction', '')
529
- lines.append(f" # 步骤 {i+1}: {original_text}")
530
- lines.append(f" await client.swipe('{direction}')")
531
- lines.append("")
532
-
533
- elif action == 'wait':
534
- seconds = log.get('seconds', 1)
535
- lines.append(f" # 步骤 {i+1}: {original_text}")
536
- lines.append(f" await asyncio.sleep({seconds})")
537
- lines.append("")
538
-
539
- elif action == 'assert_text':
540
- text = log.get('text', '')
541
- lines.append(f" # 步骤 {i+1}: {original_text}")
542
- lines.append(f" snapshot = await client.snapshot()")
543
- lines.append(f" assert '{text}' in snapshot, f\"未找到文本: {text}\"")
544
- lines.append("")
545
-
546
- lines.extend([
547
- " client.device_manager.disconnect()",
548
- "",
549
- "",
550
- "if __name__ == \"__main__\":",
551
- " asyncio.run(main())",
552
- ])
553
-
554
- return '\n'.join(lines)
555
-
556
-
557
- async def main():
558
- """测试主函数"""
559
- # 这里需要传入MCP工具
560
- # 实际使用时,应该从MCP Server获取工具
561
-
562
- runner = NLTestRunner()
563
-
564
- # 测试自然语言
565
- nl_text = """
566
- 启动应用com.im30.way,点击底部第四个图标,点击设置,点击语言,点击English,点击保存
567
- """
568
-
569
- result = await runner.execute(nl_text)
570
-
571
- print("\n" + "=" * 60)
572
- print("📊 执行总结")
573
- print("=" * 60)
574
- print(f"总步骤数: {result['total_steps']}")
575
- print(f"成功: {result['success_count']}")
576
- print(f"失败: {result['fail_count']}")
577
- print("\n" + "=" * 60)
578
- print("📝 生成的Python模板:")
579
- print("=" * 60)
580
- print(result['python_template'])
581
-
582
-
583
- if __name__ == "__main__":
584
- asyncio.run(main())
585
-