179 lines
6.9 KiB
Python
179 lines
6.9 KiB
Python
from typing import Any
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
|
|
from mlflow.data.dataset_source_registry import DatasetSourceRegistry
|
|
from mlflow.exceptions import MlflowException
|
|
|
|
from tests.resources.data.dataset_source import SampleDatasetSource
|
|
|
|
|
|
def test_register_entrypoints_and_resolve(tmp_path):
|
|
from mlflow_test_plugin.dummy_dataset_source import DummyDatasetSource
|
|
|
|
registry = DatasetSourceRegistry()
|
|
registry.register_entrypoints()
|
|
|
|
uri = "dummy:" + str(tmp_path)
|
|
resolved_source = registry.resolve(uri)
|
|
assert isinstance(resolved_source, DummyDatasetSource)
|
|
# Verify that the DummyDatasetSource is constructed with the correct URI
|
|
assert resolved_source.uri == uri
|
|
|
|
|
|
def test_register_dataset_source_and_resolve(tmp_path):
|
|
registry = DatasetSourceRegistry()
|
|
registry.register(SampleDatasetSource)
|
|
|
|
uri = "test:" + str(tmp_path)
|
|
resolved_source = registry.resolve(uri)
|
|
assert isinstance(resolved_source, SampleDatasetSource)
|
|
# Verify that the SampleDatasetSource is constructed with the correct URI
|
|
assert resolved_source.uri == uri
|
|
|
|
|
|
def test_register_dataset_source_and_load_from_json(tmp_path):
|
|
registry = DatasetSourceRegistry()
|
|
registry.register(SampleDatasetSource)
|
|
resolved_source = registry.resolve("test:" + str(tmp_path))
|
|
resolved_source_json = resolved_source.to_json()
|
|
source_from_json = registry.get_source_from_json(
|
|
source_json=resolved_source_json, source_type="test"
|
|
)
|
|
assert source_from_json.uri == resolved_source.uri
|
|
|
|
|
|
def test_load_from_json_throws_for_unrecognized_source_type(tmp_path):
|
|
registry = DatasetSourceRegistry()
|
|
registry.register(SampleDatasetSource)
|
|
|
|
with pytest.raises(MlflowException, match="unrecognized source type: foo"):
|
|
registry.get_source_from_json(source_json='{"bar": "123"}', source_type="foo")
|
|
|
|
class CandidateDatasetSource1(SampleDatasetSource):
|
|
@staticmethod
|
|
def _get_source_type() -> str:
|
|
return "candidate1"
|
|
|
|
@staticmethod
|
|
def _can_resolve(raw_source: Any) -> bool:
|
|
return raw_source.startswith("candidate1")
|
|
|
|
class CandidateDatasetSource2(CandidateDatasetSource1):
|
|
@staticmethod
|
|
def _get_source_type() -> str:
|
|
return "candidate2"
|
|
|
|
@staticmethod
|
|
def _can_resolve(raw_source: Any) -> bool:
|
|
return raw_source.startswith("candidate2")
|
|
|
|
registry = DatasetSourceRegistry()
|
|
registry.register(SampleDatasetSource)
|
|
registry.register(CandidateDatasetSource1)
|
|
registry.register(CandidateDatasetSource2)
|
|
|
|
registry.resolve("test:" + str(tmp_path))
|
|
registry.resolve("test:" + str(tmp_path), candidate_sources=[SampleDatasetSource])
|
|
with pytest.raises(MlflowException, match="Could not find a source information resolver"):
|
|
# SampleDatasetSource is the only source that can resolve raw sources with scheme "test",
|
|
# and SampleDatasetSource is not a subclass of CandidateDatasetSource1
|
|
registry.resolve("test:" + str(tmp_path), candidate_sources=[CandidateDatasetSource1])
|
|
|
|
registry.resolve("candidate1:" + str(tmp_path))
|
|
registry.resolve("candidate1:" + str(tmp_path), candidate_sources=[CandidateDatasetSource1])
|
|
# CandidateDatasetSource1 is a subclass of SampleDatasetSource and is therefore considered
|
|
# as a candidate for resolution
|
|
registry.resolve("candidate1:" + str(tmp_path), candidate_sources=[SampleDatasetSource])
|
|
with pytest.raises(MlflowException, match="Could not find a source information resolver"):
|
|
# CandidateDatasetSource2 is not a superclass of CandidateDatasetSource1 or
|
|
# SampleDatasetSource and cannot resolve raw sources with scheme "candidate1"
|
|
registry.resolve("candidate1:" + str(tmp_path), candidate_sources=[CandidateDatasetSource2])
|
|
|
|
|
|
def test_resolve_dataset_source_maintains_consistent_order_and_uses_last_registered_match(tmp_path):
|
|
from mlflow_test_plugin.dummy_dataset_source import DummyDatasetSource
|
|
|
|
class SampleDatasetSourceCopy1(SampleDatasetSource):
|
|
pass
|
|
|
|
class SampleDatasetSourceCopy2(SampleDatasetSource):
|
|
pass
|
|
|
|
registry1 = DatasetSourceRegistry()
|
|
registry1.register(SampleDatasetSource)
|
|
registry1.register(SampleDatasetSourceCopy1)
|
|
registry1.register(SampleDatasetSourceCopy2)
|
|
source1 = registry1.resolve("test:/" + str(tmp_path))
|
|
assert isinstance(source1, SampleDatasetSourceCopy2)
|
|
|
|
registry2 = DatasetSourceRegistry()
|
|
registry2.register(SampleDatasetSource)
|
|
registry2.register(SampleDatasetSourceCopy2)
|
|
registry2.register(SampleDatasetSourceCopy1)
|
|
source2 = registry2.resolve("test:/" + str(tmp_path))
|
|
assert isinstance(source2, SampleDatasetSourceCopy1)
|
|
|
|
# Verify that a different matching dataset source can still be resolved via `candidates`
|
|
source3 = registry2.resolve(
|
|
"test:/" + str(tmp_path), candidate_sources=[SampleDatasetSourceCopy2]
|
|
)
|
|
assert isinstance(source3, SampleDatasetSourceCopy2)
|
|
|
|
# Verify that last registered order applies to entrypoints too
|
|
class DummyDatasetSourceCopy(DummyDatasetSource):
|
|
pass
|
|
|
|
registry3 = DatasetSourceRegistry()
|
|
registry3.register(DummyDatasetSourceCopy)
|
|
source4 = registry3.resolve("dummy:/" + str(tmp_path))
|
|
assert isinstance(source4, DummyDatasetSourceCopy)
|
|
registry3.register_entrypoints()
|
|
source5 = registry3.resolve("dummy:/" + str(tmp_path))
|
|
assert isinstance(source5, DummyDatasetSource)
|
|
|
|
|
|
def test_resolve_dataset_source_warns_when_multiple_matching_sources_found(tmp_path):
|
|
class SampleDatasetSourceCopy1(SampleDatasetSource):
|
|
pass
|
|
|
|
class SampleDatasetSourceCopy2(SampleDatasetSource):
|
|
pass
|
|
|
|
registry1 = DatasetSourceRegistry()
|
|
registry1.register(SampleDatasetSource)
|
|
registry1.register(SampleDatasetSourceCopy1)
|
|
registry1.register(SampleDatasetSourceCopy2)
|
|
|
|
with mock.patch("mlflow.data.dataset_source_registry.warnings.warn") as mock_warn:
|
|
registry1.resolve("test:/" + str(tmp_path))
|
|
mock_warn.assert_called_once()
|
|
call_args, _ = mock_warn.call_args
|
|
multiple_match_msg = call_args[0]
|
|
assert (
|
|
"The specified dataset source can be interpreted in multiple ways" in multiple_match_msg
|
|
)
|
|
assert (
|
|
"SampleDatasetSource, SampleDatasetSourceCopy1, SampleDatasetSourceCopy2"
|
|
in multiple_match_msg
|
|
)
|
|
assert (
|
|
"MLflow will assume that this is a SampleDatasetSourceCopy2 source"
|
|
in multiple_match_msg
|
|
)
|
|
|
|
|
|
def test_dataset_sources_are_importable_from_sources_module(tmp_path):
|
|
from mlflow.data.sources import LocalArtifactDatasetSource
|
|
|
|
src = LocalArtifactDatasetSource(tmp_path)
|
|
assert src._get_source_type() == "local"
|
|
assert src.uri == tmp_path
|
|
|
|
from mlflow.data.sources import DeltaDatasetSource
|
|
|
|
src = DeltaDatasetSource(path=tmp_path)
|
|
assert src._get_source_type() == "delta_table"
|
|
assert src.path == tmp_path
|