mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-03 07:18:26 +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 |
@@ -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@v4
|
||||
- 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@v4
|
||||
- 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
|
||||
|
||||
@@ -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@v4
|
||||
- uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
enable-cache: true
|
||||
cache-dependency-glob: "**/pyproject.toml"
|
||||
|
||||
@@ -32,8 +32,8 @@ jobs:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- uses: astral-sh/setup-uv@v4
|
||||
python-version: '3.13'
|
||||
- uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
enable-cache: true
|
||||
cache-dependency-glob: pyproject.toml
|
||||
@@ -55,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"
|
||||
@@ -69,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"
|
||||
@@ -81,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:
|
||||
@@ -89,7 +125,7 @@ jobs:
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.pyv }}
|
||||
- uses: astral-sh/setup-uv@v4
|
||||
- uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
enable-cache: true
|
||||
cache-dependency-glob: pyproject.toml
|
||||
@@ -124,7 +160,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Slack Notification
|
||||
uses: rtCamp/[email protected].2
|
||||
uses: rtCamp/[email protected].3
|
||||
env:
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||
SLACK_COLOR: ${{ job.status }}
|
||||
|
||||
@@ -22,13 +22,13 @@ repos:
|
||||
- id: sort-simple-yaml
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: 'v0.8.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"]
|
||||
|
||||
+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
-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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
+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
|
||||
|
||||
|
||||
+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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
+20
-11
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
+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(
|
||||
|
||||
+9
-8
@@ -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"]
|
||||
|
||||
@@ -83,7 +85,7 @@ gdrive = ["dvc-gdrive>=3,<4"]
|
||||
gs = ["dvc-gs>=3,<4"]
|
||||
hdfs = ["dvc-hdfs>=3,<4"]
|
||||
lint = [
|
||||
"mypy==1.13.0",
|
||||
"mypy==1.15.0",
|
||||
"pandas-stubs",
|
||||
"types-colorama",
|
||||
"types-psutil",
|
||||
@@ -148,9 +150,6 @@ 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
|
||||
@@ -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", "TC", "ARG", "PGH", "PLC", "PLE", "PLR", "PLW", "TRY",
|
||||
"FLY", "PERF", "FURB", "RUF", "RUF027", "RUF029", "RUF034", "RUF036",
|
||||
"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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"}}
|
||||
|
||||
+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(
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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