1- #!/usr/bin/env python3
1+ #!/usr/bin/env python
22# -*- coding: utf-8 -*-
3+ # @time : 2024/8/21 16:32
4+ # @author : timger/yishenggudou
5+ import asyncio
6+ import logging
7+ import sys
8+ from pathlib import Path
9+ import yaml
10+ import typer
11+ from typing import Optional
12+ from py_cdp_reactive_flow_bot .engine import ReactiveAutomationFramework
13+
14+ # 配置日志
15+ logging .basicConfig (
16+ level = logging .INFO ,
17+ format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ,
18+ handlers = [
19+ logging .StreamHandler (sys .stdout )
20+ ]
21+ )
22+ logger = logging .getLogger (__name__ )
23+
24+ # 创建Typer应用实例
25+ app = typer .Typer (
26+ name = "py-cdp-reactive-flow-bot" ,
27+ help = "基于CDP的响应式自动化工作流机器人" ,
28+ add_completion = False
29+ )
30+
31+ # 主命令,支持cdp endpoint和playbook参数
32+ @app .command ("run" )
33+ def run (
34+ playbook : Path = typer .Argument (
35+ ..., # 必填参数
36+ help = "YAML格式的playbook脚本文件路径" ,
37+ exists = True ,
38+ readable = True ,
39+ resolve_path = True
40+ ),
41+ cdp_endpoint : Optional [str ] = typer .Option (
42+ None ,
43+ "--cdp-endpoint" ,
44+ "-c" ,
45+ help = "CDP服务端点地址,例如: http://localhost:9222"
46+ ),
47+ debug : bool = typer .Option (
48+ False ,
49+ "--debug" ,
50+ help = "启用调试日志模式"
51+ )
52+ ):
53+ """运行指定的playbook自动化脚本"""
54+ # 启用调试模式时设置日志级别为DEBUG
55+ if debug :
56+ logger .setLevel (logging .DEBUG )
57+ logging .getLogger ("py_cdp_reactive_flow_bot" ).setLevel (logging .DEBUG )
58+
59+ logger .info (f"开始执行playbook: { playbook } " )
60+ logger .debug (f"CDP端点配置: { cdp_endpoint or '使用默认Playwright浏览器' } " )
61+
62+ try :
63+ # 读取并解析YAML playbook文件
64+ playbook_config = load_playbook (playbook )
65+
66+ # 运行主程序逻辑
67+ asyncio .run (run_playbook (playbook_config , cdp_endpoint ))
68+
69+ logger .info ("Playbook执行完成" )
70+ return 0
71+ except Exception as e :
72+ logger .error (f"执行失败: { str (e )} " )
73+ return 1
74+
75+ def load_playbook (playbook_path : Path ) -> dict :
76+ """加载并解析YAML格式的playbook文件"""
77+ logger .debug (f"加载playbook文件: { playbook_path } " )
78+ try :
79+ with open (playbook_path , 'r' , encoding = 'utf-8' ) as f :
80+ return yaml .safe_load (f )
81+ except yaml .YAMLError as e :
82+ logger .error (f"解析YAML文件失败: { e } " )
83+ raise ValueError (f"无效的YAML文件格式: { playbook_path } " ) from e
84+ except Exception as e :
85+ logger .error (f"读取playbook文件失败: { e } " )
86+ raise IOError (f"无法读取文件: { playbook_path } " ) from e
87+
88+ async def run_playbook (playbook_config : dict , cdp_endpoint : Optional [str ]):
89+ """运行playbook自动化脚本
90+
91+ Args:
92+ playbook_config: 解析后的playbook配置
93+ cdp_endpoint: CDP服务端点地址,如果为None则启动新的浏览器实例
94+ """
95+ # 初始化框架
96+ framework = ReactiveAutomationFramework ()
97+
98+ # 创建事件用于等待执行完成
99+ completion_event = asyncio .Event ()
100+ error_occurred = None
101+
102+ try :
103+ # 初始化浏览器环境,传入cdp_endpoint参数
104+ await framework .initialize (cdp_endpoint = cdp_endpoint )
105+
106+ # 创建并执行DSL执行器
107+ logger .debug ("创建DSL执行器" )
108+ executor = framework .create_dsl_executor (playbook_config )
109+
110+ # 订阅执行状态更新
111+ def on_state_update (update ):
112+ step_index = update ["step_index" ]
113+ state = update ["state" ]
114+ result = update .get ("result" )
115+ logger .info (f"步骤 #{ step_index } 状态: { state .name } " )
116+ if result :
117+ logger .debug (f" 结果: { result } " )
118+
119+ state_subscription = framework .state_stream .subscribe (on_state_update )
120+
121+ # 订阅执行器,处理结果和错误
122+ def on_next (result ):
123+ logger .debug (f"执行器产生结果: { result } " )
124+
125+ def on_error (error ):
126+ nonlocal error_occurred
127+ logger .error (f"执行器出错: { error } " )
128+ error_occurred = error
129+ completion_event .set ()
130+
131+ def on_completed ():
132+ logger .info ("执行器完成所有任务" )
133+ completion_event .set ()
134+
135+ # 订阅执行流
136+ executor_subscription = executor .subscribe (
137+ on_next = on_next ,
138+ on_error = on_error ,
139+ on_completed = on_completed
140+ )
141+
142+ # 等待执行完成或超时
143+ logger .info ("开始执行playbook..." )
144+
145+ # 从playbook配置中获取超时时间,如果没有设置则使用默认值300秒
146+ timeout = playbook_config .get ("timeout" , 300 )
147+ logger .debug (f"设置执行超时时间: { timeout } 秒" )
148+
149+ try :
150+ # 等待执行完成,带超时保护
151+ await asyncio .wait_for (completion_event .wait (), timeout = timeout )
152+
153+ # 检查是否有错误发生
154+ if error_occurred :
155+ raise error_occurred
156+
157+ except asyncio .TimeoutError :
158+ logger .error (f"执行超时: 超过{ timeout } 秒未完成" )
159+ raise TimeoutError (f"Playbook执行超时,超过{ timeout } 秒" )
160+
161+ logger .info ("Playbook执行成功完成" )
162+
163+ finally :
164+ # 确保清理资源
165+ if 'state_subscription' in locals ():
166+ state_subscription .dispose ()
167+ if 'executor_subscription' in locals ():
168+ executor_subscription .dispose ()
169+ # 关闭框架资源
170+ await framework .close ()
171+
172+ @app .command ("version" )
173+ def version ():
174+ """显示当前版本信息"""
175+ from py_cdp_reactive_flow_bot import __version__
176+ typer .echo (f"py-cdp-reactive-flow-bot v{ __version__ } " )
177+ return 0
178+
179+ if __name__ == "__main__" :
180+ sys .exit (app ())
0 commit comments