trismik 0.9.0__py3-none-any.whl → 0.9.4__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.
trismik/client_async.py CHANGED
@@ -1,280 +1,362 @@
1
- from typing import List, Any, Optional
2
-
3
- import httpx
4
-
5
- from ._mapper import TrismikResponseMapper
6
- from ._utils import TrismikUtils
7
- from .exceptions import TrismikApiError
8
- from .types import (
9
- TrismikTest,
10
- TrismikAuth,
11
- TrismikSession,
12
- TrismikItem,
13
- TrismikResult,
14
- TrismikResponse,
15
- )
16
-
17
-
18
- class TrismikAsyncClient:
19
- _serviceUrl: str = "https://trismik.e-psychometrics.com/api"
20
-
21
- def __init__(
22
- self,
23
- service_url: Optional[str] = None,
24
- api_key: Optional[str] = None,
25
- http_client: Optional[httpx.AsyncClient] | None = None,
26
- ) -> None:
27
- """
28
- Initializes a new Trismik client (async version).
29
-
30
- Args:
31
- service_url (Optional[str]): URL of the Trismik service.
32
- api_key (Optional[str]): API key for the Trismik service.
33
- http_client (Optional[httpx.Client]): HTTP client to use for requests.
34
-
35
- Raises:
36
- TrismikError: If service_url or api_key are not provided and not found in environment.
37
- TrismikApiError: If API request fails.
38
- """
39
- self._service_url = TrismikUtils.option(
40
- service_url, self._serviceUrl, "TRISMIK_SERVICE_URL"
41
- )
42
- self._api_key = TrismikUtils.required_option(
43
- api_key, "api_key", "TRISMIK_API_KEY"
44
- )
45
- self._http_client = http_client or httpx.AsyncClient(
46
- base_url=self._service_url)
47
-
48
- async def authenticate(self) -> TrismikAuth:
49
- """
50
- Authenticates with the Trismik service.
51
-
52
- Returns:
53
- TrismikAuth: Authentication token.
54
-
55
- Raises:
56
- TrismikApiError: If API request fails.
57
- """
58
- try:
59
- url = "/client/auth"
60
- body = {"apiKey": self._api_key}
61
- response = await self._http_client.post(url, json=body)
62
- response.raise_for_status()
63
- json = response.json()
64
- return TrismikResponseMapper.to_auth(json)
65
- except httpx.HTTPStatusError as e:
66
- raise TrismikApiError(
67
- TrismikUtils.get_error_message(e.response)) from e
68
- except httpx.HTTPError as e:
69
- raise TrismikApiError(str(e)) from e
70
-
71
- async def refresh_token(self, token: str) -> TrismikAuth:
72
- """
73
- Refreshes the authentication token.
74
-
75
- Args:
76
- token (str): Current authentication token.
77
-
78
- Returns:
79
- TrismikAuth: New authentication token.
80
-
81
- Raises:
82
- TrismikApiError: If API request fails.
83
- """
84
- try:
85
- url = "/client/token"
86
- headers = {"Authorization": f"Bearer {token}"}
87
- response = await self._http_client.get(url, headers=headers)
88
- response.raise_for_status()
89
- json = response.json()
90
- return TrismikResponseMapper.to_auth(json)
91
- except httpx.HTTPStatusError as e:
92
- raise TrismikApiError(
93
- TrismikUtils.get_error_message(e.response)) from e
94
- except httpx.HTTPError as e:
95
- raise TrismikApiError(str(e)) from e
96
-
97
- async def available_tests(self, token: str) -> List[TrismikTest]:
98
- """
99
- Retrieves a list of available tests.
100
-
101
- Args:
102
- token (str): Authentication token.
103
-
104
- Returns:
105
- List[TrismikTest]: List of available tests.
106
-
107
- Raises:
108
- TrismikApiError: If API request fails.
109
- """
110
- try:
111
- url = "/client/tests"
112
- headers = {"Authorization": f"Bearer {token}"}
113
- response = await self._http_client.get(url, headers=headers)
114
- response.raise_for_status()
115
- json = response.json()
116
- return TrismikResponseMapper.to_tests(json)
117
- except httpx.HTTPStatusError as e:
118
- raise TrismikApiError(
119
- TrismikUtils.get_error_message(e.response)) from e
120
- except httpx.HTTPError as e:
121
- raise TrismikApiError(str(e)) from e
122
-
123
- async def create_session(self, test_id: str, token: str) -> TrismikSession:
124
- """
125
- Creates a new session for a test.
126
-
127
- Args:
128
- test_id (str): ID of the test.
129
- token (str): Authentication token.
130
-
131
- Returns:
132
- TrismikSession: New session
133
-
134
- Raises:
135
- TrismikApiError: If API request fails.
136
- """
137
- try:
138
- url = "/client/sessions"
139
- headers = {"Authorization": f"Bearer {token}"}
140
- body = {"testId": test_id}
141
- response = await self._http_client.post(url, headers=headers,
142
- json=body)
143
- response.raise_for_status()
144
- json = response.json()
145
- return TrismikResponseMapper.to_session(json)
146
- except httpx.HTTPStatusError as e:
147
- raise TrismikApiError(
148
- TrismikUtils.get_error_message(e.response)) from e
149
- except httpx.HTTPError as e:
150
- raise TrismikApiError(str(e)) from e
151
-
152
- async def current_item(
153
- self,
154
- session_url: str,
155
- token: str
156
- ) -> TrismikItem:
157
- """
158
- Retrieves the current test item.
159
-
160
- Args:
161
- session_url (str): URL of the session.
162
- token (str): Authentication token.
163
-
164
- Returns:
165
- TrismikItem: Current test item.
166
-
167
- Raises:
168
- TrismikApiError: If API request fails.
169
- """
170
- try:
171
- url = f"{session_url}/item"
172
- headers = {"Authorization": f"Bearer {token}"}
173
- response = await self._http_client.get(url, headers=headers)
174
- response.raise_for_status()
175
- json = response.json()
176
- return TrismikResponseMapper.to_item(json)
177
- except httpx.HTTPStatusError as e:
178
- raise TrismikApiError(
179
- TrismikUtils.get_error_message(e.response)) from e
180
- except httpx.HTTPError as e:
181
- raise TrismikApiError(str(e)) from e
182
-
183
- async def respond_to_current_item(
184
- self,
185
- session_url: str,
186
- value: Any,
187
- token: str
188
- ) -> TrismikItem | None:
189
- """
190
- Responds to the current test item.
191
-
192
- Args:
193
- session_url (str): URL of the session.
194
- value (Any): Response value.
195
- token (str): Authentication token.
196
-
197
- Returns:
198
- TrismikItem | None: Next test item or None if session is finished.
199
-
200
- Raises:
201
- TrismikApiError: If API request fails.
202
- """
203
- try:
204
- url = f"{session_url}/item"
205
- body = {"value": value}
206
- headers = {"Authorization": f"Bearer {token}"}
207
- response = await self._http_client.post(
208
- url, headers=headers, json=body
209
- )
210
- response.raise_for_status()
211
- if response.status_code == 204:
212
- return None
213
- else:
214
- json = response.json()
215
- return TrismikResponseMapper.to_item(json)
216
- except httpx.HTTPStatusError as e:
217
- raise TrismikApiError(
218
- TrismikUtils.get_error_message(e.response)) from e
219
- except httpx.HTTPError as e:
220
- raise TrismikApiError(str(e)) from e
221
-
222
- async def results(self,
223
- session_url: str,
224
- token: str
225
- ) -> List[TrismikResult]:
226
- """
227
- Retrieves the results of a session.
228
-
229
- Args:
230
- session_url (str): URL of the session.
231
- token (str): Authentication token.
232
-
233
- Returns:
234
- List[TrismikResult]: Results of the session.
235
-
236
- Raises:
237
- TrismikApiError: If API request fails.
238
- """
239
- try:
240
- url = f"{session_url}/results"
241
- headers = {"Authorization": f"Bearer {token}"}
242
- response = await self._http_client.get(url, headers=headers)
243
- response.raise_for_status()
244
- json = response.json()
245
- return TrismikResponseMapper.to_results(json)
246
- except httpx.HTTPStatusError as e:
247
- raise TrismikApiError(
248
- TrismikUtils.get_error_message(e.response)) from e
249
- except httpx.HTTPError as e:
250
- raise TrismikApiError(str(e)) from e
251
-
252
- async def responses(self,
253
- session_url: str,
254
- token: str
255
- ) -> List[TrismikResponse]:
256
- """
257
- Retrieves responses to session items.
258
-
259
- Args:
260
- session_url (str): URL of the session.
261
- token (str): Authentication token.
262
-
263
- Returns:
264
- List[TrismikResponse]: Responses of the session.
265
-
266
- Raises:
267
- TrismikApiError: If API request fails.
268
- """
269
- try:
270
- url = f"{session_url}/responses"
271
- headers = {"Authorization": f"Bearer {token}"}
272
- response = await self._http_client.get(url, headers=headers)
273
- response.raise_for_status()
274
- json = response.json()
275
- return TrismikResponseMapper.to_responses(json)
276
- except httpx.HTTPStatusError as e:
277
- raise TrismikApiError(
278
- TrismikUtils.get_error_message(e.response)) from e
279
- except httpx.HTTPError as e:
280
- raise TrismikApiError(str(e)) from e
1
+ """
2
+ Trismik async client for interacting with the Trismik API.
3
+
4
+ This module provides an asynchronous client for interacting with the Trismik
5
+ API. It uses httpx for making HTTP requests.
6
+ """
7
+
8
+ from typing import List, Optional
9
+
10
+ import httpx
11
+
12
+ from trismik._mapper import TrismikResponseMapper
13
+ from trismik._utils import TrismikUtils
14
+ from trismik.exceptions import (
15
+ TrismikApiError,
16
+ TrismikPayloadTooLargeError,
17
+ TrismikValidationError,
18
+ )
19
+ from trismik.settings import client_settings, environment_settings
20
+ from trismik.types import (
21
+ TrismikClassicEvalRequest,
22
+ TrismikClassicEvalResponse,
23
+ TrismikDataset,
24
+ TrismikMeResponse,
25
+ TrismikReplayRequest,
26
+ TrismikReplayResponse,
27
+ TrismikRunMetadata,
28
+ TrismikRunResponse,
29
+ TrismikRunSummary,
30
+ )
31
+
32
+
33
+ class TrismikAsyncClient:
34
+ """
35
+ Asynchronous client for the Trismik API.
36
+
37
+ This class provides an asynchronous interface to interact with the Trismik
38
+ API, handling authentication, dataset runs, and responses.
39
+ """
40
+
41
+ def __init__(
42
+ self,
43
+ service_url: Optional[str] = None,
44
+ api_key: Optional[str] = None,
45
+ http_client: Optional[httpx.AsyncClient] = None,
46
+ ) -> None:
47
+ """
48
+ Initialize the Trismik async client.
49
+
50
+ Args:
51
+ service_url (Optional[str]): URL of the Trismik service.
52
+ api_key (Optional[str]): API key for the Trismik service.
53
+ http_client (Optional[httpx.AsyncClient]): HTTP client to use for
54
+ requests.
55
+
56
+ Raises:
57
+ TrismikError: If service_url or api_key are not provided and not
58
+ found in environment.
59
+ TrismikApiError: If API request fails.
60
+ """
61
+ self._service_url = TrismikUtils.option(
62
+ service_url,
63
+ client_settings["endpoint"],
64
+ environment_settings["trismik_service_url"],
65
+ )
66
+ self._api_key = TrismikUtils.required_option(
67
+ api_key, "api_key", environment_settings["trismik_api_key"]
68
+ )
69
+
70
+ # Set default headers with API key
71
+ default_headers = {"x-api-key": self._api_key}
72
+
73
+ self._http_client = http_client or httpx.AsyncClient(
74
+ base_url=self._service_url, headers=default_headers
75
+ )
76
+
77
+ def _handle_http_error(self, e: httpx.HTTPStatusError) -> Exception:
78
+ """
79
+ Handle HTTP errors and return appropriate Trismik exceptions.
80
+
81
+ Args:
82
+ e (httpx.HTTPStatusError): The HTTP status error to handle.
83
+
84
+ Returns:
85
+ Exception: The appropriate Trismik exception to raise.
86
+ """
87
+ if e.response.status_code == 413:
88
+ # Handle payload too large error specifically
89
+ try:
90
+ backend_message = e.response.json().get(
91
+ "detail", "Payload too large."
92
+ )
93
+ except Exception:
94
+ backend_message = "Payload too large."
95
+ return TrismikPayloadTooLargeError(backend_message)
96
+ elif e.response.status_code == 422:
97
+ # Handle validation error specifically
98
+ try:
99
+ backend_message = e.response.json().get(
100
+ "detail", "Validation failed."
101
+ )
102
+ except Exception:
103
+ backend_message = "Validation failed."
104
+ return TrismikValidationError(backend_message)
105
+ else:
106
+ return TrismikApiError(TrismikUtils.get_error_message(e.response))
107
+
108
+ async def list_datasets(self) -> List[TrismikDataset]:
109
+ """
110
+ Get a list of available datasets.
111
+
112
+ Returns:
113
+ List[TrismikDataset]: List of available datasets.
114
+
115
+ Raises:
116
+ TrismikApiError: If API request fails.
117
+ """
118
+ try:
119
+ url = "/datasets"
120
+ response = await self._http_client.get(url)
121
+ response.raise_for_status()
122
+ json = response.json()
123
+ return TrismikResponseMapper.to_datasets(json)
124
+ except httpx.HTTPStatusError as e:
125
+ raise TrismikApiError(
126
+ TrismikUtils.get_error_message(e.response)
127
+ ) from e
128
+ except httpx.HTTPError as e:
129
+ raise TrismikApiError(str(e)) from e
130
+
131
+ async def start_run(
132
+ self,
133
+ dataset_id: str,
134
+ project_id: str,
135
+ experiment: str,
136
+ metadata: Optional[TrismikRunMetadata] = None,
137
+ ) -> TrismikRunResponse:
138
+ """
139
+ Start a new run for a dataset and get the first item.
140
+
141
+ Args:
142
+ dataset_id (str): ID of the dataset.
143
+ project_id (str): ID of the project.
144
+ experiment (str): Name of the experiment.
145
+ metadata (Optional[TrismikRunMetadata]): Run metadata.
146
+
147
+ Returns:
148
+ TrismikRunResponse: Run response.
149
+
150
+ Raises:
151
+ TrismikPayloadTooLargeError: If the request payload exceeds the
152
+ server's size limit.
153
+ TrismikApiError: If API request fails.
154
+ """
155
+ try:
156
+ url = "/runs/start"
157
+ body = {
158
+ "datasetId": dataset_id,
159
+ "projectId": project_id,
160
+ "experiment": experiment,
161
+ "metadata": metadata.toDict() if metadata else {},
162
+ }
163
+ response = await self._http_client.post(url, json=body)
164
+ response.raise_for_status()
165
+ json = response.json()
166
+ return TrismikResponseMapper.to_run_response(json)
167
+ except httpx.HTTPStatusError as e:
168
+ raise self._handle_http_error(e) from e
169
+ except httpx.HTTPError as e:
170
+ raise TrismikApiError(str(e)) from e
171
+
172
+ async def continue_run(
173
+ self, run_id: str, item_choice_id: str
174
+ ) -> TrismikRunResponse:
175
+ """
176
+ Continue a run: respond to the current item and get the next one.
177
+
178
+ Args:
179
+ run_id (str): ID of the run.
180
+ item_choice_id (str): ID of the chosen item response.
181
+
182
+ Returns:
183
+ TrismikRunResponse: Run response.
184
+
185
+ Raises:
186
+ TrismikApiError: If API request fails.
187
+ """
188
+ try:
189
+ url = "/runs/continue"
190
+ body = {"itemChoiceId": item_choice_id, "runId": run_id}
191
+ response = await self._http_client.post(url, json=body)
192
+ response.raise_for_status()
193
+ json = response.json()
194
+ return TrismikResponseMapper.to_run_response(json)
195
+ except httpx.HTTPStatusError as e:
196
+ raise TrismikApiError(
197
+ TrismikUtils.get_error_message(e.response)
198
+ ) from e
199
+ except httpx.HTTPError as e:
200
+ raise TrismikApiError(str(e)) from e
201
+
202
+ async def run_summary(self, run_id: str) -> TrismikRunSummary:
203
+ """
204
+ Get run summary including responses, dataset, and state.
205
+
206
+ Args:
207
+ run_id (str): ID of the run.
208
+
209
+ Returns:
210
+ TrismikRunSummary: Complete run summary with responses,
211
+ dataset, state, and metadata.
212
+
213
+ Raises:
214
+ TrismikApiError: If API request fails.
215
+ """
216
+ try:
217
+ url = f"/runs/{run_id}"
218
+ response = await self._http_client.get(url)
219
+ response.raise_for_status()
220
+ json = response.json()
221
+ return TrismikResponseMapper.to_run_summary(json)
222
+ except httpx.HTTPStatusError as e:
223
+ raise TrismikApiError(
224
+ TrismikUtils.get_error_message(e.response)
225
+ ) from e
226
+ except httpx.HTTPError as e:
227
+ raise TrismikApiError(str(e)) from e
228
+
229
+ async def submit_replay(
230
+ self,
231
+ run_id: str,
232
+ replay_request: TrismikReplayRequest,
233
+ metadata: Optional[TrismikRunMetadata] = None,
234
+ ) -> TrismikReplayResponse:
235
+ """
236
+ Submit a replay of a run with specific responses.
237
+
238
+ Args:
239
+ run_id (str): ID of the run to replay.
240
+ replay_request (TrismikReplayRequest): Request containing responses
241
+ to submit.
242
+ metadata (Optional[TrismikRunMetadata]): Run metadata.
243
+
244
+ Returns:
245
+ TrismikReplayResponse: Response from the replay endpoint.
246
+
247
+ Raises:
248
+ TrismikPayloadTooLargeError: If the request payload exceeds the
249
+ server's size limit.
250
+ TrismikValidationError: If the request fails validation (e.g.,
251
+ duplicate item IDs, unknown item IDs).
252
+ TrismikApiError: If API request fails.
253
+ """
254
+ try:
255
+ url = f"runs/{run_id}/replay"
256
+
257
+ # Convert TrismikReplayRequestItem objects to dictionaries
258
+ responses_dict = [
259
+ {"itemId": item.itemId, "itemChoiceId": item.itemChoiceId}
260
+ for item in replay_request.responses
261
+ ]
262
+
263
+ body = {
264
+ "responses": responses_dict,
265
+ "metadata": metadata.toDict() if metadata else {},
266
+ }
267
+ response = await self._http_client.post(url, json=body)
268
+ response.raise_for_status()
269
+ json = response.json()
270
+ return TrismikResponseMapper.to_replay_response(json)
271
+ except httpx.HTTPStatusError as e:
272
+ raise self._handle_http_error(e) from e
273
+ except httpx.HTTPError as e:
274
+ raise TrismikApiError(str(e)) from e
275
+
276
+ async def me(self) -> TrismikMeResponse:
277
+ """
278
+ Get current user information.
279
+
280
+ Returns:
281
+ TrismikMeResponse: User information including validity and payload.
282
+
283
+ Raises:
284
+ TrismikApiError: If API request fails.
285
+ """
286
+ try:
287
+ url = "../admin/api-keys/me"
288
+ response = await self._http_client.get(url)
289
+ response.raise_for_status()
290
+ json = response.json()
291
+ return TrismikResponseMapper.to_me_response(json)
292
+ except httpx.HTTPStatusError as e:
293
+ raise TrismikApiError(
294
+ TrismikUtils.get_error_message(e.response)
295
+ ) from e
296
+ except httpx.HTTPError as e:
297
+ raise TrismikApiError(str(e)) from e
298
+
299
+ async def submit_classic_eval(
300
+ self, classic_eval_request: TrismikClassicEvalRequest
301
+ ) -> TrismikClassicEvalResponse:
302
+ """
303
+ Submit a classic evaluation run with pre-computed results.
304
+
305
+ Args:
306
+ classic_eval_request (TrismikClassicEvalRequest): Request containing
307
+ project info, dataset, model outputs, and metrics.
308
+
309
+ Returns:
310
+ TrismikClassicEvalResponse: Response from the classic evaluation
311
+ endpoint.
312
+
313
+ Raises:
314
+ TrismikPayloadTooLargeError: If the request payload exceeds the
315
+ server's size limit.
316
+ TrismikValidationError: If the request fails validation.
317
+ TrismikApiError: If API request fails.
318
+ """
319
+ try:
320
+ url = "/runs/classic"
321
+
322
+ # Convert request object to dictionary
323
+ items_dict = [
324
+ {
325
+ "datasetItemId": item.datasetItemId,
326
+ "modelInput": item.modelInput,
327
+ "modelOutput": item.modelOutput,
328
+ "goldOutput": item.goldOutput,
329
+ "metrics": item.metrics,
330
+ }
331
+ for item in classic_eval_request.items
332
+ ]
333
+
334
+ metrics_dict = [
335
+ {
336
+ "metricId": metric.metricId,
337
+ "valueType": TrismikUtils.metric_value_to_type(
338
+ metric.value
339
+ ),
340
+ "value": metric.value,
341
+ }
342
+ for metric in classic_eval_request.metrics
343
+ ]
344
+
345
+ body = {
346
+ "projectId": classic_eval_request.projectId,
347
+ "experimentName": classic_eval_request.experimentName,
348
+ "datasetId": classic_eval_request.datasetId,
349
+ "modelName": classic_eval_request.modelName,
350
+ "hyperparameters": classic_eval_request.hyperparameters,
351
+ "items": items_dict,
352
+ "metrics": metrics_dict,
353
+ }
354
+
355
+ response = await self._http_client.post(url, json=body)
356
+ response.raise_for_status()
357
+ json = response.json()
358
+ return TrismikResponseMapper.to_classic_eval_response(json)
359
+ except httpx.HTTPStatusError as e:
360
+ raise self._handle_http_error(e) from e
361
+ except httpx.HTTPError as e:
362
+ raise TrismikApiError(str(e)) from e