atomhttp 1.0.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.
- atomhttp/__init__.py +76 -0
- atomhttp/adapters/__init__.py +4 -0
- atomhttp/adapters/http_adapter.py +102 -0
- atomhttp/adapters/mock_adapter.py +130 -0
- atomhttp/auth/__init__.py +4 -0
- atomhttp/auth/basic_auth.py +90 -0
- atomhttp/auth/bearer_auth.py +83 -0
- atomhttp/client.py +577 -0
- atomhttp/core/__init__.py +19 -0
- atomhttp/core/adapters.py +687 -0
- atomhttp/core/config.py +186 -0
- atomhttp/core/defaults.py +212 -0
- atomhttp/core/form_data.py +282 -0
- atomhttp/core/request.py +240 -0
- atomhttp/core/response.py +101 -0
- atomhttp/errors/__init__.py +13 -0
- atomhttp/errors/http_errors.py +142 -0
- atomhttp/interceptors/__init__.py +5 -0
- atomhttp/interceptors/manager.py +136 -0
- atomhttp/interceptors/request_interceptor.py +18 -0
- atomhttp/interceptors/response_interceptor.py +18 -0
- atomhttp/progress/__init__.py +3 -0
- atomhttp/progress/upload_progress.py +89 -0
- atomhttp/transforms/__init__.py +5 -0
- atomhttp/transforms/data_serializer.py +96 -0
- atomhttp/transforms/request_transform.py +96 -0
- atomhttp/transforms/response_transform.py +73 -0
- atomhttp/utils/__init__.py +5 -0
- atomhttp/utils/cookies.py +128 -0
- atomhttp/utils/helpers.py +111 -0
- atomhttp/utils/redirect.py +107 -0
- atomhttp-1.0.0.dist-info/METADATA +165 -0
- atomhttp-1.0.0.dist-info/RECORD +36 -0
- atomhttp-1.0.0.dist-info/WHEEL +5 -0
- atomhttp-1.0.0.dist-info/licenses/LICENSE +21 -0
- atomhttp-1.0.0.dist-info/top_level.txt +1 -0
atomhttp/client.py
ADDED
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AtomHTTP Client Module
|
|
3
|
+
----------------------
|
|
4
|
+
Main HTTP client class for the AtomHTTP library, providing a comprehensive
|
|
5
|
+
interface for making HTTP requests with support for interceptors, progress
|
|
6
|
+
tracking, FormData, concurrent requests, and extensive configuration options.
|
|
7
|
+
|
|
8
|
+
This module contains the AtomHTTP client class which serves as the primary
|
|
9
|
+
entry point for all HTTP operations. It provides a clean, axios-like API
|
|
10
|
+
with support for all standard HTTP methods, request/response interceptors,
|
|
11
|
+
upload/download progress tracking, automatic JSON serialization, and more.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
from typing import Dict, Any, Optional, Callable, List, Union
|
|
16
|
+
from urllib.parse import urljoin, urlencode, parse_qs
|
|
17
|
+
from .core.request import RequestHandler
|
|
18
|
+
from .core.response import Response
|
|
19
|
+
from .core.config import RequestConfig
|
|
20
|
+
from .core.defaults import Defaults
|
|
21
|
+
from .core.form_data import FormData
|
|
22
|
+
from .interceptors.manager import InterceptorManager
|
|
23
|
+
from .errors.http_errors import AtomHTTPError, AtomHTTPRequestError
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AtomHTTP:
|
|
27
|
+
"""
|
|
28
|
+
Main HTTP client class providing a comprehensive interface for HTTP requests.
|
|
29
|
+
|
|
30
|
+
This client is the primary entry point for the AtomHTTP library. It supports:
|
|
31
|
+
- All HTTP methods (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)
|
|
32
|
+
- Request and response interceptors
|
|
33
|
+
- Upload and download progress tracking
|
|
34
|
+
- FormData (multipart/form-data) support
|
|
35
|
+
- Automatic JSON serialization/deserialization
|
|
36
|
+
- Base URL configuration
|
|
37
|
+
- Custom headers and query parameters
|
|
38
|
+
- Timeout and redirect configuration
|
|
39
|
+
- Concurrent request helpers (all, spread)
|
|
40
|
+
- Type hints for better IDE support
|
|
41
|
+
|
|
42
|
+
The client maintains default configuration that applies to all requests,
|
|
43
|
+
which can be overridden on a per-request basis. It also manages a session
|
|
44
|
+
with connection pooling for optimal performance.
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
defaults (Defaults): Default configuration for all requests
|
|
48
|
+
interceptors (InterceptorManager): Manager for request/response interceptors
|
|
49
|
+
_request_handler (RequestHandler): Internal handler for request execution
|
|
50
|
+
|
|
51
|
+
Example:
|
|
52
|
+
>>> client = AtomHTTP({'baseURL': 'https://api.example.com', 'timeout': 30})
|
|
53
|
+
>>> response = await client.get('/users', params={'page': 1})
|
|
54
|
+
>>> print(response.status, response.data)
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, config: Optional[Union[RequestConfig, Dict]] = None):
|
|
58
|
+
"""
|
|
59
|
+
Initialize a new AtomHTTP client with optional configuration.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
config (Optional[Union[RequestConfig, Dict]]): Initial configuration
|
|
63
|
+
for the client. Can be a RequestConfig object or a dictionary
|
|
64
|
+
with configuration keys. If None, defaults are used.
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
>>> # Using dictionary
|
|
68
|
+
>>> client = AtomHTTP({'baseURL': 'https://api.example.com', 'timeout': 10})
|
|
69
|
+
>>>
|
|
70
|
+
>>> # Using RequestConfig object
|
|
71
|
+
>>> config = RequestConfig(baseURL='https://api.example.com', timeout=10)
|
|
72
|
+
>>> client = AtomHTTP(config)
|
|
73
|
+
"""
|
|
74
|
+
# Initialize default configuration
|
|
75
|
+
self.defaults = Defaults()
|
|
76
|
+
|
|
77
|
+
# Apply user configuration if provided
|
|
78
|
+
if config:
|
|
79
|
+
if isinstance(config, dict):
|
|
80
|
+
config_obj = RequestConfig(**config)
|
|
81
|
+
else:
|
|
82
|
+
config_obj = config
|
|
83
|
+
self.defaults.update(config_obj)
|
|
84
|
+
|
|
85
|
+
# Initialize interceptor manager and request handler
|
|
86
|
+
self.interceptors = InterceptorManager()
|
|
87
|
+
self._request_handler = RequestHandler(self.defaults, self.interceptors)
|
|
88
|
+
|
|
89
|
+
async def request(self, config: Union[RequestConfig, Dict]) -> Response:
|
|
90
|
+
"""
|
|
91
|
+
Make an HTTP request with the provided configuration.
|
|
92
|
+
|
|
93
|
+
This is the core method for making requests. All other HTTP methods
|
|
94
|
+
(get, post, etc.) eventually call this method. It supports full
|
|
95
|
+
configuration including custom adapters, interceptors, and transformers.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
config (Union[RequestConfig, Dict]): Request configuration. Can be
|
|
99
|
+
a RequestConfig object or a dictionary with configuration keys.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Response: The HTTP response object containing data, status, headers
|
|
103
|
+
|
|
104
|
+
Example:
|
|
105
|
+
>>> response = await client.request({
|
|
106
|
+
... 'method': 'POST',
|
|
107
|
+
... 'url': '/users',
|
|
108
|
+
... 'data': {'name': 'John'},
|
|
109
|
+
... 'headers': {'X-Custom': 'value'}
|
|
110
|
+
... })
|
|
111
|
+
"""
|
|
112
|
+
# Convert dictionary to RequestConfig if needed
|
|
113
|
+
if isinstance(config, dict):
|
|
114
|
+
config = RequestConfig(**config)
|
|
115
|
+
|
|
116
|
+
# Merge with defaults and build full URL
|
|
117
|
+
merged_config = self._merge_config(config)
|
|
118
|
+
final_url = self._build_full_url(merged_config)
|
|
119
|
+
merged_config.url = final_url
|
|
120
|
+
|
|
121
|
+
# Execute the request through the handler
|
|
122
|
+
return await self._request_handler.execute(merged_config)
|
|
123
|
+
|
|
124
|
+
async def get(
|
|
125
|
+
self,
|
|
126
|
+
url: str,
|
|
127
|
+
params: Optional[Dict] = None,
|
|
128
|
+
response_type: str = 'json',
|
|
129
|
+
on_download_progress: Optional[Callable] = None,
|
|
130
|
+
**kwargs
|
|
131
|
+
) -> Response:
|
|
132
|
+
"""
|
|
133
|
+
Make an HTTP GET request.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
url (str): Request URL (absolute or relative to baseURL)
|
|
137
|
+
params (Optional[Dict]): Query parameters to append to URL
|
|
138
|
+
response_type (str): Expected response type ('json', 'text', 'blob',
|
|
139
|
+
'arraybuffer', 'stream')
|
|
140
|
+
on_download_progress (Optional[Callable]): Callback for download progress
|
|
141
|
+
**kwargs: Additional request configuration options
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Response: HTTP response object
|
|
145
|
+
"""
|
|
146
|
+
config = RequestConfig(
|
|
147
|
+
url=url,
|
|
148
|
+
method='GET',
|
|
149
|
+
params=params or {},
|
|
150
|
+
responseType=response_type,
|
|
151
|
+
onDownloadProgress=on_download_progress,
|
|
152
|
+
**kwargs
|
|
153
|
+
)
|
|
154
|
+
return await self.request(config)
|
|
155
|
+
|
|
156
|
+
async def post(
|
|
157
|
+
self,
|
|
158
|
+
url: str,
|
|
159
|
+
data: Any = None,
|
|
160
|
+
response_type: str = 'json',
|
|
161
|
+
on_upload_progress: Optional[Callable] = None,
|
|
162
|
+
on_download_progress: Optional[Callable] = None,
|
|
163
|
+
**kwargs
|
|
164
|
+
) -> Response:
|
|
165
|
+
"""
|
|
166
|
+
Make an HTTP POST request.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
url (str): Request URL
|
|
170
|
+
data (Any): Request body (dict for JSON, FormData, bytes, etc.)
|
|
171
|
+
response_type (str): Expected response type
|
|
172
|
+
on_upload_progress (Optional[Callable]): Callback for upload progress
|
|
173
|
+
on_download_progress (Optional[Callable]): Callback for download progress
|
|
174
|
+
**kwargs: Additional request configuration options
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Response: HTTP response object
|
|
178
|
+
"""
|
|
179
|
+
config = RequestConfig(
|
|
180
|
+
url=url,
|
|
181
|
+
method='POST',
|
|
182
|
+
data=data,
|
|
183
|
+
responseType=response_type,
|
|
184
|
+
onUploadProgress=on_upload_progress,
|
|
185
|
+
onDownloadProgress=on_download_progress,
|
|
186
|
+
**kwargs
|
|
187
|
+
)
|
|
188
|
+
return await self.request(config)
|
|
189
|
+
|
|
190
|
+
async def put(
|
|
191
|
+
self,
|
|
192
|
+
url: str,
|
|
193
|
+
data: Any = None,
|
|
194
|
+
response_type: str = 'json',
|
|
195
|
+
on_upload_progress: Optional[Callable] = None,
|
|
196
|
+
on_download_progress: Optional[Callable] = None,
|
|
197
|
+
**kwargs
|
|
198
|
+
) -> Response:
|
|
199
|
+
"""
|
|
200
|
+
Make an HTTP PUT request.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
url (str): Request URL
|
|
204
|
+
data (Any): Request body
|
|
205
|
+
response_type (str): Expected response type
|
|
206
|
+
on_upload_progress (Optional[Callable]): Callback for upload progress
|
|
207
|
+
on_download_progress (Optional[Callable]): Callback for download progress
|
|
208
|
+
**kwargs: Additional request configuration options
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Response: HTTP response object
|
|
212
|
+
"""
|
|
213
|
+
config = RequestConfig(
|
|
214
|
+
url=url,
|
|
215
|
+
method='PUT',
|
|
216
|
+
data=data,
|
|
217
|
+
responseType=response_type,
|
|
218
|
+
onUploadProgress=on_upload_progress,
|
|
219
|
+
onDownloadProgress=on_download_progress,
|
|
220
|
+
**kwargs
|
|
221
|
+
)
|
|
222
|
+
return await self.request(config)
|
|
223
|
+
|
|
224
|
+
async def patch(
|
|
225
|
+
self,
|
|
226
|
+
url: str,
|
|
227
|
+
data: Any = None,
|
|
228
|
+
response_type: str = 'json',
|
|
229
|
+
on_upload_progress: Optional[Callable] = None,
|
|
230
|
+
on_download_progress: Optional[Callable] = None,
|
|
231
|
+
**kwargs
|
|
232
|
+
) -> Response:
|
|
233
|
+
"""
|
|
234
|
+
Make an HTTP PATCH request.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
url (str): Request URL
|
|
238
|
+
data (Any): Request body
|
|
239
|
+
response_type (str): Expected response type
|
|
240
|
+
on_upload_progress (Optional[Callable]): Callback for upload progress
|
|
241
|
+
on_download_progress (Optional[Callable]): Callback for download progress
|
|
242
|
+
**kwargs: Additional request configuration options
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Response: HTTP response object
|
|
246
|
+
"""
|
|
247
|
+
config = RequestConfig(
|
|
248
|
+
url=url,
|
|
249
|
+
method='PATCH',
|
|
250
|
+
data=data,
|
|
251
|
+
responseType=response_type,
|
|
252
|
+
onUploadProgress=on_upload_progress,
|
|
253
|
+
onDownloadProgress=on_download_progress,
|
|
254
|
+
**kwargs
|
|
255
|
+
)
|
|
256
|
+
return await self.request(config)
|
|
257
|
+
|
|
258
|
+
async def delete(
|
|
259
|
+
self,
|
|
260
|
+
url: str,
|
|
261
|
+
response_type: str = 'json',
|
|
262
|
+
on_download_progress: Optional[Callable] = None,
|
|
263
|
+
**kwargs
|
|
264
|
+
) -> Response:
|
|
265
|
+
"""
|
|
266
|
+
Make an HTTP DELETE request.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
url (str): Request URL
|
|
270
|
+
response_type (str): Expected response type
|
|
271
|
+
on_download_progress (Optional[Callable]): Callback for download progress
|
|
272
|
+
**kwargs: Additional request configuration options
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Response: HTTP response object
|
|
276
|
+
"""
|
|
277
|
+
config = RequestConfig(
|
|
278
|
+
url=url,
|
|
279
|
+
method='DELETE',
|
|
280
|
+
responseType=response_type,
|
|
281
|
+
onDownloadProgress=on_download_progress,
|
|
282
|
+
**kwargs
|
|
283
|
+
)
|
|
284
|
+
return await self.request(config)
|
|
285
|
+
|
|
286
|
+
async def head(
|
|
287
|
+
self,
|
|
288
|
+
url: str,
|
|
289
|
+
response_type: str = 'json',
|
|
290
|
+
on_download_progress: Optional[Callable] = None,
|
|
291
|
+
**kwargs
|
|
292
|
+
) -> Response:
|
|
293
|
+
"""
|
|
294
|
+
Make an HTTP HEAD request.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
url (str): Request URL
|
|
298
|
+
response_type (str): Expected response type
|
|
299
|
+
on_download_progress (Optional[Callable]): Callback for download progress
|
|
300
|
+
**kwargs: Additional request configuration options
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
Response: HTTP response object
|
|
304
|
+
"""
|
|
305
|
+
config = RequestConfig(
|
|
306
|
+
url=url,
|
|
307
|
+
method='HEAD',
|
|
308
|
+
responseType=response_type,
|
|
309
|
+
onDownloadProgress=on_download_progress,
|
|
310
|
+
**kwargs
|
|
311
|
+
)
|
|
312
|
+
return await self.request(config)
|
|
313
|
+
|
|
314
|
+
async def options(
|
|
315
|
+
self,
|
|
316
|
+
url: str,
|
|
317
|
+
response_type: str = 'json',
|
|
318
|
+
on_download_progress: Optional[Callable] = None,
|
|
319
|
+
**kwargs
|
|
320
|
+
) -> Response:
|
|
321
|
+
"""
|
|
322
|
+
Make an HTTP OPTIONS request.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
url (str): Request URL
|
|
326
|
+
response_type (str): Expected response type
|
|
327
|
+
on_download_progress (Optional[Callable]): Callback for download progress
|
|
328
|
+
**kwargs: Additional request configuration options
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
Response: HTTP response object
|
|
332
|
+
"""
|
|
333
|
+
config = RequestConfig(
|
|
334
|
+
url=url,
|
|
335
|
+
method='OPTIONS',
|
|
336
|
+
responseType=response_type,
|
|
337
|
+
onDownloadProgress=on_download_progress,
|
|
338
|
+
**kwargs
|
|
339
|
+
)
|
|
340
|
+
return await self.request(config)
|
|
341
|
+
|
|
342
|
+
def _merge_config(self, config: RequestConfig) -> RequestConfig:
|
|
343
|
+
"""
|
|
344
|
+
Merge user configuration with defaults.
|
|
345
|
+
|
|
346
|
+
This method combines the provided request configuration with the
|
|
347
|
+
client's default configuration. Headers and params are merged
|
|
348
|
+
(custom values override defaults), while other fields are replaced
|
|
349
|
+
if present.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
config (RequestConfig): User-provided request configuration
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
RequestConfig: Merged configuration
|
|
356
|
+
"""
|
|
357
|
+
# Start with a copy of defaults
|
|
358
|
+
merged = RequestConfig(**self.defaults.to_dict())
|
|
359
|
+
|
|
360
|
+
# Merge all attributes from user config
|
|
361
|
+
config_dict = config.to_dict()
|
|
362
|
+
for key, value in config_dict.items():
|
|
363
|
+
if value is not None:
|
|
364
|
+
if key == 'headers' and isinstance(value, dict):
|
|
365
|
+
if merged.headers is None:
|
|
366
|
+
merged.headers = {}
|
|
367
|
+
merged.headers.update(value)
|
|
368
|
+
elif key == 'params' and isinstance(value, dict):
|
|
369
|
+
if merged.params is None:
|
|
370
|
+
merged.params = {}
|
|
371
|
+
merged.params.update(value)
|
|
372
|
+
else:
|
|
373
|
+
setattr(merged, key, value)
|
|
374
|
+
|
|
375
|
+
# Ensure baseURL is properly inherited from defaults
|
|
376
|
+
if not merged.baseURL and hasattr(self.defaults, 'baseURL') and self.defaults.baseURL:
|
|
377
|
+
merged.baseURL = self.defaults.baseURL
|
|
378
|
+
|
|
379
|
+
return merged
|
|
380
|
+
|
|
381
|
+
def _build_full_url(self, config: RequestConfig) -> str:
|
|
382
|
+
"""
|
|
383
|
+
Build a complete URL from baseURL, path, and query parameters.
|
|
384
|
+
|
|
385
|
+
This method handles:
|
|
386
|
+
- Absolute URLs (ignores baseURL)
|
|
387
|
+
- Relative URLs (combines with baseURL)
|
|
388
|
+
- Query parameter merging (preserves existing query string)
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
config (RequestConfig): Configuration containing URL and parameters
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
str: Complete URL with base and query parameters
|
|
395
|
+
|
|
396
|
+
Raises:
|
|
397
|
+
AtomHTTPRequestError: If relative URL is used without baseURL
|
|
398
|
+
"""
|
|
399
|
+
url = config.url
|
|
400
|
+
base_url = config.baseURL
|
|
401
|
+
|
|
402
|
+
# Combine baseURL and relative path
|
|
403
|
+
if base_url and url:
|
|
404
|
+
if url.startswith(('http://', 'https://')):
|
|
405
|
+
final_url = url
|
|
406
|
+
else:
|
|
407
|
+
base = base_url.rstrip('/')
|
|
408
|
+
path = url.lstrip('/')
|
|
409
|
+
final_url = f"{base}/{path}"
|
|
410
|
+
elif base_url and not url:
|
|
411
|
+
final_url = base_url
|
|
412
|
+
elif url:
|
|
413
|
+
if url.startswith(('http://', 'https://')):
|
|
414
|
+
final_url = url
|
|
415
|
+
else:
|
|
416
|
+
raise AtomHTTPRequestError(
|
|
417
|
+
f"Cannot make request to relative URL '{url}' without baseURL",
|
|
418
|
+
request=config,
|
|
419
|
+
config=config
|
|
420
|
+
)
|
|
421
|
+
else:
|
|
422
|
+
raise AtomHTTPRequestError("URL is required", request=config, config=config)
|
|
423
|
+
|
|
424
|
+
# Append or merge query parameters
|
|
425
|
+
if config.params and len(config.params) > 0:
|
|
426
|
+
if '?' in final_url:
|
|
427
|
+
base_part, existing_params = final_url.split('?', 1)
|
|
428
|
+
existing_dict = parse_qs(existing_params, keep_blank_values=True)
|
|
429
|
+
for key, value in config.params.items():
|
|
430
|
+
existing_dict[key] = [str(value)]
|
|
431
|
+
flat_params = {}
|
|
432
|
+
for key, values in existing_dict.items():
|
|
433
|
+
flat_params[key] = values[0] if len(values) == 1 else values
|
|
434
|
+
query_string = urlencode(flat_params, doseq=True)
|
|
435
|
+
final_url = f"{base_part}?{query_string}"
|
|
436
|
+
else:
|
|
437
|
+
query_string = urlencode(config.params)
|
|
438
|
+
final_url = f"{final_url}?{query_string}"
|
|
439
|
+
|
|
440
|
+
return final_url
|
|
441
|
+
|
|
442
|
+
@staticmethod
|
|
443
|
+
def all(requests: List[asyncio.Task]) -> asyncio.Future:
|
|
444
|
+
"""
|
|
445
|
+
Execute multiple requests concurrently.
|
|
446
|
+
|
|
447
|
+
This method waits for all provided coroutines/tasks to complete
|
|
448
|
+
and returns their results as a list. Similar to Promise.all() in
|
|
449
|
+
JavaScript.
|
|
450
|
+
|
|
451
|
+
Args:
|
|
452
|
+
requests (List[asyncio.Task]): List of coroutines or tasks to execute
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
asyncio.Future: Future that resolves to list of all responses
|
|
456
|
+
|
|
457
|
+
Example:
|
|
458
|
+
>>> tasks = [
|
|
459
|
+
... client.get('/users/1'),
|
|
460
|
+
... client.get('/users/2'),
|
|
461
|
+
... client.get('/users/3')
|
|
462
|
+
... ]
|
|
463
|
+
>>> responses = await AtomHTTP.all(tasks)
|
|
464
|
+
"""
|
|
465
|
+
return asyncio.gather(*requests)
|
|
466
|
+
|
|
467
|
+
@staticmethod
|
|
468
|
+
async def spread(callback: Callable, *responses):
|
|
469
|
+
"""
|
|
470
|
+
Spread array of responses to callback function arguments.
|
|
471
|
+
|
|
472
|
+
This method takes a list of responses and passes them as individual
|
|
473
|
+
arguments to the callback function. Similar to axios.spread().
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
callback (Callable): Function that receives individual response arguments
|
|
477
|
+
*responses: Variable number of response objects
|
|
478
|
+
|
|
479
|
+
Returns:
|
|
480
|
+
Any: Result of the callback function
|
|
481
|
+
|
|
482
|
+
Example:
|
|
483
|
+
>>> def process(res1, res2, res3):
|
|
484
|
+
... return [res1.status, res2.status, res3.status]
|
|
485
|
+
>>>
|
|
486
|
+
>>> responses = await AtomHTTP.all(tasks)
|
|
487
|
+
>>> statuses = await AtomHTTP.spread(process, *responses)
|
|
488
|
+
"""
|
|
489
|
+
return callback(*responses)
|
|
490
|
+
|
|
491
|
+
def get_uri(self, config: Union[RequestConfig, Dict]) -> str:
|
|
492
|
+
"""
|
|
493
|
+
Generate the full URI for a request configuration without executing it.
|
|
494
|
+
|
|
495
|
+
This method is useful for debugging or when you need to see the
|
|
496
|
+
final URL that would be used for a request.
|
|
497
|
+
|
|
498
|
+
Args:
|
|
499
|
+
config (Union[RequestConfig, Dict]): Request configuration
|
|
500
|
+
|
|
501
|
+
Returns:
|
|
502
|
+
str: Full URI with baseURL and query parameters applied
|
|
503
|
+
|
|
504
|
+
Example:
|
|
505
|
+
>>> uri = client.get_uri({
|
|
506
|
+
... 'url': '/users',
|
|
507
|
+
... 'params': {'page': 1, 'limit': 10}
|
|
508
|
+
... })
|
|
509
|
+
>>> print(uri)
|
|
510
|
+
'https://api.example.com/users?page=1&limit=10'
|
|
511
|
+
"""
|
|
512
|
+
# Convert dictionary to RequestConfig if needed
|
|
513
|
+
if isinstance(config, dict):
|
|
514
|
+
config = RequestConfig(**config)
|
|
515
|
+
|
|
516
|
+
# Merge with defaults and build URL
|
|
517
|
+
if hasattr(self, 'defaults'):
|
|
518
|
+
merged = self._merge_config(config)
|
|
519
|
+
else:
|
|
520
|
+
merged = config
|
|
521
|
+
|
|
522
|
+
return self._build_full_url(merged)
|
|
523
|
+
|
|
524
|
+
def is_atomhttp_error(self, error: Exception) -> bool:
|
|
525
|
+
"""
|
|
526
|
+
Check if an exception is a AtomHTTP error.
|
|
527
|
+
|
|
528
|
+
This method is useful for error handling to distinguish between
|
|
529
|
+
AtomHTTP-specific errors and other exceptions.
|
|
530
|
+
|
|
531
|
+
Args:
|
|
532
|
+
error (Exception): Exception to check
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
bool: True if the error is a AtomHTTP error, False otherwise
|
|
536
|
+
|
|
537
|
+
Example:
|
|
538
|
+
>>> try:
|
|
539
|
+
... await client.get('https://invalid.com')
|
|
540
|
+
... except Exception as e:
|
|
541
|
+
... if client.is_atomhttp_error(e):
|
|
542
|
+
... print(f"AtomHTTP error: {e.code}")
|
|
543
|
+
"""
|
|
544
|
+
return isinstance(error, AtomHTTPError)
|
|
545
|
+
|
|
546
|
+
@staticmethod
|
|
547
|
+
def FormData() -> FormData:
|
|
548
|
+
"""
|
|
549
|
+
Create a new FormData instance for multipart/form-data requests.
|
|
550
|
+
|
|
551
|
+
Returns:
|
|
552
|
+
FormData: New FormData object for building form data with files
|
|
553
|
+
|
|
554
|
+
Example:
|
|
555
|
+
>>> form = AtomHTTP.FormData()
|
|
556
|
+
>>> form.append('username', 'john')
|
|
557
|
+
>>> form.append('avatar', open('photo.jpg', 'rb'), filename='photo.jpg')
|
|
558
|
+
>>> response = await client.post('/upload', data=form)
|
|
559
|
+
"""
|
|
560
|
+
return FormData()
|
|
561
|
+
|
|
562
|
+
async def close(self) -> None:
|
|
563
|
+
"""
|
|
564
|
+
Close the client and clean up resources.
|
|
565
|
+
|
|
566
|
+
This method should be called when the client is no longer needed
|
|
567
|
+
to properly close connections and release system resources.
|
|
568
|
+
|
|
569
|
+
Example:
|
|
570
|
+
>>> client = AtomHTTP()
|
|
571
|
+
>>> try:
|
|
572
|
+
... response = await client.get('/data')
|
|
573
|
+
... finally:
|
|
574
|
+
... await client.close()
|
|
575
|
+
"""
|
|
576
|
+
if hasattr(self._request_handler, 'default_adapter'):
|
|
577
|
+
await self._request_handler.default_adapter.close()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .request import RequestHandler
|
|
2
|
+
from .response import Response
|
|
3
|
+
from .config import RequestConfig
|
|
4
|
+
from .defaults import Defaults
|
|
5
|
+
from .form_data import FormData, FormDataItem
|
|
6
|
+
from .adapters import HTTPAdapter, MockAdapter, ProgressTracker, ProgressReader
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
'RequestHandler',
|
|
10
|
+
'Response',
|
|
11
|
+
'RequestConfig',
|
|
12
|
+
'Defaults',
|
|
13
|
+
'FormData',
|
|
14
|
+
'FormDataItem',
|
|
15
|
+
'HTTPAdapter',
|
|
16
|
+
'MockAdapter',
|
|
17
|
+
'ProgressTracker',
|
|
18
|
+
'ProgressReader'
|
|
19
|
+
]
|