mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-03 15:28:29 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
415d4bc980 | ||
|
|
f3be7d761f | ||
|
|
4bde0e754e | ||
|
|
88f1ada4bf | ||
|
|
bd65c205df | ||
|
|
e6ce5f825d | ||
|
|
d30d66c1d3 | ||
|
|
19f31d7cac | ||
|
|
99d9ca714d | ||
|
|
a3b6722523 | ||
|
|
b824bc4db7 | ||
|
|
6cc349edb4 | ||
|
|
e455ee69aa | ||
|
|
31270c14e3 | ||
|
|
28b7c8b723 | ||
|
|
1d6ea68133 | ||
|
|
25d2638a57 | ||
|
|
0b03714097 | ||
|
|
9392bbdd4b | ||
|
|
c4b18b710a | ||
|
|
da1e0b57b8 | ||
|
|
b2a873d13b | ||
|
|
89ef3463dc | ||
|
|
7d14acb5a7 | ||
|
|
dd501554fe | ||
|
|
54acfda687 | ||
|
|
e70a1e9bfc | ||
|
|
aea0e6b6cc | ||
|
|
2c6b917fc5 | ||
|
|
a43c1b31f7 | ||
|
|
683a9f09bd | ||
|
|
53a7230da0 | ||
|
|
f078a572dd | ||
|
|
007041bfe9 | ||
|
|
965dbea75e | ||
|
|
f5cbf97eea | ||
|
|
198c8f2ffd | ||
|
|
368c785410 | ||
|
|
64ccd9cfdb | ||
|
|
d38b2dbcb8 | ||
|
|
8cb0f02bf9 | ||
|
|
7e75b0616f | ||
|
|
fb24775bc0 | ||
|
|
82e2446eb4 | ||
|
|
1e08cc5da3 | ||
|
|
e56a729e3b | ||
|
|
9a050684f7 | ||
|
|
2431ec6189 | ||
|
|
70fc4e6cda | ||
|
|
bbc01b40d2 | ||
|
|
6302e5f1d3 | ||
|
|
df5429f7d8 | ||
|
|
f9583f0c3d | ||
|
|
9e26a46323 |
@@ -835,9 +835,9 @@
|
||||
"tests/func/test_dataset.py::test_dvc": 0.46001330000012786,
|
||||
"tests/func/test_dataset.py::test_dvc_dataset_pipeline": 0.7948393000000351,
|
||||
"tests/func/test_dataset.py::test_dvc_dump": 0.17996560000005957,
|
||||
"tests/func/test_dataset.py::test_dvcx": 0.22482930000001033,
|
||||
"tests/func/test_dataset.py::test_dvcx_dataset_pipeline": 0.43405649999999696,
|
||||
"tests/func/test_dataset.py::test_dvcx_dump": 0.1858166000000665,
|
||||
"tests/func/test_dataset.py::test_datachain": 0.22482930000001033,
|
||||
"tests/func/test_dataset.py::test_datachain_dataset_pipeline": 0.43405649999999696,
|
||||
"tests/func/test_dataset.py::test_datachain_dump": 0.1858166000000665,
|
||||
"tests/func/test_dataset.py::test_invalidation": 0.16291839999996682,
|
||||
"tests/func/test_dataset.py::test_parametrized": 0.19277699999997822,
|
||||
"tests/func/test_dataset.py::test_pipeline_when_not_in_sync": 0.2063928999999689,
|
||||
@@ -1627,7 +1627,7 @@
|
||||
"tests/unit/command/test_data_sync.py::test_pull": 0.17549940000003517,
|
||||
"tests/unit/command/test_data_sync.py::test_push": 0.16576090000000931,
|
||||
"tests/unit/command/test_dataset.py::test_add[spec0-lock0-Adding ds (url:/path @ main)\\n]": 0.22054290000005494,
|
||||
"tests/unit/command/test_dataset.py::test_add[spec1-lock1-Adding mydataset (dvcx://dataset @ v1)\\n]": 0.22979979999990974,
|
||||
"tests/unit/command/test_dataset.py::test_add[spec1-lock1-Adding mydataset (dc://dataset @ v1)\\n]": 0.22979979999990974,
|
||||
"tests/unit/command/test_dataset.py::test_add[spec2-lock2-Adding mydataset (s3://bucket/path)\\n]": 0.20264819999988504,
|
||||
"tests/unit/command/test_dataset.py::test_add_already_exists": 0.2456899000001158,
|
||||
"tests/unit/command/test_dataset.py::test_update[spec0-old_lock0-new_lock0-expected_outputs0-missing]": 0.2354315999998562,
|
||||
|
||||
@@ -16,14 +16,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
python-version: "3.13"
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: astral-sh/setup-uv@v3
|
||||
- uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
enable-cache: true
|
||||
cache-dependency-glob: pyproject.toml
|
||||
|
||||
@@ -25,25 +25,27 @@ jobs:
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.12
|
||||
python-version: 3.13
|
||||
|
||||
- uses: astral-sh/setup-uv@v3
|
||||
- run: uv pip install --upgrade setuptools_scm twine --system
|
||||
- uses: astral-sh/setup-uv@v6
|
||||
|
||||
- name: Force version for Test PyPI upload
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags') }}
|
||||
run: echo version=$(python -m setuptools_scm | awk -F+ '{print $1}' | tail -1) >> $GITHUB_ENV
|
||||
- name: Omit local version for Test PyPI upload
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: echo SETUPTOOLS_SCM_OVERRIDES_FOR_DVC='{local_scheme="no-local-version"}' >> $GITHUB_ENV
|
||||
|
||||
- run: echo 'PKG = "pip"'>dvc/_build.py
|
||||
- run: uv build
|
||||
env:
|
||||
SETUPTOOLS_SCM_PRETEND_VERSION_FOR_DVC: ${{ env.version }}
|
||||
- run: twine check --strict dist/*
|
||||
- name: Build Python Package
|
||||
run: |
|
||||
echo 'PKG = "pip"'>dvc/_build.py
|
||||
uv build
|
||||
|
||||
- name: Check dist
|
||||
run: uv tool run twine check --strict dist/*
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Packages
|
||||
path: dist/
|
||||
if-no-files-found: error
|
||||
|
||||
test-pypi-publish:
|
||||
name: Publish dev package to test.pypi.org
|
||||
@@ -67,7 +69,6 @@ jobs:
|
||||
- name: Upload package to Test PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
attestations: true
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
skip-existing: true
|
||||
|
||||
@@ -92,5 +93,3 @@ jobs:
|
||||
|
||||
- name: Upload package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
attestations: true
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
pyv: ["3.12"]
|
||||
pyv: ["3.13"]
|
||||
plugin: ["dvc-s3"]
|
||||
|
||||
steps:
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ matrix.pyv }}
|
||||
|
||||
- uses: astral-sh/setup-uv@v3
|
||||
- uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
enable-cache: true
|
||||
cache-dependency-glob: "**/pyproject.toml"
|
||||
|
||||
@@ -19,6 +19,8 @@ jobs:
|
||||
lint:
|
||||
timeout-minutes: 10
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
DVC_NO_ANALYTICS: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -30,8 +32,8 @@ jobs:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- uses: astral-sh/setup-uv@v3
|
||||
python-version: '3.13'
|
||||
- uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
enable-cache: true
|
||||
cache-dependency-glob: pyproject.toml
|
||||
@@ -53,7 +55,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
pyv: ["3.9", "3.10", "3.11", "3.12"]
|
||||
pyv: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
include:
|
||||
- os: windows-latest
|
||||
pyv: "3.9"
|
||||
@@ -67,6 +69,30 @@ jobs:
|
||||
- os: windows-latest
|
||||
pyv: "3.9"
|
||||
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 4"
|
||||
- os: windows-latest
|
||||
pyv: "3.10"
|
||||
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 1"
|
||||
- os: windows-latest
|
||||
pyv: "3.10"
|
||||
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 2"
|
||||
- os: windows-latest
|
||||
pyv: "3.10"
|
||||
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 3"
|
||||
- os: windows-latest
|
||||
pyv: "3.10"
|
||||
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 4"
|
||||
- os: windows-latest
|
||||
pyv: "3.11"
|
||||
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 1"
|
||||
- os: windows-latest
|
||||
pyv: "3.11"
|
||||
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 2"
|
||||
- os: windows-latest
|
||||
pyv: "3.11"
|
||||
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 3"
|
||||
- os: windows-latest
|
||||
pyv: "3.11"
|
||||
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 4"
|
||||
- os: windows-latest
|
||||
pyv: "3.12"
|
||||
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 1"
|
||||
@@ -79,6 +105,18 @@ jobs:
|
||||
- os: windows-latest
|
||||
pyv: "3.12"
|
||||
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 4"
|
||||
- os: windows-latest
|
||||
pyv: "3.13"
|
||||
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 1"
|
||||
- os: windows-latest
|
||||
pyv: "3.13"
|
||||
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 2"
|
||||
- os: windows-latest
|
||||
pyv: "3.13"
|
||||
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 3"
|
||||
- os: windows-latest
|
||||
pyv: "3.13"
|
||||
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 4"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -87,7 +125,7 @@ jobs:
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.pyv }}
|
||||
- uses: astral-sh/setup-uv@v3
|
||||
- uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
enable-cache: true
|
||||
cache-dependency-glob: pyproject.toml
|
||||
@@ -104,7 +142,7 @@ jobs:
|
||||
pytest ${{ matrix.pytestargs }} -n=logical --dist=worksteal --timeout=300 --durations=0
|
||||
--cov --cov-report=xml --cov-report=term --durations-path=./.github/.test_durations
|
||||
- name: upload coverage report
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
fail_ci_if_error: false
|
||||
@@ -122,7 +160,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Slack Notification
|
||||
uses: rtCamp/[email protected].1
|
||||
uses: rtCamp/[email protected].3
|
||||
env:
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||
SLACK_COLOR: ${{ job.status }}
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ scripts/ci/gcp-creds.json
|
||||
.gcp-creds.json
|
||||
|
||||
*~
|
||||
/dvc/_dvc_version.py
|
||||
/dvc/_version.py
|
||||
|
||||
.mypy_cache/
|
||||
.pytest_cache/
|
||||
|
||||
@@ -22,13 +22,13 @@ repos:
|
||||
- id: sort-simple-yaml
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: 'v0.7.0'
|
||||
rev: 'v0.11.8'
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.3.0
|
||||
rev: v2.4.1
|
||||
hooks:
|
||||
- id: codespell
|
||||
additional_dependencies: ["tomli"]
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
name: DVC pre-commit
|
||||
require_serial: true
|
||||
stages:
|
||||
- commit
|
||||
- pre-commit
|
||||
verbose: true
|
||||
- args:
|
||||
- git-hook
|
||||
@@ -20,7 +20,7 @@
|
||||
name: DVC pre-push
|
||||
require_serial: true
|
||||
stages:
|
||||
- push
|
||||
- pre-push
|
||||
- always_run: true
|
||||
args:
|
||||
- git-hook
|
||||
@@ -29,7 +29,7 @@
|
||||
id: dvc-post-checkout
|
||||
language: python
|
||||
language_version: python3
|
||||
minimum_pre_commit_version: 2.2.0
|
||||
minimum_pre_commit_version: 3.2.0
|
||||
name: DVC post-checkout
|
||||
require_serial: true
|
||||
stages:
|
||||
|
||||
+1
-3
@@ -1,6 +1,4 @@
|
||||
🚀 Check out our new product `DataChain <https://github.com/iterative/datachain>`_ (and give it a ⭐!) if you need to version and process a large number of files. Contact us at [email protected] to discuss commercial solutions and support for AI reproducibility and data management scenarios.
|
||||
|
||||
--------------------------
|
||||
|Banner|
|
||||
|
||||
`Website <https://dvc.org>`_
|
||||
• `Docs <https://dvc.org/doc>`_
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
from dvc.fs.dvc import _DVCFileSystem as DVCFileSystem
|
||||
|
||||
from .artifacts import artifacts_show
|
||||
from .data import get_url, open, read
|
||||
from .data import get_url, open, read # noqa: A004
|
||||
from .dataset import get as get_dataset
|
||||
from .experiments import exp_save, exp_show
|
||||
from .scm import all_branches, all_commits, all_tags
|
||||
|
||||
+6
-6
@@ -1,8 +1,8 @@
|
||||
from typing import Literal, TypedDict, Union
|
||||
|
||||
|
||||
class DVCXDataset(TypedDict):
|
||||
type: Literal["dvcx"]
|
||||
class DatachainDataset(TypedDict):
|
||||
type: Literal["dc"]
|
||||
name: str
|
||||
version: int
|
||||
|
||||
@@ -20,7 +20,7 @@ class URLDataset(TypedDict):
|
||||
path: str
|
||||
|
||||
|
||||
def get(name: str) -> Union[DVCXDataset, DVCDataset, URLDataset]:
|
||||
def get(name: str) -> Union[DatachainDataset, DVCDataset, URLDataset]:
|
||||
from difflib import get_close_matches
|
||||
|
||||
from dvc.fs import get_cloud_fs
|
||||
@@ -46,9 +46,9 @@ def get(name: str) -> Union[DVCXDataset, DVCDataset, URLDataset]:
|
||||
path=dataset.lock.path,
|
||||
sha=dataset.lock.rev_lock,
|
||||
)
|
||||
if dataset.type == "dvcx":
|
||||
return DVCXDataset(
|
||||
type="dvcx", name=dataset.name_version[0], version=dataset.lock.version
|
||||
if dataset.type == "dc":
|
||||
return DatachainDataset(
|
||||
type="dc", name=dataset.name_version[0], version=dataset.lock.version
|
||||
)
|
||||
if dataset.type == "url":
|
||||
fs_cls, _, path = get_cloud_fs(repo.config, url=dataset.lock.url)
|
||||
|
||||
+1
-2
@@ -27,8 +27,7 @@ def _postprocess(results):
|
||||
}
|
||||
processed[rev] = processed[rev] | to_merge
|
||||
|
||||
if "workspace" in processed:
|
||||
del processed["workspace"]
|
||||
processed.pop("workspace", None)
|
||||
|
||||
return processed
|
||||
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ def hide_subparsers_from_help(subparsers):
|
||||
# from the 'positional arguments' choices list
|
||||
# see: https://bugs.python.org/issue22848
|
||||
# Need to set `add_help=False`, but avoid setting `help`
|
||||
# (not even to `argparse.SUPPPRESS`).
|
||||
# (not even to `argparse.SUPPRESS`).
|
||||
# NOTE: The argument is the parent subparser, not the subcommand parser.
|
||||
cmds = [cmd for cmd, parser in subparsers.choices.items() if parser.add_help]
|
||||
subparsers.metavar = "{{{}}}".format(",".join(cmds))
|
||||
|
||||
@@ -34,7 +34,7 @@ class CmdDatasetAdd(CmdBase):
|
||||
|
||||
url = dataset.spec.url
|
||||
ver: str = ""
|
||||
if dataset.type == "dvcx":
|
||||
if dataset.type == "dc":
|
||||
ver = f"v{dataset.lock.version}"
|
||||
if dataset.type == "dvc":
|
||||
if dataset.lock.path:
|
||||
@@ -55,7 +55,7 @@ class CmdDatasetAdd(CmdBase):
|
||||
raise DvcException("--path can't be used without --dvc")
|
||||
|
||||
d = vars(self.args)
|
||||
for key in ["dvc", "dvcx", "url"]:
|
||||
for key in ["dvc", "dc", "url"]:
|
||||
if url := d.pop(key, None):
|
||||
d.update({"type": key, "url": url})
|
||||
break
|
||||
@@ -88,8 +88,8 @@ class CmdDatasetUpdate(CmdBase):
|
||||
assert new.lock
|
||||
|
||||
v: Optional[tuple[str, str]] = None
|
||||
if dataset.type == "dvcx":
|
||||
assert new.type == "dvcx"
|
||||
if dataset.type == "dc":
|
||||
assert new.type == "dc"
|
||||
if new.lock.version < dataset.lock.version:
|
||||
action = "Downgrading"
|
||||
|
||||
@@ -168,7 +168,7 @@ def add_parser(subparsers, parent_parser):
|
||||
|
||||
url_exclusive_group = ds_add_parser.add_mutually_exclusive_group(required=True)
|
||||
url_exclusive_group.add_argument(
|
||||
"--dvcx", metavar="name", help="Name of the dvcx dataset to track"
|
||||
"--dc", metavar="name", help="Name of the DataChain dataset to track"
|
||||
)
|
||||
url_exclusive_group.add_argument(
|
||||
"--dvc",
|
||||
@@ -219,7 +219,7 @@ remote://remote_name/path/to/file/or/dir (see `dvc remote`)
|
||||
"--rev",
|
||||
"--version",
|
||||
nargs="?",
|
||||
help="DVCX dataset version or Git revision (e.g. SHA, branch, tag)",
|
||||
help="DataChain dataset version or Git revision (e.g. SHA, branch, tag)",
|
||||
metavar="<version>",
|
||||
)
|
||||
ds_update_parser.set_defaults(func=CmdDatasetUpdate)
|
||||
|
||||
@@ -58,6 +58,15 @@ def add_parser(subparsers, parent_parser):
|
||||
hide_subparsers_from_help(experiments_subparsers)
|
||||
|
||||
|
||||
def add_keep_selection_flag(experiments_subcmd_parser):
|
||||
experiments_subcmd_parser.add_argument(
|
||||
"--keep",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Keep the selected experiments instead of removing them.",
|
||||
)
|
||||
|
||||
|
||||
def add_rev_selection_flags(
|
||||
experiments_subcmd_parser, command: str, default: bool = True
|
||||
):
|
||||
|
||||
@@ -34,6 +34,7 @@ class CmdExperimentsRemove(CmdBase):
|
||||
num=self.args.num,
|
||||
queue=self.args.queue,
|
||||
git_remote=self.args.git_remote,
|
||||
keep=self.args.keep,
|
||||
)
|
||||
if removed:
|
||||
ui.write(f"Removed experiments: {humanize.join(map(repr, removed))}")
|
||||
@@ -44,7 +45,7 @@ class CmdExperimentsRemove(CmdBase):
|
||||
|
||||
|
||||
def add_parser(experiments_subparsers, parent_parser):
|
||||
from . import add_rev_selection_flags
|
||||
from . import add_keep_selection_flag, add_rev_selection_flags
|
||||
|
||||
EXPERIMENTS_REMOVE_HELP = "Remove experiments."
|
||||
experiments_remove_parser = experiments_subparsers.add_parser(
|
||||
@@ -57,6 +58,7 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
)
|
||||
remove_group = experiments_remove_parser.add_mutually_exclusive_group()
|
||||
add_rev_selection_flags(experiments_remove_parser, "Remove", False)
|
||||
add_keep_selection_flag(experiments_remove_parser)
|
||||
remove_group.add_argument(
|
||||
"--queue", action="store_true", help="Remove all queued experiments."
|
||||
)
|
||||
|
||||
@@ -28,7 +28,7 @@ def _format_entry(entry, name, with_size=True, with_hash=False):
|
||||
ret = []
|
||||
if with_size:
|
||||
size = entry.get("size")
|
||||
if size is None:
|
||||
if size is None or (size <= 0 and entry.get("isdir")):
|
||||
size = ""
|
||||
else:
|
||||
size = naturalsize(size)
|
||||
@@ -77,10 +77,7 @@ def _build_tree_structure(
|
||||
|
||||
num_entries = len(entries)
|
||||
for i, (name, entry) in enumerate(entries.items()):
|
||||
# show full path for root, otherwise only show the name
|
||||
if _depth > 0:
|
||||
entry["path"] = name
|
||||
|
||||
entry["path"] = name
|
||||
is_last = i >= num_entries - 1
|
||||
tree_part = ""
|
||||
if _depth > 0:
|
||||
|
||||
+36
-4
@@ -3,26 +3,45 @@ from dvc.cli.command import CmdBaseNoRepo
|
||||
from dvc.cli.utils import DictAction, append_doc_link
|
||||
from dvc.log import logger
|
||||
|
||||
from .ls import show_entries
|
||||
from .ls import show_entries, show_tree
|
||||
|
||||
logger = logger.getChild(__name__)
|
||||
|
||||
|
||||
class CmdListUrl(CmdBaseNoRepo):
|
||||
def run(self):
|
||||
from dvc.config import Config
|
||||
def _show_tree(self, config):
|
||||
from dvc.fs import parse_external_url
|
||||
from dvc.repo.ls import _ls_tree
|
||||
|
||||
fs, fs_path = parse_external_url(
|
||||
self.args.url, fs_config=self.args.fs_config, config=config
|
||||
)
|
||||
entries = _ls_tree(fs, fs_path, maxdepth=self.args.level)
|
||||
show_tree(entries, with_color=True, with_size=self.args.size)
|
||||
return 0
|
||||
|
||||
def _show_list(self, config):
|
||||
from dvc.repo import Repo
|
||||
|
||||
entries = Repo.ls_url(
|
||||
self.args.url,
|
||||
recursive=self.args.recursive,
|
||||
maxdepth=self.args.level,
|
||||
fs_config=self.args.fs_config,
|
||||
config=Config.from_cwd(),
|
||||
config=config,
|
||||
)
|
||||
if entries:
|
||||
show_entries(entries, with_color=True, with_size=self.args.size)
|
||||
return 0
|
||||
|
||||
def run(self):
|
||||
from dvc.config import Config
|
||||
|
||||
config = Config.from_cwd()
|
||||
if self.args.tree:
|
||||
return self._show_tree(config=config)
|
||||
return self._show_list(config=config)
|
||||
|
||||
|
||||
def add_parser(subparsers, parent_parser):
|
||||
LS_HELP = "List directory contents from URL."
|
||||
@@ -40,6 +59,19 @@ def add_parser(subparsers, parent_parser):
|
||||
lsurl_parser.add_argument(
|
||||
"-R", "--recursive", action="store_true", help="Recursively list files."
|
||||
)
|
||||
lsurl_parser.add_argument(
|
||||
"-T",
|
||||
"--tree",
|
||||
action="store_true",
|
||||
help="Recurse into directories as a tree.",
|
||||
)
|
||||
lsurl_parser.add_argument(
|
||||
"-L",
|
||||
"--level",
|
||||
metavar="depth",
|
||||
type=int,
|
||||
help="Limit the depth of recursion.",
|
||||
)
|
||||
lsurl_parser.add_argument("--size", action="store_true", help="Show sizes.")
|
||||
lsurl_parser.add_argument(
|
||||
"--fs-config",
|
||||
|
||||
@@ -110,8 +110,16 @@ class CmdRemoteDefault(CmdRemote):
|
||||
class CmdRemoteList(CmdRemote):
|
||||
def run(self):
|
||||
conf = self.config.read(self.args.level)
|
||||
default_remote = conf["core"].get("remote")
|
||||
|
||||
for name, remote_conf in conf["remote"].items():
|
||||
ui.write(name, remote_conf["url"], sep="\t")
|
||||
if name == default_remote:
|
||||
text = f"{name}\t{remote_conf['url']}\t(default)"
|
||||
color = "green"
|
||||
else:
|
||||
text = f"{name}\t{remote_conf['url']}"
|
||||
color = ""
|
||||
ui.write(ui.rich_text(text, style=color), styled=True)
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
"""DVC config objects."""
|
||||
|
||||
import ntpath
|
||||
import os
|
||||
import posixpath
|
||||
import re
|
||||
from contextlib import contextmanager
|
||||
from functools import partial
|
||||
@@ -268,12 +270,17 @@ class Config(dict):
|
||||
if re.match(r"\w+://", path):
|
||||
return path
|
||||
|
||||
if os.name == "nt" and posixpath.isabs(path) and ntpath.sep not in path:
|
||||
return path
|
||||
|
||||
if os.path.isabs(path):
|
||||
return path
|
||||
|
||||
# on windows convert slashes to backslashes
|
||||
# to have path compatible with abs_conf_dir
|
||||
if os.path.sep == "\\" and "/" in path:
|
||||
if path.startswith("/"):
|
||||
path = path.replace("/", "\\\\", 1)
|
||||
path = path.replace("/", "\\")
|
||||
|
||||
expanded = os.path.expanduser(path)
|
||||
@@ -305,6 +312,9 @@ class Config(dict):
|
||||
if os.path.expanduser(path) != path:
|
||||
return localfs.as_posix(path)
|
||||
|
||||
if os.name == "nt" and posixpath.isabs(path) and ntpath.sep not in path:
|
||||
return path
|
||||
|
||||
if isinstance(path, RelPath) or not os.path.isabs(path):
|
||||
path = relpath(path, conf_dir)
|
||||
return localfs.as_posix(path)
|
||||
|
||||
+1
-1
@@ -164,7 +164,7 @@ def _spawn(
|
||||
return _detached_subprocess(executable + args, env=env, **kwargs)
|
||||
|
||||
|
||||
def daemonize(args: list[str], executable: Union[None, str, list[str]] = None) -> None:
|
||||
def daemonize(args: list[str], executable: Union[str, list[str], None] = None) -> None:
|
||||
if os.name not in ("posix", "nt"):
|
||||
return
|
||||
|
||||
|
||||
+12
-12
@@ -104,21 +104,21 @@ class AsciiCanvas:
|
||||
if dx == 0:
|
||||
y = y0
|
||||
else:
|
||||
y = y0 + int(round((x - x0) * dy / float(dx)))
|
||||
y = y0 + round((x - x0) * dy / float(dx))
|
||||
self.point(x, y, char)
|
||||
elif y0 < y1:
|
||||
for y in range(y0, y1 + 1):
|
||||
if dy == 0:
|
||||
x = x0
|
||||
else:
|
||||
x = x0 + int(round((y - y0) * dx / float(dy)))
|
||||
x = x0 + round((y - y0) * dx / float(dy))
|
||||
self.point(x, y, char)
|
||||
else:
|
||||
for y in range(y1, y0 + 1):
|
||||
if dy == 0:
|
||||
x = x0
|
||||
else:
|
||||
x = x1 + int(round((y - y1) * dx / float(dy)))
|
||||
x = x1 + round((y - y1) * dx / float(dy))
|
||||
self.point(x, y, char)
|
||||
|
||||
def text(self, x, y, text):
|
||||
@@ -258,8 +258,8 @@ def draw(vertices, edges):
|
||||
maxx = max(Xs)
|
||||
maxy = max(Ys)
|
||||
|
||||
canvas_cols = int(math.ceil(math.ceil(maxx) - math.floor(minx))) + 1
|
||||
canvas_lines = int(round(maxy - miny))
|
||||
canvas_cols = math.ceil(math.ceil(maxx) - math.floor(minx)) + 1
|
||||
canvas_lines = round(maxy - miny)
|
||||
|
||||
canvas = AsciiCanvas(canvas_cols, canvas_lines)
|
||||
|
||||
@@ -270,10 +270,10 @@ def draw(vertices, edges):
|
||||
start = edge.view._pts[index - 1]
|
||||
end = edge.view._pts[index]
|
||||
|
||||
start_x = int(round(start[0] - minx))
|
||||
start_y = int(round(start[1] - miny))
|
||||
end_x = int(round(end[0] - minx))
|
||||
end_y = int(round(end[1] - miny))
|
||||
start_x = round(start[0] - minx)
|
||||
start_y = round(start[1] - miny)
|
||||
end_x = round(end[0] - minx)
|
||||
end_y = round(end[1] - miny)
|
||||
|
||||
assert start_x >= 0
|
||||
assert start_y >= 0
|
||||
@@ -288,12 +288,12 @@ def draw(vertices, edges):
|
||||
y = vertex.view.xy[1]
|
||||
|
||||
canvas.box(
|
||||
int(round(x - minx)),
|
||||
int(round(y - miny)),
|
||||
round(x - minx),
|
||||
round(y - miny),
|
||||
vertex.view.w,
|
||||
vertex.view.h,
|
||||
)
|
||||
|
||||
canvas.text(int(round(x - minx)) + 1, int(round(y - miny)) + 1, vertex.data)
|
||||
canvas.text(round(x - minx) + 1, round(y - miny) + 1, vertex.data)
|
||||
|
||||
return canvas.draw()
|
||||
|
||||
@@ -146,6 +146,10 @@ class RepoDependency(Dependency):
|
||||
else:
|
||||
config = Config.load_file(conf)
|
||||
|
||||
# Setup config to the new DVCFileSystem to use the remote repo, but rely on the
|
||||
# local cache instead of the remote's cache. This avoids re-streaming of data,
|
||||
# but messes up the call to `_get_remote_config()` downstream, which will need
|
||||
# to ignore cache parameters.
|
||||
config["cache"] = self.repo.config["cache"]
|
||||
config["cache"]["dir"] = self.repo.cache.local_cache_dir
|
||||
|
||||
|
||||
+13
@@ -21,6 +21,19 @@ def global_config_dir():
|
||||
|
||||
|
||||
def site_cache_dir():
|
||||
from platformdirs import PlatformDirs
|
||||
from platformdirs.unix import Unix
|
||||
|
||||
if issubclass(Unix, PlatformDirs):
|
||||
# Return the cache directory shared by users, e.g. `/var/tmp/$appname`
|
||||
# NOTE: platformdirs>=5 changed `site_cache_dir` to return /var/cache/$appname.
|
||||
# as the following path is considered insecure.
|
||||
# For details, see: https://github.com/tox-dev/platformdirs/pull/239
|
||||
|
||||
# FIXME: keeping the old behavior temporarily to avoid dependency conflict.
|
||||
# In the future, consider migrating to a more secure directory.
|
||||
return f"/var/tmp/{APPNAME}" # noqa: S108
|
||||
|
||||
return os.getenv(env.DVC_SITE_CACHE_DIR) or platformdirs.site_cache_dir(
|
||||
APPNAME, APPAUTHOR, opinion=True
|
||||
)
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ from dvc_objects.fs import ( # noqa: F401
|
||||
system,
|
||||
utils,
|
||||
)
|
||||
from dvc_objects.fs.base import AnyFSPath, FileSystem # noqa: F401, TCH001
|
||||
from dvc_objects.fs.base import AnyFSPath, FileSystem # noqa: F401, TC001
|
||||
from dvc_objects.fs.errors import ( # noqa: F401
|
||||
AuthError,
|
||||
ConfigError,
|
||||
|
||||
+7
-5
@@ -500,7 +500,7 @@ class _DVCFileSystem(AbstractFileSystem):
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def _get( # noqa: C901, PLR0912
|
||||
def _get( # noqa: C901, PLR0912, PLR0915
|
||||
self,
|
||||
rpath,
|
||||
lpath,
|
||||
@@ -578,7 +578,8 @@ class _DVCFileSystem(AbstractFileSystem):
|
||||
kw = kwargs
|
||||
if isinstance(fs, DataFileSystem):
|
||||
kw = kw | {"info": info}
|
||||
return fs.get_file(src, dest, callback=callback, **kw)
|
||||
with callback.branched(src, dest) as child:
|
||||
fs.get_file(src, dest, callback=child, **kw)
|
||||
|
||||
if batch_size == 1:
|
||||
ctx: AbstractContextManager = nullcontext()
|
||||
@@ -589,7 +590,7 @@ class _DVCFileSystem(AbstractFileSystem):
|
||||
|
||||
with ctx:
|
||||
it = ((fs, f) for fs, files in _files.items() for f in files)
|
||||
deque(map_fn(get_file, it), maxlen=0)
|
||||
deque(callback.wrap(map_fn(get_file, it)), maxlen=0)
|
||||
return result
|
||||
|
||||
def get_file(self, rpath, lpath, **kwargs):
|
||||
@@ -732,9 +733,10 @@ class DVCFileSystem(FileSystem):
|
||||
return self.fs.repo_url
|
||||
|
||||
def from_os_path(self, path: str) -> str:
|
||||
if os.path.isabs(path):
|
||||
if os.path.isabs(path) or (
|
||||
os.name == "nt" and posixpath.isabs(path) and ntpath.sep not in path
|
||||
):
|
||||
path = os.path.relpath(path, self.repo.root_dir)
|
||||
|
||||
return as_posix(path)
|
||||
|
||||
def close(self):
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ def add_logging_level(level_name, level_num, method_name=None):
|
||||
self._log(level_num, message, args, **kwargs)
|
||||
|
||||
def log_to_root(message, *args, **kwargs):
|
||||
logging.log(level_num, message, *args, **kwargs)
|
||||
logging.log(level_num, message, *args, **kwargs) # noqa: LOG015
|
||||
|
||||
# getLevelName resolves the numeric log level if already defined,
|
||||
# otherwise returns a string
|
||||
|
||||
+1
-1
@@ -431,7 +431,7 @@ class Output:
|
||||
self.meta.nfiles = len(self.files)
|
||||
self.meta.size = sum(filter(None, (f.get("size") for f in self.files)))
|
||||
self.meta.remote = first(f.get("remote") for f in self.files)
|
||||
elif self.meta.nfiles or self.hash_info and self.hash_info.isdir:
|
||||
elif self.meta.nfiles or (self.hash_info and self.hash_info.isdir):
|
||||
self.meta.isdir = True
|
||||
if not self.hash_info and self.hash_name not in ("md5", "md5-dos2unix"):
|
||||
md5 = getattr(self.meta, "md5", None)
|
||||
|
||||
@@ -105,7 +105,7 @@ class Meta:
|
||||
|
||||
@staticmethod
|
||||
def update_path(meta: "Meta", path: Union[str, int]):
|
||||
dpaths = meta.dpaths[:] + [str(path)]
|
||||
dpaths = [*meta.dpaths, str(path)]
|
||||
return replace(meta, dpaths=dpaths)
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@@ -27,11 +27,9 @@ def _remove_slash(rule):
|
||||
|
||||
def _match_all_level(rule):
|
||||
if rule[:-1].find("/") >= 0 and not rule.startswith("**/"):
|
||||
if rule.startswith("/"):
|
||||
rule = rule[1:]
|
||||
rule = rule.removeprefix("/")
|
||||
return False, rule
|
||||
if rule.startswith("**/"):
|
||||
rule = rule[3:]
|
||||
rule = rule.removeprefix("**/")
|
||||
return True, rule
|
||||
|
||||
|
||||
|
||||
+23
-20
@@ -16,7 +16,7 @@ from dvc.types import StrPath
|
||||
from dvc_data.hashfile.meta import Meta
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dvcx.dataset import DatasetRecord, DatasetVersion # type: ignore[import]
|
||||
from datachain.dataset import DatasetRecord, DatasetVersion # type: ignore[import]
|
||||
from typing_extensions import Self
|
||||
|
||||
from dvc.repo import Repo
|
||||
@@ -29,10 +29,10 @@ def _get_dataset_record(name: str) -> "DatasetRecord":
|
||||
from dvc.exceptions import DvcException
|
||||
|
||||
try:
|
||||
from dvcx.catalog import get_catalog # type: ignore[import]
|
||||
from datachain.catalog import get_catalog # type: ignore[import]
|
||||
|
||||
except ImportError as exc:
|
||||
raise DvcException("dvcx is not installed") from exc
|
||||
raise DvcException("datachain is not installed") from exc
|
||||
|
||||
catalog = get_catalog()
|
||||
return catalog.get_remote_dataset(name)
|
||||
@@ -86,7 +86,7 @@ class SerDe:
|
||||
class DatasetSpec(SerDe):
|
||||
name: str
|
||||
url: str
|
||||
type: Literal["dvc", "dvcx", "url"]
|
||||
type: Literal["dvc", "dc", "url"]
|
||||
|
||||
|
||||
@frozen(kw_only=True)
|
||||
@@ -108,7 +108,7 @@ class DVCDatasetLock(DVCDatasetSpec):
|
||||
|
||||
|
||||
@frozen(kw_only=True)
|
||||
class DVCXDatasetLock(DatasetSpec):
|
||||
class DatachainDatasetLock(DatasetSpec):
|
||||
version: int
|
||||
created_at: datetime = field(converter=to_datetime)
|
||||
|
||||
@@ -160,13 +160,13 @@ class DVCDataset:
|
||||
|
||||
|
||||
@frozen(kw_only=True)
|
||||
class DVCXDataset:
|
||||
class DatachainDataset:
|
||||
manifest_path: str
|
||||
spec: "DatasetSpec"
|
||||
lock: "Optional[DVCXDatasetLock]" = field(default=None)
|
||||
lock: "Optional[DatachainDatasetLock]" = field(default=None)
|
||||
_invalidated: bool = field(default=False, eq=False, repr=False)
|
||||
|
||||
type: ClassVar[Literal["dvcx"]] = "dvcx"
|
||||
type: ClassVar[Literal["dc"]] = "dc"
|
||||
|
||||
@property
|
||||
def pinned(self) -> bool:
|
||||
@@ -193,7 +193,7 @@ class DVCXDataset:
|
||||
name, _version = self.name_version
|
||||
version = version if version is not None else _version
|
||||
version_info = _get_dataset_info(name, record=record, version=version)
|
||||
lock = DVCXDatasetLock(
|
||||
lock = DatachainDatasetLock(
|
||||
**self.spec.to_dict(),
|
||||
version=version_info.version,
|
||||
created_at=version_info.created_at,
|
||||
@@ -226,9 +226,9 @@ class URLDataset:
|
||||
return evolve(self, lock=lock)
|
||||
|
||||
|
||||
Lock = Union[DVCDatasetLock, DVCXDatasetLock, URLDatasetLock]
|
||||
Lock = Union[DVCDatasetLock, DatachainDatasetLock, URLDatasetLock]
|
||||
Spec = Union[DatasetSpec, DVCDatasetSpec]
|
||||
Dataset = Union[DVCDataset, DVCXDataset, URLDataset]
|
||||
Dataset = Union[DVCDataset, DatachainDataset, URLDataset]
|
||||
|
||||
|
||||
class DatasetNotFoundError(DvcException, KeyError):
|
||||
@@ -307,13 +307,13 @@ class Datasets(Mapping[str, Dataset]):
|
||||
raise ValueError("type should be present in spec")
|
||||
if typ == "dvc":
|
||||
return DVCDatasetSpec.from_dict(spec)
|
||||
if typ in {"dvcx", "url"}:
|
||||
if typ in {"dc", "url"}:
|
||||
return DatasetSpec.from_dict(spec)
|
||||
raise ValueError(f"unknown dataset type: {spec.get('type', '')}")
|
||||
|
||||
@staticmethod
|
||||
def _lock_from_info(lock: Optional[dict[str, Any]]) -> Optional[Lock]:
|
||||
kl = {"dvc": DVCDatasetLock, "dvcx": DVCXDatasetLock, "url": URLDatasetLock}
|
||||
kl = {"dvc": DVCDatasetLock, "dc": DatachainDatasetLock, "url": URLDatasetLock}
|
||||
if lock and (cls := kl.get(lock.get("type", ""))): # type: ignore[assignment]
|
||||
return cls.from_dict(lock) # type: ignore[attr-defined]
|
||||
return None
|
||||
@@ -356,9 +356,9 @@ class Datasets(Mapping[str, Dataset]):
|
||||
lock=lock,
|
||||
invalidated=_invalidated,
|
||||
)
|
||||
if spec.type == "dvcx":
|
||||
assert lock is None or isinstance(lock, DVCXDatasetLock)
|
||||
return DVCXDataset(
|
||||
if spec.type == "dc":
|
||||
assert lock is None or isinstance(lock, DatachainDatasetLock)
|
||||
return DatachainDataset(
|
||||
manifest_path=manifest_path,
|
||||
spec=spec,
|
||||
lock=lock,
|
||||
@@ -374,7 +374,7 @@ class Datasets(Mapping[str, Dataset]):
|
||||
manifest_path: StrPath = "dvc.yaml",
|
||||
**kwargs: Any,
|
||||
) -> Dataset:
|
||||
assert type in {"dvc", "dvcx", "url"}
|
||||
assert type in {"dvc", "dc", "url"}
|
||||
kwargs.update({"name": name, "url": url, "type": type})
|
||||
dataset = self._build_dataset(os.path.abspath(manifest_path), kwargs)
|
||||
dataset = dataset.update(self.repo)
|
||||
@@ -389,13 +389,16 @@ class Datasets(Mapping[str, Dataset]):
|
||||
|
||||
if dataset.type == "url" and (version or kwargs.get("rev")):
|
||||
raise ValueError("cannot update version/revision for a url")
|
||||
if dataset.type == "dvcx" and version is not None:
|
||||
if dataset.type == "dc" and version is not None:
|
||||
if not isinstance(version, int):
|
||||
raise TypeError(
|
||||
f"dvcx version has to be an integer, got {type(version).__name__!r}"
|
||||
"DataChain dataset version has to be an integer, "
|
||||
f"got {type(version).__name__!r}"
|
||||
)
|
||||
if version < 1:
|
||||
raise ValueError(f"dvcx version should be >=1, got {version}")
|
||||
raise ValueError(
|
||||
f"DataChain dataset version should be >=1, got {version}"
|
||||
)
|
||||
|
||||
new = dataset.update(self.repo, **kwargs)
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ def du(
|
||||
path: Optional[str] = None,
|
||||
rev: Optional[str] = None,
|
||||
summarize: bool = False,
|
||||
config: Union[None, dict[str, Any], str] = None,
|
||||
config: Union[dict[str, Any], str, None] = None,
|
||||
remote: Optional[str] = None,
|
||||
remote_config: Optional[dict] = None,
|
||||
):
|
||||
|
||||
@@ -372,7 +372,7 @@ class BaseStashQueue(ABC):
|
||||
)
|
||||
|
||||
def _stash_commit_deps(self, *args, **kwargs):
|
||||
if len(args):
|
||||
if args:
|
||||
targets = args[0]
|
||||
else:
|
||||
targets = kwargs.get("targets")
|
||||
@@ -609,9 +609,9 @@ class BaseStashQueue(ABC):
|
||||
result[exp_name] = entry_name_dict[exp_name]
|
||||
continue
|
||||
if self.scm.is_sha(exp_name):
|
||||
for rev in entry_rev_dict:
|
||||
for rev, entry in entry_rev_dict.items():
|
||||
if rev.startswith(exp_name.lower()):
|
||||
result[exp_name] = entry_rev_dict[rev]
|
||||
result[exp_name] = entry
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
@@ -67,3 +67,12 @@ class ExpRefInfo:
|
||||
baseline_sha = parts[2] + parts[3]
|
||||
name = parts[4] if len(parts) == 5 else None
|
||||
return cls(baseline_sha, name)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, ExpRefInfo):
|
||||
return False
|
||||
|
||||
return self.baseline_sha == other.baseline_sha and self.name == other.name
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.baseline_sha, self.name))
|
||||
|
||||
@@ -6,7 +6,7 @@ from dvc.repo import locked
|
||||
from dvc.repo.scm_context import scm_context
|
||||
from dvc.scm import Git, iter_revs
|
||||
|
||||
from .exceptions import UnresolvedExpNamesError
|
||||
from .exceptions import InvalidArgumentError, UnresolvedExpNamesError
|
||||
from .utils import exp_refs, exp_refs_by_baseline, push_refspec
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -24,16 +24,22 @@ logger = logger.getChild(__name__)
|
||||
@scm_context
|
||||
def remove( # noqa: C901, PLR0912
|
||||
repo: "Repo",
|
||||
exp_names: Union[None, str, list[str]] = None,
|
||||
exp_names: Union[str, list[str], None] = None,
|
||||
rev: Optional[Union[list[str], str]] = None,
|
||||
all_commits: bool = False,
|
||||
num: int = 1,
|
||||
queue: bool = False,
|
||||
git_remote: Optional[str] = None,
|
||||
keep: bool = False,
|
||||
) -> list[str]:
|
||||
removed: list[str] = []
|
||||
|
||||
if all([keep, queue]):
|
||||
raise InvalidArgumentError("Cannot use both `--keep` and `--queue`.")
|
||||
|
||||
if not any([exp_names, queue, all_commits, rev]):
|
||||
return removed
|
||||
|
||||
celery_queue: LocalCeleryQueue = repo.experiments.celery_queue
|
||||
|
||||
if queue:
|
||||
@@ -43,6 +49,7 @@ def remove( # noqa: C901, PLR0912
|
||||
|
||||
exp_ref_list: list[ExpRefInfo] = []
|
||||
queue_entry_list: list[QueueEntry] = []
|
||||
|
||||
if exp_names:
|
||||
results: dict[str, ExpRefAndQueueEntry] = (
|
||||
celery_queue.get_ref_and_entry_by_names(exp_names, git_remote)
|
||||
@@ -68,6 +75,10 @@ def remove( # noqa: C901, PLR0912
|
||||
exp_ref_list.extend(exp_ref_dict.values())
|
||||
elif all_commits:
|
||||
exp_ref_list.extend(exp_refs(repo.scm, git_remote))
|
||||
removed.extend([ref.name for ref in exp_ref_list])
|
||||
|
||||
if keep:
|
||||
exp_ref_list = list(set(exp_refs(repo.scm, git_remote)) - set(exp_ref_list))
|
||||
removed = [ref.name for ref in exp_ref_list]
|
||||
|
||||
if exp_ref_list:
|
||||
@@ -83,6 +94,7 @@ def remove( # noqa: C901, PLR0912
|
||||
|
||||
removed_refs = [str(r) for r in exp_ref_list]
|
||||
notify_refs_to_studio(repo, git_remote, removed=removed_refs)
|
||||
|
||||
return removed
|
||||
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ def run( # noqa: C901, PLR0912
|
||||
|
||||
for idx, sweep_overrides in enumerate(sweeps):
|
||||
if hydra_sweep and name_prefix is not None:
|
||||
kwargs["name"] = f"{name_prefix}-{idx+1}"
|
||||
kwargs["name"] = f"{name_prefix}-{idx + 1}"
|
||||
queue_entry = repo.experiments.queue_one(
|
||||
repo.experiments.celery_queue,
|
||||
targets=targets,
|
||||
|
||||
+4
-3
@@ -1,5 +1,6 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dvc import version_tuple
|
||||
from dvc.exceptions import DvcException
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -16,19 +17,19 @@ def pre_commit_install(scm: "Git") -> None:
|
||||
with modify_yaml(config_path) as config:
|
||||
entry = {
|
||||
"repo": "https://github.com/iterative/dvc",
|
||||
"rev": "main",
|
||||
"rev": ".".join(map(str, version_tuple[:3])),
|
||||
"hooks": [
|
||||
{
|
||||
"id": "dvc-pre-commit",
|
||||
"additional_dependencies": [".[all]"],
|
||||
"language_version": "python3",
|
||||
"stages": ["commit"],
|
||||
"stages": ["pre-commit"],
|
||||
},
|
||||
{
|
||||
"id": "dvc-pre-push",
|
||||
"additional_dependencies": [".[all]"],
|
||||
"language_version": "python3",
|
||||
"stages": ["push"],
|
||||
"stages": ["pre-push"],
|
||||
},
|
||||
{
|
||||
"id": "dvc-post-checkout",
|
||||
|
||||
+23
-14
@@ -8,7 +8,7 @@ if TYPE_CHECKING:
|
||||
def _open_repo(
|
||||
url: str,
|
||||
rev: Optional[str] = None,
|
||||
config: Union[None, dict[str, Any], str] = None,
|
||||
config: Union[dict[str, Any], str, None] = None,
|
||||
remote: Optional[str] = None,
|
||||
remote_config: Optional[dict] = None,
|
||||
):
|
||||
@@ -49,7 +49,7 @@ def ls(
|
||||
rev: Optional[str] = None,
|
||||
recursive: Optional[bool] = None,
|
||||
dvc_only: bool = False,
|
||||
config: Union[None, dict[str, Any], str] = None,
|
||||
config: Union[dict[str, Any], str, None] = None,
|
||||
remote: Optional[str] = None,
|
||||
remote_config: Optional[dict] = None,
|
||||
maxdepth: Optional[int] = None,
|
||||
@@ -90,7 +90,7 @@ def ls_tree(
|
||||
path: Optional[str] = None,
|
||||
rev: Optional[str] = None,
|
||||
dvc_only: bool = False,
|
||||
config: Union[None, dict[str, Any], str] = None,
|
||||
config: Union[dict[str, Any], str, None] = None,
|
||||
remote: Optional[str] = None,
|
||||
remote_config: Optional[dict] = None,
|
||||
maxdepth: Optional[int] = None,
|
||||
@@ -99,7 +99,9 @@ def ls_tree(
|
||||
path = path or ""
|
||||
fs: DVCFileSystem = repo.dvcfs
|
||||
fs_path = fs.from_os_path(path)
|
||||
return _ls_tree(fs, fs_path, dvc_only, maxdepth)
|
||||
return _ls_tree(
|
||||
fs, fs_path, maxdepth=maxdepth, dvc_only=dvc_only, dvcfiles=True
|
||||
)
|
||||
|
||||
|
||||
def _ls(
|
||||
@@ -145,27 +147,34 @@ def _ls(
|
||||
return ret_list
|
||||
|
||||
|
||||
def _ls_tree(
|
||||
fs, path, dvc_only: bool = False, maxdepth: Optional[int] = None, _info=None
|
||||
):
|
||||
ret = {}
|
||||
def _ls_tree(fs, path, maxdepth=None, _info=None, **fs_kwargs):
|
||||
info = _info or fs.info(path)
|
||||
if _info is None:
|
||||
# preserve the original path name
|
||||
name = path
|
||||
if not name:
|
||||
name = os.curdir if fs.protocol == "local" else fs.root_marker
|
||||
path = info["name"]
|
||||
else:
|
||||
name = path.rsplit(fs.sep, 1)[-1]
|
||||
|
||||
path = info["name"].rstrip(fs.sep) or os.curdir
|
||||
name = path.rsplit("/", 1)[-1]
|
||||
ret = {}
|
||||
ls_info = _adapt_info(info)
|
||||
ls_info["path"] = path
|
||||
|
||||
recurse = maxdepth is None or maxdepth > 0
|
||||
if recurse and info["type"] == "directory":
|
||||
infos = fs.ls(path, dvcfiles=True, dvc_only=dvc_only, detail=True)
|
||||
try:
|
||||
infos = fs.ls(path, detail=True, **fs_kwargs)
|
||||
except FileNotFoundError:
|
||||
# broken symlink?
|
||||
infos = []
|
||||
|
||||
infos.sort(key=lambda f: f["name"])
|
||||
maxdepth = maxdepth - 1 if maxdepth is not None else None
|
||||
contents = {}
|
||||
for info in infos:
|
||||
d = _ls_tree(
|
||||
fs, info["name"], dvc_only=dvc_only, maxdepth=maxdepth, _info=info
|
||||
)
|
||||
d = _ls_tree(fs, info["name"], maxdepth=maxdepth, _info=info, **fs_kwargs)
|
||||
contents.update(d)
|
||||
ls_info["contents"] = contents
|
||||
|
||||
|
||||
+16
-5
@@ -1,19 +1,30 @@
|
||||
from fsspec.implementations.local import LocalFileSystem as _LocalFileSystem
|
||||
|
||||
from dvc.exceptions import URLMissingError
|
||||
from dvc.fs import parse_external_url
|
||||
from dvc.fs import LocalFileSystem, parse_external_url
|
||||
|
||||
|
||||
def ls_url(url, *, fs_config=None, recursive=False, config=None):
|
||||
def ls_url(url, *, fs_config=None, recursive=False, maxdepth=None, config=None):
|
||||
fs, fs_path = parse_external_url(url, fs_config=fs_config, config=config)
|
||||
try:
|
||||
info = fs.info(fs_path)
|
||||
except FileNotFoundError as exc:
|
||||
raise URLMissingError(url) from exc
|
||||
if info["type"] != "directory":
|
||||
if maxdepth == 0 or info["type"] != "directory":
|
||||
return [{"path": info["name"], "isdir": False}]
|
||||
|
||||
if isinstance(fs, LocalFileSystem):
|
||||
# dvc's LocalFileSystem does not support maxdepth yet
|
||||
walk = _LocalFileSystem().walk
|
||||
else:
|
||||
walk = fs.walk
|
||||
|
||||
ret = []
|
||||
for _, dirs, files in fs.walk(fs_path, detail=True):
|
||||
if not recursive:
|
||||
for root, dirs, files in walk(fs_path, detail=True, maxdepth=maxdepth):
|
||||
parts = fs.relparts(root, fs_path)
|
||||
if parts == (".",):
|
||||
parts = ()
|
||||
if not recursive or (maxdepth and len(parts) >= maxdepth - 1):
|
||||
files.update(dirs)
|
||||
|
||||
for info in files.values():
|
||||
|
||||
+11
-5
@@ -99,7 +99,7 @@ def clean_repos():
|
||||
|
||||
def _get_remote_config(url):
|
||||
try:
|
||||
repo = Repo(url)
|
||||
repo = Repo(url, uninitialized=True)
|
||||
except NotDvcRepoError:
|
||||
return {}
|
||||
|
||||
@@ -109,10 +109,16 @@ def _get_remote_config(url):
|
||||
# Fill the empty upstream entry with a new remote pointing to the
|
||||
# original repo's cache location.
|
||||
name = "auto-generated-upstream"
|
||||
return {
|
||||
"core": {"remote": name},
|
||||
"remote": {name: {"url": repo.cache.local_cache_dir}},
|
||||
}
|
||||
try:
|
||||
local_cache_dir = repo.cache.local_cache_dir
|
||||
except AttributeError:
|
||||
# if the `.dvc` dir is missing, we get an AttributeError
|
||||
return {}
|
||||
else:
|
||||
return {
|
||||
"core": {"remote": name},
|
||||
"remote": {name: {"url": local_cache_dir}},
|
||||
}
|
||||
|
||||
# Use original remote to make sure that we are using correct url,
|
||||
# credential paths, etc if they are relative to the config location.
|
||||
|
||||
@@ -138,8 +138,7 @@ def dvc_bin(request, make_dvc_bin):
|
||||
version = Version(parse(dvc_version).base_version)
|
||||
if version not in spec:
|
||||
pytest.skip(
|
||||
f"Version {dvc_version} "
|
||||
f"does not satisfy requirement {spec!r}: {reason}"
|
||||
f"Version {dvc_version} does not satisfy requirement {spec!r}: {reason}"
|
||||
)
|
||||
return make_dvc_bin
|
||||
|
||||
@@ -150,7 +149,8 @@ def make_bench(request):
|
||||
import pytest_benchmark.plugin
|
||||
|
||||
# hack from https://github.com/ionelmc/pytest-benchmark/issues/166
|
||||
bench = pytest_benchmark.plugin.benchmark.__pytest_wrapped__.obj(request)
|
||||
bench_gen = pytest_benchmark.plugin.benchmark.__pytest_wrapped__.obj(request)
|
||||
bench = next(bench_gen)
|
||||
|
||||
suffix = f"-{name}"
|
||||
|
||||
@@ -172,7 +172,7 @@ def bench_dvc(request, dvc_bin, make_bench):
|
||||
name = kwargs.pop("name", None)
|
||||
name = f"-{name}" if name else ""
|
||||
bench = make_bench(args[0] + name)
|
||||
if request.config.getoption("--benchmark-cprofile-dump") or kwargs.pop(
|
||||
if request.config.getoption("--dvc-benchmark-cprofile-dump") or kwargs.pop(
|
||||
"cprofile", False
|
||||
):
|
||||
cprofile_results = request.config.invocation_params.dir / "prof"
|
||||
|
||||
@@ -73,7 +73,7 @@ def pytest_addoption(parser):
|
||||
)
|
||||
|
||||
parser.addoption(
|
||||
"--benchmark-cprofile-dump",
|
||||
"--dvc-benchmark-cprofile-dump",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Save cprofile results",
|
||||
|
||||
@@ -225,6 +225,39 @@ class TestLsUrl:
|
||||
],
|
||||
)
|
||||
|
||||
result = ls_url(
|
||||
str(cloud / "dir"), fs_config=cloud.config, recursive=True, maxdepth=0
|
||||
)
|
||||
match_files(
|
||||
fs,
|
||||
result,
|
||||
[{"path": (cloud / "dir").fs_path, "isdir": False}],
|
||||
)
|
||||
|
||||
result = ls_url(
|
||||
str(cloud / "dir"), fs_config=cloud.config, recursive=True, maxdepth=1
|
||||
)
|
||||
match_files(
|
||||
fs,
|
||||
result,
|
||||
[
|
||||
{"path": "foo", "isdir": False},
|
||||
{"path": "subdir", "isdir": True},
|
||||
],
|
||||
)
|
||||
|
||||
result = ls_url(
|
||||
str(cloud / "dir"), fs_config=cloud.config, recursive=True, maxdepth=2
|
||||
)
|
||||
match_files(
|
||||
fs,
|
||||
result,
|
||||
[
|
||||
{"path": "foo", "isdir": False},
|
||||
{"path": "subdir/bar", "isdir": False},
|
||||
],
|
||||
)
|
||||
|
||||
def test_nonexistent(self, cloud):
|
||||
with pytest.raises(URLMissingError):
|
||||
ls_url(str(cloud / "dir"), fs_config=cloud.config)
|
||||
|
||||
@@ -33,7 +33,7 @@ class LoaderFn(Protocol):
|
||||
def __call__(self, path: "StrPath", fs: Optional["FileSystem"] = None) -> Any: ...
|
||||
|
||||
|
||||
ReadType = Union[bytes, None, str]
|
||||
ReadType = Union[bytes, str, None]
|
||||
ParserFn = Callable[[ReadType, "StrPath"], dict]
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ if TYPE_CHECKING:
|
||||
|
||||
logger = logger.getChild(__name__)
|
||||
|
||||
STUDIO_URL = "https://studio.dvc.ai"
|
||||
STUDIO_URL = "https://studio.datachain.ai"
|
||||
|
||||
|
||||
def post(
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
try:
|
||||
from ._dvc_version import version as __version__ # type: ignore[import]
|
||||
from ._dvc_version import version_tuple # type: ignore[import]
|
||||
from ._version import version as __version__ # type: ignore[import]
|
||||
from ._version import version_tuple # type: ignore[import]
|
||||
except ImportError:
|
||||
__version__ = "UNKNOWN"
|
||||
version_tuple = (0, 0, __version__) # type: ignore[assignment]
|
||||
|
||||
+14
-13
@@ -1,6 +1,6 @@
|
||||
[build-system]
|
||||
build-backend = "setuptools.build_meta"
|
||||
requires = ["setuptools>=61", "setuptools_scm[toml]>=7"]
|
||||
requires = ["setuptools>=77", "setuptools_scm[toml]>=8"]
|
||||
|
||||
[project]
|
||||
name = "dvc"
|
||||
@@ -16,7 +16,8 @@ keywords = [
|
||||
"machine-learning",
|
||||
"reproducibility",
|
||||
]
|
||||
license = { text = "Apache License 2.0" }
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE"]
|
||||
maintainers = [{ name = "Iterative", email = "[email protected]" }]
|
||||
authors = [{ name = "Dmitry Petrov", email = "[email protected]" }]
|
||||
requires-python = ">=3.9"
|
||||
@@ -27,6 +28,7 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
]
|
||||
dynamic = ["version"]
|
||||
|
||||
@@ -34,7 +36,7 @@ dependencies = [
|
||||
"attrs>=22.2.0",
|
||||
"celery",
|
||||
"colorama>=0.3.9",
|
||||
"configobj>=5.0.6",
|
||||
"configobj>=5.0.9",
|
||||
"distro>=1.3",
|
||||
"dpath<3,>=2.1.0",
|
||||
"dulwich",
|
||||
@@ -57,7 +59,7 @@ dependencies = [
|
||||
"omegaconf",
|
||||
"packaging>=19",
|
||||
"pathspec>=0.10.3",
|
||||
"platformdirs<4,>=3.1.1",
|
||||
"platformdirs<5,>=3.1.1",
|
||||
"psutil>=5.8",
|
||||
"pydot>=1.2.4",
|
||||
"pygtrie>=2.3.2",
|
||||
@@ -83,7 +85,7 @@ gdrive = ["dvc-gdrive>=3,<4"]
|
||||
gs = ["dvc-gs>=3,<4"]
|
||||
hdfs = ["dvc-hdfs>=3,<4"]
|
||||
lint = [
|
||||
"mypy==1.12.1",
|
||||
"mypy==1.15.0",
|
||||
"pandas-stubs",
|
||||
"types-colorama",
|
||||
"types-psutil",
|
||||
@@ -99,7 +101,7 @@ s3 = ["dvc-s3>=3.2.0,<4"]
|
||||
ssh = ["dvc-ssh>=4,<5"]
|
||||
ssh_gssapi = ["dvc-ssh[gssapi]>=4,<5"]
|
||||
testing = [
|
||||
"pytest-benchmark[histogram]",
|
||||
"pytest-benchmark[histogram]>=5,<6",
|
||||
"pytest-test-utils",
|
||||
"uv",
|
||||
]
|
||||
@@ -148,15 +150,12 @@ dvc = "dvc.fs.dvc_path:DVCPath"
|
||||
hook-dirs = "dvc.__pyinstaller:get_hook_dirs"
|
||||
tests = "dvc.__pyinstaller:get_PyInstaller_tests"
|
||||
|
||||
[tool.setuptools]
|
||||
license-files = ["LICENSE"]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
exclude = ["tests", "tests.*"]
|
||||
namespaces = false
|
||||
|
||||
[tool.setuptools_scm]
|
||||
write_to = "dvc/_dvc_version.py"
|
||||
write_to = "dvc/_version.py"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = "-ra --cov-config pyproject.toml"
|
||||
@@ -172,6 +171,8 @@ filterwarnings = [
|
||||
"ignore:Deprecated call to `pkg_resources.declare_namespace:DeprecationWarning",
|
||||
# see https://github.com/networkx/networkx/issues/5723.
|
||||
"ignore:nx.nx_pydot.* depends on the pydot package, which has.*known issues and is not actively maintained:DeprecationWarning",
|
||||
# TODO: investigate where we are not closing sqlite3.Connection
|
||||
"ignore:unclosed database.*sqlite3.Connection:ResourceWarning",
|
||||
]
|
||||
log_level = "debug"
|
||||
markers = [
|
||||
@@ -264,14 +265,14 @@ show-fixes = true
|
||||
|
||||
[tool.ruff.lint]
|
||||
ignore = [
|
||||
"N818", "S101", "ISC001", "PT007", "RET502", "RET503", "SIM105", "SIM108", "SIM117",
|
||||
"N818", "S101", "A005", "PT007", "RET502", "RET503", "SIM105", "SIM108", "SIM117",
|
||||
"TRY003", "TRY300", "PERF203", "PLR2004", "PLW2901", "LOG007",
|
||||
]
|
||||
select = [
|
||||
"F", "E", "W", "C90", "I", "N", "UP", "YTT", "ASYNC", "S", "BLE", "B", "A", "C4", "DTZ", "T10",
|
||||
"EXE", "ISC", "ICN", "LOG", "G", "INP", "PIE", "T20", "PYI", "PT", "Q", "RSE", "RET",
|
||||
"SLOT", "SIM", "TID", "TCH", "ARG", "PGH", "PLC", "PLE", "PLR", "PLW", "TRY",
|
||||
"FLY", "PERF", "FURB", "RUF", "RUF022", "RUF023", "RUF024", "RUF026", "RUF027", "RUF029", "RUF030",
|
||||
"SLOT", "SIM", "TID", "TC", "ARG", "PGH", "PLC", "PLE", "PLR", "PLW", "TRY",
|
||||
"FLY", "PERF", "FURB", "RUF", "RUF027", "RUF029", "RUF036",
|
||||
]
|
||||
preview = true
|
||||
explicit-preview-rules = true
|
||||
|
||||
@@ -5,6 +5,7 @@ from funcy import first, get_in
|
||||
|
||||
from dvc import api
|
||||
from dvc.exceptions import OutputNotFoundError, PathMissingError
|
||||
from dvc.scm import CloneError
|
||||
from dvc.testing.api_tests import TestAPI # noqa: F401
|
||||
from dvc.testing.tmp_dir import make_subrepo
|
||||
from dvc.utils.fs import remove
|
||||
@@ -65,6 +66,34 @@ def test_get_url_from_remote(tmp_dir, erepo_dir, cloud, local_cloud):
|
||||
)
|
||||
|
||||
|
||||
def test_get_url_ignore_scm(tmp_dir, dvc, cloud, scm):
|
||||
tmp_dir.add_remote(config=cloud.config)
|
||||
tmp_dir.dvc_gen("foo", "foo", commit="add foo")
|
||||
|
||||
repo_posix = tmp_dir.as_posix()
|
||||
expected_url = (cloud / "files" / "md5" / "ac/bd18db4cc2f85cedef654fccc4a4d8").url
|
||||
|
||||
# Test baseline with scm
|
||||
assert api.get_url("foo", repo=repo_posix) == expected_url
|
||||
|
||||
# Simulate gitless environment (e.g. deployed container)
|
||||
(tmp_dir / ".git").rename(tmp_dir / "gitless_environment")
|
||||
|
||||
assert api.get_url("foo", repo=repo_posix) == expected_url
|
||||
assert (
|
||||
api.get_url("foo", repo=repo_posix, config={"core": {"no_scm": True}})
|
||||
== expected_url
|
||||
)
|
||||
|
||||
# Addressing repos with `file://` triggers git, so it fails in a gitless environment
|
||||
repo_url = f"file://{repo_posix}"
|
||||
with pytest.raises(
|
||||
CloneError,
|
||||
match="SCM error",
|
||||
):
|
||||
api.get_url("foo", repo=repo_url, config={"core": {"no_scm": True}})
|
||||
|
||||
|
||||
def test_open_external(tmp_dir, erepo_dir, cloud):
|
||||
erepo_dir.add_remote(config=cloud.config)
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ def test_remove_experiments_by_ref(tmp_dir, scm, dvc, exp_stage, caplog):
|
||||
ref_name_list.append(str(ref_info))
|
||||
|
||||
with pytest.raises(InvalidArgumentError):
|
||||
dvc.experiments.remove(ref_name_list[:2] + ["non-exist"])
|
||||
dvc.experiments.remove([*ref_name_list[:2], "non-exist"])
|
||||
assert scm.get_ref(ref_name_list[0]) is not None
|
||||
assert scm.get_ref(ref_name_list[1]) is not None
|
||||
assert scm.get_ref(ref_name_list[2]) is not None
|
||||
@@ -43,6 +43,26 @@ def test_remove_all_queued_experiments(tmp_dir, scm, dvc, exp_stage):
|
||||
assert scm.get_ref(str(ref_info)) is not None
|
||||
|
||||
|
||||
def test_remove_all_experiments_queued_and_completed(tmp_dir, scm, dvc, exp_stage):
|
||||
queue_length = 3
|
||||
for i in range(queue_length):
|
||||
dvc.experiments.run(
|
||||
exp_stage.addressing, params=[f"foo={i}"], name=f"exp{i}", queue=True
|
||||
)
|
||||
|
||||
results = dvc.experiments.run(
|
||||
exp_stage.addressing, params=[f"foo={queue_length}"], name=f"exp{queue_length}"
|
||||
)
|
||||
ref_info = first(exp_refs_by_rev(scm, first(results)))
|
||||
|
||||
removed = sorted(dvc.experiments.remove(all_commits=True, queue=True))
|
||||
|
||||
assert len(removed) == queue_length + 1
|
||||
assert removed == [f"exp{i}" for i in range(queue_length)] + [ref_info.name]
|
||||
assert len(dvc.experiments.stash_revs) == 0
|
||||
assert scm.get_ref(str(ref_info)) is None
|
||||
|
||||
|
||||
def test_remove_special_queued_experiments(tmp_dir, scm, dvc, exp_stage):
|
||||
dvc.experiments.run(
|
||||
exp_stage.addressing, params=["foo=1"], queue=True, name="queue1"
|
||||
@@ -179,3 +199,85 @@ def test_remove_multi_rev(tmp_dir, scm, dvc, exp_stage):
|
||||
|
||||
assert scm.get_ref(str(baseline_exp_ref)) is None
|
||||
assert scm.get_ref(str(new_exp_ref)) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"keep, expected_removed",
|
||||
[
|
||||
[["exp1"], ["exp2", "exp3"]],
|
||||
[["exp1", "exp2"], ["exp3"]],
|
||||
[["exp1", "exp2", "exp3"], []],
|
||||
[[], []], # remove does nothing if no experiments are specified
|
||||
],
|
||||
)
|
||||
def test_keep_selected_by_name(tmp_dir, scm, dvc, exp_stage, keep, expected_removed):
|
||||
# Setup: Run experiments
|
||||
refs = {}
|
||||
for i in range(1, len(keep) + len(expected_removed) + 1):
|
||||
results = dvc.experiments.run(
|
||||
exp_stage.addressing, params=[f"foo={i}"], name=f"exp{i}"
|
||||
)
|
||||
refs[f"exp{i}"] = first(exp_refs_by_rev(scm, first(results)))
|
||||
assert scm.get_ref(str(refs[f"exp{i}"])) is not None
|
||||
|
||||
removed = dvc.experiments.remove(exp_names=keep, keep=True)
|
||||
assert sorted(removed) == sorted(expected_removed)
|
||||
|
||||
for exp in expected_removed:
|
||||
assert scm.get_ref(str(refs[exp])) is None
|
||||
|
||||
for exp in keep:
|
||||
assert scm.get_ref(str(refs[exp])) is not None
|
||||
|
||||
|
||||
def test_keep_selected_by_nonexistent_name(tmp_dir, scm, dvc, exp_stage):
|
||||
# non existent name should raise an error
|
||||
with pytest.raises(UnresolvedExpNamesError):
|
||||
dvc.experiments.remove(exp_names=["nonexistent"], keep=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"num_exps, rev, num, expected_removed",
|
||||
[
|
||||
[2, "exp1", 1, ["exp2"]],
|
||||
[3, "exp3", 1, ["exp1", "exp2"]],
|
||||
[3, "exp3", 2, ["exp1"]],
|
||||
[3, "exp3", 3, []],
|
||||
[3, "exp2", 2, ["exp3"]],
|
||||
[4, "exp2", 2, ["exp3", "exp4"]],
|
||||
[4, "exp4", 2, ["exp1", "exp2"]],
|
||||
[1, None, 1, []], # remove does nothing if no experiments are specified
|
||||
],
|
||||
)
|
||||
def test_keep_selected_by_rev(
|
||||
tmp_dir, scm, dvc, exp_stage, num_exps, rev, num, expected_removed
|
||||
):
|
||||
refs = {}
|
||||
revs = {}
|
||||
# Setup: Run experiments and commit
|
||||
for i in range(1, num_exps + 1):
|
||||
scm.commit(f"commit{i}")
|
||||
results = dvc.experiments.run(
|
||||
exp_stage.addressing, params=[f"foo={i}"], name=f"exp{i}"
|
||||
)
|
||||
refs[f"exp{i}"] = first(exp_refs_by_rev(scm, first(results)))
|
||||
revs[f"exp{i}"] = scm.get_rev()
|
||||
assert scm.get_ref(str(refs[f"exp{i}"])) is not None
|
||||
|
||||
# Keep the experiment from the new revision
|
||||
removed = dvc.experiments.remove(rev=revs.get(rev), num=num, keep=True)
|
||||
assert sorted(removed) == sorted(expected_removed)
|
||||
|
||||
# Check remaining experiments
|
||||
for exp in expected_removed:
|
||||
assert scm.get_ref(str(refs[exp])) is None
|
||||
|
||||
for exp, ref in refs.items():
|
||||
if exp not in expected_removed:
|
||||
assert scm.get_ref(str(ref)) is not None
|
||||
|
||||
|
||||
def test_remove_with_queue_and_keep(tmp_dir, scm, dvc, exp_stage):
|
||||
# This should raise an exception, until decided otherwise
|
||||
with pytest.raises(InvalidArgumentError):
|
||||
dvc.experiments.remove(queue=True, keep=True)
|
||||
|
||||
@@ -133,7 +133,7 @@ def test_hydra_sweep_prefix_name(tmp_dir, params_repo, dvc):
|
||||
db_values = ["mysql", "postgresql"]
|
||||
param = "+db=" + ",".join(db_values)
|
||||
dvc.experiments.run(params=[param], queue=True, name=prefix)
|
||||
expected_names = [f"{prefix}-{i+1}" for i, _ in enumerate(db_values)]
|
||||
expected_names = [f"{prefix}-{i + 1}" for i, _ in enumerate(db_values)]
|
||||
exp_names = [entry.name for entry in dvc.experiments.celery_queue.iter_queued()]
|
||||
for name, expected in zip(exp_names, expected_names):
|
||||
assert name == expected
|
||||
|
||||
@@ -121,7 +121,7 @@ def test_artifacts(tmp_dir, dvc):
|
||||
def test_datasets(tmp_dir, dvc):
|
||||
template = {
|
||||
"datasets": [
|
||||
{"name": "${ds1.name}", "url": "${ds1.url}", "type": "dvcx"},
|
||||
{"name": "${ds1.name}", "url": "${ds1.url}", "type": "dc"},
|
||||
{
|
||||
"name": "${ds2.name}",
|
||||
"url": "${ds2.url}",
|
||||
@@ -138,7 +138,7 @@ def test_datasets(tmp_dir, dvc):
|
||||
|
||||
(tmp_dir / "params.yaml").dump(
|
||||
{
|
||||
"ds1": {"name": "dogs", "url": "dvcx://dogs"},
|
||||
"ds1": {"name": "dogs", "url": "dc://dogs"},
|
||||
"ds2": {
|
||||
"name": "example-get-started",
|
||||
"url": "[email protected]:iterative/example-get-started.git",
|
||||
@@ -153,7 +153,7 @@ def test_datasets(tmp_dir, dvc):
|
||||
|
||||
resolver = DataResolver(dvc, tmp_dir, template)
|
||||
assert resolver.resolve_datasets() == [
|
||||
{"name": "dogs", "url": "dvcx://dogs", "type": "dvcx"},
|
||||
{"name": "dogs", "url": "dc://dogs", "type": "dc"},
|
||||
{
|
||||
"name": "example-get-started",
|
||||
"url": "[email protected]:iterative/example-get-started.git",
|
||||
|
||||
@@ -205,11 +205,8 @@ def test_plots_show_nested_x_dict(tmp_dir, dvc, scm):
|
||||
|
||||
dvclive_metrics_dir.gen(
|
||||
{
|
||||
"Error.tsv": "step\tError\n" "0\t0.11\n" "1\t0.22\n" "2\t0.44\n",
|
||||
"Max_Leaf_Nodes.tsv": "step\tMax_Leaf_Nodes\n"
|
||||
"0\t5\n"
|
||||
"1\t50\n"
|
||||
"2\t500\n",
|
||||
"Error.tsv": "step\tError\n0\t0.11\n1\t0.22\n2\t0.44\n",
|
||||
"Max_Leaf_Nodes.tsv": "step\tMax_Leaf_Nodes\n0\t5\n1\t50\n2\t500\n",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -759,7 +759,7 @@ def test_repro_dry(tmp_dir, dvc, copy_script):
|
||||
|
||||
stages = dvc.reproduce(stage.addressing, dry=True)
|
||||
|
||||
assert len(stages), 2
|
||||
assert len(stages) == 2
|
||||
assert not filecmp.cmp("file1", "bar", shallow=False)
|
||||
|
||||
ret = main(["repro", "--dry", stage.addressing])
|
||||
|
||||
@@ -744,3 +744,12 @@ def test_checkout_dir_compat(tmp_dir, dvc):
|
||||
remove("data")
|
||||
dvc.checkout()
|
||||
assert (tmp_dir / "data").read_text() == {"foo": "foo"}
|
||||
|
||||
|
||||
def test_checkout_cleanup_properly_on_untracked_nested_directories(tmp_dir, scm, dvc):
|
||||
tmp_dir.dvc_gen({"datasets": {"dir1": {"file1": "file1"}}})
|
||||
tmp_dir.gen({"datasets": {"dir2": {"dir3": {"file2": "file2"}}}})
|
||||
|
||||
dvc.checkout(force=True)
|
||||
|
||||
assert (tmp_dir / "datasets").read_text() == {"dir1": {"file1": "file1"}}
|
||||
|
||||
+30
-30
@@ -8,13 +8,13 @@ from attrs import define, evolve, field, has
|
||||
from dvc.dependency.base import Dependency
|
||||
from dvc.exceptions import ReproductionError
|
||||
from dvc.repo.datasets import (
|
||||
DatachainDataset,
|
||||
DatachainDatasetLock,
|
||||
DatasetNotFoundError,
|
||||
DatasetSpec,
|
||||
DVCDataset,
|
||||
DVCDatasetLock,
|
||||
DVCDatasetSpec,
|
||||
DVCXDataset,
|
||||
DVCXDatasetLock,
|
||||
FileInfo,
|
||||
URLDataset,
|
||||
URLDatasetLock,
|
||||
@@ -27,7 +27,7 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
@define
|
||||
class MockedDVCXVersionInfo:
|
||||
class MockedDatachainVersionInfo:
|
||||
version: int
|
||||
created_at: datetime = field(factory=lambda: datetime.now(timezone.utc))
|
||||
|
||||
@@ -77,21 +77,21 @@ def test_dvc(tmp_dir, scm, dvc: "Repo"):
|
||||
assert old == new
|
||||
|
||||
|
||||
def test_dvcx(tmp_dir, dvc, mocker):
|
||||
def test_datachain(tmp_dir, dvc, mocker):
|
||||
datasets = dvc.datasets
|
||||
|
||||
version_info = [MockedDVCXVersionInfo(1), MockedDVCXVersionInfo(2)]
|
||||
version_info = [MockedDatachainVersionInfo(1), MockedDatachainVersionInfo(2)]
|
||||
version_info.append(version_info[1])
|
||||
mocker.patch("dvc.repo.datasets._get_dataset_info", side_effect=version_info)
|
||||
|
||||
dataset = datasets.add("mydataset", "dataset", "dvcx")
|
||||
expected = DVCXDataset(
|
||||
dataset = datasets.add("mydataset", "dataset", "dc")
|
||||
expected = DatachainDataset(
|
||||
manifest_path=(tmp_dir / "dvc.yaml").fs_path,
|
||||
spec=DatasetSpec(name="mydataset", url="dataset", type="dvcx"),
|
||||
lock=DVCXDatasetLock(
|
||||
spec=DatasetSpec(name="mydataset", url="dataset", type="dc"),
|
||||
lock=DatachainDatasetLock(
|
||||
name="mydataset",
|
||||
url="dataset",
|
||||
type="dvcx",
|
||||
type="dc",
|
||||
version=1,
|
||||
created_at=version_info[0].created_at,
|
||||
),
|
||||
@@ -189,16 +189,16 @@ def test_dvc_dump(tmp_dir, dvc):
|
||||
assert dvc.datasets["mydataset"] == dataset
|
||||
|
||||
|
||||
def test_dvcx_dump(tmp_dir, dvc):
|
||||
def test_datachain_dump(tmp_dir, dvc):
|
||||
manifest_path = os.path.join(tmp_dir, "dvc.yaml")
|
||||
spec = DatasetSpec(name="mydataset", url="dataset", type="dvcx")
|
||||
spec = DatasetSpec(name="mydataset", url="dataset", type="dc")
|
||||
dt = datetime.now(tz=timezone.utc)
|
||||
lock = DVCXDatasetLock(version=1, created_at=dt, **spec.to_dict())
|
||||
dataset = DVCXDataset(manifest_path=manifest_path, spec=spec, lock=lock)
|
||||
lock = DatachainDatasetLock(version=1, created_at=dt, **spec.to_dict())
|
||||
dataset = DatachainDataset(manifest_path=manifest_path, spec=spec, lock=lock)
|
||||
|
||||
dvc.datasets.dump(dataset)
|
||||
|
||||
spec_d = {"name": "mydataset", "type": "dvcx", "url": "dataset"}
|
||||
spec_d = {"name": "mydataset", "type": "dc", "url": "dataset"}
|
||||
assert (tmp_dir / "dvc.yaml").parse() == {"datasets": [spec_d]}
|
||||
assert (tmp_dir / "dvc.lock").parse() == {
|
||||
"schema": "2.0",
|
||||
@@ -244,10 +244,10 @@ def test_url_dump(tmp_dir, dvc):
|
||||
def test_invalidation(tmp_dir, dvc):
|
||||
manifest_path = os.path.join(tmp_dir, "dvc.yaml")
|
||||
spec = DatasetSpec(name="mydataset", url="url1", type="url")
|
||||
lock = DVCXDatasetLock(
|
||||
lock = DatachainDatasetLock(
|
||||
name="mydataset",
|
||||
url="dataset",
|
||||
type="dvcx",
|
||||
type="dc",
|
||||
version=1,
|
||||
created_at=datetime.now(tz=timezone.utc),
|
||||
)
|
||||
@@ -289,15 +289,15 @@ def test_dvc_dataset_pipeline(tmp_dir, dvc, scm):
|
||||
assert dvc.reproduce() == [stage]
|
||||
|
||||
|
||||
def test_dvcx_dataset_pipeline(mocker, tmp_dir, dvc):
|
||||
version_info = [MockedDVCXVersionInfo(1), MockedDVCXVersionInfo(2)]
|
||||
def test_datachain_dataset_pipeline(mocker, tmp_dir, dvc):
|
||||
version_info = [MockedDatachainVersionInfo(1), MockedDatachainVersionInfo(2)]
|
||||
mocker.patch("dvc.repo.datasets._get_dataset_info", side_effect=version_info)
|
||||
|
||||
dvc.datasets.add("mydataset", "dataset", "dvcx")
|
||||
dvc.datasets.add("mydataset", "dataset", "dc")
|
||||
|
||||
stage = dvc.stage.add(cmd="echo", name="train", deps=["ds://mydataset"])
|
||||
assert (tmp_dir / "dvc.yaml").parse() == {
|
||||
"datasets": [{"name": "mydataset", "url": "dataset", "type": "dvcx"}],
|
||||
"datasets": [{"name": "mydataset", "url": "dataset", "type": "dc"}],
|
||||
"stages": {"train": {"cmd": "echo", "deps": ["ds://mydataset"]}},
|
||||
}
|
||||
|
||||
@@ -360,10 +360,10 @@ def test_url_dataset_pipeline(mocker, tmp_dir, dvc):
|
||||
def test_pipeline_when_not_in_sync(tmp_dir, dvc):
|
||||
manifest_path = os.path.join(tmp_dir, "dvc.yaml")
|
||||
spec = DatasetSpec(name="mydataset", url="url1", type="url")
|
||||
lock = DVCXDatasetLock(
|
||||
lock = DatachainDatasetLock(
|
||||
name="mydataset",
|
||||
url="dataset",
|
||||
type="dvcx",
|
||||
type="dc",
|
||||
version=1,
|
||||
created_at=datetime.now(tz=timezone.utc),
|
||||
)
|
||||
@@ -382,9 +382,9 @@ def test_pipeline_when_not_in_sync(tmp_dir, dvc):
|
||||
def test_collect(tmp_dir, dvc):
|
||||
manifest_path1 = os.path.join(tmp_dir, "dvc.yaml")
|
||||
dt = datetime.now(tz=timezone.utc)
|
||||
spec = DatasetSpec(name="mydataset1", url="url1", type="dvcx")
|
||||
lock = DVCXDatasetLock(version=1, created_at=dt, **spec.to_dict())
|
||||
mydataset1 = DVCXDataset(manifest_path=manifest_path1, spec=spec, lock=lock)
|
||||
spec = DatasetSpec(name="mydataset1", url="url1", type="dc")
|
||||
lock = DatachainDatasetLock(version=1, created_at=dt, **spec.to_dict())
|
||||
mydataset1 = DatachainDataset(manifest_path=manifest_path1, spec=spec, lock=lock)
|
||||
dvc.datasets.dump(mydataset1)
|
||||
|
||||
(tmp_dir / "sub").mkdir()
|
||||
@@ -418,7 +418,7 @@ def test_parametrized(tmp_dir, dvc):
|
||||
(tmp_dir / "dvc.yaml").dump(
|
||||
{
|
||||
"datasets": [
|
||||
{"name": "${ds1.name}", "url": "${ds1.url}", "type": "dvcx"},
|
||||
{"name": "${ds1.name}", "url": "${ds1.url}", "type": "dc"},
|
||||
{
|
||||
"name": "${ds2.name}",
|
||||
"url": "${ds2.url}",
|
||||
@@ -435,7 +435,7 @@ def test_parametrized(tmp_dir, dvc):
|
||||
)
|
||||
(tmp_dir / "params.yaml").dump(
|
||||
{
|
||||
"ds1": {"name": "dogs", "url": "dvcx://dogs"},
|
||||
"ds1": {"name": "dogs", "url": "dc://dogs"},
|
||||
"ds2": {
|
||||
"name": "example-get-started",
|
||||
"url": "[email protected]:iterative/example-get-started.git",
|
||||
@@ -450,9 +450,9 @@ def test_parametrized(tmp_dir, dvc):
|
||||
|
||||
path = (tmp_dir / "dvc.yaml").fs_path
|
||||
assert dict(dvc.datasets.items()) == {
|
||||
"dogs": DVCXDataset(
|
||||
"dogs": DatachainDataset(
|
||||
manifest_path=path,
|
||||
spec=DatasetSpec(name="dogs", url="dvcx://dogs", type="dvcx"),
|
||||
spec=DatasetSpec(name="dogs", url="dc://dogs", type="dc"),
|
||||
),
|
||||
"example-get-started": DVCDataset(
|
||||
manifest_path=path,
|
||||
|
||||
@@ -12,7 +12,7 @@ from dvc.scm import Git
|
||||
from dvc.stage.exceptions import StagePathNotFoundError
|
||||
from dvc.testing.tmp_dir import make_subrepo
|
||||
from dvc.utils.fs import remove
|
||||
from dvc_data.hashfile import hash
|
||||
from dvc_data.hashfile import hash as _hash
|
||||
from dvc_data.index.index import DataIndex, DataIndexDirError
|
||||
|
||||
|
||||
@@ -747,7 +747,7 @@ def test_import_no_hash(
|
||||
with erepo_dir.chdir():
|
||||
erepo_dir.dvc_gen(files, commit="create foo")
|
||||
|
||||
file_md5_spy = mocker.spy(hash, "file_md5")
|
||||
file_md5_spy = mocker.spy(_hash, "file_md5")
|
||||
index_info_spy = mocker.spy(DataIndex, "info")
|
||||
name = next(iter(files))
|
||||
|
||||
|
||||
+63
-1
@@ -6,8 +6,9 @@ from os.path import join
|
||||
|
||||
import pytest
|
||||
|
||||
from dvc.fs import MemoryFileSystem
|
||||
from dvc.repo import Repo
|
||||
from dvc.repo.ls import ls_tree
|
||||
from dvc.repo.ls import _ls_tree, ls_tree
|
||||
from dvc.scm import CloneError
|
||||
|
||||
FS_STRUCTURE = {
|
||||
@@ -998,3 +999,64 @@ def test_ls_tree_maxdepth(M, tmp_dir, scm, dvc):
|
||||
"structure.xml.dvc": None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_fs_ls_tree():
|
||||
fs = MemoryFileSystem(global_store=False)
|
||||
fs.pipe({f: content.encode() for f, content in FS_STRUCTURE.items()})
|
||||
root = fs.root_marker
|
||||
|
||||
files = _ls_tree(fs, "README.md")
|
||||
assert _simplify_tree(files) == {"README.md": None}
|
||||
files = _ls_tree(fs, root)
|
||||
expected = {
|
||||
root: {
|
||||
".gitignore": None,
|
||||
"README.md": None,
|
||||
"model": {
|
||||
"script.py": None,
|
||||
"train.py": None,
|
||||
},
|
||||
}
|
||||
}
|
||||
assert _simplify_tree(files) == expected
|
||||
|
||||
files = _ls_tree(fs, "model")
|
||||
assert _simplify_tree(files) == {
|
||||
"model": {
|
||||
"script.py": None,
|
||||
"train.py": None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_fs_ls_tree_maxdepth():
|
||||
fs = MemoryFileSystem(global_store=False)
|
||||
fs.pipe({f: content.encode() for f, content in FS_STRUCTURE.items()})
|
||||
|
||||
files = _ls_tree(fs, "/", maxdepth=0)
|
||||
assert _simplify_tree(files) == {"/": None}
|
||||
|
||||
files = _ls_tree(fs, "/", maxdepth=1)
|
||||
assert _simplify_tree(files) == {
|
||||
"/": {
|
||||
".gitignore": None,
|
||||
"README.md": None,
|
||||
"model": None,
|
||||
}
|
||||
}
|
||||
|
||||
files = _ls_tree(fs, "/", maxdepth=2)
|
||||
assert _simplify_tree(files) == {
|
||||
"/": {
|
||||
".gitignore": None,
|
||||
"README.md": None,
|
||||
"model": {
|
||||
"script.py": None,
|
||||
"train.py": None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
files = _ls_tree(fs, "README.md", maxdepth=3)
|
||||
assert _simplify_tree(files) == {"README.md": None}
|
||||
|
||||
@@ -132,7 +132,8 @@ def test_merge_different_output_options(tmp_dir, dvc, caplog):
|
||||
)
|
||||
|
||||
(tmp_dir / "their").write_text(
|
||||
"outs:\n"
|
||||
r"outs:"
|
||||
"\n"
|
||||
"- md5: f987654321.dir\n"
|
||||
" hash: md5\n"
|
||||
" path: path\n"
|
||||
|
||||
@@ -115,6 +115,20 @@ def test_show_default(dvc, capsys):
|
||||
assert out == "foo\n"
|
||||
|
||||
|
||||
def test_list_shows_default(dvc, capsys):
|
||||
default_remote = "foo"
|
||||
other_remote = "bar"
|
||||
bucket_url = "s3://bucket/name"
|
||||
assert main(["remote", "add", default_remote, bucket_url]) == 0
|
||||
assert main(["remote", "add", other_remote, bucket_url]) == 0
|
||||
assert main(["remote", "default", default_remote]) == 0
|
||||
assert main(["remote", "list"]) == 0
|
||||
out, _ = capsys.readouterr()
|
||||
out_lines = out.splitlines()
|
||||
assert out_lines[0].split() == [default_remote, bucket_url, "(default)"]
|
||||
assert out_lines[1].split() == [other_remote, bucket_url]
|
||||
|
||||
|
||||
def test_upper_case_remote(tmp_dir, dvc, local_cloud):
|
||||
remote_name = "UPPERCASEREMOTE"
|
||||
|
||||
|
||||
@@ -114,13 +114,13 @@ def assert_index_equal(first, second, strict=True, ordered=True):
|
||||
assert len(first) == len(second), "Index have different no. of stages"
|
||||
assert set(first) == set(second), "Index does not have same stages"
|
||||
if ordered:
|
||||
assert list(first) == list(
|
||||
second
|
||||
), "Index does not have same sequence of stages"
|
||||
assert list(first) == list(second), (
|
||||
"Index does not have same sequence of stages"
|
||||
)
|
||||
if strict:
|
||||
assert set(map(id, first)) == set(
|
||||
map(id, second)
|
||||
), "Index is not strictly equal"
|
||||
assert set(map(id, first)) == set(map(id, second)), (
|
||||
"Index is not strictly equal"
|
||||
)
|
||||
|
||||
|
||||
def test_skip_graph_checks(dvc, mocker):
|
||||
|
||||
@@ -65,7 +65,7 @@ def test_get_unused_links(tmp_dir, dvc):
|
||||
assert set(dvc.state.get_unused_links(links, dvc.fs)) == set()
|
||||
assert set(
|
||||
dvc.state.get_unused_links(
|
||||
(links[:1] + [os.path.join(dvc.root_dir, "not-existing-file")]),
|
||||
([*links[:1], os.path.join(dvc.root_dir, "not-existing-file")]),
|
||||
dvc.fs,
|
||||
)
|
||||
) == {"bar"}
|
||||
|
||||
@@ -553,11 +553,8 @@ def test_nested_x_defn_collection(tmp_dir, dvc, scm, capsys):
|
||||
)
|
||||
dvclive_metrics_dir.gen(
|
||||
{
|
||||
"Error.tsv": "step\tError\n" "0\t0.11\n" "1\t0.22\n" "2\t0.44\n",
|
||||
"Max_Leaf_Nodes.tsv": "step\tMax_Leaf_Nodes\n"
|
||||
"0\t5\n"
|
||||
"1\t50\n"
|
||||
"2\t500\n",
|
||||
"Error.tsv": "step\tError\n0\t0.11\n1\t0.22\n2\t0.44\n",
|
||||
"Max_Leaf_Nodes.tsv": "step\tMax_Leaf_Nodes\n0\t5\n1\t50\n2\t500\n",
|
||||
}
|
||||
)
|
||||
(other_logger_dir / "multiple_metrics.json").dump(
|
||||
|
||||
@@ -14,9 +14,9 @@ from dvc.cli import main
|
||||
"Adding ds (url:/path @ main)\n",
|
||||
),
|
||||
(
|
||||
{"name": "mydataset", "url": "dvcx://dataset", "type": "dvcx"},
|
||||
{"name": "mydataset", "url": "dc://dataset", "type": "dc"},
|
||||
{"version": 1, "created_at": datetime.now(tz=timezone.utc)},
|
||||
"Adding mydataset (dvcx://dataset @ v1)\n",
|
||||
"Adding mydataset (dc://dataset @ v1)\n",
|
||||
),
|
||||
(
|
||||
{"name": "mydataset", "url": "s3://bucket/path", "type": "url"},
|
||||
@@ -45,7 +45,7 @@ def test_add_already_exists(dvc, caplog, mocker):
|
||||
dataset = dvc.datasets._build_dataset("dvc.yaml", spec, None)
|
||||
mocker.patch("dvc.repo.datasets.Datasets.get", return_value=dataset)
|
||||
|
||||
assert main(["dataset", "add", "ds", "--dvcx", "dataset"]) == 255
|
||||
assert main(["dataset", "add", "ds", "--dc", "dataset"]) == 255
|
||||
assert "ds already exists in dvc.yaml, use the --force to overwrite" in caplog.text
|
||||
|
||||
|
||||
@@ -64,21 +64,21 @@ def test_add_already_exists(dvc, caplog, mocker):
|
||||
},
|
||||
),
|
||||
(
|
||||
{"name": "mydataset", "url": "dvcx://dataset", "type": "dvcx"},
|
||||
{"name": "mydataset", "url": "dc://dataset", "type": "dc"},
|
||||
{"version": 1, "created_at": datetime.now(tz=timezone.utc)},
|
||||
{"version": 2},
|
||||
{
|
||||
"missing": "Updating mydataset (dvcx://dataset @ v2)\n",
|
||||
"missing": "Updating mydataset (dc://dataset @ v2)\n",
|
||||
"unchanged": "Nothing to update\n",
|
||||
"updated": "Updating mydataset (v1 -> v2)\n",
|
||||
},
|
||||
),
|
||||
(
|
||||
{"name": "mydataset", "url": "dvcx://dataset", "type": "dvcx"},
|
||||
{"name": "mydataset", "url": "dc://dataset", "type": "dc"},
|
||||
{"version": 2, "created_at": datetime.now(tz=timezone.utc)},
|
||||
{"version": 1},
|
||||
{
|
||||
"missing": "Updating mydataset (dvcx://dataset @ v1)\n",
|
||||
"missing": "Updating mydataset (dc://dataset @ v1)\n",
|
||||
"unchanged": "Nothing to update\n",
|
||||
"updated": "Downgrading mydataset (v2 -> v1)\n",
|
||||
},
|
||||
@@ -106,7 +106,8 @@ def test_add_already_exists(dvc, caplog, mocker):
|
||||
"missing": "Updating mydataset (s3://bucket/path)\n",
|
||||
"unchanged": "Nothing to update\n",
|
||||
"updated": (
|
||||
"Updating mydataset (s3://bucket/path)\n"
|
||||
r"Updating mydataset (s3://bucket/path)"
|
||||
"\n"
|
||||
"M\tbaz\n"
|
||||
"A\tfoobar\n"
|
||||
"D\tfoo\n"
|
||||
|
||||
@@ -384,6 +384,7 @@ def test_experiments_remove_flag(dvc, scm, mocker, capsys, caplog):
|
||||
num=2,
|
||||
queue=False,
|
||||
git_remote="myremote",
|
||||
keep=False,
|
||||
)
|
||||
|
||||
|
||||
@@ -410,6 +411,7 @@ def test_experiments_remove_special(dvc, scm, mocker, capsys, caplog):
|
||||
num=1,
|
||||
queue=False,
|
||||
git_remote="myremote",
|
||||
keep=False,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from dvc.cli import parse_args
|
||||
from dvc.commands.ls_url import CmdListUrl
|
||||
from dvc.config import Config
|
||||
from dvc.fs import LocalFileSystem
|
||||
|
||||
|
||||
def test_ls_url(mocker, M):
|
||||
@@ -12,12 +13,16 @@ def test_ls_url(mocker, M):
|
||||
assert cmd.run() == 0
|
||||
|
||||
m.assert_called_once_with(
|
||||
"src", recursive=False, fs_config=None, config=M.instance_of(Config)
|
||||
"src",
|
||||
recursive=False,
|
||||
maxdepth=None,
|
||||
fs_config=None,
|
||||
config=M.instance_of(Config),
|
||||
)
|
||||
|
||||
|
||||
def test_recursive(mocker, M):
|
||||
cli_args = parse_args(["ls-url", "-R", "src"])
|
||||
cli_args = parse_args(["ls-url", "-R", "-L", "2", "src"])
|
||||
assert cli_args.func == CmdListUrl
|
||||
cmd = cli_args.func(cli_args)
|
||||
m = mocker.patch("dvc.repo.Repo.ls_url", autospec=True)
|
||||
@@ -25,5 +30,16 @@ def test_recursive(mocker, M):
|
||||
assert cmd.run() == 0
|
||||
|
||||
m.assert_called_once_with(
|
||||
"src", recursive=True, fs_config=None, config=M.instance_of(Config)
|
||||
"src", recursive=True, maxdepth=2, fs_config=None, config=M.instance_of(Config)
|
||||
)
|
||||
|
||||
|
||||
def test_tree(mocker, M):
|
||||
cli_args = parse_args(["ls-url", "--tree", "--level", "2", "src"])
|
||||
assert cli_args.func == CmdListUrl
|
||||
cmd = cli_args.func(cli_args)
|
||||
m = mocker.patch("dvc.repo.ls._ls_tree", autospec=True)
|
||||
|
||||
assert cmd.run() == 0
|
||||
|
||||
m.assert_called_once_with(M.instance_of(LocalFileSystem), "src", maxdepth=2)
|
||||
|
||||
@@ -123,6 +123,6 @@ def test_info_on_subrepos(make_tmp_dir, tmp_dir, dvc, scm, dvcfs):
|
||||
|
||||
for path in ["subrepo", "subrepo/foo", "subrepo/foobar"]:
|
||||
info = dvcfs.info(path)
|
||||
assert info["repo"].root_dir == str(
|
||||
subrepo
|
||||
), f"repo root didn't match for {path}"
|
||||
assert info["repo"].root_dir == str(subrepo), (
|
||||
f"repo root didn't match for {path}"
|
||||
)
|
||||
|
||||
@@ -99,7 +99,7 @@ def test_load_from_pipeline_error_on_typ(dvc, typ):
|
||||
output.load_from_pipeline(Stage(dvc), ["file1"], typ)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("key", [3, "list".split()])
|
||||
@pytest.mark.parametrize("key", [3, ["list"]])
|
||||
def test_load_from_pipeline_illegal_type(dvc, key):
|
||||
stage = Stage(dvc)
|
||||
with pytest.raises(ValueError, match=f"'{type(key).__name__}' not supported."):
|
||||
|
||||
@@ -130,6 +130,5 @@ def test_feature_section_supports_arbitrary_values(caplog):
|
||||
assert "random_key_1" not in data
|
||||
assert "random_key_2" not in data
|
||||
assert (
|
||||
"'feature.random_key_1', 'feature.random_key_2' "
|
||||
"config options are unsupported"
|
||||
"'feature.random_key_1', 'feature.random_key_2' config options are unsupported"
|
||||
) in caplog.text
|
||||
|
||||
@@ -60,7 +60,7 @@ def test_context_list():
|
||||
_ = context["lst"][3]
|
||||
|
||||
context["lst"].insert(0, "baz")
|
||||
assert context["lst"] == CtxList(["baz"] + lst[:2])
|
||||
assert context["lst"] == CtxList(["baz", *lst[:2]])
|
||||
|
||||
|
||||
def test_context_setitem_getitem():
|
||||
|
||||
+10
-1
@@ -1,4 +1,8 @@
|
||||
from dvc.dirs import global_config_dir
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from dvc.dirs import global_config_dir, site_cache_dir
|
||||
from dvc.env import DVC_GLOBAL_CONFIG_DIR
|
||||
|
||||
|
||||
@@ -6,3 +10,8 @@ def test_global_config_dir_respects_env_var(monkeypatch):
|
||||
path = "/some/random/path"
|
||||
monkeypatch.setenv(DVC_GLOBAL_CONFIG_DIR, path)
|
||||
assert global_config_dir() == path
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform != "linux", reason="Only for Unix platforms")
|
||||
def test_site_cache_dir_on_unix():
|
||||
assert site_cache_dir() == "/var/tmp/dvc"
|
||||
|
||||
@@ -168,8 +168,9 @@ def test_match_ignore_from_file(
|
||||
):
|
||||
from dvc.fs import localfs
|
||||
|
||||
root = r"\\" if os.name == "nt" else "/"
|
||||
dvcignore_path = os.path.join(
|
||||
os.path.sep, "full", "path", "to", "ignore", "file", ".dvcignore"
|
||||
root, "full", "path", "to", "ignore", "file", ".dvcignore"
|
||||
)
|
||||
dvcignore_dirname = os.path.dirname(dvcignore_path)
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import logging
|
||||
import time
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
|
||||
import colorama
|
||||
import pytest
|
||||
@@ -22,11 +20,9 @@ colors = {
|
||||
|
||||
@pytest.fixture
|
||||
def dt(mocker):
|
||||
mocker.patch(
|
||||
"time.time",
|
||||
return_value=time.mktime(datetime(2020, 2, 2).timetuple()), # noqa: DTZ001
|
||||
)
|
||||
return "2020-02-02 00:00:00,000"
|
||||
dt_str = "2020-02-02 00:00:00,000"
|
||||
mocker.patch.object(formatter, "formatTime", return_value=dt_str)
|
||||
return dt_str
|
||||
|
||||
|
||||
class TestColorFormatter:
|
||||
@@ -104,9 +100,7 @@ class TestColorFormatter:
|
||||
logger.exception("")
|
||||
|
||||
expected = (
|
||||
"{red}{datetime}{nc} "
|
||||
"{red}ERROR{nc}: description\n"
|
||||
"{stack_trace}".format(
|
||||
"{red}{datetime}{nc} {red}ERROR{nc}: description\n{stack_trace}".format(
|
||||
stack_trace=stack_trace,
|
||||
**colors,
|
||||
datetime=dt,
|
||||
@@ -144,9 +138,7 @@ class TestColorFormatter:
|
||||
logger.exception("something", extra={"tb_only": True})
|
||||
|
||||
expected = (
|
||||
"{red}{datetime}{nc} "
|
||||
"{red}ERROR{nc}: something\n"
|
||||
"{stack_trace}".format(
|
||||
"{red}{datetime}{nc} {red}ERROR{nc}: something\n{stack_trace}".format(
|
||||
stack_trace=stack_trace,
|
||||
**colors,
|
||||
datetime=dt,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from pydoc import plainpager
|
||||
|
||||
import pytest
|
||||
|
||||
from dvc.env import DVC_PAGER
|
||||
@@ -70,8 +72,8 @@ def test_dvc_sets_some_options_on_less_if_less_env_defined(mocker, monkeypatch,
|
||||
assert find_pager() == "less --RAW-CONTROL-CHARS --chop-long-lines"
|
||||
|
||||
|
||||
def test_make_pager_when_no_pager_found(mocker, monkeypatch):
|
||||
assert make_pager(None).__name__ == "plainpager"
|
||||
def test_make_pager_when_no_pager_found():
|
||||
assert make_pager(None) is plainpager
|
||||
|
||||
|
||||
def test_pager(mocker, monkeypatch):
|
||||
|
||||
Reference in New Issue
Block a user