arize 8.0.0a22__py3-none-any.whl → 8.0.0b0__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.
- arize/__init__.py +28 -19
- arize/_exporter/client.py +56 -37
- arize/_exporter/parsers/tracing_data_parser.py +41 -30
- arize/_exporter/validation.py +3 -3
- arize/_flight/client.py +207 -76
- arize/_generated/api_client/__init__.py +30 -6
- arize/_generated/api_client/api/__init__.py +1 -0
- arize/_generated/api_client/api/datasets_api.py +864 -190
- arize/_generated/api_client/api/experiments_api.py +167 -131
- arize/_generated/api_client/api/projects_api.py +1197 -0
- arize/_generated/api_client/api_client.py +2 -2
- arize/_generated/api_client/configuration.py +42 -34
- arize/_generated/api_client/exceptions.py +2 -2
- arize/_generated/api_client/models/__init__.py +15 -4
- arize/_generated/api_client/models/dataset.py +10 -10
- arize/_generated/api_client/models/dataset_example.py +111 -0
- arize/_generated/api_client/models/dataset_example_update.py +100 -0
- arize/_generated/api_client/models/dataset_version.py +13 -13
- arize/_generated/api_client/models/datasets_create_request.py +16 -8
- arize/_generated/api_client/models/datasets_examples_insert_request.py +100 -0
- arize/_generated/api_client/models/datasets_examples_list200_response.py +106 -0
- arize/_generated/api_client/models/datasets_examples_update_request.py +102 -0
- arize/_generated/api_client/models/datasets_list200_response.py +10 -4
- arize/_generated/api_client/models/experiment.py +14 -16
- arize/_generated/api_client/models/experiment_run.py +108 -0
- arize/_generated/api_client/models/experiment_run_create.py +102 -0
- arize/_generated/api_client/models/experiments_create_request.py +16 -10
- arize/_generated/api_client/models/experiments_list200_response.py +10 -4
- arize/_generated/api_client/models/experiments_runs_list200_response.py +19 -5
- arize/_generated/api_client/models/{error.py → pagination_metadata.py} +13 -11
- arize/_generated/api_client/models/primitive_value.py +172 -0
- arize/_generated/api_client/models/problem.py +100 -0
- arize/_generated/api_client/models/project.py +99 -0
- arize/_generated/api_client/models/{datasets_list_examples200_response.py → projects_create_request.py} +13 -11
- arize/_generated/api_client/models/projects_list200_response.py +106 -0
- arize/_generated/api_client/rest.py +2 -2
- arize/_generated/api_client/test/test_dataset.py +4 -2
- arize/_generated/api_client/test/test_dataset_example.py +56 -0
- arize/_generated/api_client/test/test_dataset_example_update.py +52 -0
- arize/_generated/api_client/test/test_dataset_version.py +7 -2
- arize/_generated/api_client/test/test_datasets_api.py +27 -13
- arize/_generated/api_client/test/test_datasets_create_request.py +8 -4
- arize/_generated/api_client/test/{test_datasets_list_examples200_response.py → test_datasets_examples_insert_request.py} +19 -15
- arize/_generated/api_client/test/test_datasets_examples_list200_response.py +66 -0
- arize/_generated/api_client/test/test_datasets_examples_update_request.py +61 -0
- arize/_generated/api_client/test/test_datasets_list200_response.py +9 -3
- arize/_generated/api_client/test/test_experiment.py +2 -4
- arize/_generated/api_client/test/test_experiment_run.py +56 -0
- arize/_generated/api_client/test/test_experiment_run_create.py +54 -0
- arize/_generated/api_client/test/test_experiments_api.py +6 -6
- arize/_generated/api_client/test/test_experiments_create_request.py +9 -6
- arize/_generated/api_client/test/test_experiments_list200_response.py +9 -5
- arize/_generated/api_client/test/test_experiments_runs_list200_response.py +15 -5
- arize/_generated/api_client/test/test_pagination_metadata.py +53 -0
- arize/_generated/api_client/test/{test_error.py → test_primitive_value.py} +13 -14
- arize/_generated/api_client/test/test_problem.py +57 -0
- arize/_generated/api_client/test/test_project.py +58 -0
- arize/_generated/api_client/test/test_projects_api.py +59 -0
- arize/_generated/api_client/test/test_projects_create_request.py +54 -0
- arize/_generated/api_client/test/test_projects_list200_response.py +70 -0
- arize/_generated/api_client_README.md +43 -29
- arize/_generated/protocol/flight/flight_pb2.py +400 -0
- arize/_lazy.py +27 -19
- arize/client.py +181 -58
- arize/config.py +324 -116
- arize/constants/__init__.py +1 -0
- arize/constants/config.py +11 -4
- arize/constants/ml.py +6 -4
- arize/constants/openinference.py +2 -0
- arize/constants/pyarrow.py +2 -0
- arize/constants/spans.py +3 -1
- arize/datasets/__init__.py +1 -0
- arize/datasets/client.py +304 -84
- arize/datasets/errors.py +32 -2
- arize/datasets/validation.py +18 -8
- arize/embeddings/__init__.py +2 -0
- arize/embeddings/auto_generator.py +23 -19
- arize/embeddings/base_generators.py +89 -36
- arize/embeddings/constants.py +2 -0
- arize/embeddings/cv_generators.py +26 -4
- arize/embeddings/errors.py +27 -5
- arize/embeddings/nlp_generators.py +43 -18
- arize/embeddings/tabular_generators.py +46 -31
- arize/embeddings/usecases.py +12 -2
- arize/exceptions/__init__.py +1 -0
- arize/exceptions/auth.py +11 -1
- arize/exceptions/base.py +29 -4
- arize/exceptions/models.py +21 -2
- arize/exceptions/parameters.py +31 -0
- arize/exceptions/spaces.py +12 -1
- arize/exceptions/types.py +86 -7
- arize/exceptions/values.py +220 -20
- arize/experiments/__init__.py +13 -0
- arize/experiments/client.py +394 -285
- arize/experiments/evaluators/__init__.py +1 -0
- arize/experiments/evaluators/base.py +74 -41
- arize/experiments/evaluators/exceptions.py +6 -3
- arize/experiments/evaluators/executors.py +121 -73
- arize/experiments/evaluators/rate_limiters.py +106 -57
- arize/experiments/evaluators/types.py +34 -7
- arize/experiments/evaluators/utils.py +65 -27
- arize/experiments/functions.py +103 -101
- arize/experiments/tracing.py +52 -44
- arize/experiments/types.py +56 -31
- arize/logging.py +54 -22
- arize/ml/__init__.py +1 -0
- arize/ml/batch_validation/__init__.py +1 -0
- arize/{models → ml}/batch_validation/errors.py +545 -67
- arize/{models → ml}/batch_validation/validator.py +344 -303
- arize/ml/bounded_executor.py +47 -0
- arize/{models → ml}/casting.py +118 -108
- arize/{models → ml}/client.py +339 -118
- arize/{models → ml}/proto.py +97 -42
- arize/{models → ml}/stream_validation.py +43 -15
- arize/ml/surrogate_explainer/__init__.py +1 -0
- arize/{models → ml}/surrogate_explainer/mimic.py +25 -10
- arize/{types.py → ml/types.py} +355 -354
- arize/pre_releases.py +44 -0
- arize/projects/__init__.py +1 -0
- arize/projects/client.py +134 -0
- arize/regions.py +40 -0
- arize/spans/__init__.py +1 -0
- arize/spans/client.py +204 -175
- arize/spans/columns.py +13 -0
- arize/spans/conversion.py +60 -37
- arize/spans/validation/__init__.py +1 -0
- arize/spans/validation/annotations/__init__.py +1 -0
- arize/spans/validation/annotations/annotations_validation.py +6 -4
- arize/spans/validation/annotations/dataframe_form_validation.py +13 -11
- arize/spans/validation/annotations/value_validation.py +35 -11
- arize/spans/validation/common/__init__.py +1 -0
- arize/spans/validation/common/argument_validation.py +33 -8
- arize/spans/validation/common/dataframe_form_validation.py +35 -9
- arize/spans/validation/common/errors.py +211 -11
- arize/spans/validation/common/value_validation.py +81 -14
- arize/spans/validation/evals/__init__.py +1 -0
- arize/spans/validation/evals/dataframe_form_validation.py +28 -8
- arize/spans/validation/evals/evals_validation.py +34 -4
- arize/spans/validation/evals/value_validation.py +26 -3
- arize/spans/validation/metadata/__init__.py +1 -1
- arize/spans/validation/metadata/argument_validation.py +14 -5
- arize/spans/validation/metadata/dataframe_form_validation.py +26 -10
- arize/spans/validation/metadata/value_validation.py +24 -10
- arize/spans/validation/spans/__init__.py +1 -0
- arize/spans/validation/spans/dataframe_form_validation.py +35 -14
- arize/spans/validation/spans/spans_validation.py +35 -4
- arize/spans/validation/spans/value_validation.py +78 -8
- arize/utils/__init__.py +1 -0
- arize/utils/arrow.py +31 -15
- arize/utils/cache.py +34 -6
- arize/utils/dataframe.py +20 -3
- arize/utils/online_tasks/__init__.py +2 -0
- arize/utils/online_tasks/dataframe_preprocessor.py +58 -47
- arize/utils/openinference_conversion.py +44 -5
- arize/utils/proto.py +10 -0
- arize/utils/size.py +5 -3
- arize/utils/types.py +105 -0
- arize/version.py +3 -1
- {arize-8.0.0a22.dist-info → arize-8.0.0b0.dist-info}/METADATA +13 -6
- arize-8.0.0b0.dist-info/RECORD +175 -0
- {arize-8.0.0a22.dist-info → arize-8.0.0b0.dist-info}/WHEEL +1 -1
- arize-8.0.0b0.dist-info/licenses/LICENSE +176 -0
- arize-8.0.0b0.dist-info/licenses/NOTICE +13 -0
- arize/_generated/protocol/flight/export_pb2.py +0 -61
- arize/_generated/protocol/flight/ingest_pb2.py +0 -365
- arize/models/__init__.py +0 -0
- arize/models/batch_validation/__init__.py +0 -0
- arize/models/bounded_executor.py +0 -34
- arize/models/surrogate_explainer/__init__.py +0 -0
- arize-8.0.0a22.dist-info/RECORD +0 -146
- arize-8.0.0a22.dist-info/licenses/LICENSE.md +0 -12
arize/experiments/types.py
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
|
+
"""Type definitions and data models for experiments."""
|
|
2
|
+
|
|
1
3
|
from __future__ import annotations
|
|
2
4
|
|
|
3
5
|
import json
|
|
4
6
|
import textwrap
|
|
7
|
+
from collections.abc import Awaitable, Callable, Iterable, Mapping
|
|
5
8
|
from copy import copy, deepcopy
|
|
6
9
|
from dataclasses import dataclass, field
|
|
7
10
|
from datetime import datetime, timezone
|
|
8
11
|
from importlib.metadata import version
|
|
9
12
|
from random import getrandbits
|
|
10
13
|
from typing import (
|
|
11
|
-
Any,
|
|
12
|
-
Awaitable,
|
|
13
|
-
Callable,
|
|
14
|
-
Iterable,
|
|
15
|
-
Mapping,
|
|
16
14
|
cast,
|
|
17
15
|
)
|
|
18
16
|
|
|
@@ -35,8 +33,8 @@ TraceId = str
|
|
|
35
33
|
|
|
36
34
|
@dataclass(frozen=True)
|
|
37
35
|
class Example:
|
|
38
|
-
"""
|
|
39
|
-
|
|
36
|
+
"""Represents an example in an experiment dataset.
|
|
37
|
+
|
|
40
38
|
Args:
|
|
41
39
|
id: The unique identifier for the example.
|
|
42
40
|
updated_at: The timestamp when the example was last updated.
|
|
@@ -54,6 +52,7 @@ class Example:
|
|
|
54
52
|
dataset_row: Mapping[str, JSONSerializable] = field(default_factory=dict)
|
|
55
53
|
|
|
56
54
|
def __post_init__(self) -> None:
|
|
55
|
+
"""Initialize example fields from dataset_row if provided."""
|
|
57
56
|
if self.dataset_row is not None:
|
|
58
57
|
object.__setattr__(
|
|
59
58
|
self, "dataset_row", _make_read_only(self.dataset_row)
|
|
@@ -90,7 +89,8 @@ class Example:
|
|
|
90
89
|
object.__setattr__(self, "metadata", self.metadata)
|
|
91
90
|
|
|
92
91
|
@classmethod
|
|
93
|
-
def from_dict(cls, obj: Mapping[str,
|
|
92
|
+
def from_dict(cls, obj: Mapping[str, object]) -> Example:
|
|
93
|
+
"""Create an Example instance from a dictionary."""
|
|
94
94
|
return cls(
|
|
95
95
|
id=obj["id"],
|
|
96
96
|
input=obj["input"],
|
|
@@ -100,6 +100,7 @@ class Example:
|
|
|
100
100
|
)
|
|
101
101
|
|
|
102
102
|
def __repr__(self) -> str:
|
|
103
|
+
"""Return a formatted string representation of the example."""
|
|
103
104
|
spaces = " " * 4
|
|
104
105
|
name = self.__class__.__name__
|
|
105
106
|
identifiers = [f'{spaces}id="{self.id}",']
|
|
@@ -123,7 +124,9 @@ class Example:
|
|
|
123
124
|
return "\n".join([f"{name}(", *identifiers, *contents, ")"])
|
|
124
125
|
|
|
125
126
|
|
|
126
|
-
def _shorten(
|
|
127
|
+
def _shorten(
|
|
128
|
+
obj: dict[str, object] | list[object] | str | object, width: int = 50
|
|
129
|
+
) -> dict[str, object] | list[object] | str | object:
|
|
127
130
|
if isinstance(obj, str):
|
|
128
131
|
return textwrap.shorten(obj, width=width, placeholder="...")
|
|
129
132
|
if isinstance(obj, dict):
|
|
@@ -135,7 +138,9 @@ def _shorten(obj: Any, width: int = 50) -> Any:
|
|
|
135
138
|
return obj
|
|
136
139
|
|
|
137
140
|
|
|
138
|
-
def _make_read_only(
|
|
141
|
+
def _make_read_only(
|
|
142
|
+
obj: dict[str, object] | list[object] | str | object,
|
|
143
|
+
) -> dict[str, object] | list[object] | str | object:
|
|
139
144
|
if isinstance(obj, dict):
|
|
140
145
|
return _ReadOnly({k: _make_read_only(v) for k, v in obj.items()})
|
|
141
146
|
if isinstance(obj, str):
|
|
@@ -146,25 +151,25 @@ def _make_read_only(obj: Any) -> Any:
|
|
|
146
151
|
|
|
147
152
|
|
|
148
153
|
class _ReadOnly(ObjectProxy): # type: ignore[misc]
|
|
149
|
-
def __setitem__(self, *args:
|
|
154
|
+
def __setitem__(self, *args: object, **kwargs: object) -> object:
|
|
150
155
|
raise NotImplementedError
|
|
151
156
|
|
|
152
|
-
def __delitem__(self, *args:
|
|
157
|
+
def __delitem__(self, *args: object, **kwargs: object) -> object:
|
|
153
158
|
raise NotImplementedError
|
|
154
159
|
|
|
155
|
-
def __iadd__(self, *args:
|
|
160
|
+
def __iadd__(self, *args: object, **kwargs: object) -> object:
|
|
156
161
|
raise NotImplementedError
|
|
157
162
|
|
|
158
|
-
def pop(self, *args:
|
|
163
|
+
def pop(self, *args: object, **kwargs: object) -> object:
|
|
159
164
|
raise NotImplementedError
|
|
160
165
|
|
|
161
|
-
def append(self, *args:
|
|
166
|
+
def append(self, *args: object, **kwargs: object) -> object:
|
|
162
167
|
raise NotImplementedError
|
|
163
168
|
|
|
164
|
-
def __copy__(self, *args:
|
|
169
|
+
def __copy__(self, *args: object, **kwargs: object) -> object:
|
|
165
170
|
return copy(self.__wrapped__)
|
|
166
171
|
|
|
167
|
-
def __deepcopy__(self, *args:
|
|
172
|
+
def __deepcopy__(self, *args: object, **kwargs: object) -> object:
|
|
168
173
|
return deepcopy(self.__wrapped__)
|
|
169
174
|
|
|
170
175
|
def __repr__(self) -> str:
|
|
@@ -180,6 +185,8 @@ def _blue(text: str) -> str:
|
|
|
180
185
|
|
|
181
186
|
@dataclass(frozen=True)
|
|
182
187
|
class TestCase:
|
|
188
|
+
"""Container for an experiment test case with example data and repetition number."""
|
|
189
|
+
|
|
183
190
|
example: Example
|
|
184
191
|
repetition_number: RepetitionNumber
|
|
185
192
|
|
|
@@ -194,8 +201,8 @@ def _exp_id() -> str:
|
|
|
194
201
|
|
|
195
202
|
@dataclass(frozen=True)
|
|
196
203
|
class ExperimentRun:
|
|
197
|
-
"""
|
|
198
|
-
|
|
204
|
+
"""Represents a single run of an experiment.
|
|
205
|
+
|
|
199
206
|
Args:
|
|
200
207
|
start_time: The start time of the experiment run.
|
|
201
208
|
end_time: The end time of the experiment run.
|
|
@@ -219,7 +226,8 @@ class ExperimentRun:
|
|
|
219
226
|
trace_id: TraceId | None = None
|
|
220
227
|
|
|
221
228
|
@classmethod
|
|
222
|
-
def from_dict(cls, obj: Mapping[str,
|
|
229
|
+
def from_dict(cls, obj: Mapping[str, object]) -> ExperimentRun:
|
|
230
|
+
"""Create an ExperimentRun instance from a dictionary."""
|
|
223
231
|
return cls(
|
|
224
232
|
start_time=obj["start_time"],
|
|
225
233
|
end_time=obj["end_time"],
|
|
@@ -233,6 +241,11 @@ class ExperimentRun:
|
|
|
233
241
|
)
|
|
234
242
|
|
|
235
243
|
def __post_init__(self) -> None:
|
|
244
|
+
"""Validate that exactly one of output or error is specified.
|
|
245
|
+
|
|
246
|
+
Raises:
|
|
247
|
+
ValueError: If both or neither output and error are specified.
|
|
248
|
+
"""
|
|
236
249
|
if (self.output is None) == (self.error is None):
|
|
237
250
|
raise ValueError(
|
|
238
251
|
"Must specify exactly one of experiment_run_output or error"
|
|
@@ -241,8 +254,8 @@ class ExperimentRun:
|
|
|
241
254
|
|
|
242
255
|
@dataclass(frozen=True)
|
|
243
256
|
class ExperimentEvaluationRun:
|
|
244
|
-
"""
|
|
245
|
-
|
|
257
|
+
"""Represents a single evaluation run of an experiment.
|
|
258
|
+
|
|
246
259
|
Args:
|
|
247
260
|
experiment_run_id: The unique identifier for the experiment run.
|
|
248
261
|
start_time: The start time of the evaluation run.
|
|
@@ -266,7 +279,8 @@ class ExperimentEvaluationRun:
|
|
|
266
279
|
trace_id: TraceId | None = None
|
|
267
280
|
|
|
268
281
|
@classmethod
|
|
269
|
-
def from_dict(cls, obj: Mapping[str,
|
|
282
|
+
def from_dict(cls, obj: Mapping[str, object]) -> ExperimentEvaluationRun:
|
|
283
|
+
"""Create an ExperimentEvaluationRun instance from a dictionary."""
|
|
270
284
|
return cls(
|
|
271
285
|
experiment_run_id=obj["experiment_run_id"],
|
|
272
286
|
start_time=obj["start_time"],
|
|
@@ -280,6 +294,11 @@ class ExperimentEvaluationRun:
|
|
|
280
294
|
)
|
|
281
295
|
|
|
282
296
|
def __post_init__(self) -> None:
|
|
297
|
+
"""Validate that exactly one of result or error is specified.
|
|
298
|
+
|
|
299
|
+
Raises:
|
|
300
|
+
ValueError: If both or neither result and error are specified.
|
|
301
|
+
"""
|
|
283
302
|
if bool(self.result) == bool(self.error):
|
|
284
303
|
raise ValueError("Must specify either result or error")
|
|
285
304
|
|
|
@@ -288,6 +307,11 @@ _LOCAL_TIMEZONE = datetime.now(timezone.utc).astimezone().tzinfo
|
|
|
288
307
|
|
|
289
308
|
|
|
290
309
|
def local_now() -> datetime:
|
|
310
|
+
"""Get the current datetime in the local timezone.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
A datetime object representing the current time in local timezone.
|
|
314
|
+
"""
|
|
291
315
|
return datetime.now(timezone.utc).astimezone(tz=_LOCAL_TIMEZONE)
|
|
292
316
|
|
|
293
317
|
|
|
@@ -303,10 +327,12 @@ class _HasStats:
|
|
|
303
327
|
|
|
304
328
|
def __str__(self) -> str:
|
|
305
329
|
try:
|
|
306
|
-
|
|
330
|
+
pandas_major = int(version("pandas").split(".")[0])
|
|
331
|
+
if pandas_major < 1:
|
|
332
|
+
raise ImportError("Pandas version < 1.0") # noqa: TRY301
|
|
307
333
|
# `tabulate` is used by pandas >= 1.0 in DataFrame.to_markdown()
|
|
308
334
|
import tabulate # noqa: F401
|
|
309
|
-
except
|
|
335
|
+
except ImportError:
|
|
310
336
|
text = self.stats.__str__()
|
|
311
337
|
else:
|
|
312
338
|
text = self.stats.to_markdown(index=False)
|
|
@@ -315,8 +341,7 @@ class _HasStats:
|
|
|
315
341
|
|
|
316
342
|
@dataclass(frozen=True)
|
|
317
343
|
class _TaskSummary(_HasStats):
|
|
318
|
-
"""
|
|
319
|
-
Summary statistics of experiment task executions.
|
|
344
|
+
"""Summary statistics of experiment task executions.
|
|
320
345
|
|
|
321
346
|
**Users should not instantiate this object directly.**
|
|
322
347
|
"""
|
|
@@ -344,7 +369,7 @@ class _TaskSummary(_HasStats):
|
|
|
344
369
|
"n_runs": n_runs,
|
|
345
370
|
"n_errors": n_errors,
|
|
346
371
|
**(
|
|
347
|
-
|
|
372
|
+
{"top_error": _top_string(df.loc[:, "error"])}
|
|
348
373
|
if n_errors
|
|
349
374
|
else {}
|
|
350
375
|
),
|
|
@@ -355,12 +380,12 @@ class _TaskSummary(_HasStats):
|
|
|
355
380
|
return summary
|
|
356
381
|
|
|
357
382
|
@classmethod
|
|
358
|
-
def __new__(cls, *args:
|
|
383
|
+
def __new__(cls, *args: object, **kwargs: object) -> object:
|
|
359
384
|
# Direct instantiation by users is discouraged.
|
|
360
385
|
raise NotImplementedError
|
|
361
386
|
|
|
362
387
|
@classmethod
|
|
363
|
-
def __init_subclass__(cls, **kwargs:
|
|
388
|
+
def __init_subclass__(cls, **kwargs: object) -> None:
|
|
364
389
|
# Direct sub-classing by users is discouraged.
|
|
365
390
|
raise NotImplementedError
|
|
366
391
|
|
|
@@ -368,7 +393,7 @@ class _TaskSummary(_HasStats):
|
|
|
368
393
|
def _top_string(s: pd.Series, length: int = 100) -> str | None:
|
|
369
394
|
if (cnt := s.dropna().str.slice(0, length).value_counts()).empty:
|
|
370
395
|
return None
|
|
371
|
-
return cast(str, cnt.sort_values(ascending=False).index[0])
|
|
396
|
+
return cast("str", cnt.sort_values(ascending=False).index[0])
|
|
372
397
|
|
|
373
398
|
|
|
374
399
|
@dataclass
|
arize/logging.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
"""Logging utilities and configuration for the Arize SDK."""
|
|
2
|
+
|
|
1
3
|
from __future__ import annotations
|
|
2
4
|
|
|
3
5
|
import json
|
|
4
6
|
import logging
|
|
5
7
|
import os
|
|
6
8
|
import sys
|
|
7
|
-
from
|
|
9
|
+
from collections.abc import Iterable, Mapping
|
|
10
|
+
from typing import Any, ClassVar
|
|
8
11
|
|
|
9
12
|
from arize.config import _parse_bool
|
|
10
13
|
from arize.constants.config import (
|
|
@@ -30,7 +33,10 @@ _LEVEL_MAP = {
|
|
|
30
33
|
class CtxAdapter(logging.LoggerAdapter):
|
|
31
34
|
"""LoggerAdapter that merges bound context with per-call extras safely."""
|
|
32
35
|
|
|
33
|
-
def process(
|
|
36
|
+
def process(
|
|
37
|
+
self, msg: object, kwargs: dict[str, object]
|
|
38
|
+
) -> tuple[object, dict[str, object]]:
|
|
39
|
+
"""Process the logging call by merging bound and call extras."""
|
|
34
40
|
call_extra = _coerce_mapping(kwargs.pop("extra", None))
|
|
35
41
|
bound_extra = _coerce_mapping(self.extra)
|
|
36
42
|
merged = (
|
|
@@ -42,7 +48,7 @@ class CtxAdapter(logging.LoggerAdapter):
|
|
|
42
48
|
kwargs["extra"] = merged
|
|
43
49
|
return msg, kwargs
|
|
44
50
|
|
|
45
|
-
def with_extra(self, **more) -> CtxAdapter:
|
|
51
|
+
def with_extra(self, **more: object) -> CtxAdapter:
|
|
46
52
|
"""Return a copy of this adapter with additional bound extras."""
|
|
47
53
|
base = _coerce_mapping(self.extra)
|
|
48
54
|
base.update(_coerce_mapping(more))
|
|
@@ -54,12 +60,7 @@ class CtxAdapter(logging.LoggerAdapter):
|
|
|
54
60
|
|
|
55
61
|
|
|
56
62
|
class CustomLogFormatter(logging.Formatter):
|
|
57
|
-
|
|
58
|
-
blue = "\x1b[38;5;39m"
|
|
59
|
-
yellow = "\x1b[33m"
|
|
60
|
-
red = "\x1b[38;5;196m"
|
|
61
|
-
bold_red = "\x1b[31;1m"
|
|
62
|
-
reset = "\x1b[0m"
|
|
63
|
+
"""Custom log formatter with color-coded output based on log level."""
|
|
63
64
|
|
|
64
65
|
GREY = "\x1b[38;21m"
|
|
65
66
|
BLUE = "\x1b[38;5;39m"
|
|
@@ -68,7 +69,7 @@ class CustomLogFormatter(logging.Formatter):
|
|
|
68
69
|
BOLD_RED = "\x1b[31;1m"
|
|
69
70
|
RESET = "\x1b[0m"
|
|
70
71
|
|
|
71
|
-
COLORS = {
|
|
72
|
+
COLORS: ClassVar[dict[int, str]] = {
|
|
72
73
|
logging.DEBUG: BLUE,
|
|
73
74
|
logging.INFO: GREY,
|
|
74
75
|
logging.WARNING: YELLOW,
|
|
@@ -76,10 +77,16 @@ class CustomLogFormatter(logging.Formatter):
|
|
|
76
77
|
logging.CRITICAL: BOLD_RED,
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
def __init__(self, fmt: str):
|
|
80
|
+
def __init__(self, fmt: str) -> None:
|
|
81
|
+
"""Initialize the colored formatter with a format string.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
fmt: Format string for log messages.
|
|
85
|
+
"""
|
|
80
86
|
super().__init__(fmt=fmt)
|
|
81
87
|
|
|
82
88
|
def format(self, record: logging.LogRecord) -> str:
|
|
89
|
+
"""Format the log record with color based on log level."""
|
|
83
90
|
# Build the base message without any color.
|
|
84
91
|
base = super().format(record)
|
|
85
92
|
|
|
@@ -106,7 +113,7 @@ class JsonFormatter(logging.Formatter):
|
|
|
106
113
|
"""Minimal JSON formatter (one JSON object per line)."""
|
|
107
114
|
|
|
108
115
|
# fields to skip copying from record.__dict__
|
|
109
|
-
_skip = {
|
|
116
|
+
_skip: ClassVar[set[str]] = {
|
|
110
117
|
# "name",
|
|
111
118
|
# "msg",
|
|
112
119
|
# "args",
|
|
@@ -130,7 +137,8 @@ class JsonFormatter(logging.Formatter):
|
|
|
130
137
|
}
|
|
131
138
|
|
|
132
139
|
def format(self, record: logging.LogRecord) -> str:
|
|
133
|
-
|
|
140
|
+
"""Format the log record as a JSON string."""
|
|
141
|
+
payload: dict[str, object] = {
|
|
134
142
|
# "time": self.formatTime(record, datefmt="%Y-%m-%dT%H:%M:%S%z"),
|
|
135
143
|
# "logger": record.name,
|
|
136
144
|
# "level": record.levelname,
|
|
@@ -156,12 +164,11 @@ def _parse_level(val: str | None, default: int = logging.INFO) -> int:
|
|
|
156
164
|
|
|
157
165
|
|
|
158
166
|
def auto_configure_from_env() -> None:
|
|
159
|
-
"""
|
|
160
|
-
|
|
161
|
-
|
|
167
|
+
"""If ARIZE_LOG is truthy, configure logging for 'arize' once.
|
|
168
|
+
|
|
169
|
+
Using ARIZE_LOG_LEVEL and ARIZE_LOG_STRUCTURED if provided.
|
|
162
170
|
Otherwise, do nothing (library stays quiet with NullHandler).
|
|
163
171
|
"""
|
|
164
|
-
|
|
165
172
|
if not _parse_bool(os.getenv(ENV_LOG_ENABLE, DEFAULT_LOG_ENABLE)):
|
|
166
173
|
return
|
|
167
174
|
|
|
@@ -201,7 +208,16 @@ _STANDARD_RECORD_KEYS = {
|
|
|
201
208
|
}
|
|
202
209
|
|
|
203
210
|
|
|
204
|
-
def get_truncation_warning_message(instance, limit) -> str:
|
|
211
|
+
def get_truncation_warning_message(instance: str, limit: int) -> str:
|
|
212
|
+
"""Generate a warning message for data that exceeds character limits.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
instance: The data instance type that may be truncated.
|
|
216
|
+
limit: The character limit threshold.
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
A formatted warning message string.
|
|
220
|
+
"""
|
|
205
221
|
return (
|
|
206
222
|
f"Attention: {instance} exceeding the {limit} character limit will be "
|
|
207
223
|
"automatically truncated upon ingestion into the Arize platform. Should you require "
|
|
@@ -213,8 +229,7 @@ def configure_logging(
|
|
|
213
229
|
level: int = logging.INFO,
|
|
214
230
|
structured: bool = False,
|
|
215
231
|
) -> None:
|
|
216
|
-
"""
|
|
217
|
-
Configure logging for the 'arize' logger.
|
|
232
|
+
"""Configure logging for the 'arize' logger.
|
|
218
233
|
|
|
219
234
|
Args:
|
|
220
235
|
level: logging level (e.g., logging.INFO, logging.DEBUG)
|
|
@@ -241,6 +256,15 @@ def configure_logging(
|
|
|
241
256
|
|
|
242
257
|
|
|
243
258
|
def log_a_list(values: Iterable[Any] | None, join_word: str) -> str:
|
|
259
|
+
"""Format a list of values into a human-readable string with a joining word.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
values: An iterable of values to format, or None.
|
|
263
|
+
join_word: The word to use before the last item (e.g., "and", "or").
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
A formatted string joining the values, or empty string if no values.
|
|
267
|
+
"""
|
|
244
268
|
if values is None:
|
|
245
269
|
return ""
|
|
246
270
|
list_of_str = list(values)
|
|
@@ -253,13 +277,21 @@ def log_a_list(values: Iterable[Any] | None, join_word: str) -> str:
|
|
|
253
277
|
)
|
|
254
278
|
|
|
255
279
|
|
|
256
|
-
def get_arize_project_url(response:
|
|
280
|
+
def get_arize_project_url(response: object) -> str:
|
|
281
|
+
"""Extract the Arize project URL from an API response.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
response: The HTTP response object from an Arize API call.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
The real-time ingestion URI if present, otherwise an empty string.
|
|
288
|
+
"""
|
|
257
289
|
if "realTimeIngestionUri" in json.loads(response.content.decode()):
|
|
258
290
|
return json.loads(response.content.decode())["realTimeIngestionUri"]
|
|
259
291
|
return ""
|
|
260
292
|
|
|
261
293
|
|
|
262
|
-
def _coerce_mapping(obj:
|
|
294
|
+
def _coerce_mapping(obj: object) -> dict[str, object]:
|
|
263
295
|
"""Return a shallow dict copy if obj is a Mapping[str, Any], else {}."""
|
|
264
296
|
if isinstance(obj, Mapping):
|
|
265
297
|
# force keys to str to satisfy logging's expectation
|
arize/ml/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Model logging and validation functionality for the Arize SDK."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Batch validation utilities for model data."""
|