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