diff --git a/dvc/api/__init__.py b/dvc/api/__init__.py index d4007eb2d..693a1fb2b 100644 --- a/dvc/api/__init__.py +++ b/dvc/api/__init__.py @@ -2,10 +2,11 @@ from dvc.fs.dvc import _DVCFileSystem as DVCFileSystem from .data import open # pylint: disable=redefined-builtin from .data import get_url, read -from .experiments import make_checkpoint +from .experiments import exp_save, make_checkpoint from .show import metrics_show, params_show __all__ = [ + "exp_save", "get_url", "make_checkpoint", "open", diff --git a/dvc/api/experiments.py b/dvc/api/experiments.py index f6448eedb..cc6082d6e 100644 --- a/dvc/api/experiments.py +++ b/dvc/api/experiments.py @@ -1,6 +1,7 @@ import builtins import os from time import sleep +from typing import List, Optional from dvc.env import DVC_CHECKPOINT, DVC_ROOT from dvc.repo import Repo @@ -31,3 +32,40 @@ def make_checkpoint(): os.fsync(fobj.fileno()) while os.path.exists(signal_file): sleep(0.1) + + +def exp_save( + name: Optional[str] = None, + force: bool = False, + include_untracked: Optional[List[str]] = None, +): + """ + Create a new DVC experiment using `exp save`. + + See https://dvc.org/doc/command-reference/exp/save. + + Args: + name (str, optional): specify a name for this experiment. + If `None`, a default one will be generated, such as `urban-sign`. + Defaults to `None`. + force (bool): overwrite the experiment if an experiment with the same + name already exists. + Defaults to `False`. + include_untracked (List[str], optional): specify untracked file(s) to + be included in the saved experiment. + Defaults to `None`. + + Returns: + str: The `Git revision`_ of the created experiment. + + Raises: + ExperimentExistsError: If an experiment with `name` already exists and + `force=False`. + + .. _Git revision: + https://git-scm.com/docs/revisions + """ + with Repo() as repo: + return repo.experiments.save( + name=name, force=force, include_untracked=include_untracked + ) diff --git a/tests/func/api/test_experiments.py b/tests/func/api/test_experiments.py new file mode 100644 index 000000000..91279b83e --- /dev/null +++ b/tests/func/api/test_experiments.py @@ -0,0 +1,18 @@ +import pytest + +from dvc import api +from dvc.repo.experiments.exceptions import ExperimentExistsError + + +def test_exp_save(tmp_dir, dvc, scm, mocker): + tmp_dir.scm_gen({"foo": "foo"}, commit="initial") + + api.exp_save() + + api.exp_save("foo") + with pytest.raises( + ExperimentExistsError, + match="Experiment conflicts with existing experiment 'foo'.", + ): + api.exp_save("foo") + api.exp_save("foo", force=True)