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.

Files changed (130) hide show
  1. hud/__init__.py +22 -22
  2. hud/agents/__init__.py +13 -15
  3. hud/agents/base.py +599 -599
  4. hud/agents/claude.py +373 -373
  5. hud/agents/langchain.py +261 -250
  6. hud/agents/misc/__init__.py +7 -7
  7. hud/agents/misc/response_agent.py +82 -80
  8. hud/agents/openai.py +352 -352
  9. hud/agents/openai_chat_generic.py +154 -154
  10. hud/agents/tests/__init__.py +1 -1
  11. hud/agents/tests/test_base.py +742 -742
  12. hud/agents/tests/test_claude.py +324 -324
  13. hud/agents/tests/test_client.py +363 -363
  14. hud/agents/tests/test_openai.py +237 -237
  15. hud/cli/__init__.py +617 -617
  16. hud/cli/__main__.py +8 -8
  17. hud/cli/analyze.py +371 -371
  18. hud/cli/analyze_metadata.py +230 -230
  19. hud/cli/build.py +498 -427
  20. hud/cli/clone.py +185 -185
  21. hud/cli/cursor.py +92 -92
  22. hud/cli/debug.py +392 -392
  23. hud/cli/docker_utils.py +83 -83
  24. hud/cli/init.py +280 -281
  25. hud/cli/interactive.py +353 -353
  26. hud/cli/mcp_server.py +764 -756
  27. hud/cli/pull.py +330 -336
  28. hud/cli/push.py +404 -370
  29. hud/cli/remote_runner.py +311 -311
  30. hud/cli/runner.py +160 -160
  31. hud/cli/tests/__init__.py +3 -3
  32. hud/cli/tests/test_analyze.py +284 -284
  33. hud/cli/tests/test_cli_init.py +265 -265
  34. hud/cli/tests/test_cli_main.py +27 -27
  35. hud/cli/tests/test_clone.py +142 -142
  36. hud/cli/tests/test_cursor.py +253 -253
  37. hud/cli/tests/test_debug.py +453 -453
  38. hud/cli/tests/test_mcp_server.py +139 -139
  39. hud/cli/tests/test_utils.py +388 -388
  40. hud/cli/utils.py +263 -263
  41. hud/clients/README.md +143 -143
  42. hud/clients/__init__.py +16 -16
  43. hud/clients/base.py +378 -379
  44. hud/clients/fastmcp.py +222 -222
  45. hud/clients/mcp_use.py +298 -278
  46. hud/clients/tests/__init__.py +1 -1
  47. hud/clients/tests/test_client_integration.py +111 -111
  48. hud/clients/tests/test_fastmcp.py +342 -342
  49. hud/clients/tests/test_protocol.py +188 -188
  50. hud/clients/utils/__init__.py +1 -1
  51. hud/clients/utils/retry_transport.py +160 -160
  52. hud/datasets.py +327 -322
  53. hud/misc/__init__.py +1 -1
  54. hud/misc/claude_plays_pokemon.py +292 -292
  55. hud/otel/__init__.py +35 -35
  56. hud/otel/collector.py +142 -142
  57. hud/otel/config.py +164 -164
  58. hud/otel/context.py +536 -536
  59. hud/otel/exporters.py +366 -366
  60. hud/otel/instrumentation.py +97 -97
  61. hud/otel/processors.py +118 -118
  62. hud/otel/tests/__init__.py +1 -1
  63. hud/otel/tests/test_processors.py +197 -197
  64. hud/server/__init__.py +5 -5
  65. hud/server/context.py +114 -114
  66. hud/server/helper/__init__.py +5 -5
  67. hud/server/low_level.py +132 -132
  68. hud/server/server.py +170 -166
  69. hud/server/tests/__init__.py +3 -3
  70. hud/settings.py +73 -73
  71. hud/shared/__init__.py +5 -5
  72. hud/shared/exceptions.py +180 -180
  73. hud/shared/requests.py +264 -264
  74. hud/shared/tests/test_exceptions.py +157 -157
  75. hud/shared/tests/test_requests.py +275 -275
  76. hud/telemetry/__init__.py +25 -25
  77. hud/telemetry/instrument.py +379 -379
  78. hud/telemetry/job.py +309 -309
  79. hud/telemetry/replay.py +74 -74
  80. hud/telemetry/trace.py +83 -83
  81. hud/tools/__init__.py +33 -33
  82. hud/tools/base.py +365 -365
  83. hud/tools/bash.py +161 -161
  84. hud/tools/computer/__init__.py +15 -15
  85. hud/tools/computer/anthropic.py +437 -437
  86. hud/tools/computer/hud.py +376 -376
  87. hud/tools/computer/openai.py +295 -295
  88. hud/tools/computer/settings.py +82 -82
  89. hud/tools/edit.py +314 -314
  90. hud/tools/executors/__init__.py +30 -30
  91. hud/tools/executors/base.py +539 -539
  92. hud/tools/executors/pyautogui.py +621 -621
  93. hud/tools/executors/tests/__init__.py +1 -1
  94. hud/tools/executors/tests/test_base_executor.py +338 -338
  95. hud/tools/executors/tests/test_pyautogui_executor.py +165 -165
  96. hud/tools/executors/xdo.py +511 -511
  97. hud/tools/playwright.py +412 -412
  98. hud/tools/tests/__init__.py +3 -3
  99. hud/tools/tests/test_base.py +282 -282
  100. hud/tools/tests/test_bash.py +158 -158
  101. hud/tools/tests/test_bash_extended.py +197 -197
  102. hud/tools/tests/test_computer.py +425 -425
  103. hud/tools/tests/test_computer_actions.py +34 -34
  104. hud/tools/tests/test_edit.py +259 -259
  105. hud/tools/tests/test_init.py +27 -27
  106. hud/tools/tests/test_playwright_tool.py +183 -183
  107. hud/tools/tests/test_tools.py +145 -145
  108. hud/tools/tests/test_utils.py +156 -156
  109. hud/tools/types.py +72 -72
  110. hud/tools/utils.py +50 -50
  111. hud/types.py +136 -136
  112. hud/utils/__init__.py +10 -10
  113. hud/utils/async_utils.py +65 -65
  114. hud/utils/design.py +236 -168
  115. hud/utils/mcp.py +55 -55
  116. hud/utils/progress.py +149 -149
  117. hud/utils/telemetry.py +66 -66
  118. hud/utils/tests/test_async_utils.py +173 -173
  119. hud/utils/tests/test_init.py +17 -17
  120. hud/utils/tests/test_progress.py +261 -261
  121. hud/utils/tests/test_telemetry.py +82 -82
  122. hud/utils/tests/test_version.py +8 -8
  123. hud/version.py +7 -7
  124. {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/METADATA +10 -8
  125. hud_python-0.4.3.dist-info/RECORD +131 -0
  126. {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/licenses/LICENSE +21 -21
  127. hud/agents/art.py +0 -101
  128. hud_python-0.4.1.dist-info/RECORD +0 -132
  129. {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/WHEEL +0 -0
  130. {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()