Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions src/md_python/models/dataset_builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""

Expand All @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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):
Expand Down
58 changes: 58 additions & 0 deletions tests/models/test_dataset_builders.py
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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]
Loading