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
|
@@ -1,275 +1,275 @@
|
|
|
1
|
-
"""Tests for the HTTP request utilities in the HUD API."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from http import HTTPStatus
|
|
6
|
-
from typing import TYPE_CHECKING, Any
|
|
7
|
-
from unittest.mock import AsyncMock, Mock, patch
|
|
8
|
-
|
|
9
|
-
import httpx
|
|
10
|
-
import pytest
|
|
11
|
-
|
|
12
|
-
from hud.shared.exceptions import (
|
|
13
|
-
HudAuthenticationError,
|
|
14
|
-
HudNetworkError,
|
|
15
|
-
HudRequestError,
|
|
16
|
-
HudTimeoutError,
|
|
17
|
-
)
|
|
18
|
-
from hud.shared.requests import (
|
|
19
|
-
_handle_retry,
|
|
20
|
-
make_request,
|
|
21
|
-
make_request_sync,
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
if TYPE_CHECKING:
|
|
25
|
-
from collections.abc import Callable
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _create_mock_response(
|
|
29
|
-
status_code: int = 200,
|
|
30
|
-
json_data: dict[str, Any] | None = None,
|
|
31
|
-
raise_exception: Exception | None = None,
|
|
32
|
-
) -> Callable[[httpx.Request], httpx.Response]:
|
|
33
|
-
"""Create a mock response handler for httpx.MockTransport."""
|
|
34
|
-
|
|
35
|
-
def handler(request: httpx.Request) -> httpx.Response:
|
|
36
|
-
if "Authorization" not in request.headers:
|
|
37
|
-
return httpx.Response(HTTPStatus.UNAUTHORIZED, json={"error": "Unauthorized"})
|
|
38
|
-
|
|
39
|
-
if raise_exception:
|
|
40
|
-
raise raise_exception
|
|
41
|
-
|
|
42
|
-
return httpx.Response(status_code, json=json_data or {"result": "success"}, request=request)
|
|
43
|
-
|
|
44
|
-
return handler
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
@pytest.mark.asyncio
|
|
48
|
-
async def test_handle_retry():
|
|
49
|
-
"""Test the retry handler."""
|
|
50
|
-
with patch("asyncio.sleep") as mock_sleep:
|
|
51
|
-
mock_sleep.return_value = None
|
|
52
|
-
await _handle_retry(
|
|
53
|
-
attempt=2,
|
|
54
|
-
max_retries=3,
|
|
55
|
-
retry_delay=1.0,
|
|
56
|
-
url="https://example.com",
|
|
57
|
-
error_msg="Test error",
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
# Check exponential backoff formula: delay * (2 ^ (attempt - 1))
|
|
61
|
-
mock_sleep.assert_awaited_once_with(2.0)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
@pytest.mark.asyncio
|
|
65
|
-
async def test_make_request_success():
|
|
66
|
-
"""Test successful async request."""
|
|
67
|
-
expected_data = {"id": "123", "name": "test"}
|
|
68
|
-
async_client = httpx.AsyncClient(
|
|
69
|
-
transport=httpx.MockTransport(_create_mock_response(200, expected_data))
|
|
70
|
-
)
|
|
71
|
-
result = await make_request(
|
|
72
|
-
"GET", "https://api.test.com/data", api_key="test-key", client=async_client
|
|
73
|
-
)
|
|
74
|
-
assert result == expected_data
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
@pytest.mark.asyncio
|
|
78
|
-
async def test_make_request_no_api_key():
|
|
79
|
-
"""Test request without API key."""
|
|
80
|
-
with pytest.raises(HudAuthenticationError):
|
|
81
|
-
await make_request("GET", "https://api.test.com/data", api_key=None)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
@pytest.mark.asyncio
|
|
85
|
-
async def test_make_request_http_error():
|
|
86
|
-
"""Test HTTP error handling."""
|
|
87
|
-
async_client = httpx.AsyncClient(
|
|
88
|
-
transport=httpx.MockTransport(_create_mock_response(404, {"error": "Not found"}))
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
with pytest.raises(HudRequestError) as excinfo:
|
|
92
|
-
await make_request(
|
|
93
|
-
"GET", "https://api.test.com/data", api_key="test-key", client=async_client
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
assert "404" in str(excinfo.value)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
@pytest.mark.asyncio
|
|
100
|
-
async def test_make_request_network_error():
|
|
101
|
-
"""Test network error handling with retry exhaustion."""
|
|
102
|
-
request_error = httpx.RequestError(
|
|
103
|
-
"Connection error", request=httpx.Request("GET", "https://api.test.com")
|
|
104
|
-
)
|
|
105
|
-
async_client = httpx.AsyncClient(
|
|
106
|
-
transport=httpx.MockTransport(_create_mock_response(raise_exception=request_error))
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
# Replace handle_retry to avoid sleep
|
|
110
|
-
with patch("hud.shared.requests._handle_retry", AsyncMock()) as mock_retry:
|
|
111
|
-
mock_retry.return_value = None
|
|
112
|
-
|
|
113
|
-
with pytest.raises(HudNetworkError) as excinfo:
|
|
114
|
-
await make_request(
|
|
115
|
-
"GET",
|
|
116
|
-
"https://api.test.com/data",
|
|
117
|
-
api_key="test-key",
|
|
118
|
-
max_retries=2,
|
|
119
|
-
retry_delay=0.01,
|
|
120
|
-
client=async_client,
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
assert "Connection error" in str(excinfo.value)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
@pytest.mark.asyncio
|
|
127
|
-
async def test_make_request_timeout():
|
|
128
|
-
"""Test timeout error handling."""
|
|
129
|
-
timeout_error = httpx.TimeoutException(
|
|
130
|
-
"Request timed out", request=httpx.Request("GET", "https://api.test.com")
|
|
131
|
-
)
|
|
132
|
-
async_client = httpx.AsyncClient(
|
|
133
|
-
transport=httpx.MockTransport(_create_mock_response(raise_exception=timeout_error))
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
with pytest.raises(HudTimeoutError) as excinfo:
|
|
137
|
-
await make_request(
|
|
138
|
-
"GET", "https://api.test.com/data", api_key="test-key", client=async_client
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
assert "timed out" in str(excinfo.value)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
@pytest.mark.asyncio
|
|
145
|
-
async def test_make_request_unexpected_error():
|
|
146
|
-
"""Test handling of unexpected errors."""
|
|
147
|
-
unexpected_error = ValueError("Unexpected error")
|
|
148
|
-
async_client = httpx.AsyncClient(
|
|
149
|
-
transport=httpx.MockTransport(_create_mock_response(raise_exception=unexpected_error))
|
|
150
|
-
)
|
|
151
|
-
with pytest.raises(HudRequestError) as excinfo:
|
|
152
|
-
await make_request(
|
|
153
|
-
"GET", "https://api.test.com/data", api_key="test-key", client=async_client
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
assert "Unexpected error" in str(excinfo.value)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
@pytest.mark.asyncio
|
|
160
|
-
async def test_make_request_auto_client_creation():
|
|
161
|
-
"""Test automatic client creation when not provided."""
|
|
162
|
-
with patch("hud.shared.requests._create_default_async_client") as mock_create_client:
|
|
163
|
-
mock_client = AsyncMock()
|
|
164
|
-
mock_client.request.return_value = httpx.Response(
|
|
165
|
-
200, json={"result": "success"}, request=httpx.Request("GET", "https://api.test.com")
|
|
166
|
-
)
|
|
167
|
-
mock_client.aclose = AsyncMock()
|
|
168
|
-
mock_create_client.return_value = mock_client
|
|
169
|
-
|
|
170
|
-
result = await make_request("GET", "https://api.test.com/data", api_key="test-key")
|
|
171
|
-
|
|
172
|
-
assert result == {"result": "success"}
|
|
173
|
-
mock_client.aclose.assert_awaited_once()
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
def test_make_request_sync_success():
|
|
177
|
-
"""Test successful sync request."""
|
|
178
|
-
expected_data = {"id": "123", "name": "test"}
|
|
179
|
-
sync_client = httpx.Client(
|
|
180
|
-
transport=httpx.MockTransport(_create_mock_response(200, expected_data))
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
result = make_request_sync(
|
|
184
|
-
"GET", "https://api.test.com/data", api_key="test-key", client=sync_client
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
assert result == expected_data
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
def test_make_request_sync_no_api_key():
|
|
191
|
-
"""Test sync request without API key."""
|
|
192
|
-
with pytest.raises(HudAuthenticationError):
|
|
193
|
-
make_request_sync("GET", "https://api.test.com/data", api_key=None)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
def test_make_request_sync_http_error():
|
|
197
|
-
"""Test HTTP error handling."""
|
|
198
|
-
sync_client = httpx.Client(
|
|
199
|
-
transport=httpx.MockTransport(_create_mock_response(404, {"error": "Not found"}))
|
|
200
|
-
)
|
|
201
|
-
with pytest.raises(HudRequestError) as excinfo:
|
|
202
|
-
make_request_sync(
|
|
203
|
-
"GET", "https://api.test.com/data", api_key="test-key", client=sync_client
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
assert "404" in str(excinfo.value)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def test_make_request_sync_network_error():
|
|
210
|
-
"""Test network error handling with retry exhaustion."""
|
|
211
|
-
request_error = httpx.RequestError(
|
|
212
|
-
"Connection error", request=httpx.Request("GET", "https://api.test.com")
|
|
213
|
-
)
|
|
214
|
-
sync_client = httpx.Client(
|
|
215
|
-
transport=httpx.MockTransport(_create_mock_response(raise_exception=request_error))
|
|
216
|
-
)
|
|
217
|
-
with patch("time.sleep", lambda _: None):
|
|
218
|
-
with pytest.raises(HudNetworkError) as excinfo:
|
|
219
|
-
make_request_sync(
|
|
220
|
-
"GET",
|
|
221
|
-
"https://api.test.com/data",
|
|
222
|
-
api_key="test-key",
|
|
223
|
-
max_retries=2,
|
|
224
|
-
retry_delay=0.01,
|
|
225
|
-
client=sync_client,
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
assert "Connection error" in str(excinfo.value)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
def test_make_request_sync_timeout():
|
|
232
|
-
"""Test timeout error handling."""
|
|
233
|
-
timeout_error = httpx.TimeoutException(
|
|
234
|
-
"Request timed out", request=httpx.Request("GET", "https://api.test.com")
|
|
235
|
-
)
|
|
236
|
-
sync_client = httpx.Client(
|
|
237
|
-
transport=httpx.MockTransport(_create_mock_response(raise_exception=timeout_error))
|
|
238
|
-
)
|
|
239
|
-
with pytest.raises(HudTimeoutError) as excinfo:
|
|
240
|
-
make_request_sync(
|
|
241
|
-
"GET", "https://api.test.com/data", api_key="test-key", client=sync_client
|
|
242
|
-
)
|
|
243
|
-
|
|
244
|
-
assert "timed out" in str(excinfo.value)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def test_make_request_sync_unexpected_error():
|
|
248
|
-
"""Test handling of unexpected errors."""
|
|
249
|
-
unexpected_error = ValueError("Unexpected error")
|
|
250
|
-
sync_client = httpx.Client(
|
|
251
|
-
transport=httpx.MockTransport(_create_mock_response(raise_exception=unexpected_error))
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
with pytest.raises(HudRequestError) as excinfo:
|
|
255
|
-
make_request_sync(
|
|
256
|
-
"GET", "https://api.test.com/data", api_key="test-key", client=sync_client
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
assert "Unexpected error" in str(excinfo.value)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
def test_make_request_sync_auto_client_creation():
|
|
263
|
-
"""Test automatic client creation when not provided."""
|
|
264
|
-
with patch("hud.shared.requests._create_default_sync_client") as mock_create_client:
|
|
265
|
-
mock_client = Mock()
|
|
266
|
-
mock_client.request.return_value = httpx.Response(
|
|
267
|
-
200, json={"result": "success"}, request=httpx.Request("GET", "https://api.test.com")
|
|
268
|
-
)
|
|
269
|
-
mock_client.close = Mock()
|
|
270
|
-
mock_create_client.return_value = mock_client
|
|
271
|
-
|
|
272
|
-
result = make_request_sync("GET", "https://api.test.com/data", api_key="test-key")
|
|
273
|
-
|
|
274
|
-
assert result == {"result": "success"}
|
|
275
|
-
mock_client.close.assert_called_once()
|
|
1
|
+
"""Tests for the HTTP request utilities in the HUD API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from http import HTTPStatus
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
from unittest.mock import AsyncMock, Mock, patch
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from hud.shared.exceptions import (
|
|
13
|
+
HudAuthenticationError,
|
|
14
|
+
HudNetworkError,
|
|
15
|
+
HudRequestError,
|
|
16
|
+
HudTimeoutError,
|
|
17
|
+
)
|
|
18
|
+
from hud.shared.requests import (
|
|
19
|
+
_handle_retry,
|
|
20
|
+
make_request,
|
|
21
|
+
make_request_sync,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from collections.abc import Callable
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _create_mock_response(
|
|
29
|
+
status_code: int = 200,
|
|
30
|
+
json_data: dict[str, Any] | None = None,
|
|
31
|
+
raise_exception: Exception | None = None,
|
|
32
|
+
) -> Callable[[httpx.Request], httpx.Response]:
|
|
33
|
+
"""Create a mock response handler for httpx.MockTransport."""
|
|
34
|
+
|
|
35
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
36
|
+
if "Authorization" not in request.headers:
|
|
37
|
+
return httpx.Response(HTTPStatus.UNAUTHORIZED, json={"error": "Unauthorized"})
|
|
38
|
+
|
|
39
|
+
if raise_exception:
|
|
40
|
+
raise raise_exception
|
|
41
|
+
|
|
42
|
+
return httpx.Response(status_code, json=json_data or {"result": "success"}, request=request)
|
|
43
|
+
|
|
44
|
+
return handler
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.mark.asyncio
|
|
48
|
+
async def test_handle_retry():
|
|
49
|
+
"""Test the retry handler."""
|
|
50
|
+
with patch("asyncio.sleep") as mock_sleep:
|
|
51
|
+
mock_sleep.return_value = None
|
|
52
|
+
await _handle_retry(
|
|
53
|
+
attempt=2,
|
|
54
|
+
max_retries=3,
|
|
55
|
+
retry_delay=1.0,
|
|
56
|
+
url="https://example.com",
|
|
57
|
+
error_msg="Test error",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Check exponential backoff formula: delay * (2 ^ (attempt - 1))
|
|
61
|
+
mock_sleep.assert_awaited_once_with(2.0)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.mark.asyncio
|
|
65
|
+
async def test_make_request_success():
|
|
66
|
+
"""Test successful async request."""
|
|
67
|
+
expected_data = {"id": "123", "name": "test"}
|
|
68
|
+
async_client = httpx.AsyncClient(
|
|
69
|
+
transport=httpx.MockTransport(_create_mock_response(200, expected_data))
|
|
70
|
+
)
|
|
71
|
+
result = await make_request(
|
|
72
|
+
"GET", "https://api.test.com/data", api_key="test-key", client=async_client
|
|
73
|
+
)
|
|
74
|
+
assert result == expected_data
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@pytest.mark.asyncio
|
|
78
|
+
async def test_make_request_no_api_key():
|
|
79
|
+
"""Test request without API key."""
|
|
80
|
+
with pytest.raises(HudAuthenticationError):
|
|
81
|
+
await make_request("GET", "https://api.test.com/data", api_key=None)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@pytest.mark.asyncio
|
|
85
|
+
async def test_make_request_http_error():
|
|
86
|
+
"""Test HTTP error handling."""
|
|
87
|
+
async_client = httpx.AsyncClient(
|
|
88
|
+
transport=httpx.MockTransport(_create_mock_response(404, {"error": "Not found"}))
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
with pytest.raises(HudRequestError) as excinfo:
|
|
92
|
+
await make_request(
|
|
93
|
+
"GET", "https://api.test.com/data", api_key="test-key", client=async_client
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
assert "404" in str(excinfo.value)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.mark.asyncio
|
|
100
|
+
async def test_make_request_network_error():
|
|
101
|
+
"""Test network error handling with retry exhaustion."""
|
|
102
|
+
request_error = httpx.RequestError(
|
|
103
|
+
"Connection error", request=httpx.Request("GET", "https://api.test.com")
|
|
104
|
+
)
|
|
105
|
+
async_client = httpx.AsyncClient(
|
|
106
|
+
transport=httpx.MockTransport(_create_mock_response(raise_exception=request_error))
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Replace handle_retry to avoid sleep
|
|
110
|
+
with patch("hud.shared.requests._handle_retry", AsyncMock()) as mock_retry:
|
|
111
|
+
mock_retry.return_value = None
|
|
112
|
+
|
|
113
|
+
with pytest.raises(HudNetworkError) as excinfo:
|
|
114
|
+
await make_request(
|
|
115
|
+
"GET",
|
|
116
|
+
"https://api.test.com/data",
|
|
117
|
+
api_key="test-key",
|
|
118
|
+
max_retries=2,
|
|
119
|
+
retry_delay=0.01,
|
|
120
|
+
client=async_client,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
assert "Connection error" in str(excinfo.value)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@pytest.mark.asyncio
|
|
127
|
+
async def test_make_request_timeout():
|
|
128
|
+
"""Test timeout error handling."""
|
|
129
|
+
timeout_error = httpx.TimeoutException(
|
|
130
|
+
"Request timed out", request=httpx.Request("GET", "https://api.test.com")
|
|
131
|
+
)
|
|
132
|
+
async_client = httpx.AsyncClient(
|
|
133
|
+
transport=httpx.MockTransport(_create_mock_response(raise_exception=timeout_error))
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
with pytest.raises(HudTimeoutError) as excinfo:
|
|
137
|
+
await make_request(
|
|
138
|
+
"GET", "https://api.test.com/data", api_key="test-key", client=async_client
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
assert "timed out" in str(excinfo.value)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@pytest.mark.asyncio
|
|
145
|
+
async def test_make_request_unexpected_error():
|
|
146
|
+
"""Test handling of unexpected errors."""
|
|
147
|
+
unexpected_error = ValueError("Unexpected error")
|
|
148
|
+
async_client = httpx.AsyncClient(
|
|
149
|
+
transport=httpx.MockTransport(_create_mock_response(raise_exception=unexpected_error))
|
|
150
|
+
)
|
|
151
|
+
with pytest.raises(HudRequestError) as excinfo:
|
|
152
|
+
await make_request(
|
|
153
|
+
"GET", "https://api.test.com/data", api_key="test-key", client=async_client
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
assert "Unexpected error" in str(excinfo.value)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@pytest.mark.asyncio
|
|
160
|
+
async def test_make_request_auto_client_creation():
|
|
161
|
+
"""Test automatic client creation when not provided."""
|
|
162
|
+
with patch("hud.shared.requests._create_default_async_client") as mock_create_client:
|
|
163
|
+
mock_client = AsyncMock()
|
|
164
|
+
mock_client.request.return_value = httpx.Response(
|
|
165
|
+
200, json={"result": "success"}, request=httpx.Request("GET", "https://api.test.com")
|
|
166
|
+
)
|
|
167
|
+
mock_client.aclose = AsyncMock()
|
|
168
|
+
mock_create_client.return_value = mock_client
|
|
169
|
+
|
|
170
|
+
result = await make_request("GET", "https://api.test.com/data", api_key="test-key")
|
|
171
|
+
|
|
172
|
+
assert result == {"result": "success"}
|
|
173
|
+
mock_client.aclose.assert_awaited_once()
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_make_request_sync_success():
|
|
177
|
+
"""Test successful sync request."""
|
|
178
|
+
expected_data = {"id": "123", "name": "test"}
|
|
179
|
+
sync_client = httpx.Client(
|
|
180
|
+
transport=httpx.MockTransport(_create_mock_response(200, expected_data))
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
result = make_request_sync(
|
|
184
|
+
"GET", "https://api.test.com/data", api_key="test-key", client=sync_client
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
assert result == expected_data
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def test_make_request_sync_no_api_key():
|
|
191
|
+
"""Test sync request without API key."""
|
|
192
|
+
with pytest.raises(HudAuthenticationError):
|
|
193
|
+
make_request_sync("GET", "https://api.test.com/data", api_key=None)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def test_make_request_sync_http_error():
|
|
197
|
+
"""Test HTTP error handling."""
|
|
198
|
+
sync_client = httpx.Client(
|
|
199
|
+
transport=httpx.MockTransport(_create_mock_response(404, {"error": "Not found"}))
|
|
200
|
+
)
|
|
201
|
+
with pytest.raises(HudRequestError) as excinfo:
|
|
202
|
+
make_request_sync(
|
|
203
|
+
"GET", "https://api.test.com/data", api_key="test-key", client=sync_client
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
assert "404" in str(excinfo.value)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def test_make_request_sync_network_error():
|
|
210
|
+
"""Test network error handling with retry exhaustion."""
|
|
211
|
+
request_error = httpx.RequestError(
|
|
212
|
+
"Connection error", request=httpx.Request("GET", "https://api.test.com")
|
|
213
|
+
)
|
|
214
|
+
sync_client = httpx.Client(
|
|
215
|
+
transport=httpx.MockTransport(_create_mock_response(raise_exception=request_error))
|
|
216
|
+
)
|
|
217
|
+
with patch("time.sleep", lambda _: None):
|
|
218
|
+
with pytest.raises(HudNetworkError) as excinfo:
|
|
219
|
+
make_request_sync(
|
|
220
|
+
"GET",
|
|
221
|
+
"https://api.test.com/data",
|
|
222
|
+
api_key="test-key",
|
|
223
|
+
max_retries=2,
|
|
224
|
+
retry_delay=0.01,
|
|
225
|
+
client=sync_client,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
assert "Connection error" in str(excinfo.value)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def test_make_request_sync_timeout():
|
|
232
|
+
"""Test timeout error handling."""
|
|
233
|
+
timeout_error = httpx.TimeoutException(
|
|
234
|
+
"Request timed out", request=httpx.Request("GET", "https://api.test.com")
|
|
235
|
+
)
|
|
236
|
+
sync_client = httpx.Client(
|
|
237
|
+
transport=httpx.MockTransport(_create_mock_response(raise_exception=timeout_error))
|
|
238
|
+
)
|
|
239
|
+
with pytest.raises(HudTimeoutError) as excinfo:
|
|
240
|
+
make_request_sync(
|
|
241
|
+
"GET", "https://api.test.com/data", api_key="test-key", client=sync_client
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
assert "timed out" in str(excinfo.value)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def test_make_request_sync_unexpected_error():
|
|
248
|
+
"""Test handling of unexpected errors."""
|
|
249
|
+
unexpected_error = ValueError("Unexpected error")
|
|
250
|
+
sync_client = httpx.Client(
|
|
251
|
+
transport=httpx.MockTransport(_create_mock_response(raise_exception=unexpected_error))
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
with pytest.raises(HudRequestError) as excinfo:
|
|
255
|
+
make_request_sync(
|
|
256
|
+
"GET", "https://api.test.com/data", api_key="test-key", client=sync_client
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
assert "Unexpected error" in str(excinfo.value)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def test_make_request_sync_auto_client_creation():
|
|
263
|
+
"""Test automatic client creation when not provided."""
|
|
264
|
+
with patch("hud.shared.requests._create_default_sync_client") as mock_create_client:
|
|
265
|
+
mock_client = Mock()
|
|
266
|
+
mock_client.request.return_value = httpx.Response(
|
|
267
|
+
200, json={"result": "success"}, request=httpx.Request("GET", "https://api.test.com")
|
|
268
|
+
)
|
|
269
|
+
mock_client.close = Mock()
|
|
270
|
+
mock_create_client.return_value = mock_client
|
|
271
|
+
|
|
272
|
+
result = make_request_sync("GET", "https://api.test.com/data", api_key="test-key")
|
|
273
|
+
|
|
274
|
+
assert result == {"result": "success"}
|
|
275
|
+
mock_client.close.assert_called_once()
|
hud/telemetry/__init__.py
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
"""HUD Telemetry - User-facing APIs for tracing and job management.
|
|
2
|
-
|
|
3
|
-
This module provides the main telemetry APIs that users interact with:
|
|
4
|
-
- trace: Context manager for tracing code execution
|
|
5
|
-
- job: Context manager and utilities for job management
|
|
6
|
-
- instrument: Decorator for instrumenting functions
|
|
7
|
-
- get_trace: Retrieve collected traces for replay/analysis
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
from .instrument import instrument
|
|
13
|
-
from .job import Job, create_job, job
|
|
14
|
-
from .replay import clear_trace, get_trace
|
|
15
|
-
from .trace import trace
|
|
16
|
-
|
|
17
|
-
__all__ = [
|
|
18
|
-
"Job",
|
|
19
|
-
"clear_trace",
|
|
20
|
-
"create_job",
|
|
21
|
-
"get_trace",
|
|
22
|
-
"instrument",
|
|
23
|
-
"job",
|
|
24
|
-
"trace",
|
|
25
|
-
]
|
|
1
|
+
"""HUD Telemetry - User-facing APIs for tracing and job management.
|
|
2
|
+
|
|
3
|
+
This module provides the main telemetry APIs that users interact with:
|
|
4
|
+
- trace: Context manager for tracing code execution
|
|
5
|
+
- job: Context manager and utilities for job management
|
|
6
|
+
- instrument: Decorator for instrumenting functions
|
|
7
|
+
- get_trace: Retrieve collected traces for replay/analysis
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from .instrument import instrument
|
|
13
|
+
from .job import Job, create_job, job
|
|
14
|
+
from .replay import clear_trace, get_trace
|
|
15
|
+
from .trace import trace
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"Job",
|
|
19
|
+
"clear_trace",
|
|
20
|
+
"create_job",
|
|
21
|
+
"get_trace",
|
|
22
|
+
"instrument",
|
|
23
|
+
"job",
|
|
24
|
+
"trace",
|
|
25
|
+
]
|