evidently
evidently copied to clipboard
Unable to add DataStabilityTestPreset to workspace (datetime)
Bug description: When trying to add a test suite to a local or remote workspace, both DataStabilityTestPreset and DataQualityTestPreset fail because of a JSON serialization error with datetime.
Environment:
- MacOS Ventura 13.5.2 (M2 Chip)
- python 3.9
Minimal reproducible example:
from evidently.test_preset import (
DataQualityTestPreset,
DataStabilityTestPreset,
)
from evidently.test_suite import TestSuite
from evidently.ui.workspace import Workspace
from evidently import ColumnMapping
import pandas as pd
import datetime as datetime
ref = pd.DataFrame(
{
"my_target": [1, 2, 3],
"prediction": [1, 2, 3],
"feature1": [1, 2, 3],
"feature2": ["a", "b", "c"],
"datetime": [
datetime.datetime(2023, 10, 1),
datetime.datetime(2023, 11, 1),
datetime.datetime(2023, 12, 1),
],
}
)
curr = pd.DataFrame(
{
"my_target": [1, 2, 3],
"prediction": [1, 2, 3],
"feature1": [1.5, 2.5, 3.2],
"feature2": ["c", "b", "a"],
"datetime": [
datetime.datetime(2023, 10, 1),
datetime.datetime(2023, 11, 1),
datetime.datetime(2023, 12, 1),
],
}
)
test_suite = TestSuite(
tests=[DataStabilityTestPreset(columns=["feature1", "feature2"])]
)
column_mapping = ColumnMapping(target="my_target", datetime="datetime")
test_suite.run(reference_data=ref, current_data=curr, column_mapping=column_mapping)
workspace = Workspace.create("workspace")
project = workspace.create_project("test_project")
workspace.add_test_suite(project.id, test_suite)
Error shown below:
TypeError Traceback (most recent call last)
Cell In[109], line 1
----> 1 workspace.add_test_suite(project.id, test_suite)
File [~/miniconda3/lib/python3.9/site-packages/evidently/ui/workspace.py:248](https://file+.vscode-resource.vscode-cdn.net/Users/miniconda3/lib/python3.9/site-packages/evidently/ui/workspace.py:248), in WorkspaceBase.add_test_suite(self, project_id, test_suite)
247 def add_test_suite(self, project_id: STR_UUID, test_suite: TestSuite):
--> 248 self.add_snapshot(project_id, test_suite.to_snapshot())
File [~/miniconda3/lib/python3.9/site-packages/evidently/ui/workspace.py:309](https://file+.vscode-resource.vscode-cdn.net/Users/examples/miniconda3/lib/python3.9/site-packages/evidently/ui/workspace.py:309), in Workspace.add_snapshot(self, project_id, snapshot)
307 if isinstance(project_id, str):
308 project_id = uuid.UUID(project_id)
--> 309 self._projects[project_id].add_snapshot(snapshot)
File [~/miniconda3/lib/python3.9/site-packages/evidently/ui/workspace.py:125](https://file+.vscode-resource.vscode-cdn.net/Users/miniconda3/lib/python3.9/site-packages/evidently/ui/workspace.py:125), in Project.add_snapshot(self, snapshot)
123 def add_snapshot(self, snapshot: Snapshot):
124 item = ProjectSnapshot(snapshot.id, self, snapshot)
--> 125 snapshot.save(item.path)
126 self._snapshots[item.id] = item
File [~/miniconda3/lib/python3.9/site-packages/evidently/suite/base_suite.py:449](https://file+.vscode-resource.vscode-cdn.net/Users/miniconda3/lib/python3.9/site-packages/evidently/suite/base_suite.py:449), in save(self, filename)
437 except BaseException as ex:
438 test_results[test] = TestResult(
439 name=test.name,
440 status=TestStatus.ERROR,
(...)
444 exception=ex,
445 )
446 test_results[test].groups.update(
447 {
448 GroupingTypes.TestGroup.id: test.group,
--> 449 GroupingTypes.TestType.id: test.name,
450 }
451 )
453 self.context.test_results = test_results
454 self.context.state = States.Tested
File [~/miniconda3/lib/python3.9/json/__init__.py:179](https://file+.vscode-resource.vscode-cdn.net/Users/miniconda3/lib/python3.9/json/__init__.py:179), in dump(obj, fp, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw)
173 iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
174 check_circular=check_circular, allow_nan=allow_nan, indent=indent,
175 separators=separators,
176 default=default, sort_keys=sort_keys, **kw).iterencode(obj)
177 # could accelerate with writelines in some versions of Python, at
178 # a debuggability cost
--> 179 for chunk in iterable:
180 fp.write(chunk)
File [~/miniconda3/lib/python3.9/json/encoder.py:431](https://file+.vscode-resource.vscode-cdn.net/Users/miniconda3/lib/python3.9/json/encoder.py:431), in _make_iterencode.<locals>._iterencode(o, _current_indent_level)
429 yield from _iterencode_list(o, _current_indent_level)
430 elif isinstance(o, dict):
--> 431 yield from _iterencode_dict(o, _current_indent_level)
432 else:
433 if markers is not None:
File [~/miniconda3/lib/python3.9/json/encoder.py:405](https://file+.vscode-resource.vscode-cdn.net/Users/miniconda3/lib/python3.9/json/encoder.py:405), in _make_iterencode.<locals>._iterencode_dict(dct, _current_indent_level)
403 else:
404 chunks = _iterencode(value, _current_indent_level)
--> 405 yield from chunks
406 if newline_indent is not None:
407 _current_indent_level -= 1
File [~/miniconda3/lib/python3.9/json/encoder.py:405](https://file+.vscode-resource.vscode-cdn.net/Users/miniconda3/lib/python3.9/json/encoder.py:405), in _make_iterencode.<locals>._iterencode_dict(dct, _current_indent_level)
403 else:
404 chunks = _iterencode(value, _current_indent_level)
--> 405 yield from chunks
406 if newline_indent is not None:
407 _current_indent_level -= 1
File [~/miniconda3/lib/python3.9/json/encoder.py:325](https://file+.vscode-resource.vscode-cdn.net/Users/miniconda3/lib/python3.9/json/encoder.py:325), in _make_iterencode.<locals>._iterencode_list(lst, _current_indent_level)
323 else:
324 chunks = _iterencode(value, _current_indent_level)
--> 325 yield from chunks
326 if newline_indent is not None:
327 _current_indent_level -= 1
File [~/miniconda3/lib/python3.9/json/encoder.py:405](https://file+.vscode-resource.vscode-cdn.net/Users/miniconda3/lib/python3.9/json/encoder.py:405), in _make_iterencode.<locals>._iterencode_dict(dct, _current_indent_level)
403 else:
404 chunks = _iterencode(value, _current_indent_level)
--> 405 yield from chunks
406 if newline_indent is not None:
407 _current_indent_level -= 1
File [~/miniconda3/lib/python3.9/json/encoder.py:405](https://file+.vscode-resource.vscode-cdn.net/Users/miniconda3/lib/python3.9/json/encoder.py:405), in _make_iterencode.<locals>._iterencode_dict(dct, _current_indent_level)
403 else:
404 chunks = _iterencode(value, _current_indent_level)
--> 405 yield from chunks
406 if newline_indent is not None:
407 _current_indent_level -= 1
[... skipping similar frames: _make_iterencode.<locals>._iterencode_dict at line 405 (1 times)]
File [~/miniconda3/lib/python3.9/json/encoder.py:405](https://file+.vscode-resource.vscode-cdn.net/Users/miniconda3/lib/python3.9/json/encoder.py:405), in _make_iterencode.<locals>._iterencode_dict(dct, _current_indent_level)
403 else:
404 chunks = _iterencode(value, _current_indent_level)
--> 405 yield from chunks
406 if newline_indent is not None:
407 _current_indent_level -= 1
File [~/miniconda3/lib/python3.9/json/encoder.py:439](https://file+.vscode-resource.vscode-cdn.net/Users/miniconda3/lib/python3.9/json/encoder.py:439), in _make_iterencode.<locals>._iterencode(o, _current_indent_level)
437 markers[markerid] = o
438 o = _default(o)
--> 439 yield from _iterencode(o, _current_indent_level)
440 if markers is not None:
441 del markers[markerid]
File [~/miniconda3/lib/python3.9/json/encoder.py:431](https://file+.vscode-resource.vscode-cdn.net/Users/miniconda3/lib/python3.9/json/encoder.py:431), in _make_iterencode.<locals>._iterencode(o, _current_indent_level)
429 yield from _iterencode_list(o, _current_indent_level)
430 elif isinstance(o, dict):
--> 431 yield from _iterencode_dict(o, _current_indent_level)
432 else:
433 if markers is not None:
File [~/miniconda3/lib/python3.9/json/encoder.py:405](https://file+.vscode-resource.vscode-cdn.net/Users/miniconda3/lib/python3.9/json/encoder.py:405), in _make_iterencode.<locals>._iterencode_dict(dct, _current_indent_level)
403 else:
404 chunks = _iterencode(value, _current_indent_level)
--> 405 yield from chunks
406 if newline_indent is not None:
407 _current_indent_level -= 1
File [~/miniconda3/lib/python3.9/json/encoder.py:405](https://file+.vscode-resource.vscode-cdn.net/Users/miniconda3/lib/python3.9/json/encoder.py:405), in _make_iterencode.<locals>._iterencode_dict(dct, _current_indent_level)
403 else:
404 chunks = _iterencode(value, _current_indent_level)
--> 405 yield from chunks
406 if newline_indent is not None:
407 _current_indent_level -= 1
File [~/miniconda3/lib/python3.9/json/encoder.py:438](https://file+.vscode-resource.vscode-cdn.net/Users/miniconda3/lib/python3.9/json/encoder.py:438), in _make_iterencode.<locals>._iterencode(o, _current_indent_level)
436 raise ValueError("Circular reference detected")
437 markers[markerid] = o
--> 438 o = _default(o)
439 yield from _iterencode(o, _current_indent_level)
440 if markers is not None:
File [~/miniconda3/lib/python3.9/site-packages/evidently/utils/numpy_encoder.py:45](https://file+.vscode-resource.vscode-cdn.net/Users/miniconda3/lib/python3.9/site-packages/evidently/utils/numpy_encoder.py:45), in default(self, o)
8 from evidently.core import ColumnType
9 from evidently.utils.types import ApproxValue
11 _TYPES_MAPPING = (
12 (
13 (
14 np.int_,
15 np.intc,
16 np.intp,
17 np.int8,
18 np.int16,
19 np.int32,
20 np.int64,
21 np.uint8,
22 np.uint16,
23 np.uint32,
24 np.uint64,
25 ),
26 int,
27 ),
28 (
29 (np.float_, np.float16, np.float32, np.float64),
30 lambda obj: None if obj is np.nan else float(obj),
31 ),
32 ((np.ndarray,), lambda obj: obj.tolist()),
33 ((np.bool_), bool),
34 ((pd.Timedelta,), str),
35 (
36 (np.void, type(pd.NaT)),
37 lambda obj: None,
38 ), # should be before datetime as NaT is subclass of datetime.
39 ((pd.Timestamp, datetime.datetime, datetime.date), lambda obj: obj.isoformat()),
40 # map ApproxValue to json value
41 ((ApproxValue,), lambda obj: obj.dict()),
42 ((pd.Series, pd.Index, pd.Categorical), lambda obj: obj.tolist()),
43 ((pd.DataFrame,), lambda obj: obj.to_dict()),
44 ((frozenset,), lambda obj: list(obj)),
---> 45 ((uuid.UUID,), lambda obj: str(obj)),
46 ((ColumnType,), lambda obj: obj.value),
47 )
50 class NumpyEncoder(json.JSONEncoder):
51 """Numpy and Pandas data types to JSON types encoder"""
File [~/miniconda3/lib/python3.9/json/encoder.py:179](https://file+.vscode-resource.vscode-cdn.net/Users/miniconda3/lib/python3.9/json/encoder.py:179), in JSONEncoder.default(self, o)
160 def default(self, o):
161 """Implement this method in a subclass such that it returns
162 a serializable object for ``o``, or calls the base implementation
163 (to raise a ``TypeError``).
(...)
177
178 """
--> 179 raise TypeError(f'Object of type {o.__class__.__name__} '
180 f'is not JSON serializable')
TypeError: Object of type Period is not JSON serializable
Hey @joshcx! Thanks for bug report! Please check the branch above, it should fix it
Seems to work on my end. Thanks for the quick fix @mike0sv!