tracia 0.0.1__py3-none-any.whl → 0.1.0__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.
- tracia/__init__.py +150 -3
- tracia/_client.py +1100 -0
- tracia/_constants.py +39 -0
- tracia/_errors.py +87 -0
- tracia/_http.py +362 -0
- tracia/_llm.py +898 -0
- tracia/_session.py +244 -0
- tracia/_streaming.py +135 -0
- tracia/_types.py +540 -0
- tracia/_utils.py +116 -0
- tracia/py.typed +0 -0
- tracia/resources/__init__.py +6 -0
- tracia/resources/prompts.py +273 -0
- tracia/resources/spans.py +227 -0
- tracia-0.1.0.dist-info/METADATA +277 -0
- tracia-0.1.0.dist-info/RECORD +18 -0
- tracia-0.0.1.dist-info/METADATA +0 -52
- tracia-0.0.1.dist-info/RECORD +0 -5
- {tracia-0.0.1.dist-info → tracia-0.1.0.dist-info}/WHEEL +0 -0
- {tracia-0.0.1.dist-info → tracia-0.1.0.dist-info}/licenses/LICENSE +0 -0
tracia/_constants.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Constants and configuration for the Tracia SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
# SDK Version (defined here to avoid circular imports)
|
|
6
|
+
SDK_VERSION = "0.1.0"
|
|
7
|
+
|
|
8
|
+
# API Configuration
|
|
9
|
+
BASE_URL = "https://app.tracia.io"
|
|
10
|
+
|
|
11
|
+
# Timeout Configuration (in milliseconds)
|
|
12
|
+
DEFAULT_TIMEOUT_MS = 120_000 # 2 minutes
|
|
13
|
+
|
|
14
|
+
# Span Management
|
|
15
|
+
MAX_PENDING_SPANS = 1000
|
|
16
|
+
SPAN_RETRY_ATTEMPTS = 2
|
|
17
|
+
SPAN_RETRY_DELAY_MS = 500
|
|
18
|
+
|
|
19
|
+
# Span Status
|
|
20
|
+
SPAN_STATUS_SUCCESS = "SUCCESS"
|
|
21
|
+
SPAN_STATUS_ERROR = "ERROR"
|
|
22
|
+
|
|
23
|
+
# ID Prefixes
|
|
24
|
+
SPAN_ID_PREFIX = "sp_"
|
|
25
|
+
TRACE_ID_PREFIX = "tr_"
|
|
26
|
+
|
|
27
|
+
# Environment Variable Names for Provider API Keys
|
|
28
|
+
ENV_VAR_MAP = {
|
|
29
|
+
"openai": "OPENAI_API_KEY",
|
|
30
|
+
"anthropic": "ANTHROPIC_API_KEY",
|
|
31
|
+
"google": "GOOGLE_API_KEY",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Evaluation Constants
|
|
35
|
+
class Eval:
|
|
36
|
+
"""Evaluation value constants."""
|
|
37
|
+
|
|
38
|
+
POSITIVE = 1
|
|
39
|
+
NEGATIVE = 0
|
tracia/_errors.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Error handling for the Tracia SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from enum import Enum
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TraciaErrorCode(str, Enum):
|
|
10
|
+
"""Error codes for Tracia SDK errors."""
|
|
11
|
+
|
|
12
|
+
UNAUTHORIZED = "UNAUTHORIZED"
|
|
13
|
+
NOT_FOUND = "NOT_FOUND"
|
|
14
|
+
CONFLICT = "CONFLICT"
|
|
15
|
+
PROVIDER_ERROR = "PROVIDER_ERROR"
|
|
16
|
+
MISSING_VARIABLES = "MISSING_VARIABLES"
|
|
17
|
+
INVALID_REQUEST = "INVALID_REQUEST"
|
|
18
|
+
NETWORK_ERROR = "NETWORK_ERROR"
|
|
19
|
+
TIMEOUT = "TIMEOUT"
|
|
20
|
+
ABORTED = "ABORTED"
|
|
21
|
+
UNKNOWN = "UNKNOWN"
|
|
22
|
+
MISSING_PROVIDER_SDK = "MISSING_PROVIDER_SDK"
|
|
23
|
+
MISSING_PROVIDER_API_KEY = "MISSING_PROVIDER_API_KEY"
|
|
24
|
+
UNSUPPORTED_MODEL = "UNSUPPORTED_MODEL"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TraciaError(Exception):
|
|
28
|
+
"""Exception raised for Tracia SDK errors."""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
code: TraciaErrorCode,
|
|
33
|
+
message: str,
|
|
34
|
+
status_code: int | None = None,
|
|
35
|
+
) -> None:
|
|
36
|
+
self.code = code
|
|
37
|
+
self.message = message
|
|
38
|
+
self.status_code = status_code
|
|
39
|
+
super().__init__(message)
|
|
40
|
+
|
|
41
|
+
def __str__(self) -> str:
|
|
42
|
+
if self.status_code:
|
|
43
|
+
return f"[{self.code.value}] {self.message} (status: {self.status_code})"
|
|
44
|
+
return f"[{self.code.value}] {self.message}"
|
|
45
|
+
|
|
46
|
+
def __repr__(self) -> str:
|
|
47
|
+
return f"TraciaError(code={self.code!r}, message={self.message!r}, status_code={self.status_code!r})"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Patterns for sanitizing sensitive data from error messages
|
|
51
|
+
_SENSITIVE_PATTERNS = [
|
|
52
|
+
re.compile(r"sk-[a-zA-Z0-9]{20,}"), # OpenAI API keys
|
|
53
|
+
re.compile(r"sk-ant-[a-zA-Z0-9-]{20,}"), # Anthropic API keys
|
|
54
|
+
re.compile(r"Bearer\s+[a-zA-Z0-9._-]+"), # Bearer tokens
|
|
55
|
+
re.compile(r"Basic\s+[a-zA-Z0-9+/=]+"), # Basic auth
|
|
56
|
+
re.compile(r"Authorization:\s*[^\s]+", re.IGNORECASE), # Auth headers
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def sanitize_error_message(message: str) -> str:
|
|
61
|
+
"""Remove sensitive data from error messages.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
message: The error message to sanitize.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
The sanitized error message.
|
|
68
|
+
"""
|
|
69
|
+
result = message
|
|
70
|
+
for pattern in _SENSITIVE_PATTERNS:
|
|
71
|
+
result = pattern.sub("[REDACTED]", result)
|
|
72
|
+
return result
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def map_api_error_code(code: str) -> TraciaErrorCode:
|
|
76
|
+
"""Map an API error code string to a TraciaErrorCode.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
code: The error code from the API response.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
The corresponding TraciaErrorCode.
|
|
83
|
+
"""
|
|
84
|
+
try:
|
|
85
|
+
return TraciaErrorCode(code)
|
|
86
|
+
except ValueError:
|
|
87
|
+
return TraciaErrorCode.UNKNOWN
|
tracia/_http.py
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"""HTTP client for the Tracia API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, TypeVar
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from ._constants import BASE_URL, DEFAULT_TIMEOUT_MS, SDK_VERSION
|
|
10
|
+
from ._errors import TraciaError, TraciaErrorCode, map_api_error_code
|
|
11
|
+
|
|
12
|
+
T = TypeVar("T")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class HttpClient:
|
|
16
|
+
"""Synchronous HTTP client for the Tracia API."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
api_key: str,
|
|
21
|
+
base_url: str = BASE_URL,
|
|
22
|
+
timeout_ms: int = DEFAULT_TIMEOUT_MS,
|
|
23
|
+
) -> None:
|
|
24
|
+
"""Initialize the HTTP client.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
api_key: The Tracia API key.
|
|
28
|
+
base_url: The base URL for the API.
|
|
29
|
+
timeout_ms: Request timeout in milliseconds.
|
|
30
|
+
"""
|
|
31
|
+
self._api_key = api_key
|
|
32
|
+
self._base_url = base_url.rstrip("/")
|
|
33
|
+
self._timeout = timeout_ms / 1000.0 # Convert to seconds
|
|
34
|
+
|
|
35
|
+
self._client = httpx.Client(
|
|
36
|
+
base_url=self._base_url,
|
|
37
|
+
headers=self._get_headers(),
|
|
38
|
+
timeout=httpx.Timeout(self._timeout),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def _get_headers(self) -> dict[str, str]:
|
|
42
|
+
"""Get the default headers for requests."""
|
|
43
|
+
return {
|
|
44
|
+
"Content-Type": "application/json",
|
|
45
|
+
"Authorization": f"Bearer {self._api_key}",
|
|
46
|
+
"User-Agent": f"tracia-sdk-python/{SDK_VERSION}",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
def _handle_response(self, response: httpx.Response) -> Any:
|
|
50
|
+
"""Handle the API response and raise errors if needed."""
|
|
51
|
+
if response.status_code >= 400:
|
|
52
|
+
try:
|
|
53
|
+
data = response.json()
|
|
54
|
+
error_data = data.get("error", {})
|
|
55
|
+
code = map_api_error_code(error_data.get("code", "UNKNOWN"))
|
|
56
|
+
message = error_data.get("message", "Unknown error")
|
|
57
|
+
except Exception:
|
|
58
|
+
code = TraciaErrorCode.UNKNOWN
|
|
59
|
+
message = response.text or f"HTTP {response.status_code}"
|
|
60
|
+
|
|
61
|
+
raise TraciaError(
|
|
62
|
+
code=code,
|
|
63
|
+
message=message,
|
|
64
|
+
status_code=response.status_code,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if response.status_code == 204:
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
return response.json()
|
|
71
|
+
|
|
72
|
+
def get(self, path: str, params: dict[str, Any] | None = None) -> Any:
|
|
73
|
+
"""Make a GET request.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
path: The API path.
|
|
77
|
+
params: Optional query parameters.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
The JSON response.
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
TraciaError: If the request fails.
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
response = self._client.get(path, params=params)
|
|
87
|
+
return self._handle_response(response)
|
|
88
|
+
except httpx.TimeoutException as e:
|
|
89
|
+
raise TraciaError(
|
|
90
|
+
code=TraciaErrorCode.TIMEOUT,
|
|
91
|
+
message=f"Request timed out after {int(self._timeout * 1000)}ms",
|
|
92
|
+
) from e
|
|
93
|
+
except httpx.RequestError as e:
|
|
94
|
+
raise TraciaError(
|
|
95
|
+
code=TraciaErrorCode.NETWORK_ERROR,
|
|
96
|
+
message=f"Network error: {e}",
|
|
97
|
+
) from e
|
|
98
|
+
|
|
99
|
+
def post(self, path: str, body: Any = None) -> Any:
|
|
100
|
+
"""Make a POST request.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
path: The API path.
|
|
104
|
+
body: The request body.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
The JSON response.
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
TraciaError: If the request fails.
|
|
111
|
+
"""
|
|
112
|
+
try:
|
|
113
|
+
response = self._client.post(path, json=body)
|
|
114
|
+
return self._handle_response(response)
|
|
115
|
+
except httpx.TimeoutException as e:
|
|
116
|
+
raise TraciaError(
|
|
117
|
+
code=TraciaErrorCode.TIMEOUT,
|
|
118
|
+
message=f"Request timed out after {int(self._timeout * 1000)}ms",
|
|
119
|
+
) from e
|
|
120
|
+
except httpx.RequestError as e:
|
|
121
|
+
raise TraciaError(
|
|
122
|
+
code=TraciaErrorCode.NETWORK_ERROR,
|
|
123
|
+
message=f"Network error: {e}",
|
|
124
|
+
) from e
|
|
125
|
+
|
|
126
|
+
def put(self, path: str, body: Any = None) -> Any:
|
|
127
|
+
"""Make a PUT request.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
path: The API path.
|
|
131
|
+
body: The request body.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
The JSON response.
|
|
135
|
+
|
|
136
|
+
Raises:
|
|
137
|
+
TraciaError: If the request fails.
|
|
138
|
+
"""
|
|
139
|
+
try:
|
|
140
|
+
response = self._client.put(path, json=body)
|
|
141
|
+
return self._handle_response(response)
|
|
142
|
+
except httpx.TimeoutException as e:
|
|
143
|
+
raise TraciaError(
|
|
144
|
+
code=TraciaErrorCode.TIMEOUT,
|
|
145
|
+
message=f"Request timed out after {int(self._timeout * 1000)}ms",
|
|
146
|
+
) from e
|
|
147
|
+
except httpx.RequestError as e:
|
|
148
|
+
raise TraciaError(
|
|
149
|
+
code=TraciaErrorCode.NETWORK_ERROR,
|
|
150
|
+
message=f"Network error: {e}",
|
|
151
|
+
) from e
|
|
152
|
+
|
|
153
|
+
def delete(self, path: str) -> Any:
|
|
154
|
+
"""Make a DELETE request.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
path: The API path.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
The JSON response (or None for 204).
|
|
161
|
+
|
|
162
|
+
Raises:
|
|
163
|
+
TraciaError: If the request fails.
|
|
164
|
+
"""
|
|
165
|
+
try:
|
|
166
|
+
response = self._client.delete(path)
|
|
167
|
+
return self._handle_response(response)
|
|
168
|
+
except httpx.TimeoutException as e:
|
|
169
|
+
raise TraciaError(
|
|
170
|
+
code=TraciaErrorCode.TIMEOUT,
|
|
171
|
+
message=f"Request timed out after {int(self._timeout * 1000)}ms",
|
|
172
|
+
) from e
|
|
173
|
+
except httpx.RequestError as e:
|
|
174
|
+
raise TraciaError(
|
|
175
|
+
code=TraciaErrorCode.NETWORK_ERROR,
|
|
176
|
+
message=f"Network error: {e}",
|
|
177
|
+
) from e
|
|
178
|
+
|
|
179
|
+
def close(self) -> None:
|
|
180
|
+
"""Close the HTTP client."""
|
|
181
|
+
self._client.close()
|
|
182
|
+
|
|
183
|
+
def __enter__(self) -> "HttpClient":
|
|
184
|
+
return self
|
|
185
|
+
|
|
186
|
+
def __exit__(self, *args: Any) -> None:
|
|
187
|
+
self.close()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class AsyncHttpClient:
|
|
191
|
+
"""Asynchronous HTTP client for the Tracia API."""
|
|
192
|
+
|
|
193
|
+
def __init__(
|
|
194
|
+
self,
|
|
195
|
+
api_key: str,
|
|
196
|
+
base_url: str = BASE_URL,
|
|
197
|
+
timeout_ms: int = DEFAULT_TIMEOUT_MS,
|
|
198
|
+
) -> None:
|
|
199
|
+
"""Initialize the async HTTP client.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
api_key: The Tracia API key.
|
|
203
|
+
base_url: The base URL for the API.
|
|
204
|
+
timeout_ms: Request timeout in milliseconds.
|
|
205
|
+
"""
|
|
206
|
+
self._api_key = api_key
|
|
207
|
+
self._base_url = base_url.rstrip("/")
|
|
208
|
+
self._timeout = timeout_ms / 1000.0 # Convert to seconds
|
|
209
|
+
|
|
210
|
+
self._client = httpx.AsyncClient(
|
|
211
|
+
base_url=self._base_url,
|
|
212
|
+
headers=self._get_headers(),
|
|
213
|
+
timeout=httpx.Timeout(self._timeout),
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
def _get_headers(self) -> dict[str, str]:
|
|
217
|
+
"""Get the default headers for requests."""
|
|
218
|
+
return {
|
|
219
|
+
"Content-Type": "application/json",
|
|
220
|
+
"Authorization": f"Bearer {self._api_key}",
|
|
221
|
+
"User-Agent": f"tracia-sdk-python/{SDK_VERSION}",
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
def _handle_response(self, response: httpx.Response) -> Any:
|
|
225
|
+
"""Handle the API response and raise errors if needed."""
|
|
226
|
+
if response.status_code >= 400:
|
|
227
|
+
try:
|
|
228
|
+
data = response.json()
|
|
229
|
+
error_data = data.get("error", {})
|
|
230
|
+
code = map_api_error_code(error_data.get("code", "UNKNOWN"))
|
|
231
|
+
message = error_data.get("message", "Unknown error")
|
|
232
|
+
except Exception:
|
|
233
|
+
code = TraciaErrorCode.UNKNOWN
|
|
234
|
+
message = response.text or f"HTTP {response.status_code}"
|
|
235
|
+
|
|
236
|
+
raise TraciaError(
|
|
237
|
+
code=code,
|
|
238
|
+
message=message,
|
|
239
|
+
status_code=response.status_code,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if response.status_code == 204:
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
return response.json()
|
|
246
|
+
|
|
247
|
+
async def get(self, path: str, params: dict[str, Any] | None = None) -> Any:
|
|
248
|
+
"""Make a GET request.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
path: The API path.
|
|
252
|
+
params: Optional query parameters.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
The JSON response.
|
|
256
|
+
|
|
257
|
+
Raises:
|
|
258
|
+
TraciaError: If the request fails.
|
|
259
|
+
"""
|
|
260
|
+
try:
|
|
261
|
+
response = await self._client.get(path, params=params)
|
|
262
|
+
return self._handle_response(response)
|
|
263
|
+
except httpx.TimeoutException as e:
|
|
264
|
+
raise TraciaError(
|
|
265
|
+
code=TraciaErrorCode.TIMEOUT,
|
|
266
|
+
message=f"Request timed out after {int(self._timeout * 1000)}ms",
|
|
267
|
+
) from e
|
|
268
|
+
except httpx.RequestError as e:
|
|
269
|
+
raise TraciaError(
|
|
270
|
+
code=TraciaErrorCode.NETWORK_ERROR,
|
|
271
|
+
message=f"Network error: {e}",
|
|
272
|
+
) from e
|
|
273
|
+
|
|
274
|
+
async def post(self, path: str, body: Any = None) -> Any:
|
|
275
|
+
"""Make a POST request.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
path: The API path.
|
|
279
|
+
body: The request body.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
The JSON response.
|
|
283
|
+
|
|
284
|
+
Raises:
|
|
285
|
+
TraciaError: If the request fails.
|
|
286
|
+
"""
|
|
287
|
+
try:
|
|
288
|
+
response = await self._client.post(path, json=body)
|
|
289
|
+
return self._handle_response(response)
|
|
290
|
+
except httpx.TimeoutException as e:
|
|
291
|
+
raise TraciaError(
|
|
292
|
+
code=TraciaErrorCode.TIMEOUT,
|
|
293
|
+
message=f"Request timed out after {int(self._timeout * 1000)}ms",
|
|
294
|
+
) from e
|
|
295
|
+
except httpx.RequestError as e:
|
|
296
|
+
raise TraciaError(
|
|
297
|
+
code=TraciaErrorCode.NETWORK_ERROR,
|
|
298
|
+
message=f"Network error: {e}",
|
|
299
|
+
) from e
|
|
300
|
+
|
|
301
|
+
async def put(self, path: str, body: Any = None) -> Any:
|
|
302
|
+
"""Make a PUT request.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
path: The API path.
|
|
306
|
+
body: The request body.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
The JSON response.
|
|
310
|
+
|
|
311
|
+
Raises:
|
|
312
|
+
TraciaError: If the request fails.
|
|
313
|
+
"""
|
|
314
|
+
try:
|
|
315
|
+
response = await self._client.put(path, json=body)
|
|
316
|
+
return self._handle_response(response)
|
|
317
|
+
except httpx.TimeoutException as e:
|
|
318
|
+
raise TraciaError(
|
|
319
|
+
code=TraciaErrorCode.TIMEOUT,
|
|
320
|
+
message=f"Request timed out after {int(self._timeout * 1000)}ms",
|
|
321
|
+
) from e
|
|
322
|
+
except httpx.RequestError as e:
|
|
323
|
+
raise TraciaError(
|
|
324
|
+
code=TraciaErrorCode.NETWORK_ERROR,
|
|
325
|
+
message=f"Network error: {e}",
|
|
326
|
+
) from e
|
|
327
|
+
|
|
328
|
+
async def delete(self, path: str) -> Any:
|
|
329
|
+
"""Make a DELETE request.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
path: The API path.
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
The JSON response (or None for 204).
|
|
336
|
+
|
|
337
|
+
Raises:
|
|
338
|
+
TraciaError: If the request fails.
|
|
339
|
+
"""
|
|
340
|
+
try:
|
|
341
|
+
response = await self._client.delete(path)
|
|
342
|
+
return self._handle_response(response)
|
|
343
|
+
except httpx.TimeoutException as e:
|
|
344
|
+
raise TraciaError(
|
|
345
|
+
code=TraciaErrorCode.TIMEOUT,
|
|
346
|
+
message=f"Request timed out after {int(self._timeout * 1000)}ms",
|
|
347
|
+
) from e
|
|
348
|
+
except httpx.RequestError as e:
|
|
349
|
+
raise TraciaError(
|
|
350
|
+
code=TraciaErrorCode.NETWORK_ERROR,
|
|
351
|
+
message=f"Network error: {e}",
|
|
352
|
+
) from e
|
|
353
|
+
|
|
354
|
+
async def aclose(self) -> None:
|
|
355
|
+
"""Close the async HTTP client."""
|
|
356
|
+
await self._client.aclose()
|
|
357
|
+
|
|
358
|
+
async def __aenter__(self) -> "AsyncHttpClient":
|
|
359
|
+
return self
|
|
360
|
+
|
|
361
|
+
async def __aexit__(self, *args: Any) -> None:
|
|
362
|
+
await self.aclose()
|