devicebase 2026.4.1__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.
devicebase/__init__.py ADDED
@@ -0,0 +1,44 @@
1
+ """DeviceBase Python SDK for TestClaw device automation."""
2
+
3
+ from devicebase.client import DeviceBaseClient
4
+ from devicebase.http_client import (
5
+ AuthenticationError,
6
+ DeviceBaseError,
7
+ DeviceNotFoundError,
8
+ ValidationError,
9
+ )
10
+ from devicebase.models import (
11
+ AppInfo,
12
+ Bounds,
13
+ DeviceInfo,
14
+ HierarchyInfo,
15
+ InputTextRequest,
16
+ LaunchAppRequest,
17
+ OperationResult,
18
+ Point,
19
+ )
20
+ from devicebase.websocket_client import MinicapClient, MinitouchClient
21
+
22
+ __version__ = "2026.4.1"
23
+
24
+ __all__ = [
25
+ # Main client
26
+ "DeviceBaseClient",
27
+ # Exceptions
28
+ "AuthenticationError",
29
+ "DeviceBaseError",
30
+ "DeviceNotFoundError",
31
+ "ValidationError",
32
+ # Models
33
+ "AppInfo",
34
+ "Bounds",
35
+ "DeviceInfo",
36
+ "HierarchyInfo",
37
+ "InputTextRequest",
38
+ "LaunchAppRequest",
39
+ "OperationResult",
40
+ "Point",
41
+ # WebSocket clients
42
+ "MinicapClient",
43
+ "MinitouchClient",
44
+ ]
devicebase/client.py ADDED
@@ -0,0 +1,372 @@
1
+ """Main DeviceBase client providing high-level access to all device operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from collections.abc import AsyncIterator
7
+
8
+ from devicebase.http_client import (
9
+ AuthenticationError,
10
+ DeviceBaseHttpClient,
11
+ )
12
+ from devicebase.models import (
13
+ AppInfo,
14
+ Bounds,
15
+ DeviceInfo,
16
+ HierarchyInfo,
17
+ OperationResult,
18
+ Point,
19
+ )
20
+ from devicebase.websocket_client import MinicapClient, MinitouchClient
21
+
22
+
23
+ class DeviceBaseClient:
24
+ """Main client for interacting with the DeviceBase API.
25
+
26
+ This client provides a unified interface for all device automation operations,
27
+ including HTTP-based device control and WebSocket-based streaming and touch control.
28
+
29
+ Configuration can be provided via constructor parameters or environment variables:
30
+ - DEVICEBASE_BASE_URL: API base URL (default: https://api.devicebase.cn)
31
+ - DEVICEBASE_API_KEY: JWT API key for authentication
32
+
33
+ Example:
34
+ ```python
35
+ import asyncio
36
+ from devicebase import DeviceBaseClient
37
+
38
+ # Using environment variables
39
+ client = DeviceBaseClient()
40
+
41
+ # Or with explicit configuration
42
+ client = DeviceBaseClient(
43
+ base_url="https://api.devicebase.cn",
44
+ api_key="your-jwt-token"
45
+ )
46
+
47
+ # Get device info
48
+ info = client.get_device_info("device123")
49
+
50
+ # Control the device
51
+ client.tap("device123", Point(x=100, y=200))
52
+ client.launch_app("device123", "com.example.app")
53
+
54
+ # WebSocket streaming (async)
55
+ async def stream_screen():
56
+ async for frame in client.stream_minicap("device123"):
57
+ # Process JPEG frame
58
+ pass
59
+
60
+ asyncio.run(stream_screen())
61
+ ```
62
+ """
63
+
64
+ DEFAULT_BASE_URL = "https://api.devicebase.cn"
65
+
66
+ def __init__(
67
+ self,
68
+ base_url: str | None = None,
69
+ api_key: str | None = None,
70
+ timeout: float = 30.0,
71
+ ) -> None:
72
+ """Initialize the DeviceBase client.
73
+
74
+ Args:
75
+ base_url: The base URL of the DeviceBase API. If not provided,
76
+ reads from DEVICEBASE_BASE_URL environment variable,
77
+ defaults to https://api.devicebase.cn.
78
+ api_key: JWT API key for authentication. If not provided,
79
+ reads from DEVICEBASE_API_KEY environment variable.
80
+ timeout: Request timeout in seconds for HTTP operations.
81
+
82
+ Raises:
83
+ AuthenticationError: If no API key is provided via parameter
84
+ or environment variable.
85
+ """
86
+ self._base_url = base_url or os.environ.get(
87
+ "DEVICEBASE_BASE_URL", self.DEFAULT_BASE_URL
88
+ )
89
+ self._api_key = api_key or os.environ.get("DEVICEBASE_API_KEY")
90
+
91
+ if not self._api_key:
92
+ raise AuthenticationError(
93
+ "API key is required. Provide it via 'api_key' parameter "
94
+ "or DEVICEBASE_API_KEY environment variable."
95
+ )
96
+
97
+ self._http = DeviceBaseHttpClient(
98
+ base_url=self._base_url,
99
+ api_key=self._api_key,
100
+ timeout=timeout,
101
+ )
102
+
103
+ def close(self) -> None:
104
+ """Close the client and release all resources."""
105
+ self._http.close()
106
+
107
+ def __enter__(self) -> DeviceBaseClient:
108
+ """Context manager entry."""
109
+ return self
110
+
111
+ def __exit__(self, *args: object) -> None:
112
+ """Context manager exit."""
113
+ self.close()
114
+
115
+ # Device Info
116
+ def get_device_info(self, serial: str) -> DeviceInfo:
117
+ """Get detailed information about a device.
118
+
119
+ Args:
120
+ serial: The device unique identifier.
121
+
122
+ Returns:
123
+ DeviceInfo containing device status, hardware info, and connection state.
124
+
125
+ Raises:
126
+ DeviceNotFoundError: If the device is not found or not connected.
127
+ ValidationError: If the serial is invalid.
128
+ """
129
+ return self._http.get_device_info(serial)
130
+
131
+ # Touch Operations
132
+ def tap(self, serial: str, x: int, y: int) -> OperationResult:
133
+ """Perform a single tap at the specified coordinates.
134
+
135
+ Args:
136
+ serial: The device unique identifier.
137
+ x: Horizontal coordinate (pixels from left).
138
+ y: Vertical coordinate (pixels from top).
139
+
140
+ Returns:
141
+ OperationResult indicating success or failure.
142
+ """
143
+ return self._http.tap(serial, Point(x=x, y=y))
144
+
145
+ def double_tap(self, serial: str, x: int, y: int) -> OperationResult:
146
+ """Perform a double tap at the specified coordinates.
147
+
148
+ Args:
149
+ serial: The device unique identifier.
150
+ x: Horizontal coordinate.
151
+ y: Vertical coordinate.
152
+
153
+ Returns:
154
+ OperationResult indicating success or failure.
155
+ """
156
+ return self._http.double_tap(serial, Point(x=x, y=y))
157
+
158
+ def long_press(self, serial: str, x: int, y: int) -> OperationResult:
159
+ """Perform a long press at the specified coordinates.
160
+
161
+ Args:
162
+ serial: The device unique identifier.
163
+ x: Horizontal coordinate.
164
+ y: Vertical coordinate.
165
+
166
+ Returns:
167
+ OperationResult indicating success or failure.
168
+ """
169
+ return self._http.long_press(serial, Point(x=x, y=y))
170
+
171
+ def swipe(
172
+ self, serial: str, x1: int, y1: int, x2: int, y2: int
173
+ ) -> OperationResult:
174
+ """Perform a swipe gesture from start to end coordinates.
175
+
176
+ Args:
177
+ serial: The device unique identifier.
178
+ x1: Starting X coordinate.
179
+ y1: Starting Y coordinate.
180
+ x2: Ending X coordinate.
181
+ y2: Ending Y coordinate.
182
+
183
+ Returns:
184
+ OperationResult indicating success or failure.
185
+ """
186
+ return self._http.swipe(serial, Bounds(x1=x1, y1=y1, x2=x2, y2=y2))
187
+
188
+ # Navigation
189
+ def back(self, serial: str) -> OperationResult:
190
+ """Simulate the device back button press.
191
+
192
+ Args:
193
+ serial: The device unique identifier.
194
+
195
+ Returns:
196
+ OperationResult indicating success or failure.
197
+ """
198
+ return self._http.back(serial)
199
+
200
+ def home(self, serial: str) -> OperationResult:
201
+ """Simulate the device home button press.
202
+
203
+ Args:
204
+ serial: The device unique identifier.
205
+
206
+ Returns:
207
+ OperationResult indicating success or failure.
208
+ """
209
+ return self._http.home(serial)
210
+
211
+ # App Operations
212
+ def launch_app(self, serial: str, app_name: str) -> OperationResult:
213
+ """Launch an application on the device.
214
+
215
+ Args:
216
+ serial: The device unique identifier.
217
+ app_name: The package name or identifier of the app to launch.
218
+
219
+ Returns:
220
+ OperationResult indicating success or failure.
221
+ """
222
+ return self._http.launch_app(serial, app_name)
223
+
224
+ def get_current_app(self, serial: str) -> AppInfo:
225
+ """Get information about the currently running foreground app.
226
+
227
+ Args:
228
+ serial: The device unique identifier.
229
+
230
+ Returns:
231
+ AppInfo containing the current app name and details.
232
+ """
233
+ return self._http.get_current_app(serial)
234
+
235
+ # Text Input
236
+ def input_text(self, serial: str, text: str) -> OperationResult:
237
+ """Input text into the currently focused field.
238
+
239
+ Args:
240
+ serial: The device unique identifier.
241
+ text: The text to input.
242
+
243
+ Returns:
244
+ OperationResult indicating success or failure.
245
+ """
246
+ return self._http.input_text(serial, text)
247
+
248
+ def clear_text(self, serial: str) -> OperationResult:
249
+ """Clear text in the currently focused field.
250
+
251
+ Args:
252
+ serial: The device unique identifier.
253
+
254
+ Returns:
255
+ OperationResult indicating success or failure.
256
+ """
257
+ return self._http.clear_text(serial)
258
+
259
+ # UI Hierarchy
260
+ def dump_hierarchy(self, serial: str) -> HierarchyInfo:
261
+ """Get the current UI hierarchy structure.
262
+
263
+ Args:
264
+ serial: The device unique identifier.
265
+
266
+ Returns:
267
+ HierarchyInfo containing the UI element tree.
268
+ """
269
+ return self._http.dump_hierarchy(serial)
270
+
271
+ # Screenshots
272
+ def get_screenshot(self, serial: str) -> bytes:
273
+ """Get a screenshot of the device screen as JPEG bytes.
274
+
275
+ Args:
276
+ serial: The device unique identifier.
277
+
278
+ Returns:
279
+ Raw JPEG image bytes.
280
+
281
+ Raises:
282
+ DeviceNotFoundError: If the device is not found.
283
+ """
284
+ return self._http.get_screenshot(serial)
285
+
286
+ def download_screenshot(self, serial: str) -> bytes:
287
+ """Download screenshot as a file attachment.
288
+
289
+ The filename will be {serial}_screenshot.jpg.
290
+
291
+ Args:
292
+ serial: The device unique identifier.
293
+
294
+ Returns:
295
+ Raw JPEG image bytes.
296
+ """
297
+ return self._http.download_screenshot(serial)
298
+
299
+ # WebSocket Clients
300
+ def minicap_client(self, serial: str) -> MinicapClient:
301
+ """Create a minicap WebSocket client for screen streaming.
302
+
303
+ Args:
304
+ serial: The device unique identifier.
305
+
306
+ Returns:
307
+ MinicapClient configured for the device.
308
+
309
+ Example:
310
+ ```python
311
+ client = DeviceBaseClient()
312
+ minicap = client.minicap_client("device123")
313
+
314
+ async for frame in minicap.stream_frames():
315
+ # Process JPEG frame
316
+ pass
317
+ ```
318
+ """
319
+ return MinicapClient(
320
+ base_url=self._base_url,
321
+ serial=serial,
322
+ api_key=self._api_key,
323
+ )
324
+
325
+ def minitouch_client(self, serial: str) -> MinitouchClient:
326
+ """Create a minitouch WebSocket client for touch control.
327
+
328
+ Args:
329
+ serial: The device unique identifier.
330
+
331
+ Returns:
332
+ MinitouchClient configured for the device.
333
+
334
+ Example:
335
+ ```python
336
+ client = DeviceBaseClient()
337
+ minitouch = client.minitouch_client("device123")
338
+
339
+ async with minitouch:
340
+ await minitouch.tap(100, 200)
341
+ ```
342
+ """
343
+ return MinitouchClient(
344
+ base_url=self._base_url,
345
+ serial=serial,
346
+ api_key=self._api_key,
347
+ )
348
+
349
+ # Async streaming convenience methods
350
+ def stream_minicap(self, serial: str) -> AsyncIterator[bytes]:
351
+ """Stream JPEG frames from the device screen.
352
+
353
+ This is a convenience method that creates a minicap client
354
+ and yields frames from its stream.
355
+
356
+ Args:
357
+ serial: The device unique identifier.
358
+
359
+ Yields:
360
+ JPEG image bytes for each frame.
361
+
362
+ Example:
363
+ ```python
364
+ client = DeviceBaseClient()
365
+
366
+ async for frame in client.stream_minicap("device123"):
367
+ with open("frame.jpg", "wb") as f:
368
+ f.write(frame)
369
+ ```
370
+ """
371
+ client = self.minicap_client(serial)
372
+ return client.stream_frames()