Compare commits

...
Author SHA1 Message Date
mergify[bot]anddaavoo 45c9b2c9f6 get_random_exp_name: Use custom random generator. (backport #9490) (#9491)
get_random_exp_name: Use custom random generator.

See https://github.com/iterative/dvclive/pull/576

Co-authored-by: Ivan Shcheklein <[email protected]>
(cherry picked from commit eb45cda5d0)

Co-authored-by: daavoo <[email protected]>
2023-05-22 16:04:14 +05:45
mergify[bot]andskshetry a9ae450a64 stage add: introduce --run option (backport #9480) (#9487)
stage add: introduce --run option (#9480)

* stage add: introduce --run option

Part of #5846.

* fix ui message

(cherry picked from commit 1989c0fe98)

Co-authored-by: skshetry <[email protected]>
2023-05-22 12:48:15 +05:45
daavoo 60e161004d exp: Fix --rev args for ls pull and remove.
Closes #9471

(cherry picked from commit 3ddd4b87fc)
2023-05-21 16:32:31 +02:00
12 changed files with 137 additions and 33 deletions
+30 -1
View File
@@ -1,5 +1,6 @@
import argparse import argparse
import logging import logging
from contextlib import contextmanager
from itertools import chain, filterfalse from itertools import chain, filterfalse
from typing import TYPE_CHECKING, Dict, Iterable, List from typing import TYPE_CHECKING, Dict, Iterable, List
@@ -119,8 +120,22 @@ def parse_cmd(commands: List[str]) -> str:
return " ".join(map(quote_argument, commands)) return " ".join(map(quote_argument, commands))
@contextmanager
def _disable_logging(highest_level=logging.CRITICAL):
previous_level = logging.root.manager.disable
logging.disable(highest_level)
try:
yield
finally:
logging.disable(previous_level)
class CmdStageAdd(CmdBase): class CmdStageAdd(CmdBase):
def run(self): def run(self):
from dvc.repo import lock_repo
kwargs = vars(self.args) kwargs = vars(self.args)
kwargs.update( kwargs.update(
{ {
@@ -128,7 +143,15 @@ class CmdStageAdd(CmdBase):
"params": parse_params(self.args.params), "params": parse_params(self.args.params),
} }
) )
self.repo.stage.add(**kwargs)
with self.repo.scm_context, lock_repo(self.repo):
with _disable_logging(logging.INFO):
stage = self.repo.stage.add(**kwargs)
logger.info("Added stage %r in %r", stage.addressing, stage.relpath)
if self.args.run:
stage.run()
stage.dump(update_pipeline=False)
return 0 return 0
@@ -257,6 +280,12 @@ def _add_common_args(parser):
"This doesn't affect any DVC operations." "This doesn't affect any DVC operations."
), ),
) )
parser.add_argument(
"--run",
action="store_true",
default=False,
help="Execute the stage after generating it.",
)
parser.add_argument( parser.add_argument(
"command", "command",
nargs=argparse.REMAINDER, nargs=argparse.REMAINDER,
+6 -3
View File
@@ -1,6 +1,6 @@
import logging import logging
from collections import defaultdict from collections import defaultdict
from typing import Optional from typing import List, Optional, Union
from dvc.repo import locked from dvc.repo import locked
from dvc.repo.scm_context import scm_context from dvc.repo.scm_context import scm_context
@@ -15,14 +15,17 @@ logger = logging.getLogger(__name__)
@scm_context @scm_context
def ls( def ls(
repo, repo,
rev: Optional[str] = None, rev: Optional[Union[List[str], str]] = None,
all_commits: bool = False, all_commits: bool = False,
num: int = 1, num: int = 1,
git_remote: Optional[str] = None, git_remote: Optional[str] = None,
): ):
rev_set = None rev_set = None
if not all_commits: if not all_commits:
revs = iter_revs(repo.scm, [rev or "HEAD"], num) rev = rev or "HEAD"
if isinstance(rev, str):
rev = [rev]
revs = iter_revs(repo.scm, rev, num)
rev_set = set(revs.keys()) rev_set = set(revs.keys())
ref_info_dict = exp_refs_by_baseline(repo.scm, rev_set, git_remote) ref_info_dict = exp_refs_by_baseline(repo.scm, rev_set, git_remote)
+4 -2
View File
@@ -23,7 +23,7 @@ def pull( # noqa: C901
git_remote: str, git_remote: str,
exp_names: Union[Iterable[str], str], exp_names: Union[Iterable[str], str],
all_commits=False, all_commits=False,
rev: Optional[str] = None, rev: Optional[Union[List[str], str]] = None,
num=1, num=1,
force: bool = False, force: bool = False,
pull_cache: bool = False, pull_cache: bool = False,
@@ -49,7 +49,9 @@ def pull( # noqa: C901
raise UnresolvedExpNamesError(unresolved_exp_names) raise UnresolvedExpNamesError(unresolved_exp_names)
if rev: if rev:
rev_dict = iter_revs(repo.scm, [rev], num) if isinstance(rev, str):
rev = [rev]
rev_dict = iter_revs(repo.scm, rev, num)
rev_set = set(rev_dict.keys()) rev_set = set(rev_dict.keys())
ref_info_dict = exp_refs_by_baseline(repo.scm, rev_set, git_remote) ref_info_dict = exp_refs_by_baseline(repo.scm, rev_set, git_remote)
for _, ref_info_list in ref_info_dict.items(): for _, ref_info_list in ref_info_dict.items():
+5 -3
View File
@@ -24,7 +24,7 @@ logger = logging.getLogger(__name__)
def remove( # noqa: C901, PLR0912 def remove( # noqa: C901, PLR0912
repo: "Repo", repo: "Repo",
exp_names: Union[None, str, List[str]] = None, exp_names: Union[None, str, List[str]] = None,
rev: Optional[str] = None, rev: Optional[Union[List[str], str]] = None,
all_commits: bool = False, all_commits: bool = False,
num: int = 1, num: int = 1,
queue: bool = False, queue: bool = False,
@@ -60,6 +60,8 @@ def remove( # noqa: C901, PLR0912
if remained: if remained:
raise UnresolvedExpNamesError(remained, git_remote=git_remote) raise UnresolvedExpNamesError(remained, git_remote=git_remote)
elif rev: elif rev:
if isinstance(rev, str):
rev = [rev]
exp_ref_dict = _resolve_exp_by_baseline(repo, rev, num, git_remote) exp_ref_dict = _resolve_exp_by_baseline(repo, rev, num, git_remote)
removed.extend(exp_ref_dict.keys()) removed.extend(exp_ref_dict.keys())
exp_ref_list.extend(exp_ref_dict.values()) exp_ref_list.extend(exp_ref_dict.values())
@@ -85,14 +87,14 @@ def remove( # noqa: C901, PLR0912
def _resolve_exp_by_baseline( def _resolve_exp_by_baseline(
repo: "Repo", repo: "Repo",
rev: str, rev: List[str],
num: int, num: int,
git_remote: Optional[str] = None, git_remote: Optional[str] = None,
) -> Dict[str, "ExpRefInfo"]: ) -> Dict[str, "ExpRefInfo"]:
assert isinstance(repo.scm, Git) assert isinstance(repo.scm, Git)
commit_ref_dict: Dict[str, "ExpRefInfo"] = {} commit_ref_dict: Dict[str, "ExpRefInfo"] = {}
rev_dict = iter_revs(repo.scm, [rev], num) rev_dict = iter_revs(repo.scm, rev, num)
rev_set = set(rev_dict.keys()) rev_set = set(rev_dict.keys())
ref_info_dict = exp_refs_by_baseline(repo.scm, rev_set, git_remote) ref_info_dict = exp_refs_by_baseline(repo.scm, rev_set, git_remote)
for _, ref_info_list in ref_info_dict.items(): for _, ref_info_list in ref_info_dict.items():
File diff suppressed because one or more lines are too long
+9 -10
View File
@@ -122,16 +122,15 @@ class StageLoad:
force=force, force=force,
**stage_data, **stage_data,
) )
with self.repo.scm_context: stage.dump(update_lock=update_lock)
stage.dump(update_lock=update_lock) try:
try: stage.ignore_outs()
stage.ignore_outs() except FileNotFoundError as exc:
except FileNotFoundError as exc: ui.warn(
ui.warn( f"Could not create .gitignore entry in {exc.filename}."
f"Could not create .gitignore entry in {exc.filename}." " DVC will attempt to create .gitignore entry again when"
" DVC will attempt to create .gitignore entry again when" " the stage is run."
" the stage is run." )
)
return stage return stage
+1 -1
View File
@@ -303,5 +303,5 @@ ignore-names = ["M", "SCM"]
max-args = 10 max-args = 10
[tool.bandit] [tool.bandit]
exclude_dirs = ["tests", "scripts"] exclude_dirs = ["tests", "scripts", "dvc/repo/experiments/utils.py"]
skips = ["B101"] skips = ["B101"]
@@ -369,6 +369,12 @@ def test_list(tmp_dir, scm, dvc, exp_stage):
baseline_a[:7]: {ref_info_a.name, ref_info_b.name} baseline_a[:7]: {ref_info_a.name, ref_info_b.name}
} }
exp_list = dvc.experiments.ls(rev=[baseline_a, scm.get_rev()])
assert {key: set(val) for key, val in exp_list.items()} == {
baseline_a[:7]: {ref_info_a.name, ref_info_b.name},
"master": {ref_info_c.name},
}
exp_list = dvc.experiments.ls(all_commits=True) exp_list = dvc.experiments.ls(all_commits=True)
assert {key: set(val) for key, val in exp_list.items()} == { assert {key: set(val) for key, val in exp_list.items()} == {
baseline_a[:7]: {ref_info_a.name, ref_info_b.name}, baseline_a[:7]: {ref_info_a.name, ref_info_b.name},
+24
View File
@@ -251,6 +251,30 @@ def test_pull_args(tmp_dir, scm, dvc, git_downstream, exp_stage, all_, rev, resu
assert git_downstream.tmp_dir.scm.get_ref(str(ref_info3)) == result3 assert git_downstream.tmp_dir.scm.get_ref(str(ref_info3)) == result3
def test_pull_multi_rev(tmp_dir, scm, dvc, git_downstream, exp_stage):
baseline = scm.get_rev()
results = dvc.experiments.run(exp_stage.addressing, params=["foo=1"])
exp1 = first(results)
ref_info1 = first(exp_refs_by_rev(scm, exp1))
results = dvc.experiments.run(exp_stage.addressing, params=["foo=2"])
exp2 = first(results)
ref_info2 = first(exp_refs_by_rev(scm, exp2))
scm.commit("new_baseline")
results = dvc.experiments.run(exp_stage.addressing, params=["foo=3"])
exp3 = first(results)
ref_info3 = first(exp_refs_by_rev(scm, exp3))
downstream_exp = git_downstream.tmp_dir.dvc.experiments
git_downstream.tmp_dir.scm.fetch_refspecs(str(tmp_dir), ["master:master"])
downstream_exp.pull(git_downstream.remote, [], rev=[baseline, scm.get_rev()])
assert git_downstream.tmp_dir.scm.get_ref(str(ref_info1)) == exp1
assert git_downstream.tmp_dir.scm.get_ref(str(ref_info2)) == exp2
assert git_downstream.tmp_dir.scm.get_ref(str(ref_info3)) == exp3
def test_pull_diverged(tmp_dir, scm, dvc, git_downstream, exp_stage): def test_pull_diverged(tmp_dir, scm, dvc, git_downstream, exp_stage):
git_downstream.tmp_dir.scm_gen("foo", "foo", commit="init") git_downstream.tmp_dir.scm_gen("foo", "foo", commit="init")
remote_rev = git_downstream.tmp_dir.scm.get_rev() remote_rev = git_downstream.tmp_dir.scm.get_rev()
+23
View File
@@ -156,3 +156,26 @@ def test_remove_experiments_by_rev(tmp_dir, scm, dvc, exp_stage):
assert "queue2" in queue_revs assert "queue2" in queue_revs
assert scm.get_ref(new_exp_ref) is not None assert scm.get_ref(new_exp_ref) is not None
assert "queue4" in queue_revs assert "queue4" in queue_revs
def test_remove_multi_rev(tmp_dir, scm, dvc, exp_stage):
baseline = scm.get_rev()
results = dvc.experiments.run(exp_stage.addressing, params=["foo=1"])
baseline_exp_ref = first(exp_refs_by_rev(scm, first(results)))
dvc.experiments.run(
exp_stage.addressing, params=["foo=2"], queue=True, name="queue2"
)
scm.commit("new_baseline")
results = dvc.experiments.run(exp_stage.addressing, params=["foo=3"])
new_exp_ref = first(exp_refs_by_rev(scm, first(results)))
assert set(dvc.experiments.remove(rev=[baseline, scm.get_rev()])) == {
baseline_exp_ref.name,
new_exp_ref.name,
}
assert scm.get_ref(str(baseline_exp_ref)) is None
assert scm.get_ref(str(new_exp_ref)) is None
+12 -11
View File
@@ -2,18 +2,19 @@ from funcy import first
def test_generate_random_exp_name(tmp_dir, dvc, scm, exp_stage, mocker): def test_generate_random_exp_name(tmp_dir, dvc, scm, exp_stage, mocker):
mocked_generator = mocker.MagicMock()
mocked_generator.choice.side_effect = [
0,
0,
0,
0,
1,
1,
0,
0,
]
mocker.patch( mocker.patch(
"dvc.repo.experiments.utils.random.choice", "dvc.repo.experiments.utils.random.Random", return_value=mocked_generator
side_effect=[
0,
0,
0,
0,
1,
1,
0,
0,
],
) )
ref = first(dvc.experiments.run(exp_stage.addressing, params=["foo=1"])) ref = first(dvc.experiments.run(exp_stage.addressing, params=["foo=1"]))
+12
View File
@@ -86,3 +86,15 @@ def test_stage_add(mocker, dvc, command, parsed_command):
cmd=parsed_command, cmd=parsed_command,
force=True, force=True,
) )
def test_stage_add_and_run(mocker, dvc):
cli_args = parse_args(["stage", "add", "--run", "-n", "foo", "-o", "foo", "cmd"])
cmd = cli_args.func(cli_args)
add_mock = mocker.patch.object(cmd.repo.stage, "add")
assert cmd.run() == 0
assert called_once_with_subset(add_mock, name="foo", outs=["foo"], cmd="cmd")
add_mock.return_value.run.assert_called_once()
add_mock.return_value.dump.assert_called_once_with(update_pipeline=False)