trismik 0.9.1__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,338 +1,362 @@
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
+ TrismikReplayRequest,
26
+ TrismikReplayResponse,
27
+ TrismikRunMetadata,
28
+ TrismikRunResponse,
29
+ TrismikRunSummary,
16
30
  )
17
31
 
18
32
 
19
33
  class TrismikAsyncClient:
20
- _serviceUrl: str = "https://trismik.e-psychometrics.com/api"
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
+ """
21
40
 
22
41
  def __init__(
23
- self,
24
- service_url: Optional[str] = None,
25
- api_key: Optional[str] = None,
26
- http_client: Optional[httpx.AsyncClient] | None = None,
42
+ self,
43
+ service_url: Optional[str] = None,
44
+ api_key: Optional[str] = None,
45
+ http_client: Optional[httpx.AsyncClient] = None,
27
46
  ) -> None:
28
47
  """
29
- Initializes a new Trismik client (async version).
48
+ Initialize the Trismik async client.
30
49
 
31
50
  Args:
32
51
  service_url (Optional[str]): URL of the Trismik service.
33
52
  api_key (Optional[str]): API key for the Trismik service.
34
- http_client (Optional[httpx.Client]): HTTP client to use for requests.
53
+ http_client (Optional[httpx.AsyncClient]): HTTP client to use for
54
+ requests.
35
55
 
36
56
  Raises:
37
- TrismikError: If service_url or api_key are not provided and not found in environment.
57
+ TrismikError: If service_url or api_key are not provided and not
58
+ found in environment.
38
59
  TrismikApiError: If API request fails.
39
60
  """
40
61
  self._service_url = TrismikUtils.option(
41
- service_url, self._serviceUrl, "TRISMIK_SERVICE_URL"
62
+ service_url,
63
+ client_settings["endpoint"],
64
+ environment_settings["trismik_service_url"],
42
65
  )
43
66
  self._api_key = TrismikUtils.required_option(
44
- api_key, "api_key", "TRISMIK_API_KEY"
67
+ api_key, "api_key", environment_settings["trismik_api_key"]
45
68
  )
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
69
 
53
- Returns:
54
- TrismikAuth: Authentication token.
70
+ # Set default headers with API key
71
+ default_headers = {"x-api-key": self._api_key}
55
72
 
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
73
+ self._http_client = http_client or httpx.AsyncClient(
74
+ base_url=self._service_url, headers=default_headers
75
+ )
71
76
 
72
- async def refresh_token(self, token: str) -> TrismikAuth:
77
+ def _handle_http_error(self, e: httpx.HTTPStatusError) -> Exception:
73
78
  """
74
- Refreshes the authentication token.
79
+ Handle HTTP errors and return appropriate Trismik exceptions.
75
80
 
76
81
  Args:
77
- token (str): Current authentication token.
82
+ e (httpx.HTTPStatusError): The HTTP status error to handle.
78
83
 
79
84
  Returns:
80
- TrismikAuth: New authentication token.
81
-
82
- Raises:
83
- TrismikApiError: If API request fails.
85
+ Exception: The appropriate Trismik exception to raise.
84
86
  """
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]:
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]:
99
109
  """
100
- Retrieves a list of available tests.
101
-
102
- Args:
103
- token (str): Authentication token.
110
+ Get a list of available datasets.
104
111
 
105
112
  Returns:
106
- List[TrismikTest]: List of available tests.
113
+ List[TrismikDataset]: List of available datasets.
107
114
 
108
115
  Raises:
109
116
  TrismikApiError: If API request fails.
110
117
  """
111
118
  try:
112
- url = "/client/tests"
113
- headers = {"Authorization": f"Bearer {token}"}
114
- response = await self._http_client.get(url, headers=headers)
119
+ url = "/datasets"
120
+ response = await self._http_client.get(url)
115
121
  response.raise_for_status()
116
122
  json = response.json()
117
- return TrismikResponseMapper.to_tests(json)
123
+ return TrismikResponseMapper.to_datasets(json)
118
124
  except httpx.HTTPStatusError as e:
119
125
  raise TrismikApiError(
120
- TrismikUtils.get_error_message(e.response)) from e
126
+ TrismikUtils.get_error_message(e.response)
127
+ ) from e
121
128
  except httpx.HTTPError as e:
122
129
  raise TrismikApiError(str(e)) from e
123
130
 
124
- async def create_session(self, test_id: str, metadata: TrismikSessionMetadata, token: str) -> TrismikSession:
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:
125
138
  """
126
- Creates a new session for a test.
139
+ Start a new run for a dataset and get the first item.
127
140
 
128
141
  Args:
129
- test_id (str): ID of the test.
130
- token (str): Authentication token.
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.
131
146
 
132
147
  Returns:
133
- TrismikSession: New session
148
+ TrismikRunResponse: Run response.
134
149
 
135
150
  Raises:
151
+ TrismikPayloadTooLargeError: If the request payload exceeds the
152
+ server's size limit.
136
153
  TrismikApiError: If API request fails.
137
154
  """
138
155
  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)
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)
144
164
  response.raise_for_status()
145
165
  json = response.json()
146
- return TrismikResponseMapper.to_session(json)
166
+ return TrismikResponseMapper.to_run_response(json)
147
167
  except httpx.HTTPStatusError as e:
148
- raise TrismikApiError(
149
- TrismikUtils.get_error_message(e.response)) from e
168
+ raise self._handle_http_error(e) from e
150
169
  except httpx.HTTPError as e:
151
170
  raise TrismikApiError(str(e)) from e
152
-
153
- async def create_replay_session(self, previous_session_id: str, metadata: TrismikSessionMetadata, token: str) -> TrismikSession:
171
+
172
+ async def continue_run(
173
+ self, run_id: str, item_choice_id: str
174
+ ) -> TrismikRunResponse:
154
175
  """
155
- Creates a new session that replays exactly the question sequence of a previous session
176
+ Continue a run: respond to the current item and get the next one.
156
177
 
157
178
  Args:
158
- previous_session_id (str): Session id of the session to replay.
159
- token (str): Authentication token.
179
+ run_id (str): ID of the run.
180
+ item_choice_id (str): ID of the chosen item response.
160
181
 
161
182
  Returns:
162
- TrismikSession: New session
183
+ TrismikRunResponse: Run response.
163
184
 
164
185
  Raises:
165
186
  TrismikApiError: If API request fails.
166
187
  """
167
188
  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)
189
+ url = "/runs/continue"
190
+ body = {"itemChoiceId": item_choice_id, "runId": run_id}
191
+ response = await self._http_client.post(url, json=body)
173
192
  response.raise_for_status()
174
193
  json = response.json()
175
- return TrismikResponseMapper.to_session(json)
176
- except httpx.HTTPStatusError as e:
177
- raise TrismikApiError(
178
- TrismikUtils.get_error_message(e.response)) from e
179
- except httpx.HTTPError as e:
180
- raise TrismikApiError(str(e)) from e
181
-
182
- async def add_metadata(self, session_id: str, metadata: TrismikSessionMetadata, token: str) -> None:
183
- """
184
- Adds metadata to the session, merging it with any already stored
185
-
186
- Args:
187
- session_id (str): id of the session object
188
- metadata: object cotaining the metadata to add
189
- token (str): Authentication token.
190
-
191
- Returns:
192
- None
193
-
194
- Raises:
195
- TrismikApiError: If API request fails.
196
- """
197
- 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)
203
- response.raise_for_status()
194
+ return TrismikResponseMapper.to_run_response(json)
204
195
  except httpx.HTTPStatusError as e:
205
196
  raise TrismikApiError(
206
- TrismikUtils.get_error_message(e.response)) from e
197
+ TrismikUtils.get_error_message(e.response)
198
+ ) from e
207
199
  except httpx.HTTPError as e:
208
200
  raise TrismikApiError(str(e)) from e
209
201
 
210
- async def current_item(
211
- self,
212
- session_url: str,
213
- token: str
214
- ) -> TrismikItem:
202
+ async def run_summary(self, run_id: str) -> TrismikRunSummary:
215
203
  """
216
- Retrieves the current test item.
204
+ Get run summary including responses, dataset, and state.
217
205
 
218
206
  Args:
219
- session_url (str): URL of the session.
220
- token (str): Authentication token.
207
+ run_id (str): ID of the run.
221
208
 
222
209
  Returns:
223
- TrismikItem: Current test item.
210
+ TrismikRunSummary: Complete run summary with responses,
211
+ dataset, state, and metadata.
224
212
 
225
213
  Raises:
226
214
  TrismikApiError: If API request fails.
227
215
  """
228
216
  try:
229
- url = f"{session_url}/item"
230
- headers = {"Authorization": f"Bearer {token}"}
231
- response = await self._http_client.get(url, headers=headers)
217
+ url = f"/runs/{run_id}"
218
+ response = await self._http_client.get(url)
232
219
  response.raise_for_status()
233
220
  json = response.json()
234
- return TrismikResponseMapper.to_item(json)
221
+ return TrismikResponseMapper.to_run_summary(json)
235
222
  except httpx.HTTPStatusError as e:
236
223
  raise TrismikApiError(
237
- TrismikUtils.get_error_message(e.response)) from e
224
+ TrismikUtils.get_error_message(e.response)
225
+ ) from e
238
226
  except httpx.HTTPError as e:
239
227
  raise TrismikApiError(str(e)) from e
240
228
 
241
- async def respond_to_current_item(
242
- self,
243
- session_url: str,
244
- value: Any,
245
- token: str
246
- ) -> TrismikItem | None:
229
+ async def submit_replay(
230
+ self,
231
+ run_id: str,
232
+ replay_request: TrismikReplayRequest,
233
+ metadata: Optional[TrismikRunMetadata] = None,
234
+ ) -> TrismikReplayResponse:
247
235
  """
248
- Responds to the current test item.
236
+ Submit a replay of a run with specific responses.
249
237
 
250
238
  Args:
251
- session_url (str): URL of the session.
252
- value (Any): Response value.
253
- token (str): Authentication token.
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.
254
243
 
255
244
  Returns:
256
- TrismikItem | None: Next test item or None if session is finished.
245
+ TrismikReplayResponse: Response from the replay endpoint.
257
246
 
258
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).
259
252
  TrismikApiError: If API request fails.
260
253
  """
261
254
  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
- )
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
268
  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)
269
+ json = response.json()
270
+ return TrismikResponseMapper.to_replay_response(json)
274
271
  except httpx.HTTPStatusError as e:
275
- raise TrismikApiError(
276
- TrismikUtils.get_error_message(e.response)) from e
272
+ raise self._handle_http_error(e) from e
277
273
  except httpx.HTTPError as e:
278
274
  raise TrismikApiError(str(e)) from e
279
275
 
280
- async def results(self,
281
- session_url: str,
282
- token: str
283
- ) -> List[TrismikResult]:
276
+ async def me(self) -> TrismikMeResponse:
284
277
  """
285
- Retrieves the results of a session.
286
-
287
- Args:
288
- session_url (str): URL of the session.
289
- token (str): Authentication token.
278
+ Get current user information.
290
279
 
291
280
  Returns:
292
- List[TrismikResult]: Results of the session.
281
+ TrismikMeResponse: User information including validity and payload.
293
282
 
294
283
  Raises:
295
284
  TrismikApiError: If API request fails.
296
285
  """
297
286
  try:
298
- url = f"{session_url}/results"
299
- headers = {"Authorization": f"Bearer {token}"}
300
- response = await self._http_client.get(url, headers=headers)
287
+ url = "../admin/api-keys/me"
288
+ response = await self._http_client.get(url)
301
289
  response.raise_for_status()
302
290
  json = response.json()
303
- return TrismikResponseMapper.to_results(json)
291
+ return TrismikResponseMapper.to_me_response(json)
304
292
  except httpx.HTTPStatusError as e:
305
293
  raise TrismikApiError(
306
- TrismikUtils.get_error_message(e.response)) from e
294
+ TrismikUtils.get_error_message(e.response)
295
+ ) from e
307
296
  except httpx.HTTPError as e:
308
297
  raise TrismikApiError(str(e)) from e
309
298
 
310
- async def responses(self,
311
- session_url: str,
312
- token: str
313
- ) -> List[TrismikResponse]:
299
+ async def submit_classic_eval(
300
+ self, classic_eval_request: TrismikClassicEvalRequest
301
+ ) -> TrismikClassicEvalResponse:
314
302
  """
315
- Retrieves responses to session items.
303
+ Submit a classic evaluation run with pre-computed results.
316
304
 
317
305
  Args:
318
- session_url (str): URL of the session.
319
- token (str): Authentication token.
306
+ classic_eval_request (TrismikClassicEvalRequest): Request containing
307
+ project info, dataset, model outputs, and metrics.
320
308
 
321
309
  Returns:
322
- List[TrismikResponse]: Responses of the session.
310
+ TrismikClassicEvalResponse: Response from the classic evaluation
311
+ endpoint.
323
312
 
324
313
  Raises:
314
+ TrismikPayloadTooLargeError: If the request payload exceeds the
315
+ server's size limit.
316
+ TrismikValidationError: If the request fails validation.
325
317
  TrismikApiError: If API request fails.
326
318
  """
327
319
  try:
328
- url = f"{session_url}/responses"
329
- headers = {"Authorization": f"Bearer {token}"}
330
- response = await self._http_client.get(url, headers=headers)
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)
331
356
  response.raise_for_status()
332
357
  json = response.json()
333
- return TrismikResponseMapper.to_responses(json)
358
+ return TrismikResponseMapper.to_classic_eval_response(json)
334
359
  except httpx.HTTPStatusError as e:
335
- raise TrismikApiError(
336
- TrismikUtils.get_error_message(e.response)) from e
360
+ raise self._handle_http_error(e) from e
337
361
  except httpx.HTTPError as e:
338
362
  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]}"
trismik/settings.py ADDED
@@ -0,0 +1,15 @@
1
+ """Default settings for the Trismik client."""
2
+
3
+ evaluation_settings = {
4
+ "max_iterations": 150,
5
+ }
6
+
7
+ client_settings = {"endpoint": "https://dashboard.trismik.com/api"}
8
+
9
+ # Environment variable names used by the Trismik client
10
+ environment_settings = {
11
+ # URL of the Trismik service
12
+ "trismik_service_url": "TRISMIK_SERVICE_URL",
13
+ # API key for authentication
14
+ "trismik_api_key": "TRISMIK_API_KEY",
15
+ }