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/__init__.py CHANGED
@@ -1,20 +1,10 @@
1
- from .client import TrismikClient
2
- from .client_async import TrismikAsyncClient
3
- from .exceptions import (
4
- TrismikError,
5
- TrismikApiError
6
- )
7
- from .runner import TrismikRunner
8
- from .runner_async import TrismikAsyncRunner
9
- from .types import (
10
- TrismikTest,
11
- TrismikAuth,
12
- TrismikSession,
13
- TrismikItem,
14
- TrismikMultipleChoiceTextItem,
15
- TrismikChoice,
16
- TrismikTextChoice,
17
- TrismikResult,
18
- TrismikResponse,
19
- TrismikResultsAndResponses,
20
- )
1
+ """
2
+ Trismik Python Client.
3
+
4
+ A Python client for the Trismik API.
5
+ """
6
+
7
+ import importlib.metadata
8
+
9
+ # get version from pyproject.toml
10
+ __version__ = importlib.metadata.version(__package__ or __name__)
trismik/_mapper.py CHANGED
@@ -1,79 +1,335 @@
1
- from typing import List, Any
2
-
3
- from dateutil.parser import parse as parse_date
4
-
5
- from .exceptions import TrismikApiError
6
- from .types import (
7
- TrismikItem,
8
- TrismikResult,
9
- TrismikMultipleChoiceTextItem,
10
- TrismikTextChoice,
11
- TrismikAuth,
12
- TrismikTest,
13
- TrismikSession,
14
- TrismikResponse,
15
- )
16
-
17
-
18
- class TrismikResponseMapper:
19
-
20
- @staticmethod
21
- def to_auth(json: dict[str, Any]) -> TrismikAuth:
22
- return TrismikAuth(
23
- token=json["token"],
24
- expires=parse_date(json["expires"]),
25
- )
26
-
27
- @staticmethod
28
- def to_tests(json: List[dict[str, Any]]) -> List[TrismikTest]:
29
- return [
30
- TrismikTest(
31
- id=item["id"],
32
- name=item["name"],
33
- ) for item in json
34
- ]
35
-
36
- @staticmethod
37
- def to_session(json: dict[str, Any]) -> TrismikSession:
38
- return TrismikSession(
39
- id=json["id"],
40
- url=json["url"],
41
- status=json["status"],
42
- )
43
-
44
- @staticmethod
45
- def to_item(json: dict[str, Any]) -> TrismikItem:
46
- if json["type"] == "multiple_choice_text":
47
- return TrismikMultipleChoiceTextItem(
48
- id=json["id"],
49
- question=json["question"],
50
- choices=[
51
- TrismikTextChoice(
52
- id=choice["id"],
53
- text=choice["text"],
54
- ) for choice in json["choices"]
55
- ]
56
- )
57
- else:
58
- raise TrismikApiError(
59
- f"API has returned unrecognized item type: {json['type']}")
60
-
61
- @staticmethod
62
- def to_results(json: List[dict[str, Any]]) -> List[TrismikResult]:
63
- return [
64
- TrismikResult(
65
- trait=item["trait"],
66
- name=item["name"],
67
- value=item["value"],
68
- ) for item in json
69
- ]
70
-
71
- @staticmethod
72
- def to_responses(json: List[dict[str, Any]]) -> List[TrismikResponse]:
73
- return [
74
- TrismikResponse(
75
- item_id=response["itemId"],
76
- value=response["value"],
77
- score=response["score"],
78
- ) for response in json
79
- ]
1
+ from datetime import datetime
2
+ from typing import Any, Dict, List
3
+
4
+ from trismik.exceptions import TrismikApiError
5
+ from trismik.types import (
6
+ TrismikClassicEvalResponse,
7
+ TrismikDataset,
8
+ TrismikItem,
9
+ TrismikMeResponse,
10
+ TrismikMultipleChoiceTextItem,
11
+ TrismikOrganization,
12
+ TrismikReplayResponse,
13
+ TrismikResponse,
14
+ TrismikResult,
15
+ TrismikRun,
16
+ TrismikRunInfo,
17
+ TrismikRunResponse,
18
+ TrismikRunState,
19
+ TrismikRunSummary,
20
+ TrismikTextChoice,
21
+ TrismikUserInfo,
22
+ )
23
+
24
+
25
+ class TrismikResponseMapper:
26
+ """
27
+ Maps JSON responses from the Trismik API to Python objects.
28
+
29
+ This class provides static methods to convert JSON responses from various
30
+ API endpoints into their corresponding Python object representations.
31
+ """
32
+
33
+ @staticmethod
34
+ def to_datasets(json: Dict[str, Any]) -> List[TrismikDataset]:
35
+ """
36
+ Convert JSON response to a list of TrismikDataset objects.
37
+
38
+ Args:
39
+ json (Dict[str, Any]): JSON response containing dataset data.
40
+
41
+ Returns:
42
+ List[TrismikDataset]: List of
43
+ dataset objects with IDs and names.
44
+ """
45
+ return [
46
+ TrismikDataset(
47
+ id=item["id"],
48
+ name=item["name"],
49
+ )
50
+ for item in json["data"]
51
+ ]
52
+
53
+ @staticmethod
54
+ def to_run(json: Dict[str, Any]) -> TrismikRun:
55
+ """
56
+ Convert JSON response to a TrismikRun object.
57
+
58
+ Args:
59
+ json (Dict[str, Any]): JSON response containing run data.
60
+
61
+ Returns:
62
+ TrismikRun: Run object with ID, URL, and status.
63
+ """
64
+ return TrismikRun(
65
+ id=json["id"],
66
+ url=json["url"],
67
+ status=json["status"],
68
+ )
69
+
70
+ @staticmethod
71
+ def to_run_info(json: Dict[str, Any]) -> TrismikRunInfo:
72
+ """
73
+ Convert JSON response to a TrismikRunInfo object.
74
+
75
+ Args:
76
+ json (Dict[str, Any]): JSON response containing run info.
77
+
78
+ Returns:
79
+ TrismikRunInfo: Run info object with ID.
80
+ """
81
+ return TrismikRunInfo(id=json["id"])
82
+
83
+ @staticmethod
84
+ def to_run_state(json: Dict[str, Any]) -> TrismikRunState:
85
+ """
86
+ Convert JSON response to a TrismikRunState object.
87
+
88
+ Args:
89
+ json (Dict[str, Any]): JSON response containing run state.
90
+
91
+ Returns:
92
+ TrismikRunState: Run state object.
93
+ """
94
+ return TrismikRunState(
95
+ responses=json.get("responses", []),
96
+ thetas=json.get("thetas", []),
97
+ std_error_history=json.get("std_error_history", []),
98
+ kl_info_history=json.get("kl_info_history", []),
99
+ effective_difficulties=json.get("effective_difficulties", []),
100
+ )
101
+
102
+ @staticmethod
103
+ def to_run_response(json: Dict[str, Any]) -> TrismikRunResponse:
104
+ """
105
+ Convert JSON response to a TrismikRunResponse object.
106
+
107
+ Args:
108
+ json (Dict[str, Any]): JSON response from run endpoints.
109
+
110
+ Returns:
111
+ TrismikRunResponse: Run response.
112
+ """
113
+ return TrismikRunResponse(
114
+ run_info=TrismikResponseMapper.to_run_info(json["runInfo"]),
115
+ state=TrismikResponseMapper.to_run_state(json["state"]),
116
+ next_item=(
117
+ TrismikResponseMapper.to_item(json["nextItem"])
118
+ if json.get("nextItem")
119
+ else None
120
+ ),
121
+ completed=json.get("completed", False),
122
+ )
123
+
124
+ @staticmethod
125
+ def to_run_summary(json: Dict[str, Any]) -> TrismikRunSummary:
126
+ """
127
+ Convert JSON response to a TrismikRunSummary object.
128
+
129
+ Args:
130
+ json (Dict[str, Any]): JSON response from run summary endpoint.
131
+
132
+ Returns:
133
+ TrismikRunSummary: Complete run summary.
134
+ """
135
+ return TrismikRunSummary(
136
+ id=json["id"],
137
+ dataset_id=json["datasetId"],
138
+ state=TrismikResponseMapper.to_run_state(json["state"]),
139
+ dataset=[
140
+ TrismikResponseMapper.to_item(item)
141
+ for item in json.get("dataset", [])
142
+ ],
143
+ responses=TrismikResponseMapper.to_responses(
144
+ json.get("responses", [])
145
+ ),
146
+ metadata=json.get("metadata", {}),
147
+ )
148
+
149
+ @staticmethod
150
+ def to_item(json: Dict[str, Any]) -> TrismikItem:
151
+ """
152
+ Convert JSON response to a TrismikItem object.
153
+
154
+ Args:
155
+ json (Dict[str, Any]): JSON response containing item data.
156
+
157
+ Returns:
158
+ TrismikItem: Item object with question and choices.
159
+
160
+ Raises:
161
+ TrismikApiError: If the item type is not recognized.
162
+ """
163
+ if "question" in json and "choices" in json:
164
+ return TrismikMultipleChoiceTextItem(
165
+ id=json["id"],
166
+ question=json["question"],
167
+ choices=[
168
+ TrismikTextChoice(
169
+ id=choice["id"],
170
+ text=choice["value"],
171
+ )
172
+ for choice in json["choices"]
173
+ ],
174
+ )
175
+ else:
176
+ item_type = json.get("type", "unknown")
177
+ raise TrismikApiError(
178
+ f"API has returned unrecognized item type: {item_type}"
179
+ )
180
+
181
+ @staticmethod
182
+ def to_results(json: List[Dict[str, Any]]) -> List[TrismikResult]:
183
+ """
184
+ Convert JSON response to a list of TrismikResult objects.
185
+
186
+ Args:
187
+ json (List[Dict[str, Any]]): JSON response containing result data.
188
+
189
+ Returns:
190
+ List[TrismikResult]: List of result objects with trait, name, and
191
+ value.
192
+ """
193
+ return [
194
+ TrismikResult(
195
+ trait=item["trait"],
196
+ name=item["name"],
197
+ value=item["value"],
198
+ )
199
+ for item in json
200
+ ]
201
+
202
+ @staticmethod
203
+ def to_responses(json: List[Dict[str, Any]]) -> List[TrismikResponse]:
204
+ """
205
+ Convert JSON response to a list of TrismikResponse objects.
206
+
207
+ Args:
208
+ json (List[Dict[str, Any]]): JSON response containing response data.
209
+
210
+ Returns:
211
+ List[TrismikResponse]: List of response objects with dataset item
212
+ ID, value, and correctness.
213
+ """
214
+ return [
215
+ TrismikResponse(
216
+ dataset_item_id=response["datasetItemId"],
217
+ value=response["value"],
218
+ correct=response["correct"],
219
+ )
220
+ for response in json
221
+ ]
222
+
223
+ @staticmethod
224
+ def to_replay_response(json: Dict[str, Any]) -> TrismikReplayResponse:
225
+ """
226
+ Convert JSON response to a TrismikReplayResponse object.
227
+
228
+ Args:
229
+ json (Dict[str, Any]): JSON response from replay endpoint.
230
+
231
+ Returns:
232
+ TrismikReplayResponse: Replay response object.
233
+ """
234
+ # Parse datetime strings if they exist
235
+ completed_at = None
236
+ if "completedAt" in json and json["completedAt"]:
237
+ completed_at = datetime.fromisoformat(
238
+ json["completedAt"].replace("Z", "+00:00")
239
+ )
240
+
241
+ created_at = None
242
+ if "createdAt" in json and json["createdAt"]:
243
+ created_at = datetime.fromisoformat(
244
+ json["createdAt"].replace("Z", "+00:00")
245
+ )
246
+
247
+ return TrismikReplayResponse(
248
+ id=json["id"],
249
+ datasetId=json["datasetId"],
250
+ state=TrismikResponseMapper.to_run_state(json["state"]),
251
+ replay_of_run=json["replayOfRun"],
252
+ completedAt=completed_at,
253
+ createdAt=created_at,
254
+ metadata=json.get("metadata", {}),
255
+ dataset=[
256
+ TrismikResponseMapper.to_item(item)
257
+ for item in json.get("dataset", [])
258
+ ],
259
+ responses=TrismikResponseMapper.to_responses(
260
+ json.get("responses", [])
261
+ ),
262
+ )
263
+
264
+ @staticmethod
265
+ def to_me_response(json: Dict[str, Any]) -> TrismikMeResponse:
266
+ """
267
+ Convert JSON response to a TrismikMeResponse object.
268
+
269
+ Args:
270
+ json (Dict[str, Any]): JSON response from /admin/api-keys/me
271
+ endpoint.
272
+
273
+ Returns:
274
+ TrismikMeResponse: Me response object.
275
+ """
276
+ user_data = json["user"]
277
+ organization_data = json["organization"]
278
+
279
+ user_info = TrismikUserInfo(
280
+ id=user_data["id"],
281
+ email=user_data["email"],
282
+ firstname=user_data["firstname"],
283
+ lastname=user_data["lastname"],
284
+ createdAt=user_data.get("createdAt"),
285
+ )
286
+
287
+ organization = TrismikOrganization(
288
+ id=organization_data["id"],
289
+ name=organization_data["name"],
290
+ type=organization_data["type"],
291
+ role=organization_data["role"],
292
+ )
293
+
294
+ return TrismikMeResponse(
295
+ user=user_info,
296
+ organization=organization,
297
+ )
298
+
299
+ @staticmethod
300
+ def to_classic_eval_response(
301
+ json: Dict[str, Any]
302
+ ) -> TrismikClassicEvalResponse:
303
+ """
304
+ Convert JSON response to a TrismikClassicEvalResponse object.
305
+
306
+ Args:
307
+ json (Dict[str, Any]): JSON response from classic evaluation
308
+ endpoint.
309
+
310
+ Returns:
311
+ TrismikClassicEvalResponse: Classic evaluation response object.
312
+ """
313
+ user_data = json["user"]
314
+ user_info = TrismikUserInfo(
315
+ id=user_data["id"],
316
+ email=user_data["email"],
317
+ firstname=user_data["firstname"],
318
+ lastname=user_data["lastname"],
319
+ )
320
+
321
+ return TrismikClassicEvalResponse(
322
+ id=json["id"],
323
+ organizationId=json["organizationId"],
324
+ projectId=json["projectId"],
325
+ experimentId=json["experimentId"],
326
+ experimentName=json["experimentName"],
327
+ datasetId=json["datasetId"],
328
+ userId=json["userId"],
329
+ type=json["type"],
330
+ modelName=json["modelName"],
331
+ hyperparameters=json.get("hyperparameters", {}),
332
+ createdAt=json["createdAt"],
333
+ user=user_info,
334
+ responseCount=json["responseCount"],
335
+ )
trismik/_utils.py CHANGED
@@ -1,44 +1,121 @@
1
- import os
2
-
3
- import httpx
4
-
5
- from .exceptions import TrismikError
6
-
7
-
8
- class TrismikUtils:
9
-
10
- @staticmethod
11
- def get_error_message(response: httpx.Response) -> str:
12
- try:
13
- return (response.json()).get("message", "Unknown error")
14
- except (httpx.RequestError, ValueError):
15
- error_message = response.content.decode("utf-8", errors="ignore")
16
- return error_message
17
-
18
- @staticmethod
19
- def required_option(
20
- value: str | None,
21
- name: str,
22
- env: str
23
- ) -> str:
24
- if value is None:
25
- value = os.environ.get(env)
26
- if value is None:
27
- raise TrismikError(
28
- f"The {name} client option must be set either by passing "
29
- f"{env} to the client or by setting the {env} "
30
- "environment variable"
31
- )
32
- return value
33
-
34
- @staticmethod
35
- def option(
36
- value: str | None,
37
- default: str,
38
- env: str,
39
- ) -> str:
40
- if value is None:
41
- value = os.environ.get(env)
42
- if value is None:
43
- return default
44
- return value
1
+ import os
2
+ from typing import Optional, Union
3
+
4
+ import httpx
5
+
6
+ from trismik.exceptions import TrismikError
7
+
8
+
9
+ class TrismikUtils:
10
+ """
11
+ Utility functions for the Trismik client.
12
+
13
+ This class provides helper methods for error handling and configuration
14
+ management.
15
+ """
16
+
17
+ @staticmethod
18
+ def get_error_message(response: httpx.Response) -> str:
19
+ """
20
+ Extract error message from an HTTP response.
21
+
22
+ Args:
23
+ response (httpx.Response): The HTTP response containing the error.
24
+
25
+ Returns:
26
+ str: The error message from the response JSON or content.
27
+ """
28
+ try:
29
+ json_data = response.json()
30
+ message = json_data.get("message", "Unknown error")
31
+ return str(message) # Ensure we return a string
32
+ except (httpx.RequestError, ValueError):
33
+ error_message: str = response.content.decode(
34
+ "utf-8", errors="ignore"
35
+ )
36
+ return error_message
37
+
38
+ @staticmethod
39
+ def required_option(value: Optional[str], name: str, env: str) -> str:
40
+ """
41
+ Get a required configuration option.
42
+
43
+ Get a required configuration option from either the provided value or
44
+ environment variable.
45
+
46
+ Args:
47
+ value (Optional[str]): The provided value for the option.
48
+ name (str): The name of the option for error messages.
49
+ env (str): The environment variable name to check if value is None.
50
+
51
+ Returns:
52
+ str: The option value.
53
+
54
+ Raises:
55
+ TrismikError: If neither value nor environment variable is set.
56
+ """
57
+ if value is None:
58
+ value = os.environ.get(env)
59
+ if value is None:
60
+ raise TrismikError(
61
+ f"The {name} client option must be set either by passing "
62
+ f"{env} to the client or by setting the {env} "
63
+ "environment variable"
64
+ )
65
+ return value
66
+
67
+ @staticmethod
68
+ def option(
69
+ value: Optional[str],
70
+ default: str,
71
+ env: str,
72
+ ) -> str:
73
+ """
74
+ Get an optional configuration value.
75
+
76
+ Get an optional configuration value from either the provided value,
77
+ environment variable, or default value.
78
+
79
+ Args:
80
+ value (Optional[str]): The provided value for the option.
81
+ default (str): The default value to use if neither value nor env var
82
+ is set.
83
+ env (str): The environment variable name to check if value is None.
84
+
85
+ Returns:
86
+ str: The option value, falling back to default if not set.
87
+ """
88
+ if value is None:
89
+ value = os.environ.get(env)
90
+ if value is None:
91
+ return default
92
+ return value
93
+
94
+ @staticmethod
95
+ def metric_value_to_type(value: Union[str, float, int, bool]) -> str:
96
+ """
97
+ Automatically determine valueType from Python value type.
98
+
99
+ Args:
100
+ value (Union[str, float, int, bool]): The metric value to analyze.
101
+
102
+ Returns:
103
+ str: The corresponding valueType string for the API.
104
+
105
+ Raises:
106
+ TypeError: If the value type is not supported.
107
+ """
108
+ if isinstance(value, bool):
109
+ # Handle bool first since bool is a subclass of int in Python
110
+ return "Boolean"
111
+ elif isinstance(value, str):
112
+ return "String"
113
+ elif isinstance(value, float):
114
+ return "Float"
115
+ elif isinstance(value, int):
116
+ return "Integer"
117
+ else:
118
+ raise TypeError(
119
+ f"Unsupported metric value type: {type(value).__name__}. "
120
+ f"Supported types: str, float, int, bool"
121
+ )