hud-python 0.4.1__py3-none-any.whl → 0.4.3__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.
Potentially problematic release.
This version of hud-python might be problematic. Click here for more details.
- hud/__init__.py +22 -22
- hud/agents/__init__.py +13 -15
- hud/agents/base.py +599 -599
- hud/agents/claude.py +373 -373
- hud/agents/langchain.py +261 -250
- hud/agents/misc/__init__.py +7 -7
- hud/agents/misc/response_agent.py +82 -80
- hud/agents/openai.py +352 -352
- hud/agents/openai_chat_generic.py +154 -154
- hud/agents/tests/__init__.py +1 -1
- hud/agents/tests/test_base.py +742 -742
- hud/agents/tests/test_claude.py +324 -324
- hud/agents/tests/test_client.py +363 -363
- hud/agents/tests/test_openai.py +237 -237
- hud/cli/__init__.py +617 -617
- hud/cli/__main__.py +8 -8
- hud/cli/analyze.py +371 -371
- hud/cli/analyze_metadata.py +230 -230
- hud/cli/build.py +498 -427
- hud/cli/clone.py +185 -185
- hud/cli/cursor.py +92 -92
- hud/cli/debug.py +392 -392
- hud/cli/docker_utils.py +83 -83
- hud/cli/init.py +280 -281
- hud/cli/interactive.py +353 -353
- hud/cli/mcp_server.py +764 -756
- hud/cli/pull.py +330 -336
- hud/cli/push.py +404 -370
- hud/cli/remote_runner.py +311 -311
- hud/cli/runner.py +160 -160
- hud/cli/tests/__init__.py +3 -3
- hud/cli/tests/test_analyze.py +284 -284
- hud/cli/tests/test_cli_init.py +265 -265
- hud/cli/tests/test_cli_main.py +27 -27
- hud/cli/tests/test_clone.py +142 -142
- hud/cli/tests/test_cursor.py +253 -253
- hud/cli/tests/test_debug.py +453 -453
- hud/cli/tests/test_mcp_server.py +139 -139
- hud/cli/tests/test_utils.py +388 -388
- hud/cli/utils.py +263 -263
- hud/clients/README.md +143 -143
- hud/clients/__init__.py +16 -16
- hud/clients/base.py +378 -379
- hud/clients/fastmcp.py +222 -222
- hud/clients/mcp_use.py +298 -278
- hud/clients/tests/__init__.py +1 -1
- hud/clients/tests/test_client_integration.py +111 -111
- hud/clients/tests/test_fastmcp.py +342 -342
- hud/clients/tests/test_protocol.py +188 -188
- hud/clients/utils/__init__.py +1 -1
- hud/clients/utils/retry_transport.py +160 -160
- hud/datasets.py +327 -322
- hud/misc/__init__.py +1 -1
- hud/misc/claude_plays_pokemon.py +292 -292
- hud/otel/__init__.py +35 -35
- hud/otel/collector.py +142 -142
- hud/otel/config.py +164 -164
- hud/otel/context.py +536 -536
- hud/otel/exporters.py +366 -366
- hud/otel/instrumentation.py +97 -97
- hud/otel/processors.py +118 -118
- hud/otel/tests/__init__.py +1 -1
- hud/otel/tests/test_processors.py +197 -197
- hud/server/__init__.py +5 -5
- hud/server/context.py +114 -114
- hud/server/helper/__init__.py +5 -5
- hud/server/low_level.py +132 -132
- hud/server/server.py +170 -166
- hud/server/tests/__init__.py +3 -3
- hud/settings.py +73 -73
- hud/shared/__init__.py +5 -5
- hud/shared/exceptions.py +180 -180
- hud/shared/requests.py +264 -264
- hud/shared/tests/test_exceptions.py +157 -157
- hud/shared/tests/test_requests.py +275 -275
- hud/telemetry/__init__.py +25 -25
- hud/telemetry/instrument.py +379 -379
- hud/telemetry/job.py +309 -309
- hud/telemetry/replay.py +74 -74
- hud/telemetry/trace.py +83 -83
- hud/tools/__init__.py +33 -33
- hud/tools/base.py +365 -365
- hud/tools/bash.py +161 -161
- hud/tools/computer/__init__.py +15 -15
- hud/tools/computer/anthropic.py +437 -437
- hud/tools/computer/hud.py +376 -376
- hud/tools/computer/openai.py +295 -295
- hud/tools/computer/settings.py +82 -82
- hud/tools/edit.py +314 -314
- hud/tools/executors/__init__.py +30 -30
- hud/tools/executors/base.py +539 -539
- hud/tools/executors/pyautogui.py +621 -621
- hud/tools/executors/tests/__init__.py +1 -1
- hud/tools/executors/tests/test_base_executor.py +338 -338
- hud/tools/executors/tests/test_pyautogui_executor.py +165 -165
- hud/tools/executors/xdo.py +511 -511
- hud/tools/playwright.py +412 -412
- hud/tools/tests/__init__.py +3 -3
- hud/tools/tests/test_base.py +282 -282
- hud/tools/tests/test_bash.py +158 -158
- hud/tools/tests/test_bash_extended.py +197 -197
- hud/tools/tests/test_computer.py +425 -425
- hud/tools/tests/test_computer_actions.py +34 -34
- hud/tools/tests/test_edit.py +259 -259
- hud/tools/tests/test_init.py +27 -27
- hud/tools/tests/test_playwright_tool.py +183 -183
- hud/tools/tests/test_tools.py +145 -145
- hud/tools/tests/test_utils.py +156 -156
- hud/tools/types.py +72 -72
- hud/tools/utils.py +50 -50
- hud/types.py +136 -136
- hud/utils/__init__.py +10 -10
- hud/utils/async_utils.py +65 -65
- hud/utils/design.py +236 -168
- hud/utils/mcp.py +55 -55
- hud/utils/progress.py +149 -149
- hud/utils/telemetry.py +66 -66
- hud/utils/tests/test_async_utils.py +173 -173
- hud/utils/tests/test_init.py +17 -17
- hud/utils/tests/test_progress.py +261 -261
- hud/utils/tests/test_telemetry.py +82 -82
- hud/utils/tests/test_version.py +8 -8
- hud/version.py +7 -7
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/METADATA +10 -8
- hud_python-0.4.3.dist-info/RECORD +131 -0
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/licenses/LICENSE +21 -21
- hud/agents/art.py +0 -101
- hud_python-0.4.1.dist-info/RECORD +0 -132
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/WHEEL +0 -0
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/entry_points.txt +0 -0
hud/shared/requests.py
CHANGED
|
@@ -1,264 +1,264 @@
|
|
|
1
|
-
"""
|
|
2
|
-
HTTP request utilities for the HUD API.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from __future__ import annotations
|
|
6
|
-
|
|
7
|
-
import asyncio
|
|
8
|
-
import logging
|
|
9
|
-
import ssl
|
|
10
|
-
import time
|
|
11
|
-
from typing import Any
|
|
12
|
-
|
|
13
|
-
import httpx
|
|
14
|
-
|
|
15
|
-
from hud.shared.exceptions import (
|
|
16
|
-
HudAuthenticationError,
|
|
17
|
-
HudNetworkError,
|
|
18
|
-
HudRequestError,
|
|
19
|
-
HudTimeoutError,
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
# Set up logger
|
|
23
|
-
logger = logging.getLogger("hud.http")
|
|
24
|
-
logger.setLevel(logging.INFO)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# Long running requests can take up to 10 minutes.
|
|
28
|
-
_DEFAULT_TIMEOUT = 600.0
|
|
29
|
-
_DEFAULT_LIMITS = httpx.Limits(
|
|
30
|
-
max_connections=1000,
|
|
31
|
-
max_keepalive_connections=1000,
|
|
32
|
-
keepalive_expiry=10.0,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
async def _handle_retry(
|
|
37
|
-
attempt: int, max_retries: int, retry_delay: float, url: str, error_msg: str
|
|
38
|
-
) -> None:
|
|
39
|
-
"""Helper function to handle retry logic and logging."""
|
|
40
|
-
retry_time = retry_delay * (2 ** (attempt - 1)) # Exponential backoff
|
|
41
|
-
logger.debug(
|
|
42
|
-
"%s from %s, retrying in %.2f seconds (attempt %d/%d)",
|
|
43
|
-
error_msg,
|
|
44
|
-
url,
|
|
45
|
-
retry_time,
|
|
46
|
-
attempt,
|
|
47
|
-
max_retries,
|
|
48
|
-
)
|
|
49
|
-
await asyncio.sleep(retry_time)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def _create_default_async_client() -> httpx.AsyncClient:
|
|
53
|
-
"""Create a default httpx AsyncClient with standard configuration."""
|
|
54
|
-
return httpx.AsyncClient(
|
|
55
|
-
timeout=_DEFAULT_TIMEOUT,
|
|
56
|
-
limits=_DEFAULT_LIMITS,
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def _create_default_sync_client() -> httpx.Client:
|
|
61
|
-
"""Create a default httpx Client with standard configuration."""
|
|
62
|
-
return httpx.Client(
|
|
63
|
-
timeout=_DEFAULT_TIMEOUT,
|
|
64
|
-
limits=_DEFAULT_LIMITS,
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
async def make_request(
|
|
69
|
-
method: str,
|
|
70
|
-
url: str,
|
|
71
|
-
json: Any | None = None,
|
|
72
|
-
api_key: str | None = None,
|
|
73
|
-
max_retries: int = 4,
|
|
74
|
-
retry_delay: float = 2.0,
|
|
75
|
-
client: httpx.AsyncClient | None = None,
|
|
76
|
-
) -> dict[str, Any]:
|
|
77
|
-
"""
|
|
78
|
-
Make an asynchronous HTTP request to the HUD API.
|
|
79
|
-
|
|
80
|
-
Args:
|
|
81
|
-
method: HTTP method (GET, POST, etc.)
|
|
82
|
-
url: Full URL for the request
|
|
83
|
-
json: Optional JSON serializable data
|
|
84
|
-
api_key: API key for authentication
|
|
85
|
-
max_retries: Maximum number of retries
|
|
86
|
-
retry_delay: Delay between retries
|
|
87
|
-
*,
|
|
88
|
-
client: Optional custom httpx.AsyncClient
|
|
89
|
-
|
|
90
|
-
Returns:
|
|
91
|
-
dict: JSON response from the server
|
|
92
|
-
|
|
93
|
-
Raises:
|
|
94
|
-
HudAuthenticationError: If API key is missing or invalid.
|
|
95
|
-
HudRequestError: If the request fails with a non-retryable status code.
|
|
96
|
-
HudNetworkError: If there are network-related issues.
|
|
97
|
-
HudTimeoutError: If the request times out.
|
|
98
|
-
"""
|
|
99
|
-
if not api_key:
|
|
100
|
-
raise HudAuthenticationError("API key is required but not provided")
|
|
101
|
-
|
|
102
|
-
headers = {"Authorization": f"Bearer {api_key}"}
|
|
103
|
-
retry_status_codes = [502, 503, 504]
|
|
104
|
-
attempt = 0
|
|
105
|
-
should_close_client = False
|
|
106
|
-
|
|
107
|
-
if client is None:
|
|
108
|
-
client = _create_default_async_client()
|
|
109
|
-
should_close_client = True
|
|
110
|
-
|
|
111
|
-
try:
|
|
112
|
-
while attempt <= max_retries:
|
|
113
|
-
attempt += 1
|
|
114
|
-
|
|
115
|
-
try:
|
|
116
|
-
response = await client.request(method=method, url=url, json=json, headers=headers)
|
|
117
|
-
|
|
118
|
-
# Check if we got a retriable status code
|
|
119
|
-
if response.status_code in retry_status_codes and attempt <= max_retries:
|
|
120
|
-
await _handle_retry(
|
|
121
|
-
attempt,
|
|
122
|
-
max_retries,
|
|
123
|
-
retry_delay,
|
|
124
|
-
url,
|
|
125
|
-
f"Received status {response.status_code}",
|
|
126
|
-
)
|
|
127
|
-
continue
|
|
128
|
-
|
|
129
|
-
response.raise_for_status()
|
|
130
|
-
result = response.json()
|
|
131
|
-
return result
|
|
132
|
-
except httpx.TimeoutException as e:
|
|
133
|
-
raise HudTimeoutError(f"Request timed out: {e!s}") from None
|
|
134
|
-
except httpx.HTTPStatusError as e:
|
|
135
|
-
raise HudRequestError.from_httpx_error(e) from None
|
|
136
|
-
except httpx.RequestError as e:
|
|
137
|
-
if attempt <= max_retries:
|
|
138
|
-
await _handle_retry(
|
|
139
|
-
attempt, max_retries, retry_delay, url, f"Network error: {e}"
|
|
140
|
-
)
|
|
141
|
-
continue
|
|
142
|
-
else:
|
|
143
|
-
raise HudNetworkError(f"Network error: {e!s}") from None
|
|
144
|
-
except ssl.SSLError as e:
|
|
145
|
-
if attempt <= max_retries:
|
|
146
|
-
await _handle_retry(attempt, max_retries, retry_delay, url, f"SSL error: {e}")
|
|
147
|
-
continue
|
|
148
|
-
else:
|
|
149
|
-
raise HudNetworkError(f"SSL error: {e!s}") from None
|
|
150
|
-
except Exception as e:
|
|
151
|
-
raise HudRequestError(f"Unexpected error: {e!s}") from None
|
|
152
|
-
raise HudRequestError(f"Request failed after {max_retries} retries with unknown error")
|
|
153
|
-
finally:
|
|
154
|
-
if should_close_client:
|
|
155
|
-
await client.aclose()
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def make_request_sync(
|
|
159
|
-
method: str,
|
|
160
|
-
url: str,
|
|
161
|
-
json: Any | None = None,
|
|
162
|
-
api_key: str | None = None,
|
|
163
|
-
max_retries: int = 4,
|
|
164
|
-
retry_delay: float = 2.0,
|
|
165
|
-
*,
|
|
166
|
-
client: httpx.Client | None = None,
|
|
167
|
-
) -> dict[str, Any]:
|
|
168
|
-
"""
|
|
169
|
-
Make a synchronous HTTP request to the HUD API.
|
|
170
|
-
|
|
171
|
-
Args:
|
|
172
|
-
method: HTTP method (GET, POST, etc.)
|
|
173
|
-
url: Full URL for the request
|
|
174
|
-
json: Optional JSON serializable data
|
|
175
|
-
api_key: API key for authentication
|
|
176
|
-
max_retries: Maximum number of retries
|
|
177
|
-
retry_delay: Delay between retries
|
|
178
|
-
client: Optional custom httpx.Client
|
|
179
|
-
|
|
180
|
-
Returns:
|
|
181
|
-
dict: JSON response from the server
|
|
182
|
-
|
|
183
|
-
Raises:
|
|
184
|
-
HudAuthenticationError: If API key is missing or invalid.
|
|
185
|
-
HudRequestError: If the request fails with a non-retryable status code.
|
|
186
|
-
HudNetworkError: If there are network-related issues.
|
|
187
|
-
HudTimeoutError: If the request times out.
|
|
188
|
-
"""
|
|
189
|
-
if not api_key:
|
|
190
|
-
raise HudAuthenticationError("API key is required but not provided")
|
|
191
|
-
|
|
192
|
-
headers = {"Authorization": f"Bearer {api_key}"}
|
|
193
|
-
retry_status_codes = [502, 503, 504]
|
|
194
|
-
attempt = 0
|
|
195
|
-
should_close_client = False
|
|
196
|
-
|
|
197
|
-
if client is None:
|
|
198
|
-
client = _create_default_sync_client()
|
|
199
|
-
should_close_client = True
|
|
200
|
-
|
|
201
|
-
try:
|
|
202
|
-
while attempt <= max_retries:
|
|
203
|
-
attempt += 1
|
|
204
|
-
|
|
205
|
-
try:
|
|
206
|
-
response = client.request(method=method, url=url, json=json, headers=headers)
|
|
207
|
-
|
|
208
|
-
# Check if we got a retriable status code
|
|
209
|
-
if response.status_code in retry_status_codes and attempt <= max_retries:
|
|
210
|
-
retry_time = retry_delay * (2 ** (attempt - 1)) # Exponential backoff
|
|
211
|
-
logger.debug(
|
|
212
|
-
"Received status %d from %s, retrying in %.2f seconds (attempt %d/%d)",
|
|
213
|
-
response.status_code,
|
|
214
|
-
url,
|
|
215
|
-
retry_time,
|
|
216
|
-
attempt,
|
|
217
|
-
max_retries,
|
|
218
|
-
)
|
|
219
|
-
time.sleep(retry_time)
|
|
220
|
-
continue
|
|
221
|
-
|
|
222
|
-
response.raise_for_status()
|
|
223
|
-
result = response.json()
|
|
224
|
-
return result
|
|
225
|
-
except httpx.TimeoutException as e:
|
|
226
|
-
raise HudTimeoutError(f"Request timed out: {e!s}") from None
|
|
227
|
-
except httpx.HTTPStatusError as e:
|
|
228
|
-
raise HudRequestError.from_httpx_error(e) from None
|
|
229
|
-
except httpx.RequestError as e:
|
|
230
|
-
if attempt <= max_retries:
|
|
231
|
-
retry_time = retry_delay * (2 ** (attempt - 1))
|
|
232
|
-
logger.debug(
|
|
233
|
-
"Network error %s from %s, retrying in %.2f seconds (attempt %d/%d)",
|
|
234
|
-
str(e),
|
|
235
|
-
url,
|
|
236
|
-
retry_time,
|
|
237
|
-
attempt,
|
|
238
|
-
max_retries,
|
|
239
|
-
)
|
|
240
|
-
time.sleep(retry_time)
|
|
241
|
-
continue
|
|
242
|
-
else:
|
|
243
|
-
raise HudNetworkError(f"Network error: {e!s}") from None
|
|
244
|
-
except ssl.SSLError as e:
|
|
245
|
-
if attempt <= max_retries:
|
|
246
|
-
retry_time = retry_delay * (2 ** (attempt - 1)) # Exponential backoff
|
|
247
|
-
logger.debug(
|
|
248
|
-
"SSL error %s from %s, retrying in %.2f seconds (attempt %d/%d)",
|
|
249
|
-
str(e),
|
|
250
|
-
url,
|
|
251
|
-
retry_time,
|
|
252
|
-
attempt,
|
|
253
|
-
max_retries,
|
|
254
|
-
)
|
|
255
|
-
time.sleep(retry_time)
|
|
256
|
-
continue
|
|
257
|
-
else:
|
|
258
|
-
raise HudNetworkError(f"SSL error: {e!s}") from None
|
|
259
|
-
except Exception as e:
|
|
260
|
-
raise HudRequestError(f"Unexpected error: {e!s}") from None
|
|
261
|
-
raise HudRequestError(f"Request failed after {max_retries} retries with unknown error")
|
|
262
|
-
finally:
|
|
263
|
-
if should_close_client:
|
|
264
|
-
client.close()
|
|
1
|
+
"""
|
|
2
|
+
HTTP request utilities for the HUD API.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
import ssl
|
|
10
|
+
import time
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
|
|
15
|
+
from hud.shared.exceptions import (
|
|
16
|
+
HudAuthenticationError,
|
|
17
|
+
HudNetworkError,
|
|
18
|
+
HudRequestError,
|
|
19
|
+
HudTimeoutError,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Set up logger
|
|
23
|
+
logger = logging.getLogger("hud.http")
|
|
24
|
+
logger.setLevel(logging.INFO)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Long running requests can take up to 10 minutes.
|
|
28
|
+
_DEFAULT_TIMEOUT = 600.0
|
|
29
|
+
_DEFAULT_LIMITS = httpx.Limits(
|
|
30
|
+
max_connections=1000,
|
|
31
|
+
max_keepalive_connections=1000,
|
|
32
|
+
keepalive_expiry=10.0,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def _handle_retry(
|
|
37
|
+
attempt: int, max_retries: int, retry_delay: float, url: str, error_msg: str
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Helper function to handle retry logic and logging."""
|
|
40
|
+
retry_time = retry_delay * (2 ** (attempt - 1)) # Exponential backoff
|
|
41
|
+
logger.debug(
|
|
42
|
+
"%s from %s, retrying in %.2f seconds (attempt %d/%d)",
|
|
43
|
+
error_msg,
|
|
44
|
+
url,
|
|
45
|
+
retry_time,
|
|
46
|
+
attempt,
|
|
47
|
+
max_retries,
|
|
48
|
+
)
|
|
49
|
+
await asyncio.sleep(retry_time)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _create_default_async_client() -> httpx.AsyncClient:
|
|
53
|
+
"""Create a default httpx AsyncClient with standard configuration."""
|
|
54
|
+
return httpx.AsyncClient(
|
|
55
|
+
timeout=_DEFAULT_TIMEOUT,
|
|
56
|
+
limits=_DEFAULT_LIMITS,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _create_default_sync_client() -> httpx.Client:
|
|
61
|
+
"""Create a default httpx Client with standard configuration."""
|
|
62
|
+
return httpx.Client(
|
|
63
|
+
timeout=_DEFAULT_TIMEOUT,
|
|
64
|
+
limits=_DEFAULT_LIMITS,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def make_request(
|
|
69
|
+
method: str,
|
|
70
|
+
url: str,
|
|
71
|
+
json: Any | None = None,
|
|
72
|
+
api_key: str | None = None,
|
|
73
|
+
max_retries: int = 4,
|
|
74
|
+
retry_delay: float = 2.0,
|
|
75
|
+
client: httpx.AsyncClient | None = None,
|
|
76
|
+
) -> dict[str, Any]:
|
|
77
|
+
"""
|
|
78
|
+
Make an asynchronous HTTP request to the HUD API.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
method: HTTP method (GET, POST, etc.)
|
|
82
|
+
url: Full URL for the request
|
|
83
|
+
json: Optional JSON serializable data
|
|
84
|
+
api_key: API key for authentication
|
|
85
|
+
max_retries: Maximum number of retries
|
|
86
|
+
retry_delay: Delay between retries
|
|
87
|
+
*,
|
|
88
|
+
client: Optional custom httpx.AsyncClient
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
dict: JSON response from the server
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
HudAuthenticationError: If API key is missing or invalid.
|
|
95
|
+
HudRequestError: If the request fails with a non-retryable status code.
|
|
96
|
+
HudNetworkError: If there are network-related issues.
|
|
97
|
+
HudTimeoutError: If the request times out.
|
|
98
|
+
"""
|
|
99
|
+
if not api_key:
|
|
100
|
+
raise HudAuthenticationError("API key is required but not provided")
|
|
101
|
+
|
|
102
|
+
headers = {"Authorization": f"Bearer {api_key}"}
|
|
103
|
+
retry_status_codes = [502, 503, 504]
|
|
104
|
+
attempt = 0
|
|
105
|
+
should_close_client = False
|
|
106
|
+
|
|
107
|
+
if client is None:
|
|
108
|
+
client = _create_default_async_client()
|
|
109
|
+
should_close_client = True
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
while attempt <= max_retries:
|
|
113
|
+
attempt += 1
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
response = await client.request(method=method, url=url, json=json, headers=headers)
|
|
117
|
+
|
|
118
|
+
# Check if we got a retriable status code
|
|
119
|
+
if response.status_code in retry_status_codes and attempt <= max_retries:
|
|
120
|
+
await _handle_retry(
|
|
121
|
+
attempt,
|
|
122
|
+
max_retries,
|
|
123
|
+
retry_delay,
|
|
124
|
+
url,
|
|
125
|
+
f"Received status {response.status_code}",
|
|
126
|
+
)
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
response.raise_for_status()
|
|
130
|
+
result = response.json()
|
|
131
|
+
return result
|
|
132
|
+
except httpx.TimeoutException as e:
|
|
133
|
+
raise HudTimeoutError(f"Request timed out: {e!s}") from None
|
|
134
|
+
except httpx.HTTPStatusError as e:
|
|
135
|
+
raise HudRequestError.from_httpx_error(e) from None
|
|
136
|
+
except httpx.RequestError as e:
|
|
137
|
+
if attempt <= max_retries:
|
|
138
|
+
await _handle_retry(
|
|
139
|
+
attempt, max_retries, retry_delay, url, f"Network error: {e}"
|
|
140
|
+
)
|
|
141
|
+
continue
|
|
142
|
+
else:
|
|
143
|
+
raise HudNetworkError(f"Network error: {e!s}") from None
|
|
144
|
+
except ssl.SSLError as e:
|
|
145
|
+
if attempt <= max_retries:
|
|
146
|
+
await _handle_retry(attempt, max_retries, retry_delay, url, f"SSL error: {e}")
|
|
147
|
+
continue
|
|
148
|
+
else:
|
|
149
|
+
raise HudNetworkError(f"SSL error: {e!s}") from None
|
|
150
|
+
except Exception as e:
|
|
151
|
+
raise HudRequestError(f"Unexpected error: {e!s}") from None
|
|
152
|
+
raise HudRequestError(f"Request failed after {max_retries} retries with unknown error")
|
|
153
|
+
finally:
|
|
154
|
+
if should_close_client:
|
|
155
|
+
await client.aclose()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def make_request_sync(
|
|
159
|
+
method: str,
|
|
160
|
+
url: str,
|
|
161
|
+
json: Any | None = None,
|
|
162
|
+
api_key: str | None = None,
|
|
163
|
+
max_retries: int = 4,
|
|
164
|
+
retry_delay: float = 2.0,
|
|
165
|
+
*,
|
|
166
|
+
client: httpx.Client | None = None,
|
|
167
|
+
) -> dict[str, Any]:
|
|
168
|
+
"""
|
|
169
|
+
Make a synchronous HTTP request to the HUD API.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
method: HTTP method (GET, POST, etc.)
|
|
173
|
+
url: Full URL for the request
|
|
174
|
+
json: Optional JSON serializable data
|
|
175
|
+
api_key: API key for authentication
|
|
176
|
+
max_retries: Maximum number of retries
|
|
177
|
+
retry_delay: Delay between retries
|
|
178
|
+
client: Optional custom httpx.Client
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
dict: JSON response from the server
|
|
182
|
+
|
|
183
|
+
Raises:
|
|
184
|
+
HudAuthenticationError: If API key is missing or invalid.
|
|
185
|
+
HudRequestError: If the request fails with a non-retryable status code.
|
|
186
|
+
HudNetworkError: If there are network-related issues.
|
|
187
|
+
HudTimeoutError: If the request times out.
|
|
188
|
+
"""
|
|
189
|
+
if not api_key:
|
|
190
|
+
raise HudAuthenticationError("API key is required but not provided")
|
|
191
|
+
|
|
192
|
+
headers = {"Authorization": f"Bearer {api_key}"}
|
|
193
|
+
retry_status_codes = [502, 503, 504]
|
|
194
|
+
attempt = 0
|
|
195
|
+
should_close_client = False
|
|
196
|
+
|
|
197
|
+
if client is None:
|
|
198
|
+
client = _create_default_sync_client()
|
|
199
|
+
should_close_client = True
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
while attempt <= max_retries:
|
|
203
|
+
attempt += 1
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
response = client.request(method=method, url=url, json=json, headers=headers)
|
|
207
|
+
|
|
208
|
+
# Check if we got a retriable status code
|
|
209
|
+
if response.status_code in retry_status_codes and attempt <= max_retries:
|
|
210
|
+
retry_time = retry_delay * (2 ** (attempt - 1)) # Exponential backoff
|
|
211
|
+
logger.debug(
|
|
212
|
+
"Received status %d from %s, retrying in %.2f seconds (attempt %d/%d)",
|
|
213
|
+
response.status_code,
|
|
214
|
+
url,
|
|
215
|
+
retry_time,
|
|
216
|
+
attempt,
|
|
217
|
+
max_retries,
|
|
218
|
+
)
|
|
219
|
+
time.sleep(retry_time)
|
|
220
|
+
continue
|
|
221
|
+
|
|
222
|
+
response.raise_for_status()
|
|
223
|
+
result = response.json()
|
|
224
|
+
return result
|
|
225
|
+
except httpx.TimeoutException as e:
|
|
226
|
+
raise HudTimeoutError(f"Request timed out: {e!s}") from None
|
|
227
|
+
except httpx.HTTPStatusError as e:
|
|
228
|
+
raise HudRequestError.from_httpx_error(e) from None
|
|
229
|
+
except httpx.RequestError as e:
|
|
230
|
+
if attempt <= max_retries:
|
|
231
|
+
retry_time = retry_delay * (2 ** (attempt - 1))
|
|
232
|
+
logger.debug(
|
|
233
|
+
"Network error %s from %s, retrying in %.2f seconds (attempt %d/%d)",
|
|
234
|
+
str(e),
|
|
235
|
+
url,
|
|
236
|
+
retry_time,
|
|
237
|
+
attempt,
|
|
238
|
+
max_retries,
|
|
239
|
+
)
|
|
240
|
+
time.sleep(retry_time)
|
|
241
|
+
continue
|
|
242
|
+
else:
|
|
243
|
+
raise HudNetworkError(f"Network error: {e!s}") from None
|
|
244
|
+
except ssl.SSLError as e:
|
|
245
|
+
if attempt <= max_retries:
|
|
246
|
+
retry_time = retry_delay * (2 ** (attempt - 1)) # Exponential backoff
|
|
247
|
+
logger.debug(
|
|
248
|
+
"SSL error %s from %s, retrying in %.2f seconds (attempt %d/%d)",
|
|
249
|
+
str(e),
|
|
250
|
+
url,
|
|
251
|
+
retry_time,
|
|
252
|
+
attempt,
|
|
253
|
+
max_retries,
|
|
254
|
+
)
|
|
255
|
+
time.sleep(retry_time)
|
|
256
|
+
continue
|
|
257
|
+
else:
|
|
258
|
+
raise HudNetworkError(f"SSL error: {e!s}") from None
|
|
259
|
+
except Exception as e:
|
|
260
|
+
raise HudRequestError(f"Unexpected error: {e!s}") from None
|
|
261
|
+
raise HudRequestError(f"Request failed after {max_retries} retries with unknown error")
|
|
262
|
+
finally:
|
|
263
|
+
if should_close_client:
|
|
264
|
+
client.close()
|