devhelmkit 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.
Files changed (58) hide show
  1. devhelmkit/__init__.py +44 -0
  2. devhelmkit/android/__init__.py +7 -0
  3. devhelmkit/android/driver.py +18 -0
  4. devhelmkit/assets/so/arm64-v8a/agent_v1.so +0 -0
  5. devhelmkit/assets/so/arm64-v8a/agent_v2.so +0 -0
  6. devhelmkit/assets/so/x86_64/agent.so +0 -0
  7. devhelmkit/core/__init__.py +3 -0
  8. devhelmkit/core/base_component.py +214 -0
  9. devhelmkit/core/base_driver.py +414 -0
  10. devhelmkit/core/base_window.py +25 -0
  11. devhelmkit/core/selector_spec.py +102 -0
  12. devhelmkit/entry.py +90 -0
  13. devhelmkit/exceptions.py +56 -0
  14. devhelmkit/harmony/__init__.py +3 -0
  15. devhelmkit/harmony/agent/__init__.py +7 -0
  16. devhelmkit/harmony/agent/so_manager.py +300 -0
  17. devhelmkit/harmony/config.py +124 -0
  18. devhelmkit/harmony/device/__init__.py +6 -0
  19. devhelmkit/harmony/device/hdc.py +430 -0
  20. devhelmkit/harmony/driver.py +1416 -0
  21. devhelmkit/harmony/finder/__init__.py +12 -0
  22. devhelmkit/harmony/finder/component_finder.py +336 -0
  23. devhelmkit/harmony/finder/popup_handler.py +81 -0
  24. devhelmkit/harmony/finder/selector_adapter.py +101 -0
  25. devhelmkit/harmony/finder/xpath_query.py +110 -0
  26. devhelmkit/harmony/rpc/__init__.py +12 -0
  27. devhelmkit/harmony/rpc/client.py +126 -0
  28. devhelmkit/harmony/rpc/proxy_v2.py +106 -0
  29. devhelmkit/harmony/rpc/remote_object.py +48 -0
  30. devhelmkit/harmony/uiobject.py +246 -0
  31. devhelmkit/harmony/uiwindow.py +43 -0
  32. devhelmkit/harmony/webview/__init__.py +17 -0
  33. devhelmkit/harmony/webview/chromedriver_manager.py +251 -0
  34. devhelmkit/harmony/webview/devtools_finder.py +131 -0
  35. devhelmkit/harmony/webview/webview_driver.py +326 -0
  36. devhelmkit/model/__init__.py +3 -0
  37. devhelmkit/model/action.py +147 -0
  38. devhelmkit/model/app_state.py +50 -0
  39. devhelmkit/model/constants.py +22 -0
  40. devhelmkit/model/display.py +32 -0
  41. devhelmkit/model/format_string.py +24 -0
  42. devhelmkit/model/input.py +50 -0
  43. devhelmkit/model/json_base.py +42 -0
  44. devhelmkit/model/keys.py +375 -0
  45. devhelmkit/model/match_pattern.py +15 -0
  46. devhelmkit/model/page.py +13 -0
  47. devhelmkit/model/params.py +42 -0
  48. devhelmkit/model/rect.py +58 -0
  49. devhelmkit/model/runnable.py +18 -0
  50. devhelmkit/utils/__init__.py +3 -0
  51. devhelmkit/utils/logger.py +72 -0
  52. devhelmkit/utils/retry.py +46 -0
  53. devhelmkit/utils/timeout.py +64 -0
  54. devhelmkit-0.1.0.dist-info/METADATA +411 -0
  55. devhelmkit-0.1.0.dist-info/RECORD +58 -0
  56. devhelmkit-0.1.0.dist-info/WHEEL +5 -0
  57. devhelmkit-0.1.0.dist-info/licenses/LICENSE +201 -0
  58. devhelmkit-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,375 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """KeyCode:OpenHarmony 按键码枚举。
4
+
5
+ 重要:使用 OpenHarmony 按键码体系(非 Android KeyEvent),数值从
6
+ 2000 起步。禁止套用 Android 数值。
7
+ 采用 IntEnum 以支持数值比较。
8
+ """
9
+ from enum import IntEnum
10
+
11
+
12
+ class KeyCode(IntEnum):
13
+ """OpenHarmony 按键码。"""
14
+
15
+ # 功能与系统按键
16
+ FN = 0
17
+ UNKNOWN = -1
18
+ HOME = 1
19
+ BACK = 2
20
+ POWER = 18
21
+ CAMERA = 19
22
+ VOLUME_MUTE = 22
23
+ MUTE = 23
24
+ SLEEP = 2600
25
+ WAKE_UP = 2802
26
+ SCREENLOCK = 2809
27
+ VIRTUAL_MULTITASK = 2210
28
+
29
+ # 音量按键
30
+ VOLUME_UP = 16
31
+ VOLUME_DOWN = 17
32
+
33
+ # 亮度按键
34
+ BRIGHTNESS_UP = 40
35
+ BRIGHTNESS_DOWN = 41
36
+
37
+ # 多媒体按键
38
+ MEDIA_PLAY_PAUSE = 10
39
+ MEDIA_STOP = 11
40
+ MEDIA_NEXT = 12
41
+ MEDIA_PREVIOUS = 13
42
+ MEDIA_REWIND = 14
43
+ MEDIA_FAST_FORWARD = 15
44
+ MEDIA_PLAY = 2085
45
+ MEDIA_PAUSE = 2086
46
+ MEDIA_CLOSE = 2087
47
+ MEDIA_EJECT = 2088
48
+ MEDIA_RECORD = 2089
49
+
50
+ # 导航键
51
+ DPAD_UP = 2012
52
+ DPAD_DOWN = 2013
53
+ DPAD_LEFT = 2014
54
+ DPAD_RIGHT = 2015
55
+ DPAD_CENTER = 2016
56
+ PAGE_UP = 2068
57
+ PAGE_DOWN = 2069
58
+ MOVE_HOME = 2081
59
+ MOVE_END = 2082
60
+
61
+ # 数字键
62
+ NUM_0 = 2000
63
+ NUM_1 = 2001
64
+ NUM_2 = 2002
65
+ NUM_3 = 2003
66
+ NUM_4 = 2004
67
+ NUM_5 = 2005
68
+ NUM_6 = 2006
69
+ NUM_7 = 2007
70
+ NUM_8 = 2008
71
+ NUM_9 = 2009
72
+ STAR = 2010
73
+ POUND = 2011
74
+
75
+ # 字母键 A-Z
76
+ A = 2017
77
+ B = 2018
78
+ C = 2019
79
+ D = 2020
80
+ E = 2021
81
+ F = 2022
82
+ G = 2023
83
+ H = 2024
84
+ I = 2025
85
+ J = 2026
86
+ K = 2027
87
+ L = 2028
88
+ M = 2029
89
+ N = 2030
90
+ O = 2031
91
+ P = 2032
92
+ Q = 2033
93
+ R = 2034
94
+ S = 2035
95
+ T = 2036
96
+ U = 2037
97
+ V = 2038
98
+ W = 2039
99
+ X = 2040
100
+ Y = 2041
101
+ Z = 2042
102
+
103
+ # 标点与符号
104
+ COMMA = 2043
105
+ PERIOD = 2044
106
+ GRAVE = 2056
107
+ MINUS = 2057
108
+ EQUALS = 2058
109
+ LEFT_BRACKET = 2059
110
+ RIGHT_BRACKET = 2060
111
+ BACKSLASH = 2061
112
+ SEMICOLON = 2062
113
+ APOSTROPHE = 2063
114
+ SLASH = 2064
115
+ AT = 2065
116
+ PLUS = 2066
117
+
118
+ # 编辑与控制键
119
+ TAB = 2049
120
+ SPACE = 2050
121
+ SYM = 2051
122
+ EXPLORER = 2052
123
+ ENVELOPE = 2053
124
+ ENTER = 2054
125
+ DEL = 2055
126
+ ESCAPE = 2070
127
+ FORWARD_DEL = 2071
128
+ INSERT = 2083
129
+ FORWARD = 2084
130
+ MENU = 2067
131
+
132
+ # 修饰键
133
+ ALT_LEFT = 2045
134
+ ALT_RIGHT = 2046
135
+ SHIFT_LEFT = 2047
136
+ SHIFT_RIGHT = 2048
137
+ CTRL_LEFT = 2072
138
+ CTRL_RIGHT = 2073
139
+ CAPS_LOCK = 2074
140
+ SCROLL_LOCK = 2075
141
+ META_LEFT = 2076
142
+ META_RIGHT = 2077
143
+ FUNCTION = 2078
144
+ SYSRQ = 2079
145
+ BREAK = 2080
146
+
147
+ # 功能键 F1-F24
148
+ F1 = 2090
149
+ F2 = 2091
150
+ F3 = 2092
151
+ F4 = 2093
152
+ F5 = 2094
153
+ F6 = 2095
154
+ F7 = 2096
155
+ F8 = 2097
156
+ F9 = 2098
157
+ F10 = 2099
158
+ F11 = 2100
159
+ F12 = 2101
160
+ F13 = 2816
161
+ F14 = 2817
162
+ F15 = 2818
163
+ F16 = 2819
164
+ F17 = 2820
165
+ F18 = 2821
166
+ F19 = 2822
167
+ F20 = 2823
168
+ F21 = 2824
169
+ F22 = 2825
170
+ F23 = 2826
171
+ F24 = 2827
172
+
173
+ # 小键盘
174
+ NUM_LOCK = 2102
175
+ NUMPAD_0 = 2103
176
+ NUMPAD_1 = 2104
177
+ NUMPAD_2 = 2105
178
+ NUMPAD_3 = 2106
179
+ NUMPAD_4 = 2107
180
+ NUMPAD_5 = 2108
181
+ NUMPAD_6 = 2109
182
+ NUMPAD_7 = 2110
183
+ NUMPAD_8 = 2111
184
+ NUMPAD_9 = 2112
185
+ NUMPAD_DIVIDE = 2113
186
+ NUMPAD_MULTIPLY = 2114
187
+ NUMPAD_SUBTRACT = 2115
188
+ NUMPAD_ADD = 2116
189
+ NUMPAD_DOT = 2117
190
+ NUMPAD_COMMA = 2118
191
+ NUMPAD_ENTER = 2119
192
+ NUMPAD_EQUALS = 2120
193
+ NUMPAD_LEFT_PAREN = 2121
194
+ NUMPAD_RIGHT_PAREN = 2122
195
+ NUMPAD_PLUSMINUS = 2611
196
+
197
+ # 日文/韩文键
198
+ ZENKAKU_HANKAKU = 2601
199
+ ND = 2602
200
+ RO = 2603
201
+ KATAKANA = 2604
202
+ HIRAGANA = 2605
203
+ HENKAN = 2606
204
+ KATAKANA_HIRAGANA = 2607
205
+ MUHENKAN = 2608
206
+ LINEFEED = 2609
207
+ MACRO = 2610
208
+ SCALE = 2612
209
+ HANGUEL = 2613
210
+ HANJA = 2614
211
+ YEN = 2615
212
+
213
+ # 多媒体扩展键
214
+ STOP = 2616
215
+ AGAIN = 2617
216
+ PROPS = 2618
217
+ UNDO = 2619
218
+ COPY = 2620
219
+ OPEN = 2621
220
+ PASTE = 2622
221
+ FIND = 2623
222
+ CUT = 2624
223
+ HELP = 2625
224
+ CALC = 2626
225
+ FILE = 2627
226
+ BOOKMARKS = 2628
227
+ NEXT = 2629
228
+ PLAYPAUSE = 2630
229
+ PREVIOUS = 2631
230
+ STOPCD = 2632
231
+ CONFIG = 2634
232
+ REFRESH = 2635
233
+ EXIT = 2636
234
+ EDIT = 2637
235
+ SCROLLUP = 2638
236
+ SCROLLDOWN = 2639
237
+ NEW = 2640
238
+ REDO = 2641
239
+ CLOSE = 2642
240
+ PLAY = 2643
241
+ BASSBOOST = 2644
242
+ PRINT = 2645
243
+ CHAT = 2646
244
+ FINANCE = 2647
245
+ CANCEL = 2648
246
+ KBDILLUM_TOGGLE = 2649
247
+ KBDILLUM_DOWN = 2650
248
+ KBDILLUM_UP = 2651
249
+ SEND = 2652
250
+ REPLY = 2653
251
+ FORWARDMAIL = 2654
252
+ SAVE = 2655
253
+ DOCUMENTS = 2656
254
+ VIDEO_NEXT = 2657
255
+ VIDEO_PREV = 2658
256
+ BRIGHTNESS_CYCLE = 2659
257
+ BRIGHTNESS_ZERO = 2660
258
+ DISPLAY_OFF = 2661
259
+ BTN_MISC = 2662
260
+ GOTO = 2663
261
+ INFO = 2664
262
+ PROGRAM = 2665
263
+ PVR = 2666
264
+ SUBTITLE = 2667
265
+ FULL_SCREEN = 2668
266
+ KEYBOARD = 2669
267
+ ASPECT_RATIO = 2670
268
+ PC = 2671
269
+ TV = 2672
270
+ TV2 = 2673
271
+ VCR = 2674
272
+ VCR2 = 2675
273
+ SAT = 2676
274
+ CD = 2677
275
+ TAPE = 2678
276
+ TUNER = 2679
277
+ PLAYER = 2680
278
+ DVD = 2681
279
+ AUDIO = 2682
280
+ VIDEO = 2683
281
+ MEMO = 2684
282
+ CALENDAR = 2685
283
+ RED = 2686
284
+ GREEN = 2687
285
+ YELLOW = 2688
286
+ BLUE = 2689
287
+ CHANNELUP = 2690
288
+ CHANNELDOWN = 2691
289
+ LAST = 2692
290
+ RESTART = 2693
291
+ SLOW = 2694
292
+ SHUFFLE = 2695
293
+ VIDEOPHONE = 2696
294
+ GAMES = 2697
295
+ ZOOMIN = 2698
296
+ ZOOMOUT = 2699
297
+ ZOOMRESET = 2700
298
+ WORDPROCESSOR = 2701
299
+ EDITOR = 2702
300
+ SPREADSHEET = 2703
301
+ GRAPHICSEDITOR = 2704
302
+ PRESENTATION = 2705
303
+ DATABASE = 2706
304
+ NEWS = 2707
305
+ VOICEMAIL = 2708
306
+ ADDRESSBOOK = 2709
307
+ MESSENGER = 2710
308
+ BRIGHTNESS_TOGGLE = 2711
309
+ SPELLCHECK = 2712
310
+ COFFEE = 2713
311
+ MEDIA_REPEAT = 2714
312
+ IMAGES = 2715
313
+ BUTTONCONFIG = 2716
314
+ TASKMANAGER = 2717
315
+ JOURNAL = 2718
316
+ CONTROLPANEL = 2719
317
+ APPSELECT = 2720
318
+ SCREENSAVER = 2721
319
+ ASSISTANT = 2722
320
+ KBD_LAYOUT_NEXT = 2723
321
+ BRIGHTNESS_MIN = 2724
322
+ BRIGHTNESS_MAX = 2725
323
+ KBDINPUTASSIST_PREV = 2726
324
+ KBDINPUTASSIST_NEXT = 2727
325
+ KBDINPUTASSIST_PREVGROUP = 2728
326
+ KBDINPUTASSIST_NEXTGROUP = 2729
327
+ KBDINPUTASSIST_ACCEPT = 2730
328
+ KBDINPUTASSIST_CANCEL = 2731
329
+
330
+ # 其他系统键
331
+ FRONT = 2800
332
+ SETUP = 2801
333
+ SENDFILE = 2803
334
+ DELETEFILE = 2804
335
+ XFER = 2805
336
+ PROG1 = 2806
337
+ PROG2 = 2807
338
+ MSDOS = 2808
339
+ DIRECTION_ROTATE_DISPLAY = 2810
340
+ CYCLEWINDOWS = 2811
341
+ COMPUTER = 2812
342
+ EJECTCLOSECD = 2813
343
+ ISO = 2814
344
+ MOVE = 2815
345
+ PROG3 = 2828
346
+ PROG4 = 2829
347
+ DASHBOARD = 2830
348
+ SUSPEND = 2831
349
+ HP = 2832
350
+ SOUND = 2833
351
+ QUESTION = 2834
352
+ CONNECT = 2836
353
+ SPORT = 2837
354
+ SHOP = 2838
355
+ ALTERASE = 2839
356
+ SWITCHVIDEOMODE = 2841
357
+ BATTERY = 2842
358
+ BLUETOOTH = 2843
359
+ WLAN = 2844
360
+ UWB = 2845
361
+ WWAN_WIMAX = 2846
362
+ RFKILL = 2847
363
+
364
+ # 频道与游戏手柄按键
365
+ CHANNEL = 3001
366
+ BTN_0 = 3100
367
+ BTN_1 = 3101
368
+ BTN_2 = 3102
369
+ BTN_3 = 3103
370
+ BTN_4 = 3104
371
+ BTN_5 = 3105
372
+ BTN_6 = 3106
373
+ BTN_7 = 3107
374
+ BTN_8 = 3108
375
+ BTN_9 = 3109
@@ -0,0 +1,15 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """MatchPattern:匹配模式枚举。"""
4
+ from enum import IntEnum
5
+
6
+
7
+ class MatchPattern(IntEnum):
8
+ """匹配模式,用于控件文本/属性的匹配方式。"""
9
+
10
+ EQUALS = 0 # 精确匹配(默认)
11
+ CONTAINS = 1 # 包含
12
+ STARTS_WITH = 2 # 开头匹配
13
+ ENDS_WITH = 3 # 结尾匹配
14
+ REGEXP = 4 # 正则匹配
15
+ REGEXP_ICASE = 5 # 正则匹配(忽略大小写)
@@ -0,0 +1,13 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """页面信息类型。"""
4
+ from dataclasses import dataclass
5
+ from typing import Optional
6
+
7
+
8
+ @dataclass
9
+ class BasicPage:
10
+ """页面信息。"""
11
+ screenshot_path: Optional[str] = None
12
+ layout_path: Optional[str] = None
13
+ display_id: int = 0
@@ -0,0 +1,42 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """UI 操作参数类型:WindowFilter / UiParam / DeviceType / InputTextMode。"""
4
+ from dataclasses import dataclass
5
+ from enum import IntEnum
6
+ from typing import Optional
7
+
8
+
9
+ class InputTextMode(IntEnum):
10
+ """输入文本模式。"""
11
+ DEFAULT = 0
12
+ CLEAR_FIRST = 1
13
+ APPEND = 2
14
+
15
+
16
+ class DeviceType(IntEnum):
17
+ """设备类型。"""
18
+ PHONE = 0
19
+ TABLET = 1
20
+ TV = 2
21
+ WATCH = 3
22
+ CAR = 4
23
+
24
+
25
+ @dataclass
26
+ class WindowFilter:
27
+ """窗口过滤条件。"""
28
+ bundle: Optional[str] = None
29
+ title: Optional[str] = None
30
+ display_id: Optional[int] = None
31
+
32
+
33
+ class UiParam:
34
+ """UI 操作控制相关常量(类常量风格)。"""
35
+
36
+ NORMAL = "normal"
37
+ LONG = "long"
38
+ DOUBLE = "double"
39
+ LEFT = "LEFT"
40
+ RIGHT = "RIGHT"
41
+ UP = "UP"
42
+ DOWN = "DOWN"
@@ -0,0 +1,58 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """Rect 与 Point:坐标矩形与坐标点。"""
4
+ from __future__ import annotations
5
+
6
+ from dataclasses import dataclass
7
+ from typing import Tuple
8
+
9
+
10
+ @dataclass
11
+ class Point:
12
+ """坐标点。"""
13
+ x: int
14
+ y: int
15
+
16
+ def as_tuple(self) -> Tuple[int, int]:
17
+ return (self.x, self.y)
18
+
19
+
20
+ @dataclass
21
+ class Rect:
22
+ """矩形坐标。"""
23
+
24
+ left: int
25
+ top: int
26
+ right: int
27
+ bottom: int
28
+
29
+ @property
30
+ def width(self) -> int:
31
+ return self.right - self.left
32
+
33
+ @property
34
+ def height(self) -> int:
35
+ return self.bottom - self.top
36
+
37
+ @property
38
+ def center(self) -> Point:
39
+ return Point(
40
+ x=(self.left + self.right) // 2,
41
+ y=(self.top + self.bottom) // 2
42
+ )
43
+
44
+ @property
45
+ def top_left(self) -> Point:
46
+ return Point(x=self.left, y=self.top)
47
+
48
+ @property
49
+ def bottom_right(self) -> Point:
50
+ return Point(x=self.right, y=self.bottom)
51
+
52
+ def contains(self, point: Point) -> bool:
53
+ """判断点是否在矩形内。"""
54
+ return (self.left <= point.x <= self.right and
55
+ self.top <= point.y <= self.bottom)
56
+
57
+ def as_tuple(self) -> Tuple[int, int, int, int]:
58
+ return (self.left, self.top, self.right, self.bottom)
@@ -0,0 +1,18 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """Runnable:可执行任务封装。"""
4
+
5
+
6
+ class Runnable:
7
+ """延迟执行任务封装(函数 + 参数)。"""
8
+
9
+ def __init__(self, func, args, kwargs):
10
+ self.func = func
11
+ self.args = args
12
+ self.kwargs = kwargs
13
+
14
+ def run(self):
15
+ self.func(*self.args, **self.kwargs)
16
+
17
+ def __str__(self):
18
+ return "%s%s-%s" % (self.func.__name__, self.args, self.kwargs)
@@ -0,0 +1,3 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """通用工具层:只放跨平台、无业务语义的基础工具。"""
@@ -0,0 +1,72 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """轻量日志:Error / Warn / Info / Debug / Trace。
4
+
5
+ 仅控制台输出,无轮转、无脱敏。RPC 运行时默认不使用通用重试,
6
+ 避免点击、输入等有副作用操作被重复执行。
7
+ """
8
+ import logging
9
+ import sys
10
+
11
+ _LOGGER_NAME = "devhelmkit"
12
+ _logger = logging.getLogger(_LOGGER_NAME)
13
+ _logger.addHandler(logging.NullHandler())
14
+
15
+
16
+ class _LogLevel:
17
+ ERROR = logging.ERROR
18
+ WARN = logging.WARNING
19
+ INFO = logging.INFO
20
+ DEBUG = logging.DEBUG
21
+ TRACE = 5
22
+
23
+
24
+ logging.addLevelName(_LogLevel.TRACE, "TRACE")
25
+
26
+
27
+ def _trace(self, message, *args, **kwargs):
28
+ if self.isEnabledFor(_LogLevel.TRACE):
29
+ self._log(_LogLevel.TRACE, message, args, **kwargs)
30
+
31
+
32
+ logging.Logger.trace = _trace # type: ignore[attr-defined]
33
+
34
+
35
+ def setup_logger(level: int = logging.INFO) -> logging.Logger:
36
+ """配置并返回 devhelmkit logger,附加控制台 handler。"""
37
+ _logger.setLevel(level)
38
+ if not any(isinstance(h, logging.StreamHandler) and h.stream is sys.stderr
39
+ for h in _logger.handlers):
40
+ handler = logging.StreamHandler(sys.stderr)
41
+ handler.setFormatter(
42
+ logging.Formatter("[%(asctime)s][%(levelname)s][devhelmkit] %(message)s",
43
+ datefmt="%H:%M:%S")
44
+ )
45
+ _logger.addHandler(handler)
46
+ return _logger
47
+
48
+
49
+ def get_logger() -> logging.Logger:
50
+ """获取 devhelmkit logger。"""
51
+ return _logger
52
+
53
+
54
+ def error(msg, *args, **kwargs):
55
+ _logger.error(msg, *args, **kwargs)
56
+
57
+
58
+ def warn(msg, *args, **kwargs):
59
+ _logger.warning(msg, *args, **kwargs)
60
+
61
+
62
+ def info(msg, *args, **kwargs):
63
+ _logger.info(msg, *args, **kwargs)
64
+
65
+
66
+ def debug(msg, *args, **kwargs):
67
+ _logger.debug(msg, *args, **kwargs)
68
+
69
+
70
+ def trace(msg, *args, **kwargs):
71
+ if _logger.isEnabledFor(_LogLevel.TRACE):
72
+ _logger._log(_LogLevel.TRACE, msg, args, **kwargs)
@@ -0,0 +1,46 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """可控重试工具:仅用于连接阶段与无副作用流程。
4
+
5
+ RPC 运行时(控件查找、点击、输入等)默认不重试,避免副作用操作被重复执行。
6
+ """
7
+ import time
8
+ from typing import Callable, Optional, Tuple, Type
9
+
10
+ from devhelmkit.utils.logger import get_logger
11
+
12
+ _logger = get_logger()
13
+
14
+
15
+ def retry(func: Callable, retries: int = 3, delay: float = 1.0,
16
+ backoff: float = 2.0,
17
+ exceptions: Tuple[Type[BaseException], ...] = (Exception,),
18
+ sleep: Optional[Callable[[float], None]] = None) -> object:
19
+ """指数退避重试。
20
+
21
+ Args:
22
+ func: 待重试的可调用对象。
23
+ retries: 最大重试次数(不含首次)。
24
+ delay: 首次失败后等待秒数。
25
+ backoff: 退避倍率,每次等待 = delay * backoff ** attempt。
26
+ exceptions: 触发重试的异常类型。
27
+ sleep: 自定义 sleep 函数,便于测试注入。
28
+
29
+ Returns:
30
+ func 的返回值。
31
+
32
+ Raises:
33
+ 最后一次失败的异常。
34
+ """
35
+ _sleep = sleep or time.sleep
36
+ attempt = 0
37
+ while True:
38
+ try:
39
+ return func()
40
+ except exceptions as exc:
41
+ attempt += 1
42
+ if attempt > retries:
43
+ raise
44
+ wait = delay * (backoff ** (attempt - 1))
45
+ _logger.debug("第 %d 次重试,等待 %.2fs:%s", attempt, wait, exc)
46
+ _sleep(wait)
@@ -0,0 +1,64 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """超时与状态轮询工具。"""
4
+ import time
5
+ from typing import Callable, Optional, Tuple, Type
6
+
7
+ from devhelmkit.exceptions import DevhelmTimeoutError
8
+
9
+
10
+ def wait_for(predicate: Callable[[], bool], timeout: float = 10.0,
11
+ interval: float = 0.5,
12
+ sleep: Optional[Callable[[float], None]] = None) -> bool:
13
+ """轮询等待条件成立。
14
+
15
+ Args:
16
+ predicate: 返回 bool 的可调用对象,True 表示条件满足。
17
+ timeout: 总超时秒数。
18
+ interval: 轮询间隔秒数。
19
+ sleep: 自定义 sleep 函数,便于测试注入。
20
+
21
+ Returns:
22
+ True 若在超时内条件成立,否则 False。
23
+ """
24
+ _sleep = sleep or time.sleep
25
+ deadline = time.monotonic() + timeout
26
+ while True:
27
+ if predicate():
28
+ return True
29
+ if time.monotonic() >= deadline:
30
+ return False
31
+ _sleep(min(interval, max(0.0, deadline - time.monotonic())))
32
+
33
+
34
+ def wait_for_raise(predicate: Callable[[], bool], timeout: float = 10.0,
35
+ interval: float = 0.5,
36
+ exceptions: Tuple[Type[BaseException], ...] = (Exception,),
37
+ sleep: Optional[Callable[[float], None]] = None) -> bool:
38
+ """轮询等待条件成立,predicate 抛异常时视为未满足继续轮询。
39
+
40
+ 超时后返回 False 而非抛异常,调用方可自行决定是否抛 DevhelmTimeoutError。
41
+ """
42
+ _sleep = sleep or time.sleep
43
+ deadline = time.monotonic() + timeout
44
+ while True:
45
+ try:
46
+ if predicate():
47
+ return True
48
+ except exceptions:
49
+ pass
50
+ if time.monotonic() >= deadline:
51
+ return False
52
+ _sleep(min(interval, max(0.0, deadline - time.monotonic())))
53
+
54
+
55
+ def ensure_timeout(timeout: Optional[float], default: float) -> float:
56
+ """规范化超时:None 取默认值,负数视为 0。"""
57
+ if timeout is None:
58
+ return default
59
+ return max(0.0, float(timeout))
60
+
61
+
62
+ def raise_on_timeout(timeout: float, condition_desc: str) -> None:
63
+ """显式抛 DevhelmTimeoutError。"""
64
+ raise DevhelmTimeoutError("等待超时(%.2fs):%s" % (timeout, condition_desc))