validmind 2.8.12__py3-none-any.whl → 2.8.22__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.
- validmind/__init__.py +6 -5
- validmind/__version__.py +1 -1
- validmind/ai/test_descriptions.py +13 -9
- validmind/ai/utils.py +2 -2
- validmind/api_client.py +75 -32
- validmind/client.py +111 -100
- validmind/client_config.py +3 -3
- validmind/datasets/classification/__init__.py +7 -3
- validmind/datasets/credit_risk/lending_club.py +28 -16
- validmind/datasets/nlp/cnn_dailymail.py +10 -4
- validmind/datasets/regression/__init__.py +22 -5
- validmind/errors.py +17 -7
- validmind/input_registry.py +1 -1
- validmind/logging.py +44 -35
- validmind/models/foundation.py +2 -2
- validmind/models/function.py +10 -3
- validmind/template.py +33 -24
- validmind/test_suites/__init__.py +2 -2
- validmind/tests/_store.py +13 -4
- validmind/tests/comparison.py +65 -33
- validmind/tests/data_validation/ClassImbalance.py +3 -1
- validmind/tests/data_validation/DatasetDescription.py +2 -23
- validmind/tests/data_validation/DescriptiveStatistics.py +1 -1
- validmind/tests/data_validation/Skewness.py +7 -6
- validmind/tests/decorator.py +14 -11
- validmind/tests/load.py +38 -24
- validmind/tests/model_validation/ragas/AnswerCorrectness.py +4 -2
- validmind/tests/model_validation/ragas/ContextEntityRecall.py +4 -2
- validmind/tests/model_validation/ragas/ContextPrecision.py +4 -2
- validmind/tests/model_validation/ragas/ContextPrecisionWithoutReference.py +4 -2
- validmind/tests/model_validation/ragas/ContextRecall.py +4 -2
- validmind/tests/model_validation/ragas/Faithfulness.py +4 -2
- validmind/tests/model_validation/ragas/ResponseRelevancy.py +4 -2
- validmind/tests/model_validation/ragas/SemanticSimilarity.py +4 -2
- validmind/tests/model_validation/sklearn/ClassifierThresholdOptimization.py +13 -3
- validmind/tests/model_validation/sklearn/OverfitDiagnosis.py +3 -1
- validmind/tests/model_validation/sklearn/SHAPGlobalImportance.py +28 -25
- validmind/tests/model_validation/sklearn/WeakspotsDiagnosis.py +15 -10
- validmind/tests/output.py +66 -11
- validmind/tests/run.py +28 -14
- validmind/tests/test_providers.py +28 -35
- validmind/tests/utils.py +17 -4
- validmind/unit_metrics/__init__.py +1 -1
- validmind/utils.py +295 -31
- validmind/vm_models/dataset/dataset.py +83 -43
- validmind/vm_models/dataset/utils.py +5 -3
- validmind/vm_models/figure.py +6 -6
- validmind/vm_models/input.py +6 -5
- validmind/vm_models/model.py +5 -5
- validmind/vm_models/result/result.py +122 -43
- validmind/vm_models/result/utils.py +5 -5
- validmind/vm_models/test_suite/__init__.py +5 -0
- validmind/vm_models/test_suite/runner.py +5 -5
- validmind/vm_models/test_suite/summary.py +20 -2
- validmind/vm_models/test_suite/test.py +6 -6
- validmind/vm_models/test_suite/test_suite.py +10 -10
- {validmind-2.8.12.dist-info → validmind-2.8.22.dist-info}/METADATA +3 -4
- {validmind-2.8.12.dist-info → validmind-2.8.22.dist-info}/RECORD +61 -60
- {validmind-2.8.12.dist-info → validmind-2.8.22.dist-info}/WHEEL +1 -1
- {validmind-2.8.12.dist-info → validmind-2.8.22.dist-info}/LICENSE +0 -0
- {validmind-2.8.12.dist-info → validmind-2.8.22.dist-info}/entry_points.txt +0 -0
@@ -45,11 +45,11 @@ class ExtraColumns:
|
|
45
45
|
)
|
46
46
|
|
47
47
|
def __contains__(self, key):
|
48
|
-
"""Allow checking if a key is `in` the extra columns"""
|
48
|
+
"""Allow checking if a key is `in` the extra columns."""
|
49
49
|
return key in self.flatten()
|
50
50
|
|
51
51
|
def flatten(self) -> List[str]:
|
52
|
-
"""Get a list of all column names"""
|
52
|
+
"""Get a list of all column names."""
|
53
53
|
return [
|
54
54
|
self.group_by_column,
|
55
55
|
*self.extras,
|
@@ -78,13 +78,14 @@ class ExtraColumns:
|
|
78
78
|
|
79
79
|
|
80
80
|
def as_df(series_or_frame: Union[pd.Series, pd.DataFrame]) -> pd.DataFrame:
|
81
|
+
"""Convert a pandas Series or DataFrame to a DataFrame."""
|
81
82
|
if isinstance(series_or_frame, pd.Series):
|
82
83
|
return series_or_frame.to_frame()
|
83
84
|
return series_or_frame
|
84
85
|
|
85
86
|
|
86
87
|
def _is_probabilties(output):
|
87
|
-
"""Check if the output
|
88
|
+
"""Check if the output is a probability array."""
|
88
89
|
if not isinstance(output, np.ndarray) or output.ndim > 1:
|
89
90
|
return False
|
90
91
|
|
@@ -98,6 +99,7 @@ def _is_probabilties(output):
|
|
98
99
|
|
99
100
|
|
100
101
|
def compute_predictions(model, X, **kwargs) -> tuple:
|
102
|
+
"""Compute predictions and probabilities for a model."""
|
101
103
|
probability_values = None
|
102
104
|
|
103
105
|
try:
|
validmind/vm_models/figure.py
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
|
4
4
|
|
5
5
|
"""
|
6
|
-
Figure objects track the figure schema supported by the ValidMind API
|
6
|
+
Figure objects track the figure schema supported by the ValidMind API.
|
7
7
|
"""
|
8
8
|
|
9
9
|
import base64
|
@@ -38,7 +38,7 @@ def create_figure(
|
|
38
38
|
key: str,
|
39
39
|
ref_id: str,
|
40
40
|
) -> "Figure":
|
41
|
-
"""Create a VM Figure object from a raw figure object"""
|
41
|
+
"""Create a VM Figure object from a raw figure object."""
|
42
42
|
if is_matplotlib_figure(figure) or is_plotly_figure(figure) or is_png_image(figure):
|
43
43
|
return Figure(key=key, figure=figure, ref_id=ref_id)
|
44
44
|
|
@@ -48,7 +48,7 @@ def create_figure(
|
|
48
48
|
@dataclass
|
49
49
|
class Figure:
|
50
50
|
"""
|
51
|
-
Figure objects track the schema supported by the ValidMind API
|
51
|
+
Figure objects track the schema supported by the ValidMind API.
|
52
52
|
"""
|
53
53
|
|
54
54
|
key: str
|
@@ -115,7 +115,7 @@ class Figure:
|
|
115
115
|
|
116
116
|
def serialize(self):
|
117
117
|
"""
|
118
|
-
Serializes the Figure to a dictionary so it can be sent to the API
|
118
|
+
Serializes the Figure to a dictionary so it can be sent to the API.
|
119
119
|
"""
|
120
120
|
return {
|
121
121
|
"type": self._type,
|
@@ -125,7 +125,7 @@ class Figure:
|
|
125
125
|
|
126
126
|
def _get_b64_url(self):
|
127
127
|
"""
|
128
|
-
Returns a base64 encoded URL for the figure
|
128
|
+
Returns a base64 encoded URL for the figure.
|
129
129
|
"""
|
130
130
|
if is_matplotlib_figure(self.figure):
|
131
131
|
buffer = BytesIO()
|
@@ -152,7 +152,7 @@ class Figure:
|
|
152
152
|
)
|
153
153
|
|
154
154
|
def serialize_files(self):
|
155
|
-
"""Creates a `requests`-compatible files object to be sent to the API"""
|
155
|
+
"""Creates a `requests`-compatible files object to be sent to the API."""
|
156
156
|
if is_matplotlib_figure(self.figure):
|
157
157
|
buffer = BytesIO()
|
158
158
|
self.figure.savefig(buffer, bbox_inches="tight")
|
validmind/vm_models/input.py
CHANGED
@@ -5,27 +5,28 @@
|
|
5
5
|
"""Base class for ValidMind Input types"""
|
6
6
|
|
7
7
|
from abc import ABC
|
8
|
+
from typing import Any, Dict
|
8
9
|
|
9
10
|
|
10
11
|
class VMInput(ABC):
|
11
12
|
"""
|
12
|
-
Base class for ValidMind Input types
|
13
|
+
Base class for ValidMind Input types.
|
13
14
|
"""
|
14
15
|
|
15
|
-
def with_options(self, **kwargs) -> "VMInput":
|
16
|
+
def with_options(self, **kwargs: Dict[str, Any]) -> "VMInput":
|
16
17
|
"""
|
17
18
|
Allows for setting options on the input object that are passed by the user
|
18
|
-
when using the input to run a test or set of tests
|
19
|
+
when using the input to run a test or set of tests.
|
19
20
|
|
20
21
|
To allow options, just override this method in the subclass (see VMDataset)
|
21
22
|
and ensure that it returns a new instance of the input with the specified options
|
22
23
|
set.
|
23
24
|
|
24
25
|
Args:
|
25
|
-
**kwargs: Arbitrary keyword arguments that will be passed to the input object
|
26
|
+
**kwargs: Arbitrary keyword arguments that will be passed to the input object.
|
26
27
|
|
27
28
|
Returns:
|
28
|
-
VMInput: A new instance of the input with the specified options set
|
29
|
+
VMInput: A new instance of the input with the specified options set.
|
29
30
|
"""
|
30
31
|
if kwargs:
|
31
32
|
raise NotImplementedError("This type of input does not support options")
|
validmind/vm_models/model.py
CHANGED
@@ -40,7 +40,7 @@ R_MODEL_METHODS = [
|
|
40
40
|
|
41
41
|
|
42
42
|
class ModelTask(Enum):
|
43
|
-
"""Model task enums"""
|
43
|
+
"""Model task enums."""
|
44
44
|
|
45
45
|
# TODO: add more tasks
|
46
46
|
CLASSIFICATION = "classification"
|
@@ -67,7 +67,7 @@ class ModelPipeline:
|
|
67
67
|
@dataclass
|
68
68
|
class ModelAttributes:
|
69
69
|
"""
|
70
|
-
Model attributes definition
|
70
|
+
Model attributes definition.
|
71
71
|
"""
|
72
72
|
|
73
73
|
architecture: str = None
|
@@ -79,7 +79,7 @@ class ModelAttributes:
|
|
79
79
|
@classmethod
|
80
80
|
def from_dict(cls, data):
|
81
81
|
"""
|
82
|
-
Creates a ModelAttributes instance from a dictionary
|
82
|
+
Creates a ModelAttributes instance from a dictionary.
|
83
83
|
"""
|
84
84
|
return cls(
|
85
85
|
architecture=data.get("architecture"),
|
@@ -235,8 +235,8 @@ def is_model_metadata(model):
|
|
235
235
|
Checks if the model is a dictionary containing metadata about a model.
|
236
236
|
We want to check if the metadata dictionary contains at least the following keys:
|
237
237
|
|
238
|
-
-
|
239
|
-
-
|
238
|
+
- Architecture
|
239
|
+
- Language
|
240
240
|
"""
|
241
241
|
if not isinstance(model, dict):
|
242
242
|
return False
|
@@ -3,7 +3,7 @@
|
|
3
3
|
# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
|
4
4
|
|
5
5
|
"""
|
6
|
-
Result
|
6
|
+
Result objects for test results
|
7
7
|
"""
|
8
8
|
import asyncio
|
9
9
|
import json
|
@@ -19,6 +19,7 @@ from ipywidgets import HTML, VBox
|
|
19
19
|
|
20
20
|
from ... import api_client
|
21
21
|
from ...ai.utils import DescriptionFuture
|
22
|
+
from ...errors import InvalidParameterError
|
22
23
|
from ...logging import get_logger
|
23
24
|
from ...utils import (
|
24
25
|
HumanReadableEncoder,
|
@@ -43,15 +44,15 @@ logger = get_logger(__name__)
|
|
43
44
|
|
44
45
|
|
45
46
|
class RawData:
|
46
|
-
"""Holds raw data for a test result"""
|
47
|
+
"""Holds raw data for a test result."""
|
47
48
|
|
48
|
-
def __init__(self, log: bool = False, **kwargs):
|
49
|
-
"""Create a new RawData object
|
49
|
+
def __init__(self, log: bool = False, **kwargs: Any) -> None:
|
50
|
+
"""Create a new RawData object.
|
50
51
|
|
51
52
|
Args:
|
52
|
-
log (bool): If True, log the raw data to ValidMind
|
53
|
-
**kwargs: Keyword arguments to set as attributes
|
54
|
-
`RawData(log=True, dataset_duplicates=df_duplicates)
|
53
|
+
log (bool): If True, log the raw data to ValidMind.
|
54
|
+
**kwargs: Keyword arguments to set as attributes, such as
|
55
|
+
`RawData(log=True, dataset_duplicates=df_duplicates)`.
|
55
56
|
"""
|
56
57
|
self.log = log
|
57
58
|
|
@@ -61,8 +62,16 @@ class RawData:
|
|
61
62
|
def __repr__(self) -> str:
|
62
63
|
return f"RawData({', '.join(self.__dict__.keys())})"
|
63
64
|
|
64
|
-
def inspect(self, show: bool = True):
|
65
|
-
"""Inspect the raw data
|
65
|
+
def inspect(self, show: bool = True) -> Optional[Dict[str, Any]]:
|
66
|
+
"""Inspect the raw data.
|
67
|
+
|
68
|
+
Args:
|
69
|
+
show (bool): If True, print the raw data. If False, return it.
|
70
|
+
|
71
|
+
Returns:
|
72
|
+
Optional[Dict[str, Any]]: If True, print the raw data and return None. If
|
73
|
+
False, return the raw data dictionary.
|
74
|
+
"""
|
66
75
|
raw_data = {
|
67
76
|
key: getattr(self, key)
|
68
77
|
for key in self.__dict__
|
@@ -73,15 +82,21 @@ class RawData:
|
|
73
82
|
return raw_data
|
74
83
|
|
75
84
|
print(json.dumps(raw_data, indent=2, cls=HumanReadableEncoder))
|
85
|
+
return None
|
76
86
|
|
77
|
-
def serialize(self):
|
87
|
+
def serialize(self) -> Dict[str, Any]:
|
88
|
+
"""Serialize the raw data to a dictionary
|
89
|
+
|
90
|
+
Returns:
|
91
|
+
Dict[str, Any]: The serialized raw data
|
92
|
+
"""
|
78
93
|
return {key: getattr(self, key) for key in self.__dict__}
|
79
94
|
|
80
95
|
|
81
96
|
@dataclass
|
82
97
|
class ResultTable:
|
83
98
|
"""
|
84
|
-
A dataclass that holds the table summary of result
|
99
|
+
A dataclass that holds the table summary of result.
|
85
100
|
"""
|
86
101
|
|
87
102
|
data: Union[List[Any], pd.DataFrame]
|
@@ -110,33 +125,33 @@ class ResultTable:
|
|
110
125
|
|
111
126
|
@dataclass
|
112
127
|
class Result:
|
113
|
-
"""Base Class for test suite results"""
|
128
|
+
"""Base Class for test suite results."""
|
114
129
|
|
115
130
|
result_id: str = None
|
116
131
|
name: str = None
|
117
132
|
|
118
133
|
def __str__(self) -> str:
|
119
|
-
"""May be overridden by subclasses"""
|
134
|
+
"""May be overridden by subclasses."""
|
120
135
|
return self.__class__.__name__
|
121
136
|
|
122
137
|
@abstractmethod
|
123
138
|
def to_widget(self):
|
124
|
-
"""Create an
|
139
|
+
"""Create an ipywidget representation of the result... Must be overridden by subclasses."""
|
125
140
|
raise NotImplementedError
|
126
141
|
|
127
142
|
@abstractmethod
|
128
143
|
def log(self):
|
129
|
-
"""Log the result... Must be overridden by subclasses"""
|
144
|
+
"""Log the result... Must be overridden by subclasses."""
|
130
145
|
raise NotImplementedError
|
131
146
|
|
132
147
|
def show(self):
|
133
|
-
"""Display the result... May be overridden by subclasses"""
|
148
|
+
"""Display the result... May be overridden by subclasses."""
|
134
149
|
display(self.to_widget())
|
135
150
|
|
136
151
|
|
137
152
|
@dataclass
|
138
153
|
class ErrorResult(Result):
|
139
|
-
"""Result for test suites that fail to load or run properly"""
|
154
|
+
"""Result for test suites that fail to load or run properly."""
|
140
155
|
|
141
156
|
name: str = "Failed Test"
|
142
157
|
error: Exception = None
|
@@ -154,7 +169,7 @@ class ErrorResult(Result):
|
|
154
169
|
|
155
170
|
@dataclass
|
156
171
|
class TestResult(Result):
|
157
|
-
"""Test result"""
|
172
|
+
"""Test result."""
|
158
173
|
|
159
174
|
name: str = "Test Result"
|
160
175
|
ref_id: str = None
|
@@ -232,12 +247,12 @@ class TestResult(Result):
|
|
232
247
|
table: Union[ResultTable, pd.DataFrame, List[Dict[str, Any]]],
|
233
248
|
title: Optional[str] = None,
|
234
249
|
):
|
235
|
-
"""Add a new table to the result
|
250
|
+
"""Add a new table to the result.
|
236
251
|
|
237
252
|
Args:
|
238
|
-
table (Union[ResultTable, pd.DataFrame, List[Dict[str, Any]]]): The table to add
|
253
|
+
table (Union[ResultTable, pd.DataFrame, List[Dict[str, Any]]]): The table to add.
|
239
254
|
title (Optional[str]): The title of the table (can optionally be provided for
|
240
|
-
pd.DataFrame and List[Dict[str, Any]] tables)
|
255
|
+
pd.DataFrame and List[Dict[str, Any]] tables).
|
241
256
|
"""
|
242
257
|
if self.tables is None:
|
243
258
|
self.tables = []
|
@@ -248,10 +263,10 @@ class TestResult(Result):
|
|
248
263
|
self.tables.append(table)
|
249
264
|
|
250
265
|
def remove_table(self, index: int):
|
251
|
-
"""Remove a table from the result by index
|
266
|
+
"""Remove a table from the result by index.
|
252
267
|
|
253
268
|
Args:
|
254
|
-
index (int): The index of the table to remove (default is 0)
|
269
|
+
index (int): The index of the table to remove (default is 0).
|
255
270
|
"""
|
256
271
|
if self.tables is None:
|
257
272
|
return
|
@@ -267,14 +282,19 @@ class TestResult(Result):
|
|
267
282
|
bytes,
|
268
283
|
Figure,
|
269
284
|
],
|
270
|
-
):
|
271
|
-
"""Add a new figure to the result
|
285
|
+
) -> None:
|
286
|
+
"""Add a new figure to the result.
|
272
287
|
|
273
288
|
Args:
|
274
|
-
figure
|
275
|
-
|
276
|
-
|
277
|
-
|
289
|
+
figure: The figure to add. Can be one of:
|
290
|
+
- matplotlib.figure.Figure: A matplotlib figure
|
291
|
+
- plotly.graph_objs.Figure: A plotly figure
|
292
|
+
- plotly.graph_objs.FigureWidget: A plotly figure widget
|
293
|
+
- bytes: A PNG image as raw bytes
|
294
|
+
- validmind.vm_models.figure.Figure: A ValidMind figure object.
|
295
|
+
|
296
|
+
Returns:
|
297
|
+
None.
|
278
298
|
"""
|
279
299
|
if self.figures is None:
|
280
300
|
self.figures = []
|
@@ -293,10 +313,10 @@ class TestResult(Result):
|
|
293
313
|
self.figures.append(figure)
|
294
314
|
|
295
315
|
def remove_figure(self, index: int = 0):
|
296
|
-
"""Remove a figure from the result by index
|
316
|
+
"""Remove a figure from the result by index.
|
297
317
|
|
298
318
|
Args:
|
299
|
-
index (int): The index of the figure to remove (default is 0)
|
319
|
+
index (int): The index of the figure to remove (default is 0).
|
300
320
|
"""
|
301
321
|
if self.figures is None:
|
302
322
|
return
|
@@ -332,7 +352,7 @@ class TestResult(Result):
|
|
332
352
|
|
333
353
|
@classmethod
|
334
354
|
def _get_client_config(cls):
|
335
|
-
"""Get the client config, loading it if not cached"""
|
355
|
+
"""Get the client config, loading it if not cached."""
|
336
356
|
if cls._client_config_cache is None:
|
337
357
|
api_client.reload()
|
338
358
|
cls._client_config_cache = api_client.client_config
|
@@ -350,7 +370,7 @@ class TestResult(Result):
|
|
350
370
|
return cls._client_config_cache
|
351
371
|
|
352
372
|
def check_result_id_exist(self):
|
353
|
-
"""Check if the result_id exists in any test block across all sections"""
|
373
|
+
"""Check if the result_id exists in any test block across all sections."""
|
354
374
|
client_config = self._get_client_config()
|
355
375
|
|
356
376
|
# Iterate through all sections
|
@@ -371,7 +391,7 @@ class TestResult(Result):
|
|
371
391
|
def _validate_section_id_for_block(
|
372
392
|
self, section_id: str, position: Union[int, None] = None
|
373
393
|
):
|
374
|
-
"""Validate the section_id exits on the template before logging"""
|
394
|
+
"""Validate the section_id exits on the template before logging."""
|
375
395
|
client_config = self._get_client_config()
|
376
396
|
found = False
|
377
397
|
|
@@ -410,7 +430,7 @@ class TestResult(Result):
|
|
410
430
|
)
|
411
431
|
|
412
432
|
def serialize(self):
|
413
|
-
"""Serialize the result for the API"""
|
433
|
+
"""Serialize the result for the API."""
|
414
434
|
return {
|
415
435
|
"test_name": self.result_id,
|
416
436
|
"title": self.title,
|
@@ -423,10 +443,16 @@ class TestResult(Result):
|
|
423
443
|
}
|
424
444
|
|
425
445
|
async def log_async(
|
426
|
-
self,
|
446
|
+
self,
|
447
|
+
section_id: str = None,
|
448
|
+
position: int = None,
|
449
|
+
config: Dict[str, bool] = None,
|
427
450
|
):
|
428
451
|
tasks = [] # collect tasks to run in parallel (async)
|
429
452
|
|
453
|
+
# Default empty dict if None
|
454
|
+
config = config or {}
|
455
|
+
|
430
456
|
if self.metric is not None:
|
431
457
|
# metrics are logged as separate entities
|
432
458
|
tasks.append(
|
@@ -438,12 +464,13 @@ class TestResult(Result):
|
|
438
464
|
)
|
439
465
|
)
|
440
466
|
|
441
|
-
if self.tables or self.figures:
|
467
|
+
if self.tables or self.figures or self.description:
|
442
468
|
tasks.append(
|
443
469
|
api_client.alog_test_result(
|
444
470
|
result=self.serialize(),
|
445
471
|
section_id=section_id,
|
446
472
|
position=position,
|
473
|
+
config=config,
|
447
474
|
)
|
448
475
|
)
|
449
476
|
|
@@ -467,17 +494,32 @@ class TestResult(Result):
|
|
467
494
|
|
468
495
|
return await asyncio.gather(*tasks)
|
469
496
|
|
470
|
-
def log(
|
471
|
-
|
497
|
+
def log(
|
498
|
+
self,
|
499
|
+
section_id: str = None,
|
500
|
+
position: int = None,
|
501
|
+
unsafe: bool = False,
|
502
|
+
config: Dict[str, bool] = None,
|
503
|
+
):
|
504
|
+
"""Log the result to ValidMind.
|
472
505
|
|
473
506
|
Args:
|
474
507
|
section_id (str): The section ID within the model document to insert the
|
475
|
-
test result
|
508
|
+
test result.
|
476
509
|
position (int): The position (index) within the section to insert the test
|
477
|
-
result
|
510
|
+
result.
|
478
511
|
unsafe (bool): If True, log the result even if it contains sensitive data
|
479
|
-
i.e. raw data from input datasets
|
512
|
+
i.e. raw data from input datasets.
|
513
|
+
config (Dict[str, bool]): Configuration options for displaying the test result.
|
514
|
+
Available config options:
|
515
|
+
- hideTitle: Hide the title in the document view
|
516
|
+
- hideText: Hide the description text in the document view
|
517
|
+
- hideParams: Hide the parameters in the document view
|
518
|
+
- hideTables: Hide tables in the document view
|
519
|
+
- hideFigures: Hide figures in the document view
|
480
520
|
"""
|
521
|
+
if config:
|
522
|
+
self.validate_log_config(config)
|
481
523
|
|
482
524
|
self.check_result_id_exist()
|
483
525
|
|
@@ -488,4 +530,41 @@ class TestResult(Result):
|
|
488
530
|
if section_id:
|
489
531
|
self._validate_section_id_for_block(section_id, position)
|
490
532
|
|
491
|
-
run_async(
|
533
|
+
run_async(
|
534
|
+
self.log_async,
|
535
|
+
section_id=section_id,
|
536
|
+
position=position,
|
537
|
+
config=config,
|
538
|
+
)
|
539
|
+
|
540
|
+
def validate_log_config(self, config: Dict[str, bool]):
|
541
|
+
"""Validate the configuration options for logging a test result
|
542
|
+
|
543
|
+
Args:
|
544
|
+
config (Dict[str, bool]): Configuration options to validate
|
545
|
+
|
546
|
+
Raises:
|
547
|
+
InvalidParameterError: If config contains invalid keys or non-boolean values
|
548
|
+
"""
|
549
|
+
valid_keys = {
|
550
|
+
"hideTitle",
|
551
|
+
"hideText",
|
552
|
+
"hideParams",
|
553
|
+
"hideTables",
|
554
|
+
"hideFigures",
|
555
|
+
}
|
556
|
+
invalid_keys = set(config.keys()) - valid_keys
|
557
|
+
if invalid_keys:
|
558
|
+
raise InvalidParameterError(
|
559
|
+
f"Invalid config keys: {', '.join(invalid_keys)}. "
|
560
|
+
f"Valid keys are: {', '.join(valid_keys)}"
|
561
|
+
)
|
562
|
+
|
563
|
+
# Ensure all values are boolean
|
564
|
+
non_bool_keys = [
|
565
|
+
key for key, value in config.items() if not isinstance(value, bool)
|
566
|
+
]
|
567
|
+
if non_bool_keys:
|
568
|
+
raise InvalidParameterError(
|
569
|
+
f"Values for config keys must be boolean. Non-boolean values found for keys: {', '.join(non_bool_keys)}"
|
570
|
+
)
|
@@ -28,7 +28,7 @@ _result_template = None
|
|
28
28
|
|
29
29
|
|
30
30
|
def get_result_template():
|
31
|
-
"""Get the
|
31
|
+
"""Get the Jinja2 HTML template for rendering test results."""
|
32
32
|
global _result_template
|
33
33
|
|
34
34
|
if _result_template is None:
|
@@ -39,7 +39,7 @@ def get_result_template():
|
|
39
39
|
|
40
40
|
|
41
41
|
async def update_metadata(content_id: str, text: str, _json: Union[Dict, List] = None):
|
42
|
-
"""Create or
|
42
|
+
"""Create or update a metadata object."""
|
43
43
|
parts = content_id.split("::")
|
44
44
|
content_id = parts[0]
|
45
45
|
revision_name = parts[1] if len(parts) > 1 else None
|
@@ -53,7 +53,7 @@ async def update_metadata(content_id: str, text: str, _json: Union[Dict, List] =
|
|
53
53
|
|
54
54
|
|
55
55
|
def check_for_sensitive_data(data: pd.DataFrame, inputs: List[VMInput]):
|
56
|
-
"""Check if
|
56
|
+
"""Check if the data contains sensitive information from input datasets."""
|
57
57
|
dataset_columns = {
|
58
58
|
col: len(input_obj.df)
|
59
59
|
for input_obj in inputs
|
@@ -77,7 +77,7 @@ def check_for_sensitive_data(data: pd.DataFrame, inputs: List[VMInput]):
|
|
77
77
|
|
78
78
|
|
79
79
|
def tables_to_widgets(tables: List["ResultTable"]):
|
80
|
-
"""Convert
|
80
|
+
"""Convert a list of tables to ipywidgets."""
|
81
81
|
widgets = [
|
82
82
|
HTML("<h3>Tables</h3>"),
|
83
83
|
]
|
@@ -128,7 +128,7 @@ def tables_to_widgets(tables: List["ResultTable"]):
|
|
128
128
|
|
129
129
|
|
130
130
|
def figures_to_widgets(figures: List[Figure]) -> list:
|
131
|
-
"""
|
131
|
+
"""Convert a list of figures to ipywidgets."""
|
132
132
|
num_columns = 2 if len(figures) > 1 else 1
|
133
133
|
|
134
134
|
plot_widgets = GridBox(
|
@@ -17,7 +17,7 @@ logger = get_logger(__name__)
|
|
17
17
|
|
18
18
|
class TestSuiteRunner:
|
19
19
|
"""
|
20
|
-
Runs a test suite
|
20
|
+
Runs a test suite.
|
21
21
|
"""
|
22
22
|
|
23
23
|
suite: TestSuite = None
|
@@ -36,7 +36,7 @@ class TestSuiteRunner:
|
|
36
36
|
self._load_config(inputs)
|
37
37
|
|
38
38
|
def _load_config(self, inputs: dict = None):
|
39
|
-
"""Splits the config into a global config and test configs"""
|
39
|
+
"""Splits the config into a global config and test configs."""
|
40
40
|
self._test_configs = {
|
41
41
|
test.test_id: {"inputs": inputs or {}} for test in self.suite.get_tests()
|
42
42
|
}
|
@@ -59,7 +59,7 @@ class TestSuiteRunner:
|
|
59
59
|
|
60
60
|
def _start_progress_bar(self, send: bool = True):
|
61
61
|
"""
|
62
|
-
Initializes the progress bar elements
|
62
|
+
Initializes the progress bar elements.
|
63
63
|
"""
|
64
64
|
# TODO: make this work for when user runs only a section of the test suite
|
65
65
|
# if we are sending then there is a task for each test and logging its result
|
@@ -76,7 +76,7 @@ class TestSuiteRunner:
|
|
76
76
|
self.pbar.close()
|
77
77
|
|
78
78
|
async def log_results(self):
|
79
|
-
"""Logs the results of the test suite to ValidMind
|
79
|
+
"""Logs the results of the test suite to ValidMind.
|
80
80
|
|
81
81
|
This method will be called after the test suite has been run and all results have been
|
82
82
|
collected. This method will log the results to ValidMind.
|
@@ -127,7 +127,7 @@ class TestSuiteRunner:
|
|
127
127
|
summary.display()
|
128
128
|
|
129
129
|
def run(self, send: bool = True, fail_fast: bool = False):
|
130
|
-
"""Runs the test suite, renders the summary and sends the results to ValidMind
|
130
|
+
"""Runs the test suite, renders the summary and sends the results to ValidMind.
|
131
131
|
|
132
132
|
Args:
|
133
133
|
send (bool, optional): Whether to send the results to ValidMind.
|