scalebox-sdk 0.1.24__py3-none-any.whl → 1.0.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.
Files changed (87) hide show
  1. scalebox/__init__.py +2 -2
  2. scalebox/api/__init__.py +130 -128
  3. scalebox/api/client/__init__.py +8 -8
  4. scalebox/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +2 -2
  5. scalebox/api/client/api/sandboxes/post_sandboxes.py +2 -2
  6. scalebox/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py +193 -0
  7. scalebox/api/client/client.py +288 -288
  8. scalebox/api/client/models/connect_sandbox.py +59 -0
  9. scalebox/api/client/models/error.py +2 -2
  10. scalebox/api/client/models/listed_sandbox.py +19 -1
  11. scalebox/api/client/models/new_sandbox.py +10 -0
  12. scalebox/api/client/models/sandbox.py +138 -125
  13. scalebox/api/client/models/sandbox_detail.py +24 -0
  14. scalebox/api/client/types.py +46 -46
  15. scalebox/cli.py +125 -125
  16. scalebox/client/aclient.py +57 -57
  17. scalebox/client/client.py +102 -102
  18. scalebox/code_interpreter/__init__.py +12 -12
  19. scalebox/code_interpreter/charts.py +230 -230
  20. scalebox/code_interpreter/constants.py +3 -3
  21. scalebox/code_interpreter/exceptions.py +13 -13
  22. scalebox/code_interpreter/models.py +485 -485
  23. scalebox/connection_config.py +34 -1
  24. scalebox/csx_connect/__init__.py +1 -1
  25. scalebox/csx_connect/client.py +485 -485
  26. scalebox/csx_desktop/main.py +651 -651
  27. scalebox/exceptions.py +83 -83
  28. scalebox/generated/api.py +61 -61
  29. scalebox/generated/api_pb2.py +203 -203
  30. scalebox/generated/api_pb2.pyi +956 -956
  31. scalebox/generated/api_pb2_connect.py +1407 -1407
  32. scalebox/generated/rpc.py +50 -50
  33. scalebox/sandbox/main.py +146 -139
  34. scalebox/sandbox/sandbox_api.py +105 -91
  35. scalebox/sandbox/signature.py +40 -40
  36. scalebox/sandbox/utils.py +34 -34
  37. scalebox/sandbox_async/commands/command.py +307 -307
  38. scalebox/sandbox_async/commands/command_handle.py +187 -187
  39. scalebox/sandbox_async/commands/pty.py +187 -187
  40. scalebox/sandbox_async/filesystem/filesystem.py +557 -557
  41. scalebox/sandbox_async/filesystem/watch_handle.py +61 -61
  42. scalebox/sandbox_async/main.py +228 -46
  43. scalebox/sandbox_async/sandbox_api.py +124 -3
  44. scalebox/sandbox_async/utils.py +7 -7
  45. scalebox/sandbox_sync/__init__.py +2 -2
  46. scalebox/sandbox_sync/commands/command.py +300 -300
  47. scalebox/sandbox_sync/commands/command_handle.py +150 -150
  48. scalebox/sandbox_sync/commands/pty.py +181 -181
  49. scalebox/sandbox_sync/filesystem/filesystem.py +3 -3
  50. scalebox/sandbox_sync/filesystem/watch_handle.py +66 -66
  51. scalebox/sandbox_sync/main.py +208 -133
  52. scalebox/sandbox_sync/sandbox_api.py +119 -3
  53. scalebox/test/CODE_INTERPRETER_TESTS_READY.md +323 -323
  54. scalebox/test/README.md +329 -329
  55. scalebox/test/bedrock_openai_adapter.py +67 -0
  56. scalebox/test/code_interpreter_test.py +34 -34
  57. scalebox/test/code_interpreter_test_sync.py +34 -34
  58. scalebox/test/run_stress_code_interpreter_sync.py +166 -0
  59. scalebox/test/simple_upload_example.py +123 -0
  60. scalebox/test/stabitiy_test.py +310 -0
  61. scalebox/test/test_browser_use.py +25 -0
  62. scalebox/test/test_browser_use_scalebox.py +61 -0
  63. scalebox/test/test_code_interpreter_sync_comprehensive.py +115 -65
  64. scalebox/test/test_connect_pause_async.py +277 -0
  65. scalebox/test/test_connect_pause_sync.py +267 -0
  66. scalebox/test/test_desktop_sandbox_sf.py +117 -0
  67. scalebox/test/test_download_url.py +49 -0
  68. scalebox/test/test_sandbox_async_comprehensive.py +1 -1
  69. scalebox/test/test_sandbox_object_storage_example.py +146 -0
  70. scalebox/test/test_sandbox_object_storage_example_async.py +156 -0
  71. scalebox/test/test_sf.py +137 -0
  72. scalebox/test/test_watch_dir_async.py +56 -0
  73. scalebox/test/testacreate.py +1 -1
  74. scalebox/test/testagetinfo.py +1 -1
  75. scalebox/test/testcomputeuse.py +243 -243
  76. scalebox/test/testsandbox_api.py +13 -0
  77. scalebox/test/testsandbox_sync.py +1 -1
  78. scalebox/test/upload_100mb_example.py +355 -0
  79. scalebox/utils/httpcoreclient.py +297 -297
  80. scalebox/utils/httpxclient.py +403 -403
  81. scalebox/version.py +2 -2
  82. {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/METADATA +1 -1
  83. {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/RECORD +87 -69
  84. {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/WHEEL +1 -1
  85. {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/entry_points.txt +0 -0
  86. {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/licenses/LICENSE +0 -0
  87. {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/top_level.txt +0 -0
@@ -1,403 +1,403 @@
1
- import asyncio
2
- import contextlib
3
- import json
4
- from typing import Any, AsyncGenerator, Dict, Generator, Optional, Union
5
-
6
- import httpx
7
-
8
-
9
- class HTTPXClient:
10
- """
11
- 基于 httpx.AsyncClient 的高级 HTTP 工具类
12
- 支持同步/异步请求、连接池管理、流式传输等完整功能
13
- """
14
-
15
- def __init__(
16
- self,
17
- base_url: str = "",
18
- timeout: float = 30.0,
19
- max_connections: int = 100,
20
- max_keepalive_connections: int = 50,
21
- keepalive_expiry: float = 5.0,
22
- http2: bool = False,
23
- ssl_verify: bool = True,
24
- default_headers: Optional[Dict[str, str]] = None,
25
- follow_redirects: bool = True,
26
- retries: int = 0,
27
- backoff_factor: float = 0.1,
28
- ):
29
- """
30
- 初始化 HTTP 客户端
31
-
32
- :param base_url: 基础 URL 前缀
33
- :param timeout: 请求超时时间(秒)
34
- :param max_connections: 最大连接数
35
- :param max_keepalive_connections: 最大空闲连接数
36
- :param keepalive_expiry: 空闲连接超时时间(秒)
37
- :param http2: 是否启用 HTTP/2
38
- :param ssl_verify: 是否验证 SSL 证书
39
- :param default_headers: 默认请求头
40
- :param follow_redirects: 是否跟随重定向
41
- :param retries: 请求失败重试次数
42
- :param backoff_factor: 重试退避因子
43
- """
44
- self.base_url = base_url.rstrip("/")
45
- self.timeout = timeout
46
- self.http2 = http2
47
- self.ssl_verify = ssl_verify
48
- self.default_headers = default_headers or {}
49
- self.follow_redirects = follow_redirects
50
- self.retries = retries
51
- self.backoff_factor = backoff_factor
52
-
53
- # 创建同步客户端
54
- self.sync_client = httpx.Client(
55
- base_url=self.base_url,
56
- timeout=self.timeout,
57
- limits=httpx.Limits(
58
- max_connections=max_connections,
59
- max_keepalive_connections=max_keepalive_connections,
60
- keepalive_expiry=keepalive_expiry,
61
- ),
62
- http2=http2,
63
- verify=ssl_verify,
64
- headers=self.default_headers,
65
- follow_redirects=follow_redirects,
66
- )
67
-
68
- # 创建异步客户端
69
- self.async_client = httpx.AsyncClient(
70
- base_url=self.base_url,
71
- timeout=self.timeout,
72
- limits=httpx.Limits(
73
- max_connections=max_connections,
74
- max_keepalive_connections=max_keepalive_connections,
75
- keepalive_expiry=keepalive_expiry,
76
- ),
77
- http2=http2,
78
- verify=ssl_verify,
79
- headers=self.default_headers,
80
- follow_redirects=follow_redirects,
81
- )
82
-
83
- # 配置重试
84
- if retries > 0:
85
- retry_transport = httpx.HTTPTransport(
86
- retries=self.retries, backoff_factor=self.backoff_factor
87
- )
88
- async_retry_transport = httpx.AsyncHTTPTransport(
89
- retries=self.retries, backoff_factor=self.backoff_factor
90
- )
91
-
92
- self.sync_client = httpx.Client(transport=retry_transport)
93
- self.async_client = httpx.AsyncClient(transport=async_retry_transport)
94
-
95
- def close(self) -> None:
96
- """关闭同步客户端"""
97
- self.sync_client.close()
98
-
99
- async def aclose(self) -> None:
100
- """关闭异步客户端"""
101
- await self.async_client.aclose()
102
-
103
- @contextlib.contextmanager
104
- def context(self) -> "HTTPXClient":
105
- """
106
- 同步上下文管理器 (自动关闭)
107
-
108
- 使用示例:
109
- with HTTPXClient() as client:
110
- response = client.get("/api")
111
- """
112
- try:
113
- yield self
114
- finally:
115
- self.close()
116
-
117
- @contextlib.asynccontextmanager
118
- async def acontext(self) -> "HTTPXClient":
119
- """
120
- 异步上下文管理器 (自动关闭)
121
-
122
- 使用示例:
123
- async with HTTPXClient() as client:
124
- response = await client.aget("/api")
125
- """
126
- try:
127
- yield self
128
- finally:
129
- await self.aclose()
130
-
131
- @contextlib.contextmanager
132
- def stream(
133
- self,
134
- method: str,
135
- url: str,
136
- params: Optional[Dict[str, Any]] = None,
137
- headers: Optional[Dict[str, str]] = None,
138
- json_data: Optional[Any] = None,
139
- data: Optional[Union[bytes, Dict[str, Any]]] = None,
140
- ) -> Generator[httpx.Response, None, None]:
141
- """
142
- 同步流式请求上下文管理器
143
-
144
- 使用示例:
145
- with client.stream("GET", "/large-file") as response:
146
- for chunk in response.iter_bytes():
147
- process_chunk(chunk)
148
- """
149
- headers = headers or {}
150
- with self.sync_client.stream(
151
- method,
152
- url,
153
- params=params,
154
- headers={**self.default_headers, **headers},
155
- json=json_data,
156
- data=data,
157
- ) as response:
158
- yield response
159
-
160
- @contextlib.asynccontextmanager
161
- async def astream(
162
- self,
163
- method: str,
164
- url: str,
165
- params: Optional[Dict[str, Any]] = None,
166
- headers: Optional[Dict[str, str]] = None,
167
- json_data: Optional[Any] = None,
168
- data: Optional[Union[bytes, Dict[str, Any]]] = None,
169
- ) -> AsyncGenerator[httpx.Response, None]:
170
- """
171
- 异步流式请求上下文管理器
172
-
173
- 使用示例:
174
- async with client.astream("GET", "/large-file") as response:
175
- async for chunk in response.aiter_bytes():
176
- process_chunk(chunk)
177
- """
178
- headers = headers or {}
179
- async with self.async_client.stream(
180
- method,
181
- url,
182
- params=params,
183
- headers={**self.default_headers, **headers},
184
- json=json_data,
185
- data=data,
186
- ) as response:
187
- yield response
188
-
189
- # ================ 同步请求方法 ================
190
- def request(
191
- self,
192
- method: str,
193
- url: str,
194
- params: Optional[Dict[str, Any]] = None,
195
- headers: Optional[Dict[str, str]] = None,
196
- json_data: Optional[Any] = None,
197
- data: Optional[Union[bytes, Dict[str, Any]]] = None,
198
- ) -> httpx.Response:
199
- """同步 HTTP 请求"""
200
- headers = headers or {}
201
- return self.sync_client.request(
202
- method,
203
- url,
204
- params=params,
205
- headers={**self.default_headers, **headers},
206
- json=json_data,
207
- data=data,
208
- )
209
-
210
- def get(
211
- self,
212
- url: str,
213
- params: Optional[Dict[str, Any]] = None,
214
- headers: Optional[Dict[str, str]] = None,
215
- ) -> httpx.Response:
216
- """同步 GET 请求"""
217
- return self.request("GET", url, params=params, headers=headers)
218
-
219
- def post(
220
- self,
221
- url: str,
222
- json_data: Optional[Any] = None,
223
- data: Optional[Union[bytes, Dict[str, Any]]] = None,
224
- params: Optional[Dict[str, Any]] = None,
225
- headers: Optional[Dict[str, str]] = None,
226
- ) -> httpx.Response:
227
- """同步 POST 请求"""
228
- return self.request(
229
- "POST", url, params=params, headers=headers, json_data=json_data, data=data
230
- )
231
-
232
- def put(
233
- self,
234
- url: str,
235
- json_data: Optional[Any] = None,
236
- data: Optional[Union[bytes, Dict[str, Any]]] = None,
237
- params: Optional[Dict[str, Any]] = None,
238
- headers: Optional[Dict[str, str]] = None,
239
- ) -> httpx.Response:
240
- """同步 PUT 请求"""
241
- return self.request(
242
- "PUT", url, params=params, headers=headers, json_data=json_data, data=data
243
- )
244
-
245
- def delete(
246
- self,
247
- url: str,
248
- params: Optional[Dict[str, Any]] = None,
249
- headers: Optional[Dict[str, str]] = None,
250
- ) -> httpx.Response:
251
- """同步 DELETE 请求"""
252
- return self.request("DELETE", url, params=params, headers=headers)
253
-
254
- # ================ 异步请求方法 ================
255
- async def arequest(
256
- self,
257
- method: str,
258
- url: str,
259
- params: Optional[Dict[str, Any]] = None,
260
- headers: Optional[Dict[str, str]] = None,
261
- json_data: Optional[Any] = None,
262
- data: Optional[Union[bytes, Dict[str, Any]]] = None,
263
- ) -> httpx.Response:
264
- """异步 HTTP 请求"""
265
- headers = headers or {}
266
- return await self.async_client.request(
267
- method,
268
- url,
269
- params=params,
270
- headers={**self.default_headers, **headers},
271
- json=json_data,
272
- data=data,
273
- )
274
-
275
- async def aget(
276
- self,
277
- url: str,
278
- params: Optional[Dict[str, Any]] = None,
279
- headers: Optional[Dict[str, str]] = None,
280
- ) -> httpx.Response:
281
- """异步 GET 请求"""
282
- return await self.arequest("GET", url, params=params, headers=headers)
283
-
284
- async def apost(
285
- self,
286
- url: str,
287
- json_data: Optional[Any] = None,
288
- data: Optional[Union[bytes, Dict[str, Any]]] = None,
289
- params: Optional[Dict[str, Any]] = None,
290
- headers: Optional[Dict[str, str]] = None,
291
- ) -> httpx.Response:
292
- """异步 POST 请求"""
293
- return await self.arequest(
294
- "POST", url, params=params, headers=headers, json_data=json_data, data=data
295
- )
296
-
297
- async def aput(
298
- self,
299
- url: str,
300
- json_data: Optional[Any] = None,
301
- data: Optional[Union[bytes, Dict[str, Any]]] = None,
302
- params: Optional[Dict[str, Any]] = None,
303
- headers: Optional[Dict[str, str]] = None,
304
- ) -> httpx.Response:
305
- """异步 PUT 请求"""
306
- return await self.arequest(
307
- "PUT", url, params=params, headers=headers, json_data=json_data, data=data
308
- )
309
-
310
- async def adelete(
311
- self,
312
- url: str,
313
- params: Optional[Dict[str, Any]] = None,
314
- headers: Optional[Dict[str, str]] = None,
315
- ) -> httpx.Response:
316
- """异步 DELETE 请求"""
317
- return await self.arequest("DELETE", url, params=params, headers=headers)
318
-
319
- # ================ 响应处理工具 ================
320
- @staticmethod
321
- def read_response(response: httpx.Response) -> bytes:
322
- """读取完整响应内容 (同步)"""
323
- return response.content
324
-
325
- @staticmethod
326
- def read_response_text(response: httpx.Response) -> str:
327
- """读取响应文本 (同步)"""
328
- return response.text
329
-
330
- @staticmethod
331
- def read_response_json(response: httpx.Response) -> Any:
332
- """解析 JSON 响应 (同步)"""
333
- return response.json()
334
-
335
- @staticmethod
336
- async def aread_response(response: httpx.Response) -> bytes:
337
- """异步读取完整响应内容"""
338
- return await response.aread()
339
-
340
- @staticmethod
341
- async def aread_response_text(response: httpx.Response) -> str:
342
- """异步读取响应文本"""
343
- return await response.atext()
344
-
345
- @staticmethod
346
- async def aread_response_json(response: httpx.Response) -> Any:
347
- """异步解析 JSON 响应"""
348
- return await response.ajson()
349
-
350
- # ================ 连接池状态 ================
351
- def connection_pool_status(self) -> Dict[str, Any]:
352
- """获取同步连接池状态"""
353
- transport = self.sync_client._transport
354
- return {
355
- "total_connections": transport._pool.num_connections,
356
- "active_connections": transport._pool.num_active_connections,
357
- "idle_connections": transport._pool.num_idle_connections,
358
- "max_connections": transport._pool.max_connections,
359
- }
360
-
361
- async def aconnection_pool_status(self) -> Dict[str, Any]:
362
- """获取异步连接池状态"""
363
- transport = self.async_client._transport
364
- return {
365
- "total_connections": transport._pool.num_connections,
366
- "active_connections": transport._pool.num_active_connections,
367
- "idle_connections": transport._pool.num_idle_connections,
368
- "max_connections": transport._pool.max_connections,
369
- }
370
-
371
- # ================ 高级功能 ================
372
- def set_proxy(self, proxy_url: str, proxy_auth: Optional[tuple] = None) -> None:
373
- """设置代理 (同步和异步)"""
374
- self.sync_client = httpx.Client(proxies=proxy_url, auth=proxy_auth)
375
- self.async_client = httpx.AsyncClient(proxies=proxy_url, auth=proxy_auth)
376
-
377
- def set_cookies(self, cookies: Dict[str, str]) -> None:
378
- """设置 Cookies (同步和异步)"""
379
- self.sync_client.cookies = httpx.Cookies(cookies)
380
- self.async_client.cookies = httpx.Cookies(cookies)
381
-
382
- def add_event_hook(self, event: str, hook: callable) -> None:
383
- """添加事件钩子 (同步和异步)"""
384
- self.sync_client.event_hooks[event].append(hook)
385
- self.async_client.event_hooks[event].append(hook)
386
-
387
- def reset_client(self) -> None:
388
- """重置客户端 (清除所有状态)"""
389
- self.close()
390
- self.async_client = httpx.AsyncClient(
391
- base_url=self.base_url,
392
- timeout=self.timeout,
393
- http2=self.http2,
394
- verify=self.ssl_verify,
395
- headers=self.default_headers,
396
- )
397
- self.sync_client = httpx.Client(
398
- base_url=self.base_url,
399
- timeout=self.timeout,
400
- http2=self.http2,
401
- verify=self.ssl_verify,
402
- headers=self.default_headers,
403
- )
1
+ import asyncio
2
+ import contextlib
3
+ import json
4
+ from typing import Any, AsyncGenerator, Dict, Generator, Optional, Union
5
+
6
+ import httpx
7
+
8
+
9
+ class HTTPXClient:
10
+ """
11
+ 基于 httpx.AsyncClient 的高级 HTTP 工具类
12
+ 支持同步/异步请求、连接池管理、流式传输等完整功能
13
+ """
14
+
15
+ def __init__(
16
+ self,
17
+ base_url: str = "",
18
+ timeout: float = 30.0,
19
+ max_connections: int = 100,
20
+ max_keepalive_connections: int = 50,
21
+ keepalive_expiry: float = 5.0,
22
+ http2: bool = False,
23
+ ssl_verify: bool = True,
24
+ default_headers: Optional[Dict[str, str]] = None,
25
+ follow_redirects: bool = True,
26
+ retries: int = 0,
27
+ backoff_factor: float = 0.1,
28
+ ):
29
+ """
30
+ 初始化 HTTP 客户端
31
+
32
+ :param base_url: 基础 URL 前缀
33
+ :param timeout: 请求超时时间(秒)
34
+ :param max_connections: 最大连接数
35
+ :param max_keepalive_connections: 最大空闲连接数
36
+ :param keepalive_expiry: 空闲连接超时时间(秒)
37
+ :param http2: 是否启用 HTTP/2
38
+ :param ssl_verify: 是否验证 SSL 证书
39
+ :param default_headers: 默认请求头
40
+ :param follow_redirects: 是否跟随重定向
41
+ :param retries: 请求失败重试次数
42
+ :param backoff_factor: 重试退避因子
43
+ """
44
+ self.base_url = base_url.rstrip("/")
45
+ self.timeout = timeout
46
+ self.http2 = http2
47
+ self.ssl_verify = ssl_verify
48
+ self.default_headers = default_headers or {}
49
+ self.follow_redirects = follow_redirects
50
+ self.retries = retries
51
+ self.backoff_factor = backoff_factor
52
+
53
+ # 创建同步客户端
54
+ self.sync_client = httpx.Client(
55
+ base_url=self.base_url,
56
+ timeout=self.timeout,
57
+ limits=httpx.Limits(
58
+ max_connections=max_connections,
59
+ max_keepalive_connections=max_keepalive_connections,
60
+ keepalive_expiry=keepalive_expiry,
61
+ ),
62
+ http2=http2,
63
+ verify=ssl_verify,
64
+ headers=self.default_headers,
65
+ follow_redirects=follow_redirects,
66
+ )
67
+
68
+ # 创建异步客户端
69
+ self.async_client = httpx.AsyncClient(
70
+ base_url=self.base_url,
71
+ timeout=self.timeout,
72
+ limits=httpx.Limits(
73
+ max_connections=max_connections,
74
+ max_keepalive_connections=max_keepalive_connections,
75
+ keepalive_expiry=keepalive_expiry,
76
+ ),
77
+ http2=http2,
78
+ verify=ssl_verify,
79
+ headers=self.default_headers,
80
+ follow_redirects=follow_redirects,
81
+ )
82
+
83
+ # 配置重试
84
+ if retries > 0:
85
+ retry_transport = httpx.HTTPTransport(
86
+ retries=self.retries, backoff_factor=self.backoff_factor
87
+ )
88
+ async_retry_transport = httpx.AsyncHTTPTransport(
89
+ retries=self.retries, backoff_factor=self.backoff_factor
90
+ )
91
+
92
+ self.sync_client = httpx.Client(transport=retry_transport)
93
+ self.async_client = httpx.AsyncClient(transport=async_retry_transport)
94
+
95
+ def close(self) -> None:
96
+ """关闭同步客户端"""
97
+ self.sync_client.close()
98
+
99
+ async def aclose(self) -> None:
100
+ """关闭异步客户端"""
101
+ await self.async_client.aclose()
102
+
103
+ @contextlib.contextmanager
104
+ def context(self) -> "HTTPXClient":
105
+ """
106
+ 同步上下文管理器 (自动关闭)
107
+
108
+ 使用示例:
109
+ with HTTPXClient() as client:
110
+ response = client.get("/api")
111
+ """
112
+ try:
113
+ yield self
114
+ finally:
115
+ self.close()
116
+
117
+ @contextlib.asynccontextmanager
118
+ async def acontext(self) -> "HTTPXClient":
119
+ """
120
+ 异步上下文管理器 (自动关闭)
121
+
122
+ 使用示例:
123
+ async with HTTPXClient() as client:
124
+ response = await client.aget("/api")
125
+ """
126
+ try:
127
+ yield self
128
+ finally:
129
+ await self.aclose()
130
+
131
+ @contextlib.contextmanager
132
+ def stream(
133
+ self,
134
+ method: str,
135
+ url: str,
136
+ params: Optional[Dict[str, Any]] = None,
137
+ headers: Optional[Dict[str, str]] = None,
138
+ json_data: Optional[Any] = None,
139
+ data: Optional[Union[bytes, Dict[str, Any]]] = None,
140
+ ) -> Generator[httpx.Response, None, None]:
141
+ """
142
+ 同步流式请求上下文管理器
143
+
144
+ 使用示例:
145
+ with client.stream("GET", "/large-file") as response:
146
+ for chunk in response.iter_bytes():
147
+ process_chunk(chunk)
148
+ """
149
+ headers = headers or {}
150
+ with self.sync_client.stream(
151
+ method,
152
+ url,
153
+ params=params,
154
+ headers={**self.default_headers, **headers},
155
+ json=json_data,
156
+ data=data,
157
+ ) as response:
158
+ yield response
159
+
160
+ @contextlib.asynccontextmanager
161
+ async def astream(
162
+ self,
163
+ method: str,
164
+ url: str,
165
+ params: Optional[Dict[str, Any]] = None,
166
+ headers: Optional[Dict[str, str]] = None,
167
+ json_data: Optional[Any] = None,
168
+ data: Optional[Union[bytes, Dict[str, Any]]] = None,
169
+ ) -> AsyncGenerator[httpx.Response, None]:
170
+ """
171
+ 异步流式请求上下文管理器
172
+
173
+ 使用示例:
174
+ async with client.astream("GET", "/large-file") as response:
175
+ async for chunk in response.aiter_bytes():
176
+ process_chunk(chunk)
177
+ """
178
+ headers = headers or {}
179
+ async with self.async_client.stream(
180
+ method,
181
+ url,
182
+ params=params,
183
+ headers={**self.default_headers, **headers},
184
+ json=json_data,
185
+ data=data,
186
+ ) as response:
187
+ yield response
188
+
189
+ # ================ 同步请求方法 ================
190
+ def request(
191
+ self,
192
+ method: str,
193
+ url: str,
194
+ params: Optional[Dict[str, Any]] = None,
195
+ headers: Optional[Dict[str, str]] = None,
196
+ json_data: Optional[Any] = None,
197
+ data: Optional[Union[bytes, Dict[str, Any]]] = None,
198
+ ) -> httpx.Response:
199
+ """同步 HTTP 请求"""
200
+ headers = headers or {}
201
+ return self.sync_client.request(
202
+ method,
203
+ url,
204
+ params=params,
205
+ headers={**self.default_headers, **headers},
206
+ json=json_data,
207
+ data=data,
208
+ )
209
+
210
+ def get(
211
+ self,
212
+ url: str,
213
+ params: Optional[Dict[str, Any]] = None,
214
+ headers: Optional[Dict[str, str]] = None,
215
+ ) -> httpx.Response:
216
+ """同步 GET 请求"""
217
+ return self.request("GET", url, params=params, headers=headers)
218
+
219
+ def post(
220
+ self,
221
+ url: str,
222
+ json_data: Optional[Any] = None,
223
+ data: Optional[Union[bytes, Dict[str, Any]]] = None,
224
+ params: Optional[Dict[str, Any]] = None,
225
+ headers: Optional[Dict[str, str]] = None,
226
+ ) -> httpx.Response:
227
+ """同步 POST 请求"""
228
+ return self.request(
229
+ "POST", url, params=params, headers=headers, json_data=json_data, data=data
230
+ )
231
+
232
+ def put(
233
+ self,
234
+ url: str,
235
+ json_data: Optional[Any] = None,
236
+ data: Optional[Union[bytes, Dict[str, Any]]] = None,
237
+ params: Optional[Dict[str, Any]] = None,
238
+ headers: Optional[Dict[str, str]] = None,
239
+ ) -> httpx.Response:
240
+ """同步 PUT 请求"""
241
+ return self.request(
242
+ "PUT", url, params=params, headers=headers, json_data=json_data, data=data
243
+ )
244
+
245
+ def delete(
246
+ self,
247
+ url: str,
248
+ params: Optional[Dict[str, Any]] = None,
249
+ headers: Optional[Dict[str, str]] = None,
250
+ ) -> httpx.Response:
251
+ """同步 DELETE 请求"""
252
+ return self.request("DELETE", url, params=params, headers=headers)
253
+
254
+ # ================ 异步请求方法 ================
255
+ async def arequest(
256
+ self,
257
+ method: str,
258
+ url: str,
259
+ params: Optional[Dict[str, Any]] = None,
260
+ headers: Optional[Dict[str, str]] = None,
261
+ json_data: Optional[Any] = None,
262
+ data: Optional[Union[bytes, Dict[str, Any]]] = None,
263
+ ) -> httpx.Response:
264
+ """异步 HTTP 请求"""
265
+ headers = headers or {}
266
+ return await self.async_client.request(
267
+ method,
268
+ url,
269
+ params=params,
270
+ headers={**self.default_headers, **headers},
271
+ json=json_data,
272
+ data=data,
273
+ )
274
+
275
+ async def aget(
276
+ self,
277
+ url: str,
278
+ params: Optional[Dict[str, Any]] = None,
279
+ headers: Optional[Dict[str, str]] = None,
280
+ ) -> httpx.Response:
281
+ """异步 GET 请求"""
282
+ return await self.arequest("GET", url, params=params, headers=headers)
283
+
284
+ async def apost(
285
+ self,
286
+ url: str,
287
+ json_data: Optional[Any] = None,
288
+ data: Optional[Union[bytes, Dict[str, Any]]] = None,
289
+ params: Optional[Dict[str, Any]] = None,
290
+ headers: Optional[Dict[str, str]] = None,
291
+ ) -> httpx.Response:
292
+ """异步 POST 请求"""
293
+ return await self.arequest(
294
+ "POST", url, params=params, headers=headers, json_data=json_data, data=data
295
+ )
296
+
297
+ async def aput(
298
+ self,
299
+ url: str,
300
+ json_data: Optional[Any] = None,
301
+ data: Optional[Union[bytes, Dict[str, Any]]] = None,
302
+ params: Optional[Dict[str, Any]] = None,
303
+ headers: Optional[Dict[str, str]] = None,
304
+ ) -> httpx.Response:
305
+ """异步 PUT 请求"""
306
+ return await self.arequest(
307
+ "PUT", url, params=params, headers=headers, json_data=json_data, data=data
308
+ )
309
+
310
+ async def adelete(
311
+ self,
312
+ url: str,
313
+ params: Optional[Dict[str, Any]] = None,
314
+ headers: Optional[Dict[str, str]] = None,
315
+ ) -> httpx.Response:
316
+ """异步 DELETE 请求"""
317
+ return await self.arequest("DELETE", url, params=params, headers=headers)
318
+
319
+ # ================ 响应处理工具 ================
320
+ @staticmethod
321
+ def read_response(response: httpx.Response) -> bytes:
322
+ """读取完整响应内容 (同步)"""
323
+ return response.content
324
+
325
+ @staticmethod
326
+ def read_response_text(response: httpx.Response) -> str:
327
+ """读取响应文本 (同步)"""
328
+ return response.text
329
+
330
+ @staticmethod
331
+ def read_response_json(response: httpx.Response) -> Any:
332
+ """解析 JSON 响应 (同步)"""
333
+ return response.json()
334
+
335
+ @staticmethod
336
+ async def aread_response(response: httpx.Response) -> bytes:
337
+ """异步读取完整响应内容"""
338
+ return await response.aread()
339
+
340
+ @staticmethod
341
+ async def aread_response_text(response: httpx.Response) -> str:
342
+ """异步读取响应文本"""
343
+ return await response.atext()
344
+
345
+ @staticmethod
346
+ async def aread_response_json(response: httpx.Response) -> Any:
347
+ """异步解析 JSON 响应"""
348
+ return await response.ajson()
349
+
350
+ # ================ 连接池状态 ================
351
+ def connection_pool_status(self) -> Dict[str, Any]:
352
+ """获取同步连接池状态"""
353
+ transport = self.sync_client._transport
354
+ return {
355
+ "total_connections": transport._pool.num_connections,
356
+ "active_connections": transport._pool.num_active_connections,
357
+ "idle_connections": transport._pool.num_idle_connections,
358
+ "max_connections": transport._pool.max_connections,
359
+ }
360
+
361
+ async def aconnection_pool_status(self) -> Dict[str, Any]:
362
+ """获取异步连接池状态"""
363
+ transport = self.async_client._transport
364
+ return {
365
+ "total_connections": transport._pool.num_connections,
366
+ "active_connections": transport._pool.num_active_connections,
367
+ "idle_connections": transport._pool.num_idle_connections,
368
+ "max_connections": transport._pool.max_connections,
369
+ }
370
+
371
+ # ================ 高级功能 ================
372
+ def set_proxy(self, proxy_url: str, proxy_auth: Optional[tuple] = None) -> None:
373
+ """设置代理 (同步和异步)"""
374
+ self.sync_client = httpx.Client(proxies=proxy_url, auth=proxy_auth)
375
+ self.async_client = httpx.AsyncClient(proxies=proxy_url, auth=proxy_auth)
376
+
377
+ def set_cookies(self, cookies: Dict[str, str]) -> None:
378
+ """设置 Cookies (同步和异步)"""
379
+ self.sync_client.cookies = httpx.Cookies(cookies)
380
+ self.async_client.cookies = httpx.Cookies(cookies)
381
+
382
+ def add_event_hook(self, event: str, hook: callable) -> None:
383
+ """添加事件钩子 (同步和异步)"""
384
+ self.sync_client.event_hooks[event].append(hook)
385
+ self.async_client.event_hooks[event].append(hook)
386
+
387
+ def reset_client(self) -> None:
388
+ """重置客户端 (清除所有状态)"""
389
+ self.close()
390
+ self.async_client = httpx.AsyncClient(
391
+ base_url=self.base_url,
392
+ timeout=self.timeout,
393
+ http2=self.http2,
394
+ verify=self.ssl_verify,
395
+ headers=self.default_headers,
396
+ )
397
+ self.sync_client = httpx.Client(
398
+ base_url=self.base_url,
399
+ timeout=self.timeout,
400
+ http2=self.http2,
401
+ verify=self.ssl_verify,
402
+ headers=self.default_headers,
403
+ )