diff --git a/src/evidently/legacy/calculations/stattests/chisquare_stattest.py b/src/evidently/legacy/calculations/stattests/chisquare_stattest.py index be2117251c..a8d5f6abb1 100644 --- a/src/evidently/legacy/calculations/stattests/chisquare_stattest.py +++ b/src/evidently/legacy/calculations/stattests/chisquare_stattest.py @@ -41,8 +41,9 @@ def _chi_stat_test( ref_feature_dict = {**dict.fromkeys(keys, 0), **dict(reference_data.value_counts())} current_feature_dict = {**dict.fromkeys(keys, 0), **dict(current_data.value_counts())} k_norm = current_data.shape[0] / reference_data.shape[0] - f_exp = [ref_feature_dict[key] * k_norm for key in keys] f_obs = [current_feature_dict[key] for key in keys] + f_exp = [max(ref_feature_dict[key] * k_norm, 1e-6) for key in keys] + f_exp = [f / sum(f_exp) * sum(f_obs) for f in f_exp] p_value = chisquare(f_obs, f_exp)[1] return p_value, p_value < threshold diff --git a/src/evidently/legacy/tests/utils.py b/src/evidently/legacy/tests/utils.py index 697826f566..1b5093d12d 100644 --- a/src/evidently/legacy/tests/utils.py +++ b/src/evidently/legacy/tests/utils.py @@ -180,7 +180,10 @@ def dict(self, *args, **kwargs): # some monkeing for np asserts to work with ApproxValue -np.core.numeric.ScalarType = np.core.numeric.ScalarType + (ApproxValue, ApproxValueNoDict) # type: ignore[attr-defined] +if hasattr(np, "_core"): + np._core.numeric.ScalarType = np._core.numeric.ScalarType + (ApproxValue, ApproxValueNoDict) # type: ignore[attr-defined] +else: + np.core.numeric.ScalarType = np.core.numeric.ScalarType + (ApproxValue, ApproxValueNoDict) # type: ignore[attr-defined] def approx_result(value, relative=None, absolute=None): diff --git a/src/evidently/ui/workspace.py b/src/evidently/ui/workspace.py index ca7b1dadae..4f727b4a83 100644 --- a/src/evidently/ui/workspace.py +++ b/src/evidently/ui/workspace.py @@ -1198,7 +1198,10 @@ def delete_run(self, project_id: STR_UUID, snapshot_id: STR_UUID): Raises: * NotImplementedError (snapshot API not yet implemented) """ - raise NotImplementedError # todo: snapshot api + raise NotImplementedError( + "The Snapshot API is not yet implemented for RemoteWorkspace. " + "Please use a local Workspace instead." + ) # todo: snapshot api def get_run(self, project_id: STR_UUID, snapshot_id: STR_UUID) -> Optional[SnapshotModel]: """Get a snapshot (run) by ID. @@ -1213,7 +1216,10 @@ def get_run(self, project_id: STR_UUID, snapshot_id: STR_UUID) -> Optional[Snaps Raises: * NotImplementedError (snapshot API not yet implemented) """ - raise NotImplementedError # todo: snapshot api + raise NotImplementedError( + "The Snapshot API is not yet implemented for RemoteWorkspace. " + "Please use a local Workspace instead." + ) # todo: snapshot api def list_runs(self, project_id: STR_UUID) -> Sequence[SnapshotID]: """List all snapshots (runs) for a project. @@ -1227,7 +1233,10 @@ def list_runs(self, project_id: STR_UUID) -> Sequence[SnapshotID]: Raises: * NotImplementedError (snapshot API not yet implemented) """ - raise NotImplementedError # todo: snapshot api + raise NotImplementedError( + "The Snapshot API is not yet implemented for RemoteWorkspace. " + "Please use a local Workspace instead." + ) # todo: snapshot api def search_project(self, project_name: str, org_id: Optional[OrgID] = None) -> Sequence[Project]: """Search for projects by name. diff --git a/tests/conftest.py b/tests/conftest.py index cf527aeea9..bf54349bd5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,7 +9,10 @@ from evidently.pydantic_utils import PolymorphicModel # for np.testing.assert_equal to work with ApproxValue -np.core.numeric.ScalarType = np.core.numeric.ScalarType + (ApproxValue,) # type: ignore[attr-defined] +if hasattr(np, "_core"): + np._core.numeric.ScalarType = np._core.numeric.ScalarType + (ApproxValue,) # type: ignore[attr-defined] +else: + np.core.numeric.ScalarType = np.core.numeric.ScalarType + (ApproxValue,) # type: ignore[attr-defined] def smart_assert_equal(actual, expected, path=""): diff --git a/tests/ui/test_workspace.py b/tests/ui/test_workspace.py new file mode 100644 index 0000000000..ade972c972 --- /dev/null +++ b/tests/ui/test_workspace.py @@ -0,0 +1,32 @@ +import re +import uuid + +import pytest + +from evidently.ui.workspace import RemoteWorkspace + +# Shared expected message — kept as a constant so the test fails loudly +# if anyone silently removes or waters-down the user-facing guidance. +_EXPECTED_MSG = "The Snapshot API is not yet implemented for RemoteWorkspace. Please use a local Workspace instead." + + +@pytest.fixture() +def remote_workspace(): + """Return a RemoteWorkspace that skips the server-health check.""" + return RemoteWorkspace(base_url="http://localhost:0", verify=False) + + +class RemoteWorkspaceSnapshotAPITest: + """Verify that unimplemented snapshot methods raise an actionable error.""" + + def test_list_runs_raises_not_implemented(self, remote_workspace): + with pytest.raises(NotImplementedError, match=re.escape(_EXPECTED_MSG)): + remote_workspace.list_runs(str(uuid.uuid4())) + + def test_get_run_raises_not_implemented(self, remote_workspace): + with pytest.raises(NotImplementedError, match=re.escape(_EXPECTED_MSG)): + remote_workspace.get_run(str(uuid.uuid4()), str(uuid.uuid4())) + + def test_delete_run_raises_not_implemented(self, remote_workspace): + with pytest.raises(NotImplementedError, match=re.escape(_EXPECTED_MSG)): + remote_workspace.delete_run(str(uuid.uuid4()), str(uuid.uuid4()))