yuehua-ziniao-webdriver 0.1.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.
@@ -0,0 +1,237 @@
1
+ """异常定义模块
2
+
3
+ 定义所有自定义异常类,提供清晰的错误层次结构。
4
+ """
5
+
6
+ from typing import Optional, Any
7
+
8
+
9
+ class ZiniaoError(Exception):
10
+ """紫鸟浏览器 SDK 基础异常类
11
+
12
+ 所有自定义异常的基类。
13
+ """
14
+
15
+ def __init__(self, message: str, details: Optional[Any] = None) -> None:
16
+ """初始化异常
17
+
18
+ Args:
19
+ message: 错误消息
20
+ details: 额外的错误详情(可选)
21
+ """
22
+ self.message = message
23
+ self.details = details
24
+ super().__init__(message)
25
+
26
+ def __str__(self) -> str:
27
+ if self.details:
28
+ return f"{self.message} (详情: {self.details})"
29
+ return self.message
30
+
31
+
32
+ class ConfigurationError(ZiniaoError):
33
+ """配置错误
34
+
35
+ 当配置参数无效或缺失时抛出。
36
+ """
37
+ pass
38
+
39
+
40
+ class AuthenticationError(ZiniaoError):
41
+ """认证失败错误
42
+
43
+ 当登录紫鸟客户端失败时抛出。
44
+ """
45
+ pass
46
+
47
+
48
+ class ClientNotStartedError(ZiniaoError):
49
+ """客户端未启动错误
50
+
51
+ 当尝试操作未启动的客户端时抛出。
52
+ """
53
+
54
+ def __init__(self, message: str = "紫鸟客户端尚未启动,请先调用 start() 方法") -> None:
55
+ super().__init__(message)
56
+
57
+
58
+ class BrowserStartError(ZiniaoError):
59
+ """浏览器启动失败错误
60
+
61
+ 当启动紫鸟客户端进程失败时抛出。
62
+ """
63
+ pass
64
+
65
+
66
+ class ProcessError(ZiniaoError):
67
+ """进程操作错误
68
+
69
+ 当进程管理操作失败时抛出(如关闭进程失败)。
70
+ """
71
+ pass
72
+
73
+
74
+ class CommunicationError(ZiniaoError):
75
+ """通信错误
76
+
77
+ 当与紫鸟客户端 HTTP 通信失败时抛出。
78
+ """
79
+ pass
80
+
81
+
82
+ class TimeoutError(ZiniaoError):
83
+ """超时错误
84
+
85
+ 当操作超时时抛出。
86
+ """
87
+ pass
88
+
89
+
90
+ class StoreError(ZiniaoError):
91
+ """店铺操作基础错误
92
+
93
+ 所有店铺相关错误的基类。
94
+ """
95
+ pass
96
+
97
+
98
+ class StoreNotFoundError(StoreError):
99
+ """店铺未找到错误
100
+
101
+ 当按名称或 ID 查找店铺但未找到时抛出。
102
+ """
103
+
104
+ def __init__(
105
+ self,
106
+ store_identifier: str,
107
+ search_type: str = "名称"
108
+ ) -> None:
109
+ """初始化错误
110
+
111
+ Args:
112
+ store_identifier: 店铺标识(名称或 ID)
113
+ search_type: 搜索类型("名称" 或 "ID")
114
+ """
115
+ message = f"未找到匹配的店铺:{search_type}='{store_identifier}'"
116
+ super().__init__(message, {"identifier": store_identifier, "type": search_type})
117
+ self.store_identifier = store_identifier
118
+ self.search_type = search_type
119
+
120
+
121
+ class MultipleStoresFoundError(StoreError):
122
+ """找到多个店铺错误
123
+
124
+ 当按名称搜索店铺但找到多个匹配结果时抛出。
125
+ """
126
+
127
+ def __init__(
128
+ self,
129
+ store_name: str,
130
+ count: int,
131
+ store_names: Optional[list] = None
132
+ ) -> None:
133
+ """初始化错误
134
+
135
+ Args:
136
+ store_name: 搜索的店铺名称
137
+ count: 找到的店铺数量
138
+ store_names: 找到的店铺名称列表(可选)
139
+ """
140
+ if store_names:
141
+ message = (
142
+ f"找到 {count} 个匹配的店铺:'{store_name}'。"
143
+ f"匹配的店铺:{', '.join(store_names)}"
144
+ )
145
+ else:
146
+ message = f"找到 {count} 个匹配的店铺:'{store_name}'"
147
+
148
+ super().__init__(
149
+ message,
150
+ {"search_name": store_name, "count": count, "stores": store_names}
151
+ )
152
+ self.store_name = store_name
153
+ self.count = count
154
+ self.store_names = store_names
155
+
156
+
157
+ class StoreOperationError(StoreError):
158
+ """店铺操作失败错误
159
+
160
+ 当打开、关闭店铺等操作失败时抛出。
161
+ """
162
+
163
+ def __init__(
164
+ self,
165
+ operation: str,
166
+ store_id: str,
167
+ status_code: Optional[int] = None,
168
+ message: Optional[str] = None
169
+ ) -> None:
170
+ """初始化错误
171
+
172
+ Args:
173
+ operation: 操作类型(如 "打开"、"关闭")
174
+ store_id: 店铺 ID
175
+ status_code: 状态码(可选)
176
+ message: 错误消息(可选)
177
+ """
178
+ error_msg = f"{operation}店铺失败:{store_id}"
179
+ if status_code is not None:
180
+ error_msg += f",状态码:{status_code}"
181
+ if message:
182
+ error_msg += f",消息:{message}"
183
+
184
+ super().__init__(
185
+ error_msg,
186
+ {
187
+ "operation": operation,
188
+ "store_id": store_id,
189
+ "status_code": status_code,
190
+ "message": message
191
+ }
192
+ )
193
+ self.operation = operation
194
+ self.store_id = store_id
195
+ self.status_code = status_code
196
+
197
+
198
+ class IPCheckError(ZiniaoError):
199
+ """IP 检测错误
200
+
201
+ 当 IP 检测失败或超时时抛出。
202
+ """
203
+ pass
204
+
205
+
206
+ class CoreUpdateError(ZiniaoError):
207
+ """内核更新错误
208
+
209
+ 当更新浏览器内核失败时抛出。
210
+ """
211
+ pass
212
+
213
+
214
+ class UnsupportedVersionError(ZiniaoError):
215
+ """不支持的版本错误
216
+
217
+ 当客户端版本不支持某个功能时抛出。
218
+ """
219
+
220
+ def __init__(
221
+ self,
222
+ feature: str,
223
+ required_version: Optional[str] = None
224
+ ) -> None:
225
+ """初始化错误
226
+
227
+ Args:
228
+ feature: 不支持的功能名称
229
+ required_version: 需要的版本(可选)
230
+ """
231
+ message = f"当前客户端版本不支持功能:{feature}"
232
+ if required_version:
233
+ message += f",请升级到 {required_version} 或更高版本"
234
+
235
+ super().__init__(message, {"feature": feature, "required_version": required_version})
236
+ self.feature = feature
237
+ self.required_version = required_version
@@ -0,0 +1,214 @@
1
+ """HTTP 通信客户端模块
2
+
3
+ 封装与紫鸟客户端的 HTTP 通信,提供重试机制和错误处理。
4
+ """
5
+
6
+ import json
7
+ import logging
8
+ import time
9
+ from typing import Dict, Any, Optional
10
+
11
+ import requests
12
+
13
+ from .types import HttpRequestData, HttpResponse
14
+ from .exceptions import (
15
+ CommunicationError,
16
+ TimeoutError as ZiniaoTimeoutError,
17
+ AuthenticationError
18
+ )
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class HttpClient:
24
+ """HTTP 通信客户端
25
+
26
+ 负责与紫鸟客户端进行 HTTP 通信。
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ port: int,
32
+ timeout: int = 120,
33
+ max_retries: int = 3,
34
+ retry_delay: float = 2.0
35
+ ) -> None:
36
+ """初始化 HTTP 客户端
37
+
38
+ Args:
39
+ port: 通信端口
40
+ timeout: 请求超时时间(秒)
41
+ max_retries: 最大重试次数
42
+ retry_delay: 重试延迟时间(秒)
43
+ """
44
+ self.port = port
45
+ self.timeout = timeout
46
+ self.max_retries = max_retries
47
+ self.retry_delay = retry_delay
48
+ self.base_url = f"http://127.0.0.1:{port}"
49
+
50
+ logger.debug(
51
+ f"初始化 HTTP 客户端:port={port}, timeout={timeout}, "
52
+ f"max_retries={max_retries}, retry_delay={retry_delay}"
53
+ )
54
+
55
+ def send_request(
56
+ self,
57
+ data: Dict[str, Any],
58
+ retry_on_none: bool = False
59
+ ) -> Optional[HttpResponse]:
60
+ """发送 HTTP 请求
61
+
62
+ Args:
63
+ data: 请求数据字典
64
+ retry_on_none: 当返回 None 时是否重试,默认 False
65
+
66
+ Returns:
67
+ Optional[HttpResponse]: 响应数据,失败返回 None
68
+
69
+ Raises:
70
+ CommunicationError: 通信失败
71
+ ZiniaoTimeoutError: 请求超时
72
+ AuthenticationError: 认证失败
73
+ """
74
+ action = data.get("action", "unknown")
75
+ request_id = data.get("requestId", "unknown")
76
+
77
+ logger.debug(f"发送请求:action={action}, requestId={request_id}")
78
+
79
+ # 重试逻辑
80
+ last_exception: Optional[Exception] = None
81
+
82
+ for attempt in range(self.max_retries + 1):
83
+ try:
84
+ # 发送 POST 请求
85
+ response = requests.post(
86
+ self.base_url,
87
+ data=json.dumps(data).encode('utf-8'),
88
+ timeout=self.timeout
89
+ )
90
+
91
+ # 解析响应
92
+ result = json.loads(response.text)
93
+
94
+ # 检查是否需要重试(返回 None)
95
+ if result is None and retry_on_none and attempt < self.max_retries:
96
+ logger.warning(
97
+ f"请求返回 None,{self.retry_delay} 秒后重试 "
98
+ f"(尝试 {attempt + 1}/{self.max_retries})"
99
+ )
100
+ time.sleep(self.retry_delay)
101
+ continue
102
+
103
+ # 检查状态码
104
+ status_code = result.get("statusCode") if result else None
105
+
106
+ # 认证失败
107
+ if status_code == -10003:
108
+ error_msg = f"认证失败:{json.dumps(result, ensure_ascii=False)}"
109
+ logger.error(error_msg)
110
+ raise AuthenticationError(error_msg, result)
111
+
112
+ logger.debug(
113
+ f"请求成功:action={action}, statusCode={status_code}"
114
+ )
115
+
116
+ return result
117
+
118
+ except requests.Timeout as e:
119
+ last_exception = e
120
+ logger.warning(
121
+ f"请求超时:action={action}, attempt={attempt + 1}/{self.max_retries + 1}"
122
+ )
123
+
124
+ if attempt < self.max_retries:
125
+ logger.info(f"等待 {self.retry_delay} 秒后重试...")
126
+ time.sleep(self.retry_delay)
127
+ continue
128
+
129
+ # 最后一次尝试失败
130
+ error_msg = f"请求超时(已重试 {self.max_retries} 次):{e}"
131
+ logger.error(error_msg)
132
+ raise ZiniaoTimeoutError(error_msg, {"action": action, "error": str(e)})
133
+
134
+ except requests.ConnectionError as e:
135
+ last_exception = e
136
+ logger.warning(
137
+ f"连接失败:action={action}, attempt={attempt + 1}/{self.max_retries + 1}, "
138
+ f"error={e}"
139
+ )
140
+
141
+ if attempt < self.max_retries:
142
+ logger.info(f"等待 {self.retry_delay} 秒后重试...")
143
+ time.sleep(self.retry_delay)
144
+ continue
145
+
146
+ # 最后一次尝试失败
147
+ error_msg = (
148
+ f"无法连接到紫鸟客户端(已重试 {self.max_retries} 次),"
149
+ f"请确认客户端已启动且端口 {self.port} 可访问"
150
+ )
151
+ logger.error(error_msg)
152
+ raise CommunicationError(error_msg, {"port": self.port, "error": str(e)})
153
+
154
+ except json.JSONDecodeError as e:
155
+ last_exception = e
156
+ error_msg = f"响应 JSON 解析失败:{e}"
157
+ logger.error(error_msg)
158
+ raise CommunicationError(error_msg, {"error": str(e)})
159
+
160
+ except Exception as e:
161
+ last_exception = e
162
+ logger.error(f"未知错误:action={action}, error={e}")
163
+
164
+ if attempt < self.max_retries:
165
+ logger.info(f"等待 {self.retry_delay} 秒后重试...")
166
+ time.sleep(self.retry_delay)
167
+ continue
168
+
169
+ # 最后一次尝试失败
170
+ error_msg = f"通信失败(已重试 {self.max_retries} 次):{e}"
171
+ logger.error(error_msg)
172
+ raise CommunicationError(error_msg, {"action": action, "error": str(e)})
173
+
174
+ # 不应该到达这里,但为了类型检查
175
+ if last_exception:
176
+ raise CommunicationError(
177
+ f"请求失败:{last_exception}",
178
+ {"error": str(last_exception)}
179
+ )
180
+
181
+ return None
182
+
183
+ def update_port(self, new_port: int) -> None:
184
+ """更新通信端口
185
+
186
+ Args:
187
+ new_port: 新的端口号
188
+ """
189
+ self.port = new_port
190
+ self.base_url = f"http://127.0.0.1:{new_port}"
191
+ logger.info(f"更新通信端口:{new_port}")
192
+
193
+ def test_connection(self) -> bool:
194
+ """测试连接是否可用
195
+
196
+ Returns:
197
+ bool: 连接正常返回 True,否则返回 False
198
+ """
199
+ try:
200
+ response = requests.get(
201
+ self.base_url,
202
+ timeout=5
203
+ )
204
+ logger.debug(f"连接测试成功:port={self.port}")
205
+ return True
206
+ except Exception as e:
207
+ logger.debug(f"连接测试失败:port={self.port}, error={e}")
208
+ return False
209
+
210
+ def __repr__(self) -> str:
211
+ return (
212
+ f"HttpClient(port={self.port}, timeout={self.timeout}, "
213
+ f"max_retries={self.max_retries})"
214
+ )