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
@@ -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
+ ]