hammad-python 0.0.30__py3-none-any.whl → 0.0.32__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.
- ham/__init__.py +200 -0
- {hammad_python-0.0.30.dist-info → hammad_python-0.0.32.dist-info}/METADATA +6 -32
- hammad_python-0.0.32.dist-info/RECORD +6 -0
- hammad/__init__.py +0 -84
- hammad/_internal.py +0 -256
- hammad/_main.py +0 -226
- hammad/cache/__init__.py +0 -40
- hammad/cache/base_cache.py +0 -181
- hammad/cache/cache.py +0 -169
- hammad/cache/decorators.py +0 -261
- hammad/cache/file_cache.py +0 -80
- hammad/cache/ttl_cache.py +0 -74
- hammad/cli/__init__.py +0 -33
- hammad/cli/animations.py +0 -573
- hammad/cli/plugins.py +0 -867
- hammad/cli/styles/__init__.py +0 -55
- hammad/cli/styles/settings.py +0 -139
- hammad/cli/styles/types.py +0 -358
- hammad/cli/styles/utils.py +0 -634
- hammad/data/__init__.py +0 -90
- hammad/data/collections/__init__.py +0 -49
- hammad/data/collections/collection.py +0 -326
- hammad/data/collections/indexes/__init__.py +0 -37
- hammad/data/collections/indexes/qdrant/__init__.py +0 -1
- hammad/data/collections/indexes/qdrant/index.py +0 -723
- hammad/data/collections/indexes/qdrant/settings.py +0 -94
- hammad/data/collections/indexes/qdrant/utils.py +0 -210
- hammad/data/collections/indexes/tantivy/__init__.py +0 -1
- hammad/data/collections/indexes/tantivy/index.py +0 -426
- hammad/data/collections/indexes/tantivy/settings.py +0 -40
- hammad/data/collections/indexes/tantivy/utils.py +0 -176
- hammad/data/configurations/__init__.py +0 -35
- hammad/data/configurations/configuration.py +0 -564
- hammad/data/models/__init__.py +0 -50
- hammad/data/models/extensions/__init__.py +0 -4
- hammad/data/models/extensions/pydantic/__init__.py +0 -42
- hammad/data/models/extensions/pydantic/converters.py +0 -759
- hammad/data/models/fields.py +0 -546
- hammad/data/models/model.py +0 -1078
- hammad/data/models/utils.py +0 -280
- hammad/data/sql/__init__.py +0 -24
- hammad/data/sql/database.py +0 -576
- hammad/data/sql/types.py +0 -127
- hammad/data/types/__init__.py +0 -75
- hammad/data/types/file.py +0 -431
- hammad/data/types/multimodal/__init__.py +0 -36
- hammad/data/types/multimodal/audio.py +0 -200
- hammad/data/types/multimodal/image.py +0 -182
- hammad/data/types/text.py +0 -1308
- hammad/formatting/__init__.py +0 -33
- hammad/formatting/json/__init__.py +0 -27
- hammad/formatting/json/converters.py +0 -158
- hammad/formatting/text/__init__.py +0 -63
- hammad/formatting/text/converters.py +0 -723
- hammad/formatting/text/markdown.py +0 -131
- hammad/formatting/yaml/__init__.py +0 -26
- hammad/formatting/yaml/converters.py +0 -5
- hammad/genai/__init__.py +0 -217
- hammad/genai/a2a/__init__.py +0 -32
- hammad/genai/a2a/workers.py +0 -552
- hammad/genai/agents/__init__.py +0 -59
- hammad/genai/agents/agent.py +0 -1973
- hammad/genai/agents/run.py +0 -1024
- hammad/genai/agents/types/__init__.py +0 -42
- hammad/genai/agents/types/agent_context.py +0 -13
- hammad/genai/agents/types/agent_event.py +0 -128
- hammad/genai/agents/types/agent_hooks.py +0 -220
- hammad/genai/agents/types/agent_messages.py +0 -31
- hammad/genai/agents/types/agent_response.py +0 -125
- hammad/genai/agents/types/agent_stream.py +0 -327
- hammad/genai/graphs/__init__.py +0 -125
- hammad/genai/graphs/_utils.py +0 -190
- hammad/genai/graphs/base.py +0 -1828
- hammad/genai/graphs/plugins.py +0 -316
- hammad/genai/graphs/types.py +0 -638
- hammad/genai/models/__init__.py +0 -1
- hammad/genai/models/embeddings/__init__.py +0 -43
- hammad/genai/models/embeddings/model.py +0 -226
- hammad/genai/models/embeddings/run.py +0 -163
- hammad/genai/models/embeddings/types/__init__.py +0 -37
- hammad/genai/models/embeddings/types/embedding_model_name.py +0 -75
- hammad/genai/models/embeddings/types/embedding_model_response.py +0 -76
- hammad/genai/models/embeddings/types/embedding_model_run_params.py +0 -66
- hammad/genai/models/embeddings/types/embedding_model_settings.py +0 -47
- hammad/genai/models/language/__init__.py +0 -57
- hammad/genai/models/language/model.py +0 -1098
- hammad/genai/models/language/run.py +0 -878
- hammad/genai/models/language/types/__init__.py +0 -40
- hammad/genai/models/language/types/language_model_instructor_mode.py +0 -47
- hammad/genai/models/language/types/language_model_messages.py +0 -28
- hammad/genai/models/language/types/language_model_name.py +0 -239
- hammad/genai/models/language/types/language_model_request.py +0 -127
- hammad/genai/models/language/types/language_model_response.py +0 -217
- hammad/genai/models/language/types/language_model_response_chunk.py +0 -56
- hammad/genai/models/language/types/language_model_settings.py +0 -89
- hammad/genai/models/language/types/language_model_stream.py +0 -600
- hammad/genai/models/language/utils/__init__.py +0 -28
- hammad/genai/models/language/utils/requests.py +0 -421
- hammad/genai/models/language/utils/structured_outputs.py +0 -135
- hammad/genai/models/model_provider.py +0 -4
- hammad/genai/models/multimodal.py +0 -47
- hammad/genai/models/reranking.py +0 -26
- hammad/genai/types/__init__.py +0 -1
- hammad/genai/types/base.py +0 -215
- hammad/genai/types/history.py +0 -290
- hammad/genai/types/tools.py +0 -507
- hammad/logging/__init__.py +0 -35
- hammad/logging/decorators.py +0 -834
- hammad/logging/logger.py +0 -1018
- hammad/mcp/__init__.py +0 -53
- hammad/mcp/client/__init__.py +0 -35
- hammad/mcp/client/client.py +0 -624
- hammad/mcp/client/client_service.py +0 -400
- hammad/mcp/client/settings.py +0 -178
- hammad/mcp/servers/__init__.py +0 -26
- hammad/mcp/servers/launcher.py +0 -1161
- hammad/runtime/__init__.py +0 -32
- hammad/runtime/decorators.py +0 -142
- hammad/runtime/run.py +0 -299
- hammad/service/__init__.py +0 -49
- hammad/service/create.py +0 -527
- hammad/service/decorators.py +0 -283
- hammad/types.py +0 -288
- hammad/typing/__init__.py +0 -435
- hammad/web/__init__.py +0 -43
- hammad/web/http/__init__.py +0 -1
- hammad/web/http/client.py +0 -944
- hammad/web/models.py +0 -275
- hammad/web/openapi/__init__.py +0 -1
- hammad/web/openapi/client.py +0 -740
- hammad/web/search/__init__.py +0 -1
- hammad/web/search/client.py +0 -1023
- hammad/web/utils.py +0 -472
- hammad_python-0.0.30.dist-info/RECORD +0 -135
- {hammad → ham}/py.typed +0 -0
- {hammad_python-0.0.30.dist-info → hammad_python-0.0.32.dist-info}/WHEEL +0 -0
- {hammad_python-0.0.30.dist-info → hammad_python-0.0.32.dist-info}/licenses/LICENSE +0 -0
hammad/web/http/client.py
DELETED
@@ -1,944 +0,0 @@
|
|
1
|
-
"""hammad.http.client"""
|
2
|
-
|
3
|
-
from __future__ import annotations
|
4
|
-
|
5
|
-
import asyncio
|
6
|
-
import json
|
7
|
-
from typing import Any, Dict, List, Literal, Optional, Union, overload
|
8
|
-
from urllib.parse import urljoin, urlparse
|
9
|
-
|
10
|
-
import httpx
|
11
|
-
from pydantic import BaseModel, Field, field_validator
|
12
|
-
|
13
|
-
__all__ = (
|
14
|
-
"HttpError",
|
15
|
-
"HttpRequest",
|
16
|
-
"HttpResponse",
|
17
|
-
"HttpClient",
|
18
|
-
)
|
19
|
-
|
20
|
-
|
21
|
-
class HttpError(Exception):
|
22
|
-
"""Custom exception for HTTP toolkit errors with semantic feedback."""
|
23
|
-
|
24
|
-
def __init__(
|
25
|
-
self,
|
26
|
-
message: str,
|
27
|
-
suggestion: str = "",
|
28
|
-
context: Optional[Dict[str, Any]] = None,
|
29
|
-
status_code: Optional[int] = None,
|
30
|
-
response_text: Optional[str] = None,
|
31
|
-
):
|
32
|
-
self.message = message
|
33
|
-
self.suggestion = suggestion
|
34
|
-
self.context = context or {}
|
35
|
-
self.status_code = status_code
|
36
|
-
self.response_text = response_text
|
37
|
-
super().__init__(self.message)
|
38
|
-
|
39
|
-
def get_full_error(self) -> str:
|
40
|
-
"""Get the full error message with suggestion and context."""
|
41
|
-
error_msg = f"HTTP ERROR: {self.message}"
|
42
|
-
if self.status_code:
|
43
|
-
error_msg += f" (Status: {self.status_code})"
|
44
|
-
if self.suggestion:
|
45
|
-
error_msg += f"\nSUGGESTION: {self.suggestion}"
|
46
|
-
if self.context:
|
47
|
-
error_msg += f"\nCONTEXT: {self.context}"
|
48
|
-
if self.response_text:
|
49
|
-
error_msg += f"\nRESPONSE: {self.response_text[:500]}..."
|
50
|
-
return error_msg
|
51
|
-
|
52
|
-
def __str__(self) -> str:
|
53
|
-
"""Return the full error message when converting to string."""
|
54
|
-
return self.get_full_error()
|
55
|
-
|
56
|
-
|
57
|
-
class HttpRequest(BaseModel):
|
58
|
-
"""Model for HTTP request configuration with semantic parameterization."""
|
59
|
-
|
60
|
-
method: Literal["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"] = "GET"
|
61
|
-
url: str
|
62
|
-
headers: Optional[Dict[str, str]] = None
|
63
|
-
params: Optional[Dict[str, Any]] = None
|
64
|
-
json_data: Optional[Dict[str, Any]] = Field(None, alias="json")
|
65
|
-
form_data: Optional[Dict[str, Any]] = None
|
66
|
-
content: Optional[Union[str, bytes]] = None
|
67
|
-
timeout: Optional[float] = 30.0
|
68
|
-
follow_redirects: bool = True
|
69
|
-
|
70
|
-
# Semantic authentication parameters
|
71
|
-
api_key: Optional[str] = None
|
72
|
-
api_key_header: str = "X-API-Key"
|
73
|
-
bearer_token: Optional[str] = None
|
74
|
-
basic_auth: Optional[tuple[str, str]] = None
|
75
|
-
auth_header: Optional[str] = None # Custom auth header value
|
76
|
-
|
77
|
-
# Common convenience parameters
|
78
|
-
user_agent: Optional[str] = None
|
79
|
-
content_type: Optional[str] = None
|
80
|
-
accept: Optional[str] = None
|
81
|
-
|
82
|
-
# Advanced options
|
83
|
-
retry_attempts: int = 0
|
84
|
-
retry_delay: float = 1.0
|
85
|
-
|
86
|
-
def get_effective_headers(self) -> Dict[str, str]:
|
87
|
-
"""Get the effective headers with semantic parameters applied."""
|
88
|
-
effective_headers = (self.headers or {}).copy()
|
89
|
-
|
90
|
-
# Apply semantic authentication
|
91
|
-
if self.api_key:
|
92
|
-
effective_headers[self.api_key_header] = self.api_key
|
93
|
-
|
94
|
-
if self.bearer_token:
|
95
|
-
effective_headers["Authorization"] = f"Bearer {self.bearer_token}"
|
96
|
-
|
97
|
-
if self.basic_auth:
|
98
|
-
import base64
|
99
|
-
|
100
|
-
credentials = base64.b64encode(
|
101
|
-
f"{self.basic_auth[0]}:{self.basic_auth[1]}".encode()
|
102
|
-
).decode()
|
103
|
-
effective_headers["Authorization"] = f"Basic {credentials}"
|
104
|
-
|
105
|
-
if self.auth_header:
|
106
|
-
effective_headers["Authorization"] = self.auth_header
|
107
|
-
|
108
|
-
# Apply convenience headers
|
109
|
-
if self.user_agent:
|
110
|
-
effective_headers["User-Agent"] = self.user_agent
|
111
|
-
|
112
|
-
if self.content_type:
|
113
|
-
effective_headers["Content-Type"] = self.content_type
|
114
|
-
|
115
|
-
if self.accept:
|
116
|
-
effective_headers["Accept"] = self.accept
|
117
|
-
|
118
|
-
return effective_headers
|
119
|
-
|
120
|
-
@field_validator("url")
|
121
|
-
@classmethod
|
122
|
-
def validate_url(cls, v):
|
123
|
-
"""Validate URL format."""
|
124
|
-
if not v or not v.strip():
|
125
|
-
raise ValueError("URL cannot be empty")
|
126
|
-
|
127
|
-
parsed = urlparse(v)
|
128
|
-
if not parsed.scheme:
|
129
|
-
raise ValueError("URL must include scheme (http:// or https://)")
|
130
|
-
if not parsed.netloc:
|
131
|
-
raise ValueError("URL must include domain")
|
132
|
-
|
133
|
-
return v.strip()
|
134
|
-
|
135
|
-
@field_validator("method", mode="before")
|
136
|
-
@classmethod
|
137
|
-
def validate_method(cls, v):
|
138
|
-
"""Validate HTTP method."""
|
139
|
-
return v.upper()
|
140
|
-
|
141
|
-
|
142
|
-
class HttpResponse(BaseModel):
|
143
|
-
"""Model for HTTP response data."""
|
144
|
-
|
145
|
-
status_code: int
|
146
|
-
headers: Dict[str, str]
|
147
|
-
content: Union[str, bytes]
|
148
|
-
json_data: Optional[Union[Dict[str, Any], List[Any], str, int, float, bool]] = None
|
149
|
-
url: str
|
150
|
-
elapsed_ms: float
|
151
|
-
|
152
|
-
@property
|
153
|
-
def is_success(self) -> bool:
|
154
|
-
"""Check if response indicates success (2xx status)."""
|
155
|
-
return 200 <= self.status_code < 300
|
156
|
-
|
157
|
-
@property
|
158
|
-
def is_redirect(self) -> bool:
|
159
|
-
"""Check if response is a redirect (3xx status)."""
|
160
|
-
return 300 <= self.status_code < 400
|
161
|
-
|
162
|
-
@property
|
163
|
-
def is_client_error(self) -> bool:
|
164
|
-
"""Check if response is a client error (4xx status)."""
|
165
|
-
return 400 <= self.status_code < 500
|
166
|
-
|
167
|
-
@property
|
168
|
-
def is_server_error(self) -> bool:
|
169
|
-
"""Check if response is a server error (5xx status)."""
|
170
|
-
return 500 <= self.status_code < 600
|
171
|
-
|
172
|
-
|
173
|
-
class AsyncHttpClient:
|
174
|
-
"""
|
175
|
-
Base HTTP toolkit for making HTTP requests with clean type hints and semantic error handling.
|
176
|
-
|
177
|
-
This class provides a clean, well-typed interface for HTTP operations using httpx.
|
178
|
-
It includes semantic error handling and validation to provide meaningful feedback.
|
179
|
-
"""
|
180
|
-
|
181
|
-
def __init__(
|
182
|
-
self,
|
183
|
-
base_url: Optional[str] = None,
|
184
|
-
default_headers: Optional[Dict[str, str]] = None,
|
185
|
-
timeout: float = 30.0,
|
186
|
-
follow_redirects: bool = True,
|
187
|
-
verify_ssl: bool = True,
|
188
|
-
# Semantic authentication parameters
|
189
|
-
api_key: Optional[str] = None,
|
190
|
-
api_key_header: str = "X-API-Key",
|
191
|
-
bearer_token: Optional[str] = None,
|
192
|
-
basic_auth: Optional[tuple[str, str]] = None,
|
193
|
-
user_agent: Optional[str] = None,
|
194
|
-
):
|
195
|
-
"""
|
196
|
-
Initialize the HTTP toolkit.
|
197
|
-
|
198
|
-
Args:
|
199
|
-
base_url: Base URL for all requests (optional)
|
200
|
-
default_headers: Default headers to include in all requests
|
201
|
-
timeout: Default timeout in seconds
|
202
|
-
follow_redirects: Whether to follow redirects by default
|
203
|
-
verify_ssl: Whether to verify SSL certificates
|
204
|
-
api_key: API key for authentication
|
205
|
-
api_key_header: Header name for API key (default: X-API-Key)
|
206
|
-
bearer_token: Bearer token for Authorization header
|
207
|
-
basic_auth: Tuple of (username, password) for basic auth
|
208
|
-
user_agent: User-Agent header value
|
209
|
-
"""
|
210
|
-
self.base_url = base_url
|
211
|
-
self.default_headers = default_headers or {}
|
212
|
-
self.timeout = timeout
|
213
|
-
self.follow_redirects = follow_redirects
|
214
|
-
self.verify_ssl = verify_ssl
|
215
|
-
|
216
|
-
# Store semantic authentication parameters
|
217
|
-
self.api_key = api_key
|
218
|
-
self.api_key_header = api_key_header
|
219
|
-
self.bearer_token = bearer_token
|
220
|
-
self.basic_auth = basic_auth
|
221
|
-
self.user_agent = user_agent
|
222
|
-
|
223
|
-
# Apply semantic parameters to default headers
|
224
|
-
self._apply_semantic_headers()
|
225
|
-
|
226
|
-
# Validate base_url if provided
|
227
|
-
if self.base_url:
|
228
|
-
parsed = urlparse(self.base_url)
|
229
|
-
if not parsed.scheme or not parsed.netloc:
|
230
|
-
raise HttpError(
|
231
|
-
message=f"Invalid base URL: {self.base_url}",
|
232
|
-
suggestion="Provide a valid base URL with scheme and domain (e.g., https://api.example.com)",
|
233
|
-
context={"provided_base_url": self.base_url},
|
234
|
-
)
|
235
|
-
|
236
|
-
def _apply_semantic_headers(self) -> None:
|
237
|
-
"""Apply semantic authentication parameters to default headers."""
|
238
|
-
# API Key authentication
|
239
|
-
if self.api_key:
|
240
|
-
self.default_headers[self.api_key_header] = self.api_key
|
241
|
-
|
242
|
-
# Bearer token authentication
|
243
|
-
if self.bearer_token:
|
244
|
-
self.default_headers["Authorization"] = f"Bearer {self.bearer_token}"
|
245
|
-
|
246
|
-
# Basic authentication
|
247
|
-
if self.basic_auth:
|
248
|
-
import base64
|
249
|
-
|
250
|
-
credentials = base64.b64encode(
|
251
|
-
f"{self.basic_auth[0]}:{self.basic_auth[1]}".encode()
|
252
|
-
).decode()
|
253
|
-
self.default_headers["Authorization"] = f"Basic {credentials}"
|
254
|
-
|
255
|
-
# User-Agent
|
256
|
-
if self.user_agent:
|
257
|
-
self.default_headers["User-Agent"] = self.user_agent
|
258
|
-
|
259
|
-
def _build_url(self, url: str) -> str:
|
260
|
-
"""Build the complete URL, combining base_url if provided."""
|
261
|
-
if self.base_url:
|
262
|
-
return urljoin(self.base_url.rstrip("/") + "/", url.lstrip("/"))
|
263
|
-
return url
|
264
|
-
|
265
|
-
def _prepare_headers(
|
266
|
-
self, request_or_headers: Union[HttpRequest, Dict[str, str], None]
|
267
|
-
) -> Dict[str, str]:
|
268
|
-
"""Prepare headers by combining default headers with request-specific ones."""
|
269
|
-
combined_headers = self.default_headers.copy()
|
270
|
-
|
271
|
-
if request_or_headers is None:
|
272
|
-
# No additional headers
|
273
|
-
pass
|
274
|
-
elif isinstance(request_or_headers, HttpRequest):
|
275
|
-
# Get effective headers from the request (includes semantic parameters)
|
276
|
-
request_headers = request_or_headers.get_effective_headers()
|
277
|
-
combined_headers.update(request_headers)
|
278
|
-
elif isinstance(request_or_headers, dict):
|
279
|
-
# Backward compatibility: simple dict of headers
|
280
|
-
combined_headers.update(request_or_headers)
|
281
|
-
|
282
|
-
return combined_headers
|
283
|
-
|
284
|
-
async def _execute_with_retry(
|
285
|
-
self,
|
286
|
-
url: str,
|
287
|
-
headers: Dict[str, str],
|
288
|
-
params: Optional[Dict[str, Any]],
|
289
|
-
json_data: Optional[Dict[str, Any]],
|
290
|
-
form_data: Optional[Dict[str, Any]],
|
291
|
-
content: Optional[Union[str, bytes]],
|
292
|
-
request: HttpRequest,
|
293
|
-
) -> HttpResponse:
|
294
|
-
"""Execute HTTP request with retry logic."""
|
295
|
-
import asyncio
|
296
|
-
import time
|
297
|
-
|
298
|
-
last_exception = None
|
299
|
-
|
300
|
-
for attempt in range(request.retry_attempts + 1):
|
301
|
-
try:
|
302
|
-
async with httpx.AsyncClient(
|
303
|
-
timeout=request.timeout or self.timeout,
|
304
|
-
follow_redirects=request.follow_redirects,
|
305
|
-
verify=self.verify_ssl,
|
306
|
-
) as client:
|
307
|
-
# Record start time
|
308
|
-
start_time = time.time()
|
309
|
-
|
310
|
-
response = await client.request(
|
311
|
-
method=request.method,
|
312
|
-
url=url,
|
313
|
-
headers=headers,
|
314
|
-
params=params,
|
315
|
-
json=json_data,
|
316
|
-
data=form_data,
|
317
|
-
content=content,
|
318
|
-
)
|
319
|
-
|
320
|
-
# Calculate elapsed time
|
321
|
-
elapsed_ms = (time.time() - start_time) * 1000
|
322
|
-
|
323
|
-
# Handle response errors
|
324
|
-
self._handle_response_errors(response)
|
325
|
-
|
326
|
-
# Parse JSON if possible
|
327
|
-
json_response = None
|
328
|
-
if response.headers.get("content-type", "").startswith(
|
329
|
-
"application/json"
|
330
|
-
):
|
331
|
-
try:
|
332
|
-
json_response = response.json()
|
333
|
-
except json.JSONDecodeError:
|
334
|
-
# Not valid JSON, leave as None
|
335
|
-
pass
|
336
|
-
|
337
|
-
return HttpResponse(
|
338
|
-
status_code=response.status_code,
|
339
|
-
headers=dict(response.headers),
|
340
|
-
content=response.text,
|
341
|
-
json_data=json_response,
|
342
|
-
url=str(response.url),
|
343
|
-
elapsed_ms=elapsed_ms,
|
344
|
-
)
|
345
|
-
|
346
|
-
except (httpx.ConnectError, httpx.TimeoutException, HttpError) as e:
|
347
|
-
last_exception = e
|
348
|
-
|
349
|
-
# Don't retry on client errors (4xx) or authentication issues
|
350
|
-
if (
|
351
|
-
isinstance(e, HttpError)
|
352
|
-
and e.status_code
|
353
|
-
and 400 <= e.status_code < 500
|
354
|
-
):
|
355
|
-
raise e
|
356
|
-
|
357
|
-
# If this is the last attempt, raise the exception
|
358
|
-
if attempt == request.retry_attempts:
|
359
|
-
raise e
|
360
|
-
|
361
|
-
# Wait before retrying
|
362
|
-
if request.retry_delay > 0:
|
363
|
-
await asyncio.sleep(
|
364
|
-
request.retry_delay * (attempt + 1)
|
365
|
-
) # Exponential backoff
|
366
|
-
|
367
|
-
# This should never be reached, but just in case
|
368
|
-
if last_exception:
|
369
|
-
raise last_exception
|
370
|
-
else:
|
371
|
-
raise HttpError(
|
372
|
-
message="Request failed after all retry attempts",
|
373
|
-
suggestion="Check your network connection and the server status",
|
374
|
-
context={"url": url, "retry_attempts": request.retry_attempts},
|
375
|
-
)
|
376
|
-
|
377
|
-
def _handle_response_errors(self, response: httpx.Response) -> None:
|
378
|
-
"""Handle HTTP response errors with semantic feedback."""
|
379
|
-
if response.is_success:
|
380
|
-
return
|
381
|
-
|
382
|
-
status_code = response.status_code
|
383
|
-
|
384
|
-
# Get response text safely
|
385
|
-
try:
|
386
|
-
response_text = response.text
|
387
|
-
except Exception:
|
388
|
-
response_text = "Unable to decode response text"
|
389
|
-
|
390
|
-
# Provide semantic error messages based on status code
|
391
|
-
if status_code == 400:
|
392
|
-
raise HttpError(
|
393
|
-
message="Bad Request - The server cannot process the request",
|
394
|
-
suggestion="Check your request parameters, headers, and data format",
|
395
|
-
context={"url": str(response.url), "method": response.request.method},
|
396
|
-
status_code=status_code,
|
397
|
-
response_text=response_text,
|
398
|
-
)
|
399
|
-
elif status_code == 401:
|
400
|
-
raise HttpError(
|
401
|
-
message="Unauthorized - Authentication is required",
|
402
|
-
suggestion="Provide valid authentication credentials (API key, token, etc.)",
|
403
|
-
context={"url": str(response.url)},
|
404
|
-
status_code=status_code,
|
405
|
-
response_text=response_text,
|
406
|
-
)
|
407
|
-
elif status_code == 403:
|
408
|
-
raise HttpError(
|
409
|
-
message="Forbidden - Access is denied",
|
410
|
-
suggestion="Check your permissions or API key scope",
|
411
|
-
context={"url": str(response.url)},
|
412
|
-
status_code=status_code,
|
413
|
-
response_text=response_text,
|
414
|
-
)
|
415
|
-
elif status_code == 404:
|
416
|
-
raise HttpError(
|
417
|
-
message="Not Found - The requested resource does not exist",
|
418
|
-
suggestion="Verify the URL path and any path parameters",
|
419
|
-
context={"url": str(response.url)},
|
420
|
-
status_code=status_code,
|
421
|
-
response_text=response_text,
|
422
|
-
)
|
423
|
-
elif status_code == 429:
|
424
|
-
raise HttpError(
|
425
|
-
message="Too Many Requests - Rate limit exceeded",
|
426
|
-
suggestion="Reduce request frequency or wait before retrying",
|
427
|
-
context={"url": str(response.url)},
|
428
|
-
status_code=status_code,
|
429
|
-
response_text=response_text,
|
430
|
-
)
|
431
|
-
elif 400 <= status_code < 500:
|
432
|
-
raise HttpError(
|
433
|
-
message=f"Client Error ({status_code}) - Request cannot be fulfilled",
|
434
|
-
suggestion="Review your request parameters and try again",
|
435
|
-
context={"url": str(response.url), "method": response.request.method},
|
436
|
-
status_code=status_code,
|
437
|
-
response_text=response_text,
|
438
|
-
)
|
439
|
-
elif 500 <= status_code < 600:
|
440
|
-
raise HttpError(
|
441
|
-
message=f"Server Error ({status_code}) - Server encountered an error",
|
442
|
-
suggestion="The server is experiencing issues. Try again later or contact support",
|
443
|
-
context={"url": str(response.url)},
|
444
|
-
status_code=status_code,
|
445
|
-
response_text=response_text,
|
446
|
-
)
|
447
|
-
else:
|
448
|
-
raise HttpError(
|
449
|
-
message=f"HTTP Error ({status_code})",
|
450
|
-
suggestion="An unexpected HTTP error occurred",
|
451
|
-
context={"url": str(response.url), "method": response.request.method},
|
452
|
-
status_code=status_code,
|
453
|
-
response_text=response_text,
|
454
|
-
)
|
455
|
-
|
456
|
-
async def request(self, request: HttpRequest) -> HttpResponse:
|
457
|
-
"""
|
458
|
-
Make an HTTP request with semantic error handling.
|
459
|
-
|
460
|
-
Args:
|
461
|
-
request: HttpRequest configuration object
|
462
|
-
|
463
|
-
Returns:
|
464
|
-
HttpResponse object with response data
|
465
|
-
|
466
|
-
Raises:
|
467
|
-
HttpError: On request failures with semantic feedback
|
468
|
-
"""
|
469
|
-
try:
|
470
|
-
# Build the complete URL
|
471
|
-
url = self._build_url(request.url)
|
472
|
-
|
473
|
-
# Prepare headers
|
474
|
-
headers = self._prepare_headers(request)
|
475
|
-
|
476
|
-
# Prepare request data
|
477
|
-
json_data = request.json_data
|
478
|
-
form_data = request.form_data
|
479
|
-
content = request.content
|
480
|
-
|
481
|
-
# Validate data payload
|
482
|
-
data_count = sum(
|
483
|
-
1 for x in [json_data, form_data, content] if x is not None
|
484
|
-
)
|
485
|
-
if data_count > 1:
|
486
|
-
raise HttpError(
|
487
|
-
message="Multiple data payloads provided",
|
488
|
-
suggestion="Provide only one of: json_data, form_data, or content",
|
489
|
-
context={
|
490
|
-
"has_json": json_data is not None,
|
491
|
-
"has_form": form_data is not None,
|
492
|
-
"has_content": content is not None,
|
493
|
-
},
|
494
|
-
)
|
495
|
-
|
496
|
-
# Execute the request with retry logic
|
497
|
-
return await self._execute_with_retry(
|
498
|
-
url, headers, request.params, json_data, form_data, content, request
|
499
|
-
)
|
500
|
-
|
501
|
-
except httpx.TimeoutException:
|
502
|
-
raise HttpError(
|
503
|
-
message="Request timed out",
|
504
|
-
suggestion=f"The request took longer than {request.timeout or self.timeout} seconds. Try increasing the timeout or check the server status",
|
505
|
-
context={
|
506
|
-
"url": request.url,
|
507
|
-
"timeout": request.timeout or self.timeout,
|
508
|
-
},
|
509
|
-
)
|
510
|
-
except httpx.ConnectError:
|
511
|
-
raise HttpError(
|
512
|
-
message="Connection failed",
|
513
|
-
suggestion="Check the URL and your internet connection. The server might be down",
|
514
|
-
context={"url": request.url},
|
515
|
-
)
|
516
|
-
except HttpError:
|
517
|
-
# Re-raise HttpError as-is
|
518
|
-
raise
|
519
|
-
except httpx.HTTPStatusError as e:
|
520
|
-
# This shouldn't happen since we handle status errors above, but just in case
|
521
|
-
raise HttpError(
|
522
|
-
message=f"HTTP error {e.response.status_code}",
|
523
|
-
suggestion="The server returned an error status",
|
524
|
-
context={"url": request.url, "status_code": e.response.status_code},
|
525
|
-
status_code=e.response.status_code,
|
526
|
-
)
|
527
|
-
except Exception as e:
|
528
|
-
raise HttpError(
|
529
|
-
message=f"Unexpected error: {str(e)}",
|
530
|
-
suggestion="An unexpected error occurred. Check your request configuration",
|
531
|
-
context={"url": request.url, "error_type": type(e).__name__},
|
532
|
-
)
|
533
|
-
|
534
|
-
# Convenience methods
|
535
|
-
async def get(
|
536
|
-
self,
|
537
|
-
url: str,
|
538
|
-
params: Optional[Dict[str, Any]] = None,
|
539
|
-
headers: Optional[Dict[str, str]] = None,
|
540
|
-
timeout: Optional[float] = None,
|
541
|
-
api_key: Optional[str] = None,
|
542
|
-
bearer_token: Optional[str] = None,
|
543
|
-
retry_attempts: int = 0,
|
544
|
-
**kwargs,
|
545
|
-
) -> HttpResponse:
|
546
|
-
"""Make a GET request with semantic parameters."""
|
547
|
-
request = HttpRequest(
|
548
|
-
method="GET",
|
549
|
-
url=url,
|
550
|
-
params=params,
|
551
|
-
headers=headers,
|
552
|
-
timeout=timeout,
|
553
|
-
api_key=api_key,
|
554
|
-
bearer_token=bearer_token,
|
555
|
-
retry_attempts=retry_attempts,
|
556
|
-
**kwargs,
|
557
|
-
)
|
558
|
-
return await self.request(request)
|
559
|
-
|
560
|
-
async def post(
|
561
|
-
self,
|
562
|
-
url: str,
|
563
|
-
json_data: Optional[Dict[str, Any]] = None,
|
564
|
-
form_data: Optional[Dict[str, Any]] = None,
|
565
|
-
headers: Optional[Dict[str, str]] = None,
|
566
|
-
timeout: Optional[float] = None,
|
567
|
-
api_key: Optional[str] = None,
|
568
|
-
bearer_token: Optional[str] = None,
|
569
|
-
retry_attempts: int = 0,
|
570
|
-
**kwargs,
|
571
|
-
) -> HttpResponse:
|
572
|
-
"""Make a POST request with semantic parameters."""
|
573
|
-
request = HttpRequest(
|
574
|
-
method="POST",
|
575
|
-
url=url,
|
576
|
-
json_data=json_data,
|
577
|
-
form_data=form_data,
|
578
|
-
headers=headers,
|
579
|
-
timeout=timeout,
|
580
|
-
api_key=api_key,
|
581
|
-
bearer_token=bearer_token,
|
582
|
-
retry_attempts=retry_attempts,
|
583
|
-
**kwargs,
|
584
|
-
)
|
585
|
-
return await self.request(request)
|
586
|
-
|
587
|
-
async def put(
|
588
|
-
self,
|
589
|
-
url: str,
|
590
|
-
json_data: Optional[Dict[str, Any]] = None,
|
591
|
-
form_data: Optional[Dict[str, Any]] = None,
|
592
|
-
headers: Optional[Dict[str, str]] = None,
|
593
|
-
timeout: Optional[float] = None,
|
594
|
-
api_key: Optional[str] = None,
|
595
|
-
bearer_token: Optional[str] = None,
|
596
|
-
retry_attempts: int = 0,
|
597
|
-
**kwargs,
|
598
|
-
) -> HttpResponse:
|
599
|
-
"""Make a PUT request with semantic parameters."""
|
600
|
-
request = HttpRequest(
|
601
|
-
method="PUT",
|
602
|
-
url=url,
|
603
|
-
json_data=json_data,
|
604
|
-
form_data=form_data,
|
605
|
-
headers=headers,
|
606
|
-
timeout=timeout,
|
607
|
-
api_key=api_key,
|
608
|
-
bearer_token=bearer_token,
|
609
|
-
retry_attempts=retry_attempts,
|
610
|
-
**kwargs,
|
611
|
-
)
|
612
|
-
return await self.request(request)
|
613
|
-
|
614
|
-
async def patch(
|
615
|
-
self,
|
616
|
-
url: str,
|
617
|
-
json_data: Optional[Dict[str, Any]] = None,
|
618
|
-
form_data: Optional[Dict[str, Any]] = None,
|
619
|
-
headers: Optional[Dict[str, str]] = None,
|
620
|
-
timeout: Optional[float] = None,
|
621
|
-
api_key: Optional[str] = None,
|
622
|
-
bearer_token: Optional[str] = None,
|
623
|
-
retry_attempts: int = 0,
|
624
|
-
**kwargs,
|
625
|
-
) -> HttpResponse:
|
626
|
-
"""Make a PATCH request with semantic parameters."""
|
627
|
-
request = HttpRequest(
|
628
|
-
method="PATCH",
|
629
|
-
url=url,
|
630
|
-
json_data=json_data,
|
631
|
-
form_data=form_data,
|
632
|
-
headers=headers,
|
633
|
-
timeout=timeout,
|
634
|
-
api_key=api_key,
|
635
|
-
bearer_token=bearer_token,
|
636
|
-
retry_attempts=retry_attempts,
|
637
|
-
**kwargs,
|
638
|
-
)
|
639
|
-
return await self.request(request)
|
640
|
-
|
641
|
-
async def delete(
|
642
|
-
self,
|
643
|
-
url: str,
|
644
|
-
headers: Optional[Dict[str, str]] = None,
|
645
|
-
timeout: Optional[float] = None,
|
646
|
-
api_key: Optional[str] = None,
|
647
|
-
bearer_token: Optional[str] = None,
|
648
|
-
retry_attempts: int = 0,
|
649
|
-
**kwargs,
|
650
|
-
) -> HttpResponse:
|
651
|
-
"""Make a DELETE request with semantic parameters."""
|
652
|
-
request = HttpRequest(
|
653
|
-
method="DELETE",
|
654
|
-
url=url,
|
655
|
-
headers=headers,
|
656
|
-
timeout=timeout,
|
657
|
-
api_key=api_key,
|
658
|
-
bearer_token=bearer_token,
|
659
|
-
retry_attempts=retry_attempts,
|
660
|
-
**kwargs,
|
661
|
-
)
|
662
|
-
return await self.request(request)
|
663
|
-
|
664
|
-
|
665
|
-
class HttpClient:
|
666
|
-
"""
|
667
|
-
Base HTTP toolkit for making HTTP requests with clean type hints and semantic error handling.
|
668
|
-
|
669
|
-
This class provides a clean, well-typed interface for HTTP operations using httpx.
|
670
|
-
It includes semantic error handling and validation to provide meaningful feedback.
|
671
|
-
"""
|
672
|
-
|
673
|
-
def __init__(
|
674
|
-
self,
|
675
|
-
base_url: Optional[str] = None,
|
676
|
-
default_headers: Optional[Dict[str, str]] = None,
|
677
|
-
timeout: float = 30.0,
|
678
|
-
follow_redirects: bool = True,
|
679
|
-
verify_ssl: bool = True,
|
680
|
-
# Semantic authentication parameters
|
681
|
-
api_key: Optional[str] = None,
|
682
|
-
api_key_header: str = "X-API-Key",
|
683
|
-
bearer_token: Optional[str] = None,
|
684
|
-
basic_auth: Optional[tuple[str, str]] = None,
|
685
|
-
user_agent: Optional[str] = None,
|
686
|
-
):
|
687
|
-
"""
|
688
|
-
Initialize the HttpClient.
|
689
|
-
|
690
|
-
Args:
|
691
|
-
base_url: Base URL for all requests
|
692
|
-
default_headers: Default headers to include in all requests
|
693
|
-
timeout: Default timeout for HTTP requests in seconds
|
694
|
-
follow_redirects: Whether to follow HTTP redirects
|
695
|
-
verify_ssl: Whether to verify SSL certificates
|
696
|
-
api_key: API key for authentication
|
697
|
-
api_key_header: Header name for API key
|
698
|
-
bearer_token: Bearer token for authentication
|
699
|
-
basic_auth: Username and password tuple for basic authentication
|
700
|
-
user_agent: User-Agent header for HTTP requests
|
701
|
-
"""
|
702
|
-
self._async_client = AsyncHttpClient(
|
703
|
-
base_url=base_url,
|
704
|
-
default_headers=default_headers,
|
705
|
-
timeout=timeout,
|
706
|
-
follow_redirects=follow_redirects,
|
707
|
-
verify_ssl=verify_ssl,
|
708
|
-
api_key=api_key,
|
709
|
-
api_key_header=api_key_header,
|
710
|
-
bearer_token=bearer_token,
|
711
|
-
basic_auth=basic_auth,
|
712
|
-
user_agent=user_agent,
|
713
|
-
)
|
714
|
-
|
715
|
-
def _run_async(self, coro):
|
716
|
-
"""Run an async coroutine in a new event loop."""
|
717
|
-
try:
|
718
|
-
# Try to get the current event loop
|
719
|
-
loop = asyncio.get_running_loop()
|
720
|
-
# If we're already in an event loop, we need to use a thread
|
721
|
-
import concurrent.futures
|
722
|
-
|
723
|
-
with concurrent.futures.ThreadPoolExecutor() as executor:
|
724
|
-
future = executor.submit(asyncio.run, coro)
|
725
|
-
return future.result()
|
726
|
-
except RuntimeError:
|
727
|
-
# No event loop running, we can create our own
|
728
|
-
return asyncio.run(coro)
|
729
|
-
|
730
|
-
def request(self, request: HttpRequest) -> HttpResponse:
|
731
|
-
"""
|
732
|
-
Make an HTTP request with semantic error handling.
|
733
|
-
|
734
|
-
Args:
|
735
|
-
request: HttpRequest configuration object
|
736
|
-
|
737
|
-
Returns:
|
738
|
-
HttpResponse object with response data
|
739
|
-
|
740
|
-
Raises:
|
741
|
-
HttpError: On request failures with semantic feedback
|
742
|
-
"""
|
743
|
-
return self._run_async(self._async_client.request(request))
|
744
|
-
|
745
|
-
def get(
|
746
|
-
self,
|
747
|
-
url: str,
|
748
|
-
params: Optional[Dict[str, Any]] = None,
|
749
|
-
headers: Optional[Dict[str, str]] = None,
|
750
|
-
timeout: Optional[float] = None,
|
751
|
-
api_key: Optional[str] = None,
|
752
|
-
bearer_token: Optional[str] = None,
|
753
|
-
retry_attempts: int = 0,
|
754
|
-
**kwargs,
|
755
|
-
) -> HttpResponse:
|
756
|
-
"""Make a GET request with semantic parameters."""
|
757
|
-
return self._run_async(
|
758
|
-
self._async_client.get(
|
759
|
-
url,
|
760
|
-
params=params,
|
761
|
-
headers=headers,
|
762
|
-
timeout=timeout,
|
763
|
-
api_key=api_key,
|
764
|
-
bearer_token=bearer_token,
|
765
|
-
retry_attempts=retry_attempts,
|
766
|
-
**kwargs,
|
767
|
-
)
|
768
|
-
)
|
769
|
-
|
770
|
-
def post(
|
771
|
-
self,
|
772
|
-
url: str,
|
773
|
-
json_data: Optional[Dict[str, Any]] = None,
|
774
|
-
form_data: Optional[Dict[str, Any]] = None,
|
775
|
-
headers: Optional[Dict[str, str]] = None,
|
776
|
-
timeout: Optional[float] = None,
|
777
|
-
api_key: Optional[str] = None,
|
778
|
-
bearer_token: Optional[str] = None,
|
779
|
-
retry_attempts: int = 0,
|
780
|
-
**kwargs,
|
781
|
-
) -> HttpResponse:
|
782
|
-
"""Make a POST request with semantic parameters."""
|
783
|
-
return self._run_async(
|
784
|
-
self._async_client.post(
|
785
|
-
url,
|
786
|
-
json_data=json_data,
|
787
|
-
form_data=form_data,
|
788
|
-
headers=headers,
|
789
|
-
timeout=timeout,
|
790
|
-
api_key=api_key,
|
791
|
-
bearer_token=bearer_token,
|
792
|
-
retry_attempts=retry_attempts,
|
793
|
-
**kwargs,
|
794
|
-
)
|
795
|
-
)
|
796
|
-
|
797
|
-
def put(
|
798
|
-
self,
|
799
|
-
url: str,
|
800
|
-
json_data: Optional[Dict[str, Any]] = None,
|
801
|
-
form_data: Optional[Dict[str, Any]] = None,
|
802
|
-
headers: Optional[Dict[str, str]] = None,
|
803
|
-
timeout: Optional[float] = None,
|
804
|
-
api_key: Optional[str] = None,
|
805
|
-
bearer_token: Optional[str] = None,
|
806
|
-
retry_attempts: int = 0,
|
807
|
-
**kwargs,
|
808
|
-
) -> HttpResponse:
|
809
|
-
"""Make a PUT request with semantic parameters."""
|
810
|
-
return self._run_async(
|
811
|
-
self._async_client.put(
|
812
|
-
url,
|
813
|
-
json_data=json_data,
|
814
|
-
form_data=form_data,
|
815
|
-
headers=headers,
|
816
|
-
timeout=timeout,
|
817
|
-
api_key=api_key,
|
818
|
-
bearer_token=bearer_token,
|
819
|
-
retry_attempts=retry_attempts,
|
820
|
-
**kwargs,
|
821
|
-
)
|
822
|
-
)
|
823
|
-
|
824
|
-
def patch(
|
825
|
-
self,
|
826
|
-
url: str,
|
827
|
-
json_data: Optional[Dict[str, Any]] = None,
|
828
|
-
form_data: Optional[Dict[str, Any]] = None,
|
829
|
-
headers: Optional[Dict[str, str]] = None,
|
830
|
-
timeout: Optional[float] = None,
|
831
|
-
api_key: Optional[str] = None,
|
832
|
-
bearer_token: Optional[str] = None,
|
833
|
-
retry_attempts: int = 0,
|
834
|
-
**kwargs,
|
835
|
-
) -> HttpResponse:
|
836
|
-
"""Make a PATCH request with semantic parameters."""
|
837
|
-
return self._run_async(
|
838
|
-
self._async_client.patch(
|
839
|
-
url,
|
840
|
-
json_data=json_data,
|
841
|
-
form_data=form_data,
|
842
|
-
headers=headers,
|
843
|
-
timeout=timeout,
|
844
|
-
api_key=api_key,
|
845
|
-
bearer_token=bearer_token,
|
846
|
-
retry_attempts=retry_attempts,
|
847
|
-
**kwargs,
|
848
|
-
)
|
849
|
-
)
|
850
|
-
|
851
|
-
def delete(
|
852
|
-
self,
|
853
|
-
url: str,
|
854
|
-
headers: Optional[Dict[str, str]] = None,
|
855
|
-
timeout: Optional[float] = None,
|
856
|
-
api_key: Optional[str] = None,
|
857
|
-
bearer_token: Optional[str] = None,
|
858
|
-
retry_attempts: int = 0,
|
859
|
-
**kwargs,
|
860
|
-
) -> HttpResponse:
|
861
|
-
"""Make a DELETE request with semantic parameters."""
|
862
|
-
return self._run_async(
|
863
|
-
self._async_client.delete(
|
864
|
-
url,
|
865
|
-
headers=headers,
|
866
|
-
timeout=timeout,
|
867
|
-
api_key=api_key,
|
868
|
-
bearer_token=bearer_token,
|
869
|
-
retry_attempts=retry_attempts,
|
870
|
-
**kwargs,
|
871
|
-
)
|
872
|
-
)
|
873
|
-
|
874
|
-
|
875
|
-
@overload
|
876
|
-
def create_http_client(
|
877
|
-
base_url: Optional[str] = None,
|
878
|
-
default_headers: Optional[Dict[str, str]] = None,
|
879
|
-
timeout: float = 30.0,
|
880
|
-
follow_redirects: bool = True,
|
881
|
-
verify_ssl: bool = True,
|
882
|
-
# Semantic authentication parameters
|
883
|
-
api_key: Optional[str] = None,
|
884
|
-
api_key_header: str = "X-API-Key",
|
885
|
-
bearer_token: Optional[str] = None,
|
886
|
-
basic_auth: Optional[tuple[str, str]] = None,
|
887
|
-
user_agent: Optional[str] = None,
|
888
|
-
async_client: Literal[True] = ...,
|
889
|
-
) -> AsyncHttpClient: ...
|
890
|
-
|
891
|
-
|
892
|
-
@overload
|
893
|
-
def create_http_client(
|
894
|
-
base_url: Optional[str] = None,
|
895
|
-
default_headers: Optional[Dict[str, str]] = None,
|
896
|
-
timeout: float = 30.0,
|
897
|
-
follow_redirects: bool = True,
|
898
|
-
verify_ssl: bool = True,
|
899
|
-
# Semantic authentication parameters
|
900
|
-
api_key: Optional[str] = None,
|
901
|
-
api_key_header: str = "X-API-Key",
|
902
|
-
bearer_token: Optional[str] = None,
|
903
|
-
basic_auth: Optional[tuple[str, str]] = None,
|
904
|
-
user_agent: Optional[str] = None,
|
905
|
-
async_client: Literal[False] = ...,
|
906
|
-
) -> HttpClient: ...
|
907
|
-
|
908
|
-
|
909
|
-
def create_http_client(
|
910
|
-
base_url: Optional[str] = None,
|
911
|
-
default_headers: Optional[Dict[str, str]] = None,
|
912
|
-
timeout: float = 30.0,
|
913
|
-
follow_redirects: bool = True,
|
914
|
-
verify_ssl: bool = True,
|
915
|
-
# Semantic authentication parameters
|
916
|
-
api_key: Optional[str] = None,
|
917
|
-
api_key_header: str = "X-API-Key",
|
918
|
-
bearer_token: Optional[str] = None,
|
919
|
-
basic_auth: Optional[tuple[str, str]] = None,
|
920
|
-
user_agent: Optional[str] = None,
|
921
|
-
async_client: bool = False,
|
922
|
-
) -> Union[HttpClient, AsyncHttpClient]:
|
923
|
-
"""
|
924
|
-
Create a new HttpClient instance.
|
925
|
-
|
926
|
-
Args:
|
927
|
-
base_url: Base URL for all requests (optional)
|
928
|
-
default_headers: Default headers to include in all requests
|
929
|
-
timeout: Default timeout in seconds
|
930
|
-
follow_redirects: Whether to follow redirects by default
|
931
|
-
verify_ssl: Whether to verify SSL certificates
|
932
|
-
api_key: API key for authentication
|
933
|
-
api_key_header: Header name for API key (default: X-API-Key)
|
934
|
-
bearer_token: Bearer token for Authorization header
|
935
|
-
basic_auth: Tuple of (username, password) for basic auth
|
936
|
-
user_agent: User-Agent header value
|
937
|
-
"""
|
938
|
-
params = locals()
|
939
|
-
del params["async_client"]
|
940
|
-
|
941
|
-
if async_client:
|
942
|
-
return AsyncHttpClient(**params)
|
943
|
-
else:
|
944
|
-
return HttpClient(**params)
|