mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-02 15:28:30 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e1b75feb5 | ||
|
|
4ec6d64629 | ||
|
|
a86470ccbc | ||
|
|
5d2701341b | ||
|
|
97589a1358 | ||
|
|
9e72ce53cd | ||
|
|
e34a6d3b30 | ||
|
|
25dcc33580 | ||
|
|
18fa1a0272 | ||
|
|
49a27f4140 | ||
|
|
9b2aa79c18 | ||
|
|
24023c6977 | ||
|
|
5310bcdcec | ||
|
|
ae92154cec | ||
|
|
f661cee8f6 | ||
|
|
e07c85a993 | ||
|
|
58d9e379fe | ||
|
|
524162dc3f | ||
|
|
aa15b21dce | ||
|
|
0404967591 | ||
|
|
465d886c29 | ||
|
|
459adfb40f | ||
|
|
fe9f8654a3 | ||
|
|
8208846003 | ||
|
|
73aea2d972 | ||
|
|
10184e9370 | ||
|
|
2690f05cee | ||
|
|
34c2c95115 | ||
|
|
5c6c1dc719 | ||
|
|
98bcfad8e7 | ||
|
|
7ab4c59d70 | ||
|
|
a137b2a6bc | ||
|
|
6f0e8a8db5 | ||
|
|
d294945499 | ||
|
|
db70d1cc5c | ||
|
|
ed6e11e856 | ||
|
|
c1cd13c34f | ||
|
|
12dd746165 | ||
|
|
5165fb67a1 | ||
|
|
b2c1c4dccd | ||
|
|
b060cadbf1 | ||
|
|
7c55a9c7cd | ||
|
|
037b5b2e0a | ||
|
|
99acb8e335 | ||
|
|
b46cfbda5d | ||
|
|
a8d193bf2a | ||
|
|
0b1cf6c771 | ||
|
|
b3aa698cd4 | ||
|
|
bb9a3a0403 | ||
|
|
3427e115eb | ||
|
|
3d5f4dc66c | ||
|
|
80633f4fad | ||
|
|
8bda3650c5 | ||
|
|
ea2e5dc3e5 | ||
|
|
61afdca3a6 | ||
|
|
6a6bc7035a | ||
|
|
ccc97622fa | ||
|
|
916b5d169f | ||
|
|
38e9b2ba95 | ||
|
|
18f039d7c7 | ||
|
|
f1c9e9365b | ||
|
|
b50d1ee674 | ||
|
|
1c33024b2c | ||
|
|
dd2d2dce19 | ||
|
|
5f558d003a | ||
|
|
3676f35b7c | ||
|
|
9af6fcad4a | ||
|
|
6d80b72434 | ||
|
|
46d9290fc2 | ||
|
|
c47c2123fb | ||
|
|
d25d26ff74 | ||
|
|
c2e4f51479 | ||
|
|
977a1f9f43 | ||
|
|
608269dc88 | ||
|
|
e48d0d1d9c | ||
|
|
bf08bd3f12 | ||
|
|
db5dfdd6bc | ||
|
|
3b10f40ca3 | ||
|
|
4660443d0e | ||
|
|
a92ace0054 | ||
|
|
309ae4d995 | ||
|
|
e1254c9ece | ||
|
|
7a65ac06b2 | ||
|
|
b0052aa666 | ||
|
|
da27174e02 | ||
|
|
aabdddc2b7 | ||
|
|
fc1a192c90 | ||
|
|
178244f666 | ||
|
|
dba7d3c320 | ||
|
|
8b075eb754 | ||
|
|
f507e4d9c0 | ||
|
|
5169205229 | ||
|
|
3bcd683677 | ||
|
|
30ec1cf5e9 | ||
|
|
a814f04ee0 | ||
|
|
90a27179eb | ||
|
|
175c578579 | ||
|
|
5441964fdb | ||
|
|
d4bfe38a54 | ||
|
|
a060049186 | ||
|
|
c02285ab49 |
@@ -1,12 +1,19 @@
|
||||
[flake8]
|
||||
ignore=
|
||||
E203, # Whitespace before ':'
|
||||
E266, # Too many leading '#' for block comment
|
||||
W503, # Line break occurred before a binary operator
|
||||
P1, # unindexed parameters in the str.format, see:
|
||||
# Whitespace before ':'
|
||||
E203
|
||||
# Too many leading '#' for block comment
|
||||
E266
|
||||
# Line break occurred before a binary operator
|
||||
W503
|
||||
# unindexed parameters in the str.format, see:
|
||||
# https://pypi.org/project/flake8-string-format/
|
||||
max_line_length = 79
|
||||
max-complexity = 15
|
||||
select = B,C,E,F,W,T4,B902,T,P
|
||||
P1
|
||||
# use `!r` instead of surrounding by quotes
|
||||
B028
|
||||
max_line_length = 88
|
||||
max-complexity = 10
|
||||
select = B,C,E,F,W,T4,B902,T,P,TYP
|
||||
show_source = true
|
||||
count = true
|
||||
min_python_version = 3.8.0
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# The following commits will be ignored by the GitHub blame view.
|
||||
# You can configure `git blame` to use this file as the default ignore file:
|
||||
# git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||
# or, you can also manually ignore commits from this file by doing:
|
||||
# git blame --ignore-revs-file .git-blame-ignore-revs <filepath>
|
||||
# see the `blame.markIgnoredLines` and `blame.markUnblamableLines` options as well.
|
||||
# Refer to: https://git-scm.com/docs/git-blame#Documentation/git-blame.txt---ignore-revs-fileltfilegt
|
||||
|
||||
# update black to 2023 stable style and change line-length to 88
|
||||
a86470ccbc693d6f0a4f8066cbd1357e5191c4ff
|
||||
@@ -0,0 +1,54 @@
|
||||
name: Remote Plugin Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
timeout-minutes: 45
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04]
|
||||
pyv: ["3.10"]
|
||||
plugin: ["dvc-s3"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
path: dvc
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: iterative/${{ matrix.plugin }}
|
||||
ref: main
|
||||
path: ${{ matrix.plugin }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.pyv }}
|
||||
cache: pip
|
||||
cache-dependency-path: |
|
||||
dvc/pyproject.toml
|
||||
${{ matrix.plugin }}/setup.cfg
|
||||
|
||||
- name: Install plugin + DVC@PR
|
||||
run: |
|
||||
pip install --upgrade pip wheel
|
||||
pip install "./dvc[testing]"
|
||||
pip install -e "./${{ matrix.plugin }}[tests]"
|
||||
|
||||
- name: Run plugin tests
|
||||
timeout-minutes: 15
|
||||
working-directory: ${{ matrix.plugin }}
|
||||
run: pytest -v -n=auto
|
||||
@@ -12,44 +12,18 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: # added using https://github.com/step-security/secure-workflows
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.8
|
||||
cache: pip
|
||||
cache-dependency-path: pyproject.toml
|
||||
- name: Install requirements
|
||||
run: |
|
||||
pip install --upgrade pip wheel
|
||||
pip install ".[dev]"
|
||||
- uses: pre-commit/[email protected]
|
||||
tests:
|
||||
timeout-minutes: 45
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, windows-latest]
|
||||
os: [ubuntu-20.04, windows-latest, macos-latest]
|
||||
pyv: ["3.8", "3.9", "3.10", "3.11"]
|
||||
pytest-filter:
|
||||
- "import or plot or live or experiment"
|
||||
- "not (import or plot or live or experiment)"
|
||||
include:
|
||||
- {os: macos-latest, pyv: "3.8"}
|
||||
- {os: macos-latest, pyv: "3.9"}
|
||||
- {os: macos-latest, pyv: "3.10"}
|
||||
- {os: macos-latest, pyv: "3.11"}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -65,12 +39,12 @@ jobs:
|
||||
run: |
|
||||
pip install --upgrade pip wheel
|
||||
pip install -e ".[dev]"
|
||||
- uses: pre-commit/[email protected]
|
||||
- name: run tests
|
||||
timeout-minutes: 30
|
||||
run: >
|
||||
pytest -nauto --timeout=300 --durations=100
|
||||
--cov --cov-report=xml --cov-report=term
|
||||
-k "${{ matrix.pytest-filter }}"
|
||||
- name: upload coverage report
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
@@ -78,7 +52,7 @@ jobs:
|
||||
fail_ci_if_error: false
|
||||
check:
|
||||
if: always()
|
||||
needs: [lint, tests]
|
||||
needs: [tests]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: re-actors/alls-green@release/v1
|
||||
@@ -86,7 +60,7 @@ jobs:
|
||||
jobs: ${{ toJSON(needs) }}
|
||||
notify:
|
||||
if: github.ref == 'refs/heads/main' && failure()
|
||||
needs: [lint, tests]
|
||||
needs: [tests]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Slack Notification
|
||||
|
||||
+13
-6
@@ -6,7 +6,7 @@ ci:
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.3.0
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-case-conflict
|
||||
@@ -29,34 +29,38 @@ repos:
|
||||
- id: beautysh
|
||||
args: ["-i", "2"]
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.2.0
|
||||
rev: v3.3.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args:
|
||||
- --py38-plus
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.10.0
|
||||
rev: 23.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.10.1
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: 'v0.0.238'
|
||||
hooks:
|
||||
- id: ruff
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.2
|
||||
hooks:
|
||||
- id: codespell
|
||||
additional_dependencies: ["tomli"]
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 5.0.4
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
- flake8-broken-line
|
||||
- flake8-bugbear
|
||||
- flake8-comprehensions
|
||||
- flake8-debugger
|
||||
- flake8-string-format
|
||||
- flake8-typing-imports
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: mypy
|
||||
@@ -65,11 +69,14 @@ repos:
|
||||
files: ^dvc/
|
||||
language: system
|
||||
types: [python]
|
||||
require_serial: true
|
||||
- id: pylint
|
||||
name: pylint
|
||||
entry: pylint
|
||||
files: ^dvc/
|
||||
language: system
|
||||
types: [python]
|
||||
require_serial: true
|
||||
- hooks:
|
||||
- args:
|
||||
- git-hook
|
||||
|
||||
+34
-25
@@ -8,15 +8,19 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
@contextmanager
|
||||
def viztracer_profile(path: Union[Callable[[], str], str], depth: int = -1):
|
||||
def viztracer_profile(
|
||||
path: Union[Callable[[], str], str],
|
||||
depth: int = -1,
|
||||
log_async: bool = False,
|
||||
):
|
||||
try:
|
||||
import viztracer # pylint: disable=import-error
|
||||
except ImportError:
|
||||
print("Failed to run profiler, viztracer is not installed")
|
||||
print("Failed to run profiler, viztracer is not installed") # noqa: T201
|
||||
yield
|
||||
return
|
||||
|
||||
tracer = viztracer.VizTracer(max_stack_depth=depth)
|
||||
tracer = viztracer.VizTracer(max_stack_depth=depth, log_async=log_async)
|
||||
|
||||
tracer.start()
|
||||
yield
|
||||
@@ -27,14 +31,14 @@ def viztracer_profile(path: Union[Callable[[], str], str], depth: int = -1):
|
||||
|
||||
@contextmanager
|
||||
def yappi_profile(
|
||||
path: Union[Callable[[], str], str] = None,
|
||||
path: Optional[Union[Callable[[], str], str]] = None,
|
||||
wall_clock: Optional[bool] = True,
|
||||
separate_threads: Optional[bool] = False,
|
||||
):
|
||||
try:
|
||||
import yappi # pylint: disable=import-error
|
||||
except ImportError:
|
||||
print("Failed to run profiler, yappi is not installed")
|
||||
print("Failed to run profiler, yappi is not installed") # noqa: T201
|
||||
yield
|
||||
return
|
||||
|
||||
@@ -60,7 +64,7 @@ def yappi_profile(
|
||||
st.save(out, type="callgrind")
|
||||
else:
|
||||
if ctx_id is not None:
|
||||
print(f"\nThread {ctx_id}")
|
||||
print(f"\nThread {ctx_id}") # noqa: T201
|
||||
st.print_all() # pylint:disable=no-member
|
||||
if ctx_id is None:
|
||||
threads.print_all() # pylint:disable=no-member
|
||||
@@ -74,7 +78,7 @@ def instrument(html_output=False):
|
||||
try:
|
||||
from pyinstrument import Profiler # pylint: disable=import-error
|
||||
except ImportError:
|
||||
print("Failed to run profiler, pyinstrument is not installed")
|
||||
print("Failed to run profiler, pyinstrument is not installed") # noqa: T201
|
||||
yield
|
||||
return
|
||||
|
||||
@@ -87,11 +91,11 @@ def instrument(html_output=False):
|
||||
if html_output:
|
||||
profiler.open_in_browser()
|
||||
return
|
||||
print(profiler.output_text(unicode=True, color=True))
|
||||
print(profiler.output_text(unicode=True, color=True)) # noqa: T201
|
||||
|
||||
|
||||
@contextmanager
|
||||
def profile(dump_path: str = None):
|
||||
def profile(dump_path: Optional[str] = None):
|
||||
"""Run a cprofile"""
|
||||
import cProfile
|
||||
|
||||
@@ -115,28 +119,31 @@ def debug():
|
||||
try:
|
||||
import ipdb as pdb # noqa: T100, pylint: disable=import-error
|
||||
except ImportError:
|
||||
import pdb # noqa: T100
|
||||
import pdb # type: ignore[no-redef] # noqa: T100
|
||||
pdb.post_mortem()
|
||||
|
||||
raise # prevent from jumping ahead
|
||||
|
||||
|
||||
def _sigshow(_, frame: Optional["FrameType"]) -> None:
|
||||
import sys
|
||||
from shutil import get_terminal_size
|
||||
from traceback import format_stack
|
||||
|
||||
from dvc.ui import ui
|
||||
|
||||
lines = "\u2015" * get_terminal_size().columns
|
||||
ui.error_write(lines, "\n", *format_stack(frame), lines, sep="")
|
||||
stack = format_stack(frame)
|
||||
print(lines, "\n", *stack, lines, sep="", file=sys.stderr) # noqa: T201
|
||||
|
||||
|
||||
@contextmanager
|
||||
def show_stack():
|
||||
r"""Show stack trace on SIGQUIT (Ctrl-\) or SIGINFO (Ctrl-T on macOS)."""
|
||||
import signal
|
||||
import sys
|
||||
|
||||
if sys.platform != "win32":
|
||||
signal.signal(signal.SIGQUIT, _sigshow)
|
||||
|
||||
signal.signal(signal.SIGQUIT, _sigshow)
|
||||
try:
|
||||
signal.signal(signal.SIGINFO, _sigshow) # only available on macOS
|
||||
except AttributeError:
|
||||
@@ -154,7 +161,7 @@ def _get_path_func(tool: str, ext: str):
|
||||
|
||||
|
||||
@contextmanager
|
||||
def debugtools(args: "Namespace" = None, **kwargs):
|
||||
def debugtools(args: Optional["Namespace"] = None, **kwargs):
|
||||
kw = vars(args) if args else {}
|
||||
kw.update(kwargs)
|
||||
|
||||
@@ -175,10 +182,15 @@ def debugtools(args: "Namespace" = None, **kwargs):
|
||||
separate_threads=kw.get("yappi_separate_threads"),
|
||||
)
|
||||
)
|
||||
if kw.get("viztracer") or kw.get("viztracer_depth"):
|
||||
if (
|
||||
kw.get("viztracer")
|
||||
or kw.get("viztracer_depth")
|
||||
or kw.get("viztracer_async")
|
||||
):
|
||||
path_func = _get_path_func("viztracer", "json")
|
||||
depth = kw.get("viztracer_depth") or -1
|
||||
prof = viztracer_profile(path=path_func, depth=depth)
|
||||
log_async = kw.get("viztracer_async") or False
|
||||
prof = viztracer_profile(path=path_func, depth=depth, log_async=log_async)
|
||||
stack.enter_context(prof)
|
||||
yield
|
||||
|
||||
@@ -186,12 +198,8 @@ def debugtools(args: "Namespace" = None, **kwargs):
|
||||
def add_debugging_flags(parser):
|
||||
from argparse import SUPPRESS
|
||||
|
||||
parser.add_argument(
|
||||
"--cprofile", action="store_true", default=False, help=SUPPRESS
|
||||
)
|
||||
parser.add_argument(
|
||||
"--yappi", action="store_true", default=False, help=SUPPRESS
|
||||
)
|
||||
parser.add_argument("--cprofile", action="store_true", default=False, help=SUPPRESS)
|
||||
parser.add_argument("--yappi", action="store_true", default=False, help=SUPPRESS)
|
||||
parser.add_argument(
|
||||
"--yappi-separate-threads",
|
||||
action="store_true",
|
||||
@@ -202,10 +210,11 @@ def add_debugging_flags(parser):
|
||||
"--viztracer", action="store_true", default=False, help=SUPPRESS
|
||||
)
|
||||
parser.add_argument("--viztracer-depth", type=int, help=SUPPRESS)
|
||||
parser.add_argument("--cprofile-dump", help=SUPPRESS)
|
||||
parser.add_argument(
|
||||
"--pdb", action="store_true", default=False, help=SUPPRESS
|
||||
"--viztracer-async", action="store_true", default=False, help=SUPPRESS
|
||||
)
|
||||
parser.add_argument("--cprofile-dump", help=SUPPRESS)
|
||||
parser.add_argument("--pdb", action="store_true", default=False, help=SUPPRESS)
|
||||
parser.add_argument(
|
||||
"--instrument", action="store_true", default=False, help=SUPPRESS
|
||||
)
|
||||
|
||||
+11
-5
@@ -82,10 +82,9 @@ def send(path):
|
||||
|
||||
|
||||
def _scm_in_use():
|
||||
from scmrepo.noscm import NoSCM
|
||||
|
||||
from dvc.exceptions import NotDvcRepoError
|
||||
from dvc.repo import Repo
|
||||
from dvc.scm import NoSCM
|
||||
|
||||
from .scm import SCM, SCMError
|
||||
|
||||
@@ -102,17 +101,24 @@ def _runtime_info():
|
||||
"""
|
||||
Gather information from the environment where DVC runs to fill a report.
|
||||
"""
|
||||
from iterative_telemetry import find_or_create_user_id
|
||||
from iterative_telemetry import _generate_ci_id, find_or_create_user_id
|
||||
|
||||
from dvc import __version__
|
||||
from dvc.utils import is_binary
|
||||
|
||||
ci_id = _generate_ci_id()
|
||||
if ci_id:
|
||||
group_id, user_id = ci_id
|
||||
else:
|
||||
group_id, user_id = None, find_or_create_user_id()
|
||||
|
||||
return {
|
||||
"dvc_version": __version__,
|
||||
"is_binary": is_binary(),
|
||||
"scm_class": _scm_in_use(),
|
||||
"system_info": _system_info(),
|
||||
"user_id": find_or_create_user_id(),
|
||||
"user_id": user_id,
|
||||
"group_id": group_id,
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +131,7 @@ def _system_info():
|
||||
system = platform.system()
|
||||
|
||||
if system == "Windows":
|
||||
version = sys.getwindowsversion()
|
||||
version = sys.getwindowsversion() # type: ignore[attr-defined]
|
||||
|
||||
return {
|
||||
"os": "windows",
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ class Annotation:
|
||||
PARAM_META: ClassVar[str] = "meta"
|
||||
|
||||
desc: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
type: Optional[str] = None # noqa: A003
|
||||
labels: List[str] = field(default_factory=list)
|
||||
meta: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
+8
-8
@@ -27,23 +27,23 @@ def get_url(path, repo=None, rev=None, remote=None):
|
||||
if not dvc_info:
|
||||
raise OutputNotFoundError(path, repo)
|
||||
|
||||
dvc_repo = info["repo"]
|
||||
dvc_repo = info["repo"] # pylint: disable=unsubscriptable-object
|
||||
md5 = dvc_info["md5"]
|
||||
|
||||
return dvc_repo.cloud.get_url_for(remote, checksum=md5)
|
||||
|
||||
|
||||
class _OpenContextManager(GCM):
|
||||
def __init__(
|
||||
self, func, args, kwds
|
||||
): # pylint: disable=super-init-not-called
|
||||
def __init__(self, func, args, kwds): # pylint: disable=super-init-not-called
|
||||
self.gen = func(*args, **kwds)
|
||||
self.func, self.args, self.kwds = func, args, kwds
|
||||
self.func, self.args, self.kwds = ( # type: ignore[assignment]
|
||||
func,
|
||||
args,
|
||||
kwds,
|
||||
)
|
||||
|
||||
def __getattr__(self, name):
|
||||
raise AttributeError(
|
||||
"dvc.api.open() should be used in a with statement."
|
||||
)
|
||||
raise AttributeError("dvc.api.open() should be used in a with statement.")
|
||||
|
||||
|
||||
def open( # noqa, pylint: disable=redefined-builtin
|
||||
|
||||
+4
-3
@@ -1,3 +1,4 @@
|
||||
import typing
|
||||
from collections import Counter
|
||||
from typing import Dict, Iterable, Optional, Union
|
||||
|
||||
@@ -238,15 +239,15 @@ def params_show(
|
||||
if isinstance(stages, str):
|
||||
stages = [stages]
|
||||
|
||||
def _onerror_raise(result: Dict, exception: Exception, *args, **kwargs):
|
||||
def _onerror_raise(_result: Dict, exception: Exception, *args, **kwargs):
|
||||
raise exception
|
||||
|
||||
def _postprocess(params):
|
||||
processed = {}
|
||||
processed: Dict[str, Dict] = {}
|
||||
for rev, rev_data in params.items():
|
||||
processed[rev] = {}
|
||||
|
||||
counts = Counter()
|
||||
counts: typing.Counter[str] = Counter()
|
||||
for file_data in rev_data["data"].values():
|
||||
for k in file_data["data"]:
|
||||
counts[k] += 1
|
||||
|
||||
+34
-13
@@ -45,7 +45,6 @@ def _log_unknown_exceptions() -> None:
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
logger.debug("Version info for developers:\n%s", get_dvc_info())
|
||||
logger.info(FOOTER)
|
||||
return None
|
||||
|
||||
|
||||
def _log_exceptions(exc: Exception) -> Optional[int]:
|
||||
@@ -57,9 +56,11 @@ def _log_exceptions(exc: Exception) -> Optional[int]:
|
||||
|
||||
if exc.errno == errno.EMFILE:
|
||||
logger.exception(
|
||||
"too many open files, please visit "
|
||||
"{} to see how to handle this "
|
||||
"problem".format(error_link("many-files")),
|
||||
(
|
||||
"too many open files, please visit "
|
||||
"%s to see how to handle this problem"
|
||||
),
|
||||
error_link("many-files"),
|
||||
extra={"tb_only": True},
|
||||
)
|
||||
else:
|
||||
@@ -81,7 +82,7 @@ def _log_exceptions(exc: Exception) -> Optional[int]:
|
||||
if cmd:
|
||||
link = format_link("https://dvc.org/doc/install")
|
||||
hint = (
|
||||
f"To install dvc with those dependencies, run:\n"
|
||||
"To install dvc with those dependencies, run:\n"
|
||||
"\n"
|
||||
f"\t{cmd}\n"
|
||||
"\n"
|
||||
@@ -92,8 +93,10 @@ def _log_exceptions(exc: Exception) -> Optional[int]:
|
||||
hint = f"\nPlease report this bug to {link}. Thank you!"
|
||||
|
||||
logger.exception(
|
||||
f"URL '{exc.url}' is supported but requires these missing "
|
||||
f"dependencies: {exc.missing_deps}. {hint}",
|
||||
"URL '%s' is supported but requires these missing dependencies: %s. %s",
|
||||
exc.url,
|
||||
exc.missing_deps,
|
||||
hint,
|
||||
extra={"tb_only": True},
|
||||
)
|
||||
return None
|
||||
@@ -102,7 +105,9 @@ def _log_exceptions(exc: Exception) -> Optional[int]:
|
||||
link = format_link("https://man.dvc.org/remote/modify")
|
||||
logger.exception("configuration error")
|
||||
logger.exception(
|
||||
f"{exc!s}\nLearn more about configuration settings at {link}.",
|
||||
"%s\nLearn more about configuration settings at %s.",
|
||||
exc,
|
||||
link,
|
||||
extra={"tb_only": True},
|
||||
)
|
||||
return 251
|
||||
@@ -114,9 +119,14 @@ def _log_exceptions(exc: Exception) -> Optional[int]:
|
||||
|
||||
directory = relpath(exc.directory)
|
||||
logger.exception(
|
||||
f"Could not open pickled '{exc.type}' cache.\n"
|
||||
f"Remove the '{directory}' directory and then retry this command."
|
||||
f"\nSee {error_link('pickle')} for more information.",
|
||||
(
|
||||
"Could not open pickled '%s' cache.\n"
|
||||
"Remove the '%s' directory and then retry this command."
|
||||
"\nSee %s for more information."
|
||||
),
|
||||
exc.type,
|
||||
directory,
|
||||
error_link("pickle"),
|
||||
extra={"tb_only": True},
|
||||
)
|
||||
return None
|
||||
@@ -168,12 +178,23 @@ def main(argv=None): # noqa: C901
|
||||
elif args.verbose == 1:
|
||||
level = logging.DEBUG
|
||||
elif args.verbose > 1:
|
||||
level = logging.TRACE
|
||||
level = logging.TRACE # type: ignore[attr-defined]
|
||||
|
||||
if level is not None:
|
||||
set_loggers_level(level)
|
||||
|
||||
logger.trace(args)
|
||||
if level and level <= logging.DEBUG:
|
||||
from platform import platform, python_implementation, python_version
|
||||
|
||||
from dvc import __version__
|
||||
from dvc.utils.pkg import PKG
|
||||
|
||||
pyv = " ".join([python_implementation(), python_version()])
|
||||
pkg = f" ({PKG})" if PKG else ""
|
||||
logger.debug("v%s%s, %s on %s", __version__, pkg, pyv, platform())
|
||||
logger.debug("command: %s", " ".join(argv or sys.argv))
|
||||
|
||||
logger.trace(args) # type: ignore[attr-defined]
|
||||
|
||||
if not sys.stdout.closed and not args.quiet:
|
||||
from dvc.ui import ui
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ from argparse import _AppendAction
|
||||
|
||||
|
||||
class CommaSeparatedArgs(_AppendAction): # pylint: disable=protected-access
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
def __call__(self, parser, namespace, values, option_string=None): # noqa: ARG002
|
||||
from funcy import ldistinct
|
||||
|
||||
items = getattr(namespace, self.dest) or []
|
||||
@@ -11,7 +11,7 @@ class CommaSeparatedArgs(_AppendAction): # pylint: disable=protected-access
|
||||
|
||||
|
||||
class KeyValueArgs(_AppendAction):
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
def __call__(self, parser, namespace, values, option_string=None): # noqa: ARG002
|
||||
items = getattr(namespace, self.dest) or {}
|
||||
for value in filter(bool, values):
|
||||
key, _, value = value.partition("=")
|
||||
|
||||
+6
-2
@@ -1,6 +1,10 @@
|
||||
import logging
|
||||
import os
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dvc.config import Config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -8,13 +12,13 @@ logger = logging.getLogger(__name__)
|
||||
class CmdBase(ABC):
|
||||
UNINITIALIZED = False
|
||||
|
||||
def __init__(self, args):
|
||||
def __init__(self, args: Any):
|
||||
from dvc.repo import Repo
|
||||
|
||||
os.chdir(args.cd)
|
||||
|
||||
self.repo: "Repo" = Repo(uninitialized=self.UNINITIALIZED)
|
||||
self.config = self.repo.config
|
||||
self.config: "Config" = self.repo.config
|
||||
self.args = args
|
||||
|
||||
def do_run(self):
|
||||
|
||||
+1
-3
@@ -28,7 +28,5 @@ def hide_subparsers_from_help(subparsers):
|
||||
# Need to set `add_help=False`, but avoid setting `help`
|
||||
# (not even to `argparse.SUPPPRESS`).
|
||||
# NOTE: The argument is the parent subparser, not the subcommand parser.
|
||||
cmds = [
|
||||
cmd for cmd, parser in subparsers.choices.items() if parser.add_help
|
||||
]
|
||||
cmds = [cmd for cmd, parser in subparsers.choices.items() if parser.add_help]
|
||||
subparsers.metavar = "{{{}}}".format(",".join(cmds))
|
||||
|
||||
+1
-4
@@ -41,10 +41,7 @@ def _add_annotating_args(parser: argparse.ArgumentParser) -> None:
|
||||
|
||||
class CmdAdd(CmdBase):
|
||||
def run(self):
|
||||
from dvc.exceptions import (
|
||||
DvcException,
|
||||
RecursiveAddingWhileUsingFilename,
|
||||
)
|
||||
from dvc.exceptions import DvcException, RecursiveAddingWhileUsingFilename
|
||||
|
||||
try:
|
||||
if len(self.args.targets) > 1 and self.args.file:
|
||||
|
||||
@@ -42,7 +42,7 @@ def add_parser(subparsers, parent_parser):
|
||||
|
||||
cache_subparsers = cache_parser.add_subparsers(
|
||||
dest="cmd",
|
||||
help="Use `dvc cache CMD --help` for command-specific " "help.",
|
||||
help="Use `dvc cache CMD --help` for command-specific help.",
|
||||
)
|
||||
|
||||
fix_subparsers(cache_subparsers)
|
||||
@@ -68,10 +68,12 @@ def add_parser(subparsers, parent_parser):
|
||||
)
|
||||
cache_dir_parser.add_argument(
|
||||
"value",
|
||||
help="Path to cache directory. Relative paths are resolved relative "
|
||||
"to the current directory and saved to config relative to the "
|
||||
"config file location. If no path is provided, it returns the "
|
||||
"current cache directory.",
|
||||
help=(
|
||||
"Path to cache directory. Relative paths are resolved relative "
|
||||
"to the current directory and saved to config relative to the "
|
||||
"config file location. If no path is provided, it returns the "
|
||||
"current cache directory."
|
||||
),
|
||||
nargs="?",
|
||||
).complete = completion.DIR
|
||||
cache_dir_parser.set_defaults(func=CmdCacheDir)
|
||||
|
||||
@@ -62,9 +62,7 @@ class CmdCheckIgnore(CmdBase):
|
||||
raise DvcException("cannot have both `targets` and `--stdin`")
|
||||
|
||||
if self.args.non_matching and not self.args.details:
|
||||
raise DvcException(
|
||||
"`--non-matching` is only valid with `--details`"
|
||||
)
|
||||
raise DvcException("`--non-matching` is only valid with `--details`")
|
||||
|
||||
if self.args.all and not self.args.details:
|
||||
raise DvcException("`--all` is only valid with `--details`")
|
||||
@@ -80,9 +78,7 @@ class CmdCheckIgnore(CmdBase):
|
||||
|
||||
|
||||
def add_parser(subparsers, parent_parser):
|
||||
ADD_HELP = (
|
||||
"Check whether files or directories are excluded due to `.dvcignore`."
|
||||
)
|
||||
ADD_HELP = "Check whether files or directories are excluded due to `.dvcignore`."
|
||||
|
||||
parser = subparsers.add_parser(
|
||||
"check-ignore",
|
||||
@@ -103,16 +99,20 @@ def add_parser(subparsers, parent_parser):
|
||||
"--all",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Include the target paths which don’t match any pattern "
|
||||
"in the `--details` list.",
|
||||
help=(
|
||||
"Include the target paths which don't match any pattern "
|
||||
"in the `--details` list."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"-n",
|
||||
"--non-matching",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Include the target paths which don’t match any pattern "
|
||||
"in the `--details` list.",
|
||||
help=(
|
||||
"Include the target paths which don't match any pattern "
|
||||
"in the `--details` list."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stdin",
|
||||
|
||||
@@ -22,9 +22,7 @@ def log_changes(stats):
|
||||
continue
|
||||
|
||||
for entry in entries:
|
||||
ui.write(
|
||||
f"[{color}]{state[0].upper()}", entry, styled=True, sep="\t"
|
||||
)
|
||||
ui.write(f"[{color}]{state[0].upper()}", entry, styled=True, sep="\t")
|
||||
|
||||
|
||||
class CmdCheckout(CmdBase):
|
||||
@@ -46,9 +44,7 @@ class CmdCheckout(CmdBase):
|
||||
|
||||
if self.args.summary:
|
||||
default_message = "No changes."
|
||||
msg = get_summary(
|
||||
sorted(stats.items(), key=operator.itemgetter(0))
|
||||
)
|
||||
msg = get_summary(sorted(stats.items(), key=operator.itemgetter(0)))
|
||||
ui.write(msg or default_message)
|
||||
else:
|
||||
log_changes(stats)
|
||||
|
||||
@@ -24,11 +24,7 @@ class CmdCommit(CmdBase):
|
||||
force=self.args.force,
|
||||
)
|
||||
except DvcException:
|
||||
logger.exception(
|
||||
"failed to commit{}".format(
|
||||
(" " + target) if target else ""
|
||||
)
|
||||
)
|
||||
logger.exception("failed to commit%s", (" " + target) if target else "")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
+9
-16
@@ -17,9 +17,7 @@ def _name_type(value):
|
||||
match = re.match(NAME_REGEX, value)
|
||||
if not match:
|
||||
raise argparse.ArgumentTypeError(
|
||||
"name argument should look like "
|
||||
"remote.name.option or "
|
||||
"section.option"
|
||||
"name argument should look like remote.name.option or section.option"
|
||||
)
|
||||
return (
|
||||
bool(match.group("remote")),
|
||||
@@ -37,13 +35,12 @@ class CmdConfig(CmdBaseNoRepo):
|
||||
self.config = Config(validate=False)
|
||||
|
||||
def run(self):
|
||||
if self.args.show_origin:
|
||||
if any((self.args.value, self.args.unset)):
|
||||
logger.error(
|
||||
"--show-origin can't be used together with any of these "
|
||||
"options: -u/--unset, value"
|
||||
)
|
||||
return 1
|
||||
if self.args.show_origin and (self.args.value or self.args.unset):
|
||||
logger.error(
|
||||
"--show-origin can't be used together with any of these "
|
||||
"options: -u/--unset, value"
|
||||
)
|
||||
return 1
|
||||
|
||||
if self.args.list:
|
||||
return self._list()
|
||||
@@ -71,9 +68,7 @@ class CmdConfig(CmdBaseNoRepo):
|
||||
|
||||
for level in levels:
|
||||
conf = self.config.read(level)
|
||||
prefix = self._config_file_prefix(
|
||||
self.args.show_origin, self.config, level
|
||||
)
|
||||
prefix = self._config_file_prefix(self.args.show_origin, self.config, level)
|
||||
configs = list(self._format_config(conf, prefix))
|
||||
if configs:
|
||||
ui.write("\n".join(configs))
|
||||
@@ -132,9 +127,7 @@ class CmdConfig(CmdBaseNoRepo):
|
||||
raise ConfigError(f"{name} '{section}' doesn't exist")
|
||||
|
||||
if opt and opt not in conf[section]:
|
||||
raise ConfigError(
|
||||
f"option '{opt}' doesn't exist in {name} '{section}'"
|
||||
)
|
||||
raise ConfigError(f"option '{opt}' doesn't exist in {name} '{section}'")
|
||||
|
||||
def _get_appropriate_levels(self, levels):
|
||||
if levels:
|
||||
|
||||
@@ -46,7 +46,7 @@ def add_parser(subparsers, parent_parser):
|
||||
|
||||
daemon_subparsers = daemon_parser.add_subparsers(
|
||||
dest="cmd",
|
||||
help="Use `dvc daemon CMD --help` for command-specific " "help.",
|
||||
help="Use `dvc daemon CMD --help` for command-specific help.",
|
||||
)
|
||||
|
||||
fix_subparsers(daemon_subparsers)
|
||||
|
||||
+5
-5
@@ -86,9 +86,7 @@ def _collect_targets(repo, target, outs):
|
||||
targets.extend([str(out) for out in stage.outs])
|
||||
continue
|
||||
|
||||
for out in outs_trie.itervalues( # noqa: B301
|
||||
prefix=repo.fs.path.parts(path)
|
||||
):
|
||||
for out in outs_trie.itervalues(prefix=repo.fs.path.parts(path)): # noqa: B301
|
||||
targets.extend(str(out))
|
||||
|
||||
return targets
|
||||
@@ -204,7 +202,9 @@ def add_parser(subparsers, parent_parser):
|
||||
dag_parser.add_argument(
|
||||
"target",
|
||||
nargs="?",
|
||||
help="Stage name or output to show pipeline for. "
|
||||
"Finds all stages in the workspace by default.",
|
||||
help=(
|
||||
"Stage name or output to show pipeline for. "
|
||||
"Finds all stages in the workspace by default."
|
||||
),
|
||||
)
|
||||
dag_parser.set_defaults(func=CmdDAG)
|
||||
|
||||
+14
-21
@@ -8,11 +8,7 @@ from funcy import chunks, compact, log_durations
|
||||
from dvc.cli import completion
|
||||
from dvc.cli.actions import CommaSeparatedArgs
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.cli.utils import (
|
||||
append_doc_link,
|
||||
fix_subparsers,
|
||||
hide_subparsers_from_help,
|
||||
)
|
||||
from dvc.cli.utils import append_doc_link, fix_subparsers, hide_subparsers_from_help
|
||||
from dvc.ui import ui
|
||||
from dvc.utils import colorize
|
||||
|
||||
@@ -39,20 +35,19 @@ class CmdDataStatus(CmdBase):
|
||||
}
|
||||
HINTS = {
|
||||
"not_in_cache": ('use "dvc fetch <file>..." to download files',),
|
||||
"committed": (
|
||||
"git commit the corresponding dvc files to update the repo",
|
||||
),
|
||||
"committed": ("git commit the corresponding dvc files to update the repo",),
|
||||
"uncommitted": (
|
||||
'use "dvc commit <file>..." to track changes',
|
||||
'use "dvc checkout <file>..." to discard changes',
|
||||
),
|
||||
"untracked": (
|
||||
'use "git add <file> ..." or '
|
||||
'"dvc add <file>..." to commit to git or to dvc',
|
||||
(
|
||||
'use "git add <file> ..." or '
|
||||
'"dvc add <file>..." to commit to git or to dvc'
|
||||
),
|
||||
),
|
||||
"git_dirty": (
|
||||
"there are {}changes not tracked by dvc, "
|
||||
'use "git status" to see',
|
||||
'there are {}changes not tracked by dvc, use "git status" to see',
|
||||
),
|
||||
}
|
||||
|
||||
@@ -72,7 +67,7 @@ class CmdDataStatus(CmdBase):
|
||||
yield stage, items
|
||||
|
||||
@classmethod
|
||||
def _show_status(cls, status: "DataStatus") -> int:
|
||||
def _show_status(cls, status: "DataStatus") -> int: # noqa: C901
|
||||
git_info = status.pop("git") # type: ignore[misc]
|
||||
result = dict(cls._process_status(status))
|
||||
if not result:
|
||||
@@ -96,8 +91,7 @@ class CmdDataStatus(CmdBase):
|
||||
|
||||
if isinstance(stage_status, dict):
|
||||
items = [
|
||||
": ".join([state, file])
|
||||
for file, state in stage_status.items()
|
||||
": ".join([state, file]) for file, state in stage_status.items()
|
||||
]
|
||||
else:
|
||||
items = stage_status
|
||||
@@ -114,7 +108,9 @@ class CmdDataStatus(CmdBase):
|
||||
return 0
|
||||
|
||||
def run(self) -> int:
|
||||
with log_durations(logger.trace, "in data_status"): # type: ignore
|
||||
with log_durations(
|
||||
logger.trace, "in data_status" # type: ignore[attr-defined]
|
||||
):
|
||||
status = self.repo.data_status(
|
||||
granular=self.args.granular,
|
||||
untracked_files=self.args.untracked_files,
|
||||
@@ -178,9 +174,7 @@ class CmdDataLs(CmdBase):
|
||||
|
||||
filter_labels = set(self.args.labels)
|
||||
filter_types = set(self.args.type)
|
||||
d = ls(
|
||||
self.repo, targets=self.args.targets, recursive=self.args.recursive
|
||||
)
|
||||
d = ls(self.repo, targets=self.args.targets, recursive=self.args.recursive)
|
||||
self._show_table(
|
||||
d,
|
||||
filter_labels=filter_labels,
|
||||
@@ -203,8 +197,7 @@ def add_parser(subparsers, parent_parser):
|
||||
fix_subparsers(data_subparsers)
|
||||
|
||||
DATA_STATUS_HELP = (
|
||||
"Show changes between the last git commit, "
|
||||
"the dvcfiles and the workspace."
|
||||
"Show changes between the last git commit, the dvcfiles and the workspace."
|
||||
)
|
||||
data_status_parser = data_subparsers.add_parser(
|
||||
"status",
|
||||
|
||||
+15
-14
@@ -122,7 +122,7 @@ def shared_parent_parser():
|
||||
"Limit command scope to these tracked files/directories, "
|
||||
".dvc files and stage names."
|
||||
),
|
||||
).complete = completion.DVC_FILE
|
||||
).complete = completion.DVC_FILE # type: ignore[attr-defined]
|
||||
|
||||
return parent_parser
|
||||
|
||||
@@ -262,9 +262,7 @@ def add_parser(subparsers, _parent_parser):
|
||||
push_parser.set_defaults(func=CmdDataPush)
|
||||
|
||||
# Fetch
|
||||
FETCH_HELP = (
|
||||
"Download files or directories from remote storage to the cache."
|
||||
)
|
||||
FETCH_HELP = "Download files or directories from remote storage to the cache."
|
||||
|
||||
fetch_parser = subparsers.add_parser(
|
||||
"fetch",
|
||||
@@ -302,7 +300,7 @@ def add_parser(subparsers, _parent_parser):
|
||||
"--with-deps",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Fetch cache for all dependencies of the " "specified target.",
|
||||
help="Fetch cache for all dependencies of the specified target.",
|
||||
)
|
||||
fetch_parser.add_argument(
|
||||
"-R",
|
||||
@@ -320,9 +318,7 @@ def add_parser(subparsers, _parent_parser):
|
||||
fetch_parser.set_defaults(func=CmdDataFetch)
|
||||
|
||||
# Status
|
||||
STATUS_HELP = (
|
||||
"Show changed stages, compare local cache and a remote storage."
|
||||
)
|
||||
STATUS_HELP = "Show changed stages, compare local cache and a remote storage."
|
||||
|
||||
status_parser = subparsers.add_parser(
|
||||
"status",
|
||||
@@ -360,24 +356,29 @@ def add_parser(subparsers, _parent_parser):
|
||||
"--all-branches",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Show status of a local cache compared to a remote repository "
|
||||
"for all branches.",
|
||||
help=(
|
||||
"Show status of a local cache compared to a remote repository "
|
||||
"for all branches."
|
||||
),
|
||||
)
|
||||
status_parser.add_argument(
|
||||
"-T",
|
||||
"--all-tags",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Show status of a local cache compared to a remote repository "
|
||||
"for all tags.",
|
||||
help=(
|
||||
"Show status of a local cache compared to a remote repository for all tags."
|
||||
),
|
||||
)
|
||||
status_parser.add_argument(
|
||||
"-A",
|
||||
"--all-commits",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Show status of a local cache compared to a remote repository "
|
||||
"for all commits.",
|
||||
help=(
|
||||
"Show status of a local cache compared to a remote repository "
|
||||
"for all commits."
|
||||
),
|
||||
)
|
||||
status_parser.add_argument(
|
||||
"-d",
|
||||
|
||||
+10
-15
@@ -109,12 +109,10 @@ class CmdDiff(CmdBase):
|
||||
ui.write()
|
||||
|
||||
if not sum(summary.values()):
|
||||
return None
|
||||
return
|
||||
|
||||
states_summary = ", ".join(
|
||||
f"{summary[state]} {state}"
|
||||
for state in states
|
||||
if summary[state] > 0
|
||||
f"{summary[state]} {state}" for state in states if summary[state] > 0
|
||||
)
|
||||
ui.write("files summary:", states_summary)
|
||||
|
||||
@@ -122,9 +120,7 @@ class CmdDiff(CmdBase):
|
||||
from dvc.exceptions import DvcException
|
||||
|
||||
try:
|
||||
diff = self.repo.diff(
|
||||
self.args.a_rev, self.args.b_rev, self.args.targets
|
||||
)
|
||||
diff = self.repo.diff(self.args.a_rev, self.args.b_rev, self.args.targets)
|
||||
show_hash = self.args.show_hash
|
||||
hide_missing = self.args.b_rev or self.args.hide_missing
|
||||
if hide_missing:
|
||||
@@ -133,9 +129,11 @@ class CmdDiff(CmdBase):
|
||||
for key, entries in diff.items():
|
||||
entries = sorted(
|
||||
entries,
|
||||
key=lambda entry: entry["path"]["old"]
|
||||
if isinstance(entry["path"], dict)
|
||||
else entry["path"],
|
||||
key=lambda entry: (
|
||||
entry["path"]["old"]
|
||||
if isinstance(entry["path"], dict)
|
||||
else entry["path"]
|
||||
),
|
||||
)
|
||||
if not show_hash:
|
||||
for entry in entries:
|
||||
@@ -170,10 +168,7 @@ def add_parser(subparsers, parent_parser):
|
||||
diff_parser.add_argument(
|
||||
"--targets",
|
||||
nargs="*",
|
||||
help=(
|
||||
"Specific DVC-tracked files to compare. "
|
||||
"Accepts one or more file paths."
|
||||
),
|
||||
help="Specific DVC-tracked files to compare. Accepts one or more file paths.",
|
||||
metavar="<paths>",
|
||||
).complete = completion.FILE
|
||||
diff_parser.add_argument(
|
||||
@@ -184,7 +179,7 @@ def add_parser(subparsers, parent_parser):
|
||||
)
|
||||
diff_parser.add_argument(
|
||||
"b_rev",
|
||||
help=("New Git commit to compare (defaults to the current workspace)"),
|
||||
help="New Git commit to compare (defaults to the current workspace)",
|
||||
nargs="?",
|
||||
)
|
||||
diff_parser.add_argument(
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import argparse
|
||||
|
||||
from dvc.cli.utils import (
|
||||
append_doc_link,
|
||||
fix_subparsers,
|
||||
hide_subparsers_from_help,
|
||||
)
|
||||
from dvc.cli.utils import append_doc_link, fix_subparsers, hide_subparsers_from_help
|
||||
from dvc.commands.experiments import (
|
||||
apply,
|
||||
branch,
|
||||
@@ -54,8 +50,7 @@ def add_parser(subparsers, parent_parser):
|
||||
|
||||
experiments_subparsers = experiments_parser.add_subparsers(
|
||||
dest="cmd",
|
||||
help="Use `dvc experiments CMD --help` to display "
|
||||
"command-specific help.",
|
||||
help="Use `dvc experiments CMD --help` to display command-specific help.",
|
||||
)
|
||||
|
||||
fix_subparsers(experiments_subparsers)
|
||||
|
||||
@@ -10,19 +10,13 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class CmdExperimentsApply(CmdBase):
|
||||
def run(self):
|
||||
|
||||
self.repo.experiments.apply(
|
||||
self.args.experiment, force=self.args.force
|
||||
)
|
||||
self.repo.experiments.apply(self.args.experiment, force=self.args.force)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def add_parser(experiments_subparsers, parent_parser):
|
||||
|
||||
EXPERIMENTS_APPLY_HELP = (
|
||||
"Apply the changes from an experiment to your workspace."
|
||||
)
|
||||
EXPERIMENTS_APPLY_HELP = "Apply the changes from an experiment to your workspace."
|
||||
experiments_apply_parser = experiments_subparsers.add_parser(
|
||||
"apply",
|
||||
parents=[parent_parser],
|
||||
|
||||
@@ -9,14 +9,12 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class CmdExperimentsBranch(CmdBase):
|
||||
def run(self):
|
||||
|
||||
self.repo.experiments.branch(self.args.experiment, self.args.branch)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def add_parser(experiments_subparsers, parent_parser):
|
||||
|
||||
EXPERIMENTS_BRANCH_HELP = "Promote an experiment to a Git branch."
|
||||
experiments_branch_parser = experiments_subparsers.add_parser(
|
||||
"branch",
|
||||
@@ -28,7 +26,5 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
experiments_branch_parser.add_argument(
|
||||
"experiment", help="Experiment to be promoted."
|
||||
)
|
||||
experiments_branch_parser.add_argument(
|
||||
"branch", help="Name for the new Git branch"
|
||||
)
|
||||
experiments_branch_parser.add_argument("branch", help="Name for the new Git branch")
|
||||
experiments_branch_parser.set_defaults(func=CmdExperimentsBranch)
|
||||
|
||||
@@ -13,7 +13,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class CmdExperimentsDiff(CmdBase):
|
||||
def run(self):
|
||||
|
||||
try:
|
||||
diff = self.repo.experiments.diff(
|
||||
a_rev=self.args.a_rev,
|
||||
@@ -53,10 +52,7 @@ class CmdExperimentsDiff(CmdBase):
|
||||
|
||||
|
||||
def add_parser(experiments_subparsers, parent_parser):
|
||||
|
||||
EXPERIMENTS_DIFF_HELP = (
|
||||
"Show changes between experiments in the DVC repository."
|
||||
)
|
||||
EXPERIMENTS_DIFF_HELP = "Show changes between experiments in the DVC repository."
|
||||
|
||||
experiments_diff_parser = experiments_subparsers.add_parser(
|
||||
"diff",
|
||||
|
||||
@@ -9,10 +9,7 @@ class CmdExecutorRun(CmdBaseNoRepo):
|
||||
"""Run an experiment executor."""
|
||||
|
||||
def run(self):
|
||||
from dvc.repo.experiments.executor.base import (
|
||||
BaseExecutor,
|
||||
ExecutorInfo,
|
||||
)
|
||||
from dvc.repo.experiments.executor.base import BaseExecutor, ExecutorInfo
|
||||
from dvc.utils.serialize import load_json
|
||||
|
||||
info = ExecutorInfo.from_dict(load_json(self.args.infofile))
|
||||
|
||||
@@ -41,10 +41,7 @@ class CmdExperimentsGC(CmdBase):
|
||||
msg += " and all git tags"
|
||||
|
||||
if self.args.commit_date:
|
||||
msg += (
|
||||
" and all git commits before date "
|
||||
f"{self.args.commit_date}"
|
||||
)
|
||||
msg += f" and all git commits before date {self.args.commit_date}"
|
||||
msg += " of the current repo."
|
||||
|
||||
if self.args.queued:
|
||||
@@ -79,11 +76,9 @@ class CmdExperimentsGC(CmdBase):
|
||||
|
||||
|
||||
def add_parser(experiments_subparsers, parent_parser):
|
||||
|
||||
EXPERIMENTS_GC_HELP = "Garbage collect unneeded experiments."
|
||||
EXPERIMENTS_GC_DESCRIPTION = (
|
||||
"Removes all experiments which are not derived from the specified"
|
||||
"Git revisions."
|
||||
"Removes all experiments which are not derived from the specifiedGit revisions."
|
||||
)
|
||||
experiments_gc_parser = experiments_subparsers.add_parser(
|
||||
"gc",
|
||||
|
||||
@@ -92,9 +92,7 @@ class CmdExperimentsInit(CmdBase):
|
||||
|
||||
if new_out_dirs:
|
||||
out_dirs_paths = humanize.join(map(path_fmt, new_out_dirs))
|
||||
ui.write(
|
||||
f"Creating output directories: {out_dirs_paths}", styled=True
|
||||
)
|
||||
ui.write(f"Creating output directories: {out_dirs_paths}", styled=True)
|
||||
|
||||
ui.write(
|
||||
f"Creating [b]{self.args.name}[/b] stage in [green]dvc.yaml[/]",
|
||||
@@ -120,7 +118,6 @@ class CmdExperimentsInit(CmdBase):
|
||||
|
||||
|
||||
def add_parser(experiments_subparsers, parent_parser):
|
||||
|
||||
EXPERIMENTS_INIT_HELP = "Quickly setup any project to use experiments."
|
||||
experiments_init_parser = experiments_subparsers.add_parser(
|
||||
"init",
|
||||
@@ -167,35 +164,47 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--code",
|
||||
help="Path to the source file or directory "
|
||||
"which your experiments depend"
|
||||
f" (default: {CmdExperimentsInit.CODE})",
|
||||
help=(
|
||||
"Path to the source file or directory "
|
||||
"which your experiments depend"
|
||||
f" (default: {CmdExperimentsInit.CODE})"
|
||||
),
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--data",
|
||||
help="Path to the data file or directory "
|
||||
"which your experiments depend"
|
||||
f" (default: {CmdExperimentsInit.DATA})",
|
||||
help=(
|
||||
"Path to the data file or directory "
|
||||
"which your experiments depend"
|
||||
f" (default: {CmdExperimentsInit.DATA})"
|
||||
),
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--models",
|
||||
help="Path to the model file or directory for your experiments"
|
||||
f" (default: {CmdExperimentsInit.MODELS})",
|
||||
help=(
|
||||
"Path to the model file or directory for your experiments"
|
||||
f" (default: {CmdExperimentsInit.MODELS})"
|
||||
),
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--params",
|
||||
help="Path to the parameters file for your experiments"
|
||||
f" (default: {CmdExperimentsInit.DEFAULT_PARAMS})",
|
||||
help=(
|
||||
"Path to the parameters file for your experiments"
|
||||
f" (default: {CmdExperimentsInit.DEFAULT_PARAMS})"
|
||||
),
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--metrics",
|
||||
help="Path to the metrics file for your experiments"
|
||||
f" (default: {CmdExperimentsInit.DEFAULT_METRICS})",
|
||||
help=(
|
||||
"Path to the metrics file for your experiments"
|
||||
f" (default: {CmdExperimentsInit.DEFAULT_METRICS})"
|
||||
),
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--plots",
|
||||
help="Path to the plots file or directory for your experiments"
|
||||
f" (default: {CmdExperimentsInit.PLOTS})",
|
||||
help=(
|
||||
"Path to the plots file or directory for your experiments"
|
||||
f" (default: {CmdExperimentsInit.PLOTS})"
|
||||
),
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--live",
|
||||
|
||||
@@ -11,9 +11,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class CmdExperimentsPull(CmdBase):
|
||||
def raise_error_if_all_disabled(self):
|
||||
if not any(
|
||||
[self.args.experiment, self.args.all_commits, self.args.rev]
|
||||
):
|
||||
if not any([self.args.experiment, self.args.all_commits, self.args.rev]):
|
||||
raise InvalidArgumentError(
|
||||
"Either provide an `experiment` argument, or use the "
|
||||
"`--rev` or `--all-commits` flag."
|
||||
@@ -74,10 +72,7 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
"--no-cache",
|
||||
action="store_false",
|
||||
dest="pull_cache",
|
||||
help=(
|
||||
"Do not pull cached outputs for this experiment from DVC remote "
|
||||
"storage."
|
||||
),
|
||||
help="Do not pull cached outputs for this experiment from DVC remote storage.",
|
||||
)
|
||||
experiments_pull_parser.add_argument(
|
||||
"-r",
|
||||
@@ -92,8 +87,7 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
type=int,
|
||||
metavar="<number>",
|
||||
help=(
|
||||
"Number of jobs to run simultaneously when pulling from DVC "
|
||||
"remote storage."
|
||||
"Number of jobs to run simultaneously when pulling from DVC remote storage."
|
||||
),
|
||||
)
|
||||
experiments_pull_parser.add_argument(
|
||||
|
||||
@@ -12,16 +12,13 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class CmdExperimentsPush(CmdBase):
|
||||
def raise_error_if_all_disabled(self):
|
||||
if not any(
|
||||
[self.args.experiment, self.args.all_commits, self.args.rev]
|
||||
):
|
||||
if not any([self.args.experiment, self.args.all_commits, self.args.rev]):
|
||||
raise InvalidArgumentError(
|
||||
"Either provide an `experiment` argument, or use the "
|
||||
"`--rev` or `--all-commits` flag."
|
||||
)
|
||||
|
||||
def run(self):
|
||||
|
||||
self.raise_error_if_all_disabled()
|
||||
|
||||
pushed_exps = self.repo.experiments.push(
|
||||
@@ -47,8 +44,10 @@ class CmdExperimentsPush(CmdBase):
|
||||
if not self.args.push_cache:
|
||||
ui.write(
|
||||
"To push cached outputs",
|
||||
"for this experiment to DVC remote storage,"
|
||||
"re-run this command without '--no-cache'.",
|
||||
(
|
||||
"for this experiment to DVC remote storage,"
|
||||
"re-run this command without '--no-cache'."
|
||||
),
|
||||
)
|
||||
|
||||
return 0
|
||||
@@ -76,10 +75,7 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
"--no-cache",
|
||||
action="store_false",
|
||||
dest="push_cache",
|
||||
help=(
|
||||
"Do not push cached outputs for this experiment to DVC remote "
|
||||
"storage."
|
||||
),
|
||||
help="Do not push cached outputs for this experiment to DVC remote storage.",
|
||||
)
|
||||
experiments_push_parser.add_argument(
|
||||
"-r",
|
||||
@@ -93,10 +89,7 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
"--jobs",
|
||||
type=int,
|
||||
metavar="<number>",
|
||||
help=(
|
||||
"Number of jobs to run simultaneously when pushing to DVC remote "
|
||||
"storage."
|
||||
),
|
||||
help="Number of jobs to run simultaneously when pushing to DVC remote storage.",
|
||||
)
|
||||
experiments_push_parser.add_argument(
|
||||
"--run-cache",
|
||||
|
||||
@@ -24,7 +24,6 @@ class CmdExperimentsRemove(CmdBase):
|
||||
)
|
||||
|
||||
def run(self):
|
||||
|
||||
self.check_arguments()
|
||||
|
||||
removed_list = self.repo.experiments.remove(
|
||||
|
||||
@@ -17,13 +17,10 @@ class CmdExperimentsRun(CmdRepro):
|
||||
|
||||
if self.args.checkpoint_resume:
|
||||
if self.args.reset:
|
||||
raise InvalidArgumentError(
|
||||
"--reset and --rev are mutually exclusive."
|
||||
)
|
||||
raise InvalidArgumentError("--reset and --rev are mutually exclusive.")
|
||||
if not (self.args.queue or self.args.tmp_dir):
|
||||
raise InvalidArgumentError(
|
||||
"--rev can only be used in conjunction with "
|
||||
"--queue or --temp."
|
||||
"--rev can only be used in conjunction with --queue or --temp."
|
||||
)
|
||||
|
||||
if self.args.reset:
|
||||
@@ -51,7 +48,6 @@ class CmdExperimentsRun(CmdRepro):
|
||||
|
||||
|
||||
def add_parser(experiments_subparsers, parent_parser):
|
||||
|
||||
EXPERIMENTS_RUN_HELP = "Run or resume an experiment."
|
||||
experiments_run_parser = experiments_subparsers.add_parser(
|
||||
"run",
|
||||
|
||||
@@ -11,7 +11,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class CmdExperimentsSave(CmdBase):
|
||||
def run(self):
|
||||
|
||||
try:
|
||||
ref = self.repo.experiments.save(
|
||||
name=self.args.name,
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
import re
|
||||
from collections import Counter, OrderedDict, defaultdict
|
||||
from datetime import date, datetime
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any, Dict, Set
|
||||
|
||||
from funcy import lmap
|
||||
|
||||
@@ -37,9 +37,9 @@ def _update_names(names, items):
|
||||
|
||||
|
||||
def _collect_names(all_experiments, **kwargs):
|
||||
metric_names = defaultdict(dict)
|
||||
param_names = defaultdict(dict)
|
||||
deps_names = set()
|
||||
metric_names: Dict[str, Dict[str, Any]] = defaultdict(dict)
|
||||
param_names: Dict[str, Dict[str, Any]] = defaultdict(dict)
|
||||
deps_names: Set[str] = set()
|
||||
|
||||
for _, experiments in all_experiments.items():
|
||||
for exp_data in experiments.values():
|
||||
@@ -62,7 +62,7 @@ experiment_types = {
|
||||
}
|
||||
|
||||
|
||||
def _collect_rows(
|
||||
def _collect_rows( # noqa: C901
|
||||
base_rev,
|
||||
experiments,
|
||||
all_headers,
|
||||
@@ -77,16 +77,14 @@ def _collect_rows(
|
||||
fill_value=FILL_VALUE,
|
||||
iso=False,
|
||||
):
|
||||
from scmrepo.git import Git
|
||||
from dvc.scm import Git
|
||||
|
||||
if sort_by:
|
||||
sort_path, sort_name, sort_type = _sort_column(
|
||||
sort_by, metric_names, param_names
|
||||
)
|
||||
reverse = sort_order == "desc"
|
||||
experiments = _sort_exp(
|
||||
experiments, sort_path, sort_name, sort_type, reverse
|
||||
)
|
||||
experiments = _sort_exp(experiments, sort_path, sort_name, sort_type, reverse)
|
||||
|
||||
new_checkpoint = True
|
||||
for i, (rev, results) in enumerate(experiments.items()):
|
||||
@@ -116,9 +114,7 @@ def _collect_rows(
|
||||
typ = "baseline"
|
||||
elif tip:
|
||||
if tip == parent_tip:
|
||||
typ = (
|
||||
"checkpoint_tip" if new_checkpoint else "checkpoint_commit"
|
||||
)
|
||||
typ = "checkpoint_tip" if new_checkpoint else "checkpoint_commit"
|
||||
elif parent_rev == base_rev:
|
||||
typ = "checkpoint_base"
|
||||
else:
|
||||
@@ -135,9 +131,7 @@ def _collect_rows(
|
||||
row_dict["Experiment"] = exp.get("name", "")
|
||||
row_dict["rev"] = name_rev
|
||||
row_dict["typ"] = typ
|
||||
row_dict["Created"] = format_time(
|
||||
exp.get("timestamp"), fill_value, iso
|
||||
)
|
||||
row_dict["Created"] = format_time(exp.get("timestamp"), fill_value, iso)
|
||||
row_dict["parent"] = parent
|
||||
row_dict["State"] = state
|
||||
row_dict["Executor"] = exp.get("executor", fill_value)
|
||||
@@ -314,7 +308,7 @@ def baseline_styler(typ):
|
||||
return {"style": "bold"} if typ == "baseline" else {}
|
||||
|
||||
|
||||
def show_experiments(
|
||||
def show_experiments( # noqa: C901
|
||||
all_experiments,
|
||||
keep=None,
|
||||
drop=None,
|
||||
@@ -328,7 +322,7 @@ def show_experiments(
|
||||
|
||||
metric_names, param_names, deps_names = _collect_names(all_experiments)
|
||||
|
||||
headers = [
|
||||
headers_list = [
|
||||
"Experiment",
|
||||
"rev",
|
||||
"typ",
|
||||
@@ -340,13 +334,13 @@ def show_experiments(
|
||||
|
||||
names = {**metric_names, **param_names}
|
||||
counter = Counter(flatten_list([list(a.keys()) for a in names.values()]))
|
||||
counter.update(headers)
|
||||
counter.update(headers_list)
|
||||
metric_headers = _normalize_headers(metric_names, counter)
|
||||
param_headers = _normalize_headers(param_names, counter)
|
||||
|
||||
td = experiments_table(
|
||||
all_experiments,
|
||||
headers,
|
||||
headers_list,
|
||||
metric_headers,
|
||||
metric_names,
|
||||
param_headers,
|
||||
@@ -359,7 +353,7 @@ def show_experiments(
|
||||
kwargs.get("iso"),
|
||||
)
|
||||
if keep:
|
||||
for col in td.keys():
|
||||
for col in td.keys(): # noqa: SIM118
|
||||
if re.match(keep, col):
|
||||
td.protect(col)
|
||||
|
||||
@@ -371,9 +365,7 @@ def show_experiments(
|
||||
|
||||
if not csv:
|
||||
merge_headers = ["Experiment", "rev", "typ", "parent"]
|
||||
td.column("Experiment")[:] = map(
|
||||
prepare_exp_id, td.as_dict(merge_headers)
|
||||
)
|
||||
td.column("Experiment")[:] = map(prepare_exp_id, td.as_dict(merge_headers))
|
||||
td.drop(*merge_headers[1:])
|
||||
|
||||
headers = {
|
||||
@@ -410,13 +402,13 @@ def show_experiments(
|
||||
|
||||
cols_to_drop = set()
|
||||
if drop is not None:
|
||||
cols_to_drop = {col for col in td.keys() if re.match(drop, col)}
|
||||
cols_to_drop = {col for col in td.keys() if re.match(drop, col)} # noqa: SIM118
|
||||
if pcp:
|
||||
cols_to_drop.add("Created")
|
||||
td.drop(*cols_to_drop)
|
||||
|
||||
if pcp:
|
||||
subset = {x for x in td.keys() if x != "Experiment"}
|
||||
subset = {x for x in td.keys() if x != "Experiment"} # noqa: SIM118
|
||||
td.dropna(
|
||||
"rows",
|
||||
how="all",
|
||||
@@ -489,12 +481,10 @@ class CmdExperimentsShow(CmdBase):
|
||||
ui.write_json(all_experiments, default=_format_json)
|
||||
else:
|
||||
precision = (
|
||||
self.args.precision or None
|
||||
if self.args.csv
|
||||
else DEFAULT_PRECISION
|
||||
self.args.precision or None if self.args.csv else DEFAULT_PRECISION
|
||||
)
|
||||
fill_value = "" if self.args.csv else FILL_VALUE
|
||||
iso = True if self.args.csv else False
|
||||
iso = self.args.csv
|
||||
|
||||
show_experiments(
|
||||
all_experiments,
|
||||
@@ -580,10 +570,7 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
)
|
||||
experiments_show_parser.add_argument(
|
||||
"--sort-order",
|
||||
help=(
|
||||
"Sort order to use with --sort-by."
|
||||
" Defaults to ascending ('asc')."
|
||||
),
|
||||
help="Sort order to use with --sort-by. Defaults to ascending ('asc').",
|
||||
choices=("asc", "desc"),
|
||||
default="asc",
|
||||
)
|
||||
|
||||
+8
-11
@@ -10,7 +10,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CmdGC(CmdBase):
|
||||
def run(self):
|
||||
def run(self): # noqa: C901
|
||||
from dvc.repo.gc import _validate_args
|
||||
|
||||
_validate_args(
|
||||
@@ -40,14 +40,9 @@ class CmdGC(CmdBase):
|
||||
elif self.args.all_tags:
|
||||
msg += " and all git tags"
|
||||
if self.args.commit_date:
|
||||
msg += (
|
||||
" and all git commits before date "
|
||||
f"{self.args.commit_date}"
|
||||
)
|
||||
msg += f" and all git commits before date {self.args.commit_date}"
|
||||
if self.args.rev:
|
||||
msg += (
|
||||
f" and last {self.args.num} commits from {self.args.rev}"
|
||||
)
|
||||
msg += f" and last {self.args.num} commits from {self.args.rev}"
|
||||
|
||||
if self.args.all_experiments:
|
||||
msg += " and all experiments"
|
||||
@@ -198,9 +193,11 @@ def add_parser(subparsers, parent_parser):
|
||||
dest="repos",
|
||||
type=str,
|
||||
nargs="*",
|
||||
help="Keep data files required by these projects "
|
||||
"in addition to the current one. "
|
||||
"Useful if you share a single cache across repos.",
|
||||
help=(
|
||||
"Keep data files required by these projects "
|
||||
"in addition to the current one. "
|
||||
"Useful if you share a single cache across repos."
|
||||
),
|
||||
metavar="<paths>",
|
||||
)
|
||||
gc_parser.set_defaults(func=CmdGC)
|
||||
|
||||
+8
-11
@@ -3,11 +3,10 @@ import logging
|
||||
|
||||
from dvc.cli import completion
|
||||
from dvc.cli.command import CmdBaseNoRepo
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.scm import CloneError
|
||||
|
||||
from ..cli.utils import append_doc_link
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -17,9 +16,7 @@ class CmdGet(CmdBaseNoRepo):
|
||||
from dvc.ui import ui
|
||||
|
||||
try:
|
||||
url = get_url(
|
||||
self.args.path, repo=self.args.url, rev=self.args.rev
|
||||
)
|
||||
url = get_url(self.args.path, repo=self.args.url, rev=self.args.rev)
|
||||
ui.write(url, force=True)
|
||||
except DvcException:
|
||||
logger.exception("failed to show URL")
|
||||
@@ -46,13 +43,11 @@ class CmdGet(CmdBaseNoRepo):
|
||||
)
|
||||
return 0
|
||||
except CloneError:
|
||||
logger.exception(f"failed to get '{self.args.path}'")
|
||||
logger.exception("failed to get '%s'", self.args.path)
|
||||
return 1
|
||||
except DvcException:
|
||||
logger.exception(
|
||||
"failed to get '{}' from '{}'".format(
|
||||
self.args.path, self.args.url
|
||||
)
|
||||
"failed to get '%s' from '%s'", self.args.path, self.args.url
|
||||
)
|
||||
return 1
|
||||
|
||||
@@ -88,8 +83,10 @@ def add_parser(subparsers, parent_parser):
|
||||
get_parser.add_argument(
|
||||
"--show-url",
|
||||
action="store_true",
|
||||
help="Print the storage location (URL) the target data would be "
|
||||
"downloaded from, and exit.",
|
||||
help=(
|
||||
"Print the storage location (URL) the target data would be "
|
||||
"downloaded from, and exit."
|
||||
),
|
||||
)
|
||||
get_parser.add_argument(
|
||||
"-j",
|
||||
|
||||
@@ -3,10 +3,9 @@ import logging
|
||||
|
||||
from dvc.cli import completion
|
||||
from dvc.cli.command import CmdBaseNoRepo
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.exceptions import DvcException
|
||||
|
||||
from ..cli.utils import append_doc_link
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
@@ -64,15 +64,15 @@ class CmdPrePush(CmdHookBase):
|
||||
|
||||
class CmdMergeDriver(CmdHookBase):
|
||||
def _run(self):
|
||||
from dvc.dvcfile import Dvcfile
|
||||
from dvc.dvcfile import load_file
|
||||
from dvc.repo import Repo
|
||||
|
||||
dvc = Repo()
|
||||
|
||||
try:
|
||||
ancestor = Dvcfile(dvc, self.args.ancestor, verify=False)
|
||||
our = Dvcfile(dvc, self.args.our, verify=False)
|
||||
their = Dvcfile(dvc, self.args.their, verify=False)
|
||||
ancestor = load_file(dvc, self.args.ancestor, verify=False)
|
||||
our = load_file(dvc, self.args.our, verify=False)
|
||||
their = load_file(dvc, self.args.their, verify=False)
|
||||
|
||||
our.merge(ancestor, their, allowed=["add", "remove", "change"])
|
||||
|
||||
|
||||
+8
-6
@@ -28,13 +28,13 @@ class CmdImport(CmdBase):
|
||||
jobs=self.args.jobs,
|
||||
)
|
||||
except CloneError:
|
||||
logger.exception(f"failed to import '{self.args.path}'")
|
||||
logger.exception("failed to import '%s'", self.args.path)
|
||||
return 1
|
||||
except DvcException:
|
||||
logger.exception(
|
||||
"failed to import '{}' from '{}'.".format(
|
||||
self.args.path, self.args.url
|
||||
)
|
||||
"failed to import '%s' from '%s'.",
|
||||
self.args.path,
|
||||
self.args.url,
|
||||
)
|
||||
return 1
|
||||
return 0
|
||||
@@ -90,8 +90,10 @@ def add_parser(subparsers, parent_parser):
|
||||
"--no-download",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Create .dvc file including target data hash value(s)"
|
||||
" but do not actually download the file(s).",
|
||||
help=(
|
||||
"Create .dvc file including target data hash value(s)"
|
||||
" but do not actually download the file(s)."
|
||||
),
|
||||
)
|
||||
import_parser.add_argument(
|
||||
"-j",
|
||||
|
||||
+23
-23
@@ -29,10 +29,11 @@ class CmdImportUrl(CmdBase):
|
||||
)
|
||||
except DvcException:
|
||||
logger.exception(
|
||||
"failed to import {}. You could also try downloading "
|
||||
"it manually, and adding it with `dvc add`.".format(
|
||||
self.args.url
|
||||
)
|
||||
(
|
||||
"failed to import %s. You could also try downloading "
|
||||
"it manually, and adding it with `dvc add`."
|
||||
),
|
||||
self.args.url,
|
||||
)
|
||||
return 1
|
||||
return 0
|
||||
@@ -41,9 +42,7 @@ class CmdImportUrl(CmdBase):
|
||||
def add_parser(subparsers, parent_parser):
|
||||
from .add import _add_annotating_args
|
||||
|
||||
IMPORT_HELP = (
|
||||
"Download or copy file from URL and take it under DVC control."
|
||||
)
|
||||
IMPORT_HELP = "Download or copy file from URL and take it under DVC control."
|
||||
|
||||
import_parser = subparsers.add_parser(
|
||||
"import-url",
|
||||
@@ -54,16 +53,18 @@ def add_parser(subparsers, parent_parser):
|
||||
)
|
||||
import_parser.add_argument(
|
||||
"url",
|
||||
help="Location of the data to download. Supported URLs:\n"
|
||||
"/absolute/path/to/file/or/dir\n"
|
||||
"relative/path/to/file/or/dir\n"
|
||||
"C:\\\\path\\to\\file\\or\\dir\n"
|
||||
"https://example.com/path/to/file\n"
|
||||
"s3://bucket/key/path\n"
|
||||
"gs://bucket/path/to/file/or/dir\n"
|
||||
"hdfs://example.com/path/to/file\n"
|
||||
"ssh://example.com/absolute/path/to/file/or/dir\n"
|
||||
"remote://remote_name/path/to/file/or/dir (see `dvc remote`)",
|
||||
help=(
|
||||
"Location of the data to download. Supported URLs:\n"
|
||||
"/absolute/path/to/file/or/dir\n"
|
||||
"relative/path/to/file/or/dir\n"
|
||||
"C:\\\\path\\to\\file\\or\\dir\n"
|
||||
"https://example.com/path/to/file\n"
|
||||
"s3://bucket/key/path\n"
|
||||
"gs://bucket/path/to/file/or/dir\n"
|
||||
"hdfs://example.com/path/to/file\n"
|
||||
"ssh://example.com/absolute/path/to/file/or/dir\n"
|
||||
"remote://remote_name/path/to/file/or/dir (see `dvc remote`)"
|
||||
),
|
||||
)
|
||||
import_parser.add_argument(
|
||||
"out", nargs="?", help="Destination path to put files to."
|
||||
@@ -96,8 +97,10 @@ def add_parser(subparsers, parent_parser):
|
||||
"--no-download",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Create .dvc file including target data hash value(s)"
|
||||
" but do not actually download the file(s).",
|
||||
help=(
|
||||
"Create .dvc file including target data hash value(s)"
|
||||
" but do not actually download the file(s)."
|
||||
),
|
||||
)
|
||||
import_parser.add_argument(
|
||||
"-j",
|
||||
@@ -113,10 +116,7 @@ def add_parser(subparsers, parent_parser):
|
||||
"--version-aware",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=(
|
||||
"Import using cloud versioning. Implied if the URL contains a "
|
||||
"version ID."
|
||||
),
|
||||
help="Import using cloud versioning. Implied if the URL contains a version ID.",
|
||||
)
|
||||
_add_annotating_args(import_parser)
|
||||
import_parser.set_defaults(func=CmdImportUrl)
|
||||
|
||||
@@ -75,8 +75,9 @@ def add_parser(subparsers, parent_parser):
|
||||
"--no-scm",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Initiate DVC in directory that is "
|
||||
"not tracked by any SCM tool (e.g. Git).",
|
||||
help=(
|
||||
"Initiate DVC in directory that is not tracked by any SCM tool (e.g. Git)."
|
||||
),
|
||||
)
|
||||
init_parser.add_argument(
|
||||
"-f",
|
||||
@@ -84,8 +85,7 @@ def add_parser(subparsers, parent_parser):
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=(
|
||||
"Overwrite existing '.dvc/' directory. "
|
||||
"This operation removes local cache."
|
||||
"Overwrite existing '.dvc/' directory. This operation removes local cache."
|
||||
),
|
||||
)
|
||||
init_parser.add_argument(
|
||||
|
||||
@@ -31,7 +31,9 @@ def add_parser(subparsers, parent_parser):
|
||||
"--use-pre-commit-tool",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Install DVC hooks using pre-commit "
|
||||
"(https://pre-commit.com) if it is installed.",
|
||||
help=(
|
||||
"Install DVC hooks using pre-commit "
|
||||
"(https://pre-commit.com) if it is installed."
|
||||
),
|
||||
)
|
||||
install_parser.set_defaults(func=CmdInstall)
|
||||
|
||||
@@ -20,7 +20,7 @@ class LsColors:
|
||||
else:
|
||||
self._codes[code] = color
|
||||
|
||||
def format(self, entry):
|
||||
def format(self, entry): # noqa: A003
|
||||
text = entry["path"]
|
||||
|
||||
if entry.get("isout", False) and "out" in self._codes:
|
||||
|
||||
+13
-31
@@ -1,4 +1,5 @@
|
||||
import argparse
|
||||
from typing import Dict, List
|
||||
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.cli.utils import append_doc_link, fix_subparsers
|
||||
@@ -6,7 +7,6 @@ from dvc.commands.config import CmdConfig
|
||||
from dvc.compare import TabularData
|
||||
from dvc.config import ConfigError
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.types import Dict, List
|
||||
from dvc.ui import ui
|
||||
from dvc.utils import format_link
|
||||
|
||||
@@ -18,7 +18,6 @@ class MachineDisabledError(ConfigError):
|
||||
|
||||
class CmdMachineConfig(CmdConfig):
|
||||
def __init__(self, args):
|
||||
|
||||
super().__init__(args)
|
||||
if not self.config["feature"].get("machine", False):
|
||||
raise MachineDisabledError
|
||||
@@ -163,20 +162,18 @@ class CmdMachineRename(CmdBase):
|
||||
all_config = self.config.load_config_to_level(None)
|
||||
if self.args.new in all_config.get("machine", {}):
|
||||
raise ConfigError(
|
||||
"Rename failed. Machine '{}' already exists.".format(
|
||||
self.args.new
|
||||
)
|
||||
f"Rename failed. Machine '{self.args.new}' already exists."
|
||||
)
|
||||
ui.write(f"Rename machine '{self.args.name}' to '{self.args.new}'.")
|
||||
|
||||
def run(self):
|
||||
|
||||
self._check_before_rename()
|
||||
|
||||
with self.config.edit(self.args.level) as conf:
|
||||
self._check_exists(conf)
|
||||
conf["machine"][self.args.new] = conf["machine"][self.args.name]
|
||||
try:
|
||||
assert self.repo.machine
|
||||
self.repo.machine.rename(self.args.name, self.args.new)
|
||||
except DvcException as error:
|
||||
del conf["machine"][self.args.new]
|
||||
@@ -199,7 +196,7 @@ class CmdMachineDefault(CmdMachineConfig):
|
||||
if self.args.name is None and not self.args.unset:
|
||||
conf = self.config.read(self.args.level)
|
||||
try:
|
||||
print(conf["core"]["machine"])
|
||||
ui.write(conf["core"]["machine"])
|
||||
except KeyError:
|
||||
ui.write("No default machine set")
|
||||
return 1
|
||||
@@ -208,9 +205,7 @@ class CmdMachineDefault(CmdMachineConfig):
|
||||
if self.args.unset:
|
||||
conf["core"].pop("machine", None)
|
||||
else:
|
||||
merged_conf = self.config.load_config_to_level(
|
||||
self.args.level
|
||||
)
|
||||
merged_conf = self.config.load_config_to_level(self.args.level)
|
||||
if (
|
||||
self.args.name in conf["machine"]
|
||||
or self.args.name in merged_conf["machine"]
|
||||
@@ -218,8 +213,7 @@ class CmdMachineDefault(CmdMachineConfig):
|
||||
conf["core"]["machine"] = self.args.name
|
||||
else:
|
||||
raise ConfigError(
|
||||
"default machine must be present in machine "
|
||||
"list."
|
||||
"default machine must be present in machine list."
|
||||
)
|
||||
return 0
|
||||
|
||||
@@ -250,7 +244,6 @@ class CmdMachineStatus(CmdBase):
|
||||
all_status: List[Dict],
|
||||
td: TabularData,
|
||||
):
|
||||
|
||||
if not all_status:
|
||||
row = [
|
||||
name,
|
||||
@@ -324,7 +317,7 @@ def add_parser(subparsers, parent_parser):
|
||||
|
||||
machine_subparsers = machine_parser.add_subparsers(
|
||||
dest="cmd",
|
||||
help="Use `dvc machine CMD --help` for " "command-specific help.",
|
||||
help="Use `dvc machine CMD --help` for command-specific help.",
|
||||
)
|
||||
|
||||
fix_subparsers(machine_subparsers)
|
||||
@@ -342,8 +335,7 @@ def add_parser(subparsers, parent_parser):
|
||||
"cloud",
|
||||
help="Machine cloud. See full list of supported clouds at {}".format(
|
||||
format_link(
|
||||
"https://github.com/iterative/"
|
||||
"terraform-provider-iterative#machine"
|
||||
"https://github.com/iterative/terraform-provider-iterative#machine"
|
||||
)
|
||||
),
|
||||
)
|
||||
@@ -371,9 +363,7 @@ def add_parser(subparsers, parent_parser):
|
||||
help=machine_DEFAULT_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
machine_default_parser.add_argument(
|
||||
"name", nargs="?", help="Name of the machine"
|
||||
)
|
||||
machine_default_parser.add_argument("name", nargs="?", help="Name of the machine")
|
||||
machine_default_parser.add_argument(
|
||||
"-u",
|
||||
"--unset",
|
||||
@@ -413,9 +403,7 @@ def add_parser(subparsers, parent_parser):
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
machine_modify_parser.add_argument("name", help="Name of the machine")
|
||||
machine_modify_parser.add_argument(
|
||||
"option", help="Name of the option to modify."
|
||||
)
|
||||
machine_modify_parser.add_argument("option", help="Name of the option to modify.")
|
||||
machine_modify_parser.add_argument(
|
||||
"value", nargs="?", help="(optional) Value of the option."
|
||||
)
|
||||
@@ -448,9 +436,7 @@ def add_parser(subparsers, parent_parser):
|
||||
help=machine_REMOVE_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
machine_remove_parser.add_argument(
|
||||
"name", help="Name of the machine to remove."
|
||||
)
|
||||
machine_remove_parser.add_argument("name", help="Name of the machine to remove.")
|
||||
machine_remove_parser.set_defaults(func=CmdMachineRemove)
|
||||
|
||||
machine_CREATE_HELP = "Create and start a machine instance."
|
||||
@@ -461,14 +447,10 @@ def add_parser(subparsers, parent_parser):
|
||||
help=machine_CREATE_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
machine_create_parser.add_argument(
|
||||
"name", help="Name of the machine to create."
|
||||
)
|
||||
machine_create_parser.add_argument("name", help="Name of the machine to create.")
|
||||
machine_create_parser.set_defaults(func=CmdMachineCreate)
|
||||
|
||||
machine_STATUS_HELP = (
|
||||
"List the status of running instances for one/all machines."
|
||||
)
|
||||
machine_STATUS_HELP = "List the status of running instances for one/all machines."
|
||||
machine_status_parser = machine_subparsers.add_parser(
|
||||
"status",
|
||||
parents=[parent_parser],
|
||||
|
||||
@@ -14,9 +14,7 @@ class CmdMove(CmdBase):
|
||||
try:
|
||||
self.repo.move(self.args.src, self.args.dst)
|
||||
except DvcException:
|
||||
msg = "failed to move '{}' -> '{}'".format(
|
||||
self.args.src, self.args.dst
|
||||
)
|
||||
msg = f"failed to move '{self.args.src}' -> '{self.args.dst}'"
|
||||
logger.exception(msg)
|
||||
return 1
|
||||
return 0
|
||||
@@ -40,7 +38,5 @@ def add_parser(subparsers, parent_parser):
|
||||
move_parser.add_argument(
|
||||
"src", help="Source path to a data file or directory."
|
||||
).complete = completion.FILE
|
||||
move_parser.add_argument(
|
||||
"dst", help="Destination path."
|
||||
).complete = completion.FILE
|
||||
move_parser.add_argument("dst", help="Destination path.").complete = completion.FILE
|
||||
move_parser.set_defaults(func=CmdMove)
|
||||
|
||||
@@ -79,7 +79,7 @@ def add_parser(subparsers, parent_parser):
|
||||
params_diff_parser.add_argument(
|
||||
"b_rev",
|
||||
nargs="?",
|
||||
help=("New Git commit to compare (defaults to the current workspace)"),
|
||||
help="New Git commit to compare (defaults to the current workspace)",
|
||||
)
|
||||
params_diff_parser.add_argument(
|
||||
"--targets",
|
||||
|
||||
+18
-39
@@ -18,9 +18,7 @@ logger = logging.getLogger(__name__)
|
||||
def _show_json(renderers, split=False):
|
||||
from dvc.render.convert import to_json
|
||||
|
||||
result = {
|
||||
renderer.name: to_json(renderer, split) for renderer in renderers
|
||||
}
|
||||
result = {renderer.name: to_json(renderer, split) for renderer in renderers}
|
||||
ui.write_json(result)
|
||||
|
||||
|
||||
@@ -79,10 +77,8 @@ class CmdPlots(CmdBase):
|
||||
return {k: v for k, v in props.items() if v is not None}
|
||||
|
||||
def _config_files(self):
|
||||
config_files = None
|
||||
if self.args.from_config:
|
||||
config_files = {self.args.from_config}
|
||||
return config_files
|
||||
return {self.args.from_config}
|
||||
|
||||
def _html_template_path(self):
|
||||
html_template_path = self.args.html_template
|
||||
@@ -91,12 +87,11 @@ class CmdPlots(CmdBase):
|
||||
"html_template", None
|
||||
)
|
||||
if html_template_path and not os.path.isabs(html_template_path):
|
||||
html_template_path = os.path.join(
|
||||
self.repo.dvc_dir, html_template_path
|
||||
)
|
||||
return html_template_path
|
||||
assert self.repo.dvc_dir
|
||||
html_template_path = os.path.join(self.repo.dvc_dir, html_template_path)
|
||||
return html_template_path # noqa: RET504
|
||||
|
||||
def run(self):
|
||||
def run(self) -> int: # noqa: C901
|
||||
from pathlib import Path
|
||||
|
||||
from dvc.render.match import match_defs_renderers
|
||||
@@ -107,14 +102,11 @@ class CmdPlots(CmdBase):
|
||||
logger.error("please specify a target for `--show-vega`")
|
||||
return 1
|
||||
if len(self.args.targets) > 1:
|
||||
logger.error(
|
||||
"you can only specify one target for `--show-vega`"
|
||||
)
|
||||
logger.error("you can only specify one target for `--show-vega`")
|
||||
return 1
|
||||
if self.args.json:
|
||||
logger.error(
|
||||
"'--show-vega' and '--json' are mutually exclusive "
|
||||
"options."
|
||||
"'--show-vega' and '--json' are mutually exclusive options."
|
||||
)
|
||||
return 1
|
||||
|
||||
@@ -127,17 +119,14 @@ class CmdPlots(CmdBase):
|
||||
|
||||
if not plots_data:
|
||||
ui.error_write(
|
||||
"No plots were loaded, "
|
||||
"visualization file will not be created."
|
||||
"No plots were loaded, visualization file will not be created."
|
||||
)
|
||||
|
||||
out: str = self.args.out or self.repo.config.get("plots", {}).get(
|
||||
"out_dir", "dvc_plots"
|
||||
)
|
||||
|
||||
renderers_out = (
|
||||
out if self.args.json else os.path.join(out, "static")
|
||||
)
|
||||
renderers_out = out if self.args.json else os.path.join(out, "static")
|
||||
|
||||
renderers = match_defs_renderers(
|
||||
data=plots_data,
|
||||
@@ -223,9 +212,8 @@ class CmdPlotsTemplates(CmdBase):
|
||||
return 0
|
||||
raise InvalidArgumentError(f"Unexpected template: {target}.")
|
||||
|
||||
else:
|
||||
for template in TEMPLATES:
|
||||
ui.write(template.DEFAULT_NAME)
|
||||
for template in TEMPLATES:
|
||||
ui.write(template.DEFAULT_NAME)
|
||||
|
||||
return 0
|
||||
except DvcException:
|
||||
@@ -251,8 +239,7 @@ def add_parser(subparsers, parent_parser):
|
||||
fix_subparsers(plots_subparsers)
|
||||
|
||||
SHOW_HELP = (
|
||||
"Generate plots from target files or from `plots`"
|
||||
" definitions in `dvc.yaml`."
|
||||
"Generate plots from target files or from `plots` definitions in `dvc.yaml`."
|
||||
)
|
||||
plots_show_parser = plots_subparsers.add_parser(
|
||||
"show",
|
||||
@@ -275,8 +262,7 @@ def add_parser(subparsers, parent_parser):
|
||||
plots_show_parser.set_defaults(func=CmdPlotsShow)
|
||||
|
||||
PLOTS_DIFF_HELP = (
|
||||
"Show multiple versions of a plot by overlaying them "
|
||||
"in a single image."
|
||||
"Show multiple versions of a plot by overlaying them in a single image."
|
||||
)
|
||||
plots_diff_parser = plots_subparsers.add_parser(
|
||||
"diff",
|
||||
@@ -334,9 +320,7 @@ def add_parser(subparsers, parent_parser):
|
||||
)
|
||||
plots_modify_parser.set_defaults(func=CmdPlotsModify)
|
||||
|
||||
TEMPLATES_HELP = (
|
||||
"List built-in plots templates or show JSON specification for one."
|
||||
)
|
||||
TEMPLATES_HELP = "List built-in plots templates or show JSON specification for one."
|
||||
plots_templates_parser = plots_subparsers.add_parser(
|
||||
"templates",
|
||||
parents=[parent_parser],
|
||||
@@ -362,11 +346,8 @@ def _add_props_arguments(parser):
|
||||
"--template",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help=(
|
||||
"Special JSON or HTML schema file to inject with the data. "
|
||||
"See {}".format(
|
||||
format_link("https://man.dvc.org/plots#plot-templates")
|
||||
)
|
||||
help="Special JSON or HTML schema file to inject with the data. See {}".format(
|
||||
format_link("https://man.dvc.org/plots#plot-templates")
|
||||
),
|
||||
metavar="<path>",
|
||||
).complete = completion.FILE
|
||||
@@ -383,9 +364,7 @@ def _add_props_arguments(parser):
|
||||
default=None, # Use default None to distinguish when it's not used
|
||||
help="Provided CSV or TSV datafile does not have a header.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--title", default=None, metavar="<text>", help="Plot title."
|
||||
)
|
||||
parser.add_argument("--title", default=None, metavar="<text>", help="Plot title.")
|
||||
parser.add_argument(
|
||||
"--x-label", default=None, help="X axis label", metavar="<text>"
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@ def add_parser(subparsers, parent_parser):
|
||||
|
||||
queue_subparsers = queue_parser.add_subparsers(
|
||||
dest="cmd",
|
||||
help="Use `dvc queue CMD --help` to display " "command-specific help.",
|
||||
help="Use `dvc queue CMD --help` to display command-specific help.",
|
||||
)
|
||||
|
||||
fix_subparsers(queue_subparsers)
|
||||
|
||||
@@ -20,8 +20,7 @@ class CmdQueueKill(CmdBase):
|
||||
|
||||
def add_parser(queue_subparsers, parent_parser):
|
||||
QUEUE_KILL_HELP = (
|
||||
"Gracefully interrupt running experiment queue tasks "
|
||||
"(equivalent to Ctrl-C)"
|
||||
"Gracefully interrupt running experiment queue tasks (equivalent to Ctrl-C)"
|
||||
)
|
||||
queue_kill_parser = queue_subparsers.add_parser(
|
||||
"kill",
|
||||
|
||||
@@ -34,9 +34,7 @@ def add_parser(queue_subparsers, parent_parser):
|
||||
queue_logs_parser.add_argument(
|
||||
"-e",
|
||||
"--encoding",
|
||||
help=(
|
||||
"Text encoding for log output. Defaults to system locale encoding."
|
||||
),
|
||||
help="Text encoding for log output. Defaults to system locale encoding.",
|
||||
metavar="<encoding>",
|
||||
)
|
||||
queue_logs_parser.add_argument(
|
||||
|
||||
@@ -28,7 +28,6 @@ class CmdQueueRemove(CmdBase):
|
||||
)
|
||||
|
||||
def run(self):
|
||||
|
||||
self.check_arguments()
|
||||
|
||||
if self.args.all:
|
||||
@@ -57,7 +56,6 @@ class CmdQueueRemove(CmdBase):
|
||||
|
||||
|
||||
def add_parser(queue_subparsers, parent_parser):
|
||||
|
||||
QUEUE_REMOVE_HELP = "Remove queued and completed tasks from the queue."
|
||||
queue_remove_parser = queue_subparsers.add_parser(
|
||||
"remove",
|
||||
|
||||
@@ -12,20 +12,15 @@ class CmdQueueStart(CmdBase):
|
||||
"""Start exp queue workers."""
|
||||
|
||||
def run(self):
|
||||
started = self.repo.experiments.celery_queue.start_workers(
|
||||
self.args.jobs
|
||||
)
|
||||
started = self.repo.experiments.celery_queue.start_workers(self.args.jobs)
|
||||
|
||||
suffix = "s" if started > 1 else ""
|
||||
ui.write(
|
||||
f"Started '{started}' new experiments task queue worker{suffix}."
|
||||
)
|
||||
ui.write(f"Started '{started}' new experiments task queue worker{suffix}.")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def add_parser(queue_subparsers, parent_parser):
|
||||
|
||||
QUEUE_START_HELP = "Start the experiments task queue worker."
|
||||
queue_start_parser = queue_subparsers.add_parser(
|
||||
"start",
|
||||
@@ -39,10 +34,7 @@ def add_parser(queue_subparsers, parent_parser):
|
||||
"--jobs",
|
||||
type=int,
|
||||
default=1,
|
||||
help=(
|
||||
"Maximum number of concurrent queue workers to start. "
|
||||
"Defaults to 1."
|
||||
),
|
||||
help="Maximum number of concurrent queue workers to start. Defaults to 1.",
|
||||
metavar="<number>",
|
||||
)
|
||||
queue_start_parser.set_defaults(func=CmdQueueStart)
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
import argparse
|
||||
import logging
|
||||
from typing import List, Mapping, Optional
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.commands.experiments.show import format_time
|
||||
from dvc.compare import TabularData
|
||||
from dvc.ui import ui
|
||||
|
||||
from ..experiments.show import format_time
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CmdQueueStatus(CmdBase):
|
||||
"""Show queue task and worker status."""
|
||||
|
||||
def run(self):
|
||||
def run(self) -> int:
|
||||
result: List[
|
||||
Mapping[str, Optional[str]]
|
||||
Dict[str, Optional[str]]
|
||||
] = self.repo.experiments.celery_queue.status()
|
||||
if result:
|
||||
all_headers = ["Task", "Name", "Created", "Status"]
|
||||
td = TabularData(all_headers)
|
||||
for exp in result:
|
||||
created = format_time(exp.get("timestamp"))
|
||||
assert exp["rev"]
|
||||
assert exp["status"]
|
||||
td.append(
|
||||
[
|
||||
exp["rev"][:7],
|
||||
exp.get("name", ""),
|
||||
exp.get("name") or "",
|
||||
created,
|
||||
exp["status"],
|
||||
]
|
||||
@@ -38,9 +39,7 @@ class CmdQueueStatus(CmdBase):
|
||||
ui.write()
|
||||
|
||||
worker_status = self.repo.experiments.celery_queue.worker_status()
|
||||
active_count = len(
|
||||
[name for name, task in worker_status.items() if task]
|
||||
)
|
||||
active_count = len([name for name, task in worker_status.items() if task])
|
||||
idle_count = len(worker_status) - active_count
|
||||
|
||||
ui.write(f"Worker status: {active_count} active, {idle_count} idle")
|
||||
@@ -49,9 +48,7 @@ class CmdQueueStatus(CmdBase):
|
||||
|
||||
|
||||
def add_parser(queue_subparsers, parent_parser):
|
||||
QUEUE_STATUS_HELP = (
|
||||
"Show the status of experiments queue tasks and workers."
|
||||
)
|
||||
QUEUE_STATUS_HELP = "Show the status of experiments queue tasks and workers."
|
||||
queue_status_parser = queue_subparsers.add_parser(
|
||||
"status",
|
||||
parents=[parent_parser],
|
||||
|
||||
@@ -26,7 +26,6 @@ class CmdQueueStop(CmdBase):
|
||||
|
||||
|
||||
def add_parser(queue_subparsers, parent_parser):
|
||||
|
||||
QUEUE_STOP_HELP = "Stop all experiments task queue workers."
|
||||
queue_stop_parser = queue_subparsers.add_parser(
|
||||
"stop",
|
||||
|
||||
+5
-13
@@ -95,9 +95,7 @@ class CmdRemoteDefault(CmdRemote):
|
||||
if self.args.unset:
|
||||
conf["core"].pop("remote", None)
|
||||
else:
|
||||
merged_conf = self.config.load_config_to_level(
|
||||
self.args.level
|
||||
)
|
||||
merged_conf = self.config.load_config_to_level(self.args.level)
|
||||
if (
|
||||
self.args.name in conf["remote"]
|
||||
or self.args.name in merged_conf["remote"]
|
||||
@@ -164,7 +162,7 @@ def add_parser(subparsers, parent_parser):
|
||||
|
||||
remote_subparsers = remote_parser.add_subparsers(
|
||||
dest="cmd",
|
||||
help="Use `dvc remote CMD --help` for " "command-specific help.",
|
||||
help="Use `dvc remote CMD --help` for command-specific help.",
|
||||
)
|
||||
|
||||
fix_subparsers(remote_subparsers)
|
||||
@@ -208,9 +206,7 @@ def add_parser(subparsers, parent_parser):
|
||||
help=REMOTE_DEFAULT_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
remote_default_parser.add_argument(
|
||||
"name", nargs="?", help="Name of the remote"
|
||||
)
|
||||
remote_default_parser.add_argument("name", nargs="?", help="Name of the remote")
|
||||
remote_default_parser.add_argument(
|
||||
"-u",
|
||||
"--unset",
|
||||
@@ -229,9 +225,7 @@ def add_parser(subparsers, parent_parser):
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
remote_modify_parser.add_argument("name", help="Name of the remote")
|
||||
remote_modify_parser.add_argument(
|
||||
"option", help="Name of the option to modify."
|
||||
)
|
||||
remote_modify_parser.add_argument("option", help="Name of the option to modify.")
|
||||
remote_modify_parser.add_argument(
|
||||
"value", nargs="?", help="(optional) Value of the option."
|
||||
)
|
||||
@@ -262,9 +256,7 @@ def add_parser(subparsers, parent_parser):
|
||||
help=REMOTE_REMOVE_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
remote_remove_parser.add_argument(
|
||||
"name", help="Name of the remote to remove."
|
||||
)
|
||||
remote_remove_parser.add_argument("name", help="Name of the remote to remove.")
|
||||
remote_remove_parser.set_defaults(func=CmdRemoteRemove)
|
||||
REMOTE_RENAME_HELP = "Rename a DVC remote"
|
||||
remote_rename_parser = remote_subparsers.add_parser(
|
||||
|
||||
@@ -22,8 +22,7 @@ class CmdRemove(CmdBase):
|
||||
|
||||
def add_parser(subparsers, parent_parser):
|
||||
REMOVE_HELP = (
|
||||
"Remove stages from dvc.yaml and/or"
|
||||
" stop tracking files or directories."
|
||||
"Remove stages from dvc.yaml and/or stop tracking files or directories."
|
||||
)
|
||||
remove_parser = subparsers.add_parser(
|
||||
"remove",
|
||||
|
||||
+13
-18
@@ -10,9 +10,7 @@ class CmdRepro(CmdBase):
|
||||
def run(self):
|
||||
from dvc.ui import ui
|
||||
|
||||
stages = self.repo.reproduce(
|
||||
**self._common_kwargs, **self._repro_kwargs
|
||||
)
|
||||
stages = self.repo.reproduce(**self._common_kwargs, **self._repro_kwargs)
|
||||
if len(stages) == 0:
|
||||
ui.write(CmdDataStatus.UP_TO_DATE_MSG)
|
||||
else:
|
||||
@@ -55,16 +53,14 @@ def add_arguments(repro_parser):
|
||||
repro_parser.add_argument(
|
||||
"targets",
|
||||
nargs="*",
|
||||
help=(
|
||||
"""\
|
||||
help="""\
|
||||
Stages to reproduce. 'dvc.yaml' by default.
|
||||
The targets can be path to a dvc.yaml file or `.dvc` file,
|
||||
or a stage name from dvc.yaml file from
|
||||
current working directory. To run a stage from dvc.yaml
|
||||
from other directories, the target must be a path followed by colon `:`
|
||||
and then the stage name name.
|
||||
"""
|
||||
),
|
||||
""",
|
||||
).complete = completion.DVCFILES_AND_STAGE
|
||||
repro_parser.add_argument(
|
||||
"-f",
|
||||
@@ -85,16 +81,14 @@ and then the stage name name.
|
||||
"--single-item",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Reproduce only single data item without recursive dependencies "
|
||||
"check.",
|
||||
help="Reproduce only single data item without recursive dependencies check.",
|
||||
)
|
||||
repro_parser.add_argument(
|
||||
"-p",
|
||||
"--pipeline",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Reproduce the whole pipeline that the specified targets "
|
||||
"belong to.",
|
||||
help="Reproduce the whole pipeline that the specified targets belong to.",
|
||||
)
|
||||
repro_parser.add_argument(
|
||||
"-P",
|
||||
@@ -127,8 +121,10 @@ and then the stage name name.
|
||||
"--force-downstream",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Reproduce all descendants of a changed stage even if their "
|
||||
"direct dependencies didn't change.",
|
||||
help=(
|
||||
"Reproduce all descendants of a changed stage even if their "
|
||||
"direct dependencies didn't change."
|
||||
),
|
||||
)
|
||||
repro_parser.add_argument(
|
||||
"--pull",
|
||||
@@ -143,15 +139,14 @@ and then the stage name name.
|
||||
"--dry",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Only print the commands that would be executed without "
|
||||
"actually executing.",
|
||||
help=(
|
||||
"Only print the commands that would be executed without actually executing."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def add_parser(subparsers, parent_parser):
|
||||
REPRO_HELP = (
|
||||
"Reproduce complete or partial pipelines by executing their stages."
|
||||
)
|
||||
REPRO_HELP = "Reproduce complete or partial pipelines by executing their stages."
|
||||
repro_parser = subparsers.add_parser(
|
||||
"repro",
|
||||
parents=[parent_parser],
|
||||
|
||||
+3
-7
@@ -39,7 +39,7 @@ class CmdRun(CmdBase):
|
||||
{
|
||||
"cmd": parse_cmd(self.args.command),
|
||||
"fname": kwargs.pop("file"),
|
||||
"no_exec": (self.args.no_exec or bool(self.args.checkpoints)),
|
||||
"no_exec": self.args.no_exec or bool(self.args.checkpoints),
|
||||
"run_cache": not kwargs.pop("no_run_cache"),
|
||||
}
|
||||
)
|
||||
@@ -55,9 +55,7 @@ class CmdRun(CmdBase):
|
||||
def add_parser(subparsers, parent_parser):
|
||||
from dvc.commands.stage import _add_common_args
|
||||
|
||||
RUN_HELP = (
|
||||
"Generate a dvc.yaml file from a command and execute the command."
|
||||
)
|
||||
RUN_HELP = "Generate a dvc.yaml file from a command and execute the command."
|
||||
run_parser = subparsers.add_parser(
|
||||
"run",
|
||||
parents=[parent_parser],
|
||||
@@ -66,9 +64,7 @@ def add_parser(subparsers, parent_parser):
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
run_parser.add_argument("-n", "--name", help="Stage name.")
|
||||
run_parser.add_argument(
|
||||
"--file", metavar="<filename>", help=argparse.SUPPRESS
|
||||
)
|
||||
run_parser.add_argument("--file", metavar="<filename>", help=argparse.SUPPRESS)
|
||||
run_parser.add_argument(
|
||||
"--single-stage",
|
||||
action="store_true",
|
||||
|
||||
+17
-19
@@ -44,9 +44,7 @@ def generate_description(stage: "Stage") -> str:
|
||||
return "; ".join(desc)
|
||||
|
||||
|
||||
def prepare_description(
|
||||
stage: "Stage", max_length: int = MAX_TEXT_LENGTH
|
||||
) -> str:
|
||||
def prepare_description(stage: "Stage", max_length: int = MAX_TEXT_LENGTH) -> str:
|
||||
desc = stage.short_description() or generate_description(stage)
|
||||
return truncate_text(desc, max_length)
|
||||
|
||||
@@ -57,9 +55,9 @@ def prepare_stages_data(
|
||||
max_length: int = MAX_TEXT_LENGTH,
|
||||
) -> Dict[str, str]:
|
||||
return {
|
||||
stage.addressing: prepare_description(stage, max_length=max_length)
|
||||
if description
|
||||
else ""
|
||||
stage.addressing: (
|
||||
prepare_description(stage, max_length=max_length) if description else ""
|
||||
)
|
||||
for stage in stages
|
||||
}
|
||||
|
||||
@@ -67,7 +65,7 @@ def prepare_stages_data(
|
||||
class CmdStageList(CmdBase):
|
||||
def _get_stages(self) -> Iterable["Stage"]:
|
||||
if self.args.all:
|
||||
stages: List["Stage"] = self.repo.index.stages # type: ignore
|
||||
stages: List["Stage"] = self.repo.index.stages
|
||||
logger.trace( # type: ignore[attr-defined]
|
||||
"%d no. of stages found", len(stages)
|
||||
)
|
||||
@@ -75,9 +73,7 @@ class CmdStageList(CmdBase):
|
||||
|
||||
# removing duplicates while maintaining order
|
||||
collected = chain.from_iterable(
|
||||
self.repo.stage.collect(
|
||||
target=target, recursive=self.args.recursive
|
||||
)
|
||||
self.repo.stage.collect(target=target, recursive=self.args.recursive)
|
||||
for target in self.args.targets
|
||||
)
|
||||
return dict.fromkeys(collected).keys()
|
||||
@@ -95,7 +91,7 @@ class CmdStageList(CmdBase):
|
||||
|
||||
stages = self._get_stages()
|
||||
data = prepare_stages_data(stages, description=not self.args.name_only)
|
||||
ui.table(data.items())
|
||||
ui.table(list(data.items()))
|
||||
|
||||
return 0
|
||||
|
||||
@@ -173,8 +169,7 @@ def _add_common_args(parser):
|
||||
"--outs-no-cache",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Declare output file or directory "
|
||||
"(do not put into DVC cache).",
|
||||
help="Declare output file or directory (do not put into DVC cache).",
|
||||
metavar="<filename>",
|
||||
).complete = completion.FILE
|
||||
parser.add_argument(
|
||||
@@ -182,8 +177,10 @@ def _add_common_args(parser):
|
||||
"--checkpoints",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Declare checkpoint output file or directory for 'dvc exp run'. "
|
||||
"Not compatible with 'dvc repro'.",
|
||||
help=(
|
||||
"Declare checkpoint output file or directory for 'dvc exp run'. "
|
||||
"Not compatible with 'dvc repro'."
|
||||
),
|
||||
metavar="<filename>",
|
||||
).complete = completion.FILE
|
||||
parser.add_argument(
|
||||
@@ -196,16 +193,17 @@ def _add_common_args(parser):
|
||||
"--outs-persist",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Declare output file or directory that will not be "
|
||||
"removed upon repro.",
|
||||
help="Declare output file or directory that will not be removed upon repro.",
|
||||
metavar="<filename>",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--outs-persist-no-cache",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Declare output file or directory that will not be "
|
||||
"removed upon repro (do not put into DVC cache).",
|
||||
help=(
|
||||
"Declare output file or directory that will not be "
|
||||
"removed upon repro (do not put into DVC cache)."
|
||||
),
|
||||
metavar="<filename>",
|
||||
)
|
||||
parser.add_argument(
|
||||
|
||||
@@ -81,9 +81,7 @@ class CmdDataStatus(CmdDataBase):
|
||||
if not self.repo.index.stages:
|
||||
ui.write(self.EMPTY_PROJECT_MSG)
|
||||
elif self.args.cloud or self.args.remote:
|
||||
remote = self.args.remote or self.repo.config["core"].get(
|
||||
"remote"
|
||||
)
|
||||
remote = self.args.remote or self.repo.config["core"].get("remote")
|
||||
ui.write(self.IN_SYNC_MSG.format(remote=remote))
|
||||
else:
|
||||
ui.write(self.UP_TO_DATE_MSG)
|
||||
|
||||
@@ -61,8 +61,10 @@ def add_parser(subparsers, parent_parser):
|
||||
"--no-download",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Update .dvc file git revision/hash value(s)"
|
||||
" but do not download the file(s).",
|
||||
help=(
|
||||
"Update .dvc file git revision/hash value(s)"
|
||||
" but do not download the file(s)."
|
||||
),
|
||||
)
|
||||
update_parser.add_argument(
|
||||
"--to-remote",
|
||||
|
||||
@@ -21,9 +21,7 @@ class CmdVersion(CmdBaseNoRepo):
|
||||
|
||||
|
||||
def add_parser(subparsers, parent_parser):
|
||||
VERSION_HELP = (
|
||||
"Display the DVC version and system/environment information."
|
||||
)
|
||||
VERSION_HELP = "Display the DVC version and system/environment information."
|
||||
version_parser = subparsers.add_parser(
|
||||
"version",
|
||||
parents=[parent_parser],
|
||||
|
||||
+33
-49
@@ -58,15 +58,13 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
return self._columns[name]
|
||||
|
||||
def items(self) -> ItemsView[str, Column]:
|
||||
projection = {k: self.column(k) for k in self.keys()}
|
||||
projection = {k: self.column(k) for k in self.keys()} # noqa: SIM118
|
||||
return projection.items()
|
||||
|
||||
def keys(self) -> List[str]:
|
||||
return self._keys
|
||||
|
||||
def _iter_col_row(
|
||||
self, row: Sequence["CellT"]
|
||||
) -> Iterator[Tuple["CellT", Column]]:
|
||||
def _iter_col_row(self, row: Sequence["CellT"]) -> Iterator[Tuple["CellT", Column]]:
|
||||
for val, col in zip_longest(row, self.columns):
|
||||
if col is None:
|
||||
break
|
||||
@@ -103,9 +101,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __setitem__(
|
||||
self, item: slice, value: Iterable[Sequence["CellT"]]
|
||||
) -> None:
|
||||
def __setitem__(self, item: slice, value: Iterable[Sequence["CellT"]]) -> None:
|
||||
...
|
||||
|
||||
def __setitem__(self, item, value) -> None:
|
||||
@@ -113,8 +109,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
if isinstance(item, slice):
|
||||
n = len(self.columns)
|
||||
normalized_rows = (
|
||||
chain(val, repeat(self._fill_value, n - len(val)))
|
||||
for val in value
|
||||
chain(val, repeat(self._fill_value, n - len(val))) for val in value
|
||||
)
|
||||
# we need to transpose those rows into columnar format
|
||||
# as we work in terms of column-based arrays
|
||||
@@ -167,7 +162,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
return buff.getvalue()
|
||||
|
||||
def to_parallel_coordinates(
|
||||
self, output_path: "StrPath", color_by: str = None
|
||||
self, output_path: "StrPath", color_by: Optional[str] = None
|
||||
) -> "StrPath":
|
||||
from dvc_render.html import render_html
|
||||
from dvc_render.plotly import ParallelCoordinatesRenderer
|
||||
@@ -195,7 +190,8 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
self.add_column(key)
|
||||
|
||||
row: List["CellT"] = [
|
||||
with_value(d.get(key), self._fill_value) for key in self.keys()
|
||||
with_value(d.get(key), self._fill_value)
|
||||
for key in self.keys() # noqa: SIM118
|
||||
]
|
||||
self.append(row)
|
||||
|
||||
@@ -208,14 +204,12 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
ui.table(self, headers=self.keys(), **kwargs)
|
||||
|
||||
def as_dict(
|
||||
self, cols: Iterable[str] = None
|
||||
self, cols: Optional[Iterable[str]] = None
|
||||
) -> Iterable[Dict[str, "CellT"]]:
|
||||
keys = self.keys() if cols is None else set(cols)
|
||||
return [
|
||||
{k: self._columns[k][i] for k in keys} for i in range(len(self))
|
||||
]
|
||||
return [{k: self._columns[k][i] for k in keys} for i in range(len(self))]
|
||||
|
||||
def dropna(
|
||||
def dropna( # noqa: C901
|
||||
self,
|
||||
axis: str = "rows",
|
||||
how="any",
|
||||
@@ -223,13 +217,10 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
):
|
||||
if axis not in ["rows", "cols"]:
|
||||
raise ValueError(
|
||||
f"Invalid 'axis' value {axis}."
|
||||
"Choose one of ['rows', 'cols']"
|
||||
f"Invalid 'axis' value {axis}.Choose one of ['rows', 'cols']"
|
||||
)
|
||||
if how not in ["any", "all"]:
|
||||
raise ValueError(
|
||||
f"Invalid 'how' value {how}." "Choose one of ['any', 'all']"
|
||||
)
|
||||
raise ValueError(f"Invalid 'how' value {how}. Choose one of ['any', 'all']")
|
||||
|
||||
match_line: Set = set()
|
||||
match_any = True
|
||||
@@ -244,8 +235,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
if axis == "rows":
|
||||
match_line.add(n_row)
|
||||
break
|
||||
else:
|
||||
match_line.add(self.keys()[n_col])
|
||||
match_line.add(self.keys()[n_col])
|
||||
|
||||
to_drop = match_line
|
||||
if how == "all":
|
||||
@@ -256,18 +246,14 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
to_drop -= match_line
|
||||
|
||||
if axis == "rows":
|
||||
for name in self.keys():
|
||||
for name in self.keys(): # noqa: SIM118
|
||||
self._columns[name] = Column(
|
||||
[
|
||||
x
|
||||
for n, x in enumerate(self._columns[name])
|
||||
if n not in to_drop
|
||||
]
|
||||
[x for n, x in enumerate(self._columns[name]) if n not in to_drop]
|
||||
)
|
||||
else:
|
||||
self.drop(*to_drop)
|
||||
|
||||
def drop_duplicates(
|
||||
def drop_duplicates( # noqa: C901
|
||||
self,
|
||||
axis: str = "rows",
|
||||
subset: Optional[Iterable[str]] = None,
|
||||
@@ -275,8 +261,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
):
|
||||
if axis not in ["rows", "cols"]:
|
||||
raise ValueError(
|
||||
f"Invalid 'axis' value {axis}."
|
||||
"Choose one of ['rows', 'cols']"
|
||||
f"Invalid 'axis' value {axis}.Choose one of ['rows', 'cols']"
|
||||
)
|
||||
|
||||
if axis == "cols":
|
||||
@@ -309,7 +294,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
else:
|
||||
unique_rows.append(tuple_row)
|
||||
|
||||
for name in self.keys():
|
||||
for name in self.keys(): # noqa: SIM118
|
||||
self._columns[name] = Column(
|
||||
[
|
||||
x
|
||||
@@ -324,12 +309,13 @@ def _normalize_float(val: float, precision: int):
|
||||
|
||||
|
||||
def _format_field(
|
||||
val: Any, precision: int = None, round_digits: bool = False
|
||||
val: Any, precision: Optional[int] = None, round_digits: bool = False
|
||||
) -> str:
|
||||
def _format(_val):
|
||||
if isinstance(_val, float) and precision:
|
||||
func = round if round_digits else _normalize_float
|
||||
return func(_val, precision)
|
||||
if round_digits:
|
||||
return round(_val, precision)
|
||||
return _normalize_float(_val, precision)
|
||||
if isinstance(_val, abc.Mapping):
|
||||
return {k: _format(v) for k, v in _val.items()}
|
||||
if isinstance(_val, list):
|
||||
@@ -345,11 +331,11 @@ def diff_table(
|
||||
old: bool = True,
|
||||
no_path: bool = False,
|
||||
show_changes: bool = True,
|
||||
precision: int = None,
|
||||
precision: Optional[int] = None,
|
||||
round_digits: bool = False,
|
||||
on_empty_diff: str = None,
|
||||
a_rev: str = None,
|
||||
b_rev: str = None,
|
||||
on_empty_diff: Optional[str] = None,
|
||||
a_rev: Optional[str] = None,
|
||||
b_rev: Optional[str] = None,
|
||||
) -> TabularData:
|
||||
a_rev = a_rev or "HEAD"
|
||||
b_rev = b_rev or "workspace"
|
||||
@@ -361,9 +347,7 @@ def diff_table(
|
||||
for item, change in sorted(diff_in_file.items()):
|
||||
old_value = with_value(change.get("old"), fill_value)
|
||||
new_value = with_value(change.get("new"), fill_value)
|
||||
diff_value = with_value(
|
||||
change.get("diff", on_empty_diff), fill_value
|
||||
)
|
||||
diff_value = with_value(change.get("diff", on_empty_diff), fill_value)
|
||||
td.append(
|
||||
[
|
||||
fname,
|
||||
@@ -393,12 +377,12 @@ def show_diff(
|
||||
old: bool = True,
|
||||
no_path: bool = False,
|
||||
show_changes: bool = True,
|
||||
precision: int = None,
|
||||
precision: Optional[int] = None,
|
||||
round_digits: bool = False,
|
||||
on_empty_diff: str = None,
|
||||
on_empty_diff: Optional[str] = None,
|
||||
markdown: bool = False,
|
||||
a_rev: str = None,
|
||||
b_rev: str = None,
|
||||
a_rev: Optional[str] = None,
|
||||
b_rev: Optional[str] = None,
|
||||
) -> None:
|
||||
td = diff_table(
|
||||
diff,
|
||||
@@ -420,7 +404,7 @@ def metrics_table(
|
||||
all_branches: bool = False,
|
||||
all_tags: bool = False,
|
||||
all_commits: bool = False,
|
||||
precision: int = None,
|
||||
precision: Optional[int] = None,
|
||||
round_digits: bool = False,
|
||||
):
|
||||
from dvc.utils.diff import format_dict
|
||||
@@ -460,7 +444,7 @@ def show_metrics(
|
||||
all_branches: bool = False,
|
||||
all_tags: bool = False,
|
||||
all_commits: bool = False,
|
||||
precision: int = None,
|
||||
precision: Optional[int] = None,
|
||||
round_digits: bool = False,
|
||||
) -> None:
|
||||
td = metrics_table(
|
||||
|
||||
+23
-11
@@ -4,11 +4,18 @@ import os
|
||||
import re
|
||||
from contextlib import contextmanager
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, Dict, Optional
|
||||
|
||||
from funcy import cached_property, compact, memoize, re_find
|
||||
from funcy import compact, memoize, re_find
|
||||
|
||||
from dvc.exceptions import DvcException, NotDvcRepoError
|
||||
|
||||
from .utils.objects import cached_property
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dvc.fs import FileSystem
|
||||
from dvc.types import DictStrAny, StrPath
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -84,7 +91,11 @@ class Config(dict):
|
||||
CONFIG_LOCAL = "config.local"
|
||||
|
||||
def __init__(
|
||||
self, dvc_dir=None, validate=True, fs=None, config=None
|
||||
self,
|
||||
dvc_dir: Optional["StrPath"] = None,
|
||||
validate: bool = True,
|
||||
fs: Optional["FileSystem"] = None,
|
||||
config: Optional["DictStrAny"] = None,
|
||||
): # pylint: disable=super-init-not-called
|
||||
from dvc.fs import LocalFileSystem
|
||||
|
||||
@@ -116,7 +127,7 @@ class Config(dict):
|
||||
return site_config_dir(cls.APPNAME, cls.APPAUTHOR)
|
||||
|
||||
@cached_property
|
||||
def files(self):
|
||||
def files(self) -> Dict[str, str]:
|
||||
files = {
|
||||
level: os.path.join(self.get_dir(level), self.CONFIG)
|
||||
for level in ("system", "global")
|
||||
@@ -139,10 +150,10 @@ class Config(dict):
|
||||
dvc.config.Config: config object.
|
||||
"""
|
||||
config_file = os.path.join(dvc_dir, Config.CONFIG)
|
||||
open(config_file, "w+", encoding="utf-8").close()
|
||||
return Config(dvc_dir)
|
||||
with open(config_file, "w+", encoding="utf-8"):
|
||||
return Config(dvc_dir)
|
||||
|
||||
def load(self, validate=True, config=None):
|
||||
def load(self, validate: bool = True, config: Optional["DictStrAny"] = None):
|
||||
"""Loads config from all the config files.
|
||||
|
||||
Raises:
|
||||
@@ -169,7 +180,7 @@ class Config(dict):
|
||||
return self.fs if level == "repo" else self.wfs
|
||||
|
||||
def _load_config(self, level):
|
||||
from configobj import ConfigObj
|
||||
from configobj import ConfigObj, ConfigObjError
|
||||
|
||||
filename = self.files[level]
|
||||
fs = self._get_fs(level)
|
||||
@@ -180,6 +191,8 @@ class Config(dict):
|
||||
conf_obj = ConfigObj(fobj)
|
||||
except UnicodeDecodeError as exc:
|
||||
raise ConfigError(str(exc)) from exc
|
||||
except ConfigObjError as exc:
|
||||
raise ConfigError(str(exc)) from exc
|
||||
else:
|
||||
conf_obj = ConfigObj()
|
||||
return _parse_named(_lower_keys(conf_obj.dict()))
|
||||
@@ -278,7 +291,7 @@ class Config(dict):
|
||||
return Schema(dirs_schema, extra=ALLOW_EXTRA)(conf)
|
||||
|
||||
def load_config_to_level(self, level=None):
|
||||
merged_conf = {}
|
||||
merged_conf: Dict = {}
|
||||
for merge_level in self.LEVELS:
|
||||
if merge_level == level:
|
||||
break
|
||||
@@ -324,7 +337,7 @@ class Config(dict):
|
||||
|
||||
|
||||
def _parse_named(conf):
|
||||
result = {"remote": {}, "machine": {}}
|
||||
result: Dict[str, Dict] = {"remote": {}, "machine": {}}
|
||||
|
||||
for section, val in conf.items():
|
||||
match = re_find(r'^\s*(remote|machine)\s*"(.*)"\s*$', section)
|
||||
@@ -361,6 +374,5 @@ def merge(into, update):
|
||||
|
||||
def _lower_keys(data):
|
||||
return {
|
||||
k.lower(): _lower_keys(v) if isinstance(v, dict) else v
|
||||
for k, v in data.items()
|
||||
k.lower(): _lower_keys(v) if isinstance(v, dict) else v for k, v in data.items()
|
||||
}
|
||||
|
||||
+4
-16
@@ -2,16 +2,7 @@ import os
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from funcy import walk_values
|
||||
from voluptuous import (
|
||||
All,
|
||||
Any,
|
||||
Coerce,
|
||||
Invalid,
|
||||
Lower,
|
||||
Optional,
|
||||
Range,
|
||||
Schema,
|
||||
)
|
||||
from voluptuous import All, Any, Coerce, Invalid, Lower, Optional, Range, Schema
|
||||
|
||||
Bool = All(
|
||||
Lower,
|
||||
@@ -34,9 +25,7 @@ def supported_cache_type(types):
|
||||
|
||||
unsupported = set(types) - {"reflink", "hardlink", "symlink", "copy"}
|
||||
if unsupported:
|
||||
raise Invalid(
|
||||
"Unsupported cache type(s): {}".format(", ".join(unsupported))
|
||||
)
|
||||
raise Invalid("Unsupported cache type(s): {}".format(", ".join(unsupported)))
|
||||
|
||||
return types
|
||||
|
||||
@@ -106,6 +95,7 @@ WEBDAV_COMMON = {
|
||||
"password": str,
|
||||
"ask_password": Bool,
|
||||
"token": str,
|
||||
"custom_auth_header": str,
|
||||
"cert_path": str,
|
||||
"key_path": str,
|
||||
"timeout": Coerce(int),
|
||||
@@ -257,9 +247,7 @@ SCHEMA = {
|
||||
"machine": {
|
||||
str: {
|
||||
"cloud": All(Lower, Choices("aws", "azure")),
|
||||
"region": All(
|
||||
Lower, Choices("us-west", "us-east", "eu-west", "eu-north")
|
||||
),
|
||||
"region": All(Lower, Choices("us-west", "us-east", "eu-west", "eu-north")),
|
||||
"image": str,
|
||||
"spot": Bool,
|
||||
"spot_price": Coerce(float),
|
||||
|
||||
+19
-16
@@ -25,21 +25,22 @@ def _popen(cmd, **kwargs):
|
||||
|
||||
|
||||
def _spawn_windows(cmd, env):
|
||||
from subprocess import (
|
||||
CREATE_NEW_PROCESS_GROUP,
|
||||
CREATE_NO_WINDOW,
|
||||
STARTF_USESHOWWINDOW,
|
||||
STARTUPINFO,
|
||||
)
|
||||
if sys.platform == "win32":
|
||||
from subprocess import (
|
||||
CREATE_NEW_PROCESS_GROUP,
|
||||
CREATE_NO_WINDOW,
|
||||
STARTF_USESHOWWINDOW,
|
||||
STARTUPINFO,
|
||||
)
|
||||
|
||||
# https://stackoverflow.com/a/7006424
|
||||
# https://bugs.python.org/issue41619
|
||||
creationflags = CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW
|
||||
# https://stackoverflow.com/a/7006424
|
||||
# https://bugs.python.org/issue41619
|
||||
creationflags = CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW
|
||||
|
||||
startupinfo = STARTUPINFO()
|
||||
startupinfo.dwFlags |= STARTF_USESHOWWINDOW
|
||||
startupinfo = STARTUPINFO()
|
||||
startupinfo.dwFlags |= STARTF_USESHOWWINDOW
|
||||
|
||||
_popen(cmd, env=env, creationflags=creationflags, startupinfo=startupinfo)
|
||||
_popen(cmd, env=env, creationflags=creationflags, startupinfo=startupinfo)
|
||||
|
||||
|
||||
def _spawn_posix(cmd, env):
|
||||
@@ -49,17 +50,19 @@ def _spawn_posix(cmd, env):
|
||||
# with PyInstaller has trouble with SystemExit exception and throws
|
||||
# errors such as "[26338] Failed to execute script __main__"
|
||||
try:
|
||||
pid = os.fork() # pylint: disable=no-member
|
||||
# pylint: disable-next=no-member
|
||||
pid = os.fork() # type: ignore[attr-defined]
|
||||
if pid > 0:
|
||||
return
|
||||
except OSError:
|
||||
logger.exception("failed at first fork")
|
||||
os._exit(1) # pylint: disable=protected-access
|
||||
|
||||
os.setsid() # pylint: disable=no-member
|
||||
os.setsid() # type: ignore[attr-defined] # pylint: disable=no-member
|
||||
|
||||
try:
|
||||
pid = os.fork() # pylint: disable=no-member
|
||||
# pylint: disable-next=no-member
|
||||
pid = os.fork() # type: ignore[attr-defined]
|
||||
if pid > 0:
|
||||
os._exit(0) # pylint: disable=protected-access
|
||||
except OSError:
|
||||
@@ -100,7 +103,7 @@ def daemon(args):
|
||||
Args:
|
||||
args (list): list of arguments to append to `dvc daemon` command.
|
||||
"""
|
||||
daemonize(["daemon", "-q"] + args)
|
||||
daemonize(["daemon", "-q", *args])
|
||||
|
||||
|
||||
def daemonize(cmd: List[str]):
|
||||
|
||||
+3
-6
@@ -59,8 +59,7 @@ class AsciiCanvas:
|
||||
def draw(self):
|
||||
"""Draws ASCII canvas on the screen."""
|
||||
lines = map("".join, self.canvas)
|
||||
joined_lines = os.linesep.join(lines)
|
||||
return joined_lines
|
||||
return os.linesep.join(lines)
|
||||
|
||||
def point(self, x, y, char):
|
||||
"""Create a point on ASCII canvas.
|
||||
@@ -81,7 +80,7 @@ class AsciiCanvas:
|
||||
|
||||
self.canvas[y][x] = char
|
||||
|
||||
def line(self, x0, y0, x1, y1, char):
|
||||
def line(self, x0, y0, x1, y1, char): # noqa: C901
|
||||
"""Create a line on ASCII canvas.
|
||||
|
||||
Args:
|
||||
@@ -273,8 +272,6 @@ def draw(vertices, edges):
|
||||
vertex.view.h,
|
||||
)
|
||||
|
||||
canvas.text(
|
||||
int(round(x - minx)) + 1, int(round(y - miny)) + 1, vertex.data
|
||||
)
|
||||
canvas.text(int(round(x - minx)) + 1, int(round(y - miny)) + 1, vertex.data)
|
||||
|
||||
return canvas.draw()
|
||||
|
||||
+22
-20
@@ -3,12 +3,12 @@
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Iterable, Optional
|
||||
|
||||
from funcy import cached_property
|
||||
|
||||
from dvc.config import NoRemoteError, RemoteConfigError
|
||||
from dvc.utils.objects import cached_property
|
||||
from dvc_data.hashfile.db import get_index
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dvc.fs import FileSystem
|
||||
from dvc_data.hashfile.db import HashFileDB
|
||||
from dvc_data.hashfile.hash_info import HashInfo
|
||||
from dvc_data.hashfile.status import CompareStatusResult
|
||||
@@ -18,23 +18,16 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Remote:
|
||||
def __init__(self, path, fs, **config):
|
||||
def __init__(self, name: str, path: str, fs: "FileSystem", **config):
|
||||
self.path = path
|
||||
self.fs = fs
|
||||
self.name = name
|
||||
|
||||
self.worktree = config.pop("worktree", False)
|
||||
self.worktree: bool = config.pop("worktree", False)
|
||||
self.config = config
|
||||
if self.worktree:
|
||||
version_aware = self.config.get("version_aware")
|
||||
if version_aware is False:
|
||||
raise RemoteConfigError(
|
||||
"worktree remotes require version_aware cloud"
|
||||
)
|
||||
if version_aware is None:
|
||||
self.fs.version_aware = True
|
||||
|
||||
@cached_property
|
||||
def odb(self):
|
||||
def odb(self) -> "HashFileDB":
|
||||
from dvc_data.hashfile.db import get_odb
|
||||
|
||||
path = self.path
|
||||
@@ -70,9 +63,19 @@ class DataCloud:
|
||||
from dvc.fs import get_cloud_fs
|
||||
|
||||
cls, config, fs_path = get_cloud_fs(self.repo, name=name)
|
||||
|
||||
if config.get("worktree"):
|
||||
version_aware = config.get("version_aware")
|
||||
if version_aware is False:
|
||||
raise RemoteConfigError(
|
||||
"worktree remotes require version_aware cloud"
|
||||
)
|
||||
if version_aware is None:
|
||||
config["version_aware"] = True
|
||||
|
||||
fs = cls(**config)
|
||||
config["tmp_dir"] = self.repo.index_db_dir
|
||||
return Remote(fs_path, fs, **config)
|
||||
return Remote(name, fs_path, fs, **config)
|
||||
|
||||
if bool(self.repo.config["remote"]):
|
||||
error_msg = (
|
||||
@@ -108,8 +111,11 @@ class DataCloud:
|
||||
for hash_info in status.missing
|
||||
)
|
||||
logger.warning(
|
||||
"Some of the cache files do not exist neither locally "
|
||||
f"nor on remote. Missing cache files:\n{missing_desc}"
|
||||
(
|
||||
"Some of the cache files do not exist neither locally "
|
||||
"nor on remote. Missing cache files:\n%s"
|
||||
),
|
||||
missing_desc,
|
||||
)
|
||||
|
||||
def transfer(
|
||||
@@ -184,7 +190,6 @@ class DataCloud:
|
||||
jobs: Optional[int] = None,
|
||||
remote: Optional[str] = None,
|
||||
odb: Optional["HashFileDB"] = None,
|
||||
log_missing: bool = True,
|
||||
):
|
||||
"""Check status of data items in a cloud-agnostic way.
|
||||
|
||||
@@ -195,8 +200,6 @@ class DataCloud:
|
||||
cache to. By default remote from core.remote config option
|
||||
is used.
|
||||
odb: optional ODB to check status from. Overrides remote.
|
||||
log_missing: log warning messages if file doesn't exist
|
||||
neither in cache, neither in cloud.
|
||||
"""
|
||||
from dvc_data.hashfile.status import compare_status
|
||||
|
||||
@@ -207,7 +210,6 @@ class DataCloud:
|
||||
odb,
|
||||
objs,
|
||||
jobs=jobs,
|
||||
log_missing=log_missing,
|
||||
dest_index=get_index(odb),
|
||||
cache_odb=self.repo.odb.local,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from collections import defaultdict
|
||||
from typing import Any, Mapping
|
||||
from typing import Any, Mapping, Set
|
||||
|
||||
from dvc.output import ARTIFACT_SCHEMA, DIR_FILES_SCHEMA, Output
|
||||
|
||||
@@ -59,7 +59,7 @@ def _merge_params(s_list):
|
||||
default_file = ParamsDependency.DEFAULT_PARAMS_FILE
|
||||
|
||||
# figure out completely tracked params file, and ignore specific keys
|
||||
wholly_tracked = set()
|
||||
wholly_tracked: Set[str] = set()
|
||||
for key in s_list:
|
||||
if not isinstance(key, dict):
|
||||
continue
|
||||
@@ -74,7 +74,7 @@ def _merge_params(s_list):
|
||||
if not isinstance(key, dict):
|
||||
msg = "Only list of str/dict is supported. Got: "
|
||||
msg += f"'{type(key).__name__}'."
|
||||
raise ValueError(msg)
|
||||
raise ValueError(msg) # noqa: TRY004
|
||||
|
||||
for k, params in key.items():
|
||||
if k in wholly_tracked:
|
||||
@@ -83,13 +83,11 @@ def _merge_params(s_list):
|
||||
if not isinstance(params, list):
|
||||
msg = "Expected list of params for custom params file "
|
||||
msg += f"'{k}', got '{type(params).__name__}'."
|
||||
raise ValueError(msg)
|
||||
raise ValueError(msg) # noqa: TRY004
|
||||
d[k].extend(params)
|
||||
return d
|
||||
|
||||
|
||||
def loads_params(stage, s_list):
|
||||
d = _merge_params(s_list)
|
||||
return [
|
||||
ParamsDependency(stage, path, params) for path, params in d.items()
|
||||
]
|
||||
return [ParamsDependency(stage, path, params) for path, params in d.items()]
|
||||
|
||||
+5
-11
@@ -25,11 +25,9 @@ class DependencyIsStageFileError(DvcException):
|
||||
class Dependency(Output):
|
||||
IS_DEPENDENCY = True
|
||||
|
||||
DoesNotExistError = DependencyDoesNotExistError # type: Type[DvcException]
|
||||
IsNotFileOrDirError = (
|
||||
DependencyIsNotFileOrDirError
|
||||
) # type: Type[DvcException]
|
||||
IsStageFileError = DependencyIsStageFileError # type: Type[DvcException]
|
||||
DoesNotExistError: Type[DvcException] = DependencyDoesNotExistError
|
||||
IsNotFileOrDirError: Type[DvcException] = DependencyIsNotFileOrDirError
|
||||
IsStageFileError: Type[DvcException] = DependencyIsStageFileError
|
||||
|
||||
def workspace_status(self) -> Dict[str, str]:
|
||||
if self.fs.version_aware:
|
||||
@@ -49,9 +47,7 @@ class Dependency(Output):
|
||||
self.def_path = self.fs.path.version_path(
|
||||
self.def_path, self.meta.version_id
|
||||
)
|
||||
self.fs_path = self.fs.path.version_path(
|
||||
self.fs_path, self.meta.version_id
|
||||
)
|
||||
self.fs_path = self.fs.path.version_path(self.fs_path, self.meta.version_id)
|
||||
|
||||
def download(self, to, jobs=None):
|
||||
fs_download(self.fs, self.fs_path, to, jobs=jobs)
|
||||
@@ -59,9 +55,7 @@ class Dependency(Output):
|
||||
def save(self):
|
||||
super().save()
|
||||
if self.fs.version_aware:
|
||||
self.fs_path = self.fs.path.version_path(
|
||||
self.fs_path, self.meta.version_id
|
||||
)
|
||||
self.fs_path = self.fs.path.version_path(self.fs_path, self.meta.version_id)
|
||||
|
||||
def dumpd(self, **kwargs):
|
||||
if self.fs.version_aware:
|
||||
|
||||
+19
-11
@@ -2,7 +2,7 @@ import logging
|
||||
import os
|
||||
import typing
|
||||
from collections import defaultdict
|
||||
from typing import Dict
|
||||
from typing import Dict, cast
|
||||
|
||||
import dpath.util
|
||||
from voluptuous import Any
|
||||
@@ -41,7 +41,10 @@ class ParamsDependency(Dependency):
|
||||
self.params = list(params) if params else []
|
||||
hash_info = HashInfo()
|
||||
if isinstance(params, dict):
|
||||
hash_info = HashInfo(self.PARAM_PARAMS, params)
|
||||
hash_info = HashInfo(
|
||||
self.PARAM_PARAMS,
|
||||
params, # type: ignore[arg-type]
|
||||
)
|
||||
repo = repo or stage.repo
|
||||
path = path or os.path.join(repo.root_dir, self.DEFAULT_PARAMS_FILE)
|
||||
super().__init__(stage, path, repo=repo)
|
||||
@@ -64,7 +67,10 @@ class ParamsDependency(Dependency):
|
||||
for param in self.params:
|
||||
if param in values:
|
||||
info[param] = values[param]
|
||||
self.hash_info = HashInfo(self.PARAM_PARAMS, info)
|
||||
self.hash_info = HashInfo(
|
||||
self.PARAM_PARAMS,
|
||||
info, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
def read_params(
|
||||
self, flatten: bool = True, **kwargs: typing.Any
|
||||
@@ -104,8 +110,8 @@ class ParamsDependency(Dependency):
|
||||
|
||||
from funcy import ldistinct
|
||||
|
||||
status = defaultdict(dict)
|
||||
info = self.hash_info.value if self.hash_info else {}
|
||||
status: Dict[str, Any] = defaultdict(dict)
|
||||
info = cast(dict, self.hash_info.value) if self.hash_info else {}
|
||||
actual = self.read_params()
|
||||
|
||||
# NOTE: we want to preserve the order of params as specified in the
|
||||
@@ -119,9 +125,13 @@ class ParamsDependency(Dependency):
|
||||
elif param not in info:
|
||||
st = "new"
|
||||
elif actual[param] != info[param]:
|
||||
if (
|
||||
isinstance(actual[param], tuple)
|
||||
and list(actual[param]) == info[param]
|
||||
):
|
||||
continue
|
||||
st = "modified"
|
||||
else:
|
||||
assert actual[param] == info[param]
|
||||
continue
|
||||
|
||||
status[str(self)][param] = st
|
||||
@@ -144,9 +154,7 @@ class ParamsDependency(Dependency):
|
||||
try:
|
||||
return load_path(self.fs_path, self.repo.fs)
|
||||
except ParseError as exc:
|
||||
raise BadParamFileError(
|
||||
f"Unable to read parameters from '{self}'"
|
||||
) from exc
|
||||
raise BadParamFileError(f"Unable to read parameters from '{self}'") from exc
|
||||
|
||||
def get_hash(self):
|
||||
info = self.read_params()
|
||||
@@ -159,13 +167,13 @@ class ParamsDependency(Dependency):
|
||||
)
|
||||
)
|
||||
|
||||
return HashInfo(self.PARAM_PARAMS, info)
|
||||
return HashInfo(self.PARAM_PARAMS, info) # type: ignore[arg-type]
|
||||
|
||||
def save(self):
|
||||
if not self.exists:
|
||||
raise self.DoesNotExistError(self)
|
||||
|
||||
if not self.isfile and not self.isdir:
|
||||
if not self.isfile() and not self.isdir():
|
||||
raise self.IsNotFileOrDirError(self)
|
||||
|
||||
self.ignore()
|
||||
|
||||
+25
-26
@@ -1,18 +1,23 @@
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from copy import copy
|
||||
from typing import TYPE_CHECKING, Dict, Optional, Set, Tuple
|
||||
from typing import TYPE_CHECKING, Dict, Optional, Set, Tuple, Union
|
||||
|
||||
from voluptuous import Required
|
||||
|
||||
from dvc.prompt import confirm
|
||||
from dvc_data.hashfile.meta import Meta
|
||||
|
||||
from .base import Dependency
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dvc_data.hashfile.file import HashFile
|
||||
from typing import ContextManager
|
||||
|
||||
from dvc.output import Output
|
||||
from dvc.repo import Repo
|
||||
from dvc.stage import Stage
|
||||
from dvc_data.hashfile.hash_info import HashInfo
|
||||
from dvc_data.hashfile.meta import Meta
|
||||
from dvc_data.hashfile.obj import HashFile
|
||||
from dvc_objects.db import ObjectDB
|
||||
|
||||
|
||||
@@ -30,13 +35,13 @@ class RepoDependency(Dependency):
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, def_repo, stage, *args, **kwargs):
|
||||
def __init__(self, def_repo: Dict[str, str], stage: "Stage", *args, **kwargs):
|
||||
self.def_repo = def_repo
|
||||
self._objs: Dict[str, "HashFile"] = {}
|
||||
self._meta: Dict[str, Meta] = {}
|
||||
self._meta: Dict[str, "Meta"] = {}
|
||||
super().__init__(stage, *args, **kwargs)
|
||||
|
||||
def _parse_path(self, fs, fs_path):
|
||||
def _parse_path(self, fs, fs_path): # noqa: ARG002
|
||||
return None
|
||||
|
||||
@property
|
||||
@@ -61,10 +66,10 @@ class RepoDependency(Dependency):
|
||||
def save(self):
|
||||
pass
|
||||
|
||||
def dumpd(self, **kwargs):
|
||||
def dumpd(self, **kwargs) -> Dict[str, Union[str, Dict[str, str]]]:
|
||||
return {self.PARAM_PATH: self.def_path, self.PARAM_REPO: self.def_repo}
|
||||
|
||||
def download(self, to, jobs=None):
|
||||
def download(self, to: "Output", jobs: Optional[int] = None):
|
||||
from dvc_data.hashfile.checkout import checkout
|
||||
|
||||
for odb, objs in self.get_used_objs().items():
|
||||
@@ -81,27 +86,25 @@ class RepoDependency(Dependency):
|
||||
prompt=confirm,
|
||||
)
|
||||
|
||||
def update(self, rev=None):
|
||||
def update(self, rev: Optional[str] = None):
|
||||
if rev:
|
||||
self.def_repo[self.PARAM_REV] = rev
|
||||
with self._make_repo(locked=False) as repo:
|
||||
self.def_repo[self.PARAM_REV_LOCK] = repo.get_rev()
|
||||
|
||||
def changed_checksum(self):
|
||||
def changed_checksum(self) -> bool:
|
||||
# From current repo point of view what describes RepoDependency is its
|
||||
# origin project url and rev_lock, and it makes RepoDependency
|
||||
# immutable, hence its impossible for checksum to change.
|
||||
return False
|
||||
|
||||
def get_used_objs(
|
||||
self, **kwargs
|
||||
) -> Dict[Optional["ObjectDB"], Set["HashInfo"]]:
|
||||
def get_used_objs(self, **kwargs) -> Dict[Optional["ObjectDB"], Set["HashInfo"]]:
|
||||
used, _, _ = self._get_used_and_obj(**kwargs)
|
||||
return used
|
||||
|
||||
def _get_used_and_obj(
|
||||
self, obj_only=False, **kwargs
|
||||
) -> Tuple[Dict[Optional["ObjectDB"], Set["HashInfo"]], Meta, "HashFile"]:
|
||||
self, obj_only: bool = False, **kwargs
|
||||
) -> Tuple[Dict[Optional["ObjectDB"], Set["HashInfo"]], "Meta", "HashFile"]:
|
||||
from dvc.config import NoRemoteError
|
||||
from dvc.exceptions import NoOutputOrStageError, PathMissingError
|
||||
from dvc.utils import as_posix
|
||||
@@ -130,7 +133,7 @@ class RepoDependency(Dependency):
|
||||
self._check_circular_import(odb, obj_ids)
|
||||
used_obj_ids[odb].update(obj_ids)
|
||||
except (NoRemoteError, NoOutputOrStageError):
|
||||
pass
|
||||
pass # noqa: S110
|
||||
|
||||
try:
|
||||
object_store, meta, obj = build(
|
||||
@@ -154,7 +157,7 @@ class RepoDependency(Dependency):
|
||||
used_obj_ids[object_store].update(oid for _, _, oid in obj)
|
||||
return used_obj_ids, meta, obj
|
||||
|
||||
def _check_circular_import(self, odb, obj_ids):
|
||||
def _check_circular_import(self, odb: "ObjectDB", obj_ids: Set["HashInfo"]) -> None:
|
||||
from dvc.exceptions import CircularImportError
|
||||
from dvc.fs.dvc import DVCFileSystem
|
||||
from dvc_data.hashfile.db.reference import ReferenceHashFileDB
|
||||
@@ -169,16 +172,14 @@ class RepoDependency(Dependency):
|
||||
tree = Tree.load(odb, hash_info)
|
||||
yield from (odb.get(hi.value) for _, _, hi in tree)
|
||||
else:
|
||||
assert hash_info.value
|
||||
yield odb.get(hash_info.value)
|
||||
|
||||
checked_urls = set()
|
||||
for obj in iter_objs():
|
||||
if not isinstance(obj.fs, DVCFileSystem):
|
||||
continue
|
||||
if (
|
||||
obj.fs.repo_url in checked_urls
|
||||
or obj.fs.repo.root_dir in checked_urls
|
||||
):
|
||||
if obj.fs.repo_url in checked_urls or obj.fs.repo.root_dir in checked_urls:
|
||||
continue
|
||||
self_url = self.repo.url or self.repo.root_dir
|
||||
if (
|
||||
@@ -209,15 +210,13 @@ class RepoDependency(Dependency):
|
||||
)
|
||||
return meta
|
||||
|
||||
def _make_repo(self, locked=True, **kwargs):
|
||||
def _make_repo(self, locked: bool = True, **kwargs) -> "ContextManager[Repo]":
|
||||
from dvc.external_repo import external_repo
|
||||
|
||||
d = self.def_repo
|
||||
rev = self._get_rev(locked=locked)
|
||||
return external_repo(d[self.PARAM_URL], rev=rev, **kwargs)
|
||||
|
||||
def _get_rev(self, locked=True):
|
||||
def _get_rev(self, locked: bool = True):
|
||||
d = self.def_repo
|
||||
return (d.get(self.PARAM_REV_LOCK) if locked else None) or d.get(
|
||||
self.PARAM_REV
|
||||
)
|
||||
return (d.get(self.PARAM_REV_LOCK) if locked else None) or d.get(self.PARAM_REV)
|
||||
|
||||
+44
-65
@@ -7,13 +7,12 @@ from typing import (
|
||||
Callable,
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
from funcy import cached_property
|
||||
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.parsing.versions import LOCKFILE_VERSION, SCHEMA_KWD
|
||||
from dvc.stage import serialize
|
||||
@@ -22,21 +21,25 @@ from dvc.stage.exceptions import (
|
||||
StageFileDoesNotExistError,
|
||||
StageFileIsNotDvcFileError,
|
||||
)
|
||||
from dvc.types import AnyPath
|
||||
from dvc.utils import relpath
|
||||
from dvc.utils.collections import apply_diff
|
||||
from dvc.utils.objects import cached_property
|
||||
from dvc.utils.serialize import dump_yaml, modify_yaml
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dvc.repo import Repo
|
||||
from dvc.types import StrOrBytesPath
|
||||
|
||||
from .parsing import DataResolver
|
||||
from .stage import Stage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
_T = TypeVar("_T")
|
||||
|
||||
DVC_FILE = "Dvcfile"
|
||||
DVC_FILE_SUFFIX = ".dvc"
|
||||
PIPELINE_FILE = "dvc.yaml"
|
||||
PIPELINE_LOCK = "dvc.lock"
|
||||
PROJECT_FILE = "dvc.yaml"
|
||||
LOCK_FILE = "dvc.lock"
|
||||
|
||||
|
||||
class FileIsGitIgnored(DvcException):
|
||||
@@ -55,18 +58,16 @@ class ParametrizedDumpError(DvcException):
|
||||
def is_valid_filename(path):
|
||||
return path.endswith(DVC_FILE_SUFFIX) or os.path.basename(path) in [
|
||||
DVC_FILE,
|
||||
PIPELINE_FILE,
|
||||
PROJECT_FILE,
|
||||
]
|
||||
|
||||
|
||||
def is_dvc_file(path):
|
||||
return os.path.isfile(path) and (
|
||||
is_valid_filename(path) or is_lock_file(path)
|
||||
)
|
||||
return os.path.isfile(path) and (is_valid_filename(path) or is_lock_file(path))
|
||||
|
||||
|
||||
def is_lock_file(path):
|
||||
return os.path.basename(path) == PIPELINE_LOCK
|
||||
return os.path.basename(path) == LOCK_FILE
|
||||
|
||||
|
||||
def is_git_ignored(repo, path):
|
||||
@@ -74,9 +75,7 @@ def is_git_ignored(repo, path):
|
||||
from dvc.scm import NoSCMError
|
||||
|
||||
try:
|
||||
return isinstance(repo.fs, LocalFileSystem) and repo.scm.is_ignored(
|
||||
path
|
||||
)
|
||||
return isinstance(repo.fs, LocalFileSystem) and repo.scm.is_ignored(path)
|
||||
except NoSCMError:
|
||||
return False
|
||||
|
||||
@@ -86,7 +85,7 @@ def check_dvcfile_path(repo, path):
|
||||
raise StageFileBadNameError(
|
||||
"bad DVC file name '{}'. DVC files should be named "
|
||||
"'{}' or have a '.dvc' suffix (e.g. '{}.dvc').".format(
|
||||
relpath(path), PIPELINE_FILE, os.path.basename(path)
|
||||
relpath(path), PROJECT_FILE, os.path.basename(path)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -149,9 +148,7 @@ class FileMixin:
|
||||
# 4. when the file is git ignored
|
||||
if not self.exists():
|
||||
dvc_ignored = self.repo.dvcignore.is_ignored_file(self.path)
|
||||
raise StageFileDoesNotExistError(
|
||||
self.path, dvc_ignored=dvc_ignored
|
||||
)
|
||||
raise StageFileDoesNotExistError(self.path, dvc_ignored=dvc_ignored)
|
||||
|
||||
self._verify_filename()
|
||||
if not self.repo.fs.isfile(self.path):
|
||||
@@ -161,7 +158,7 @@ class FileMixin:
|
||||
return self._load_yaml(**kwargs)
|
||||
|
||||
@classmethod
|
||||
def validate(cls, d: _T, fname: str = None) -> _T:
|
||||
def validate(cls, d: _T, fname: Optional[str] = None) -> _T:
|
||||
from dvc.utils.strictyaml import validate
|
||||
|
||||
return validate(d, cls.SCHEMA, path=fname) # type: ignore[arg-type]
|
||||
@@ -176,7 +173,8 @@ class FileMixin:
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def remove(self, force=False): # pylint: disable=unused-argument
|
||||
# pylint: disable-next=unused-argument
|
||||
def remove(self, force=False): # noqa: ARG002
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
os.unlink(self.path)
|
||||
|
||||
@@ -192,20 +190,20 @@ class SingleStageFile(FileMixin):
|
||||
from dvc.stage.loader import SingleStageLoader as LOADER
|
||||
|
||||
metrics: List[str] = []
|
||||
plots: Dict[str, Any] = {}
|
||||
plots: Any = {}
|
||||
params: List[str] = []
|
||||
|
||||
@property
|
||||
def stage(self):
|
||||
def stage(self) -> "Stage":
|
||||
data, raw = self._load()
|
||||
return self.LOADER.load_stage(self, data, raw)
|
||||
|
||||
@property
|
||||
def stages(self):
|
||||
def stages(self) -> LOADER:
|
||||
data, raw = self._load()
|
||||
return self.LOADER(self, data, raw)
|
||||
|
||||
def dump(self, stage, **kwargs):
|
||||
def dump(self, stage, **kwargs) -> None:
|
||||
"""Dumps given stage appropriately in the dvcfile."""
|
||||
from dvc.stage import PipelineStage
|
||||
|
||||
@@ -216,7 +214,8 @@ class SingleStageFile(FileMixin):
|
||||
dump_yaml(self.path, serialize.to_single_stage_file(stage, **kwargs))
|
||||
self.repo.scm_context.track_file(self.relpath)
|
||||
|
||||
def remove_stage(self, stage): # pylint: disable=unused-argument
|
||||
# pylint: disable-next=unused-argument
|
||||
def remove_stage(self, stage): # noqa: ARG002
|
||||
self.remove()
|
||||
|
||||
def merge(self, ancestor, other, allowed=None):
|
||||
@@ -228,7 +227,7 @@ class SingleStageFile(FileMixin):
|
||||
self.dump(stage)
|
||||
|
||||
|
||||
class PipelineFile(FileMixin):
|
||||
class ProjectFile(FileMixin):
|
||||
"""Abstraction for pipelines file, .yaml + .lock combined."""
|
||||
|
||||
from dvc.schema import COMPILED_MULTI_STAGE_SCHEMA as SCHEMA
|
||||
@@ -242,6 +241,7 @@ class PipelineFile(FileMixin):
|
||||
self.__dict__.pop("contents", None)
|
||||
self.__dict__.pop("lockfile_contents", None)
|
||||
self.__dict__.pop("resolver", None)
|
||||
self.__dict__.pop("stages", None)
|
||||
|
||||
def dump(
|
||||
self, stage, update_pipeline=True, update_lock=True, **kwargs
|
||||
@@ -265,9 +265,7 @@ class PipelineFile(FileMixin):
|
||||
@staticmethod
|
||||
def _check_if_parametrized(stage, action: str = "dump") -> None:
|
||||
if stage.raw_data.parametrized:
|
||||
raise ParametrizedDumpError(
|
||||
f"cannot {action} a parametrized {stage}"
|
||||
)
|
||||
raise ParametrizedDumpError(f"cannot {action} a parametrized {stage}")
|
||||
|
||||
def _dump_pipeline_file(self, stage):
|
||||
self._check_if_parametrized(stage)
|
||||
@@ -280,9 +278,7 @@ class PipelineFile(FileMixin):
|
||||
data["stages"] = data.get("stages", {})
|
||||
existing_entry = stage.name in data["stages"]
|
||||
action = "Modifying" if existing_entry else "Adding"
|
||||
logger.info(
|
||||
"%s stage '%s' in '%s'", action, stage.name, self.relpath
|
||||
)
|
||||
logger.info("%s stage '%s' in '%s'", action, stage.name, self.relpath)
|
||||
|
||||
if existing_entry:
|
||||
orig_stage_data = data["stages"][stage.name]
|
||||
@@ -294,39 +290,37 @@ class PipelineFile(FileMixin):
|
||||
|
||||
@property
|
||||
def stage(self):
|
||||
raise DvcException(
|
||||
"PipelineFile has multiple stages. Please specify it's name."
|
||||
)
|
||||
raise DvcException("ProjectFile has multiple stages. Please specify it's name.")
|
||||
|
||||
@cached_property
|
||||
def contents(self):
|
||||
def contents(self) -> Dict[str, Any]:
|
||||
return self._load()[0]
|
||||
|
||||
@cached_property
|
||||
def lockfile_contents(self):
|
||||
def lockfile_contents(self) -> Dict[str, Any]:
|
||||
return self._lockfile.load()
|
||||
|
||||
@cached_property
|
||||
def resolver(self):
|
||||
def resolver(self) -> "DataResolver":
|
||||
from .parsing import DataResolver
|
||||
|
||||
wdir = self.repo.fs.path.parent(self.path)
|
||||
return DataResolver(self.repo, wdir, self.contents)
|
||||
|
||||
@property
|
||||
def stages(self):
|
||||
@cached_property
|
||||
def stages(self) -> LOADER:
|
||||
return self.LOADER(self, self.contents, self.lockfile_contents)
|
||||
|
||||
@property
|
||||
def metrics(self):
|
||||
def metrics(self) -> List[str]:
|
||||
return self.contents.get("metrics", [])
|
||||
|
||||
@property
|
||||
def plots(self):
|
||||
def plots(self) -> Any:
|
||||
return self.contents.get("plots", {})
|
||||
|
||||
@property
|
||||
def params(self):
|
||||
def params(self) -> List[str]:
|
||||
return self.contents.get("params", [])
|
||||
|
||||
def remove(self, force=False):
|
||||
@@ -360,10 +354,7 @@ class PipelineFile(FileMixin):
|
||||
|
||||
|
||||
def get_lockfile_schema(d):
|
||||
from dvc.schema import (
|
||||
COMPILED_LOCKFILE_V1_SCHEMA,
|
||||
COMPILED_LOCKFILE_V2_SCHEMA,
|
||||
)
|
||||
from dvc.schema import COMPILED_LOCKFILE_V1_SCHEMA, COMPILED_LOCKFILE_V2_SCHEMA
|
||||
|
||||
schema = {
|
||||
LOCKFILE_VERSION.V1: COMPILED_LOCKFILE_V1_SCHEMA,
|
||||
@@ -416,9 +407,7 @@ class Lockfile(FileMixin):
|
||||
with modify_yaml(self.path, fs=self.repo.fs) as data:
|
||||
version = LOCKFILE_VERSION.from_dict(data)
|
||||
if version == LOCKFILE_VERSION.V1:
|
||||
logger.info(
|
||||
"Migrating lock file '%s' from v1 to v2", self.relpath
|
||||
)
|
||||
logger.info("Migrating lock file '%s' from v1 to v2", self.relpath)
|
||||
migrate_lock_v1_to_v2(data, self.latest_version_info)
|
||||
else:
|
||||
if not data:
|
||||
@@ -460,20 +449,10 @@ class Lockfile(FileMixin):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Dvcfile:
|
||||
def __new__(cls, repo: "Repo", path: AnyPath, **kwargs: Any):
|
||||
assert path
|
||||
assert repo
|
||||
|
||||
return make_dvcfile(repo, path, **kwargs)
|
||||
|
||||
|
||||
DVCFile = Union["PipelineFile", "SingleStageFile"]
|
||||
|
||||
|
||||
def make_dvcfile(repo: "Repo", path: AnyPath, **kwargs: Any) -> DVCFile:
|
||||
_, ext = os.path.splitext(str(path))
|
||||
if ext in [".yaml", ".yml"]:
|
||||
return PipelineFile(repo, path, **kwargs)
|
||||
# fallback to single stage file for better error messages
|
||||
def load_file(
|
||||
repo: "Repo", path: "StrOrBytesPath", **kwargs: Any
|
||||
) -> Union[ProjectFile, SingleStageFile]:
|
||||
_, ext = os.path.splitext(path)
|
||||
if ext in (".yaml", ".yml"):
|
||||
return ProjectFile(repo, path, **kwargs)
|
||||
return SingleStageFile(repo, path, **kwargs)
|
||||
|
||||
+12
-21
@@ -1,4 +1,6 @@
|
||||
"""Exceptions raised by the dvc."""
|
||||
from typing import Dict, List
|
||||
|
||||
from dvc.utils import format_link
|
||||
|
||||
|
||||
@@ -148,8 +150,9 @@ class CyclicGraphError(DvcException):
|
||||
class ConfirmRemoveError(DvcException):
|
||||
def __init__(self, path):
|
||||
super().__init__(
|
||||
"unable to remove '{}' without a confirmation. Use "
|
||||
"`-f` to force.".format(path)
|
||||
"unable to remove '{}' without a confirmation. Use `-f` to force.".format(
|
||||
path
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -175,9 +178,7 @@ class BadMetricError(DvcException):
|
||||
|
||||
class RecursiveAddingWhileUsingFilename(DvcException):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
"cannot use `fname` with multiple targets or `-R|--recursive`"
|
||||
)
|
||||
super().__init__("cannot use `fname` with multiple targets or `-R|--recursive`")
|
||||
|
||||
|
||||
class OverlappingOutputPathsError(DvcException):
|
||||
@@ -204,9 +205,7 @@ class FileMissingError(DvcException):
|
||||
def __init__(self, path, hint=None):
|
||||
self.path = path
|
||||
hint = "" if hint is None else f". {hint}"
|
||||
super().__init__(
|
||||
f"Can't find '{path}' neither locally nor on remote{hint}"
|
||||
)
|
||||
super().__init__(f"Can't find '{path}' neither locally nor on remote{hint}")
|
||||
|
||||
|
||||
class FileTransferError(DvcException):
|
||||
@@ -227,7 +226,7 @@ class UploadError(FileTransferError):
|
||||
|
||||
|
||||
class CheckoutError(DvcException):
|
||||
def __init__(self, target_infos, stats=None):
|
||||
def __init__(self, target_infos: List[str], stats: Dict[str, List[str]]):
|
||||
from dvc.utils import error_link
|
||||
|
||||
self.target_infos = target_infos
|
||||
@@ -248,9 +247,7 @@ class CollectCacheError(DvcException):
|
||||
|
||||
class NoRemoteInExternalRepoError(DvcException):
|
||||
def __init__(self, url):
|
||||
super().__init__(
|
||||
f"No DVC remote is specified in target repository '{url}'."
|
||||
)
|
||||
super().__init__(f"No DVC remote is specified in target repository '{url}'.")
|
||||
|
||||
|
||||
class NoOutputInExternalRepoError(DvcException):
|
||||
@@ -275,8 +272,7 @@ class PathMissingError(DvcException):
|
||||
" neither as a DVC output nor as a Git-tracked file."
|
||||
)
|
||||
default_msg_dvc_only = (
|
||||
"The path '{}' does not exist in the target repository '{}'"
|
||||
" as an DVC output."
|
||||
"The path '{}' does not exist in the target repository '{}' as an DVC output."
|
||||
)
|
||||
|
||||
def __init__(self, path, repo, dvc_only=False):
|
||||
@@ -292,7 +288,6 @@ class URLMissingError(DvcException):
|
||||
|
||||
class RemoteCacheRequiredError(DvcException):
|
||||
def __init__(self, scheme, fs_path):
|
||||
|
||||
super().__init__(
|
||||
(
|
||||
"Current operation was unsuccessful because '{}' requires "
|
||||
@@ -317,8 +312,7 @@ class NoOutputOrStageError(DvcException):
|
||||
|
||||
def __init__(self, target, file):
|
||||
super().__init__(
|
||||
f"'{target}' "
|
||||
f"does not exist as an output or a stage name in '{file}'"
|
||||
f"'{target}' does not exist as an output or a stage name in '{file}'"
|
||||
)
|
||||
|
||||
|
||||
@@ -327,11 +321,8 @@ class MergeError(DvcException):
|
||||
|
||||
|
||||
class CacheLinkError(DvcException):
|
||||
|
||||
SUPPORT_LINK = "See {} for more information.".format(
|
||||
format_link(
|
||||
"https://dvc.org/doc/user-guide/troubleshooting#cache-types"
|
||||
)
|
||||
format_link("https://dvc.org/doc/user-guide/troubleshooting#cache-types")
|
||||
)
|
||||
|
||||
def __init__(self, fs_paths):
|
||||
|
||||
+20
-20
@@ -3,7 +3,7 @@ import os
|
||||
import tempfile
|
||||
import threading
|
||||
from contextlib import contextmanager
|
||||
from typing import TYPE_CHECKING, Dict
|
||||
from typing import TYPE_CHECKING, Collection, Dict, Iterator, Optional, Tuple
|
||||
|
||||
from funcy import retry, wrap_with
|
||||
|
||||
@@ -20,7 +20,9 @@ from dvc.scm import CloneError, map_scm_exception
|
||||
from dvc.utils import relpath
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from scmrepo import Git
|
||||
from dvc.types import StrPath
|
||||
|
||||
from .scm import Git
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -28,12 +30,16 @@ logger = logging.getLogger(__name__)
|
||||
@contextmanager
|
||||
@map_scm_exception()
|
||||
def external_repo(
|
||||
url, rev=None, for_write=False, cache_dir=None, cache_types=None, **kwargs
|
||||
):
|
||||
from scmrepo.git import Git
|
||||
|
||||
url,
|
||||
rev: Optional[str] = None,
|
||||
for_write: bool = False,
|
||||
cache_dir: Optional["StrPath"] = None,
|
||||
cache_types: Optional[Collection[str]] = None,
|
||||
**kwargs,
|
||||
) -> Iterator["Repo"]:
|
||||
from dvc.config import NoRemoteError
|
||||
from dvc.fs import GitFileSystem
|
||||
from dvc.scm import Git
|
||||
|
||||
logger.debug("Creating external repo %s@%s", url, rev)
|
||||
path = _cached_clone(url, rev, for_write=for_write)
|
||||
@@ -82,9 +88,7 @@ def external_repo(
|
||||
raise NoRemoteInExternalRepoError(url) from exc
|
||||
except OutputNotFoundError as exc:
|
||||
if exc.repo is repo:
|
||||
raise NoOutputInExternalRepoError(
|
||||
exc.output, repo.root_dir, url
|
||||
) from exc
|
||||
raise NoOutputInExternalRepoError(exc.output, repo.root_dir, url) from exc
|
||||
raise
|
||||
except FileMissingError as exc:
|
||||
raise PathMissingError(exc.path, url) from exc
|
||||
@@ -108,7 +112,7 @@ def erepo_factory(url, root_dir, cache_config):
|
||||
return make_repo
|
||||
|
||||
|
||||
CLONES: Dict[str, str] = {}
|
||||
CLONES: Dict[str, Tuple[str, bool]] = {}
|
||||
CACHE_DIRS: Dict[str, str] = {}
|
||||
|
||||
|
||||
@@ -185,14 +189,14 @@ def _cached_clone(url, rev, for_write=False):
|
||||
|
||||
|
||||
@wrap_with(threading.Lock())
|
||||
def _clone_default_branch(url, rev, for_write=False):
|
||||
def _clone_default_branch(url, rev, for_write=False): # noqa: C901
|
||||
"""Get or create a clean clone of the url.
|
||||
|
||||
The cloned is reactualized with git pull unless rev is a known sha.
|
||||
"""
|
||||
from scmrepo.git import Git
|
||||
from dvc.scm import Git
|
||||
|
||||
clone_path, shallow = CLONES.get(url, (None, False))
|
||||
clone_path, shallow = CLONES.get(url) or (None, False)
|
||||
|
||||
git = None
|
||||
try:
|
||||
@@ -228,9 +232,7 @@ def _clone_default_branch(url, rev, for_write=False):
|
||||
os.path.join(clone_path, Git.GIT_DIR, "shallow")
|
||||
)
|
||||
if shallow:
|
||||
logger.debug(
|
||||
"erepo: using shallow clone for branch '%s'", rev
|
||||
)
|
||||
logger.debug("erepo: using shallow clone for branch '%s'", rev)
|
||||
except CloneError:
|
||||
git_dir = os.path.join(clone_path, ".git")
|
||||
if os.path.exists(git_dir):
|
||||
@@ -267,7 +269,7 @@ def _merge_upstream(git: "Git"):
|
||||
|
||||
|
||||
def _git_checkout(repo_path, rev):
|
||||
from scmrepo.git import Git
|
||||
from dvc.scm import Git
|
||||
|
||||
logger.debug("erepo: git checkout %s@%s", repo_path, rev)
|
||||
git = Git(repo_path)
|
||||
@@ -286,8 +288,6 @@ def _remove(path):
|
||||
try:
|
||||
os_retry(remove)(path)
|
||||
except PermissionError:
|
||||
logger.warning(
|
||||
"Failed to remove '%s'", relpath(path), exc_info=True
|
||||
)
|
||||
logger.warning("Failed to remove '%s'", relpath(path), exc_info=True)
|
||||
else:
|
||||
remove(path)
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ def get_fs_config(config, **kwargs):
|
||||
except KeyError:
|
||||
from dvc.config import RemoteNotFoundError
|
||||
|
||||
raise RemoteNotFoundError(f"remote '{name}' doesn't exist")
|
||||
raise RemoteNotFoundError(f"remote '{name}' doesn't exist") # noqa: B904
|
||||
else:
|
||||
remote_conf = kwargs
|
||||
return _resolve_remote_refs(config, remote_conf)
|
||||
|
||||
+14
-13
@@ -1,9 +1,8 @@
|
||||
# pylint: disable=unused-import
|
||||
from contextlib import ExitStack
|
||||
from typing import TYPE_CHECKING, Any, BinaryIO, Dict, Optional, Union
|
||||
|
||||
from funcy import cached_property
|
||||
from typing import TYPE_CHECKING, Any, BinaryIO, Dict, Optional, Union, cast
|
||||
|
||||
from dvc.utils.objects import cached_property
|
||||
from dvc_objects.fs.callbacks import ( # noqa: F401
|
||||
DEFAULT_CALLBACK,
|
||||
Callback,
|
||||
@@ -11,6 +10,8 @@ from dvc_objects.fs.callbacks import ( # noqa: F401
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from rich.progress import TaskID
|
||||
|
||||
from dvc.ui._rich_progress import RichTransferProgress
|
||||
|
||||
|
||||
@@ -19,10 +20,10 @@ class RichCallback(Callback):
|
||||
self,
|
||||
size: Optional[int] = None,
|
||||
value: int = 0,
|
||||
progress: "RichTransferProgress" = None,
|
||||
desc: str = None,
|
||||
bytes: bool = False, # pylint: disable=redefined-builtin
|
||||
unit: str = None,
|
||||
progress: Optional["RichTransferProgress"] = None,
|
||||
desc: Optional[str] = None,
|
||||
bytes: bool = False, # noqa: A002, pylint: disable=redefined-builtin
|
||||
unit: Optional[str] = None,
|
||||
disable: bool = False,
|
||||
transient: bool = True,
|
||||
) -> None:
|
||||
@@ -41,7 +42,7 @@ class RichCallback(Callback):
|
||||
super().__init__(size=size, value=value)
|
||||
|
||||
@cached_property
|
||||
def progress(self):
|
||||
def progress(self) -> "RichTransferProgress":
|
||||
from dvc.ui import ui
|
||||
from dvc.ui._rich_progress import RichTransferProgress
|
||||
|
||||
@@ -53,11 +54,11 @@ class RichCallback(Callback):
|
||||
disable=self.disable,
|
||||
console=ui.error_console,
|
||||
)
|
||||
return self._stack.enter_context(progress)
|
||||
return cast(RichTransferProgress, self._stack.enter_context(progress))
|
||||
|
||||
@cached_property
|
||||
def task(self):
|
||||
return self.progress.add_task(**self._task_kwargs)
|
||||
def task(self) -> "TaskID":
|
||||
return self.progress.add_task(**self._task_kwargs) # type: ignore[arg-type]
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
@@ -67,7 +68,7 @@ class RichCallback(Callback):
|
||||
self.progress.clear_task(self.task)
|
||||
self._stack.close()
|
||||
|
||||
def call(self, hook_name=None, **kwargs):
|
||||
def call(self, hook_name=None, **kwargs): # noqa: ARG002
|
||||
self.progress.update(
|
||||
self.task,
|
||||
completed=self.value,
|
||||
@@ -80,7 +81,7 @@ class RichCallback(Callback):
|
||||
path_1: Union[str, BinaryIO],
|
||||
path_2: str,
|
||||
kwargs: Dict[str, Any],
|
||||
child: "Callback" = None,
|
||||
child: Optional["Callback"] = None,
|
||||
):
|
||||
child = child or RichCallback(
|
||||
progress=self.progress,
|
||||
|
||||
+12
-7
@@ -1,13 +1,15 @@
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
|
||||
from funcy import cached_property, wrap_prop
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dvc.utils import as_posix
|
||||
from dvc_data.fs import DataFileSystem as _DataFileSystem
|
||||
from dvc_objects.fs.base import FileSystem
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dvc_data.fs import DataFileSystem as _DataFileSystem
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -19,9 +21,12 @@ class DataFileSystem(FileSystem):
|
||||
def _prepare_credentials(self, **config):
|
||||
return config
|
||||
|
||||
@wrap_prop(threading.Lock())
|
||||
@cached_property
|
||||
def fs(self):
|
||||
@functools.cached_property
|
||||
def fs( # pylint: disable=invalid-overridden-method
|
||||
self,
|
||||
) -> "_DataFileSystem":
|
||||
from dvc_data.fs import DataFileSystem as _DataFileSystem
|
||||
|
||||
return _DataFileSystem(**self.fs_args)
|
||||
|
||||
def isdvc(self, path, **kwargs):
|
||||
|
||||
+25
-30
@@ -1,14 +1,15 @@
|
||||
import errno
|
||||
import functools
|
||||
import logging
|
||||
import ntpath
|
||||
import os
|
||||
import posixpath
|
||||
import threading
|
||||
from contextlib import suppress
|
||||
from typing import TYPE_CHECKING, Any, Callable, Optional, Tuple, Type, Union
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple, Type, Union
|
||||
|
||||
from fsspec.spec import AbstractFileSystem
|
||||
from funcy import cached_property, wrap_prop, wrap_with
|
||||
from funcy import wrap_with
|
||||
|
||||
from dvc_objects.fs.base import FileSystem
|
||||
from dvc_objects.fs.path import Path
|
||||
@@ -17,10 +18,11 @@ from .data import DataFileSystem
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dvc.repo import Repo
|
||||
from dvc.types import StrPath
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
RepoFactory = Union[Callable[[str], "Repo"], Type["Repo"]]
|
||||
RepoFactory = Union[Callable[..., "Repo"], Type["Repo"]]
|
||||
Key = Tuple[str, ...]
|
||||
|
||||
|
||||
@@ -115,10 +117,8 @@ class _DVCFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
|
||||
|
||||
super().__init__()
|
||||
if repo is None:
|
||||
repo = self._make_repo(
|
||||
url=url, rev=rev, subrepos=subrepos, **repo_kwargs
|
||||
)
|
||||
assert repo
|
||||
repo = self._make_repo(url=url, rev=rev, subrepos=subrepos, **repo_kwargs)
|
||||
assert repo is not None
|
||||
# pylint: disable=protected-access
|
||||
repo_factory = repo._fs_conf["repo_factory"]
|
||||
|
||||
@@ -131,10 +131,9 @@ class _DVCFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
|
||||
|
||||
def _getcwd():
|
||||
relparts = ()
|
||||
assert repo is not None
|
||||
if repo.fs.path.isin(repo.fs.path.getcwd(), repo.root_dir):
|
||||
relparts = repo.fs.path.relparts(
|
||||
repo.fs.path.getcwd(), repo.root_dir
|
||||
)
|
||||
relparts = repo.fs.path.relparts(repo.fs.path.getcwd(), repo.root_dir)
|
||||
return self.root_marker + self.sep.join(relparts)
|
||||
|
||||
self.path = Path(self.sep, getcwd=_getcwd)
|
||||
@@ -154,16 +153,16 @@ class _DVCFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
|
||||
if hasattr(repo, "dvc_dir"):
|
||||
self._datafss[key] = DataFileSystem(index=repo.index.data["repo"])
|
||||
|
||||
def _get_key(self, path) -> Key:
|
||||
def _get_key(self, path: "StrPath") -> Key:
|
||||
parts = self.repo.fs.path.relparts(path, self.repo.root_dir)
|
||||
if parts == (".",):
|
||||
parts = ()
|
||||
if parts == (os.curdir,):
|
||||
return ()
|
||||
return parts
|
||||
|
||||
def _get_key_from_relative(self, path) -> Key:
|
||||
parts = self.path.relparts(path, self.root_marker)
|
||||
if parts and parts[0] == os.curdir:
|
||||
parts = parts[1:]
|
||||
return parts[1:]
|
||||
return parts
|
||||
|
||||
def _from_key(self, parts: Key) -> str:
|
||||
@@ -171,8 +170,6 @@ class _DVCFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
|
||||
|
||||
@property
|
||||
def repo_url(self):
|
||||
if self.repo is None:
|
||||
return None
|
||||
return self.repo.url
|
||||
|
||||
@classmethod
|
||||
@@ -212,9 +209,7 @@ class _DVCFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
|
||||
scm=self.repo.scm,
|
||||
repo_factory=self.repo_factory,
|
||||
)
|
||||
self._datafss[key] = DataFileSystem(
|
||||
index=repo.index.data["repo"]
|
||||
)
|
||||
self._datafss[key] = DataFileSystem(index=repo.index.data["repo"])
|
||||
self._subrepos_trie[key] = repo
|
||||
|
||||
def _is_dvc_repo(self, dir_path):
|
||||
@@ -294,7 +289,7 @@ class _DVCFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
|
||||
):
|
||||
fs_infos[fs.path.name(info["name"])] = info
|
||||
except (FileNotFoundError, NotADirectoryError):
|
||||
pass
|
||||
pass # noqa: S110
|
||||
|
||||
dvcfiles = kwargs.get("dvcfiles", False)
|
||||
|
||||
@@ -321,7 +316,7 @@ class _DVCFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
|
||||
ignore_subrepos = kwargs.get("ignore_subrepos", True)
|
||||
return self._info(key, path, ignore_subrepos=ignore_subrepos)
|
||||
|
||||
def _info(self, key, path, ignore_subrepos=True, check_ignored=True):
|
||||
def _info(self, key, path, ignore_subrepos=True, check_ignored=True): # noqa: C901
|
||||
repo, dvc_fs, subkey = self._get_subrepo_info(key)
|
||||
|
||||
dvc_info = None
|
||||
@@ -369,30 +364,30 @@ class DVCFileSystem(FileSystem):
|
||||
protocol = "local"
|
||||
PARAM_CHECKSUM = "md5"
|
||||
|
||||
def _prepare_credentials(self, **config):
|
||||
def _prepare_credentials(self, **config) -> Dict[str, Any]:
|
||||
return config
|
||||
|
||||
@wrap_prop(threading.Lock())
|
||||
@cached_property
|
||||
def fs(self):
|
||||
@functools.cached_property
|
||||
# pylint: disable-next=invalid-overridden-method
|
||||
def fs(self) -> "DVCFileSystem":
|
||||
return _DVCFileSystem(**self.fs_args)
|
||||
|
||||
def isdvc(self, path, **kwargs):
|
||||
def isdvc(self, path, **kwargs) -> bool:
|
||||
return self.fs.isdvc(path, **kwargs)
|
||||
|
||||
@property
|
||||
def path(self): # pylint: disable=invalid-overridden-method
|
||||
def path(self) -> Path: # pylint: disable=invalid-overridden-method
|
||||
return self.fs.path
|
||||
|
||||
@property
|
||||
def repo(self):
|
||||
def repo(self) -> "Repo":
|
||||
return self.fs.repo
|
||||
|
||||
@property
|
||||
def repo_url(self):
|
||||
def repo_url(self) -> str:
|
||||
return self.fs.repo_url
|
||||
|
||||
def from_os_path(self, path):
|
||||
def from_os_path(self, path: str) -> str:
|
||||
if os.path.isabs(path):
|
||||
path = os.path.relpath(path, self.repo.root_dir)
|
||||
|
||||
|
||||
+14
-14
@@ -1,15 +1,14 @@
|
||||
import threading
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from funcy import cached_property, wrap_prop
|
||||
import functools
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from . import FileSystem
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from scmrepo.fs import GitFileSystem as FsspecGitFileSystem
|
||||
from scmrepo.git import Git
|
||||
from scmrepo.git.objects import GitTrie
|
||||
|
||||
from dvc.scm import Git
|
||||
|
||||
|
||||
class GitFileSystem(FileSystem): # pylint:disable=abstract-method
|
||||
"""Proxies the repo file access methods to Git objects"""
|
||||
@@ -19,10 +18,10 @@ class GitFileSystem(FileSystem): # pylint:disable=abstract-method
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path: str = None,
|
||||
rev: str = None,
|
||||
scm: "Git" = None,
|
||||
trie: "GitTrie" = None,
|
||||
path: Optional[str] = None,
|
||||
rev: Optional[str] = None,
|
||||
scm: Optional["Git"] = None,
|
||||
trie: Optional["GitTrie"] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
from dvc.scm import resolve_rev
|
||||
@@ -39,15 +38,16 @@ class GitFileSystem(FileSystem): # pylint:disable=abstract-method
|
||||
}
|
||||
)
|
||||
|
||||
@wrap_prop(threading.Lock())
|
||||
@cached_property
|
||||
def fs(self) -> "FsspecGitFileSystem":
|
||||
@functools.cached_property
|
||||
def fs( # pylint: disable=invalid-overridden-method
|
||||
self,
|
||||
) -> "FsspecGitFileSystem":
|
||||
from scmrepo.fs import GitFileSystem as FsspecGitFileSystem
|
||||
|
||||
return FsspecGitFileSystem(**self.fs_args)
|
||||
|
||||
@cached_property
|
||||
def path(self):
|
||||
@functools.cached_property
|
||||
def path(self): # pylint: disable=invalid-overridden-method
|
||||
return self.fs.path
|
||||
|
||||
@property
|
||||
|
||||
+30
-36
@@ -3,14 +3,17 @@ import os
|
||||
import re
|
||||
from collections import namedtuple
|
||||
from itertools import chain, groupby, takewhile
|
||||
from typing import TYPE_CHECKING, List, Optional
|
||||
|
||||
from pathspec.patterns import GitWildMatchPattern
|
||||
from pathspec.util import normalize_file
|
||||
from pygtrie import Trie
|
||||
|
||||
from dvc.fs import AnyFSPath, FileSystem, Schemes, localfs
|
||||
from dvc.fs import Schemes, localfs
|
||||
from dvc.pathspec_math import PatternInfo, merge_patterns
|
||||
from dvc.types import List, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dvc.fs import AnyFSPath, FileSystem
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -24,26 +27,25 @@ class DvcIgnore:
|
||||
|
||||
class DvcIgnorePatterns(DvcIgnore):
|
||||
def __init__(self, pattern_list, dirname, sep):
|
||||
if pattern_list:
|
||||
if isinstance(pattern_list[0], str):
|
||||
pattern_list = [
|
||||
PatternInfo(pattern, "") for pattern in pattern_list
|
||||
]
|
||||
from pathspec.patterns.gitwildmatch import _DIR_MARK
|
||||
|
||||
if pattern_list and isinstance(pattern_list[0], str):
|
||||
pattern_list = [PatternInfo(pattern, "") for pattern in pattern_list]
|
||||
|
||||
self.sep = sep
|
||||
self.pattern_list = pattern_list
|
||||
self.dirname = dirname
|
||||
|
||||
self.regex_pattern_list = [
|
||||
GitWildMatchPattern.pattern_to_regex(pattern_info.patterns)
|
||||
for pattern_info in pattern_list
|
||||
]
|
||||
self.regex_pattern_list = []
|
||||
for count, pattern in enumerate(pattern_list):
|
||||
pattern, group = GitWildMatchPattern.pattern_to_regex(pattern.patterns)
|
||||
if pattern:
|
||||
pattern = pattern.replace(f"<{_DIR_MARK}>", f"<{_DIR_MARK}{count}>")
|
||||
self.regex_pattern_list.append((pattern, group))
|
||||
|
||||
self.ignore_spec = [
|
||||
(ignore, re.compile("|".join(item[0] for item in group)))
|
||||
for ignore, group in groupby(
|
||||
self.regex_pattern_list, lambda x: x[1]
|
||||
)
|
||||
for ignore, group in groupby(self.regex_pattern_list, lambda x: x[1])
|
||||
if ignore is not None
|
||||
]
|
||||
|
||||
@@ -54,9 +56,7 @@ class DvcIgnorePatterns(DvcIgnore):
|
||||
with fs.open(path, encoding="utf-8") as fobj:
|
||||
path_spec_lines = [
|
||||
PatternInfo(line, f"{name}:{line_no + 1}:{line}")
|
||||
for line_no, line in enumerate(
|
||||
map(str.strip, fobj.readlines())
|
||||
)
|
||||
for line_no, line in enumerate(map(str.strip, fobj.readlines()))
|
||||
if line and not (line.strip().startswith("#"))
|
||||
]
|
||||
|
||||
@@ -84,7 +84,7 @@ class DvcIgnorePatterns(DvcIgnore):
|
||||
return False
|
||||
|
||||
if os.name == "nt":
|
||||
path = normalize_file(path)
|
||||
return normalize_file(path)
|
||||
return path
|
||||
|
||||
def matches(self, dirname, basename, is_dir=False, details: bool = False):
|
||||
@@ -148,9 +148,7 @@ class DvcIgnorePatterns(DvcIgnore):
|
||||
return bool(self.pattern_list)
|
||||
|
||||
|
||||
CheckIgnoreResult = namedtuple(
|
||||
"CheckIgnoreResult", ["file", "match", "patterns"]
|
||||
)
|
||||
CheckIgnoreResult = namedtuple("CheckIgnoreResult", ["file", "match", "patterns"])
|
||||
|
||||
|
||||
def _no_match(path):
|
||||
@@ -195,8 +193,8 @@ class DvcIgnoreFilter:
|
||||
|
||||
def _get_key(self, path):
|
||||
parts = self.fs.path.relparts(path, self.root_dir)
|
||||
if parts == (".",):
|
||||
parts = ()
|
||||
if parts == (os.curdir,):
|
||||
return ()
|
||||
return parts
|
||||
|
||||
def _update_trie(self, dirname: str, trie: Trie) -> None:
|
||||
@@ -239,9 +237,7 @@ class DvcIgnoreFilter:
|
||||
dnames = []
|
||||
|
||||
for dname in dnames:
|
||||
self._update_sub_repo(
|
||||
self.fs.path.join(dirname, dname), ignore_trie
|
||||
)
|
||||
self._update_sub_repo(self.fs.path.join(dirname, dname), ignore_trie)
|
||||
|
||||
def _update_sub_repo(self, path, ignore_trie: Trie):
|
||||
from dvc.repo import Repo
|
||||
@@ -299,7 +295,7 @@ class DvcIgnoreFilter:
|
||||
|
||||
return [fs_dict[name] for name in chain(dirs, nondirs)]
|
||||
|
||||
def walk(self, fs: FileSystem, path: AnyFSPath, **kwargs):
|
||||
def walk(self, fs: "FileSystem", path: "AnyFSPath", **kwargs):
|
||||
detail = kwargs.get("detail", False)
|
||||
ignore_subrepos = kwargs.pop("ignore_subrepos", True)
|
||||
if fs.protocol == Schemes.LOCAL:
|
||||
@@ -323,7 +319,7 @@ class DvcIgnoreFilter:
|
||||
else:
|
||||
yield from fs.walk(path, **kwargs)
|
||||
|
||||
def find(self, fs: FileSystem, path: AnyFSPath, **kwargs):
|
||||
def find(self, fs: "FileSystem", path: "AnyFSPath", **kwargs):
|
||||
if fs.protocol == Schemes.LOCAL:
|
||||
for root, _, files in self.walk(fs, path, **kwargs):
|
||||
for file in files:
|
||||
@@ -379,7 +375,7 @@ class DvcIgnoreFilter:
|
||||
return False
|
||||
|
||||
def is_ignored_dir(self, path: str, ignore_subrepos: bool = True) -> bool:
|
||||
"Only used in LocalFileSystem"
|
||||
# only used in LocalFileSystem
|
||||
path = self.fs.path.abspath(path)
|
||||
if path == self.root_dir:
|
||||
return False
|
||||
@@ -387,7 +383,7 @@ class DvcIgnoreFilter:
|
||||
return self._is_ignored(path, True, ignore_subrepos=ignore_subrepos)
|
||||
|
||||
def is_ignored_file(self, path: str, ignore_subrepos: bool = True) -> bool:
|
||||
"Only used in LocalFileSystem"
|
||||
# only used in LocalFileSystem
|
||||
path = self.fs.path.abspath(path)
|
||||
return self._is_ignored(path, False, ignore_subrepos=ignore_subrepos)
|
||||
|
||||
@@ -399,9 +395,7 @@ class DvcIgnoreFilter:
|
||||
# https://github.com/iterative/dvc/issues/5046
|
||||
full_target = self.fs.path.abspath(target)
|
||||
if not self._outside_repo(full_target):
|
||||
dirname, basename = self.fs.path.split(
|
||||
self.fs.path.normpath(full_target)
|
||||
)
|
||||
dirname, basename = self.fs.path.split(self.fs.path.normpath(full_target))
|
||||
pattern = self._get_trie_pattern(dirname)
|
||||
if pattern:
|
||||
matches = pattern.matches(
|
||||
@@ -413,7 +407,7 @@ class DvcIgnoreFilter:
|
||||
return _no_match(target)
|
||||
|
||||
def is_ignored(
|
||||
self, fs: FileSystem, path: str, ignore_subrepos: bool = True
|
||||
self, fs: "FileSystem", path: str, ignore_subrepos: bool = True
|
||||
) -> bool:
|
||||
# NOTE: can't use self.check_ignore(path).match for now, see
|
||||
# https://github.com/iterative/dvc/issues/4555
|
||||
@@ -423,9 +417,9 @@ class DvcIgnoreFilter:
|
||||
return self.is_ignored_file(path, ignore_subrepos)
|
||||
if fs.isdir(path):
|
||||
return self.is_ignored_dir(path, ignore_subrepos)
|
||||
return self.is_ignored_file(
|
||||
return self.is_ignored_file(path, ignore_subrepos) or self.is_ignored_dir(
|
||||
path, ignore_subrepos
|
||||
) or self.is_ignored_dir(path, ignore_subrepos)
|
||||
)
|
||||
|
||||
|
||||
def init(path):
|
||||
|
||||
+6
-7
@@ -22,15 +22,15 @@ SUBPROJECTS = (
|
||||
"dvclive",
|
||||
"scmrepo",
|
||||
)
|
||||
package = "" if PKG is None else f"({PKG})"
|
||||
package = "" if PKG is None else f" ({PKG})"
|
||||
|
||||
|
||||
def get_dvc_info():
|
||||
dvc_version = f"DVC version: {__version__}{package}"
|
||||
info = [
|
||||
f"DVC version: {__version__} {package}",
|
||||
"---------------------------------",
|
||||
f"Platform: Python {platform.python_version()} on "
|
||||
f"{platform.platform()}",
|
||||
dvc_version,
|
||||
"-" * len(dvc_version),
|
||||
f"Platform: Python {platform.python_version()} on {platform.platform()}",
|
||||
f"Subprojects:{_get_subprojects()}",
|
||||
f"Supports:{_get_supported_remotes()}",
|
||||
]
|
||||
@@ -124,8 +124,7 @@ def _get_supported_remotes():
|
||||
dependencies = []
|
||||
for requirement in fs_cls.REQUIRES:
|
||||
dependencies.append(
|
||||
f"{requirement} = "
|
||||
f"{importlib_metadata.version(requirement)}"
|
||||
f"{requirement} = {importlib_metadata.version(requirement)}"
|
||||
)
|
||||
|
||||
remote_info = scheme
|
||||
|
||||
+9
-13
@@ -20,9 +20,7 @@ FAILED_TO_LOCK_MESSAGE = (
|
||||
"Unable to acquire lock. Most likely another DVC process is running or "
|
||||
"was terminated abruptly. Check the page {} for other possible reasons "
|
||||
"and to learn how to resolve this."
|
||||
).format(
|
||||
format_link("https://dvc.org/doc/user-guide/troubleshooting#lock-issue")
|
||||
)
|
||||
).format(format_link("https://dvc.org/doc/user-guide/troubleshooting#lock-issue"))
|
||||
|
||||
|
||||
class LockError(DvcException):
|
||||
@@ -61,9 +59,7 @@ class LockBase(ABC):
|
||||
|
||||
|
||||
class LockNoop(LockBase):
|
||||
def __init__(
|
||||
self, *args, **kwargs
|
||||
): # pylint: disable=super-init-not-called
|
||||
def __init__(self, *args, **kwargs): # pylint: disable=super-init-not-called
|
||||
self._lock = False
|
||||
|
||||
def lock(self):
|
||||
@@ -107,16 +103,14 @@ class Lock(LockBase):
|
||||
with Tqdm(
|
||||
bar_format="{desc}",
|
||||
disable=not self._friendly,
|
||||
desc=(
|
||||
"If DVC froze, see `hardlink_lock` in {}".format(
|
||||
format_link("https://man.dvc.org/config#core")
|
||||
)
|
||||
desc="If DVC froze, see `hardlink_lock` in {}".format(
|
||||
format_link("https://man.dvc.org/config#core")
|
||||
),
|
||||
):
|
||||
self._lock = zc.lockfile.LockFile(self._lockfile)
|
||||
except zc.lockfile.LockError:
|
||||
self._lock_failed = True
|
||||
raise LockError(FAILED_TO_LOCK_MESSAGE)
|
||||
raise LockError(FAILED_TO_LOCK_MESSAGE) # noqa: B904
|
||||
|
||||
def lock(self):
|
||||
retries = 6
|
||||
@@ -131,6 +125,7 @@ class Lock(LockBase):
|
||||
|
||||
if not self.is_locked:
|
||||
raise DvcException("Unlock called on an unlocked lock")
|
||||
assert self._lock
|
||||
self._lock.close()
|
||||
self._lock = None
|
||||
|
||||
@@ -185,14 +180,15 @@ class HardlinkLock(flufl.lock.Lock, LockBase):
|
||||
try:
|
||||
super().lock(timedelta(seconds=DEFAULT_TIMEOUT))
|
||||
except flufl.lock.TimeOutError:
|
||||
raise LockError(FAILED_TO_LOCK_MESSAGE)
|
||||
raise LockError(FAILED_TO_LOCK_MESSAGE) # noqa: B904
|
||||
|
||||
def _set_claimfile(self):
|
||||
super()._set_claimfile()
|
||||
|
||||
if self._tmp_dir is not None:
|
||||
# Under Windows file path length is limited so we hash it
|
||||
filename = hashlib.md5(self._claimfile.encode()).hexdigest()
|
||||
hasher = hashlib.md5(self._claimfile.encode()) # noqa: S324
|
||||
filename = hasher.hexdigest()
|
||||
self._claimfile = os.path.join(self._tmp_dir, filename + ".lock")
|
||||
|
||||
|
||||
|
||||
+11
-20
@@ -68,7 +68,7 @@ class LoggingException(Exception):
|
||||
|
||||
def excludeFilter(level):
|
||||
class ExcludeLevelFilter(logging.Filter):
|
||||
def filter(self, record):
|
||||
def filter(self, record): # noqa: A003
|
||||
return record.levelno < level
|
||||
|
||||
return ExcludeLevelFilter
|
||||
@@ -94,7 +94,7 @@ class ColorFormatter(logging.Formatter):
|
||||
"CRITICAL": colorama.Fore.RED,
|
||||
}
|
||||
|
||||
def format(self, record):
|
||||
def format(self, record): # noqa: A003
|
||||
record.message = record.getMessage()
|
||||
msg = self.formatMessage(record)
|
||||
|
||||
@@ -156,12 +156,10 @@ class LoggerHandler(logging.StreamHandler):
|
||||
if not _is_verbose():
|
||||
return
|
||||
except Exception: # noqa, pylint: disable=broad-except
|
||||
pass
|
||||
pass # noqa: S110
|
||||
|
||||
msg = self.format(record)
|
||||
Tqdm.write(
|
||||
msg, file=self.stream, end=getattr(self, "terminator", "\n")
|
||||
)
|
||||
Tqdm.write(msg, file=self.stream, end=getattr(self, "terminator", "\n"))
|
||||
self.flush()
|
||||
except (BrokenPipeError, RecursionError):
|
||||
raise
|
||||
@@ -171,9 +169,7 @@ class LoggerHandler(logging.StreamHandler):
|
||||
|
||||
def _is_verbose():
|
||||
return (
|
||||
logging.NOTSET
|
||||
< logging.getLogger("dvc").getEffectiveLevel()
|
||||
<= logging.DEBUG
|
||||
logging.NOTSET < logging.getLogger("dvc").getEffectiveLevel() <= logging.DEBUG
|
||||
)
|
||||
|
||||
|
||||
@@ -186,16 +182,11 @@ def _iter_causes(exc):
|
||||
def _stack_trace(exc_info):
|
||||
import traceback
|
||||
|
||||
return (
|
||||
"\n"
|
||||
"{red}{line}{nc}\n"
|
||||
"{trace}"
|
||||
"{red}{line}{nc}".format(
|
||||
red=colorama.Fore.RED,
|
||||
line="-" * 60,
|
||||
trace="".join(traceback.format_exception(*exc_info)),
|
||||
nc=colorama.Fore.RESET,
|
||||
)
|
||||
return "\n{red}{line}{nc}\n{trace}{red}{line}{nc}".format(
|
||||
red=colorama.Fore.RED,
|
||||
line="-" * 60,
|
||||
trace="".join(traceback.format_exception(*exc_info)),
|
||||
nc=colorama.Fore.RESET,
|
||||
)
|
||||
|
||||
|
||||
@@ -204,7 +195,7 @@ def disable_other_loggers():
|
||||
loggerDict = logging.root.manager.loggerDict # pylint: disable=no-member
|
||||
for logger_name, logger in loggerDict.items():
|
||||
if logger_name != "dvc" and not logger_name.startswith("dvc."):
|
||||
logger.disabled = True
|
||||
logger.disabled = True # type: ignore[union-attr]
|
||||
|
||||
|
||||
def set_loggers_level(level: int = logging.INFO) -> None:
|
||||
|
||||
+10
-7
@@ -13,10 +13,10 @@ from typing import (
|
||||
)
|
||||
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.types import StrPath
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dvc.repo import Repo
|
||||
from dvc.types import StrPath
|
||||
|
||||
from .backend.base import BaseMachineBackend
|
||||
|
||||
@@ -67,16 +67,14 @@ class MachineBackends(Mapping):
|
||||
initialized = self.initialized.get(key)
|
||||
if not initialized:
|
||||
backend = self.backends[key]
|
||||
initialized = backend(
|
||||
os.path.join(self.tmp_dir, key), **self.kwargs
|
||||
)
|
||||
initialized = backend(os.path.join(self.tmp_dir, key), **self.kwargs)
|
||||
self.initialized[key] = initialized
|
||||
return initialized
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
selected: Optional[Iterable[str]],
|
||||
tmp_dir: StrPath,
|
||||
tmp_dir: "StrPath",
|
||||
**kwargs,
|
||||
) -> None:
|
||||
selected = selected or list(self.DEFAULT)
|
||||
@@ -127,6 +125,7 @@ class MachineManager:
|
||||
self, repo: "Repo", backends: Optional[Iterable[str]] = None, **kwargs
|
||||
):
|
||||
self.repo = repo
|
||||
assert self.repo.tmp_dir
|
||||
tmp_dir = os.path.join(self.repo.tmp_dir, "machine")
|
||||
self.backends = MachineBackends(backends, tmp_dir=tmp_dir, **kwargs)
|
||||
|
||||
@@ -167,7 +166,9 @@ class MachineManager:
|
||||
except KeyError:
|
||||
from dvc.config import MachineNotFoundError
|
||||
|
||||
raise MachineNotFoundError(f"machine '{name}' doesn't exist")
|
||||
raise MachineNotFoundError( # noqa: B904
|
||||
f"machine '{name}' doesn't exist"
|
||||
)
|
||||
else:
|
||||
conf = kwargs
|
||||
return conf
|
||||
@@ -179,7 +180,9 @@ class MachineManager:
|
||||
backend = self.CLOUD_BACKENDS[cloud]
|
||||
return self.backends[backend]
|
||||
except KeyError:
|
||||
raise NoMachineError(f"Machine platform '{cloud}' unsupported")
|
||||
raise NoMachineError( # noqa: B904
|
||||
f"Machine platform '{cloud}' unsupported"
|
||||
)
|
||||
|
||||
def create(self, name: Optional[str]):
|
||||
"""Create and start the specified machine instance."""
|
||||
|
||||
@@ -21,9 +21,7 @@ class BaseMachineBackend(ABC):
|
||||
"""Stop and destroy all instances of the specified machine."""
|
||||
|
||||
@abstractmethod
|
||||
def instances(
|
||||
self, name: Optional[str] = None, **config
|
||||
) -> Iterator[dict]:
|
||||
def instances(self, name: Optional[str] = None, **config) -> Iterator[dict]:
|
||||
"""Iterate over status of all instances of the specified machine."""
|
||||
|
||||
def close(self): # noqa: B027
|
||||
|
||||
@@ -6,7 +6,6 @@ from typing import TYPE_CHECKING, Iterator, Optional
|
||||
from dvc_ssh import DEFAULT_PORT, SSHFileSystem
|
||||
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.utils.fs import makedirs
|
||||
|
||||
from .base import BaseMachineBackend
|
||||
|
||||
@@ -30,7 +29,7 @@ def _sshfs(resource: dict):
|
||||
class TerraformBackend(BaseMachineBackend):
|
||||
def __init__(self, tmp_dir: "StrPath", **kwargs):
|
||||
super().__init__(tmp_dir, **kwargs)
|
||||
makedirs(tmp_dir, exist_ok=True)
|
||||
os.makedirs(tmp_dir, exist_ok=True)
|
||||
|
||||
@contextmanager
|
||||
def make_tpi(self, name: str):
|
||||
@@ -39,7 +38,7 @@ class TerraformBackend(BaseMachineBackend):
|
||||
|
||||
try:
|
||||
working_dir = os.path.join(self.tmp_dir, name)
|
||||
makedirs(working_dir, exist_ok=True)
|
||||
os.makedirs(working_dir, exist_ok=True)
|
||||
yield TPIBackend(working_dir=working_dir)
|
||||
except TPIError as exc:
|
||||
raise DvcException("TPI operation failed") from exc
|
||||
@@ -57,12 +56,8 @@ class TerraformBackend(BaseMachineBackend):
|
||||
|
||||
create = partialmethod(_tpi_func, "create") # type: ignore[assignment]
|
||||
destroy = partialmethod(_tpi_func, "destroy") # type: ignore[assignment]
|
||||
instances = partialmethod(
|
||||
_tpi_func, "instances"
|
||||
) # type: ignore[assignment]
|
||||
run_shell = partialmethod(
|
||||
_tpi_func, "run_shell"
|
||||
) # type: ignore[assignment]
|
||||
instances = partialmethod(_tpi_func, "instances") # type: ignore[assignment]
|
||||
run_shell = partialmethod(_tpi_func, "run_shell") # type: ignore[assignment]
|
||||
|
||||
def get_executor_kwargs(self, name: str, **config) -> dict:
|
||||
with self.make_tpi(name) as tpi:
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ class ODBManager:
|
||||
from dvc.config_schema import LOCAL_COMMON
|
||||
|
||||
settings = {"url": config["dir"]}
|
||||
for opt in LOCAL_COMMON.keys():
|
||||
for opt in LOCAL_COMMON:
|
||||
if opt in config:
|
||||
settings[str(opt)] = config.get(opt)
|
||||
|
||||
|
||||
+148
-124
@@ -1,11 +1,11 @@
|
||||
import logging
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, cast
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from funcy import cached_property, collecting, project
|
||||
from voluptuous import And, Any, Coerce, Length, Lower, Required, SetTo
|
||||
from funcy import collecting, first, project
|
||||
from voluptuous import All, And, Any, Coerce, Length, Lower, Required, SetTo
|
||||
|
||||
from dvc import prompt
|
||||
from dvc.exceptions import (
|
||||
@@ -17,6 +17,7 @@ from dvc.exceptions import (
|
||||
MergeError,
|
||||
RemoteCacheRequiredError,
|
||||
)
|
||||
from dvc.utils.objects import cached_property
|
||||
from dvc_data.hashfile import Tree
|
||||
from dvc_data.hashfile import check as ocheck
|
||||
from dvc_data.hashfile import load as oload
|
||||
@@ -41,6 +42,7 @@ if TYPE_CHECKING:
|
||||
from dvc_objects.db import ObjectDB
|
||||
|
||||
from .fs.callbacks import Callback
|
||||
from .ignore import DvcIgnoreFilter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -143,17 +145,17 @@ def _split_dict(d, keys):
|
||||
|
||||
|
||||
def _merge_data(s_list):
|
||||
d = defaultdict(dict)
|
||||
d: Dict[str, Dict] = defaultdict(dict)
|
||||
for key in s_list:
|
||||
if isinstance(key, str):
|
||||
d[key].update({})
|
||||
continue
|
||||
if not isinstance(key, dict):
|
||||
raise ValueError(f"'{type(key).__name__}' not supported.")
|
||||
raise ValueError(f"'{type(key).__name__}' not supported.") # noqa: TRY004
|
||||
|
||||
for k, flags in key.items():
|
||||
if not isinstance(flags, dict):
|
||||
raise ValueError(
|
||||
raise ValueError( # noqa: TRY004
|
||||
f"Expected dict for '{k}', got: '{type(flags).__name__}'"
|
||||
)
|
||||
d[k].update(flags)
|
||||
@@ -203,6 +205,30 @@ def load_from_pipeline(stage, data, typ="outs"):
|
||||
)
|
||||
|
||||
|
||||
def split_file_meta_from_cloud(entry: Dict) -> Dict:
|
||||
if remote_name := entry.pop(Meta.PARAM_REMOTE, None):
|
||||
remote_meta = {}
|
||||
for key in (
|
||||
S3_PARAM_CHECKSUM,
|
||||
HDFS_PARAM_CHECKSUM,
|
||||
Meta.PARAM_VERSION_ID,
|
||||
):
|
||||
if value := entry.pop(key, None):
|
||||
remote_meta[key] = value
|
||||
|
||||
if remote_meta:
|
||||
entry[Output.PARAM_CLOUD] = {remote_name: remote_meta}
|
||||
return entry
|
||||
|
||||
|
||||
def merge_file_meta_from_cloud(entry: Dict) -> Dict:
|
||||
cloud_meta = entry.pop(Output.PARAM_CLOUD, {})
|
||||
if remote_name := first(cloud_meta):
|
||||
entry.update(cloud_meta[remote_name])
|
||||
entry[Meta.PARAM_REMOTE] = remote_name
|
||||
return entry
|
||||
|
||||
|
||||
class OutputDoesNotExistError(DvcException):
|
||||
def __init__(self, path):
|
||||
msg = f"output '{path}' does not exist"
|
||||
@@ -257,6 +283,7 @@ class Output:
|
||||
PARAM_PERSIST = "persist"
|
||||
PARAM_REMOTE = "remote"
|
||||
PARAM_PUSH = "push"
|
||||
PARAM_CLOUD = "cloud"
|
||||
|
||||
METRIC_SCHEMA = Any(
|
||||
None,
|
||||
@@ -267,10 +294,10 @@ class Output:
|
||||
},
|
||||
)
|
||||
|
||||
DoesNotExistError = OutputDoesNotExistError # type: Type[DvcException]
|
||||
IsNotFileOrDirError = OutputIsNotFileOrDirError # type: Type[DvcException]
|
||||
IsStageFileError = OutputIsStageFileError # type: Type[DvcException]
|
||||
IsIgnoredError = OutputIsIgnoredError # type: Type[DvcException]
|
||||
DoesNotExistError: Type[DvcException] = OutputDoesNotExistError
|
||||
IsNotFileOrDirError: Type[DvcException] = OutputIsNotFileOrDirError
|
||||
IsStageFileError: Type[DvcException] = OutputIsStageFileError
|
||||
IsIgnoredError: Type[DvcException] = OutputIsIgnoredError
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -283,30 +310,31 @@ class Output:
|
||||
persist=False,
|
||||
checkpoint=False,
|
||||
desc=None,
|
||||
type=None, # pylint: disable=redefined-builtin
|
||||
type=None, # noqa: A002, pylint: disable=redefined-builtin
|
||||
labels=None,
|
||||
meta=None,
|
||||
remote=None,
|
||||
repo=None,
|
||||
fs_config=None,
|
||||
files: List[Dict[str, Any]] = None,
|
||||
files: Optional[List[Dict[str, Any]]] = None,
|
||||
push: bool = True,
|
||||
):
|
||||
self.annot = Annotation(
|
||||
desc=desc, type=type, labels=labels or [], meta=meta or {}
|
||||
)
|
||||
self.repo = stage.repo if not repo and stage else repo
|
||||
meta = Meta.from_dict(info or {})
|
||||
meta_d = merge_file_meta_from_cloud(info or {})
|
||||
meta = Meta.from_dict(meta_d)
|
||||
# NOTE: when version_aware is not passed into get_cloud_fs, it will be
|
||||
# set based on whether or not path is versioned
|
||||
fs_kwargs = {"version_aware": True} if meta.version_id else {}
|
||||
fs_kwargs = {}
|
||||
if meta.version_id or files:
|
||||
fs_kwargs["version_aware"] = True
|
||||
|
||||
if fs_config is not None:
|
||||
fs_kwargs.update(**fs_config)
|
||||
|
||||
fs_cls, fs_config, fs_path = get_cloud_fs(
|
||||
self.repo, url=path, **fs_kwargs
|
||||
)
|
||||
fs_cls, fs_config, fs_path = get_cloud_fs(self.repo, url=path, **fs_kwargs)
|
||||
self.fs = fs_cls(**fs_config)
|
||||
|
||||
if (
|
||||
@@ -315,7 +343,7 @@ class Output:
|
||||
and isinstance(stage.repo.fs, LocalFileSystem)
|
||||
and path_isin(path, stage.repo.root_dir)
|
||||
):
|
||||
self.def_path = relpath(path, stage.wdir)
|
||||
self.def_path: str = relpath(path, stage.wdir)
|
||||
self.fs = stage.repo.fs
|
||||
else:
|
||||
self.def_path = path
|
||||
@@ -339,6 +367,9 @@ class Output:
|
||||
# should be absolute and don't contain remote:// refs.
|
||||
self.stage = stage
|
||||
self.meta = meta
|
||||
|
||||
if files is not None:
|
||||
files = [merge_file_meta_from_cloud(f) for f in files]
|
||||
self.files = files
|
||||
self.use_cache = False if self.IS_DEPENDENCY else cache
|
||||
self.metric = False if self.IS_DEPENDENCY else metric
|
||||
@@ -367,7 +398,18 @@ class Output:
|
||||
name=self.hash_name,
|
||||
value=getattr(self.meta, self.hash_name, None),
|
||||
)
|
||||
if self.meta.nfiles or self.hash_info and self.hash_info.isdir:
|
||||
self._compute_meta_hash_info_from_files()
|
||||
|
||||
def _compute_meta_hash_info_from_files(self) -> None:
|
||||
if self.files:
|
||||
tree = Tree.from_list(self.files, hash_name=self.hash_name)
|
||||
tree.digest()
|
||||
self.hash_info = tree.hash_info
|
||||
self.meta.isdir = True
|
||||
self.meta.nfiles = len(self.files)
|
||||
self.meta.size = sum(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:
|
||||
self.meta.isdir = True
|
||||
if not self.hash_info and self.hash_name != "md5":
|
||||
md5 = getattr(self.meta, "md5", None)
|
||||
@@ -454,9 +496,7 @@ class Output:
|
||||
|
||||
@property
|
||||
def cache_path(self):
|
||||
return self.odb.fs.unstrip_protocol(
|
||||
self.odb.oid_to_path(self.hash_info.value)
|
||||
)
|
||||
return self.odb.fs.unstrip_protocol(self.odb.oid_to_path(self.hash_info.value))
|
||||
|
||||
def get_hash(self):
|
||||
_, hash_info = self._get_hash_meta()
|
||||
@@ -486,10 +526,9 @@ class Output:
|
||||
return self.hash_info.isdir
|
||||
|
||||
def _is_path_dvcignore(self, path) -> bool:
|
||||
if not self.IS_DEPENDENCY and self.dvcignore:
|
||||
if self.dvcignore.is_ignored(self.fs, path, ignore_subrepos=False):
|
||||
return True
|
||||
return False
|
||||
if self.IS_DEPENDENCY or not self.dvcignore:
|
||||
return False
|
||||
return self.dvcignore.is_ignored(self.fs, path, ignore_subrepos=False)
|
||||
|
||||
@property
|
||||
def exists(self):
|
||||
@@ -510,13 +549,6 @@ class Output:
|
||||
return workspace, key
|
||||
|
||||
def get_entry(self) -> "DataIndexEntry":
|
||||
from dvc.config import NoRemoteError
|
||||
|
||||
try:
|
||||
remote = self.repo.cloud.get_remote_odb(self.remote)
|
||||
except NoRemoteError:
|
||||
remote = None
|
||||
|
||||
if self.files and not self.obj:
|
||||
self.obj = self.get_obj()
|
||||
|
||||
@@ -524,9 +556,6 @@ class Output:
|
||||
meta=self.meta,
|
||||
obj=self.obj,
|
||||
hash_info=self.hash_info,
|
||||
odb=self.odb,
|
||||
cache=self.odb,
|
||||
remote=remote,
|
||||
)
|
||||
if self.stage.is_import and not self.stage.is_repo_import:
|
||||
dep = self.stage.deps[0]
|
||||
@@ -561,7 +590,7 @@ class Output:
|
||||
return self.meta.version_id != self.get_meta().version_id
|
||||
return False
|
||||
|
||||
def workspace_status(self):
|
||||
def workspace_status(self) -> Dict[str, str]:
|
||||
if not self.exists:
|
||||
return {str(self): "deleted"}
|
||||
|
||||
@@ -573,40 +602,40 @@ class Output:
|
||||
|
||||
return {}
|
||||
|
||||
def status(self):
|
||||
def status(self) -> Dict[str, str]:
|
||||
if self.hash_info and self.use_cache and self.changed_cache():
|
||||
return {str(self): "not in cache"}
|
||||
|
||||
return self.workspace_status()
|
||||
|
||||
def changed(self):
|
||||
def changed(self) -> bool:
|
||||
status = self.status()
|
||||
logger.debug(str(status))
|
||||
return bool(status)
|
||||
|
||||
@property
|
||||
def dvcignore(self):
|
||||
def dvcignore(self) -> Optional["DvcIgnoreFilter"]:
|
||||
if self.fs.protocol == "local":
|
||||
return self.repo.dvcignore
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_empty(self):
|
||||
def is_empty(self) -> bool:
|
||||
return self.fs.is_empty(self.fs_path)
|
||||
|
||||
def isdir(self):
|
||||
def isdir(self) -> bool:
|
||||
if self._is_path_dvcignore(self.fs_path):
|
||||
return False
|
||||
return self.fs.isdir(self.fs_path)
|
||||
|
||||
def isfile(self):
|
||||
def isfile(self) -> bool:
|
||||
if self._is_path_dvcignore(self.fs_path):
|
||||
return False
|
||||
return self.fs.isfile(self.fs_path)
|
||||
|
||||
# pylint: disable=no-member
|
||||
|
||||
def ignore(self):
|
||||
def ignore(self) -> None:
|
||||
if not self.use_scm_ignore:
|
||||
return
|
||||
|
||||
@@ -615,7 +644,7 @@ class Output:
|
||||
|
||||
self.repo.scm_context.ignore(self.fspath)
|
||||
|
||||
def ignore_remove(self):
|
||||
def ignore_remove(self) -> None:
|
||||
if not self.use_scm_ignore:
|
||||
return
|
||||
|
||||
@@ -623,11 +652,11 @@ class Output:
|
||||
|
||||
# pylint: enable=no-member
|
||||
|
||||
def save(self):
|
||||
def save(self) -> None:
|
||||
if not self.exists:
|
||||
raise self.DoesNotExistError(self)
|
||||
|
||||
if not self.isfile and not self.isdir:
|
||||
if not self.isfile() and not self.isdir():
|
||||
raise self.IsNotFileOrDirError(self)
|
||||
|
||||
if self.is_empty:
|
||||
@@ -656,18 +685,16 @@ class Output:
|
||||
dry_run=True,
|
||||
)
|
||||
if not self.IS_DEPENDENCY:
|
||||
logger.debug(
|
||||
"Output '%s' doesn't use cache. Skipping saving.", self
|
||||
)
|
||||
logger.debug("Output '%s' doesn't use cache. Skipping saving.", self)
|
||||
|
||||
self.hash_info = self.obj.hash_info
|
||||
self.files = None
|
||||
|
||||
def set_exec(self):
|
||||
def set_exec(self) -> None:
|
||||
if self.isfile() and self.meta.isexec:
|
||||
self.odb.set_exec(self.fs_path)
|
||||
|
||||
def _checkout(self, *args, **kwargs):
|
||||
def _checkout(self, *args, **kwargs) -> Optional[bool]:
|
||||
from dvc_data.hashfile.checkout import CheckoutError as _CheckoutError
|
||||
from dvc_data.hashfile.checkout import LinkError, PromptError
|
||||
|
||||
@@ -675,13 +702,13 @@ class Output:
|
||||
try:
|
||||
return checkout(*args, **kwargs)
|
||||
except PromptError as exc:
|
||||
raise ConfirmRemoveError(exc.path)
|
||||
raise ConfirmRemoveError(exc.path) # noqa: B904
|
||||
except LinkError as exc:
|
||||
raise CacheLinkError([exc.path])
|
||||
raise CacheLinkError([exc.path]) # noqa: B904
|
||||
except _CheckoutError as exc:
|
||||
raise CheckoutError(exc.paths)
|
||||
raise CheckoutError(exc.paths, {}) # noqa: B904
|
||||
|
||||
def commit(self, filter_info=None):
|
||||
def commit(self, filter_info=None) -> None:
|
||||
if not self.exists:
|
||||
raise self.DoesNotExistError(self)
|
||||
|
||||
@@ -689,9 +716,7 @@ class Output:
|
||||
|
||||
if self.use_cache:
|
||||
granular = (
|
||||
self.is_dir_checksum
|
||||
and filter_info
|
||||
and filter_info != self.fs_path
|
||||
self.is_dir_checksum and filter_info and filter_info != self.fs_path
|
||||
)
|
||||
if granular:
|
||||
obj = self._commit_granular_dir(filter_info)
|
||||
@@ -721,10 +746,8 @@ class Output:
|
||||
)
|
||||
self.set_exec()
|
||||
|
||||
def _commit_granular_dir(self, filter_info):
|
||||
prefix = self.fs.path.parts(
|
||||
self.fs.path.relpath(filter_info, self.fs_path)
|
||||
)
|
||||
def _commit_granular_dir(self, filter_info) -> Optional["HashFile"]:
|
||||
prefix = self.fs.path.parts(self.fs.path.relpath(filter_info, self.fs_path))
|
||||
staging, _, save_obj = build(
|
||||
self.odb,
|
||||
self.fs_path,
|
||||
@@ -732,7 +755,8 @@ class Output:
|
||||
self.hash_name,
|
||||
ignore=self.dvcignore,
|
||||
)
|
||||
save_obj = save_obj.filter(prefix)
|
||||
save_obj = cast(Tree, save_obj)
|
||||
save_obj = cast(Tree, save_obj.filter(prefix))
|
||||
checkout_obj = save_obj.get_obj(self.odb, prefix)
|
||||
otransfer(
|
||||
staging,
|
||||
@@ -743,15 +767,22 @@ class Output:
|
||||
)
|
||||
return checkout_obj
|
||||
|
||||
def dumpd(self, **kwargs):
|
||||
meta = self.meta.to_dict()
|
||||
meta.pop("isdir", None)
|
||||
ret = {**self.hash_info.to_dict(), **meta}
|
||||
def dumpd(self, **kwargs): # noqa: C901
|
||||
ret: Dict[str, Any] = {}
|
||||
with_files = (
|
||||
(not self.IS_DEPENDENCY or self.stage.is_import)
|
||||
and self.hash_info.isdir
|
||||
and (kwargs.get("with_files") or self.files is not None)
|
||||
)
|
||||
|
||||
if not with_files:
|
||||
meta_d = self.meta.to_dict()
|
||||
meta_d.pop("isdir", None)
|
||||
ret.update(self.hash_info.to_dict())
|
||||
ret.update(split_file_meta_from_cloud(meta_d))
|
||||
|
||||
if self.is_in_repo:
|
||||
path = self.fs.path.as_posix(
|
||||
relpath(self.fs_path, self.stage.wdir)
|
||||
)
|
||||
path = self.fs.path.as_posix(relpath(self.fs_path, self.stage.wdir))
|
||||
else:
|
||||
path = self.def_path
|
||||
|
||||
@@ -762,12 +793,12 @@ class Output:
|
||||
if not self.use_cache:
|
||||
ret[self.PARAM_CACHE] = self.use_cache
|
||||
|
||||
if isinstance(self.metric, dict):
|
||||
if (
|
||||
self.PARAM_METRIC_XPATH in self.metric
|
||||
and not self.metric[self.PARAM_METRIC_XPATH]
|
||||
):
|
||||
del self.metric[self.PARAM_METRIC_XPATH]
|
||||
if (
|
||||
isinstance(self.metric, dict)
|
||||
and self.PARAM_METRIC_XPATH in self.metric
|
||||
and not self.metric[self.PARAM_METRIC_XPATH]
|
||||
):
|
||||
del self.metric[self.PARAM_METRIC_XPATH]
|
||||
|
||||
if self.metric:
|
||||
ret[self.PARAM_METRIC] = self.metric
|
||||
@@ -787,24 +818,18 @@ class Output:
|
||||
if not self.can_push:
|
||||
ret[self.PARAM_PUSH] = self.can_push
|
||||
|
||||
if (
|
||||
(not self.IS_DEPENDENCY or self.stage.is_import)
|
||||
and self.hash_info.isdir
|
||||
and (kwargs.get("with_files") or self.files is not None)
|
||||
):
|
||||
if self.obj:
|
||||
obj = self.obj
|
||||
else:
|
||||
obj = self.get_obj()
|
||||
ret[self.PARAM_FILES] = obj.as_list(with_meta=True)
|
||||
|
||||
if with_files:
|
||||
obj = self.obj or self.get_obj()
|
||||
if obj:
|
||||
obj = cast("Tree", obj)
|
||||
ret[self.PARAM_FILES] = [
|
||||
split_file_meta_from_cloud(f) for f in obj.as_list(with_meta=True)
|
||||
]
|
||||
return ret
|
||||
|
||||
def verify_metric(self):
|
||||
if self.fs.protocol != "local":
|
||||
raise DvcException(
|
||||
f"verify metric is not supported for {self.protocol}"
|
||||
)
|
||||
raise DvcException(f"verify metric is not supported for {self.protocol}")
|
||||
if not self.metric:
|
||||
return
|
||||
|
||||
@@ -827,16 +852,15 @@ class Output:
|
||||
obj: Optional["HashFile"] = None
|
||||
if self.obj:
|
||||
obj = self.obj
|
||||
elif self.files:
|
||||
tree = Tree.from_list(self.files, hash_name=self.hash_name)
|
||||
tree.digest()
|
||||
obj = tree
|
||||
elif self.hash_info:
|
||||
if self.files:
|
||||
tree = Tree.from_list(self.files, hash_name=self.hash_name)
|
||||
tree.digest()
|
||||
obj = tree
|
||||
else:
|
||||
try:
|
||||
obj = oload(self.odb, self.hash_info)
|
||||
except (FileNotFoundError, ObjectFormatError):
|
||||
return None
|
||||
try:
|
||||
obj = oload(self.odb, self.hash_info)
|
||||
except (FileNotFoundError, ObjectFormatError):
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -844,25 +868,24 @@ class Output:
|
||||
fs_path = self.fs.path
|
||||
if filter_info and filter_info != self.fs_path:
|
||||
prefix = fs_path.relparts(filter_info, self.fs_path)
|
||||
obj = cast(Tree, obj)
|
||||
obj = obj.get_obj(self.odb, prefix)
|
||||
|
||||
return obj
|
||||
|
||||
def checkout(
|
||||
self,
|
||||
force=False,
|
||||
force: bool = False,
|
||||
progress_callback: "Callback" = DEFAULT_CALLBACK,
|
||||
relink=False,
|
||||
filter_info=None,
|
||||
allow_missing=False,
|
||||
checkpoint_reset=False,
|
||||
relink: bool = False,
|
||||
filter_info: Optional[str] = None,
|
||||
allow_missing: bool = False,
|
||||
checkpoint_reset: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
) -> Optional[Tuple[bool, Optional[bool]]]:
|
||||
if not self.use_cache:
|
||||
if progress_callback != DEFAULT_CALLBACK:
|
||||
progress_callback.relative_update(
|
||||
self.get_files_number(filter_info)
|
||||
)
|
||||
progress_callback.relative_update(self.get_files_number(filter_info))
|
||||
return None
|
||||
|
||||
obj = self.get_obj(filter_info=filter_info)
|
||||
@@ -898,7 +921,7 @@ class Output:
|
||||
return added, False if added else modified
|
||||
|
||||
def remove(self, ignore_remove=False):
|
||||
self.fs.remove(self.fs_path)
|
||||
self.fs.remove(self.fs_path, recursive=True)
|
||||
if self.protocol != Schemes.LOCAL:
|
||||
return
|
||||
|
||||
@@ -1020,7 +1043,7 @@ class Output:
|
||||
"Would you like to continue? Use '-f' to force."
|
||||
)
|
||||
if not force and not prompt.confirm(msg.format(self.fs_path)):
|
||||
raise CollectCacheError(
|
||||
raise CollectCacheError( # noqa: B904
|
||||
"unable to fully collect used cache"
|
||||
" without cache for directory '{}'".format(self)
|
||||
)
|
||||
@@ -1029,13 +1052,12 @@ class Output:
|
||||
obj = self.get_obj()
|
||||
if filter_info and filter_info != self.fs_path:
|
||||
assert obj
|
||||
prefix = self.fs.path.parts(
|
||||
self.fs.path.relpath(filter_info, self.fs_path)
|
||||
)
|
||||
obj = obj.filter(prefix)
|
||||
return obj
|
||||
prefix = self.fs.path.parts(self.fs.path.relpath(filter_info, self.fs_path))
|
||||
obj = cast(Tree, obj)
|
||||
return obj.filter(prefix)
|
||||
return cast(Tree, obj)
|
||||
|
||||
def get_used_objs(
|
||||
def get_used_objs( # noqa: C901
|
||||
self, **kwargs
|
||||
) -> Dict[Optional["ObjectDB"], Set["HashInfo"]]:
|
||||
"""Return filtered set of used object IDs for this out."""
|
||||
@@ -1071,6 +1093,7 @@ class Output:
|
||||
logger.warning(msg)
|
||||
return {}
|
||||
|
||||
obj: Optional["HashFile"]
|
||||
if self.is_dir_checksum:
|
||||
obj = self._collect_used_dir_cache(**kwargs)
|
||||
else:
|
||||
@@ -1137,14 +1160,10 @@ class Output:
|
||||
other.pop(opt, None)
|
||||
|
||||
if my != other:
|
||||
raise MergeError(
|
||||
"unable to auto-merge outputs with different options"
|
||||
)
|
||||
raise MergeError("unable to auto-merge outputs with different options")
|
||||
|
||||
if not out.is_dir_checksum:
|
||||
raise MergeError(
|
||||
"unable to auto-merge outputs that are not directories"
|
||||
)
|
||||
raise MergeError("unable to auto-merge outputs that are not directories")
|
||||
|
||||
def merge(self, ancestor, other, allowed=None):
|
||||
from dvc_data.hashfile.tree import MergeError as TreeMergeError
|
||||
@@ -1219,7 +1238,8 @@ class Output:
|
||||
if not self.obj or not other.hash_info.isdir:
|
||||
return
|
||||
other_obj = other.obj if other.obj is not None else other.get_obj()
|
||||
assert isinstance(self.obj, Tree) and isinstance(other_obj, Tree)
|
||||
assert isinstance(self.obj, Tree)
|
||||
assert isinstance(other_obj, Tree)
|
||||
updated = update_meta(self.obj, other_obj)
|
||||
assert updated.hash_info == self.obj.hash_info
|
||||
self.obj = updated
|
||||
@@ -1233,6 +1253,8 @@ META_SCHEMA = {
|
||||
Meta.PARAM_VERSION_ID: str,
|
||||
}
|
||||
|
||||
CLOUD_SCHEMA = All({str: {**META_SCHEMA, **CHECKSUMS_SCHEMA}}, Length(max=1))
|
||||
|
||||
ARTIFACT_SCHEMA = {
|
||||
**CHECKSUMS_SCHEMA,
|
||||
**META_SCHEMA,
|
||||
@@ -1240,12 +1262,14 @@ ARTIFACT_SCHEMA = {
|
||||
Output.PARAM_PLOT: bool,
|
||||
Output.PARAM_PERSIST: bool,
|
||||
Output.PARAM_CHECKPOINT: bool,
|
||||
Output.PARAM_CLOUD: CLOUD_SCHEMA,
|
||||
}
|
||||
|
||||
DIR_FILES_SCHEMA: Dict[str, Any] = {
|
||||
**CHECKSUMS_SCHEMA,
|
||||
**META_SCHEMA,
|
||||
Required(Tree.PARAM_RELPATH): str,
|
||||
Output.PARAM_CLOUD: CLOUD_SCHEMA,
|
||||
}
|
||||
|
||||
SCHEMA = {
|
||||
|
||||
+33
-37
@@ -14,10 +14,11 @@ from typing import (
|
||||
Union,
|
||||
)
|
||||
|
||||
from funcy import cached_property, collecting, first, isa, join, reraise
|
||||
from funcy import collecting, first, isa, join, reraise
|
||||
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.parsing.interpolate import ParseError
|
||||
from dvc.utils.objects import cached_property
|
||||
|
||||
from .context import (
|
||||
Context,
|
||||
@@ -25,7 +26,6 @@ from .context import (
|
||||
KeyNotInContext,
|
||||
MergeError,
|
||||
Node,
|
||||
SeqOrMap,
|
||||
VarsAlreadyLoaded,
|
||||
)
|
||||
from .interpolate import (
|
||||
@@ -39,6 +39,10 @@ if TYPE_CHECKING:
|
||||
from typing import NoReturn
|
||||
|
||||
from dvc.repo import Repo
|
||||
from dvc.types import DictStrAny
|
||||
|
||||
from .context import SeqOrMap
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -52,7 +56,6 @@ DO_KWD = "do"
|
||||
DEFAULT_PARAMS_FILE = "params.yaml"
|
||||
|
||||
JOIN = "@"
|
||||
DictStr = Dict[str, Any]
|
||||
|
||||
|
||||
class ResolveError(DvcException):
|
||||
@@ -69,9 +72,7 @@ def _format_preamble(msg: str, path: str, spacing: str = " ") -> str:
|
||||
|
||||
def format_and_raise(exc: Exception, msg: str, path: str) -> "NoReturn":
|
||||
spacing = (
|
||||
"\n"
|
||||
if isinstance(exc, (ParseError, MergeError, VarsAlreadyLoaded))
|
||||
else " "
|
||||
"\n" if isinstance(exc, (ParseError, MergeError, VarsAlreadyLoaded)) else " "
|
||||
)
|
||||
message = _format_preamble(msg, path, spacing) + str(exc)
|
||||
|
||||
@@ -81,7 +82,7 @@ def format_and_raise(exc: Exception, msg: str, path: str) -> "NoReturn":
|
||||
|
||||
|
||||
def _reraise_err(
|
||||
exc_cls: Type[Exception], *args, from_exc: Exception = None
|
||||
exc_cls: Type[Exception], *args, from_exc: Optional[Exception] = None
|
||||
) -> "NoReturn":
|
||||
err = exc_cls(*args)
|
||||
if from_exc and logger.isEnabledFor(logging.DEBUG):
|
||||
@@ -90,7 +91,7 @@ def _reraise_err(
|
||||
|
||||
|
||||
def check_syntax_errors(
|
||||
definition: DictStr, name: str, path: str, where: str = "stages"
|
||||
definition: "DictStrAny", name: str, path: str, where: str = "stages"
|
||||
):
|
||||
for key, d in definition.items():
|
||||
try:
|
||||
@@ -109,12 +110,11 @@ def split_foreach_name(name: str) -> Tuple[str, Optional[str]]:
|
||||
return group, first(keys)
|
||||
|
||||
|
||||
def check_interpolations(data: DictStr, where: str, path: str):
|
||||
def func(s: DictStr) -> None:
|
||||
def check_interpolations(data: "DictStrAny", where: str, path: str):
|
||||
def func(s: "DictStrAny") -> None:
|
||||
if is_interpolated_string(s):
|
||||
raise ResolveError(
|
||||
_format_preamble(f"'{where}'", path)
|
||||
+ "interpolating is not allowed"
|
||||
_format_preamble(f"'{where}'", path) + "interpolating is not allowed"
|
||||
)
|
||||
|
||||
return recurse(func)(data)
|
||||
@@ -124,7 +124,7 @@ Definition = Union["ForeachDefinition", "EntryDefinition"]
|
||||
|
||||
|
||||
def make_definition(
|
||||
resolver: "DataResolver", name: str, definition: DictStr, **kwargs
|
||||
resolver: "DataResolver", name: str, definition: "DictStrAny", **kwargs
|
||||
) -> Definition:
|
||||
args = resolver, resolver.context, name, definition
|
||||
if FOREACH_KWD in definition:
|
||||
@@ -185,24 +185,22 @@ class DataResolver:
|
||||
def resolve(self):
|
||||
"""Used for testing purposes, otherwise use resolve_one()."""
|
||||
data = join(map(self.resolve_one, self.get_keys()))
|
||||
logger.trace( # type: ignore[attr-defined]
|
||||
"Resolved dvc.yaml:\n%s", data
|
||||
)
|
||||
logger.trace("Resolved dvc.yaml:\n%s", data) # type: ignore[attr-defined]
|
||||
return {STAGES_KWD: data}
|
||||
|
||||
def has_key(self, key: str):
|
||||
return self._has_group_and_key(*split_foreach_name(key))
|
||||
|
||||
def _has_group_and_key(self, group: str, key: str = None):
|
||||
def _has_group_and_key(self, group: str, key: Optional[str] = None):
|
||||
try:
|
||||
definition = self.definitions[group]
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
if key:
|
||||
return isinstance(
|
||||
definition, ForeachDefinition
|
||||
) and definition.has_member(key)
|
||||
return isinstance(definition, ForeachDefinition) and definition.has_member(
|
||||
key
|
||||
)
|
||||
return not isinstance(definition, ForeachDefinition)
|
||||
|
||||
@collecting
|
||||
@@ -223,7 +221,7 @@ class EntryDefinition:
|
||||
resolver: DataResolver,
|
||||
context: Context,
|
||||
name: str,
|
||||
definition: DictStr,
|
||||
definition: "DictStrAny",
|
||||
where: str = STAGES_KWD,
|
||||
):
|
||||
self.resolver = resolver
|
||||
@@ -235,7 +233,7 @@ class EntryDefinition:
|
||||
self.where = where
|
||||
|
||||
def _resolve_wdir(
|
||||
self, context: Context, name: str, wdir: str = None
|
||||
self, context: Context, name: str, wdir: Optional[str] = None
|
||||
) -> str:
|
||||
if not wdir:
|
||||
return self.wdir
|
||||
@@ -252,7 +250,7 @@ class EntryDefinition:
|
||||
except ContextError as exc:
|
||||
format_and_raise(exc, f"stage '{self.name}'", self.relpath)
|
||||
|
||||
def resolve_stage(self, skip_checks: bool = False) -> DictStr:
|
||||
def resolve_stage(self, skip_checks: bool = False) -> "DictStrAny":
|
||||
context = self.context
|
||||
name = self.name
|
||||
if not skip_checks:
|
||||
@@ -300,15 +298,13 @@ class EntryDefinition:
|
||||
|
||||
def _resolve(
|
||||
self, context: "Context", value: Any, key: str, skip_checks: bool
|
||||
) -> DictStr:
|
||||
) -> "DictStrAny":
|
||||
try:
|
||||
return context.resolve(
|
||||
value, skip_interpolation_checks=skip_checks, key=key
|
||||
)
|
||||
except (ParseError, KeyNotInContext) as exc:
|
||||
format_and_raise(
|
||||
exc, f"'{self.where}.{self.name}.{key}'", self.relpath
|
||||
)
|
||||
format_and_raise(exc, f"'{self.where}.{self.name}.{key}'", self.relpath)
|
||||
|
||||
|
||||
class IterationPair(NamedTuple):
|
||||
@@ -322,7 +318,7 @@ class ForeachDefinition:
|
||||
resolver: DataResolver,
|
||||
context: Context,
|
||||
name: str,
|
||||
definition: DictStr,
|
||||
definition: "DictStrAny",
|
||||
where: str = STAGES_KWD,
|
||||
):
|
||||
self.resolver = resolver
|
||||
@@ -347,13 +343,11 @@ class ForeachDefinition:
|
||||
def resolved_iterable(self):
|
||||
return self._resolve_foreach_data()
|
||||
|
||||
def _resolve_foreach_data(self) -> SeqOrMap:
|
||||
def _resolve_foreach_data(self) -> "SeqOrMap":
|
||||
try:
|
||||
iterable = self.context.resolve(self.foreach_data, unwrap=False)
|
||||
except (ContextError, ParseError) as exc:
|
||||
format_and_raise(
|
||||
exc, f"'{self.where}.{self.name}.foreach'", self.relpath
|
||||
)
|
||||
format_and_raise(exc, f"'{self.where}.{self.name}.foreach'", self.relpath)
|
||||
|
||||
# foreach data can be a resolved dictionary/list.
|
||||
self._check_is_map_or_seq(iterable)
|
||||
@@ -378,8 +372,10 @@ class ForeachDefinition:
|
||||
if warn_for:
|
||||
linking_verb = "is" if len(warn_for) == 1 else "are"
|
||||
logger.warning(
|
||||
"%s %s already specified, "
|
||||
"will be overwritten for stages generated from '%s'",
|
||||
(
|
||||
"%s %s already specified, "
|
||||
"will be overwritten for stages generated from '%s'"
|
||||
),
|
||||
" and ".join(warn_for),
|
||||
linking_verb,
|
||||
self.name,
|
||||
@@ -415,13 +411,13 @@ class ForeachDefinition:
|
||||
def _generate_name(self, key: str) -> str:
|
||||
return f"{self.name}{JOIN}{key}"
|
||||
|
||||
def resolve_all(self) -> DictStr:
|
||||
def resolve_all(self) -> "DictStrAny":
|
||||
return join(map(self.resolve_one, self.normalized_iterable))
|
||||
|
||||
def resolve_one(self, key: str) -> DictStr:
|
||||
def resolve_one(self, key: str) -> "DictStrAny":
|
||||
return self._each_iter(key)
|
||||
|
||||
def _each_iter(self, key: str) -> DictStr:
|
||||
def _each_iter(self, key: str) -> "DictStrAny":
|
||||
err_message = f"Could not find '{key}' in foreach group '{self.name}'"
|
||||
with reraise(KeyError, EntryNotFound(err_message)):
|
||||
value = self.normalized_iterable[key]
|
||||
|
||||
+28
-38
@@ -48,9 +48,7 @@ class MergeError(ContextError):
|
||||
self.key = key
|
||||
to_node = into[key]
|
||||
if not isinstance(to_node, Node) or not isinstance(new, Node):
|
||||
super().__init__(
|
||||
f"cannot merge '{key}' as it already exists in {into}"
|
||||
)
|
||||
super().__init__(f"cannot merge '{key}' as it already exists in {into}")
|
||||
return
|
||||
|
||||
assert isinstance(to_node, Node)
|
||||
@@ -138,9 +136,7 @@ class Node:
|
||||
@dataclass
|
||||
class Value(Node):
|
||||
_value: Any
|
||||
meta: Meta = field(
|
||||
compare=False, default_factory=_default_meta, repr=False
|
||||
)
|
||||
meta: Meta = field(compare=False, default_factory=_default_meta, repr=False)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self._value)
|
||||
@@ -172,7 +168,7 @@ class Container(Node, ABC): # noqa: B024
|
||||
return self._convert_with_meta(value, meta)
|
||||
|
||||
@staticmethod
|
||||
def _convert_with_meta(value, meta: Meta = None):
|
||||
def _convert_with_meta(value, meta: Optional[Meta] = None):
|
||||
if value is None or isinstance(value, PRIMITIVES):
|
||||
assert meta
|
||||
return Value(value, meta=meta)
|
||||
@@ -182,10 +178,7 @@ class Container(Node, ABC): # noqa: B024
|
||||
assert meta
|
||||
container = CtxDict if isinstance(value, dict) else CtxList
|
||||
return container(value, meta=meta)
|
||||
msg = (
|
||||
"Unsupported value of type "
|
||||
f"'{type(value).__name__}' in '{meta}'"
|
||||
)
|
||||
msg = f"Unsupported value of type '{type(value).__name__}' in '{meta}'"
|
||||
raise TypeError(msg)
|
||||
|
||||
def __repr__(self):
|
||||
@@ -219,16 +212,14 @@ class Container(Node, ABC): # noqa: B024
|
||||
try:
|
||||
d = self[index]
|
||||
except LookupError as exc:
|
||||
raise ValueError(
|
||||
f"Could not find '{index}' in {self.data}"
|
||||
) from exc
|
||||
raise ValueError(f"Could not find '{index}' in {self.data}") from exc
|
||||
|
||||
if not rems:
|
||||
return d
|
||||
|
||||
rem = rems[0]
|
||||
if not isinstance(d, Container):
|
||||
raise ValueError(
|
||||
raise ValueError( # noqa: TRY004
|
||||
f"{index} is a primitive value, cannot get '{rem}'"
|
||||
)
|
||||
return d.select(rem)
|
||||
@@ -240,7 +231,7 @@ class Container(Node, ABC): # noqa: B024
|
||||
class CtxList(Container, MutableSequence):
|
||||
_key_transform = staticmethod(int)
|
||||
|
||||
def __init__(self, values: Sequence, meta: Meta = None):
|
||||
def __init__(self, values: Sequence, meta: Optional[Meta] = None):
|
||||
super().__init__(meta=meta)
|
||||
self.data: list = []
|
||||
self.extend(values)
|
||||
@@ -263,7 +254,12 @@ class CtxList(Container, MutableSequence):
|
||||
|
||||
|
||||
class CtxDict(Container, MutableMapping):
|
||||
def __init__(self, mapping: Mapping = None, meta: Meta = None, **kwargs):
|
||||
def __init__(
|
||||
self,
|
||||
mapping: Optional[Mapping] = None,
|
||||
meta: Optional[Meta] = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(meta=meta)
|
||||
|
||||
self.data: dict = {}
|
||||
@@ -301,7 +297,7 @@ class Context(CtxDict):
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
self._track = False
|
||||
self._tracked_data = defaultdict(dict)
|
||||
self._tracked_data: Dict[str, Dict] = defaultdict(dict)
|
||||
self.imports = {}
|
||||
self._reserved_keys = {}
|
||||
|
||||
@@ -354,7 +350,7 @@ class Context(CtxDict):
|
||||
|
||||
@classmethod
|
||||
def load_from(
|
||||
cls, fs, path: str, select_keys: List[str] = None
|
||||
cls, fs, path: str, select_keys: Optional[List[str]] = None
|
||||
) -> "Context":
|
||||
from dvc.utils.serialize import load_path
|
||||
|
||||
@@ -375,9 +371,7 @@ class Context(CtxDict):
|
||||
data = {key: data[key] for key in select_keys}
|
||||
except KeyError as exc:
|
||||
key, *_ = exc.args
|
||||
raise ParamsLoadError(
|
||||
f"could not find '{key}' in '{path}'"
|
||||
) from exc
|
||||
raise ParamsLoadError(f"could not find '{key}' in '{path}'") from exc
|
||||
|
||||
meta = Meta(source=path, local=False)
|
||||
ctx = cls(data, meta=meta)
|
||||
@@ -414,27 +408,27 @@ class Context(CtxDict):
|
||||
self.imports[path].extend(cp)
|
||||
|
||||
def check_loaded(self, path, item, keys):
|
||||
if not keys and isinstance(self.imports[path], list):
|
||||
imported = self.imports[path]
|
||||
if not keys and isinstance(imported, list):
|
||||
raise VarsAlreadyLoaded(
|
||||
f"cannot load '{item}' as it's partially loaded already"
|
||||
)
|
||||
elif keys and self.imports[path] is None:
|
||||
if keys and imported is None:
|
||||
raise VarsAlreadyLoaded(
|
||||
f"cannot partially load '{item}' as it's already loaded."
|
||||
)
|
||||
elif isinstance(self.imports[path], list):
|
||||
if not set(keys).isdisjoint(set(self.imports[path])):
|
||||
raise VarsAlreadyLoaded(
|
||||
f"cannot load '{item}' as it's partially loaded already"
|
||||
)
|
||||
if isinstance(imported, list) and set(keys) & set(imported):
|
||||
raise VarsAlreadyLoaded(
|
||||
f"cannot load '{item}' as it's partially loaded already"
|
||||
)
|
||||
|
||||
def load_from_vars(
|
||||
self,
|
||||
fs,
|
||||
vars_: List,
|
||||
wdir: str,
|
||||
stage_name: str = None,
|
||||
default: str = None,
|
||||
stage_name: Optional[str] = None,
|
||||
default: Optional[str] = None,
|
||||
):
|
||||
if default:
|
||||
to_import = fs.path.join(wdir, default)
|
||||
@@ -475,14 +469,12 @@ class Context(CtxDict):
|
||||
But for now, just `item` and `key`, this should do.
|
||||
"""
|
||||
# using dict to make the error messages ordered
|
||||
new = dict.fromkeys(
|
||||
[key for key in keys if key not in self._reserved_keys]
|
||||
)
|
||||
new = dict.fromkeys([key for key in keys if key not in self._reserved_keys])
|
||||
self._reserved_keys.update(new)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
for key in new.keys():
|
||||
for key in new:
|
||||
self._reserved_keys.pop(key)
|
||||
|
||||
@contextmanager
|
||||
@@ -537,9 +529,7 @@ class Context(CtxDict):
|
||||
if is_exact_string(src, matches):
|
||||
# replace "${enabled}", if `enabled` is a boolean, with it's actual
|
||||
# value rather than it's string counterparts.
|
||||
expr = get_expression(
|
||||
matches[0], skip_checks=skip_interpolation_checks
|
||||
)
|
||||
expr = get_expression(matches[0], skip_checks=skip_interpolation_checks)
|
||||
value = self.select(expr, unwrap=unwrap)
|
||||
validate_value(value, key)
|
||||
return value
|
||||
|
||||
@@ -92,14 +92,13 @@ def _(obj: bool):
|
||||
|
||||
|
||||
@to_str.register(dict)
|
||||
def _(obj: dict):
|
||||
def _(obj: dict): # noqa: C901
|
||||
from dvc.config import Config
|
||||
|
||||
config = Config().get("parsing", {})
|
||||
|
||||
result = ""
|
||||
for k, v in flatten(obj).items():
|
||||
|
||||
if isinstance(v, bool):
|
||||
if v:
|
||||
result += f"--{k} "
|
||||
@@ -115,9 +114,7 @@ def _(obj: dict):
|
||||
if isinstance(i, str):
|
||||
i = escape_str(i)
|
||||
elif isinstance(i, Iterable):
|
||||
raise ParseError(
|
||||
f"Cannot interpolate nested iterable in '{k}'"
|
||||
)
|
||||
raise ParseError(f"Cannot interpolate nested iterable in '{k}'")
|
||||
|
||||
if config.get("list", "nargs") == "append":
|
||||
result += f"--{k} {i} "
|
||||
@@ -186,7 +183,7 @@ def parse_expr(s: str):
|
||||
result = get_parser().parseString(s, parseAll=True)
|
||||
except ParseException as exc:
|
||||
format_and_raise_parse_error(exc)
|
||||
raise AssertionError("unreachable")
|
||||
raise AssertionError("unreachable") # noqa: B904
|
||||
|
||||
joined = result.asList()
|
||||
assert len(joined) == 1
|
||||
@@ -204,12 +201,9 @@ def validate_value(value, key):
|
||||
not_primitive = value is not None and not isinstance(value, PRIMITIVES)
|
||||
not_foreach = key is not None and "foreach" not in key
|
||||
if not_primitive and not_foreach:
|
||||
if isinstance(value, dict):
|
||||
if key == "cmd":
|
||||
return True
|
||||
raise ParseError(
|
||||
f"Cannot interpolate data of type '{type(value).__name__}'"
|
||||
)
|
||||
if isinstance(value, dict) and key == "cmd":
|
||||
return True
|
||||
raise ParseError(f"Cannot interpolate data of type '{type(value).__name__}'")
|
||||
|
||||
|
||||
def str_interpolate(
|
||||
|
||||
@@ -9,9 +9,7 @@ META_KWD = "meta"
|
||||
|
||||
def lockfile_version_schema(value):
|
||||
expected = [LOCKFILE_VERSION.V2.value] # pylint: disable=no-member
|
||||
msg = "invalid schema version {}, expected one of {}".format(
|
||||
value, expected
|
||||
)
|
||||
msg = f"invalid schema version {value}, expected one of {expected}"
|
||||
return validators.Any(*expected, msg=msg)(value)
|
||||
|
||||
|
||||
|
||||
@@ -50,8 +50,7 @@ def change_rule(rule, rel):
|
||||
rule = f"!/{rel}{rule}"
|
||||
else:
|
||||
rule = f"/{rel}{rule}"
|
||||
rule = normalize_file(rule)
|
||||
return rule
|
||||
return normalize_file(rule)
|
||||
|
||||
|
||||
def _change_dirname(dirname, pattern_list, new_dirname):
|
||||
|
||||
@@ -13,9 +13,7 @@ class ProcessNotFoundError(DvcException):
|
||||
|
||||
class TimeoutExpired(DvcException):
|
||||
def __init__(self, cmd, timeout):
|
||||
super().__init__(
|
||||
f"'{cmd}' did not complete before timeout '{timeout}'"
|
||||
)
|
||||
super().__init__(f"'{cmd}' did not complete before timeout '{timeout}'")
|
||||
self.cmd = cmd
|
||||
self.timeout = timeout
|
||||
|
||||
|
||||
+12
-16
@@ -38,7 +38,7 @@ class ProcessManager:
|
||||
with open(info_path, encoding="utf-8") as fobj:
|
||||
return ProcessInfo.from_dict(json.load(fobj))
|
||||
except FileNotFoundError:
|
||||
raise KeyError
|
||||
raise KeyError # noqa: B904
|
||||
|
||||
@reraise(FileNotFoundError, KeyError)
|
||||
def __setitem__(self, key: str, value: "ProcessInfo"):
|
||||
@@ -83,18 +83,15 @@ class ProcessManager:
|
||||
def send_signal(self, name: str, sig: int):
|
||||
"""Send `signal` to the specified named process."""
|
||||
process_info = self[name]
|
||||
if sys.platform == "win32":
|
||||
if sig not in (
|
||||
signal.SIGTERM,
|
||||
signal.CTRL_C_EVENT,
|
||||
signal.CTRL_BREAK_EVENT,
|
||||
):
|
||||
raise UnsupportedSignalError(sig)
|
||||
if sys.platform == "win32" and sig not in (
|
||||
signal.SIGTERM,
|
||||
signal.CTRL_C_EVENT, # pylint: disable=no-member
|
||||
signal.CTRL_BREAK_EVENT, # pylint: disable=no-member
|
||||
):
|
||||
raise UnsupportedSignalError(sig)
|
||||
|
||||
def handle_closed_process():
|
||||
logging.warning(
|
||||
f"Process {name} had already aborted unexpectedly."
|
||||
)
|
||||
logging.warning("Process %s had already aborted unexpectedly.", name)
|
||||
process_info.returncode = -1
|
||||
self[name] = process_info
|
||||
|
||||
@@ -105,10 +102,9 @@ class ProcessManager:
|
||||
handle_closed_process()
|
||||
raise
|
||||
except OSError as exc:
|
||||
if sys.platform == "win32":
|
||||
if exc.winerror == 87:
|
||||
handle_closed_process()
|
||||
raise ProcessLookupError from exc
|
||||
if getattr(exc, "winerror", None) == 87:
|
||||
handle_closed_process()
|
||||
raise ProcessLookupError from exc
|
||||
raise
|
||||
|
||||
def terminate(self, name: str):
|
||||
@@ -120,7 +116,7 @@ class ProcessManager:
|
||||
if sys.platform == "win32":
|
||||
self.send_signal(name, signal.SIGTERM)
|
||||
else:
|
||||
self.send_signal(name, signal.SIGKILL)
|
||||
self.send_signal(name, signal.SIGKILL) # pylint: disable=no-member
|
||||
|
||||
def remove(self, name: str, force: bool = False):
|
||||
"""Remove the specified named process from this manager.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user