Compare commits

...
25 Commits
Author SHA1 Message Date
David de la Iglesia Castro dc3d9c9677 bump DVCLive lower bound 2022-12-14 20:45:57 +01:00
Daniele Trifirò fcf92b2755 remove a few experiments names 2022-12-14 18:14:36 +01:00
David de la Iglesia CastroandDave Berenbaum c806275167 Bump dvclive lower bound (#8683)
* Bump dvclive lower bound

* Update pyproject.toml

Co-authored-by: Dave Berenbaum <[email protected]>

Co-authored-by: Dave Berenbaum <[email protected]>
2022-12-14 14:13:20 +00:00
dependabot[bot] bd1add0b95 build(deps-dev): Bump dvc-task from 0.1.6 to 0.1.8
Bumps [dvc-task](https://github.com/iterative/dvc-task) from 0.1.6 to 0.1.8.
- [Release notes](https://github.com/iterative/dvc-task/releases)
- [Commits](https://github.com/iterative/dvc-task/compare/0.1.6...0.1.8)

---
updated-dependencies:
- dependency-name: dvc-task
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-12-14 11:23:26 +08:00
Ivan Shcheklein e36f0e9d85 reset all indecies of brancher switch 2022-12-13 23:43:23 +02:00
dependabot[bot] bc1b8f3494 build(deps-dev): Bump pylint from 2.15.7 to 2.15.8
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.15.7 to 2.15.8.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.15.7...v2.15.8)

---
updated-dependencies:
- dependency-name: pylint
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-12-13 14:53:57 +01:00
dependabot[bot] 9169d273e3 build(deps-dev): Bump filelock from 3.8.0 to 3.8.2
Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.8.0 to 3.8.2.
- [Release notes](https://github.com/tox-dev/py-filelock/releases)
- [Changelog](https://github.com/tox-dev/py-filelock/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/py-filelock/compare/3.8.0...3.8.2)

---
updated-dependencies:
- dependency-name: filelock
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-12-12 20:38:22 +01:00
David de la Iglesia Castro bb7512687c exp: Generate a human-readable name beforehand.
- Add `get_random_exp_name` to utils.
Generates a name (`f"{adjective}-{noun}"`) by randomly chosing pairs of adjetive, noun.
Recieves `scm` and `baseline_rev` and retries the random choice until a non-existing ref is found.

Closes https://github.com/iterative/dvc/issues/8650
2022-12-09 10:32:38 +01:00
Saugat Pachhai (सौगात) 334f297ad3 deps: remove 3.11 checks for hydra; has 3.11 support now 2022-12-09 10:32:12 +05:45
Peter Rowlands ace3ba0ef9 worktree push: disable rev flags 2022-12-09 09:21:19 +09:00
Peter Rowlands 959d149381 worktree fetch: support revs flags 2022-12-09 09:21:19 +09:00
Peter Rowlands 7a65d67e7f worktree: support recursive/with_deps targets 2022-12-09 09:21:19 +09:00
Peter Rowlands e9be6779ea worktree: disable run-cache push/fetch 2022-12-09 09:21:19 +09:00
Peter Rowlands 876d123f04 index: use lazy view loading 2022-12-08 20:56:05 +09:00
Peter Rowlands 2bfe403a28 deps: bump dvc-data to 0.28.4 2022-12-08 20:56:05 +09:00
Peter Rowlands 9bef123182 worktree: fix default worktree remote/odb exception 2022-12-08 11:01:42 +09:00
Peter Rowlands e98ca36f39 worktree push: only index known (tracked) files/dirs in the remote 2022-12-07 18:48:34 +09:00
Peter Rowlands b9bf73f043 push/fetch: support targets in cloud version push/fetch 2022-12-07 18:48:34 +09:00
dependabot[bot] 7628fe9d44 build(deps-dev): Bump pytest-xdist from 3.0.2 to 3.1.0
Bumps [pytest-xdist](https://github.com/pytest-dev/pytest-xdist) from 3.0.2 to 3.1.0.
- [Release notes](https://github.com/pytest-dev/pytest-xdist/releases)
- [Changelog](https://github.com/pytest-dev/pytest-xdist/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-xdist/compare/v3.0.2...v3.1.0)

---
updated-dependencies:
- dependency-name: pytest-xdist
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-12-06 20:16:21 +05:45
Peter Rowlands d415a05460 remote: disable gc and status -c for versioned remotes 2022-12-06 19:33:52 +09:00
Peter Rowlands f5699ce16b update: disable rev/to-remote/no-download for worktrees 2022-12-06 15:14:17 +09:00
Peter Rowlands baeedead3a update: add support for worktree update 2022-12-06 15:14:17 +09:00
Peter Rowlands 5630db55ad worktree: add support for updating outs 2022-12-06 15:14:17 +09:00
Peter Rowlands f1bd47ce0e index: make collect_targets public 2022-12-06 15:14:17 +09:00
dependabot[bot] 74a13a4d9a build(deps-dev): Bump dvc-render from 0.0.14 to 0.0.15
Bumps [dvc-render](https://github.com/iterative/dvc-render) from 0.0.14 to 0.0.15.
- [Release notes](https://github.com/iterative/dvc-render/releases)
- [Commits](https://github.com/iterative/dvc-render/compare/0.0.14...0.0.15)

---
updated-dependencies:
- dependency-name: dvc-render
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-12-02 08:30:37 +01:00
21 changed files with 473 additions and 236 deletions
+2 -1
View File
@@ -31,7 +31,8 @@ class CmdUpdate(CmdBase):
def add_parser(subparsers, parent_parser):
UPDATE_HELP = (
"Update data artifact imported (via dvc import or dvc import-url) "
"from an external DVC repository or URL."
"from an external DVC repository or URL, or update data from a "
"DVC worktree remote."
)
update_parser = subparsers.add_parser(
"update",
+5 -3
View File
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Iterable, Optional
from funcy import cached_property
from dvc.config import RemoteConfigError
from dvc.config import NoRemoteError, RemoteConfigError
from dvc_data.hashfile.db import get_index
if TYPE_CHECKING:
@@ -63,8 +63,6 @@ class DataCloud:
name: Optional[str] = None,
command: str = "<command>",
) -> "Remote":
from dvc.config import NoRemoteError
if not name:
name = self.repo.config["core"].get("remote")
@@ -97,6 +95,10 @@ class DataCloud:
command: str = "<command>",
) -> "HashFileDB":
remote = self.get_remote(name=name, command=command)
if remote.fs.version_aware or remote.worktree:
raise NoRemoteError(
f"'{command}' is unsupported for cloud versioned remotes"
)
return remote.odb
def _log_missing(self, status: "CompareStatusResult"):
+7 -2
View File
@@ -536,11 +536,16 @@ class Repo:
self.scm.close()
self.state.close()
def _reset_cached_indecies(self):
self.__dict__.pop("index", None)
self.__dict__.pop("dvcignore", None)
self.__dict__.pop("dvcfs", None)
self.__dict__.pop("datafs", None)
def _reset(self):
self.state.close()
self.scm._reset() # pylint: disable=protected-access
self.__dict__.pop("index", None)
self.__dict__.pop("dvcignore", None)
self._reset_cached_indecies()
def __enter__(self):
return self
+1 -1
View File
@@ -77,7 +77,7 @@ def brancher( # noqa: E302
from dvc.fs import GitFileSystem
for sha, names in found_revs.items():
self.__dict__.pop("index", None)
self._reset_cached_indecies() # pylint: disable=W0212
self.fs = GitFileSystem(scm=scm, rev=sha)
self.root_dir = self.fs.path.join("/", *repo_root_parts)
+9 -18
View File
@@ -32,7 +32,7 @@ from .refs import (
ExpRefInfo,
)
from .stash import ExpStashEntry
from .utils import exp_refs_by_rev, unlocked_repo
from .utils import check_ref_format, exp_refs_by_rev, unlocked_repo
logger = logging.getLogger(__name__)
@@ -236,17 +236,6 @@ class Experiments:
"\tdvc exp branch <exp> <branch>\n"
)
def _validate_new_ref(self, exp_ref: ExpRefInfo):
from .utils import check_ref_format
if not exp_ref.name:
return
check_ref_format(self.scm, exp_ref)
if self.scm.get_ref(str(exp_ref)):
raise ExperimentExistsError(exp_ref.name)
def new(
self,
queue: BaseStashQueue,
@@ -265,13 +254,15 @@ class Experiments:
name = kwargs.get("name", None)
baseline_sha = kwargs.get("baseline_rev") or self.repo.scm.get_rev()
exp_ref = ExpRefInfo(baseline_sha=baseline_sha, name=name)
try:
self._validate_new_ref(exp_ref)
except ExperimentExistsError as err:
if not (kwargs.get("force", False) or kwargs.get("reset", False)):
raise err
if name:
exp_ref = ExpRefInfo(baseline_sha=baseline_sha, name=name)
check_ref_format(self.scm, exp_ref)
force = kwargs.get("force", False)
reset = kwargs.get("reset", False)
if self.scm.get_ref(str(exp_ref)) and not (force or reset):
raise ExperimentExistsError(exp_ref.name)
return queue.put(*args, **kwargs)
def _resume_checkpoint(
+11 -4
View File
@@ -30,7 +30,13 @@ from ..executor.base import BaseExecutor, ExecutorResult
from ..executor.local import WorkspaceExecutor
from ..refs import ExpRefInfo
from ..stash import ExpStash, ExpStashEntry
from ..utils import EXEC_PID_DIR, EXEC_TMP_DIR, exp_refs_by_rev, get_exp_rwlock
from ..utils import (
EXEC_PID_DIR,
EXEC_TMP_DIR,
exp_refs_by_rev,
get_exp_rwlock,
get_random_exp_name,
)
from .utils import get_remote_executor_refs
if TYPE_CHECKING:
@@ -281,7 +287,7 @@ class BaseStashQueue(ABC):
output.
"""
def _stash_exp(
def _stash_exp( # noqa: C901
self,
*args,
params: Optional[Dict[str, List[str]]] = None,
@@ -399,8 +405,9 @@ class BaseStashQueue(ABC):
run_env = {
DVC_EXP_BASELINE_REV: baseline_rev,
}
if name:
run_env[DVC_EXP_NAME] = name
if not name:
name = get_random_exp_name(self.scm, baseline_rev)
run_env[DVC_EXP_NAME] = name
if resume_rev:
run_env[DVCLIVE_RESUME] = "1"
self._pack_args(*args, run_env=run_env, **kwargs)
File diff suppressed because one or more lines are too long
+60 -21
View File
@@ -1,6 +1,6 @@
import logging
from contextlib import suppress
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING, Optional, Sequence
from dvc.config import NoRemoteError
from dvc.exceptions import DownloadError
@@ -9,6 +9,7 @@ from dvc.fs import Schemes
from . import locked
if TYPE_CHECKING:
from dvc.cloud import Remote
from dvc.repo import Repo
from dvc.types import TargetType
from dvc_data.hashfile.transfer import TransferResult
@@ -45,18 +46,19 @@ def fetch(
remote is configured
"""
from dvc.repo.imports import save_imports
from dvc.repo.worktree import fetch_worktree
from dvc_data.hashfile.transfer import TransferResult
if isinstance(targets, str):
targets = [targets]
worktree_remote: Optional["Remote"] = None
with suppress(NoRemoteError):
_remote = self.cloud.get_remote(name=remote)
if _remote.worktree:
return fetch_worktree(self, _remote)
if _remote.worktree or _remote.fs.version_aware:
worktree_remote = _remote
failed_count = 0
transferred_count = 0
try:
if run_cache:
@@ -67,22 +69,35 @@ def fetch(
no_remote_msg: Optional[str] = None
result = TransferResult(set(), set())
try:
d, f = _fetch(
self,
targets,
all_branches=all_branches,
all_tags=all_tags,
all_commits=all_commits,
with_deps=with_deps,
force=True,
remote=remote,
jobs=jobs,
recursive=recursive,
revs=revs,
odb=odb,
)
result.transferred.update(d)
result.failed.update(f)
if worktree_remote is not None:
transferred_count += _fetch_worktree(
self,
worktree_remote,
revs=revs,
all_branches=all_branches,
all_tags=all_tags,
all_commits=all_commits,
targets=targets,
with_deps=with_deps,
recursive=recursive,
)
else:
d, f = _fetch(
self,
targets,
all_branches=all_branches,
all_tags=all_tags,
all_commits=all_commits,
with_deps=with_deps,
force=True,
remote=remote,
jobs=jobs,
recursive=recursive,
revs=revs,
odb=odb,
)
result.transferred.update(d)
result.failed.update(f)
except NoRemoteError as exc:
no_remote_msg = str(exc)
@@ -108,7 +123,8 @@ def fetch(
logger.error(no_remote_msg)
raise DownloadError(failed_count)
return len(result.transferred)
transferred_count += len(result.transferred)
return transferred_count
def _fetch(
@@ -155,3 +171,26 @@ def _fetch(
result.transferred.update(d)
result.failed.update(f)
return result
def _fetch_worktree(
repo: "Repo",
remote: "Remote",
revs: Optional[Sequence[str]] = None,
all_branches: bool = False,
all_tags: bool = False,
all_commits: bool = False,
targets: Optional["TargetType"] = None,
**kwargs,
) -> int:
from dvc.repo.worktree import fetch_worktree
downloaded = 0
for _ in repo.brancher(
revs=revs,
all_branches=all_branches,
all_tags=all_tags,
all_commits=all_commits,
):
downloaded += fetch_worktree(repo, remote, targets=targets, **kwargs)
return downloaded
+3 -4
View File
@@ -166,7 +166,7 @@ class Index:
)
)
def _collect_targets(
def collect_targets(
self, targets: Optional["TargetType"], **kwargs: Any
) -> List["StageInfo"]:
from itertools import chain
@@ -214,7 +214,7 @@ class Index:
"""
stage_infos = [
stage_info
for stage_info in self._collect_targets(targets, **kwargs)
for stage_info in self.collect_targets(targets, **kwargs)
if not stage_filter or stage_filter(stage_info.stage)
]
return IndexView(self, stage_infos, outs_filter=outs_filter)
@@ -310,7 +310,7 @@ class Index:
from collections import defaultdict
used: "ObjectContainer" = defaultdict(set)
pairs = self._collect_targets(
pairs = self.collect_targets(
targets, recursive=recursive, with_deps=with_deps
)
for stage, filter_info in pairs:
@@ -484,7 +484,6 @@ class IndexView:
data: Dict[str, Union["DataIndex", "DataIndexView"]] = {}
for workspace, data_index in self._index.data.items():
if self._stages:
data_index.load()
data[workspace] = view(
data_index, partial(key_filter, workspace)
)
+73 -36
View File
@@ -1,11 +1,14 @@
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING, Optional, Sequence
from dvc.exceptions import UploadError
from dvc.exceptions import InvalidArgumentError, UploadError
from ..utils import glob_targets
from . import locked
if TYPE_CHECKING:
from dvc.cloud import Remote
from dvc.repo import Repo
from dvc.types import TargetType
from dvc_objects.db.base import ObjectDB
@@ -26,55 +29,89 @@ def push(
odb: Optional["ObjectDB"] = None,
include_imports=False,
):
from dvc.repo.worktree import push_worktree
worktree_remote: Optional["Remote"] = None
_remote = self.cloud.get_remote(name=remote)
if _remote.worktree or _remote.fs.version_aware:
return push_worktree(self, _remote)
worktree_remote = _remote
pushed = 0
used_run_cache = (
self.stage_cache.push(remote, odb=odb) if run_cache else []
)
pushed += len(used_run_cache)
if isinstance(targets, str):
targets = [targets]
expanded_targets = glob_targets(targets, glob=glob)
used = self.used_objs(
expanded_targets,
all_branches=all_branches,
all_tags=all_tags,
all_commits=all_commits,
with_deps=with_deps,
force=True,
remote=remote,
jobs=jobs,
recursive=recursive,
used_run_cache=used_run_cache,
revs=revs,
push=True,
)
pushed = len(used_run_cache)
if odb:
all_ids = set()
for dest_odb, obj_ids in used.items():
if not include_imports and dest_odb and dest_odb.read_only:
continue
all_ids.update(obj_ids)
result = self.cloud.push(all_ids, jobs, remote=remote, odb=odb)
if result.failed:
raise UploadError(len(result.failed))
pushed += len(result.transferred)
if worktree_remote is not None:
pushed += _push_worktree(
self,
worktree_remote,
revs=revs,
all_branches=all_branches,
all_tags=all_tags,
all_commits=all_commits,
targets=expanded_targets,
with_deps=with_deps,
recursive=recursive,
)
else:
for dest_odb, obj_ids in used.items():
if dest_odb and dest_odb.read_only:
continue
result = self.cloud.push(
obj_ids, jobs, remote=remote, odb=odb or dest_odb
)
used = self.used_objs(
expanded_targets,
all_branches=all_branches,
all_tags=all_tags,
all_commits=all_commits,
with_deps=with_deps,
force=True,
remote=remote,
jobs=jobs,
recursive=recursive,
used_run_cache=used_run_cache,
revs=revs,
push=True,
)
if odb:
all_ids = set()
for dest_odb, obj_ids in used.items():
if not include_imports and dest_odb and dest_odb.read_only:
continue
all_ids.update(obj_ids)
result = self.cloud.push(all_ids, jobs, remote=remote, odb=odb)
if result.failed:
raise UploadError(len(result.failed))
pushed += len(result.transferred)
else:
for dest_odb, obj_ids in used.items():
if dest_odb and dest_odb.read_only:
continue
result = self.cloud.push(
obj_ids, jobs, remote=remote, odb=odb or dest_odb
)
if result.failed:
raise UploadError(len(result.failed))
pushed += len(result.transferred)
return pushed
def _push_worktree(
repo: "Repo",
remote: "Remote",
revs: Optional[Sequence[str]] = None,
all_branches: bool = False,
all_tags: bool = False,
all_commits: bool = False,
targets: Optional["TargetType"] = None,
**kwargs,
) -> int:
from dvc.repo.worktree import push_worktree
if revs or all_branches or all_tags or all_commits:
raise InvalidArgumentError(
"Multiple rev push is unsupported for cloud versioned remotes"
)
return push_worktree(repo, remote, targets=targets, **kwargs)
+45 -7
View File
@@ -1,7 +1,13 @@
from typing import TYPE_CHECKING, List
from dvc.exceptions import InvalidArgumentError
from dvc.stage.exceptions import StageUpdateError
from . import locked
if TYPE_CHECKING:
from dvc.repo.stage import StageInfo
@locked
def update(
@@ -15,6 +21,7 @@ def update(
jobs=None,
):
from ..dvcfile import Dvcfile
from .worktree import update_worktree_stages
if not targets:
targets = [None]
@@ -28,15 +35,21 @@ def update(
)
if not to_remote and remote:
raise InvalidArgumentError(
"--remote can't be used without --to-remote"
)
if not self.cloud.get_remote(name=remote).worktree:
raise InvalidArgumentError(
"--remote can't be used without --to-remote"
)
stages = set()
for target in targets:
stages.update(self.stage.collect(target, recursive=recursive))
import_stages = set()
other_stage_infos: List["StageInfo"] = []
for stage in stages:
for stage_info in self.index.collect_targets(targets, recursive=recursive):
if stage_info.stage.is_import:
import_stages.add(stage_info.stage)
else:
other_stage_infos.append(stage_info)
for stage in import_stages:
stage.update(
rev,
to_remote=to_remote,
@@ -47,4 +60,29 @@ def update(
dvcfile = Dvcfile(self, stage.path)
dvcfile.dump(stage)
if other_stage_infos:
remote_obj = self.cloud.get_remote(name=remote)
if not remote_obj.worktree:
raise StageUpdateError(other_stage_infos[0].stage.relpath)
if rev:
raise InvalidArgumentError(
"--rev can't be used with worktree update"
)
if no_download:
raise InvalidArgumentError(
"--no-download can't be used with worktree update"
)
if to_remote:
raise InvalidArgumentError(
"--to-remote can't be used with worktree update"
)
update_worktree_stages(
self,
other_stage_infos,
remote_obj,
)
stages = import_stages | {
stage_info.stage for stage_info in other_stage_infos
}
return list(stages)
+171 -46
View File
@@ -1,5 +1,6 @@
import logging
from typing import TYPE_CHECKING, Any, Dict, Optional
from functools import partial
from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional, Union, cast
from dvc.fs.callbacks import Callback
@@ -8,13 +9,25 @@ if TYPE_CHECKING:
from dvc.index import Index, IndexView
from dvc.output import Output
from dvc.repo import Repo
from dvc.repo.stage import StageInfo
from dvc.stage import Stage
from dvc.types import TargetType
from dvc_data.hashfile.meta import Meta
from dvc_data.hashfile.tree import Tree
from dvc_data.index import DataIndex, DataIndexView
from dvc_objects.fs.base import FileSystem
logger = logging.getLogger(__name__)
# for files, if our version's checksum (etag) matches the latest remote
# checksum, we do not need to push, even if the version IDs don't match
def _meta_checksum(fs: "FileSystem", meta: "Meta") -> Any:
if not meta or meta.isdir:
return meta
return getattr(meta, fs.PARAM_CHECKSUM)
def worktree_view(
index: "Index",
targets: Optional["TargetType"] = None,
@@ -57,10 +70,15 @@ def worktree_view(
)
def fetch_worktree(repo: "Repo", remote: "Remote") -> int:
def fetch_worktree(
repo: "Repo",
remote: "Remote",
targets: Optional["TargetType"] = None,
**kwargs,
) -> int:
from dvc_data.index import save
view = worktree_view(repo.index)
view = worktree_view(repo.index, targets=targets, **kwargs)
index = view.data["repo"]
for key, entry in index.iteritems():
entry.fs = remote.fs
@@ -76,31 +94,34 @@ def fetch_worktree(repo: "Repo", remote: "Remote") -> int:
return save(index, callback=cb)
def push_worktree(repo: "Repo", remote: "Remote") -> int:
from dvc_data.index import build, checkout
def push_worktree(
repo: "Repo",
remote: "Remote",
targets: Optional["TargetType"] = None,
**kwargs,
) -> int:
from dvc_data.index import checkout
view = worktree_view(repo.index, push=True, latest_only=remote.worktree)
view = worktree_view(
repo.index,
push=True,
targets=targets,
latest_only=remote.worktree,
**kwargs,
)
new_index = view.data["repo"]
if remote.worktree:
logger.debug("Indexing latest worktree for '%s'", remote.path)
old_index = build(remote.path, remote.fs)
logger.debug("indexing latest worktree for '%s'", remote.path)
old_index = _build_worktree_index(repo, remote, view)
logger.debug("Pushing worktree changes to '%s'", remote.path)
else:
old_index = None
logger.debug("Pushing version-aware files to '%s'", remote.path)
if remote.worktree:
# for files, if our version's checksum (etag) matches the latest remote
# checksum, we do not need to push, even if the version IDs don't match
def _checksum(meta: "Meta") -> Any:
if not meta or meta.isdir:
return meta
return getattr(meta, remote.fs.PARAM_CHECKSUM)
diff_kwargs: Dict[str, Any] = {
"meta_only": True,
"meta_cmp_key": _checksum,
"meta_cmp_key": partial(_meta_checksum, remote.fs),
}
else:
diff_kwargs = {}
@@ -121,41 +142,145 @@ def push_worktree(repo: "Repo", remote: "Remote") -> int:
**diff_kwargs,
)
if pushed:
_update_pushed_meta(repo, view)
for stage in view.stages:
for out in stage.outs:
workspace, _key = out.index_key
_update_out_meta(out, repo.index.data[workspace])
stage.dvcfile.dump(stage, with_files=True, update_pipeline=False)
return pushed
def _update_pushed_meta(repo: "Repo", view: "IndexView"):
def _build_worktree_index(
repo: "Repo", remote: "Remote", view: "IndexView"
) -> "DataIndex":
from dvc_data.index import DataIndex
from dvc_data.index.build import build_entries
index = DataIndex()
for out in view.outs:
_workspace, key = out.index_key
parts = out.fs.path.relparts(out.fs_path, repo.root_dir)
path = remote.fs.path.join(remote.path, *parts)
for entry in build_entries(path, remote.fs):
if not entry.key or entry.key == ("",):
entry.key = key
else:
entry.key = key + entry.key
index.add(entry)
return index
def _update_out_meta(
out: "Output", index: Union["DataIndex", "DataIndexView"]
):
from dvc_data.index.save import build_tree
_, key = out.index_key
entry = index[key]
repo = out.stage.repo
if out.isdir():
old_tree = cast("Tree", out.get_obj())
entry.hash_info = old_tree.hash_info
entry.meta = out.meta
for subkey, entry in index.iteritems(key):
if entry.meta.isdir:
continue
fs_path = repo.fs.path.join(repo.root_dir, *subkey)
meta, hash_info = old_tree.get(
repo.fs.path.relparts(fs_path, out.fs_path)
)
entry.hash_info = hash_info
if meta.version_id is not None:
# preserve existing version IDs for unchanged files in
# this dir (entry will have the latest remote version
# ID after checkout)
entry.meta = meta
tree_meta, new_tree = build_tree(index, key)
out.obj = new_tree
out.hash_info = new_tree.hash_info
out.meta = tree_meta
else:
if entry.hash_info:
out.hash_info = entry.hash_info
if out.meta.version_id is None:
out.meta = entry.meta
def update_worktree_stages(
repo: "Repo",
stage_infos: Iterable["StageInfo"],
remote: "Remote",
):
from dvc.repo.index import IndexView
from dvc_data.index import build
def outs_filter(out: "Output") -> bool:
return out.is_in_repo and out.use_cache and out.can_push
view = IndexView(
repo.index,
stage_infos,
outs_filter=outs_filter,
)
local_index = view.data["repo"]
logger.debug("indexing latest worktree for '%s'", remote.path)
remote_index = build(remote.path, remote.fs)
for stage in view.stages:
for out in stage.outs:
workspace, key = out.index_key
index = repo.index.data[workspace]
entry = index[key]
if out.isdir():
old_tree = out.get_obj()
entry.hash_info = old_tree.hash_info
entry.meta = out.meta
for subkey, entry in index.iteritems(key):
if entry.meta.isdir:
continue
fs_path = repo.fs.path.join(repo.root_dir, *subkey)
meta, hash_info = old_tree.get(
repo.fs.path.relparts(fs_path, out.fs_path)
_workspace, key = out.index_key
if key not in remote_index:
logger.warning(
"Could not update '%s', it does not exist in the remote",
out,
)
continue
entry = remote_index[key]
if entry.meta and entry.meta.isdir:
if not any(
subkey != key and subentry.meta and not subentry.meta.isdir
for subkey, subentry in remote_index.iteritems(key)
):
logger.warning(
"Could not update '%s', directory is empty in the "
"remote",
out,
)
entry.hash_info = hash_info
if meta.version_id is not None:
# preserve existing version IDs for unchanged files in
# this dir (entry will have the latest remote version
# ID after checkout)
entry.meta = meta
tree_meta, new_tree = build_tree(index, key)
out.obj = new_tree
out.hash_info = new_tree.hash_info
out.meta = tree_meta
else:
out.hash_info = entry.hash_info
if out.meta.version_id is None:
out.meta = entry.meta
continue
_fetch_out_changes(out, local_index, remote_index, remote)
stage.save(merge_versioned=True)
for out in stage.outs:
_update_out_meta(out, remote_index)
stage.dvcfile.dump(stage, with_files=True, update_pipeline=False)
def _fetch_out_changes(
out: "Output",
local_index: "DataIndex",
remote_index: "DataIndex",
remote: "Remote",
):
from dvc_data.index import DataIndex, checkout
_, key = out.index_key
old = DataIndex()
new = DataIndex()
for _, entry in local_index.iteritems(key):
old.add(entry)
for _, entry in remote_index.iteritems(key):
new.add(entry)
total = len(new)
with Callback.as_tqdm_callback(
unit="file", desc=f"Updating '{out}'", disable=total == 0
) as cb:
cb.set_size(total)
checkout(
new,
out.repo.root_dir,
out.fs,
old=old,
delete=True,
update_meta=False,
meta_only=True,
meta_cmp_key=partial(_meta_checksum, remote.fs),
callback=cb,
)
+6 -2
View File
@@ -267,11 +267,15 @@ class StageCache:
return ret
def push(self, remote: Optional[str], odb: Optional["ObjectDB"] = None):
dest_odb = odb or self.repo.cloud.get_remote_odb(remote)
dest_odb = odb or self.repo.cloud.get_remote_odb(
remote, "push --run-cache"
)
return self.transfer(self.repo.odb.local, dest_odb)
def pull(self, remote: Optional[str], odb: Optional["ObjectDB"] = None):
odb = odb or self.repo.cloud.get_remote_odb(remote)
odb = odb or self.repo.cloud.get_remote_odb(
remote, "fetch --run-cache"
)
return self.transfer(odb, self.repo.odb.local)
def get_used_objs(self, used_run_cache, *args, **kwargs):
+13 -35
View File
@@ -1,27 +1,9 @@
import sys
from pathlib import Path
from typing import TYPE_CHECKING, List
try:
from hydra import compose, initialize_config_dir
from hydra._internal.config_loader_impl import ConfigLoaderImpl
from hydra._internal.core_plugins.basic_sweeper import BasicSweeper
from hydra.core.override_parser.overrides_parser import OverridesParser
from hydra.core.override_parser.types import ValueType
from hydra.errors import ConfigCompositionException, OverrideParseException
from omegaconf import OmegaConf
hydra_compatible = True
except ValueError:
if sys.version_info >= (3, 11):
hydra_compatible = False
else:
raise
from dvc.exceptions import DvcException, InvalidArgumentError
from dvc.exceptions import InvalidArgumentError
from .collections import merge_dicts, remove_missing_keys, to_omegaconf
from .serialize import DUMPERS, MODIFIERS
if TYPE_CHECKING:
from dvc.types import StrPath
@@ -46,10 +28,10 @@ def compose_and_dump(
.. _Hydra Override:
https://hydra.cc/docs/advanced/override_grammar/basic/
"""
if not hydra_compatible:
raise DvcException(
"hydra functionality is not supported in Python >= 3.11"
)
from hydra import compose, initialize_config_dir
from omegaconf import OmegaConf
from .serialize import DUMPERS
with initialize_config_dir(config_dir, version_base=None):
cfg = compose(config_name=config_name, overrides=overrides)
@@ -71,10 +53,11 @@ def apply_overrides(path: "StrPath", overrides: List[str]) -> None:
.. _Hydra Override:
https://hydra.cc/docs/next/advanced/override_grammar/basic/
"""
if not hydra_compatible:
raise DvcException(
"hydra functionality is not supported in Python >= 3.11"
)
from hydra._internal.config_loader_impl import ConfigLoaderImpl
from hydra.errors import ConfigCompositionException, OverrideParseException
from omegaconf import OmegaConf
from .serialize import MODIFIERS
suffix = Path(path).suffix.lower()
@@ -101,10 +84,7 @@ def apply_overrides(path: "StrPath", overrides: List[str]) -> None:
def to_hydra_overrides(path_overrides):
if not hydra_compatible:
raise DvcException(
"hydra functionality is not supported in Python >= 3.11"
)
from hydra.core.override_parser.overrides_parser import OverridesParser
parser = OverridesParser.create()
return parser.parse_overrides(overrides=path_overrides)
@@ -117,10 +97,8 @@ def dict_product(dicts):
def get_hydra_sweeps(path_overrides):
if not hydra_compatible:
raise DvcException(
"hydra functionality is not supported in Python >= 3.11"
)
from hydra._internal.core_plugins.basic_sweeper import BasicSweeper
from hydra.core.override_parser.types import ValueType
path_sweeps = {}
for path, overrides in path_overrides.items():
+8 -8
View File
@@ -51,10 +51,10 @@ dependencies = [
"pyparsing>=2.4.7",
"typing-extensions>=3.7.4",
"scmrepo==0.1.4",
"dvc-render==0.0.14",
"dvc-task==0.1.6",
"dvclive>=1.0",
"dvc-data==0.28.3",
"dvc-render==0.0.15",
"dvc-task==0.1.8",
"dvclive>=1.2.2",
"dvc-data==0.28.4",
"dvc-http==2.27.2",
"hydra-core>=1.1.0",
"iterative-telemetry==0.0.6",
@@ -90,18 +90,18 @@ tests = [
# Test requirements
"pytest==7.2.0",
"pytest-cov==4.0.0",
"pytest-xdist==3.0.2",
"pytest-xdist==3.1.0",
"pytest-mock==3.10.0",
"pytest-lazy-fixture==0.6.3",
# https://github.com/docker/docker-py/issues/2902
"pytest-docker==0.11.0; python_version < '3.10' or sys_platform != 'win32'",
"flaky==3.7.0",
"pytest-timeout==2.1.0",
"filelock==3.8.0",
"filelock==3.8.2",
"beautifulsoup4==4.11.1",
"pywin32>=225; sys_platform == 'win32'", # optional dependency
# pylint requirements
"pylint==2.15.7",
"pylint==2.15.8",
# we use this to suppress pytest-related false positives in our tests.
"pylint-pytest==1.1.2",
# we use this to suppress some messages in tests, eg: foo/bar naming,
@@ -161,7 +161,7 @@ line_length = 79
[tool.pytest.ini_options]
log_level = "debug"
addopts = "-ra -p no:hydra_pytest --cov-config pyproject.toml"
addopts = "-ra --cov-config pyproject.toml"
markers = [
"needs_internet: Might need network access for the tests",
"vscode: Tests verifying contract between DVC and VSCode plugin",
-12
View File
@@ -250,18 +250,6 @@ def run_copy_metrics(tmp_dir, copy_script):
return run
@pytest.fixture(autouse=True)
def mock_hydra_conf(mocker):
if sys.version_info < (3, 11):
return
# `hydra.conf` fails to import in 3.11, it raises ValueError due to changes
# in dataclasses. See https://github.com/python/cpython/pull/29867.
# NOTE: using sentinel here so that any imports from `hydra.conf`
# return a mock.
sys.modules["hydra.conf"] = mocker.sentinel
@pytest.fixture(autouse=True)
def gc_collect_on_dvc_close_on_win_311(mocker):
if sys.version_info < (3, 11) and os.name != "nt":
+2 -2
View File
@@ -684,7 +684,7 @@ def test_experiments_workspace_not_log_exception(caplog, dvc, scm):
assert not caplog.text
def test_run_env(tmp_dir, dvc, scm):
def test_run_env(tmp_dir, dvc, scm, mocker):
dump_run_env = dedent(
"""\
import os
@@ -703,7 +703,7 @@ def test_run_env(tmp_dir, dvc, scm):
)
dvc.experiments.run()
assert (tmp_dir / "DVC_EXP_BASELINE_REV").read_text().strip() == baseline
assert (tmp_dir / "DVC_EXP_NAME").read_text().strip() == ""
assert (tmp_dir / "DVC_EXP_NAME").read_text().strip() != ""
dvc.experiments.run(name="foo")
assert (tmp_dir / "DVC_EXP_BASELINE_REV").read_text().strip() == baseline
+2 -20
View File
@@ -1,5 +1,3 @@
import sys
import pytest
from dvc.exceptions import InvalidArgumentError
@@ -21,18 +19,7 @@ def test_modify_params(params_repo, dvc, changes, expected):
assert fobj.read().strip() == expected
@pytest.mark.parametrize(
"hydra_enabled",
[
pytest.param(
True,
marks=pytest.mark.skipif(
sys.version_info >= (3, 11), reason="unsupported on 3.11"
),
),
False,
],
)
@pytest.mark.parametrize("hydra_enabled", [True, False])
@pytest.mark.parametrize(
"config_dir,config_name",
[
@@ -93,12 +80,7 @@ def test_hydra_compose_and_dump(
"hydra_enabled,overrides,expected",
[
(
pytest.param(
True,
marks=pytest.mark.xfail(
sys.version_info >= (3, 11), reason="unsupported on 3.11"
),
),
True,
["db=mysql,postgresql"],
[
{"params.yaml": ["db=mysql"]},
+1
View File
@@ -220,6 +220,7 @@ def test_show_failed_experiment(tmp_dir, scm, dvc, failed_exp_stage):
"msg": "Experiment run failed.",
"type": "",
},
"name": ANY,
}
}
+29
View File
@@ -0,0 +1,29 @@
from funcy import first
def test_generate_random_exp_name(tmp_dir, dvc, scm, exp_stage, mocker):
mocker.patch(
"dvc.repo.experiments.utils.random.choice",
side_effect=[
0,
0,
0,
0,
1,
1,
0,
0,
],
)
ref = first(dvc.experiments.run(exp_stage.addressing, params=["foo=1"]))
assert dvc.experiments.get_exact_name([ref])[ref] == "0-0"
# Causes 1 retry
ref = first(dvc.experiments.run(exp_stage.addressing, params=["foo=2"]))
assert dvc.experiments.get_exact_name([ref])[ref] == "1-1"
tmp_dir.scm_gen({"foo": "bar"}, commit="foo")
# Can use same name because of different baseline_rev
ref = first(dvc.experiments.run(exp_stage.addressing, params=["foo=1"]))
assert dvc.experiments.get_exact_name([ref])[ref] == "0-0"
+10 -14
View File
@@ -1,5 +1,3 @@
import sys
import pytest
from dvc.exceptions import InvalidArgumentError
@@ -93,16 +91,16 @@ from dvc.exceptions import InvalidArgumentError
def test_apply_overrides(tmp_dir, suffix, overrides, expected):
from dvc.utils.hydra import apply_overrides
if suffix == "toml":
if overrides in [
["foo=baz"],
["foo.0=bar"],
["foo=baz", "goo=bar"],
["lorem=null"],
]:
# TOML dumper breaks when overriding a list/dict with other type
# or when handling `null` values.
pytest.xfail()
if suffix == "toml" and overrides in [
["foo=baz"],
["foo.0=bar"],
["foo=baz", "goo=bar"],
["lorem=null"],
]:
pytest.skip(
"TOML dumper breaks when overriding a list/dict with other type or"
" when handling `null` values."
)
params_file = tmp_dir / f"params.{suffix}"
params_file.dump(
@@ -140,7 +138,6 @@ def hydra_setup(tmp_dir, config_dir, config_name):
return str(config_dir)
@pytest.mark.skipif(sys.version_info >= (3, 11), reason="unsupported on 3.11")
@pytest.mark.parametrize("suffix", ["yaml", "toml", "json"])
@pytest.mark.parametrize(
"overrides,expected",
@@ -180,7 +177,6 @@ def test_compose_and_dump(tmp_dir, suffix, overrides, expected):
assert output_file.parse() == expected
@pytest.mark.skipif(sys.version_info >= (3, 11), reason="unsupported on 3.11")
def test_compose_and_dump_yaml_handles_string(tmp_dir):
"""Regression test for 8583"""
from dvc.utils.hydra import compose_and_dump