diff --git a/src/md_python/models/dataset_builders.py b/src/md_python/models/dataset_builders.py index fe623a2..ccae43c 100644 --- a/src/md_python/models/dataset_builders.py +++ b/src/md_python/models/dataset_builders.py @@ -33,6 +33,18 @@ def _dose_column_to_numbers(values: List[Any]) -> List[float]: class BaseDatasetBuilder(ABC): """Abstract base for dataset builders that produce Dataset objects. + .. deprecated:: + Every builder in this module is deprecated. Build a :class:`Dataset` + directly and pass it to ``client.datasets.create()``. The builders + duplicate each job's parameter schema in Python, so they drift whenever + a job changes server-side; ``client.jobs.list()`` is the live schema. + + This base class carries no ``@deprecated`` decorator on purpose. The + decorator fires at class-definition time for an ABC, so decorating it would + emit a warning for every subclass as soon as ``md_python`` is imported, even + for callers that never touch a builder. The concrete subclasses carry it + instead, where it fires on use. + Shared parameters across dataset builders. """ @@ -54,9 +66,19 @@ def run(self, client: "BaseMDClient") -> str: return client.datasets.create(self.to_dataset()) # type: ignore[attr-defined, no-any-return] +@deprecated( + "MinimalDataset is deprecated and may be removed in a future release. " + "Build a Dataset directly and pass it to client.datasets.create(): Dataset(input_dataset_ids=..., name=..., job_slug=..., job_run_params=...). The job's own parameter schema is available from client.jobs.list()." +) @pydantic_dataclass class MinimalDataset(BaseDatasetBuilder): - """Builder for a minimal dataset (name, inputs, job slug only).""" + """Builder for a minimal dataset (name, inputs, job slug only). + + .. deprecated:: + Use :class:`Dataset` directly. This builder only forwards its four + fields, so ``Dataset(input_dataset_ids=..., name=..., job_slug=..., + job_run_params=...)`` is the same call with one fewer layer. + """ job_slug: str job_run_params: Optional[Dict[str, Any]] = None @@ -178,7 +200,7 @@ def _batch_correction_technique_key(entity_type: str) -> str: @deprecated( "NormalisationImputationDataset is deprecated and may be removed in a " - "future release." + "future release. Build a Dataset directly and pass it to client.datasets.create(): Dataset(input_dataset_ids=..., name=..., job_slug=..., job_run_params=...). The job's own parameter schema is available from client.jobs.list()." ) @pydantic_dataclass class NormalisationImputationDataset(BaseDatasetBuilder): @@ -629,7 +651,10 @@ def validate(self) -> None: # noqa: C901 ) -@deprecated("DoseResponseDataset is deprecated and may be removed in a future release.") +@deprecated( + "DoseResponseDataset is deprecated and may be removed in a future release. " + "Build a Dataset directly and pass it to client.datasets.create(): Dataset(input_dataset_ids=..., name=..., job_slug=..., job_run_params=...). The job's own parameter schema is available from client.jobs.list()." +) @pydantic_dataclass class DoseResponseDataset(BaseDatasetBuilder): """Builder for a dose response analysis dataset. @@ -718,7 +743,8 @@ def validate(self) -> None: @deprecated( - "PairwiseComparisonDataset is deprecated and may be removed in a future " "release." + "PairwiseComparisonDataset is deprecated and may be removed in a future " + "release. Build a Dataset directly and pass it to client.datasets.create(): Dataset(input_dataset_ids=..., name=..., job_slug=..., job_run_params=...). The job's own parameter schema is available from client.jobs.list()." ) @pydantic_dataclass class PairwiseComparisonDataset(BaseDatasetBuilder): diff --git a/tests/models/test_dataset_builders.py b/tests/models/test_dataset_builders.py index 3d678bb..924a27b 100644 --- a/tests/models/test_dataset_builders.py +++ b/tests/models/test_dataset_builders.py @@ -1,5 +1,9 @@ +import importlib +import warnings from uuid import UUID +import pytest + from md_python.models import SampleMetadata from md_python.models.dataset_builders import ( DoseResponseDataset, @@ -586,3 +590,57 @@ def test_ni_extra_params_overrides_typed_field(): p = ni.to_dataset().job_run_params assert p["std_position"] == 9.9 assert p["custom_future_field"] == "x" + + +def test_every_concrete_builder_warns_on_use(): + """Each builder emits a DeprecationWarning naming what to use instead. + + The abstract base is deliberately excluded: ``@deprecated`` on an ABC fires + at class-definition time, so decorating it would warn on import of + ``md_python`` even for callers that never build a dataset. + """ + common = {"input_dataset_ids": [str(UUID(int=1))], "dataset_name": "n"} + builders = [ + (MinimalDataset, {**common, "job_slug": "ora"}), + ( + DoseResponseDataset, + {**common, "sample_names": ["1", "2"], "control_samples": ["1"]}, + ), + ( + PairwiseComparisonDataset, + { + **common, + "sample_metadata": SampleMetadata( + data=[["sample_name", "condition"], ["1", "a"], ["2", "b"]] + ), + "condition_column": "condition", + "condition_comparisons": [["a", "b"]], + }, + ), + ( + NormalisationImputationDataset, + { + **common, + "normalisation_method": "median", + "imputation_method": "skip", + "filtration_method": "skip", + }, + ), + ] + for builder, kwargs in builders: + with pytest.warns(DeprecationWarning) as record: + builder(**kwargs) + message = str(record[0].message) + assert builder.__name__ in message + assert ( + "client.datasets.create()" in message + ), f"{builder.__name__} does not say what to use instead" + + +def test_importing_md_python_does_not_warn(): + """Importing the package must stay silent; only using a builder warns.""" + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + importlib.reload(importlib.import_module("md_python.models.dataset_builders")) + deprecations = [w for w in caught if issubclass(w.category, DeprecationWarning)] + assert not deprecations, [str(w.message) for w in deprecations]