mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-02 07:18:25 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c72e1fb0b8 | ||
|
|
9fbe5d929d | ||
|
|
0635b0ca15 | ||
|
|
06abf73c22 | ||
|
|
5e2c441d28 | ||
|
|
6efd699267 | ||
|
|
f639ab50ab | ||
|
|
953ae56536 | ||
|
|
ac0397e536 | ||
|
|
b243ccab1f | ||
|
|
c31a5d9df9 | ||
|
|
44b78b81b9 | ||
|
|
d25cb79e0a | ||
|
|
79238fd2a3 | ||
|
|
2bf7bd990a | ||
|
|
68b9ecb95c | ||
|
|
594716ec79 | ||
|
|
84b2e685e2 | ||
|
|
0bb2f3d10c | ||
|
|
f42468bcaf | ||
|
|
28545c0cc5 | ||
|
|
0ee752d464 | ||
|
|
9a1bcd7f1d | ||
|
|
422caa13c4 | ||
|
|
3b56c40add | ||
|
|
b647475a64 | ||
|
|
966adec21c | ||
|
|
4eb073868c | ||
|
|
196c1c7a17 | ||
|
|
df5b582c3f | ||
|
|
20edd72a43 | ||
|
|
ba06c0504a | ||
|
|
18a9fea4af | ||
|
|
bfb65870a4 | ||
|
|
a122b80d52 | ||
|
|
71afff6db3 | ||
|
|
48ec86de28 | ||
|
|
78ab929ad1 |
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.8
|
||||
python-version: 3.12
|
||||
|
||||
- run: python -m pip install --upgrade pip setuptools_scm build twine
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, windows-latest, macos-latest]
|
||||
pyv: ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
||||
pyv: ["3.9", "3.10", "3.11", "3.12"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
run: |
|
||||
pip install --upgrade pip wheel
|
||||
pip install -e ".[dev]"
|
||||
- uses: pre-commit/[email protected].0
|
||||
- uses: pre-commit/[email protected].1
|
||||
- name: run tests
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
|
||||
@@ -22,7 +22,7 @@ repos:
|
||||
- id: sort-simple-yaml
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: 'v0.1.11'
|
||||
rev: 'v0.2.2'
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
|
||||
@@ -5,10 +5,7 @@ from PyInstaller.utils.hooks import collect_submodules, is_module_or_submodule
|
||||
# Celery dynamically imports most celery internals at runtime
|
||||
# pyinstaller hook must expose all modules loaded by
|
||||
# kombu.utils.imports:symbol_by_name()
|
||||
_EXCLUDES = (
|
||||
"celery.bin",
|
||||
"celery.contrib",
|
||||
)
|
||||
_EXCLUDES = ("celery.bin", "celery.contrib")
|
||||
hiddenimports = collect_submodules(
|
||||
"celery",
|
||||
filter=lambda name: not any(
|
||||
|
||||
+10
-10
@@ -1,5 +1,5 @@
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
from typing import Any, ClassVar, Dict, List, Optional
|
||||
from typing import Any, ClassVar, Optional
|
||||
|
||||
from funcy import compact
|
||||
from voluptuous import Required
|
||||
@@ -13,11 +13,11 @@ class Annotation:
|
||||
PARAM_META: ClassVar[str] = "meta"
|
||||
|
||||
desc: Optional[str] = None
|
||||
type: Optional[str] = None # noqa: A003
|
||||
labels: List[str] = field(default_factory=list)
|
||||
meta: Dict[str, Any] = field(default_factory=dict)
|
||||
type: Optional[str] = None
|
||||
labels: list[str] = field(default_factory=list)
|
||||
meta: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> Dict[str, str]:
|
||||
def to_dict(self) -> dict[str, str]:
|
||||
return compact(asdict(self))
|
||||
|
||||
|
||||
@@ -31,11 +31,11 @@ class Artifact:
|
||||
|
||||
path: str
|
||||
desc: Optional[str] = None
|
||||
type: Optional[str] = None # noqa: A003
|
||||
labels: List[str] = field(default_factory=list)
|
||||
meta: Dict[str, Any] = field(default_factory=dict)
|
||||
type: Optional[str] = None
|
||||
labels: list[str] = field(default_factory=list)
|
||||
meta: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> Dict[str, str]:
|
||||
def to_dict(self) -> dict[str, str]:
|
||||
return compact(asdict(self))
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ ANNOTATION_SCHEMA = {
|
||||
Annotation.PARAM_LABELS: [str],
|
||||
Annotation.PARAM_META: object,
|
||||
}
|
||||
ARTIFACT_SCHEMA: Dict[Any, Any] = {
|
||||
ARTIFACT_SCHEMA: dict[Any, Any] = {
|
||||
Required(Artifact.PARAM_PATH): str,
|
||||
**ANNOTATION_SCHEMA, # type: ignore[arg-type]
|
||||
}
|
||||
|
||||
+2
-2
@@ -7,6 +7,7 @@ from .scm import all_branches, all_commits, all_tags
|
||||
from .show import metrics_show, params_show
|
||||
|
||||
__all__ = [
|
||||
"DVCFileSystem",
|
||||
"all_branches",
|
||||
"all_commits",
|
||||
"all_tags",
|
||||
@@ -14,9 +15,8 @@ __all__ = [
|
||||
"exp_save",
|
||||
"exp_show",
|
||||
"get_url",
|
||||
"metrics_show",
|
||||
"open",
|
||||
"params_show",
|
||||
"metrics_show",
|
||||
"read",
|
||||
"DVCFileSystem",
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
from dvc.repo import Repo
|
||||
|
||||
@@ -8,7 +8,7 @@ def artifacts_show(
|
||||
version: Optional[str] = None,
|
||||
stage: Optional[str] = None,
|
||||
repo: Optional[str] = None,
|
||||
) -> Dict[str, str]:
|
||||
) -> dict[str, str]:
|
||||
"""
|
||||
Return path and Git revision for an artifact in a DVC project.
|
||||
|
||||
@@ -36,7 +36,7 @@ def artifacts_show(
|
||||
if version and stage:
|
||||
raise ValueError("Artifact version and stage are mutually exclusive.")
|
||||
|
||||
repo_kwargs: Dict[str, Any] = {
|
||||
repo_kwargs: dict[str, Any] = {
|
||||
"subrepos": True,
|
||||
"uninitialized": True,
|
||||
}
|
||||
|
||||
+7
-15
@@ -1,6 +1,6 @@
|
||||
from contextlib import _GeneratorContextManager as GCM
|
||||
from contextlib import contextmanager
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
from funcy import reraise
|
||||
|
||||
@@ -39,7 +39,7 @@ def get_url(path, repo=None, rev=None, remote=None):
|
||||
from dvc.config import NoRemoteError
|
||||
from dvc_data.index import StorageKeyError
|
||||
|
||||
repo_kwargs: Dict[str, Any] = {}
|
||||
repo_kwargs: dict[str, Any] = {}
|
||||
if remote:
|
||||
repo_kwargs["config"] = {"core": {"remote": remote}}
|
||||
with Repo.open(
|
||||
@@ -57,11 +57,7 @@ def get_url(path, repo=None, rev=None, remote=None):
|
||||
class _OpenContextManager(GCM):
|
||||
def __init__(self, func, args, kwds):
|
||||
self.gen = func(*args, **kwds)
|
||||
self.func, self.args, self.kwds = ( # type: ignore[assignment]
|
||||
func,
|
||||
args,
|
||||
kwds,
|
||||
)
|
||||
self.func, self.args, self.kwds = (func, args, kwds) # type: ignore[assignment]
|
||||
|
||||
def __getattr__(self, name):
|
||||
raise AttributeError("dvc.api.open() should be used in a with statement.")
|
||||
@@ -74,8 +70,8 @@ def open( # noqa: A001
|
||||
remote: Optional[str] = None,
|
||||
mode: str = "r",
|
||||
encoding: Optional[str] = None,
|
||||
config: Optional[Dict[str, Any]] = None,
|
||||
remote_config: Optional[Dict[str, Any]] = None,
|
||||
config: Optional[dict[str, Any]] = None,
|
||||
remote_config: Optional[dict[str, Any]] = None,
|
||||
):
|
||||
"""
|
||||
Opens a file tracked in a DVC project.
|
||||
@@ -233,7 +229,7 @@ def _open(
|
||||
config=None,
|
||||
remote_config=None,
|
||||
):
|
||||
repo_kwargs: Dict[str, Any] = {
|
||||
repo_kwargs: dict[str, Any] = {
|
||||
"subrepos": True,
|
||||
"uninitialized": True,
|
||||
"remote": remote,
|
||||
@@ -262,11 +258,7 @@ def _open(
|
||||
fs_path = fs.from_os_path(path)
|
||||
|
||||
try:
|
||||
with fs.open(
|
||||
fs_path,
|
||||
mode=mode,
|
||||
encoding=encoding,
|
||||
) as fobj:
|
||||
with fs.open(fs_path, mode=mode, encoding=encoding) as fobj:
|
||||
yield fobj
|
||||
except FileNotFoundError as exc:
|
||||
raise FileMissingError(path) from exc
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Dict, List, Optional, Union
|
||||
from typing import Optional, Union
|
||||
|
||||
from rich.text import Text
|
||||
|
||||
@@ -9,7 +9,7 @@ from dvc.repo.experiments.show import tabulate
|
||||
def exp_save(
|
||||
name: Optional[str] = None,
|
||||
force: bool = False,
|
||||
include_untracked: Optional[List[str]] = None,
|
||||
include_untracked: Optional[list[str]] = None,
|
||||
):
|
||||
"""
|
||||
Create a new DVC experiment using `exp save`.
|
||||
@@ -61,12 +61,12 @@ def _postprocess(exp_rows):
|
||||
|
||||
def exp_show(
|
||||
repo: Optional[str] = None,
|
||||
revs: Optional[Union[str, List[str]]] = None,
|
||||
revs: Optional[Union[str, list[str]]] = None,
|
||||
num: int = 1,
|
||||
param_deps: bool = False,
|
||||
force: bool = False,
|
||||
config: Optional[Dict] = None,
|
||||
) -> List[Dict]:
|
||||
config: Optional[dict] = None,
|
||||
) -> list[dict]:
|
||||
"""Get DVC experiments tracked in `repo`.
|
||||
|
||||
Without arguments, this function will retrieve all experiments derived from
|
||||
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
from typing import List, Optional
|
||||
from typing import Optional
|
||||
|
||||
from dvc.repo import Repo
|
||||
|
||||
|
||||
def all_branches(repo: Optional[str] = None) -> List[str]:
|
||||
def all_branches(repo: Optional[str] = None) -> list[str]:
|
||||
"""Get all Git branches in a DVC repository.
|
||||
|
||||
Args:
|
||||
@@ -20,7 +20,7 @@ def all_branches(repo: Optional[str] = None) -> List[str]:
|
||||
return _repo.scm.list_branches()
|
||||
|
||||
|
||||
def all_commits(repo: Optional[str] = None) -> List[str]:
|
||||
def all_commits(repo: Optional[str] = None) -> list[str]:
|
||||
"""Get all Git commits in a DVC repository.
|
||||
|
||||
Args:
|
||||
@@ -37,7 +37,7 @@ def all_commits(repo: Optional[str] = None) -> List[str]:
|
||||
return _repo.scm.list_all_commits()
|
||||
|
||||
|
||||
def all_tags(repo: Optional[str] = None) -> List[str]:
|
||||
def all_tags(repo: Optional[str] = None) -> list[str]:
|
||||
"""Get all Git tags in a DVC repository.
|
||||
|
||||
Args:
|
||||
|
||||
+8
-7
@@ -1,6 +1,7 @@
|
||||
import typing
|
||||
from collections import Counter
|
||||
from typing import Dict, Iterable, Optional, Union
|
||||
from collections.abc import Iterable
|
||||
from typing import Optional, Union
|
||||
|
||||
from funcy import first
|
||||
|
||||
@@ -8,7 +9,7 @@ from dvc.repo import Repo
|
||||
|
||||
|
||||
def _postprocess(results):
|
||||
processed: Dict[str, Dict] = {}
|
||||
processed: dict[str, dict] = {}
|
||||
for rev, rev_data in results.items():
|
||||
if not rev_data:
|
||||
continue
|
||||
@@ -24,7 +25,7 @@ def _postprocess(results):
|
||||
(k if counts[k] == 1 else f"{file_name}:{k}"): v
|
||||
for k, v in file_data["data"].items()
|
||||
}
|
||||
processed[rev] = {**processed[rev], **to_merge}
|
||||
processed[rev] = processed[rev] | to_merge
|
||||
|
||||
if "workspace" in processed:
|
||||
del processed["workspace"]
|
||||
@@ -36,8 +37,8 @@ def metrics_show(
|
||||
*targets: str,
|
||||
repo: Optional[str] = None,
|
||||
rev: Optional[str] = None,
|
||||
config: Optional[Dict] = None,
|
||||
) -> Dict:
|
||||
config: Optional[dict] = None,
|
||||
) -> dict:
|
||||
"""Get metrics tracked in `repo`.
|
||||
|
||||
Without arguments, this function will retrieve all metrics from all tracked
|
||||
@@ -159,8 +160,8 @@ def params_show(
|
||||
stages: Optional[Union[str, Iterable[str]]] = None,
|
||||
rev: Optional[str] = None,
|
||||
deps: bool = False,
|
||||
config: Optional[Dict] = None,
|
||||
) -> Dict:
|
||||
config: Optional[dict] = None,
|
||||
) -> dict:
|
||||
"""Get parameters tracked in `repo`.
|
||||
|
||||
Without arguments, this function will retrieve all params from all tracked
|
||||
|
||||
+4
-10
@@ -1,5 +1,5 @@
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Optional, Tuple
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from dvc.fs import GitFileSystem, Schemes
|
||||
from dvc_data.hashfile.db import get_odb
|
||||
@@ -15,7 +15,7 @@ def _get_odb(
|
||||
repo,
|
||||
settings,
|
||||
fs=None,
|
||||
prefix: Optional[Tuple[str, ...]] = None,
|
||||
prefix: Optional[tuple[str, ...]] = None,
|
||||
hash_name: Optional[str] = None,
|
||||
**kwargs,
|
||||
):
|
||||
@@ -144,15 +144,9 @@ def migrate_2_to_3(repo: "Repo", dry: bool = False):
|
||||
)
|
||||
return
|
||||
|
||||
with TqdmCallback(
|
||||
desc="Computing DVC 3.0 hashes",
|
||||
unit="files",
|
||||
) as cb:
|
||||
with TqdmCallback(desc="Computing DVC 3.0 hashes", unit="files") as cb:
|
||||
migration = prepare(src, dest, callback=cb)
|
||||
|
||||
with TqdmCallback(
|
||||
desc="Migrating to DVC 3.0 cache",
|
||||
unit="files",
|
||||
) as cb:
|
||||
with TqdmCallback(desc="Migrating to DVC 3.0 cache", unit="files") as cb:
|
||||
count = migrate(migration, callback=cb)
|
||||
ui.write(f"Migrated {count} files to DVC 3.0 cache location.")
|
||||
|
||||
+31
-31
@@ -55,48 +55,48 @@ from . import DvcParserError, formatter
|
||||
logger = logger.getChild(__name__)
|
||||
|
||||
COMMANDS = [
|
||||
init,
|
||||
queue,
|
||||
add,
|
||||
artifacts,
|
||||
cache,
|
||||
check_ignore,
|
||||
checkout,
|
||||
commit,
|
||||
completion,
|
||||
config,
|
||||
daemon,
|
||||
dag,
|
||||
data,
|
||||
data_sync,
|
||||
destroy,
|
||||
diff,
|
||||
du,
|
||||
experiments,
|
||||
freeze,
|
||||
gc,
|
||||
get,
|
||||
get_url,
|
||||
destroy,
|
||||
add,
|
||||
remove,
|
||||
move,
|
||||
unprotect,
|
||||
repro,
|
||||
data_sync,
|
||||
gc,
|
||||
git_hook,
|
||||
imp,
|
||||
imp_db,
|
||||
imp_url,
|
||||
config,
|
||||
checkout,
|
||||
remote,
|
||||
cache,
|
||||
metrics,
|
||||
params,
|
||||
init,
|
||||
install,
|
||||
root,
|
||||
ls,
|
||||
ls_url,
|
||||
freeze,
|
||||
dag,
|
||||
daemon,
|
||||
commit,
|
||||
completion,
|
||||
diff,
|
||||
version,
|
||||
update,
|
||||
git_hook,
|
||||
metrics,
|
||||
move,
|
||||
params,
|
||||
plots,
|
||||
queue,
|
||||
remote,
|
||||
remove,
|
||||
repro,
|
||||
root,
|
||||
stage,
|
||||
experiments,
|
||||
check_ignore,
|
||||
data,
|
||||
artifacts,
|
||||
studio,
|
||||
du,
|
||||
unprotect,
|
||||
update,
|
||||
version,
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, ClassVar
|
||||
|
||||
from funcy import chunks, compact, log_durations
|
||||
|
||||
@@ -17,14 +17,14 @@ logger = logger.getChild(__name__)
|
||||
|
||||
|
||||
class CmdDataStatus(CmdBase):
|
||||
COLORS = {
|
||||
COLORS: ClassVar[dict[str, str]] = {
|
||||
"not_in_remote": "red",
|
||||
"not_in_cache": "red",
|
||||
"committed": "green",
|
||||
"uncommitted": "yellow",
|
||||
"untracked": "cyan",
|
||||
}
|
||||
LABELS = {
|
||||
LABELS: ClassVar[dict[str, str]] = {
|
||||
"not_in_remote": "Not in remote",
|
||||
"not_in_cache": "Not in cache",
|
||||
"committed": "DVC committed changes",
|
||||
@@ -32,7 +32,7 @@ class CmdDataStatus(CmdBase):
|
||||
"untracked": "Untracked files",
|
||||
"unchanged": "DVC unchanged files",
|
||||
}
|
||||
HINTS = {
|
||||
HINTS: ClassVar[dict[str, tuple[str, ...]]] = {
|
||||
"not_in_remote": ('use "dvc push <file>..." to upload files',),
|
||||
"not_in_cache": ('use "dvc fetch <file>..." to download files',),
|
||||
"committed": ("git commit the corresponding dvc files to update the repo",),
|
||||
|
||||
@@ -429,5 +429,11 @@ def add_parser(subparsers, _parent_parser):
|
||||
default=False,
|
||||
help="Show status in JSON format.",
|
||||
)
|
||||
status_parser.add_argument(
|
||||
"--no-updates",
|
||||
dest="check_updates",
|
||||
action="store_false",
|
||||
help="Ignore updates to imported data.",
|
||||
)
|
||||
|
||||
status_parser.set_defaults(func=CmdDataStatus)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, Dict
|
||||
from typing import Any
|
||||
|
||||
from dvc.cli import completion, formatter
|
||||
from dvc.cli.command import CmdBase
|
||||
@@ -11,7 +11,7 @@ logger = logger.getChild(__name__)
|
||||
|
||||
class CmdExperimentsPush(CmdBase):
|
||||
@staticmethod
|
||||
def log_result(result: Dict[str, Any], remote: str):
|
||||
def log_result(result: dict[str, Any], remote: str):
|
||||
from dvc.utils import humanize
|
||||
|
||||
def join_exps(exps):
|
||||
|
||||
@@ -107,8 +107,4 @@ def _add_run_common(parser):
|
||||
default=None,
|
||||
help="Custom commit message to use when committing the experiment.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-M", # obsolete
|
||||
dest="message",
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
parser.add_argument("-M", dest="message", help=argparse.SUPPRESS) # obsolete
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import argparse
|
||||
|
||||
from dvc.cli import formatter
|
||||
from dvc.cli import completion, formatter
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.exceptions import DvcException
|
||||
@@ -14,7 +14,9 @@ class CmdExperimentsSave(CmdBase):
|
||||
def run(self):
|
||||
try:
|
||||
ref = self.repo.experiments.save(
|
||||
targets=self.args.targets,
|
||||
name=self.args.name,
|
||||
recursive=self.args.recursive,
|
||||
force=self.args.force,
|
||||
include_untracked=self.args.include_untracked,
|
||||
message=self.args.message,
|
||||
@@ -41,6 +43,18 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
help=EXPERIMENTS_SAVE_HELP,
|
||||
formatter_class=formatter.RawDescriptionHelpFormatter,
|
||||
)
|
||||
save_parser.add_argument(
|
||||
"targets",
|
||||
nargs="*",
|
||||
help=("Limit DVC caching to these .dvc files and stage names."),
|
||||
).complete = completion.DVCFILES_AND_STAGE
|
||||
save_parser.add_argument(
|
||||
"-R",
|
||||
"--recursive",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Cache subdirectories of the specified directory.",
|
||||
)
|
||||
save_parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
@@ -79,9 +93,5 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
default=None,
|
||||
help="Custom commit message to use when committing the experiment.",
|
||||
)
|
||||
save_parser.add_argument(
|
||||
"-M", # obsolete
|
||||
dest="message",
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
save_parser.add_argument("-M", dest="message", help=argparse.SUPPRESS) # obsolete
|
||||
save_parser.set_defaults(func=CmdExperimentsSave)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import argparse
|
||||
import re
|
||||
from collections.abc import Iterable
|
||||
from datetime import date, datetime
|
||||
from typing import TYPE_CHECKING, Dict, Iterable
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from funcy import lmap
|
||||
|
||||
@@ -57,7 +58,7 @@ def baseline_styler(typ):
|
||||
|
||||
def show_experiments(
|
||||
td: "TabularData",
|
||||
headers: Dict[str, Iterable[str]],
|
||||
headers: dict[str, Iterable[str]],
|
||||
keep=None,
|
||||
drop=None,
|
||||
pager=True,
|
||||
|
||||
@@ -57,6 +57,9 @@ class CmdList(CmdBaseNoRepo):
|
||||
elif entries:
|
||||
show_entries(entries, with_color=True, with_size=self.args.size)
|
||||
return 0
|
||||
except FileNotFoundError:
|
||||
logger.exception("")
|
||||
return 1
|
||||
except DvcException:
|
||||
logger.exception("failed to list '%s'", self.args.url)
|
||||
return 1
|
||||
@@ -119,11 +122,7 @@ def add_parser(subparsers, parent_parser):
|
||||
"specified by '--remote') in the target repository."
|
||||
),
|
||||
)
|
||||
list_parser.add_argument(
|
||||
"--size",
|
||||
action="store_true",
|
||||
help="Show sizes.",
|
||||
)
|
||||
list_parser.add_argument("--size", action="store_true", help="Show sizes.")
|
||||
list_parser.add_argument(
|
||||
"path",
|
||||
nargs="?",
|
||||
|
||||
@@ -20,7 +20,7 @@ class LsColors:
|
||||
else:
|
||||
self._codes[code] = color
|
||||
|
||||
def format(self, entry): # noqa: A003
|
||||
def format(self, entry):
|
||||
text = entry["path"]
|
||||
|
||||
if entry.get("isout", False) and "out" in self._codes:
|
||||
|
||||
@@ -38,16 +38,9 @@ def add_parser(subparsers, parent_parser):
|
||||
"url", help="See `dvc import-url -h` for full list of supported URLs."
|
||||
)
|
||||
lsurl_parser.add_argument(
|
||||
"-R",
|
||||
"--recursive",
|
||||
action="store_true",
|
||||
help="Recursively list files.",
|
||||
)
|
||||
lsurl_parser.add_argument(
|
||||
"--size",
|
||||
action="store_true",
|
||||
help="Show sizes.",
|
||||
"-R", "--recursive", action="store_true", help="Recursively list files."
|
||||
)
|
||||
lsurl_parser.add_argument("--size", action="store_true", help="Show sizes.")
|
||||
lsurl_parser.add_argument(
|
||||
"--fs-config",
|
||||
type=str,
|
||||
|
||||
+7
-19
@@ -1,6 +1,6 @@
|
||||
import argparse
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from funcy import compact, first, get_in
|
||||
|
||||
@@ -20,35 +20,26 @@ logger = logger.getChild(__name__)
|
||||
|
||||
|
||||
def _show_json(
|
||||
renderers_with_errors: List["RendererWithErrors"],
|
||||
renderers_with_errors: list["RendererWithErrors"],
|
||||
split=False,
|
||||
errors: Optional[Dict[str, Exception]] = None,
|
||||
errors: Optional[dict[str, Exception]] = None,
|
||||
):
|
||||
from dvc.render.convert import to_json
|
||||
from dvc.utils.serialize import encode_exception
|
||||
|
||||
all_errors: List[Dict] = []
|
||||
all_errors: list[dict] = []
|
||||
data = {}
|
||||
|
||||
for renderer, src_errors, def_errors in renderers_with_errors:
|
||||
name = renderer.name
|
||||
data[name] = to_json(renderer, split)
|
||||
all_errors.extend(
|
||||
{
|
||||
"name": name,
|
||||
"rev": rev,
|
||||
"source": source,
|
||||
**encode_exception(e),
|
||||
}
|
||||
{"name": name, "rev": rev, "source": source, **encode_exception(e)}
|
||||
for rev, per_rev_src_errors in src_errors.items()
|
||||
for source, e in per_rev_src_errors.items()
|
||||
)
|
||||
all_errors.extend(
|
||||
{
|
||||
"name": name,
|
||||
"rev": rev,
|
||||
**encode_exception(e),
|
||||
}
|
||||
{"name": name, "rev": rev, **encode_exception(e)}
|
||||
for rev, e in def_errors.items()
|
||||
)
|
||||
|
||||
@@ -101,10 +92,7 @@ class CmdPlots(CmdBase):
|
||||
return 1
|
||||
|
||||
try:
|
||||
plots_data = self._func(
|
||||
targets=self.args.targets,
|
||||
props=self._props(),
|
||||
)
|
||||
plots_data = self._func(targets=self.args.targets, props=self._props())
|
||||
|
||||
if not plots_data and not self.args.json:
|
||||
ui.error_write(
|
||||
|
||||
@@ -2,14 +2,7 @@ from dvc.cli import formatter
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.commands.queue import kill, logs, remove, start, status, stop
|
||||
|
||||
SUB_COMMANDS = [
|
||||
start,
|
||||
stop,
|
||||
status,
|
||||
logs,
|
||||
remove,
|
||||
kill,
|
||||
]
|
||||
SUB_COMMANDS = [start, stop, status, logs, remove, kill]
|
||||
|
||||
|
||||
def add_parser(subparsers, parent_parser):
|
||||
|
||||
@@ -45,9 +45,5 @@ def add_parser(queue_subparsers, parent_parser):
|
||||
),
|
||||
action="store_true",
|
||||
)
|
||||
queue_logs_parser.add_argument(
|
||||
"task",
|
||||
help="Task to show.",
|
||||
metavar="<task>",
|
||||
)
|
||||
queue_logs_parser.add_argument("task", help="Task to show.", metavar="<task>")
|
||||
queue_logs_parser.set_defaults(func=CmdQueueLogs)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import argparse
|
||||
import logging
|
||||
from collections.abc import Iterable
|
||||
from contextlib import contextmanager
|
||||
from itertools import chain, filterfalse
|
||||
from typing import TYPE_CHECKING, Dict, Iterable, List
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dvc.cli import completion, formatter
|
||||
from dvc.cli.command import CmdBase
|
||||
@@ -33,7 +34,7 @@ def generate_description(stage: "Stage") -> str:
|
||||
def is_plot_or_metric(out: "Output"):
|
||||
return bool(out.plot) or bool(out.metric)
|
||||
|
||||
desc: List[str] = []
|
||||
desc: list[str] = []
|
||||
|
||||
outs = list(filterfalse(is_plot_or_metric, stage.outs))
|
||||
if outs:
|
||||
@@ -55,7 +56,7 @@ def prepare_stages_data(
|
||||
stages: Iterable["Stage"],
|
||||
description: bool = True,
|
||||
max_length: int = MAX_TEXT_LENGTH,
|
||||
) -> Dict[str, str]:
|
||||
) -> dict[str, str]:
|
||||
return {
|
||||
stage.addressing: (
|
||||
prepare_description(stage, max_length=max_length) if description else ""
|
||||
@@ -67,7 +68,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
|
||||
stages: list["Stage"] = self.repo.index.stages
|
||||
logger.trace("%d no. of stages found", len(stages))
|
||||
return stages
|
||||
|
||||
@@ -96,7 +97,7 @@ class CmdStageList(CmdBase):
|
||||
return 0
|
||||
|
||||
|
||||
def parse_cmd(commands: List[str]) -> str:
|
||||
def parse_cmd(commands: list[str]) -> str:
|
||||
"""
|
||||
We need to take into account two cases:
|
||||
|
||||
|
||||
@@ -60,18 +60,19 @@ class CmdDataStatus(CmdDataBase):
|
||||
all_commits=self.args.all_commits,
|
||||
with_deps=self.args.with_deps,
|
||||
recursive=self.args.recursive,
|
||||
check_updates=self.args.check_updates,
|
||||
)
|
||||
except DvcException:
|
||||
logger.exception("")
|
||||
return 1
|
||||
|
||||
if self.args.quiet:
|
||||
return bool(st)
|
||||
|
||||
if self.args.json:
|
||||
ui.write_json(st)
|
||||
return 0
|
||||
|
||||
if self.args.quiet:
|
||||
return bool(st)
|
||||
|
||||
if st:
|
||||
self._show(st, indent)
|
||||
return 0
|
||||
|
||||
+19
-9
@@ -5,6 +5,7 @@ from funcy import get_in
|
||||
from dvc.cli import formatter
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.commands.config import CmdConfig
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.log import logger
|
||||
|
||||
logger = logger.getChild(__name__)
|
||||
@@ -22,12 +23,21 @@ class CmdStudioLogin(CmdConfig):
|
||||
hostname = self.args.hostname or os.environ.get(DVC_STUDIO_URL) or STUDIO_URL
|
||||
scopes = self.args.scopes
|
||||
|
||||
studio = self.config.get("studio", {})
|
||||
if studio.get("url", hostname) == hostname and "token" in studio:
|
||||
raise DvcException(
|
||||
"Token already exists. "
|
||||
"To login with a different token, "
|
||||
"logout using 'dvc studio logout'."
|
||||
)
|
||||
|
||||
open_browser = not self.args.no_open
|
||||
try:
|
||||
token_name, access_token = get_access_token(
|
||||
_, access_token = get_access_token(
|
||||
token_name=name,
|
||||
hostname=hostname,
|
||||
scopes=scopes,
|
||||
use_device_code=self.args.no_open,
|
||||
open_browser=open_browser,
|
||||
client_name="DVC",
|
||||
)
|
||||
except StudioAuthError as e:
|
||||
@@ -35,11 +45,9 @@ class CmdStudioLogin(CmdConfig):
|
||||
return 1
|
||||
|
||||
self.save_config(hostname, access_token)
|
||||
ui.write(
|
||||
"Authentication has been successfully completed."
|
||||
"The generated token will now be accessible as"
|
||||
f" {token_name} in the user's Studio profile."
|
||||
)
|
||||
|
||||
config_path = self.config.files["global"]
|
||||
ui.write(f"Authentication complete. Saved token to {config_path}.")
|
||||
return 0
|
||||
|
||||
def save_config(self, hostname, token):
|
||||
@@ -54,12 +62,14 @@ class CmdStudioLogout(CmdConfig):
|
||||
|
||||
with self.config.edit("global") as conf:
|
||||
if not get_in(conf, ["studio", "token"]):
|
||||
ui.error_write("Not logged in to Studio.")
|
||||
ui.error_write(
|
||||
"Not logged in to Studio. Log in with 'dvc studio login'."
|
||||
)
|
||||
return 1
|
||||
|
||||
del conf["studio"]["token"]
|
||||
|
||||
ui.write("Logged out from Studio")
|
||||
ui.write("Logged out from Studio (you can log back in with 'dvc studio login')")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
+20
-28
@@ -1,23 +1,15 @@
|
||||
from collections import abc
|
||||
from itertools import chain, repeat, zip_longest
|
||||
from operator import itemgetter
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Dict,
|
||||
from collections.abc import (
|
||||
ItemsView,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Mapping,
|
||||
MutableSequence,
|
||||
Optional,
|
||||
Sequence,
|
||||
Set,
|
||||
Tuple,
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
from itertools import chain, repeat, zip_longest
|
||||
from operator import itemgetter
|
||||
from typing import TYPE_CHECKING, Any, Optional, Union, overload
|
||||
|
||||
from funcy import reraise
|
||||
|
||||
@@ -25,7 +17,7 @@ if TYPE_CHECKING:
|
||||
from dvc.ui.table import CellT
|
||||
|
||||
|
||||
class Column(List["CellT"]):
|
||||
class Column(list["CellT"]):
|
||||
pass
|
||||
|
||||
|
||||
@@ -35,13 +27,13 @@ def with_value(value, default):
|
||||
|
||||
class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
def __init__(self, columns: Sequence[str], fill_value: Optional[str] = ""):
|
||||
self._columns: Dict[str, Column] = {name: Column() for name in columns}
|
||||
self._keys: List[str] = list(columns)
|
||||
self._columns: dict[str, Column] = {name: Column() for name in columns}
|
||||
self._keys: list[str] = list(columns)
|
||||
self._fill_value = fill_value
|
||||
self._protected: Set[str] = set()
|
||||
self._protected: set[str] = set()
|
||||
|
||||
@property
|
||||
def columns(self) -> List[Column]:
|
||||
def columns(self) -> list[Column]:
|
||||
return list(map(self.column, self.keys()))
|
||||
|
||||
def is_protected(self, col_name) -> bool:
|
||||
@@ -60,10 +52,10 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
projection = {k: self.column(k) for k in self.keys()}
|
||||
return projection.items()
|
||||
|
||||
def keys(self) -> List[str]:
|
||||
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
|
||||
@@ -81,7 +73,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
for val, col in self._iter_col_row(value):
|
||||
col.insert(index, val)
|
||||
|
||||
def __iter__(self) -> Iterator[List["CellT"]]:
|
||||
def __iter__(self) -> Iterator[list["CellT"]]:
|
||||
return map(list, zip(*self.columns))
|
||||
|
||||
def __getattr__(self, item: str) -> Column:
|
||||
@@ -127,7 +119,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
return len(self.columns[0])
|
||||
|
||||
@property
|
||||
def shape(self) -> Tuple[int, int]:
|
||||
def shape(self) -> tuple[int, int]:
|
||||
return len(self.columns), len(self)
|
||||
|
||||
def drop(self, *col_names: str) -> None:
|
||||
@@ -170,7 +162,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
if key not in keys:
|
||||
self.add_column(key)
|
||||
|
||||
row: List["CellT"] = [
|
||||
row: list["CellT"] = [
|
||||
with_value(d.get(key), self._fill_value) for key in self.keys()
|
||||
]
|
||||
self.append(row)
|
||||
@@ -185,7 +177,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
|
||||
def as_dict(
|
||||
self, cols: Optional[Iterable[str]] = None
|
||||
) -> Iterable[Dict[str, "CellT"]]:
|
||||
) -> 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))]
|
||||
|
||||
@@ -202,7 +194,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
if how not in ["any", "all"]:
|
||||
raise ValueError(f"Invalid 'how' value {how}. Choose one of ['any', 'all']")
|
||||
|
||||
match_line: Set = set()
|
||||
match_line: set = set()
|
||||
match_any = True
|
||||
if how == "all":
|
||||
match_any = False
|
||||
@@ -245,7 +237,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
)
|
||||
|
||||
if axis == "cols":
|
||||
cols_to_drop: List[str] = []
|
||||
cols_to_drop: list[str] = []
|
||||
for n_col, col in enumerate(self.columns):
|
||||
if subset and self.keys()[n_col] not in subset:
|
||||
continue
|
||||
@@ -259,7 +251,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
|
||||
elif axis == "rows":
|
||||
unique_rows = []
|
||||
rows_to_drop: List[int] = []
|
||||
rows_to_drop: list[int] = []
|
||||
for n_row, row in enumerate(self):
|
||||
if subset:
|
||||
row = [
|
||||
@@ -319,7 +311,7 @@ def diff_table(
|
||||
) -> TabularData:
|
||||
a_rev = a_rev or "HEAD"
|
||||
b_rev = b_rev or "workspace"
|
||||
headers: List[str] = ["Path", title, a_rev, b_rev, "Change"]
|
||||
headers: list[str] = ["Path", title, a_rev, b_rev, "Change"]
|
||||
fill_value = "-"
|
||||
td = TabularData(headers, fill_value=fill_value)
|
||||
|
||||
@@ -394,7 +386,7 @@ def metrics_table(
|
||||
|
||||
for branch, val in metrics.items():
|
||||
for fname, metric in val.get("data", {}).items():
|
||||
row_data: Dict[str, str] = {"Revision": branch, "Path": fname}
|
||||
row_data: dict[str, str] = {"Revision": branch, "Path": fname}
|
||||
metric = metric.get("data", {})
|
||||
flattened = (
|
||||
flatten(format_dict(metric))
|
||||
|
||||
+4
-4
@@ -3,7 +3,7 @@ import os
|
||||
import re
|
||||
from contextlib import contextmanager
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, Dict, Optional
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from funcy import compact, memoize, re_find
|
||||
|
||||
@@ -138,7 +138,7 @@ class Config(dict):
|
||||
return system_config_dir()
|
||||
|
||||
@cached_property
|
||||
def files(self) -> Dict[str, str]:
|
||||
def files(self) -> dict[str, str]:
|
||||
files = {
|
||||
level: os.path.join(self.get_dir(level), self.CONFIG)
|
||||
for level in ("system", "global")
|
||||
@@ -344,7 +344,7 @@ class Config(dict):
|
||||
return Schema(dirs_schema, extra=ALLOW_EXTRA)(conf)
|
||||
|
||||
def load_config_to_level(self, level=None):
|
||||
merged_conf: Dict = {}
|
||||
merged_conf: dict = {}
|
||||
for merge_level in self.LEVELS:
|
||||
if merge_level == level:
|
||||
break
|
||||
@@ -390,7 +390,7 @@ class Config(dict):
|
||||
|
||||
|
||||
def _parse_named(conf):
|
||||
result: Dict[str, Dict] = {"remote": {}, "machine": {}, "db": {}}
|
||||
result: dict[str, dict] = {"remote": {}, "machine": {}, "db": {}}
|
||||
|
||||
for section, val in conf.items():
|
||||
match = re_find(r'^\s*(remote|machine|db)\s*"(.*)"\s*$', section)
|
||||
|
||||
@@ -181,7 +181,7 @@ SCHEMA = {
|
||||
"remote": {
|
||||
str: ByUrl(
|
||||
{
|
||||
"": {**LOCAL_COMMON, **REMOTE_COMMON},
|
||||
"": LOCAL_COMMON | REMOTE_COMMON,
|
||||
"s3": {
|
||||
"region": str,
|
||||
"profile": str,
|
||||
@@ -260,9 +260,9 @@ SCHEMA = {
|
||||
"exclude_shared_token_cache_credential": Bool,
|
||||
"exclude_managed_identity_credential": Bool,
|
||||
Optional("verify", default=False): Bool,
|
||||
"timeout": int,
|
||||
"read_timeout": int,
|
||||
"connection_timeout": int,
|
||||
"timeout": Coerce(int),
|
||||
"read_timeout": Coerce(int),
|
||||
"connection_timeout": Coerce(int),
|
||||
**REMOTE_COMMON,
|
||||
},
|
||||
"oss": {
|
||||
@@ -285,10 +285,10 @@ SCHEMA = {
|
||||
Optional("verify", default=True): Bool,
|
||||
**REMOTE_COMMON,
|
||||
},
|
||||
"http": {**HTTP_COMMON, **REMOTE_COMMON},
|
||||
"https": {**HTTP_COMMON, **REMOTE_COMMON},
|
||||
"webdav": {**WEBDAV_COMMON, **REMOTE_COMMON},
|
||||
"webdavs": {**WEBDAV_COMMON, **REMOTE_COMMON},
|
||||
"http": HTTP_COMMON | REMOTE_COMMON,
|
||||
"https": HTTP_COMMON | REMOTE_COMMON,
|
||||
"webdav": WEBDAV_COMMON | REMOTE_COMMON,
|
||||
"webdavs": WEBDAV_COMMON | REMOTE_COMMON,
|
||||
"remote": {str: object}, # Any of the above options are valid
|
||||
}
|
||||
)
|
||||
@@ -346,6 +346,7 @@ SCHEMA = {
|
||||
Exclusive("config_dir", "config_source"): str,
|
||||
Exclusive("config_module", "config_source"): str,
|
||||
"config_name": str,
|
||||
"plugins_path": str,
|
||||
},
|
||||
"studio": {
|
||||
"token": str,
|
||||
|
||||
+11
-15
@@ -5,13 +5,14 @@ import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from collections.abc import Mapping, Sequence
|
||||
from contextlib import nullcontext
|
||||
from typing import TYPE_CHECKING, Any, List, Mapping, Optional, Sequence, Union
|
||||
from typing import TYPE_CHECKING, Any, Optional, Union
|
||||
|
||||
from dvc.log import logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import ContextManager
|
||||
from contextlib import AbstractContextManager
|
||||
|
||||
from dvc.env import DVC_DAEMON, DVC_DAEMON_LOGFILE
|
||||
from dvc.utils import fix_env, is_binary
|
||||
@@ -55,7 +56,7 @@ def _win_detached_subprocess(args: Sequence[str], **kwargs) -> int:
|
||||
return popen.pid
|
||||
|
||||
|
||||
def _get_dvc_args() -> List[str]:
|
||||
def _get_dvc_args() -> list[str]:
|
||||
args = [sys.executable]
|
||||
if not is_binary():
|
||||
root_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
@@ -106,12 +107,7 @@ def _posix_detached_subprocess(args: Sequence[str], **kwargs) -> int:
|
||||
os.close(read_end)
|
||||
return int(pid_str)
|
||||
|
||||
proc = subprocess.Popen(
|
||||
args,
|
||||
shell=False, # noqa: S603
|
||||
close_fds=True,
|
||||
**kwargs,
|
||||
)
|
||||
proc = subprocess.Popen(args, shell=False, close_fds=True, **kwargs) # noqa: S603
|
||||
os.close(read_end)
|
||||
os.write(write_end, str(proc.pid).encode("utf8"))
|
||||
os.close(write_end)
|
||||
@@ -136,7 +132,7 @@ def _map_log_level_to_flag() -> Optional[str]:
|
||||
return flags.get(logger.getEffectiveLevel())
|
||||
|
||||
|
||||
def daemon(args: List[str]) -> None:
|
||||
def daemon(args: list[str]) -> None:
|
||||
"""Launch a `dvc daemon` command in a detached process.
|
||||
|
||||
Args:
|
||||
@@ -148,12 +144,12 @@ def daemon(args: List[str]) -> None:
|
||||
|
||||
|
||||
def _spawn(
|
||||
args: List[str],
|
||||
executable: Optional[Union[str, List[str]]] = None,
|
||||
args: list[str],
|
||||
executable: Optional[Union[str, list[str]]] = None,
|
||||
env: Optional[Mapping[str, str]] = None,
|
||||
output_file: Optional[str] = None,
|
||||
) -> int:
|
||||
file: "ContextManager[Any]" = nullcontext()
|
||||
file: "AbstractContextManager[Any]" = nullcontext()
|
||||
kwargs = {}
|
||||
if output_file:
|
||||
file = open(output_file, "ab") # noqa: SIM115
|
||||
@@ -168,9 +164,9 @@ def _spawn(
|
||||
return _detached_subprocess(executable + args, env=env, **kwargs)
|
||||
|
||||
|
||||
def daemonize(args: List[str], executable: Union[None, str, List[str]] = None) -> None:
|
||||
def daemonize(args: list[str], executable: Union[None, str, list[str]] = None) -> None:
|
||||
if os.name not in ("posix", "nt"):
|
||||
return None
|
||||
return
|
||||
|
||||
if os.environ.get(DVC_DAEMON):
|
||||
logger.debug("skipping launching a new daemon.")
|
||||
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
"""Manages dvc remotes that user can use with push/pull/status commands."""
|
||||
|
||||
from typing import TYPE_CHECKING, Iterable, Optional, Set, Tuple
|
||||
from collections.abc import Iterable
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from dvc.config import NoRemoteError, RemoteConfigError
|
||||
from dvc.log import logger
|
||||
@@ -50,7 +51,7 @@ class Remote:
|
||||
|
||||
def _split_legacy_hash_infos(
|
||||
hash_infos: Iterable["HashInfo"],
|
||||
) -> Tuple[Set["HashInfo"], Set["HashInfo"]]:
|
||||
) -> tuple[set["HashInfo"], set["HashInfo"]]:
|
||||
from dvc.cachemgr import LEGACY_HASH_NAMES
|
||||
|
||||
legacy = set()
|
||||
|
||||
+4
-3
@@ -1,8 +1,9 @@
|
||||
import os
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, Optional, Union
|
||||
from typing import TYPE_CHECKING, Any, Callable, Optional, Union
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.engine import make_url as _make_url
|
||||
@@ -30,7 +31,7 @@ def make_url(url: Union["URL", str], **kwargs: Any) -> "URL":
|
||||
return _make_url(url).set(**kwargs)
|
||||
|
||||
|
||||
def url_from_config(config: Union[str, "URL", Dict[str, str]]) -> "URL":
|
||||
def url_from_config(config: Union[str, "URL", dict[str, str]]) -> "URL":
|
||||
if isinstance(config, dict):
|
||||
return make_url(**config)
|
||||
return make_url(config)
|
||||
@@ -118,7 +119,7 @@ def handle_error(url: "URL"):
|
||||
|
||||
@contextmanager
|
||||
def client(
|
||||
url_or_config: Union[str, "URL", Dict[str, str]], **engine_kwargs: Any
|
||||
url_or_config: Union[str, "URL", dict[str, str]], **engine_kwargs: Any
|
||||
) -> Iterator[Client]:
|
||||
url = url_from_config(url_or_config)
|
||||
echo = env2bool(env.DVC_SQLALCHEMY_ECHO, False)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
from collections import defaultdict
|
||||
from typing import Any, Dict, List, Mapping, Set
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from dvc.output import ARTIFACT_SCHEMA, DIR_FILES_SCHEMA, Output
|
||||
|
||||
from .base import Dependency
|
||||
from .dataset import DatasetDependency
|
||||
from .db import DbDependency
|
||||
from .param import ParamsDependency
|
||||
from .repo import RepoDependency
|
||||
@@ -32,6 +34,8 @@ def _get(stage, p, info, **kwargs):
|
||||
return DbDependency(stage, d)
|
||||
|
||||
assert p
|
||||
if DatasetDependency.is_dataset(p):
|
||||
return DatasetDependency(stage, p, info)
|
||||
if repo:
|
||||
return RepoDependency(repo, stage, p, info)
|
||||
return Dependency(stage, p, info, **kwargs)
|
||||
@@ -55,23 +59,15 @@ def loads_from(stage, s_list, erepo=None, fs_config=None, db=None):
|
||||
info = {RepoDependency.PARAM_REPO: erepo} if erepo else {}
|
||||
if db:
|
||||
info.update({"db": db})
|
||||
return [
|
||||
_get(
|
||||
stage,
|
||||
s,
|
||||
info.copy(),
|
||||
fs_config=fs_config,
|
||||
)
|
||||
for s in s_list
|
||||
]
|
||||
return [_get(stage, s, info.copy(), fs_config=fs_config) for s in s_list]
|
||||
|
||||
|
||||
def _merge_params(s_list) -> Dict[str, List[str]]:
|
||||
def _merge_params(s_list) -> dict[str, list[str]]:
|
||||
d = defaultdict(list)
|
||||
default_file = ParamsDependency.DEFAULT_PARAMS_FILE
|
||||
|
||||
# figure out completely tracked params file, and ignore specific keys
|
||||
wholly_tracked: Set[str] = set()
|
||||
wholly_tracked: set[str] = set()
|
||||
for key in s_list:
|
||||
if not isinstance(key, dict):
|
||||
continue
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from typing import Dict, Type
|
||||
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.fs import download as fs_download
|
||||
from dvc.output import Output
|
||||
@@ -25,11 +23,11 @@ class DependencyIsStageFileError(DvcException):
|
||||
class Dependency(Output):
|
||||
IS_DEPENDENCY = True
|
||||
|
||||
DoesNotExistError: Type[DvcException] = DependencyDoesNotExistError
|
||||
IsNotFileOrDirError: Type[DvcException] = DependencyIsNotFileOrDirError
|
||||
IsStageFileError: Type[DvcException] = DependencyIsStageFileError
|
||||
DoesNotExistError: type[DvcException] = DependencyDoesNotExistError
|
||||
IsNotFileOrDirError: type[DvcException] = DependencyIsNotFileOrDirError
|
||||
IsStageFileError: type[DvcException] = DependencyIsStageFileError
|
||||
|
||||
def workspace_status(self) -> Dict[str, str]:
|
||||
def workspace_status(self) -> dict[str, str]:
|
||||
if self.fs.version_aware:
|
||||
old_fs_path = self.fs_path
|
||||
try:
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
from typing import TYPE_CHECKING, Any, ClassVar
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from funcy import compact, merge
|
||||
|
||||
from dvc_data.hashfile.hash_info import HashInfo
|
||||
|
||||
from .db import AbstractDependency
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dvc.stage import Stage
|
||||
|
||||
|
||||
class DatasetDependency(AbstractDependency):
|
||||
PARAM_DATASET = "dataset"
|
||||
DATASET_SCHEMA: ClassVar[dict] = {PARAM_DATASET: dict}
|
||||
|
||||
def __init__(self, stage: "Stage", p, info, *args, **kwargs):
|
||||
super().__init__(stage, info, *args, **kwargs)
|
||||
self.def_path = p
|
||||
self.name = urlparse(p).netloc
|
||||
dataset_info = info.get(self.PARAM_DATASET) or {}
|
||||
self.hash_info = HashInfo(self.PARAM_DATASET, dataset_info) # type: ignore[arg-type]
|
||||
self.hash_name = self.PARAM_DATASET
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.def_path!r})"
|
||||
|
||||
def __str__(self):
|
||||
return self.def_path
|
||||
|
||||
@classmethod
|
||||
def is_dataset(cls, p: str):
|
||||
return urlparse(p).scheme == "ds"
|
||||
|
||||
@property
|
||||
def protocol(self):
|
||||
return None
|
||||
|
||||
def dumpd(self, **kwargs):
|
||||
return compact({self.PARAM_PATH: self.def_path, **self.hash_info.to_dict()})
|
||||
|
||||
def fill_values(self, values=None):
|
||||
"""Load params values dynamically."""
|
||||
self.hash_info = HashInfo(
|
||||
self.PARAM_DATASET, merge(self.hash_info.value, values or {})
|
||||
)
|
||||
|
||||
def workspace_status(self):
|
||||
registered = self.repo.index.datasets.get(self.name, {})
|
||||
info: dict[str, Any] = self.hash_info.value if self.hash_info else {} # type: ignore[assignment]
|
||||
if info != registered:
|
||||
return {str(self): "modified"}
|
||||
return {}
|
||||
|
||||
def status(self):
|
||||
return self.workspace_status()
|
||||
|
||||
def get_hash(self):
|
||||
return HashInfo(self.PARAM_DATASET, self.repo.index.datasets.get(self.name, {}))
|
||||
|
||||
def save(self):
|
||||
self.hash_info = self.get_hash()
|
||||
|
||||
def download(self, to, jobs=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def update(self, rev=None):
|
||||
raise NotImplementedError
|
||||
@@ -1,5 +1,6 @@
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, Optional
|
||||
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Optional
|
||||
|
||||
from funcy import compact, log_durations
|
||||
|
||||
@@ -31,7 +32,7 @@ def download_progress(to: "Output") -> Iterator[Callable[[int], Any]]:
|
||||
class AbstractDependency(Dependency):
|
||||
"""Dependency without workspace/fs/fs_path"""
|
||||
|
||||
def __init__(self, stage: "Stage", info: Dict[str, Any], *args, **kwargs):
|
||||
def __init__(self, stage: "Stage", info: dict[str, Any], *args, **kwargs):
|
||||
self.repo = stage.repo
|
||||
self.stage = stage
|
||||
self.fs = None
|
||||
@@ -46,7 +47,7 @@ class DbDependency(AbstractDependency):
|
||||
PARAM_QUERY = "query"
|
||||
PARAM_TABLE = "table"
|
||||
PARAM_FILE_FORMAT = "file_format"
|
||||
DB_SCHEMA = {
|
||||
DB_SCHEMA: ClassVar[dict] = {
|
||||
PARAM_DB: {
|
||||
PARAM_QUERY: str,
|
||||
PARAM_CONNECTION: str,
|
||||
@@ -57,7 +58,7 @@ class DbDependency(AbstractDependency):
|
||||
|
||||
def __init__(self, stage: "Stage", info, *args, **kwargs):
|
||||
super().__init__(stage, info, *args, **kwargs)
|
||||
self.db_info: Dict[str, str] = self.info.get(self.PARAM_DB, {})
|
||||
self.db_info: dict[str, str] = self.info.get(self.PARAM_DB, {})
|
||||
self.connection = self.db_info.get(self.PARAM_CONNECTION)
|
||||
|
||||
@property
|
||||
@@ -110,6 +111,7 @@ class DbDependency(AbstractDependency):
|
||||
raise DvcException(f"connection {self.connection} not found in config")
|
||||
|
||||
file_format = file_format or self.db_info.get(self.PARAM_FILE_FORMAT, "csv")
|
||||
assert file_format
|
||||
with client(config) as db:
|
||||
msg = "Testing connection"
|
||||
with log_durations(logger.debug, msg), ui.status(msg) as status:
|
||||
|
||||
+6
-12
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
import typing
|
||||
from collections import defaultdict
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
import dpath
|
||||
|
||||
@@ -37,7 +37,7 @@ class BadParamFileError(DvcException):
|
||||
def read_param_file(
|
||||
fs: "FileSystem",
|
||||
path: str,
|
||||
key_paths: Optional[List[str]] = None,
|
||||
key_paths: Optional[list[str]] = None,
|
||||
flatten: bool = False,
|
||||
**load_kwargs,
|
||||
) -> Any:
|
||||
@@ -76,10 +76,7 @@ class ParamsDependency(Dependency):
|
||||
self.params = list(params) if params else []
|
||||
hash_info = HashInfo()
|
||||
if isinstance(params, dict):
|
||||
hash_info = HashInfo(
|
||||
self.PARAM_PARAMS,
|
||||
params, # type: ignore[arg-type]
|
||||
)
|
||||
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)
|
||||
@@ -103,14 +100,11 @@ class ParamsDependency(Dependency):
|
||||
for param in self.params:
|
||||
if param in values:
|
||||
info[param] = values[param]
|
||||
self.hash_info = HashInfo(
|
||||
self.PARAM_PARAMS,
|
||||
info, # type: ignore[arg-type]
|
||||
)
|
||||
self.hash_info = HashInfo(self.PARAM_PARAMS, info) # type: ignore[arg-type]
|
||||
|
||||
def read_params(
|
||||
self, flatten: bool = True, **kwargs: typing.Any
|
||||
) -> Dict[str, typing.Any]:
|
||||
) -> dict[str, typing.Any]:
|
||||
try:
|
||||
self.validate_filepath()
|
||||
except MissingParamsFile:
|
||||
@@ -134,7 +128,7 @@ class ParamsDependency(Dependency):
|
||||
|
||||
from funcy import ldistinct
|
||||
|
||||
status: Dict[str, Any] = defaultdict(dict)
|
||||
status: dict[str, Any] = defaultdict(dict)
|
||||
info = self.hash_info.value if self.hash_info else {}
|
||||
assert isinstance(info, dict)
|
||||
actual = self.read_params()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from copy import deepcopy
|
||||
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -20,7 +20,7 @@ class RepoDependency(Dependency):
|
||||
PARAM_CONFIG = "config"
|
||||
PARAM_REMOTE = "remote"
|
||||
|
||||
REPO_SCHEMA = {
|
||||
REPO_SCHEMA: ClassVar[dict] = {
|
||||
PARAM_REPO: {
|
||||
vol.Required(PARAM_URL): str,
|
||||
PARAM_REV: str,
|
||||
@@ -30,7 +30,7 @@ class RepoDependency(Dependency):
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, def_repo: Dict[str, Any], stage: "Stage", *args, **kwargs):
|
||||
def __init__(self, def_repo: dict[str, Any], stage: "Stage", *args, **kwargs):
|
||||
self.def_repo = def_repo
|
||||
super().__init__(stage, *args, **kwargs)
|
||||
|
||||
@@ -65,7 +65,7 @@ class RepoDependency(Dependency):
|
||||
self.def_repo[self.PARAM_REV_LOCK] = rev
|
||||
|
||||
@classmethod
|
||||
def _dump_def_repo(cls, def_repo) -> Dict[str, str]:
|
||||
def _dump_def_repo(cls, def_repo) -> dict[str, str]:
|
||||
repo = {cls.PARAM_URL: def_repo[cls.PARAM_URL]}
|
||||
|
||||
rev = def_repo.get(cls.PARAM_REV)
|
||||
@@ -85,7 +85,7 @@ class RepoDependency(Dependency):
|
||||
repo[cls.PARAM_REMOTE] = remote
|
||||
return repo
|
||||
|
||||
def dumpd(self, **kwargs) -> Dict[str, Union[str, Dict[str, str]]]:
|
||||
def dumpd(self, **kwargs) -> dict[str, Union[str, dict[str, str]]]:
|
||||
return {
|
||||
self.PARAM_PATH: self.def_path,
|
||||
self.PARAM_REPO: self._dump_def_repo(self.def_repo),
|
||||
|
||||
+17
-22
@@ -1,16 +1,6 @@
|
||||
import contextlib
|
||||
import os
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Optional, TypeVar, Union
|
||||
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.log import logger
|
||||
@@ -133,7 +123,7 @@ class FileMixin:
|
||||
d, _ = self._load(**kwargs)
|
||||
return d
|
||||
|
||||
def _load(self, **kwargs: Any) -> Tuple[Any, str]:
|
||||
def _load(self, **kwargs: Any) -> tuple[Any, str]:
|
||||
# it raises the proper exceptions by priority:
|
||||
# 1. when the file doesn't exists
|
||||
# 2. filename is not a DVC file
|
||||
@@ -156,7 +146,7 @@ class FileMixin:
|
||||
|
||||
return validate(d, cls.SCHEMA, path=fname) # type: ignore[arg-type]
|
||||
|
||||
def _load_yaml(self, **kwargs: Any) -> Tuple[Any, str]:
|
||||
def _load_yaml(self, **kwargs: Any) -> tuple[Any, str]:
|
||||
from dvc.utils import strictyaml
|
||||
|
||||
return strictyaml.load(
|
||||
@@ -181,10 +171,11 @@ class SingleStageFile(FileMixin):
|
||||
from dvc.schema import COMPILED_SINGLE_STAGE_SCHEMA as SCHEMA
|
||||
from dvc.stage.loader import SingleStageLoader as LOADER # noqa: N814
|
||||
|
||||
metrics: List[str] = []
|
||||
plots: Any = {}
|
||||
params: List[str] = []
|
||||
artifacts: Dict[str, Optional[Dict[str, Any]]] = {}
|
||||
datasets: ClassVar[list[dict[str, Any]]] = []
|
||||
metrics: ClassVar[list[str]] = []
|
||||
plots: ClassVar[Any] = {}
|
||||
params: ClassVar[list[str]] = []
|
||||
artifacts: ClassVar[dict[str, Optional[dict[str, Any]]]] = {}
|
||||
|
||||
@property
|
||||
def stage(self) -> "Stage":
|
||||
@@ -283,11 +274,11 @@ class ProjectFile(FileMixin):
|
||||
raise DvcException("ProjectFile has multiple stages. Please specify it's name.")
|
||||
|
||||
@cached_property
|
||||
def contents(self) -> Dict[str, Any]:
|
||||
def contents(self) -> dict[str, Any]:
|
||||
return self._load()[0]
|
||||
|
||||
@cached_property
|
||||
def lockfile_contents(self) -> Dict[str, Any]:
|
||||
def lockfile_contents(self) -> dict[str, Any]:
|
||||
return self._lockfile.load()
|
||||
|
||||
@cached_property
|
||||
@@ -302,7 +293,7 @@ class ProjectFile(FileMixin):
|
||||
return self.LOADER(self, self.contents, self.lockfile_contents)
|
||||
|
||||
@property
|
||||
def metrics(self) -> List[str]:
|
||||
def metrics(self) -> list[str]:
|
||||
return self.contents.get("metrics", [])
|
||||
|
||||
@property
|
||||
@@ -310,11 +301,15 @@ class ProjectFile(FileMixin):
|
||||
return self.contents.get("plots", {})
|
||||
|
||||
@property
|
||||
def params(self) -> List[str]:
|
||||
def params(self) -> list[str]:
|
||||
return self.contents.get("params", [])
|
||||
|
||||
@property
|
||||
def artifacts(self) -> Dict[str, Optional[Dict[str, Any]]]:
|
||||
def datasets(self) -> list[dict[str, Any]]:
|
||||
return self.contents.get("datasets", [])
|
||||
|
||||
@property
|
||||
def artifacts(self) -> dict[str, Optional[dict[str, Any]]]:
|
||||
return self.contents.get("artifacts", {})
|
||||
|
||||
def remove(self, force=False):
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
"""Exceptions raised by the dvc."""
|
||||
import errno
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from dvc.utils import format_link
|
||||
|
||||
@@ -34,7 +34,7 @@ class OutputDuplicationError(DvcException):
|
||||
stages (list): list of paths to stages.
|
||||
"""
|
||||
|
||||
def __init__(self, output: str, stages: Set["Stage"]):
|
||||
def __init__(self, output: str, stages: set["Stage"]):
|
||||
from funcy import first
|
||||
|
||||
assert isinstance(output, str)
|
||||
@@ -241,7 +241,7 @@ class UploadError(FileTransferError):
|
||||
|
||||
|
||||
class CheckoutError(DvcException):
|
||||
def __init__(self, target_infos: List[str], stats: Dict[str, List[str]]):
|
||||
def __init__(self, target_infos: list[str], stats: dict[str, list[str]]):
|
||||
from dvc.utils import error_link
|
||||
|
||||
self.target_infos = target_infos
|
||||
|
||||
+3
-13
@@ -52,10 +52,7 @@ def download(
|
||||
|
||||
from .callbacks import TqdmCallback
|
||||
|
||||
with TqdmCallback(
|
||||
desc=f"Downloading {fs.name(fs_path)}",
|
||||
unit="files",
|
||||
) as cb:
|
||||
with TqdmCallback(desc=f"Downloading {fs.name(fs_path)}", unit="files") as cb:
|
||||
# NOTE: We use dvc-objects generic.copy over fs.get since it makes file
|
||||
# download atomic and avoids fsspec glob/regex path expansion.
|
||||
if fs.isdir(fs_path):
|
||||
@@ -76,14 +73,7 @@ def download(
|
||||
lfs_prefetch(fs, from_infos)
|
||||
cb.set_size(len(from_infos))
|
||||
jobs = jobs or fs.jobs
|
||||
generic.copy(
|
||||
fs,
|
||||
from_infos,
|
||||
localfs,
|
||||
to_infos,
|
||||
callback=cb,
|
||||
batch_size=jobs,
|
||||
)
|
||||
generic.copy(fs, from_infos, localfs, to_infos, callback=cb, batch_size=jobs)
|
||||
return len(to_infos)
|
||||
|
||||
|
||||
@@ -168,5 +158,5 @@ def get_cloud_fs(repo_config, **kwargs):
|
||||
fs_path = cls._strip_protocol(url)
|
||||
|
||||
extras = cls._get_kwargs_from_urls(url)
|
||||
conf = {**extras, **remote_conf} # remote config takes priority
|
||||
conf = extras | remote_conf # remote config takes priority
|
||||
return cls, conf, fs_path
|
||||
|
||||
+18
-57
@@ -1,67 +1,38 @@
|
||||
from contextlib import ExitStack
|
||||
from typing import TYPE_CHECKING, Any, BinaryIO, Dict, Optional, Union
|
||||
from typing import TYPE_CHECKING, BinaryIO, Optional, Union
|
||||
|
||||
import fsspec
|
||||
from fsspec.callbacks import DEFAULT_CALLBACK, Callback # noqa: F401
|
||||
|
||||
from dvc.progress import Tqdm
|
||||
from dvc.utils.objects import cached_property
|
||||
from dvc_objects.fs.callbacks import DEFAULT_CALLBACK, Callback # noqa: F401
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from rich.progress import TaskID
|
||||
from tqdm import tqdm
|
||||
|
||||
from dvc.ui._rich_progress import RichTransferProgress
|
||||
|
||||
|
||||
# NOTE: this is very similar to dvc-objects.fs.callbacks.TqdmCallback,
|
||||
# but it works with our own Tqdm instance.
|
||||
class TqdmCallback(Callback):
|
||||
class TqdmCallback(fsspec.callbacks.TqdmCallback):
|
||||
def __init__(
|
||||
self,
|
||||
size: Optional[int] = None,
|
||||
value: int = 0,
|
||||
progress_bar: Optional["Tqdm"] = None,
|
||||
progress_bar: Optional["tqdm"] = None,
|
||||
tqdm_cls: Optional[type["tqdm"]] = None,
|
||||
**tqdm_kwargs,
|
||||
):
|
||||
tqdm_kwargs["total"] = size or -1
|
||||
self._tqdm_kwargs = tqdm_kwargs
|
||||
self._progress_bar = progress_bar
|
||||
self._stack = ExitStack()
|
||||
super().__init__(size=size, value=value)
|
||||
|
||||
@cached_property
|
||||
def progress_bar(self):
|
||||
progress_bar = (
|
||||
self._progress_bar
|
||||
if self._progress_bar is not None
|
||||
else Tqdm(**self._tqdm_kwargs)
|
||||
tqdm_kwargs.pop("total", None)
|
||||
super().__init__(
|
||||
tqdm_kwargs=tqdm_kwargs, tqdm_cls=tqdm_cls or Tqdm, size=size, value=value
|
||||
)
|
||||
return self._stack.enter_context(progress_bar)
|
||||
if progress_bar is not None:
|
||||
self.tqdm = progress_bar
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def close(self):
|
||||
self._stack.close()
|
||||
|
||||
def set_size(self, size):
|
||||
# Tqdm tries to be smart when to refresh,
|
||||
# so we try to force it to re-render.
|
||||
super().set_size(size)
|
||||
self.progress_bar.refresh()
|
||||
|
||||
def call(self, hook_name=None, **kwargs): # noqa: ARG002
|
||||
self.progress_bar.update_to(self.value, total=self.size)
|
||||
|
||||
def branch(
|
||||
self,
|
||||
path_1: "Union[str, BinaryIO]",
|
||||
path_2: str,
|
||||
kwargs: Dict[str, Any],
|
||||
child: Optional[Callback] = None,
|
||||
):
|
||||
child = child or TqdmCallback(
|
||||
bytes=True, desc=path_1 if isinstance(path_1, str) else path_2
|
||||
)
|
||||
return super().branch(path_1, path_2, kwargs, child=child)
|
||||
def branched(self, path_1: "Union[str, BinaryIO]", path_2: str, **kwargs):
|
||||
desc = path_1 if isinstance(path_1, str) else path_2
|
||||
return TqdmCallback(bytes=True, desc=desc)
|
||||
|
||||
|
||||
class RichCallback(Callback):
|
||||
@@ -110,9 +81,6 @@ class RichCallback(Callback):
|
||||
def task(self) -> "TaskID":
|
||||
return self.progress.add_task(**self._task_kwargs) # type: ignore[arg-type]
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def close(self):
|
||||
if self._transient:
|
||||
self.progress.clear_task(self.task)
|
||||
@@ -126,17 +94,10 @@ class RichCallback(Callback):
|
||||
visible=not self.disable,
|
||||
)
|
||||
|
||||
def branch(
|
||||
self,
|
||||
path_1: Union[str, BinaryIO],
|
||||
path_2: str,
|
||||
kwargs: Dict[str, Any],
|
||||
child: Optional["Callback"] = None,
|
||||
):
|
||||
child = child or RichCallback(
|
||||
def branched(self, path_1: Union[str, BinaryIO], path_2: str, **kwargs):
|
||||
return RichCallback(
|
||||
progress=self.progress,
|
||||
desc=path_1 if isinstance(path_1, str) else path_2,
|
||||
bytes=True,
|
||||
transient=self._transient,
|
||||
)
|
||||
return super().branch(path_1, path_2, kwargs, child=child)
|
||||
|
||||
+1
-3
@@ -22,9 +22,7 @@ class DataFileSystem(FileSystem):
|
||||
return config
|
||||
|
||||
@functools.cached_property
|
||||
def fs(
|
||||
self,
|
||||
) -> "_DataFileSystem":
|
||||
def fs(self) -> "_DataFileSystem":
|
||||
from dvc_data.fs import DataFileSystem as _DataFileSystem
|
||||
|
||||
return _DataFileSystem(**self.fs_args)
|
||||
|
||||
+42
-24
@@ -6,7 +6,7 @@ import posixpath
|
||||
import threading
|
||||
from collections import deque
|
||||
from contextlib import ExitStack, suppress
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple, Type, Union
|
||||
from typing import TYPE_CHECKING, Any, Callable, Optional, Union
|
||||
|
||||
from fsspec.spec import AbstractFileSystem
|
||||
from funcy import wrap_with
|
||||
@@ -22,8 +22,8 @@ if TYPE_CHECKING:
|
||||
|
||||
logger = logger.getChild(__name__)
|
||||
|
||||
RepoFactory = Union[Callable[..., "Repo"], Type["Repo"]]
|
||||
Key = Tuple[str, ...]
|
||||
RepoFactory = Union[Callable[..., "Repo"], type["Repo"]]
|
||||
Key = tuple[str, ...]
|
||||
|
||||
|
||||
def as_posix(path: str) -> str:
|
||||
@@ -85,7 +85,7 @@ class _DVCFileSystem(AbstractFileSystem):
|
||||
subrepos: bool = False,
|
||||
repo_factory: Optional[RepoFactory] = None,
|
||||
fo: Optional[str] = None,
|
||||
target_options: Optional[Dict[str, Any]] = None, # noqa: ARG002
|
||||
target_options: Optional[dict[str, Any]] = None, # noqa: ARG002
|
||||
target_protocol: Optional[str] = None, # noqa: ARG002
|
||||
config: Optional["DictStrAny"] = None,
|
||||
remote: Optional[str] = None,
|
||||
@@ -145,7 +145,7 @@ class _DVCFileSystem(AbstractFileSystem):
|
||||
}
|
||||
|
||||
def getcwd(self):
|
||||
relparts: Tuple[str, ...] = ()
|
||||
relparts: tuple[str, ...] = ()
|
||||
assert self.repo is not None
|
||||
if self.repo.fs.isin(self.repo.fs.getcwd(), self.repo.root_dir):
|
||||
relparts = self.repo.fs.relparts(self.repo.fs.getcwd(), self.repo.root_dir)
|
||||
@@ -156,7 +156,7 @@ class _DVCFileSystem(AbstractFileSystem):
|
||||
return posixpath.join(*parts)
|
||||
|
||||
@classmethod
|
||||
def parts(cls, path: str) -> Tuple[str, ...]:
|
||||
def parts(cls, path: str) -> tuple[str, ...]:
|
||||
ret = []
|
||||
while True:
|
||||
path, part = posixpath.split(path)
|
||||
@@ -187,7 +187,7 @@ class _DVCFileSystem(AbstractFileSystem):
|
||||
start = "."
|
||||
return posixpath.relpath(self.abspath(path), start=self.abspath(start))
|
||||
|
||||
def relparts(self, path: str, start: Optional[str] = None) -> Tuple[str, ...]:
|
||||
def relparts(self, path: str, start: Optional[str] = None) -> tuple[str, ...]:
|
||||
return self.parts(self.relpath(path, start=start))
|
||||
|
||||
@functools.cached_property
|
||||
@@ -321,7 +321,7 @@ class _DVCFileSystem(AbstractFileSystem):
|
||||
|
||||
def _get_subrepo_info(
|
||||
self, key: Key
|
||||
) -> Tuple["Repo", Optional[DataFileSystem], Key]:
|
||||
) -> tuple["Repo", Optional[DataFileSystem], Key]:
|
||||
"""
|
||||
Returns information about the subrepo the key is part of.
|
||||
"""
|
||||
@@ -360,38 +360,47 @@ class _DVCFileSystem(AbstractFileSystem):
|
||||
except FileNotFoundError:
|
||||
return False
|
||||
|
||||
def ls(self, path, detail=True, dvc_only=False, **kwargs):
|
||||
def ls(self, path, detail=True, dvc_only=False, **kwargs): # noqa: C901
|
||||
key = self._get_key_from_relative(path)
|
||||
repo, dvc_fs, subkey = self._get_subrepo_info(key)
|
||||
|
||||
dvc_exists = False
|
||||
dvc_infos = {}
|
||||
dvc_info = {}
|
||||
if dvc_fs:
|
||||
dvc_path = _get_dvc_path(dvc_fs, subkey)
|
||||
with suppress(FileNotFoundError, NotADirectoryError):
|
||||
for info in dvc_fs.ls(dvc_path, detail=True):
|
||||
dvc_infos[dvc_fs.name(info["name"])] = info
|
||||
dvc_exists = True
|
||||
with suppress(FileNotFoundError):
|
||||
dvc_info = dvc_fs.info(dvc_path)
|
||||
if dvc_info["type"] == "file":
|
||||
dvc_infos[""] = dvc_info
|
||||
else:
|
||||
for info in dvc_fs.ls(dvc_path, detail=True):
|
||||
dvc_infos[dvc_fs.name(info["name"])] = info
|
||||
|
||||
fs_exists = False
|
||||
fs_infos = {}
|
||||
fs_info = {}
|
||||
ignore_subrepos = kwargs.get("ignore_subrepos", True)
|
||||
if not dvc_only:
|
||||
fs = self.repo.fs
|
||||
fs_path = self._from_key(key)
|
||||
try:
|
||||
for info in repo.dvcignore.ls(
|
||||
fs, fs_path, detail=True, ignore_subrepos=ignore_subrepos
|
||||
):
|
||||
fs_infos[fs.name(info["name"])] = info
|
||||
fs_exists = True
|
||||
fs_info = fs.info(fs_path)
|
||||
if fs_info["type"] == "file":
|
||||
fs_infos[""] = fs_info
|
||||
else:
|
||||
for info in repo.dvcignore.ls(
|
||||
fs, fs_path, detail=True, ignore_subrepos=ignore_subrepos
|
||||
):
|
||||
fs_infos[fs.name(info["name"])] = info
|
||||
except (FileNotFoundError, NotADirectoryError):
|
||||
pass
|
||||
|
||||
if not (dvc_exists or fs_exists):
|
||||
if not (fs_info or dvc_info):
|
||||
# broken symlink or TreeError
|
||||
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), path)
|
||||
|
||||
if fs_info and dvc_info and dvc_info["type"] != fs_info["type"]:
|
||||
dvc_infos.clear() # invalidate dvc_info if file type differs
|
||||
|
||||
dvcfiles = kwargs.get("dvcfiles", False)
|
||||
|
||||
infos = []
|
||||
@@ -402,7 +411,7 @@ class _DVCFileSystem(AbstractFileSystem):
|
||||
if not dvcfiles and _is_dvc_file(name):
|
||||
continue
|
||||
|
||||
entry_path = self.join(path, name)
|
||||
entry_path = self.join(path, name) if name else path
|
||||
info = _merge_info(
|
||||
repo, (*subkey, name), fs_infos.get(name), dvc_infos.get(name)
|
||||
)
|
||||
@@ -459,7 +468,7 @@ class _DVCFileSystem(AbstractFileSystem):
|
||||
continue
|
||||
|
||||
if not dvc_info and not fs_info:
|
||||
raise FileNotFoundError
|
||||
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), path)
|
||||
|
||||
info = _merge_info(repo, subkey, fs_info, dvc_info)
|
||||
info["name"] = path
|
||||
@@ -525,13 +534,22 @@ class DVCFileSystem(FileSystem):
|
||||
protocol = "local"
|
||||
PARAM_CHECKSUM = "md5"
|
||||
|
||||
def _prepare_credentials(self, **config) -> Dict[str, Any]:
|
||||
def _prepare_credentials(self, **config) -> dict[str, Any]:
|
||||
return config
|
||||
|
||||
@functools.cached_property
|
||||
def fs(self) -> "_DVCFileSystem":
|
||||
return _DVCFileSystem(**self.fs_args)
|
||||
|
||||
@property
|
||||
def immutable(self):
|
||||
from dvc.scm import NoSCM
|
||||
|
||||
if isinstance(self.fs.repo.scm, NoSCM):
|
||||
return False
|
||||
|
||||
return self.fs._repo_kwargs.get("rev") == self.fs.repo.get_rev()
|
||||
|
||||
def getcwd(self):
|
||||
return self.fs.getcwd()
|
||||
|
||||
|
||||
+1
-3
@@ -39,9 +39,7 @@ class GitFileSystem(FileSystem):
|
||||
)
|
||||
|
||||
@functools.cached_property
|
||||
def fs(
|
||||
self,
|
||||
) -> "FsspecGitFileSystem":
|
||||
def fs(self) -> "FsspecGitFileSystem":
|
||||
from scmrepo.fs import GitFileSystem as FsspecGitFileSystem
|
||||
|
||||
return FsspecGitFileSystem(**self.fs_args)
|
||||
|
||||
+4
-4
@@ -2,7 +2,7 @@ import os
|
||||
import re
|
||||
from collections import namedtuple
|
||||
from itertools import chain, groupby, takewhile
|
||||
from typing import TYPE_CHECKING, List, Optional
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from pathspec.patterns import GitWildMatchPattern
|
||||
from pathspec.util import normalize_file
|
||||
@@ -62,7 +62,7 @@ class DvcIgnorePatterns(DvcIgnore):
|
||||
|
||||
return cls(path_spec_lines, dirname, fs.sep)
|
||||
|
||||
def __call__(self, root: List[str], dirs: List[str], files: List[str]):
|
||||
def __call__(self, root: list[str], dirs: list[str], files: list[str]):
|
||||
files = [f for f in files if not self.matches(root, f)]
|
||||
dirs = [d for d in dirs if not self.matches(root, d, True)]
|
||||
|
||||
@@ -226,7 +226,7 @@ class DvcIgnoreFilter:
|
||||
self,
|
||||
dirname: str,
|
||||
ignore_trie: Trie,
|
||||
dnames: Optional["List"],
|
||||
dnames: Optional["list"],
|
||||
ignore_subrepos: bool,
|
||||
) -> None:
|
||||
self._update_trie(dirname, ignore_trie)
|
||||
@@ -331,7 +331,7 @@ class DvcIgnoreFilter:
|
||||
yield from fs.find(path)
|
||||
|
||||
def _get_trie_pattern(
|
||||
self, dirname, dnames: Optional["List"] = None, ignore_subrepos=True
|
||||
self, dirname, dnames: Optional["list"] = None, ignore_subrepos=True
|
||||
) -> Optional["DvcIgnorePatterns"]:
|
||||
if ignore_subrepos:
|
||||
ignores_trie = self.ignores_trie_fs
|
||||
|
||||
+1
-3
@@ -185,9 +185,7 @@ class HardlinkLock(flufl.lock.Lock, LockBase):
|
||||
|
||||
if self._tmp_dir is not None:
|
||||
# Under Windows file path length is limited so we hash it
|
||||
hasher = hashlib.md5( # noqa: S324
|
||||
self._claimfile.encode()
|
||||
)
|
||||
hasher = hashlib.md5(self._claimfile.encode()) # noqa: S324
|
||||
filename = hasher.hexdigest()
|
||||
self._claimfile = os.path.join(self._tmp_dir, filename + ".lock")
|
||||
|
||||
|
||||
+3
-2
@@ -5,6 +5,7 @@ import logging.config
|
||||
import logging.handlers
|
||||
import os
|
||||
import sys
|
||||
from typing import ClassVar
|
||||
|
||||
import colorama
|
||||
|
||||
@@ -78,7 +79,7 @@ class ColorFormatter(logging.Formatter):
|
||||
"""
|
||||
|
||||
reset = colorama.Fore.RESET
|
||||
color_codes = {
|
||||
color_codes: ClassVar[dict[str, str]] = {
|
||||
"TRACE": colorama.Fore.GREEN,
|
||||
"DEBUG": colorama.Fore.BLUE,
|
||||
"WARNING": colorama.Fore.YELLOW,
|
||||
@@ -91,7 +92,7 @@ class ColorFormatter(logging.Formatter):
|
||||
self.log_colors = log_colors
|
||||
self.show_traceback = show_traceback
|
||||
|
||||
def format(self, record) -> str: # noqa: A003, C901
|
||||
def format(self, record) -> str: # noqa: C901
|
||||
record.message = record.getMessage()
|
||||
msg = self.formatMessage(record)
|
||||
|
||||
|
||||
+33
-67
@@ -4,7 +4,7 @@ import posixpath
|
||||
from collections import defaultdict
|
||||
from contextlib import suppress
|
||||
from operator import itemgetter
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Type, Union
|
||||
from typing import TYPE_CHECKING, Any, Optional, Union
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -148,7 +148,7 @@ def _split_dict(d, keys):
|
||||
|
||||
|
||||
def _merge_data(s_list):
|
||||
d: Dict[str, Dict] = defaultdict(dict)
|
||||
d: dict[str, dict] = defaultdict(dict)
|
||||
for key in s_list:
|
||||
if isinstance(key, str):
|
||||
d[key].update({})
|
||||
@@ -167,11 +167,7 @@ def _merge_data(s_list):
|
||||
|
||||
@collecting
|
||||
def load_from_pipeline(stage, data, typ="outs"):
|
||||
if typ not in (
|
||||
stage.PARAM_OUTS,
|
||||
stage.PARAM_METRICS,
|
||||
stage.PARAM_PLOTS,
|
||||
):
|
||||
if typ not in (stage.PARAM_OUTS, stage.PARAM_METRICS, stage.PARAM_PLOTS):
|
||||
raise ValueError(f"'{typ}' key is not allowed for pipeline files.")
|
||||
|
||||
metric = typ == stage.PARAM_METRICS
|
||||
@@ -197,24 +193,13 @@ def load_from_pipeline(stage, data, typ="outs"):
|
||||
],
|
||||
)
|
||||
|
||||
yield _get(
|
||||
stage,
|
||||
path,
|
||||
info={},
|
||||
plot=plt_d or plot,
|
||||
metric=metric,
|
||||
**extra,
|
||||
)
|
||||
yield _get(stage, path, info={}, plot=plt_d or plot, metric=metric, **extra)
|
||||
|
||||
|
||||
def split_file_meta_from_cloud(entry: Dict) -> Dict:
|
||||
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,
|
||||
):
|
||||
for key in (S3_PARAM_CHECKSUM, HDFS_PARAM_CHECKSUM, Meta.PARAM_VERSION_ID):
|
||||
if value := entry.pop(key, None):
|
||||
remote_meta[key] = value
|
||||
|
||||
@@ -223,7 +208,7 @@ def split_file_meta_from_cloud(entry: Dict) -> Dict:
|
||||
return entry
|
||||
|
||||
|
||||
def merge_file_meta_from_cloud(entry: Dict) -> Dict:
|
||||
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])
|
||||
@@ -231,7 +216,7 @@ def merge_file_meta_from_cloud(entry: Dict) -> Dict:
|
||||
return entry
|
||||
|
||||
|
||||
def _serialize_tree_obj_to_files(obj: Tree) -> List[Dict[str, Any]]:
|
||||
def _serialize_tree_obj_to_files(obj: Tree) -> list[dict[str, Any]]:
|
||||
key = obj.PARAM_RELPATH
|
||||
return sorted(
|
||||
(
|
||||
@@ -246,7 +231,7 @@ def _serialize_tree_obj_to_files(obj: Tree) -> List[Dict[str, Any]]:
|
||||
)
|
||||
|
||||
|
||||
def _serialize_hi_to_dict(hash_info: Optional[HashInfo]) -> Dict[str, Any]:
|
||||
def _serialize_hi_to_dict(hash_info: Optional[HashInfo]) -> dict[str, Any]:
|
||||
if hash_info:
|
||||
if hash_info.name == "md5-dos2unix":
|
||||
return {"md5": hash_info.value}
|
||||
@@ -316,10 +301,10 @@ class Output:
|
||||
PARAM_HASH = "hash"
|
||||
PARAM_FS_CONFIG = "fs_config"
|
||||
|
||||
DoesNotExistError: Type[DvcException] = OutputDoesNotExistError
|
||||
IsNotFileOrDirError: Type[DvcException] = OutputIsNotFileOrDirError
|
||||
IsStageFileError: Type[DvcException] = OutputIsStageFileError
|
||||
IsIgnoredError: Type[DvcException] = OutputIsIgnoredError
|
||||
DoesNotExistError: type[DvcException] = OutputDoesNotExistError
|
||||
IsNotFileOrDirError: type[DvcException] = OutputIsNotFileOrDirError
|
||||
IsStageFileError: type[DvcException] = OutputIsStageFileError
|
||||
IsIgnoredError: type[DvcException] = OutputIsIgnoredError
|
||||
|
||||
def __init__( # noqa: PLR0913
|
||||
self,
|
||||
@@ -337,7 +322,7 @@ class Output:
|
||||
remote=None,
|
||||
repo=None,
|
||||
fs_config=None,
|
||||
files: Optional[List[Dict[str, Any]]] = None,
|
||||
files: Optional[list[dict[str, Any]]] = None,
|
||||
push: bool = True,
|
||||
hash_name: Optional[str] = DEFAULT_ALGORITHM,
|
||||
):
|
||||
@@ -420,7 +405,7 @@ class Output:
|
||||
|
||||
def _compute_hash_info_from_meta(
|
||||
self, hash_name: Optional[str]
|
||||
) -> Tuple[str, HashInfo]:
|
||||
) -> tuple[str, HashInfo]:
|
||||
if self.is_in_repo:
|
||||
if hash_name is None:
|
||||
# Legacy 2.x output, use "md5-dos2unix" but read "md5" from
|
||||
@@ -433,10 +418,7 @@ class Output:
|
||||
hash_name = meta_name = self.fs.PARAM_CHECKSUM
|
||||
assert hash_name
|
||||
|
||||
hash_info = HashInfo(
|
||||
name=hash_name,
|
||||
value=getattr(self.meta, meta_name, None),
|
||||
)
|
||||
hash_info = HashInfo(name=hash_name, value=getattr(self.meta, meta_name, None))
|
||||
return hash_name, hash_info
|
||||
|
||||
def _compute_meta_hash_info_from_files(self) -> None:
|
||||
@@ -515,10 +497,7 @@ class Output:
|
||||
if self.fs.isabs(self.def_path):
|
||||
return False
|
||||
|
||||
return self.repo and self.fs.isin(
|
||||
self.fs_path,
|
||||
self.repo.root_dir,
|
||||
)
|
||||
return self.repo and self.fs.isin(self.fs_path, self.repo.root_dir)
|
||||
|
||||
@property
|
||||
def use_scm_ignore(self):
|
||||
@@ -555,7 +534,7 @@ class Output:
|
||||
|
||||
def _build(
|
||||
self, *args, no_progress_bar=False, **kwargs
|
||||
) -> Tuple["HashFileDB", "Meta", "HashFile"]:
|
||||
) -> tuple["HashFileDB", "Meta", "HashFile"]:
|
||||
from dvc.ui import ui
|
||||
|
||||
with ui.progress(
|
||||
@@ -601,7 +580,7 @@ class Output:
|
||||
return self.fs.exists(self.fs_path)
|
||||
|
||||
@cached_property
|
||||
def index_key(self) -> Tuple[str, "DataIndexKey"]:
|
||||
def index_key(self) -> tuple[str, "DataIndexKey"]:
|
||||
if self.is_in_repo:
|
||||
workspace = "repo"
|
||||
key = self.repo.fs.relparts(self.fs_path, self.repo.root_dir)
|
||||
@@ -633,7 +612,7 @@ class Output:
|
||||
return self.meta.version_id != self.get_meta().version_id
|
||||
return False
|
||||
|
||||
def workspace_status(self) -> Dict[str, str]:
|
||||
def workspace_status(self) -> dict[str, str]:
|
||||
if not self.exists:
|
||||
return {str(self): "deleted"}
|
||||
|
||||
@@ -645,7 +624,7 @@ class Output:
|
||||
|
||||
return {}
|
||||
|
||||
def status(self) -> Dict[str, str]:
|
||||
def status(self) -> dict[str, str]:
|
||||
if self.hash_info and self.use_cache and self.changed_cache():
|
||||
return {str(self): "not in cache"}
|
||||
|
||||
@@ -813,20 +792,13 @@ class Output:
|
||||
def _commit_granular_dir(self, filter_info, hardlink) -> Optional["HashFile"]:
|
||||
prefix = self.fs.parts(self.fs.relpath(filter_info, self.fs_path))
|
||||
staging, _, obj = self._build(
|
||||
self.cache,
|
||||
self.fs_path,
|
||||
self.fs,
|
||||
self.hash_name,
|
||||
ignore=self.dvcignore,
|
||||
self.cache, self.fs_path, self.fs, self.hash_name, ignore=self.dvcignore
|
||||
)
|
||||
assert isinstance(obj, Tree)
|
||||
save_obj = obj.filter(prefix)
|
||||
assert isinstance(save_obj, Tree)
|
||||
checkout_obj = save_obj.get_obj(self.cache, prefix)
|
||||
with TqdmCallback(
|
||||
desc=f"Committing {self} to cache",
|
||||
unit="file",
|
||||
) as cb:
|
||||
with TqdmCallback(desc=f"Committing {self} to cache", unit="file") as cb:
|
||||
otransfer(
|
||||
staging,
|
||||
self.cache,
|
||||
@@ -840,7 +812,7 @@ class Output:
|
||||
def dumpd(self, **kwargs): # noqa: C901, PLR0912
|
||||
from dvc.cachemgr import LEGACY_HASH_NAMES
|
||||
|
||||
ret: Dict[str, Any] = {}
|
||||
ret: dict[str, Any] = {}
|
||||
with_files = (
|
||||
(not self.IS_DEPENDENCY or self.stage.is_import)
|
||||
and self.hash_info.isdir
|
||||
@@ -962,7 +934,7 @@ class Output:
|
||||
filter_info: Optional[str] = None,
|
||||
allow_missing: bool = False,
|
||||
**kwargs,
|
||||
) -> Optional[Tuple[bool, Optional[bool]]]:
|
||||
) -> Optional[tuple[bool, Optional[bool]]]:
|
||||
# callback passed act as a aggregate callback.
|
||||
# do not let checkout to call set_size and change progressbar.
|
||||
class CallbackProxy(Callback):
|
||||
@@ -1157,7 +1129,7 @@ class Output:
|
||||
|
||||
def get_used_objs( # noqa: PLR0911
|
||||
self, **kwargs
|
||||
) -> Dict[Optional["HashFileDB"], Set["HashInfo"]]:
|
||||
) -> dict[Optional["HashFileDB"], set["HashInfo"]]:
|
||||
"""Return filtered set of used object IDs for this out."""
|
||||
from dvc.cachemgr import LEGACY_HASH_NAMES
|
||||
|
||||
@@ -1286,12 +1258,9 @@ class Output:
|
||||
|
||||
self.hash_info = merged.hash_info
|
||||
self.files = None
|
||||
self.meta = Meta(
|
||||
size=du(self.cache, merged),
|
||||
nfiles=len(merged),
|
||||
)
|
||||
self.meta = Meta(size=du(self.cache, merged), nfiles=len(merged))
|
||||
|
||||
def unstage(self, path: str) -> Tuple["Meta", "Tree"]:
|
||||
def unstage(self, path: str) -> tuple["Meta", "Tree"]:
|
||||
from pygtrie import Trie
|
||||
|
||||
rel_key = tuple(self.fs.parts(self.fs.relpath(path, self.fs_path)))
|
||||
@@ -1324,7 +1293,7 @@ class Output:
|
||||
path: str,
|
||||
obj: Union["Tree", "HashFile"],
|
||||
meta: "Meta",
|
||||
) -> Tuple["Meta", "Tree"]:
|
||||
) -> tuple["Meta", "Tree"]:
|
||||
from pygtrie import Trie
|
||||
|
||||
append_only = True
|
||||
@@ -1429,10 +1398,7 @@ class Output:
|
||||
|
||||
assert staging
|
||||
assert obj.hash_info
|
||||
with TqdmCallback(
|
||||
desc=f"Adding {self} to cache",
|
||||
unit="file",
|
||||
) as cb:
|
||||
with TqdmCallback(desc=f"Adding {self} to cache", unit="file") as cb:
|
||||
otransfer(
|
||||
staging,
|
||||
self.cache,
|
||||
@@ -1512,9 +1478,9 @@ META_SCHEMA = {
|
||||
Meta.PARAM_VERSION_ID: str,
|
||||
}
|
||||
|
||||
CLOUD_SCHEMA = vol.All({str: {**META_SCHEMA, **CHECKSUMS_SCHEMA}}, vol.Length(max=1))
|
||||
CLOUD_SCHEMA = vol.All({str: META_SCHEMA | CHECKSUMS_SCHEMA}, vol.Length(max=1))
|
||||
|
||||
ARTIFACT_SCHEMA: Dict[Any, Any] = {
|
||||
ARTIFACT_SCHEMA: dict[Any, Any] = {
|
||||
**CHECKSUMS_SCHEMA,
|
||||
**META_SCHEMA,
|
||||
Output.PARAM_PATH: str,
|
||||
@@ -1523,7 +1489,7 @@ ARTIFACT_SCHEMA: Dict[Any, Any] = {
|
||||
Output.PARAM_HASH: str,
|
||||
}
|
||||
|
||||
DIR_FILES_SCHEMA: Dict[Any, Any] = {
|
||||
DIR_FILES_SCHEMA: dict[Any, Any] = {
|
||||
**CHECKSUMS_SCHEMA,
|
||||
**META_SCHEMA,
|
||||
vol.Required(Tree.PARAM_RELPATH): str,
|
||||
|
||||
+14
-24
@@ -3,17 +3,7 @@ import os
|
||||
from collections.abc import Mapping, Sequence
|
||||
from copy import deepcopy
|
||||
from itertools import product
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Tuple,
|
||||
Type,
|
||||
Union,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Any, NamedTuple, Optional, Union
|
||||
|
||||
from funcy import collecting, first, isa, join, reraise
|
||||
|
||||
@@ -85,7 +75,7 @@ def format_and_raise(exc: Exception, msg: str, path: str) -> "NoReturn":
|
||||
|
||||
|
||||
def _reraise_err(
|
||||
exc_cls: Type[Exception], *args, from_exc: Optional[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):
|
||||
@@ -108,7 +98,7 @@ def is_map_or_seq(data: Any) -> bool:
|
||||
return not isinstance(data, str) and _is_map_or_seq(data)
|
||||
|
||||
|
||||
def split_group_name(name: str) -> Tuple[str, Optional[str]]:
|
||||
def split_group_name(name: str) -> tuple[str, Optional[str]]:
|
||||
group, *keys = name.rsplit(JOIN, maxsplit=1)
|
||||
return group, first(keys)
|
||||
|
||||
@@ -161,14 +151,14 @@ class DataResolver:
|
||||
|
||||
# we use `tracked_vars` to keep a dictionary of used variables
|
||||
# by the interpolated entries.
|
||||
self.tracked_vars: Dict[str, Mapping] = {}
|
||||
self.tracked_vars: dict[str, Mapping] = {}
|
||||
|
||||
stages_data = d.get(STAGES_KWD, {})
|
||||
# we wrap the definitions into:
|
||||
# ForeachDefinition, MatrixDefinition, and EntryDefinition
|
||||
# that helps us to optimize, cache and selectively load each one of
|
||||
# them as we need, and simplify all of this DSL/parsing logic.
|
||||
self.definitions: Dict[str, Definition] = {
|
||||
self.definitions: dict[str, Definition] = {
|
||||
name: make_definition(self, name, definition)
|
||||
for name, definition in stages_data.items()
|
||||
}
|
||||
@@ -376,7 +366,7 @@ class ForeachDefinition:
|
||||
f" in '{self.relpath}': expected list/dictionary, got " + typ
|
||||
)
|
||||
|
||||
def _warn_if_overwriting(self, keys: List[str]):
|
||||
def _warn_if_overwriting(self, keys: list[str]):
|
||||
warn_for = [k for k in keys if k in self.context]
|
||||
if warn_for:
|
||||
linking_verb = "is" if len(warn_for) == 1 else "are"
|
||||
@@ -390,7 +380,7 @@ class ForeachDefinition:
|
||||
self.name,
|
||||
)
|
||||
|
||||
def _inserted_keys(self, iterable) -> List[str]:
|
||||
def _inserted_keys(self, iterable) -> list[str]:
|
||||
keys = [self.pair.value]
|
||||
if isinstance(iterable, Mapping):
|
||||
keys.append(self.pair.key)
|
||||
@@ -490,10 +480,10 @@ class MatrixDefinition:
|
||||
return self._template
|
||||
|
||||
@cached_property
|
||||
def resolved_iterable(self) -> Dict[str, List]:
|
||||
def resolved_iterable(self) -> dict[str, list]:
|
||||
return self._resolve_matrix_data()
|
||||
|
||||
def _resolve_matrix_data(self) -> Dict[str, List]:
|
||||
def _resolve_matrix_data(self) -> dict[str, list]:
|
||||
try:
|
||||
iterable = self.context.resolve(self.matrix_data, unwrap=False)
|
||||
except (ContextError, ParseError) as exc:
|
||||
@@ -504,7 +494,7 @@ class MatrixDefinition:
|
||||
self._warn_if_overwriting([self.pair.key, self.pair.value])
|
||||
return iterable
|
||||
|
||||
def _warn_if_overwriting(self, keys: List[str]):
|
||||
def _warn_if_overwriting(self, keys: list[str]):
|
||||
warn_for = [k for k in keys if k in self.context]
|
||||
if warn_for:
|
||||
linking_verb = "is" if len(warn_for) == 1 else "are"
|
||||
@@ -519,16 +509,16 @@ class MatrixDefinition:
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def normalized_iterable(self) -> Dict[str, "DictStrAny"]:
|
||||
def normalized_iterable(self) -> dict[str, "DictStrAny"]:
|
||||
"""Convert sequence to Mapping with keys normalized."""
|
||||
iterable = self.resolved_iterable
|
||||
assert isinstance(iterable, Mapping)
|
||||
|
||||
ret: Dict[str, "DictStrAny"] = {}
|
||||
ret: dict[str, "DictStrAny"] = {}
|
||||
matrix = {key: enumerate(v) for key, v in iterable.items()}
|
||||
for combination in product(*matrix.values()):
|
||||
d: "DictStrAny" = {}
|
||||
fragments: List[str] = []
|
||||
fragments: list[str] = []
|
||||
for k, (i, v) in zip(matrix.keys(), combination):
|
||||
d[k] = v
|
||||
fragments.append(f"{k}{i}" if is_map_or_seq(v) else to_str(v))
|
||||
@@ -540,7 +530,7 @@ class MatrixDefinition:
|
||||
def has_member(self, key: str) -> bool:
|
||||
return key in self.normalized_iterable
|
||||
|
||||
def get_generated_names(self) -> List[str]:
|
||||
def get_generated_names(self) -> list[str]:
|
||||
return list(map(self._generate_name, self.normalized_iterable))
|
||||
|
||||
def _generate_name(self, key: str) -> str:
|
||||
|
||||
@@ -4,7 +4,7 @@ from collections.abc import Mapping, MutableMapping, MutableSequence, Sequence
|
||||
from contextlib import contextmanager
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass, field, replace
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from funcy import identity, lfilter, nullcontext, select
|
||||
|
||||
@@ -22,7 +22,7 @@ from dvc.parsing.interpolate import (
|
||||
|
||||
logger = logger.getChild(__name__)
|
||||
SeqOrMap = Union[Sequence, Mapping]
|
||||
DictStr = Dict[str, Any]
|
||||
DictStr = dict[str, Any]
|
||||
|
||||
|
||||
class ContextError(DvcException):
|
||||
@@ -100,7 +100,7 @@ def recurse_not_a_node(data: dict):
|
||||
@dataclass
|
||||
class Meta:
|
||||
source: Optional[str] = None
|
||||
dpaths: List[str] = field(default_factory=list)
|
||||
dpaths: list[str] = field(default_factory=list)
|
||||
local: bool = True
|
||||
|
||||
@staticmethod
|
||||
@@ -297,7 +297,7 @@ class Context(CtxDict):
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
self._track = False
|
||||
self._tracked_data: Dict[str, Dict] = defaultdict(dict)
|
||||
self._tracked_data: dict[str, dict] = defaultdict(dict)
|
||||
self.imports = {}
|
||||
self._reserved_keys = {}
|
||||
|
||||
@@ -322,7 +322,7 @@ class Context(CtxDict):
|
||||
continue
|
||||
params_file = self._tracked_data[source]
|
||||
keys = [keys] if isinstance(keys, str) else keys
|
||||
params_file.update({key: node.value for key in keys})
|
||||
params_file.update(dict.fromkeys(keys, node.value))
|
||||
|
||||
def select(self, key: str, unwrap: bool = False):
|
||||
"""Select the item using key, similar to `__getitem__`
|
||||
@@ -348,7 +348,7 @@ class Context(CtxDict):
|
||||
|
||||
@classmethod
|
||||
def load_from(
|
||||
cls, fs, path: str, select_keys: Optional[List[str]] = None
|
||||
cls, fs, path: str, select_keys: Optional[list[str]] = None
|
||||
) -> "Context":
|
||||
from dvc.utils.serialize import load_path
|
||||
|
||||
@@ -423,7 +423,7 @@ class Context(CtxDict):
|
||||
def load_from_vars(
|
||||
self,
|
||||
fs,
|
||||
vars_: List,
|
||||
vars_: list,
|
||||
wdir: str,
|
||||
stage_name: Optional[str] = None,
|
||||
default: Optional[str] = None,
|
||||
|
||||
@@ -10,7 +10,8 @@ from dvc.exceptions import DvcException
|
||||
from dvc.utils.flatten import flatten
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from typing import List, Match, NoReturn
|
||||
from re import Match
|
||||
from typing import NoReturn
|
||||
|
||||
from pyparsing import ParseException
|
||||
|
||||
@@ -205,7 +206,7 @@ def validate_value(value, key):
|
||||
|
||||
def str_interpolate(
|
||||
template: str,
|
||||
matches: "List[Match]",
|
||||
matches: "list[Match]",
|
||||
context: "Context",
|
||||
skip_checks: bool = False,
|
||||
key=None,
|
||||
@@ -225,5 +226,5 @@ def str_interpolate(
|
||||
return buf.replace(r"\${", BRACE_OPEN)
|
||||
|
||||
|
||||
def is_exact_string(src: str, matches: "List[Match]"):
|
||||
def is_exact_string(src: str, matches: "list[Match]"):
|
||||
return len(matches) == 1 and src == matches[0].group(0)
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
import logging
|
||||
import sys
|
||||
from threading import RLock
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any, ClassVar
|
||||
|
||||
from tqdm import tqdm
|
||||
|
||||
@@ -33,7 +33,7 @@ class Tqdm(tqdm):
|
||||
" [{elapsed}<{remaining}, {rate_fmt:>11}]"
|
||||
)
|
||||
BAR_FMT_NOTOTAL = "{desc}{bar:b}|{postfix[info]}{n_fmt} [{elapsed}, {rate_fmt:>11}]"
|
||||
BYTES_DEFAULTS = {
|
||||
BYTES_DEFAULTS: ClassVar[dict[str, Any]] = {
|
||||
"unit": "B",
|
||||
"unit_scale": True,
|
||||
"unit_divisor": 1024,
|
||||
@@ -68,7 +68,7 @@ class Tqdm(tqdm):
|
||||
"""
|
||||
kwargs = kwargs.copy()
|
||||
if bytes:
|
||||
kwargs = {**self.BYTES_DEFAULTS, **kwargs}
|
||||
kwargs = self.BYTES_DEFAULTS | kwargs
|
||||
else:
|
||||
kwargs.setdefault("unit_scale", total > 999 if total else True)
|
||||
if file is None:
|
||||
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
"""Manages user prompts."""
|
||||
|
||||
from collections.abc import Collection
|
||||
from getpass import getpass
|
||||
from typing import Collection, Optional
|
||||
from typing import Optional
|
||||
|
||||
from dvc.log import logger
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Dict, List, Union
|
||||
from typing import Union
|
||||
|
||||
from dvc.render import REVISION, REVISIONS, SRC, TYPE_KEY
|
||||
from dvc.render.converter.image import ImageConverter
|
||||
@@ -18,7 +18,7 @@ def _get_converter(
|
||||
raise ValueError(f"Invalid renderer class {renderer_class}")
|
||||
|
||||
|
||||
def to_json(renderer, split: bool = False) -> List[Dict]:
|
||||
def to_json(renderer, split: bool = False) -> list[dict]:
|
||||
if renderer.TYPE == "vega":
|
||||
if not renderer.datapoints:
|
||||
return []
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
class Converter:
|
||||
def __init__(
|
||||
self,
|
||||
plot_id: str,
|
||||
data: Optional[Dict[str, Any]] = None,
|
||||
properties: Optional[Dict] = None,
|
||||
data: Optional[dict[str, Any]] = None,
|
||||
properties: Optional[dict] = None,
|
||||
):
|
||||
self.plot_id = plot_id
|
||||
self.properties = properties or {}
|
||||
self.data = data or {}
|
||||
|
||||
def convert(self) -> Tuple[List[Tuple[str, str, Any]], Dict]:
|
||||
def convert(self) -> tuple[list[tuple[str, str, Any]], dict]:
|
||||
raise NotImplementedError
|
||||
|
||||
def flat_datapoints(self, revision: str) -> Tuple[List[Dict], Dict]:
|
||||
def flat_datapoints(self, revision: str) -> tuple[list[dict], dict]:
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import base64
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Tuple
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from dvc.render import FILENAME, REVISION, SRC
|
||||
|
||||
@@ -28,19 +28,17 @@ class ImageConverter(Converter):
|
||||
return img_path
|
||||
|
||||
@staticmethod
|
||||
def _encode_image(
|
||||
image_data: bytes,
|
||||
) -> str:
|
||||
def _encode_image(image_data: bytes) -> str:
|
||||
base64_str = base64.b64encode(image_data).decode()
|
||||
return f"data:image;base64,{base64_str}"
|
||||
|
||||
def convert(self) -> Tuple[List[Tuple[str, str, Any]], Dict]:
|
||||
def convert(self) -> tuple[list[tuple[str, str, Any]], dict]:
|
||||
datas = []
|
||||
for filename, image_data in self.data.items():
|
||||
datas.append((filename, "", image_data))
|
||||
return datas, self.properties
|
||||
|
||||
def flat_datapoints(self, revision: str) -> Tuple[List[Dict], Dict]:
|
||||
def flat_datapoints(self, revision: str) -> tuple[list[dict], dict]:
|
||||
"""
|
||||
Convert the DVC Plots content to DVC Render datapoints.
|
||||
Return both generated datapoints and updated properties.
|
||||
@@ -57,10 +55,6 @@ class ImageConverter(Converter):
|
||||
)
|
||||
else:
|
||||
src = self._encode_image(image_data)
|
||||
datapoint = {
|
||||
REVISION: revision,
|
||||
FILENAME: filename,
|
||||
SRC: src,
|
||||
}
|
||||
datapoint = {REVISION: revision, FILENAME: filename, SRC: src}
|
||||
datapoints.append(datapoint)
|
||||
return datapoints, properties
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
|
||||
from collections.abc import Iterable
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from funcy import first, last
|
||||
|
||||
@@ -18,7 +19,7 @@ class FieldNotFoundError(DvcException):
|
||||
)
|
||||
|
||||
|
||||
def _lists(blob: Union[Dict, List]) -> Iterable[List]:
|
||||
def _lists(blob: Union[dict, list]) -> Iterable[list]:
|
||||
if isinstance(blob, list):
|
||||
yield blob
|
||||
else:
|
||||
@@ -40,33 +41,29 @@ def _file_field(*args):
|
||||
yield file, field
|
||||
|
||||
|
||||
def _find(
|
||||
filename: str,
|
||||
field: str,
|
||||
data_series: List[Tuple[str, str, Any]],
|
||||
):
|
||||
def _find(filename: str, field: str, data_series: list[tuple[str, str, Any]]):
|
||||
for data_file, data_field, data in data_series:
|
||||
if data_file == filename and data_field == field:
|
||||
return data_file, data_field, data
|
||||
return None
|
||||
|
||||
|
||||
def _verify_field(file2datapoints: Dict[str, List], filename: str, field: str):
|
||||
def _verify_field(file2datapoints: dict[str, list], filename: str, field: str):
|
||||
if filename in file2datapoints:
|
||||
datapoint = first(file2datapoints[filename])
|
||||
if field not in datapoint:
|
||||
raise FieldNotFoundError(field, datapoint.keys())
|
||||
|
||||
|
||||
def _get_xs(properties: Dict, file2datapoints: Dict[str, List[Dict]]):
|
||||
x = properties.get("x", None)
|
||||
def _get_xs(properties: dict, file2datapoints: dict[str, list[dict]]):
|
||||
x = properties.get("x")
|
||||
if x is not None and isinstance(x, dict):
|
||||
for filename, field in _file_field(x):
|
||||
_verify_field(file2datapoints, filename, field)
|
||||
yield filename, field
|
||||
|
||||
|
||||
def _get_ys(properties, file2datapoints: Dict[str, List[Dict]]):
|
||||
def _get_ys(properties, file2datapoints: dict[str, list[dict]]):
|
||||
y = properties.get("y", None)
|
||||
if y is not None:
|
||||
for filename, field in _file_field(y):
|
||||
@@ -74,7 +71,7 @@ def _get_ys(properties, file2datapoints: Dict[str, List[Dict]]):
|
||||
yield filename, field
|
||||
|
||||
|
||||
def _is_datapoints(lst: List[Dict]):
|
||||
def _is_datapoints(lst: list[dict]):
|
||||
"""
|
||||
check if dict keys match, datapoints with different keys mgiht lead
|
||||
to unexpected behavior
|
||||
@@ -85,8 +82,8 @@ def _is_datapoints(lst: List[Dict]):
|
||||
}
|
||||
|
||||
|
||||
def get_datapoints(file_content: Dict):
|
||||
result: List[Dict[str, Any]] = []
|
||||
def get_datapoints(file_content: dict):
|
||||
result: list[dict[str, Any]] = []
|
||||
for lst in _lists(file_content):
|
||||
if _is_datapoints(lst):
|
||||
for index, datapoint in enumerate(lst):
|
||||
@@ -107,8 +104,8 @@ class VegaConverter(Converter):
|
||||
def __init__(
|
||||
self,
|
||||
plot_id: str,
|
||||
data: Optional[Dict] = None,
|
||||
properties: Optional[Dict] = None,
|
||||
data: Optional[dict] = None,
|
||||
properties: Optional[dict] = None,
|
||||
):
|
||||
super().__init__(plot_id, data, properties)
|
||||
self.plot_id = plot_id
|
||||
@@ -126,7 +123,7 @@ class VegaConverter(Converter):
|
||||
x = self.properties.get("x", None)
|
||||
y = self.properties.get("y", None)
|
||||
|
||||
inferred_properties: Dict = {}
|
||||
inferred_properties: dict = {}
|
||||
|
||||
# Infer x.
|
||||
if isinstance(x, str):
|
||||
@@ -195,16 +192,13 @@ class VegaConverter(Converter):
|
||||
def flat_datapoints(self, revision): # noqa: C901, PLR0912
|
||||
file2datapoints, properties = self.convert()
|
||||
|
||||
props_update: Dict[str, Union[str, List[Dict[str, str]]]] = {}
|
||||
props_update: dict[str, Union[str, list[dict[str, str]]]] = {}
|
||||
|
||||
xs = list(_get_xs(properties, file2datapoints))
|
||||
|
||||
# assign "step" if no x provided
|
||||
if not xs:
|
||||
x_file, x_field = (
|
||||
None,
|
||||
INDEX,
|
||||
)
|
||||
x_file, x_field = None, INDEX
|
||||
else:
|
||||
x_file, x_field = xs[0]
|
||||
|
||||
@@ -243,10 +237,7 @@ class VegaConverter(Converter):
|
||||
common_prefix_len = 0
|
||||
|
||||
props_update["anchors_y_definitions"] = [
|
||||
{
|
||||
FILENAME: _get_short_y_file(y_file, common_prefix_len),
|
||||
FIELD: y_field,
|
||||
}
|
||||
{FILENAME: _get_short_y_file(y_file, common_prefix_len), FIELD: y_field}
|
||||
for y_file, y_field in ys
|
||||
]
|
||||
|
||||
@@ -255,7 +246,7 @@ class VegaConverter(Converter):
|
||||
x_file, x_field = xs[i]
|
||||
datapoints = [{**d} for d in file2datapoints.get(y_file, [])]
|
||||
|
||||
if props_update.get("y", None) == "dvc_inferred_y_value":
|
||||
if props_update.get("y") == "dvc_inferred_y_value":
|
||||
_update_from_field(
|
||||
datapoints,
|
||||
field="dvc_inferred_y_value",
|
||||
@@ -294,13 +285,11 @@ class VegaConverter(Converter):
|
||||
if not all_datapoints:
|
||||
return [], {}
|
||||
|
||||
properties = {**properties, **props_update}
|
||||
properties = properties | props_update
|
||||
|
||||
return all_datapoints, properties
|
||||
|
||||
def convert(
|
||||
self,
|
||||
):
|
||||
def convert(self):
|
||||
"""
|
||||
Convert the data. Fill necessary fields ('x', 'y') and return both
|
||||
generated datapoints and updated properties. `x`, `y` values and labels
|
||||
@@ -309,7 +298,7 @@ class VegaConverter(Converter):
|
||||
inferred_properties = self._infer_x_y()
|
||||
|
||||
datapoints = self._find_datapoints()
|
||||
properties = {**self.properties, **inferred_properties}
|
||||
properties = self.properties | inferred_properties
|
||||
|
||||
properties["y_label"] = self.infer_y_label(properties)
|
||||
properties["x_label"] = self.infer_x_label(properties)
|
||||
@@ -322,9 +311,9 @@ def _get_short_y_file(y_file, common_prefix_len):
|
||||
|
||||
|
||||
def _update_from_field(
|
||||
target_datapoints: List[Dict],
|
||||
target_datapoints: list[dict],
|
||||
field: str,
|
||||
source_datapoints: Optional[List[Dict]] = None,
|
||||
source_datapoints: Optional[list[dict]] = None,
|
||||
source_field: Optional[str] = None,
|
||||
):
|
||||
if source_datapoints is None:
|
||||
@@ -341,11 +330,11 @@ def _update_from_field(
|
||||
datapoint[field] = source_datapoint[source_field]
|
||||
|
||||
|
||||
def _update_from_index(datapoints: List[Dict], new_field: str):
|
||||
def _update_from_index(datapoints: list[dict], new_field: str):
|
||||
for index, datapoint in enumerate(datapoints):
|
||||
datapoint[new_field] = index
|
||||
|
||||
|
||||
def _update_all(datapoints: List[Dict], update_dict: Dict):
|
||||
def _update_all(datapoints: list[dict], update_dict: dict):
|
||||
for datapoint in datapoints:
|
||||
datapoint.update(update_dict)
|
||||
|
||||
+12
-12
@@ -1,6 +1,6 @@
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from typing import TYPE_CHECKING, DefaultDict, Dict, List, NamedTuple, Optional
|
||||
from typing import TYPE_CHECKING, NamedTuple, Optional
|
||||
|
||||
import dpath
|
||||
import dpath.options
|
||||
@@ -21,16 +21,16 @@ dpath.options.ALLOW_EMPTY_STRING_KEYS = True
|
||||
logger = logger.getChild(__name__)
|
||||
|
||||
|
||||
def _squash_plots_properties(data: List) -> Dict:
|
||||
def _squash_plots_properties(data: list) -> dict:
|
||||
configs = [last(group) for group in data]
|
||||
resolved: Dict = {}
|
||||
resolved: dict = {}
|
||||
for config in reversed(configs):
|
||||
resolved = {**resolved, **config}
|
||||
resolved = resolved | config
|
||||
return resolved
|
||||
|
||||
|
||||
class PlotsData:
|
||||
def __init__(self, data: Dict):
|
||||
def __init__(self, data: dict):
|
||||
self.data = data
|
||||
|
||||
def group_definitions(self):
|
||||
@@ -62,15 +62,15 @@ class PlotsData:
|
||||
|
||||
class RendererWithErrors(NamedTuple):
|
||||
renderer: "Renderer"
|
||||
source_errors: Dict[str, Dict[str, Exception]]
|
||||
definition_errors: Dict[str, Exception]
|
||||
source_errors: dict[str, dict[str, Exception]]
|
||||
definition_errors: dict[str, Exception]
|
||||
|
||||
|
||||
def match_defs_renderers( # noqa: C901, PLR0912
|
||||
data,
|
||||
out=None,
|
||||
templates_dir: Optional["StrPath"] = None,
|
||||
) -> List[RendererWithErrors]:
|
||||
) -> list[RendererWithErrors]:
|
||||
from dvc_render import ImageRenderer, VegaRenderer
|
||||
|
||||
plots_data = PlotsData(data)
|
||||
@@ -78,12 +78,12 @@ def match_defs_renderers( # noqa: C901, PLR0912
|
||||
renderer_cls = None
|
||||
|
||||
for plot_id, group in plots_data.group_definitions().items():
|
||||
plot_datapoints: List[Dict] = []
|
||||
plot_datapoints: list[dict] = []
|
||||
props = _squash_plots_properties(group)
|
||||
first_props: Dict = {}
|
||||
first_props: dict = {}
|
||||
|
||||
def_errors: Dict[str, Exception] = {}
|
||||
src_errors: DefaultDict[str, Dict[str, Exception]] = defaultdict(dict)
|
||||
def_errors: dict[str, Exception] = {}
|
||||
src_errors: defaultdict[str, dict[str, Exception]] = defaultdict(dict)
|
||||
|
||||
if out is not None:
|
||||
props["out"] = out
|
||||
|
||||
+27
-39
@@ -1,16 +1,9 @@
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from contextlib import contextmanager
|
||||
from collections.abc import Iterable
|
||||
from contextlib import AbstractContextManager, contextmanager
|
||||
from functools import wraps
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
ContextManager,
|
||||
Iterable,
|
||||
Optional,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Callable, Optional, Union
|
||||
|
||||
from dvc.exceptions import (
|
||||
DvcException,
|
||||
@@ -20,6 +13,7 @@ from dvc.exceptions import (
|
||||
)
|
||||
from dvc.ignore import DvcIgnoreFilter
|
||||
from dvc.log import logger
|
||||
from dvc.utils import as_posix
|
||||
from dvc.utils.objects import cached_property
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -111,7 +105,7 @@ class Repo:
|
||||
fs: Optional["FileSystem"] = None,
|
||||
uninitialized: bool = False,
|
||||
scm: Optional[Union["Git", "NoSCM"]] = None,
|
||||
) -> Tuple[str, Optional[str]]:
|
||||
) -> tuple[str, Optional[str]]:
|
||||
from dvc.fs import localfs
|
||||
from dvc.scm import SCM, SCMError
|
||||
|
||||
@@ -181,14 +175,8 @@ class Repo:
|
||||
|
||||
self.root_dir: str
|
||||
self.dvc_dir: Optional[str]
|
||||
(
|
||||
self.root_dir,
|
||||
self.dvc_dir,
|
||||
) = self._get_repo_dirs(
|
||||
root_dir=root_dir,
|
||||
fs=self.fs,
|
||||
uninitialized=uninitialized,
|
||||
scm=scm,
|
||||
(self.root_dir, self.dvc_dir) = self._get_repo_dirs(
|
||||
root_dir=root_dir, fs=self.fs, uninitialized=uninitialized, scm=scm
|
||||
)
|
||||
|
||||
self._uninitialized = uninitialized
|
||||
@@ -268,16 +256,12 @@ class Repo:
|
||||
if not isinstance(self.fs, GitFileSystem):
|
||||
return None
|
||||
|
||||
relparts: Tuple[str, ...] = ()
|
||||
relparts: tuple[str, ...] = ()
|
||||
if self.root_dir != "/":
|
||||
# subrepo
|
||||
relparts = self.fs.relparts(self.root_dir, "/")
|
||||
|
||||
dvc_dir = os.path.join(
|
||||
self.scm.root_dir,
|
||||
*relparts,
|
||||
self.DVC_DIR,
|
||||
)
|
||||
dvc_dir = os.path.join(self.scm.root_dir, *relparts, self.DVC_DIR)
|
||||
if os.path.exists(dvc_dir):
|
||||
return dvc_dir
|
||||
|
||||
@@ -306,7 +290,7 @@ class Repo:
|
||||
new.check_graph()
|
||||
|
||||
@staticmethod
|
||||
def open(url: Optional[str], *args, **kwargs) -> "Repo": # noqa: A003
|
||||
def open(url: Optional[str], *args, **kwargs) -> "Repo":
|
||||
from .open_repo import open_repo
|
||||
|
||||
return open_repo(url, *args, **kwargs)
|
||||
@@ -367,6 +351,16 @@ class Repo:
|
||||
# fs.
|
||||
self._reset()
|
||||
|
||||
@property
|
||||
def subrepo_relpath(self) -> str:
|
||||
from dvc.fs import GitFileSystem
|
||||
|
||||
scm_root_dir = "/" if isinstance(self.fs, GitFileSystem) else self.scm.root_dir
|
||||
|
||||
relpath = as_posix(self.fs.relpath(self.root_dir, scm_root_dir))
|
||||
|
||||
return "" if relpath == "." else relpath
|
||||
|
||||
@property
|
||||
def data_index(self) -> "DataIndex":
|
||||
from dvc_data.index import DataIndex
|
||||
@@ -392,7 +386,7 @@ class Repo:
|
||||
self,
|
||||
path: str,
|
||||
workspace: str = "repo",
|
||||
) -> Tuple["DataIndex", "DataIndexEntry"]:
|
||||
) -> tuple["DataIndex", "DataIndexEntry"]:
|
||||
if self.subrepos:
|
||||
fs_path = self.dvcfs.from_os_path(path)
|
||||
fs = self.dvcfs.fs
|
||||
@@ -476,7 +470,7 @@ class Repo:
|
||||
|
||||
return brancher(self, *args, **kwargs)
|
||||
|
||||
def switch(self, rev: str) -> ContextManager[str]:
|
||||
def switch(self, rev: str) -> AbstractContextManager[str]:
|
||||
from dvc.repo.brancher import switch
|
||||
|
||||
return switch(self, rev)
|
||||
@@ -625,12 +619,12 @@ class Repo:
|
||||
|
||||
cache_dir = self.config["core"].get("site_cache_dir") or site_cache_dir()
|
||||
|
||||
subdir = None
|
||||
if isinstance(self.fs, GitFileSystem):
|
||||
relparts: Tuple[str, ...] = ()
|
||||
if self.root_dir != "/":
|
||||
# subrepo
|
||||
relparts = self.fs.relparts(self.root_dir, "/")
|
||||
root_dir = os.path.join(self.scm.root_dir, *relparts)
|
||||
subdir = self.root_dir
|
||||
root_dir = self.scm.root_dir
|
||||
else:
|
||||
root_dir = self.root_dir
|
||||
|
||||
@@ -646,7 +640,7 @@ class Repo:
|
||||
# components were changed (useful to prevent newer dvc versions from
|
||||
# using older broken cache). Please reset this back to 0 if other parts
|
||||
# of the token components are changed.
|
||||
salt = 2
|
||||
salt = 0
|
||||
|
||||
# NOTE: This helps us avoid accidentally reusing cache for repositories
|
||||
# that just happened to be at the same path as old deleted ones.
|
||||
@@ -654,13 +648,7 @@ class Repo:
|
||||
|
||||
md5 = hashlib.md5( # noqa: S324
|
||||
str(
|
||||
(
|
||||
root_dir,
|
||||
btime,
|
||||
getpass.getuser(),
|
||||
version_tuple[0],
|
||||
salt,
|
||||
)
|
||||
(root_dir, subdir, btime, getpass.getuser(), version_tuple[0], salt)
|
||||
).encode()
|
||||
)
|
||||
repo_token = md5.hexdigest()
|
||||
|
||||
+8
-16
@@ -1,15 +1,7 @@
|
||||
import os
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Dict,
|
||||
Iterator,
|
||||
List,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
from typing import TYPE_CHECKING, NamedTuple, Optional, Union
|
||||
|
||||
from dvc.exceptions import (
|
||||
CacheLinkError,
|
||||
@@ -37,7 +29,7 @@ class StageInfo(NamedTuple):
|
||||
|
||||
def find_targets(
|
||||
targets: Union["StrOrBytesPath", Iterator["StrOrBytesPath"]], glob: bool = False
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
if isinstance(targets, (str, bytes, os.PathLike)):
|
||||
targets_list = [os.fsdecode(targets)]
|
||||
else:
|
||||
@@ -99,7 +91,7 @@ OVERLAPPING_PARENT_FMT = (
|
||||
|
||||
|
||||
@contextmanager
|
||||
def translate_graph_error(stages: List["Stage"]) -> Iterator[None]:
|
||||
def translate_graph_error(stages: list["Stage"]) -> Iterator[None]:
|
||||
try:
|
||||
yield
|
||||
except OverlappingOutputPathsError as exc:
|
||||
@@ -124,7 +116,7 @@ def translate_graph_error(stages: List["Stage"]) -> Iterator[None]:
|
||||
)
|
||||
|
||||
|
||||
def progress_iter(stages: Dict[str, StageInfo]) -> Iterator[Tuple[str, StageInfo]]:
|
||||
def progress_iter(stages: dict[str, StageInfo]) -> Iterator[tuple[str, StageInfo]]:
|
||||
total = len(stages)
|
||||
desc = "Adding..."
|
||||
with ui.progress(
|
||||
@@ -151,8 +143,8 @@ LINK_FAILURE_MESSAGE = (
|
||||
|
||||
|
||||
@contextmanager
|
||||
def warn_link_failures() -> Iterator[List[str]]:
|
||||
link_failures: List[str] = []
|
||||
def warn_link_failures() -> Iterator[list[str]]:
|
||||
link_failures: list[str] = []
|
||||
try:
|
||||
yield link_failures
|
||||
finally:
|
||||
@@ -202,7 +194,7 @@ def add(
|
||||
to_remote: bool = False,
|
||||
remote_jobs: Optional[int] = None,
|
||||
force: bool = False,
|
||||
) -> List["Stage"]:
|
||||
) -> list["Stage"]:
|
||||
add_targets = find_targets(targets, glob=glob)
|
||||
if not add_targets:
|
||||
return []
|
||||
|
||||
+14
-22
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
import posixpath
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
|
||||
from typing import TYPE_CHECKING, Any, Optional, Union
|
||||
|
||||
from dvc.annotations import Artifact
|
||||
from dvc.dvcfile import PROJECT_FILE
|
||||
@@ -95,13 +95,10 @@ class Artifacts:
|
||||
return self.repo.scm
|
||||
return None
|
||||
|
||||
def read(self) -> Dict[str, Dict[str, Artifact]]:
|
||||
def read(self) -> dict[str, dict[str, Artifact]]:
|
||||
"""Read artifacts from dvc.yaml."""
|
||||
artifacts: Dict[str, Dict[str, Artifact]] = {}
|
||||
for (
|
||||
dvcfile,
|
||||
dvcfile_artifacts,
|
||||
) in self.repo.index._artifacts.items():
|
||||
artifacts: dict[str, dict[str, Artifact]] = {}
|
||||
for dvcfile, dvcfile_artifacts in self.repo.index._artifacts.items():
|
||||
dvcyaml = relpath(dvcfile, self.repo.root_dir)
|
||||
artifacts[dvcyaml] = {}
|
||||
for name, value in dvcfile_artifacts.items():
|
||||
@@ -140,14 +137,14 @@ class Artifacts:
|
||||
|
||||
assert not (version and stage)
|
||||
name = _reformat_name(name)
|
||||
tags: List["GitTag"] = find(
|
||||
tags: list["GitTag"] = find(
|
||||
name=name, version=version, stage=stage, scm=self.scm
|
||||
)
|
||||
if not tags:
|
||||
raise ArtifactNotFoundError(name, version=version, stage=stage)
|
||||
if version or stage:
|
||||
return tags[-1].target
|
||||
gto_tags: List["GTOTag"] = sort_versions(parse_tag(tag) for tag in tags)
|
||||
gto_tags: list["GTOTag"] = sort_versions(parse_tag(tag) for tag in tags)
|
||||
return gto_tags[0].tag.target
|
||||
|
||||
def get_path(self, name: str):
|
||||
@@ -177,7 +174,7 @@ class Artifacts:
|
||||
out: Optional[str] = None,
|
||||
force: bool = False,
|
||||
jobs: Optional[int] = None,
|
||||
) -> Tuple[int, str]:
|
||||
) -> tuple[int, str]:
|
||||
"""Download the specified artifact."""
|
||||
from dvc.fs import download as fs_download
|
||||
|
||||
@@ -188,12 +185,7 @@ class Artifacts:
|
||||
out = resolve_output(path, out, force=force)
|
||||
fs = self.repo.dvcfs
|
||||
fs_path = fs.from_os_path(path)
|
||||
count = fs_download(
|
||||
fs,
|
||||
fs_path,
|
||||
os.path.abspath(out),
|
||||
jobs=jobs,
|
||||
)
|
||||
count = fs_download(fs, fs_path, os.path.abspath(out), jobs=jobs)
|
||||
return count, out
|
||||
|
||||
@staticmethod
|
||||
@@ -205,9 +197,9 @@ class Artifacts:
|
||||
out: Optional[str] = None,
|
||||
force: bool = False,
|
||||
jobs: Optional[int] = None,
|
||||
dvc_studio_config: Optional[Dict[str, Any]] = None,
|
||||
dvc_studio_config: Optional[dict[str, Any]] = None,
|
||||
**kwargs,
|
||||
) -> Tuple[int, str]:
|
||||
) -> tuple[int, str]:
|
||||
from dvc_studio_client.model_registry import get_download_uris
|
||||
|
||||
from dvc.fs import HTTPFileSystem, generic, localfs
|
||||
@@ -215,8 +207,8 @@ class Artifacts:
|
||||
|
||||
logger.debug("Trying to download artifact '%s' via studio", name)
|
||||
out = out or os.getcwd()
|
||||
to_infos: List[str] = []
|
||||
from_infos: List[str] = []
|
||||
to_infos: list[str] = []
|
||||
from_infos: list[str] = []
|
||||
if dvc_studio_config is None:
|
||||
dvc_studio_config = {}
|
||||
dvc_studio_config["repo_url"] = repo_url
|
||||
@@ -263,9 +255,9 @@ class Artifacts:
|
||||
name: str,
|
||||
version: Optional[str] = None,
|
||||
stage: Optional[str] = None,
|
||||
config: Optional[Union[str, Dict[str, Any]]] = None,
|
||||
config: Optional[Union[str, dict[str, Any]]] = None,
|
||||
remote: Optional[str] = None,
|
||||
remote_config: Optional[Union[str, Dict[str, Any]]] = None,
|
||||
remote_config: Optional[Union[str, dict[str, Any]]] = None,
|
||||
out: Optional[str] = None,
|
||||
force: bool = False,
|
||||
jobs: Optional[int] = None,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
from typing import TYPE_CHECKING, Iterator, Optional, Tuple
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from scmrepo.git import Git
|
||||
|
||||
@@ -59,11 +60,11 @@ def brancher(
|
||||
|
||||
from dvc.fs import LocalFileSystem
|
||||
|
||||
repo_root_parts: Tuple[str, ...] = ()
|
||||
repo_root_parts: tuple[str, ...] = ()
|
||||
if self.fs.isin(self.root_dir, self.scm.root_dir):
|
||||
repo_root_parts = self.fs.relparts(self.root_dir, self.scm.root_dir)
|
||||
|
||||
cwd_parts: Tuple[str, ...] = ()
|
||||
cwd_parts: tuple[str, ...] = ()
|
||||
if self.fs.isin(self.fs.getcwd(), self.scm.root_dir):
|
||||
cwd_parts = self.fs.relparts(self.fs.getcwd(), self.scm.root_dir)
|
||||
|
||||
@@ -112,8 +113,8 @@ def brancher(
|
||||
def _switch_fs(
|
||||
repo: "Repo",
|
||||
rev: str,
|
||||
repo_root_parts: Tuple[str, ...],
|
||||
cwd_parts: Tuple[str, ...],
|
||||
repo_root_parts: tuple[str, ...],
|
||||
cwd_parts: tuple[str, ...],
|
||||
):
|
||||
from dvc.fs import GitFileSystem, LocalFileSystem
|
||||
|
||||
@@ -147,11 +148,11 @@ def switch(repo: "Repo", rev: str) -> Iterator[str]:
|
||||
if rev != "workspace":
|
||||
rev = resolve_rev(repo.scm, rev)
|
||||
|
||||
repo_root_parts: Tuple[str, ...] = ()
|
||||
repo_root_parts: tuple[str, ...] = ()
|
||||
if repo.fs.isin(repo.root_dir, repo.scm.root_dir):
|
||||
repo_root_parts = repo.fs.relparts(repo.root_dir, repo.scm.root_dir)
|
||||
|
||||
cwd_parts: Tuple[str, ...] = ()
|
||||
cwd_parts: tuple[str, ...] = ()
|
||||
if repo.fs.isin(repo.fs.getcwd(), repo.scm.root_dir):
|
||||
cwd_parts = repo.fs.relparts(repo.fs.getcwd(), repo.scm.root_dir)
|
||||
|
||||
|
||||
+1
-5
@@ -5,11 +5,7 @@ def check_missing(repo, rev=None, max_size=None, types=None):
|
||||
from dvc_data.index import StorageKeyError
|
||||
|
||||
with repo.switch(rev or "workspace"):
|
||||
idx = repo.index.targets_view(
|
||||
None,
|
||||
max_size=max_size,
|
||||
types=types,
|
||||
)
|
||||
idx = repo.index.targets_view(None, max_size=max_size, types=types)
|
||||
|
||||
index = idx.data["repo"]
|
||||
|
||||
|
||||
+9
-32
@@ -1,5 +1,5 @@
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Dict, List
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dvc.exceptions import (
|
||||
CheckoutError,
|
||||
@@ -66,7 +66,7 @@ def _build_out_changes(index, changes):
|
||||
|
||||
|
||||
def _check_can_delete(
|
||||
entries: List["DataIndexEntry"],
|
||||
entries: list["DataIndexEntry"],
|
||||
index: "BaseDataIndex",
|
||||
path: str,
|
||||
fs: "FileSystem",
|
||||
@@ -107,7 +107,7 @@ def checkout( # noqa: C901
|
||||
from dvc.stage.exceptions import StageFileBadNameError, StageFileDoesNotExistError
|
||||
from dvc_data.index.checkout import ADD, DELETE, MODIFY, apply, compare
|
||||
|
||||
stats: Dict[str, List[str]] = {
|
||||
stats: dict[str, list[str]] = {
|
||||
"added": [],
|
||||
"deleted": [],
|
||||
"modified": [],
|
||||
@@ -122,42 +122,23 @@ def checkout( # noqa: C901
|
||||
def onerror(target, exc):
|
||||
if target and isinstance(
|
||||
exc,
|
||||
(
|
||||
StageFileDoesNotExistError,
|
||||
StageFileBadNameError,
|
||||
NoOutputOrStageError,
|
||||
),
|
||||
(StageFileDoesNotExistError, StageFileBadNameError, NoOutputOrStageError),
|
||||
):
|
||||
raise CheckoutErrorSuggestGit(target) from exc
|
||||
raise
|
||||
|
||||
view = self.index.targets_view(
|
||||
targets,
|
||||
recursive=recursive,
|
||||
with_deps=with_deps,
|
||||
onerror=onerror,
|
||||
targets, recursive=recursive, with_deps=with_deps, onerror=onerror
|
||||
)
|
||||
|
||||
with ui.progress(
|
||||
unit="entry",
|
||||
desc="Building workspace index",
|
||||
leave=True,
|
||||
) as pb:
|
||||
with ui.progress(unit="entry", desc="Building workspace index", leave=True) as pb:
|
||||
old = build_data_index(
|
||||
view,
|
||||
self.root_dir,
|
||||
self.fs,
|
||||
compute_hash=True,
|
||||
callback=pb.as_callback(),
|
||||
view, self.root_dir, self.fs, compute_hash=True, callback=pb.as_callback()
|
||||
)
|
||||
|
||||
new = view.data["repo"]
|
||||
|
||||
with ui.progress(
|
||||
desc="Comparing indexes",
|
||||
unit="entry",
|
||||
leave=True,
|
||||
) as pb:
|
||||
with ui.progress(desc="Comparing indexes", unit="entry", leave=True) as pb:
|
||||
diff = compare(old, new, relink=relink, delete=True, callback=pb.as_callback())
|
||||
|
||||
if not force:
|
||||
@@ -175,11 +156,7 @@ def checkout( # noqa: C901
|
||||
if self.fs.isin_or_eq(dest_path, out_path):
|
||||
failed.add(out_path)
|
||||
|
||||
with ui.progress(
|
||||
unit="file",
|
||||
desc="Applying changes",
|
||||
leave=True,
|
||||
) as pb:
|
||||
with ui.progress(unit="file", desc="Applying changes", leave=True) as pb:
|
||||
apply(
|
||||
diff,
|
||||
self.root_dir,
|
||||
|
||||
+6
-5
@@ -1,4 +1,5 @@
|
||||
from typing import TYPE_CHECKING, Callable, Iterable, List, Optional, Tuple
|
||||
from collections.abc import Iterable
|
||||
from typing import TYPE_CHECKING, Callable, Optional
|
||||
|
||||
from dvc.log import logger
|
||||
|
||||
@@ -10,8 +11,8 @@ logger = logger.getChild(__name__)
|
||||
|
||||
|
||||
FilterFn = Callable[["Output"], bool]
|
||||
Outputs = List["Output"]
|
||||
StrPaths = List[str]
|
||||
Outputs = list["Output"]
|
||||
StrPaths = list[str]
|
||||
|
||||
|
||||
def _collect_outs(
|
||||
@@ -43,7 +44,7 @@ def _collect_paths(
|
||||
|
||||
def _filter_outs(
|
||||
outs: Outputs, fs_paths: StrPaths, duplicates=False
|
||||
) -> Tuple[Outputs, StrPaths]:
|
||||
) -> tuple[Outputs, StrPaths]:
|
||||
res_outs: Outputs = []
|
||||
fs_res_paths = fs_paths
|
||||
|
||||
@@ -65,7 +66,7 @@ def collect(
|
||||
output_filter: Optional[FilterFn] = None,
|
||||
recursive: bool = False,
|
||||
duplicates: bool = False,
|
||||
) -> Tuple[Outputs, StrPaths]:
|
||||
) -> tuple[Outputs, StrPaths]:
|
||||
assert targets or output_filter
|
||||
|
||||
outs: Outputs = _collect_outs(repo, output_filter=output_filter, deps=deps)
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
from typing import TYPE_CHECKING, Set
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dvc import prompt
|
||||
|
||||
@@ -122,7 +122,7 @@ def commit_2_to_3(repo: "Repo", dry: bool = False):
|
||||
stage.dump(update_pipeline=False)
|
||||
|
||||
|
||||
def _migrateable_dvcfiles(view: "IndexView") -> Set[str]:
|
||||
def _migrateable_dvcfiles(view: "IndexView") -> set[str]:
|
||||
from dvc.dvcfile import ProjectFile
|
||||
|
||||
migrated = set()
|
||||
|
||||
+20
-32
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import posixpath
|
||||
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, TypedDict, Union
|
||||
from collections.abc import Iterable
|
||||
from typing import TYPE_CHECKING, Any, TypedDict, Union
|
||||
|
||||
from dvc.fs.callbacks import DEFAULT_CALLBACK
|
||||
from dvc.ui import ui
|
||||
@@ -53,11 +54,11 @@ def _diff(
|
||||
not_in_remote: bool = False,
|
||||
remote_refresh: bool = False,
|
||||
callback: "Callback" = DEFAULT_CALLBACK,
|
||||
) -> Dict[str, List[str]]:
|
||||
) -> dict[str, list[str]]:
|
||||
from dvc_data.index import StorageError
|
||||
from dvc_data.index.diff import UNCHANGED, UNKNOWN, diff
|
||||
|
||||
ret: Dict[str, List[str]] = {}
|
||||
ret: dict[str, list[str]] = {}
|
||||
|
||||
def _add_change(typ, change):
|
||||
typ = _adapt_typ(typ)
|
||||
@@ -115,9 +116,9 @@ def _diff(
|
||||
|
||||
|
||||
class GitInfo(TypedDict, total=False):
|
||||
staged: Dict[str, List[str]]
|
||||
unstaged: Dict[str, List[str]]
|
||||
untracked: List[str]
|
||||
staged: dict[str, list[str]]
|
||||
unstaged: dict[str, list[str]]
|
||||
untracked: list[str]
|
||||
is_empty: bool
|
||||
is_dirty: bool
|
||||
|
||||
@@ -150,13 +151,10 @@ def _git_info(scm: Union["Git", "NoSCM"], untracked_files: str = "all") -> GitIn
|
||||
)
|
||||
|
||||
|
||||
def _diff_index_to_wtree(repo: "Repo", **kwargs: Any) -> Dict[str, List[str]]:
|
||||
def _diff_index_to_wtree(repo: "Repo", **kwargs: Any) -> dict[str, list[str]]:
|
||||
from .index import build_data_index
|
||||
|
||||
with ui.progress(
|
||||
desc="Building workspace index",
|
||||
unit="entry",
|
||||
) as pb:
|
||||
with ui.progress(desc="Building workspace index", unit="entry") as pb:
|
||||
workspace = build_data_index(
|
||||
repo.index,
|
||||
repo.root_dir,
|
||||
@@ -180,30 +178,27 @@ def _diff_index_to_wtree(repo: "Repo", **kwargs: Any) -> Dict[str, List[str]]:
|
||||
|
||||
def _diff_head_to_index(
|
||||
repo: "Repo", head: str = "HEAD", **kwargs: Any
|
||||
) -> Dict[str, List[str]]:
|
||||
) -> dict[str, list[str]]:
|
||||
index = repo.index.data["repo"]
|
||||
|
||||
with repo.switch(head):
|
||||
head_index = repo.index.data["repo"]
|
||||
|
||||
with ui.progress(
|
||||
desc="Calculating diff between head/index",
|
||||
unit="entry",
|
||||
) as pb:
|
||||
with ui.progress(desc="Calculating diff between head/index", unit="entry") as pb:
|
||||
return _diff(head_index, index, callback=pb.as_callback(), **kwargs)
|
||||
|
||||
|
||||
class Status(TypedDict):
|
||||
not_in_cache: List[str]
|
||||
not_in_remote: List[str]
|
||||
committed: Dict[str, List[str]]
|
||||
uncommitted: Dict[str, List[str]]
|
||||
untracked: List[str]
|
||||
unchanged: List[str]
|
||||
not_in_cache: list[str]
|
||||
not_in_remote: list[str]
|
||||
committed: dict[str, list[str]]
|
||||
uncommitted: dict[str, list[str]]
|
||||
untracked: list[str]
|
||||
unchanged: list[str]
|
||||
git: GitInfo
|
||||
|
||||
|
||||
def _transform_git_paths_to_dvc(repo: "Repo", files: Iterable[str]) -> List[str]:
|
||||
def _transform_git_paths_to_dvc(repo: "Repo", files: Iterable[str]) -> list[str]:
|
||||
"""Transform files rel. to Git root to DVC root, and drop outside files."""
|
||||
rel = repo.fs.relpath(repo.root_dir, repo.scm.root_dir).rstrip("/")
|
||||
|
||||
@@ -222,18 +217,11 @@ def _transform_git_paths_to_dvc(repo: "Repo", files: Iterable[str]) -> List[str]
|
||||
return [repo.fs.relpath(file, start) for file in files]
|
||||
|
||||
|
||||
def status(
|
||||
repo: "Repo",
|
||||
untracked_files: str = "no",
|
||||
**kwargs: Any,
|
||||
) -> Status:
|
||||
def status(repo: "Repo", untracked_files: str = "no", **kwargs: Any) -> Status:
|
||||
from dvc.scm import NoSCMError, SCMError
|
||||
|
||||
head = kwargs.pop("head", "HEAD")
|
||||
uncommitted_diff = _diff_index_to_wtree(
|
||||
repo,
|
||||
**kwargs,
|
||||
)
|
||||
uncommitted_diff = _diff_index_to_wtree(repo, **kwargs)
|
||||
unchanged = set(uncommitted_diff.pop("unchanged", []))
|
||||
|
||||
try:
|
||||
|
||||
+10
-36
@@ -1,7 +1,7 @@
|
||||
import errno
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from typing import Dict, List, Optional
|
||||
from typing import Optional
|
||||
|
||||
from dvc.log import logger
|
||||
from dvc.repo import locked
|
||||
@@ -26,7 +26,7 @@ def _diff(old, new, data_keys, with_missing=False):
|
||||
from dvc_data.index.diff import ADD, DELETE, MODIFY, RENAME
|
||||
from dvc_data.index.diff import diff as idiff
|
||||
|
||||
ret: "Dict[str, List[Dict]]" = {
|
||||
ret: "dict[str, list[dict]]" = {
|
||||
"added": [],
|
||||
"deleted": [],
|
||||
"modified": [],
|
||||
@@ -52,36 +52,22 @@ def _diff(old, new, data_keys, with_missing=False):
|
||||
continue
|
||||
|
||||
if change.typ == ADD:
|
||||
ret["added"].append(
|
||||
{
|
||||
"path": _path(change.new),
|
||||
"hash": _hash(change.new),
|
||||
}
|
||||
)
|
||||
ret["added"].append({"path": _path(change.new), "hash": _hash(change.new)})
|
||||
elif change.typ == DELETE:
|
||||
ret["deleted"].append(
|
||||
{
|
||||
"path": _path(change.old),
|
||||
"hash": _hash(change.old),
|
||||
}
|
||||
{"path": _path(change.old), "hash": _hash(change.old)}
|
||||
)
|
||||
elif change.typ == MODIFY:
|
||||
ret["modified"].append(
|
||||
{
|
||||
"path": _path(change.old),
|
||||
"hash": {
|
||||
"old": _hash(change.old),
|
||||
"new": _hash(change.new),
|
||||
},
|
||||
"hash": {"old": _hash(change.old), "new": _hash(change.new)},
|
||||
}
|
||||
)
|
||||
elif change.typ == RENAME:
|
||||
ret["renamed"].append(
|
||||
{
|
||||
"path": {
|
||||
"old": _path(change.old),
|
||||
"new": _path(change.new),
|
||||
},
|
||||
"path": {"old": _path(change.old), "new": _path(change.new)},
|
||||
"hash": _hash(change.old),
|
||||
}
|
||||
)
|
||||
@@ -93,10 +79,7 @@ def _diff(old, new, data_keys, with_missing=False):
|
||||
and not old.storage_map.cache_exists(change.old)
|
||||
):
|
||||
ret["not in cache"].append(
|
||||
{
|
||||
"path": _path(change.old),
|
||||
"hash": _hash(change.old),
|
||||
}
|
||||
{"path": _path(change.old), "hash": _hash(change.old)}
|
||||
)
|
||||
|
||||
return ret if any(ret.values()) else {}
|
||||
@@ -107,7 +90,7 @@ def diff(
|
||||
self,
|
||||
a_rev: str = "HEAD",
|
||||
b_rev: Optional[str] = None,
|
||||
targets: Optional[List[str]] = None,
|
||||
targets: Optional[list[str]] = None,
|
||||
recursive: bool = False,
|
||||
):
|
||||
"""
|
||||
@@ -137,11 +120,7 @@ def diff(
|
||||
def onerror(target, _exc):
|
||||
missing_targets[rev].add(target) # noqa: B023
|
||||
|
||||
view = self.index.targets_view(
|
||||
targets,
|
||||
onerror=onerror,
|
||||
recursive=recursive,
|
||||
)
|
||||
view = self.index.targets_view(targets, onerror=onerror, recursive=recursive)
|
||||
|
||||
data_keys.update(view.data_keys.get("repo", set()))
|
||||
|
||||
@@ -149,12 +128,7 @@ def diff(
|
||||
from .index import build_data_index
|
||||
|
||||
with ui.status("Building workspace index"):
|
||||
data = build_data_index(
|
||||
view,
|
||||
self.root_dir,
|
||||
self.fs,
|
||||
compute_hash=True,
|
||||
)
|
||||
data = build_data_index(view, self.root_dir, self.fs, compute_hash=True)
|
||||
else:
|
||||
data = view.data["repo"]
|
||||
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
from typing import Any, Dict, Optional, Union
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
|
||||
def du(
|
||||
@@ -6,7 +6,7 @@ def du(
|
||||
path: Optional[str] = None,
|
||||
rev: Optional[str] = None,
|
||||
summarize: bool = False,
|
||||
config: Union[None, Dict[str, Any], str] = None,
|
||||
config: Union[None, dict[str, Any], str] = None,
|
||||
remote: Optional[str] = None,
|
||||
remote_config: Optional[dict] = None,
|
||||
):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import re
|
||||
from typing import TYPE_CHECKING, Dict, Iterable, List, Optional
|
||||
from collections.abc import Iterable
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from funcy import chain, first
|
||||
|
||||
@@ -104,7 +105,7 @@ class Experiments:
|
||||
return ExpCache(self.repo)
|
||||
|
||||
@property
|
||||
def stash_revs(self) -> Dict[str, "ExpStashEntry"]:
|
||||
def stash_revs(self) -> dict[str, "ExpStashEntry"]:
|
||||
revs = {}
|
||||
for queue in (self.workspace_queue, self.celery_queue):
|
||||
revs.update(queue.stash.stash_revs)
|
||||
@@ -113,7 +114,7 @@ class Experiments:
|
||||
def reproduce_one(
|
||||
self,
|
||||
tmp_dir: bool = False,
|
||||
copy_paths: Optional[List[str]] = None,
|
||||
copy_paths: Optional[list[str]] = None,
|
||||
message: Optional[str] = None,
|
||||
**kwargs,
|
||||
):
|
||||
@@ -130,33 +131,20 @@ class Experiments:
|
||||
self._log_reproduced(results, tmp_dir=tmp_dir)
|
||||
return results
|
||||
|
||||
def queue_one(
|
||||
self,
|
||||
queue: "BaseStashQueue",
|
||||
**kwargs,
|
||||
) -> "QueueEntry":
|
||||
def queue_one(self, queue: "BaseStashQueue", **kwargs) -> "QueueEntry":
|
||||
"""Queue a single experiment."""
|
||||
return self.new(
|
||||
queue,
|
||||
**kwargs,
|
||||
)
|
||||
return self.new(queue, **kwargs)
|
||||
|
||||
def reproduce_celery(
|
||||
self, entries: Optional[Iterable["QueueEntry"]] = None, **kwargs
|
||||
) -> Dict[str, str]:
|
||||
results: Dict[str, str] = {}
|
||||
) -> dict[str, str]:
|
||||
results: dict[str, str] = {}
|
||||
if entries is None:
|
||||
entries = list(
|
||||
chain(
|
||||
self.celery_queue.iter_active(),
|
||||
self.celery_queue.iter_queued(),
|
||||
)
|
||||
chain(self.celery_queue.iter_active(), self.celery_queue.iter_queued())
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
"reproduce all these entries '%s'",
|
||||
entries,
|
||||
)
|
||||
logger.debug("reproduce all these entries '%s'", entries)
|
||||
|
||||
if not entries:
|
||||
return results
|
||||
@@ -211,12 +199,7 @@ class Experiments:
|
||||
else:
|
||||
ui.write("Experiment results have been applied to your workspace.")
|
||||
|
||||
def new(
|
||||
self,
|
||||
queue: "BaseStashQueue",
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> "QueueEntry":
|
||||
def new(self, queue: "BaseStashQueue", *args, **kwargs) -> "QueueEntry":
|
||||
"""Create and enqueue a new experiment.
|
||||
|
||||
Experiment will be derived from the current workspace.
|
||||
@@ -250,10 +233,10 @@ class Experiments:
|
||||
def _reproduce_queue(
|
||||
self,
|
||||
queue: "BaseStashQueue",
|
||||
copy_paths: Optional[List[str]] = None,
|
||||
copy_paths: Optional[list[str]] = None,
|
||||
message: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> Dict[str, str]:
|
||||
) -> dict[str, str]:
|
||||
"""Reproduce queued experiments.
|
||||
|
||||
Arguments:
|
||||
@@ -265,7 +248,7 @@ class Experiments:
|
||||
"""
|
||||
exec_results = queue.reproduce(copy_paths=copy_paths, message=message)
|
||||
|
||||
results: Dict[str, str] = {}
|
||||
results: dict[str, str] = {}
|
||||
for _, exp_result in exec_results.items():
|
||||
results.update(exp_result)
|
||||
return results
|
||||
@@ -319,12 +302,12 @@ class Experiments:
|
||||
raise MultipleBranchError(rev, ref_infos)
|
||||
return str(ref_infos[0])
|
||||
|
||||
def get_exact_name(self, revs: Iterable[str]) -> Dict[str, Optional[str]]:
|
||||
def get_exact_name(self, revs: Iterable[str]) -> dict[str, Optional[str]]:
|
||||
"""Returns preferred name for the specified revision.
|
||||
|
||||
Prefers tags, branches (heads), experiments in that order.
|
||||
"""
|
||||
result: Dict[str, Optional[str]] = {}
|
||||
result: dict[str, Optional[str]] = {}
|
||||
exclude = f"{EXEC_NAMESPACE}/*"
|
||||
ref_dict = self.scm.describe(revs, base=EXPS_NAMESPACE, exclude=exclude)
|
||||
for rev in revs:
|
||||
|
||||
@@ -33,10 +33,9 @@ def apply(repo: "Repo", rev: str, **kwargs):
|
||||
try:
|
||||
exp_rev = resolve_rev(repo.scm, rev)
|
||||
except RevError as exc:
|
||||
(
|
||||
exp_ref_info,
|
||||
queue_entry,
|
||||
) = exps.celery_queue.get_ref_and_entry_by_names(rev)[rev]
|
||||
(exp_ref_info, queue_entry) = exps.celery_queue.get_ref_and_entry_by_names(rev)[
|
||||
rev
|
||||
]
|
||||
if exp_ref_info:
|
||||
exp_rev = repo.scm.get_ref(str(exp_ref_info))
|
||||
elif queue_entry:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from collections.abc import Iterator
|
||||
from contextlib import ExitStack, contextmanager
|
||||
from typing import TYPE_CHECKING, Iterator, Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dvc.repo.experiments.exceptions import InvalidExpRevError
|
||||
from dvc.scm import RevError
|
||||
@@ -9,10 +10,7 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
@contextmanager
|
||||
def switch_repo(
|
||||
repo: "Repo",
|
||||
rev: str,
|
||||
) -> Iterator[Tuple["Repo", str]]:
|
||||
def switch_repo(repo: "Repo", rev: str) -> Iterator[tuple["Repo", str]]:
|
||||
"""Return a repo instance (brancher) switched to rev.
|
||||
|
||||
If rev is the name of a running experiment, the returned instance will be
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
import itertools
|
||||
import os
|
||||
from collections.abc import Collection, Iterable, Iterator
|
||||
from dataclasses import fields
|
||||
from datetime import datetime
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Collection,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
|
||||
from funcy import first
|
||||
from scmrepo.exceptions import SCMError as InnerSCMError
|
||||
@@ -63,13 +54,7 @@ def collect_rev(
|
||||
else:
|
||||
orig_cwd = None
|
||||
try:
|
||||
data = _collect_rev(
|
||||
repo,
|
||||
rev,
|
||||
param_deps=param_deps,
|
||||
force=force,
|
||||
**kwargs,
|
||||
)
|
||||
data = _collect_rev(repo, rev, param_deps=param_deps, force=force, **kwargs)
|
||||
if not (rev == "workspace" or param_deps or data.contains_error):
|
||||
cache.put(data, force=True)
|
||||
return ExpState(rev=rev, data=data)
|
||||
@@ -142,7 +127,7 @@ def collect_queued(
|
||||
repo: "Repo",
|
||||
baseline_revs: Collection[str],
|
||||
**kwargs,
|
||||
) -> Dict[str, List["ExpRange"]]:
|
||||
) -> dict[str, list["ExpRange"]]:
|
||||
"""Collect queued experiments derived from the specified revisions.
|
||||
|
||||
Args:
|
||||
@@ -177,7 +162,7 @@ def collect_active(
|
||||
repo: "Repo",
|
||||
baseline_revs: Collection[str],
|
||||
**kwargs,
|
||||
) -> Dict[str, List["ExpRange"]]:
|
||||
) -> dict[str, list["ExpRange"]]:
|
||||
"""Collect active (running) experiments derived from the specified revisions.
|
||||
|
||||
Args:
|
||||
@@ -189,7 +174,7 @@ def collect_active(
|
||||
"""
|
||||
if not baseline_revs:
|
||||
return {}
|
||||
result: Dict[str, List["ExpRange"]] = {}
|
||||
result: dict[str, list["ExpRange"]] = {}
|
||||
exps = repo.experiments
|
||||
for queue in (exps.workspace_queue, exps.tempdir_queue, exps.celery_queue):
|
||||
for baseline, active_exps in queue.collect_active_data(
|
||||
@@ -206,7 +191,7 @@ def collect_failed(
|
||||
repo: "Repo",
|
||||
baseline_revs: Collection[str],
|
||||
**kwargs,
|
||||
) -> Dict[str, List["ExpRange"]]:
|
||||
) -> dict[str, list["ExpRange"]]:
|
||||
"""Collect failed experiments derived from the specified revisions.
|
||||
|
||||
Args:
|
||||
@@ -225,7 +210,7 @@ def collect_successful(
|
||||
repo: "Repo",
|
||||
baseline_revs: Collection[str],
|
||||
**kwargs,
|
||||
) -> Dict[str, List["ExpRange"]]:
|
||||
) -> dict[str, list["ExpRange"]]:
|
||||
"""Collect successful experiments derived from the specified revisions.
|
||||
|
||||
Args:
|
||||
@@ -235,7 +220,7 @@ def collect_successful(
|
||||
Returns:
|
||||
Dict mapping baseline revision to successful experiments.
|
||||
"""
|
||||
result: Dict[str, List["ExpRange"]] = {}
|
||||
result: dict[str, list["ExpRange"]] = {}
|
||||
for baseline_rev in baseline_revs:
|
||||
result[baseline_rev] = list(_collect_baseline(repo, baseline_rev, **kwargs))
|
||||
return result
|
||||
@@ -261,6 +246,7 @@ def _collect_baseline(
|
||||
ref_it = (ref for ref in iter(refs) if ref.startswith(str(ref_info)))
|
||||
else:
|
||||
ref_it = repo.scm.iter_refs(base=str(ref_info))
|
||||
executors = repo.experiments.celery_queue.collect_success_executors([baseline_rev])
|
||||
for ref in ref_it:
|
||||
try:
|
||||
ref_info = ExpRefInfo.from_ref(ref)
|
||||
@@ -272,12 +258,16 @@ def _collect_baseline(
|
||||
exps = list(collect_branch(repo, exp_rev, baseline_rev, **kwargs))
|
||||
if exps:
|
||||
exps[0].name = ref_info.name
|
||||
yield ExpRange(exps, name=ref_info.name)
|
||||
yield ExpRange(
|
||||
exps,
|
||||
name=ref_info.name,
|
||||
executor=executors.get(str(ref_info)),
|
||||
)
|
||||
|
||||
|
||||
def collect(
|
||||
repo: "Repo",
|
||||
revs: Union[List[str], str, None] = None,
|
||||
revs: Union[list[str], str, None] = None,
|
||||
all_branches: bool = False,
|
||||
all_tags: bool = False,
|
||||
all_commits: bool = False,
|
||||
@@ -286,7 +276,7 @@ def collect(
|
||||
hide_failed: bool = False,
|
||||
sha_only: bool = False,
|
||||
**kwargs,
|
||||
) -> List["ExpState"]:
|
||||
) -> list["ExpState"]:
|
||||
"""Collect baseline revisions and derived experiments."""
|
||||
assert isinstance(repo.scm, Git)
|
||||
if repo.scm.no_commits:
|
||||
@@ -307,14 +297,14 @@ def collect(
|
||||
)
|
||||
)
|
||||
if sha_only:
|
||||
baseline_names: Dict[str, Optional[str]] = {}
|
||||
baseline_names: dict[str, Optional[str]] = {}
|
||||
else:
|
||||
baseline_names = describe(
|
||||
repo.scm, baseline_revs, refs=cached_refs, logger=logger
|
||||
)
|
||||
|
||||
workspace_data = collect_rev(repo, "workspace", **kwargs)
|
||||
result: List["ExpState"] = [workspace_data]
|
||||
result: list["ExpState"] = [workspace_data]
|
||||
queued = collect_queued(repo, baseline_revs, **kwargs) if not hide_queued else {}
|
||||
active = collect_active(repo, baseline_revs, **kwargs)
|
||||
failed = collect_failed(repo, baseline_revs, **kwargs) if not hide_failed else {}
|
||||
@@ -340,10 +330,10 @@ def collect(
|
||||
return result
|
||||
|
||||
|
||||
def _sorted_ranges(exp_ranges: Iterable["ExpRange"]) -> List["ExpRange"]:
|
||||
def _sorted_ranges(exp_ranges: Iterable["ExpRange"]) -> list["ExpRange"]:
|
||||
"""Return list of ExpRange sorted by (timestamp, rev)."""
|
||||
|
||||
def _head_timestamp(exp_range: "ExpRange") -> Tuple[datetime, str]:
|
||||
def _head_timestamp(exp_range: "ExpRange") -> tuple[datetime, str]:
|
||||
head_exp = first(exp_range.revs)
|
||||
if head_exp and head_exp.data and head_exp.data.timestamp:
|
||||
return head_exp.data.timestamp, head_exp.rev
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import TYPE_CHECKING, Collection, Iterable, Optional
|
||||
from collections.abc import Collection, Iterable
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from dvc.exceptions import DvcException, InvalidArgumentError
|
||||
|
||||
@@ -50,11 +51,7 @@ class MultipleBranchError(DvcException):
|
||||
|
||||
|
||||
class AmbiguousExpRefInfo(InvalidArgumentError):
|
||||
def __init__(
|
||||
self,
|
||||
exp_name: str,
|
||||
exp_ref_list: Iterable["ExpRefInfo"],
|
||||
):
|
||||
def __init__(self, exp_name: str, exp_ref_list: Iterable["ExpRefInfo"]):
|
||||
msg = [
|
||||
(
|
||||
f"Ambiguous name '{exp_name}' refers to multiple experiments."
|
||||
|
||||
@@ -3,25 +3,12 @@ import os
|
||||
import pickle
|
||||
import shutil
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Iterable, Iterator
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import asdict, dataclass
|
||||
from enum import IntEnum
|
||||
from itertools import chain
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Any, Callable, NamedTuple, Optional, Union
|
||||
|
||||
from scmrepo.exceptions import SCMError
|
||||
|
||||
@@ -41,6 +28,8 @@ from dvc.utils.studio import env_to_config
|
||||
if TYPE_CHECKING:
|
||||
from queue import Queue
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from dvc.repo import Repo
|
||||
from dvc.repo.experiments.stash import ExpStashEntry
|
||||
from dvc.scm import Git
|
||||
@@ -112,9 +101,6 @@ class ExecutorInfo:
|
||||
return cls.from_dict(load_json(filename))
|
||||
|
||||
|
||||
_T = TypeVar("_T", bound="BaseExecutor")
|
||||
|
||||
|
||||
class BaseExecutor(ABC):
|
||||
"""Base class for executing experiments in parallel.
|
||||
|
||||
@@ -185,7 +171,7 @@ class BaseExecutor(ABC):
|
||||
@property
|
||||
def info(self) -> "ExecutorInfo":
|
||||
if self.result is not None:
|
||||
result_dict: Dict[str, Any] = {
|
||||
result_dict: dict[str, Any] = {
|
||||
"result_hash": self.result.exp_hash,
|
||||
"result_ref": (
|
||||
str(self.result.ref_info) if self.result.ref_info else None
|
||||
@@ -207,7 +193,7 @@ class BaseExecutor(ABC):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_info(cls: Type[_T], info: "ExecutorInfo") -> _T:
|
||||
def from_info(cls, info: "ExecutorInfo") -> "Self":
|
||||
if info.result_hash:
|
||||
result: Optional["ExecutorResult"] = ExecutorResult(
|
||||
info.result_hash,
|
||||
@@ -229,21 +215,21 @@ class BaseExecutor(ABC):
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def from_stash_entry(
|
||||
cls: Type[_T],
|
||||
cls,
|
||||
repo: "Repo",
|
||||
entry: "ExpStashEntry",
|
||||
**kwargs,
|
||||
) -> _T:
|
||||
) -> "Self":
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def _from_stash_entry(
|
||||
cls: Type[_T],
|
||||
cls,
|
||||
repo: "Repo",
|
||||
entry: "ExpStashEntry",
|
||||
root_dir: str,
|
||||
**kwargs,
|
||||
) -> _T:
|
||||
) -> "Self":
|
||||
return cls(
|
||||
root_dir=root_dir,
|
||||
dvc_dir=relpath(repo.dvc_dir, repo.scm.root_dir),
|
||||
@@ -255,7 +241,7 @@ class BaseExecutor(ABC):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_top_level_paths(cls, repo: "Repo") -> List["str"]:
|
||||
def _get_top_level_paths(cls, repo: "Repo") -> list["str"]:
|
||||
return list(
|
||||
chain(
|
||||
_collect_top_level_metrics(repo),
|
||||
@@ -268,8 +254,10 @@ class BaseExecutor(ABC):
|
||||
def save(
|
||||
cls,
|
||||
info: "ExecutorInfo",
|
||||
targets: Optional[Iterable[str]] = None,
|
||||
recursive: bool = False,
|
||||
force: bool = False,
|
||||
include_untracked: Optional[List[str]] = None,
|
||||
include_untracked: Optional[list[str]] = None,
|
||||
message: Optional[str] = None,
|
||||
) -> ExecutorResult:
|
||||
from dvc.dvcfile import LOCK_FILE
|
||||
@@ -293,7 +281,16 @@ class BaseExecutor(ABC):
|
||||
include_untracked.append(LOCK_FILE)
|
||||
|
||||
try:
|
||||
stages = dvc.commit([], force=True, relink=False)
|
||||
stages = []
|
||||
if targets:
|
||||
for target in targets:
|
||||
stages.append(
|
||||
dvc.commit(
|
||||
target, recursive=recursive, force=True, relink=False
|
||||
)
|
||||
)
|
||||
else:
|
||||
stages = dvc.commit([], recursive=recursive, force=True, relink=False)
|
||||
exp_hash = cls.hash_exp(stages)
|
||||
if include_untracked:
|
||||
dvc.scm.add(include_untracked, force=True) # type: ignore[call-arg]
|
||||
@@ -371,7 +368,7 @@ class BaseExecutor(ABC):
|
||||
def fetch_exps(
|
||||
self,
|
||||
dest_scm: "Git",
|
||||
refs: List[str],
|
||||
refs: list[str],
|
||||
force: bool = False,
|
||||
on_diverged: Optional[Callable[[str], None]] = None,
|
||||
**kwargs,
|
||||
@@ -446,7 +443,7 @@ class BaseExecutor(ABC):
|
||||
infofile: Optional[str] = None,
|
||||
log_errors: bool = True,
|
||||
log_level: Optional[int] = None,
|
||||
copy_paths: Optional[List[str]] = None,
|
||||
copy_paths: Optional[list[str]] = None,
|
||||
message: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> "ExecutorResult":
|
||||
@@ -554,7 +551,7 @@ class BaseExecutor(ABC):
|
||||
git_remote,
|
||||
repro_force,
|
||||
message: Optional[str] = None,
|
||||
) -> Tuple[Optional[str], Optional["ExpRefInfo"], bool]:
|
||||
) -> tuple[Optional[str], Optional["ExpRefInfo"], bool]:
|
||||
cls.commit(
|
||||
dvc.scm,
|
||||
exp_hash,
|
||||
@@ -587,7 +584,7 @@ class BaseExecutor(ABC):
|
||||
info: "ExecutorInfo",
|
||||
infofile: Optional[str] = None,
|
||||
log_errors: bool = True,
|
||||
copy_paths: Optional[List[str]] = None,
|
||||
copy_paths: Optional[list[str]] = None,
|
||||
message: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> Iterator["Repo"]:
|
||||
@@ -617,7 +614,7 @@ class BaseExecutor(ABC):
|
||||
# set missing config options using saved config
|
||||
# inferring repo url will fail if not set here
|
||||
run_env_config = env_to_config(kwargs.get("run_env", {}))
|
||||
dvc_studio_config = {**run_env_config, **dvc_studio_config}
|
||||
dvc_studio_config = run_env_config | dvc_studio_config
|
||||
try:
|
||||
post_live_metrics(
|
||||
"start",
|
||||
@@ -779,7 +776,7 @@ class BaseExecutor(ABC):
|
||||
raise DvcException(f"Unable to copy '{src}' to '{dst}'.") from exc
|
||||
|
||||
@contextmanager
|
||||
def set_temp_refs(self, scm: "Git", temp_dict: Dict[str, str]):
|
||||
def set_temp_refs(self, scm: "Git", temp_dict: dict[str, str]):
|
||||
try:
|
||||
for ref, rev in temp_dict.items():
|
||||
scm.set_ref(ref, rev)
|
||||
|
||||
@@ -132,11 +132,7 @@ class TempDirExecutor(BaseLocalExecutor):
|
||||
self._update_config(repo.config.read("local"))
|
||||
|
||||
def _update_config(self, update):
|
||||
local_config = os.path.join(
|
||||
self.root_dir,
|
||||
self.dvc_dir,
|
||||
"config.local",
|
||||
)
|
||||
local_config = os.path.join(self.root_dir, self.dvc_dir, "config.local")
|
||||
logger.debug("Writing experiments local config '%s'", local_config)
|
||||
if os.path.exists(local_config):
|
||||
conf_obj = ConfigObj(local_config)
|
||||
@@ -188,12 +184,7 @@ class WorkspaceExecutor(BaseLocalExecutor):
|
||||
self._detach_stack = ExitStack()
|
||||
|
||||
@classmethod
|
||||
def from_stash_entry(
|
||||
cls,
|
||||
repo: "Repo",
|
||||
entry: "ExpStashEntry",
|
||||
**kwargs,
|
||||
):
|
||||
def from_stash_entry(cls, repo: "Repo", entry: "ExpStashEntry", **kwargs):
|
||||
root_dir = repo.scm.root_dir
|
||||
executor: "WorkspaceExecutor" = cls._from_stash_entry(
|
||||
repo, entry, root_dir, **kwargs
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from collections import defaultdict
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
from typing import Optional, Union
|
||||
|
||||
from dvc.log import logger
|
||||
from dvc.repo import locked
|
||||
@@ -15,11 +15,11 @@ logger = logger.getChild(__name__)
|
||||
@scm_context
|
||||
def ls(
|
||||
repo,
|
||||
rev: Optional[Union[List[str], str]] = None,
|
||||
rev: Optional[Union[list[str], str]] = None,
|
||||
all_commits: bool = False,
|
||||
num: int = 1,
|
||||
git_remote: Optional[str] = None,
|
||||
) -> Dict[str, List[Tuple[str, Optional[str]]]]:
|
||||
) -> dict[str, list[tuple[str, Optional[str]]]]:
|
||||
"""List experiments.
|
||||
|
||||
Returns a dict mapping baseline revs to a list of (exp_name, exp_sha) tuples.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import Iterable, List, Mapping, Optional, Set, Union
|
||||
from collections.abc import Iterable, Mapping
|
||||
from typing import Optional, Union
|
||||
|
||||
from funcy import group_by
|
||||
from scmrepo.git.backend.base import SyncStatus
|
||||
@@ -23,13 +24,13 @@ def pull( # noqa: C901
|
||||
git_remote: str,
|
||||
exp_names: Optional[Union[Iterable[str], str]] = None,
|
||||
all_commits=False,
|
||||
rev: Optional[Union[List[str], str]] = None,
|
||||
rev: Optional[Union[list[str], str]] = None,
|
||||
num=1,
|
||||
force: bool = False,
|
||||
pull_cache: bool = False,
|
||||
**kwargs,
|
||||
) -> Iterable[str]:
|
||||
exp_ref_set: Set["ExpRefInfo"] = set()
|
||||
exp_ref_set: set["ExpRefInfo"] = set()
|
||||
if all_commits:
|
||||
exp_ref_set.update(exp_refs(repo.scm, git_remote))
|
||||
elif exp_names:
|
||||
@@ -81,7 +82,7 @@ def _pull(
|
||||
git_remote: str,
|
||||
refs: Iterable["ExpRefInfo"],
|
||||
force: bool,
|
||||
) -> Mapping[SyncStatus, List["ExpRefInfo"]]:
|
||||
) -> Mapping[SyncStatus, list["ExpRefInfo"]]:
|
||||
refspec_list = [f"{exp_ref}:{exp_ref}" for exp_ref in refs]
|
||||
logger.debug("git pull experiment '%s' -> '%s'", git_remote, refspec_list)
|
||||
|
||||
@@ -96,7 +97,7 @@ def _pull(
|
||||
def group_result(refspec):
|
||||
return results[str(refspec)]
|
||||
|
||||
pull_result: Mapping[SyncStatus, List["ExpRefInfo"]] = group_by(group_result, refs)
|
||||
pull_result: Mapping[SyncStatus, list["ExpRefInfo"]] = group_by(group_result, refs)
|
||||
|
||||
return pull_result
|
||||
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
Mapping,
|
||||
Optional,
|
||||
Set,
|
||||
Union,
|
||||
)
|
||||
from collections.abc import Iterable, Mapping
|
||||
from typing import TYPE_CHECKING, Any, Optional, Union
|
||||
|
||||
from funcy import compact, group_by
|
||||
from scmrepo.git.backend.base import SyncStatus
|
||||
@@ -39,7 +30,7 @@ class UploadError(DvcException):
|
||||
|
||||
|
||||
def notify_refs_to_studio(
|
||||
repo: "Repo", git_remote: str, **refs: List[str]
|
||||
repo: "Repo", git_remote: str, **refs: list[str]
|
||||
) -> Optional[str]:
|
||||
import os
|
||||
|
||||
@@ -67,7 +58,7 @@ def notify_refs_to_studio(
|
||||
return d.get("url")
|
||||
|
||||
|
||||
def exp_refs_from_names(scm: "Git", exp_names: List[str]) -> Set["ExpRefInfo"]:
|
||||
def exp_refs_from_names(scm: "Git", exp_names: list[str]) -> set["ExpRefInfo"]:
|
||||
exp_ref_set = set()
|
||||
exp_ref_dict = resolve_name(scm, exp_names)
|
||||
unresolved_exp_names = []
|
||||
@@ -82,7 +73,7 @@ def exp_refs_from_names(scm: "Git", exp_names: List[str]) -> Set["ExpRefInfo"]:
|
||||
return exp_ref_set
|
||||
|
||||
|
||||
def exp_refs_from_rev(scm: "Git", rev: List[str], num: int = 1) -> Set["ExpRefInfo"]:
|
||||
def exp_refs_from_rev(scm: "Git", rev: list[str], num: int = 1) -> set["ExpRefInfo"]:
|
||||
exp_ref_set = set()
|
||||
rev_dict = iter_revs(scm, rev, num)
|
||||
rev_set = set(rev_dict.keys())
|
||||
@@ -97,15 +88,15 @@ def exp_refs_from_rev(scm: "Git", rev: List[str], num: int = 1) -> Set["ExpRefIn
|
||||
def push(
|
||||
repo: "Repo",
|
||||
git_remote: str,
|
||||
exp_names: Optional[Union[List[str], str]] = None,
|
||||
exp_names: Optional[Union[list[str], str]] = None,
|
||||
all_commits: bool = False,
|
||||
rev: Optional[Union[List[str], str]] = None,
|
||||
rev: Optional[Union[list[str], str]] = None,
|
||||
num: int = 1,
|
||||
force: bool = False,
|
||||
push_cache: bool = False,
|
||||
**kwargs: Any,
|
||||
) -> Dict[str, Any]:
|
||||
exp_ref_set: Set["ExpRefInfo"] = set()
|
||||
) -> dict[str, Any]:
|
||||
exp_ref_set: set["ExpRefInfo"] = set()
|
||||
assert isinstance(repo.scm, Git)
|
||||
if all_commits:
|
||||
exp_ref_set.update(exp_refs(repo.scm))
|
||||
@@ -123,7 +114,7 @@ def push(
|
||||
status.name.lower(): [ref.name for ref in ref_list]
|
||||
for status, ref_list in push_result.items()
|
||||
}
|
||||
result: Dict[str, Any] = {**refs, "uploaded": 0}
|
||||
result: dict[str, Any] = {**refs, "uploaded": 0}
|
||||
|
||||
pushed_refs_info = (
|
||||
push_result[SyncStatus.UP_TO_DATE] + push_result[SyncStatus.SUCCESS]
|
||||
@@ -149,7 +140,7 @@ def _push(
|
||||
git_remote: str,
|
||||
refs: Iterable["ExpRefInfo"],
|
||||
force: bool,
|
||||
) -> Mapping[SyncStatus, List["ExpRefInfo"]]:
|
||||
) -> Mapping[SyncStatus, list["ExpRefInfo"]]:
|
||||
from scmrepo.exceptions import AuthError
|
||||
|
||||
from dvc.scm import GitAuthError
|
||||
@@ -171,7 +162,7 @@ def _push(
|
||||
def group_result(refspec):
|
||||
return results[str(refspec)]
|
||||
|
||||
pull_result: Mapping[SyncStatus, List["ExpRefInfo"]] = group_by(group_result, refs)
|
||||
pull_result: Mapping[SyncStatus, list["ExpRefInfo"]] = group_by(group_result, refs)
|
||||
|
||||
return pull_result
|
||||
|
||||
|
||||
@@ -1,20 +1,8 @@
|
||||
import os
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Collection, Generator, Iterable, Mapping
|
||||
from dataclasses import asdict, dataclass
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Collection,
|
||||
Dict,
|
||||
Generator,
|
||||
Iterable,
|
||||
List,
|
||||
Mapping,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Type,
|
||||
Union,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Any, NamedTuple, Optional, Union
|
||||
|
||||
from dvc_studio_client.post_live_metrics import get_studio_config
|
||||
from funcy import retry
|
||||
@@ -69,11 +57,11 @@ class QueueEntry:
|
||||
and self.stash_rev == other.stash_rev
|
||||
)
|
||||
|
||||
def asdict(self) -> Dict[str, Any]:
|
||||
def asdict(self) -> dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: Dict[str, Any]) -> "QueueEntry":
|
||||
def from_dict(cls, d: dict[str, Any]) -> "QueueEntry":
|
||||
return cls(**d)
|
||||
|
||||
|
||||
@@ -150,7 +138,7 @@ class BaseStashQueue(ABC):
|
||||
all_: bool = False,
|
||||
queued: bool = False,
|
||||
**kwargs,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
"""Remove the specified entries from the queue.
|
||||
|
||||
Arguments:
|
||||
@@ -165,8 +153,8 @@ class BaseStashQueue(ABC):
|
||||
if all_ or queued:
|
||||
return self.clear()
|
||||
|
||||
name_to_remove: List[str] = []
|
||||
entry_to_remove: List[ExpStashEntry] = []
|
||||
name_to_remove: list[str] = []
|
||||
entry_to_remove: list[ExpStashEntry] = []
|
||||
queue_entries = self.match_queue_entry_by_name(revs, self.iter_queued())
|
||||
for name, entry in queue_entries.items():
|
||||
if entry:
|
||||
@@ -176,7 +164,7 @@ class BaseStashQueue(ABC):
|
||||
self.stash.remove_revs(entry_to_remove)
|
||||
return name_to_remove
|
||||
|
||||
def clear(self, **kwargs) -> List[str]:
|
||||
def clear(self, **kwargs) -> list[str]:
|
||||
"""Remove all entries from the queue."""
|
||||
stash_revs = self.stash.stash_revs
|
||||
name_to_remove = list(stash_revs)
|
||||
@@ -184,11 +172,11 @@ class BaseStashQueue(ABC):
|
||||
|
||||
return name_to_remove
|
||||
|
||||
def status(self) -> List[Dict[str, Any]]:
|
||||
def status(self) -> list[dict[str, Any]]:
|
||||
"""Show the status of exp tasks in queue"""
|
||||
from datetime import datetime
|
||||
|
||||
result: List[Dict[str, Optional[str]]] = []
|
||||
result: list[dict[str, Optional[str]]] = []
|
||||
|
||||
def _get_timestamp(rev: str) -> datetime:
|
||||
commit = self.scm.resolve_commit(rev)
|
||||
@@ -198,7 +186,7 @@ class BaseStashQueue(ABC):
|
||||
entry: QueueEntry,
|
||||
exp_result: Optional["ExecutorResult"] = None,
|
||||
status: str = "Unknown",
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
name = entry.name
|
||||
if not name and exp_result and exp_result.ref_info:
|
||||
name = exp_result.ref_info.name
|
||||
@@ -251,7 +239,7 @@ class BaseStashQueue(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def reproduce(
|
||||
self, copy_paths: Optional[List[str]] = None, message: Optional[str] = None
|
||||
self, copy_paths: Optional[list[str]] = None, message: Optional[str] = None
|
||||
) -> Mapping[str, Mapping[str, str]]:
|
||||
"""Reproduce queued experiments sequentially."""
|
||||
|
||||
@@ -281,12 +269,7 @@ class BaseStashQueue(ABC):
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def logs(
|
||||
self,
|
||||
rev: str,
|
||||
encoding: Optional[str] = None,
|
||||
follow: bool = False,
|
||||
):
|
||||
def logs(self, rev: str, encoding: Optional[str] = None, follow: bool = False):
|
||||
"""Print redirected output logs for an exp process.
|
||||
|
||||
Args:
|
||||
@@ -300,7 +283,7 @@ class BaseStashQueue(ABC):
|
||||
def _stash_exp(
|
||||
self,
|
||||
*args,
|
||||
params: Optional[Dict[str, List[str]]] = None,
|
||||
params: Optional[dict[str, list[str]]] = None,
|
||||
baseline_rev: Optional[str] = None,
|
||||
branch: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
@@ -342,9 +325,7 @@ class BaseStashQueue(ABC):
|
||||
self._stash_commit_deps(*args, **kwargs)
|
||||
|
||||
# save additional repro command line arguments
|
||||
run_env = {
|
||||
DVC_EXP_BASELINE_REV: baseline_rev,
|
||||
}
|
||||
run_env = {DVC_EXP_BASELINE_REV: baseline_rev}
|
||||
if not name:
|
||||
name = get_random_exp_name(self.scm, baseline_rev)
|
||||
run_env[DVC_EXP_NAME] = name
|
||||
@@ -356,7 +337,7 @@ class BaseStashQueue(ABC):
|
||||
studio_config = get_studio_config(
|
||||
dvc_studio_config=self.repo.config.get("studio")
|
||||
)
|
||||
run_env = {**config_to_env(studio_config), **run_env}
|
||||
run_env = config_to_env(studio_config) | run_env
|
||||
self._pack_args(*args, run_env=run_env, **kwargs)
|
||||
# save experiment as a stash commit
|
||||
msg = self._stash_msg(
|
||||
@@ -460,7 +441,7 @@ class BaseStashQueue(ABC):
|
||||
f"from '{config_path}': {param_list}"
|
||||
)
|
||||
|
||||
def _update_params(self, params: Dict[str, List[str]]):
|
||||
def _update_params(self, params: dict[str, list[str]]):
|
||||
"""Update param files with the provided `Hydra Override`_ patterns.
|
||||
|
||||
Args:
|
||||
@@ -486,11 +467,15 @@ class BaseStashQueue(ABC):
|
||||
else:
|
||||
config_dir = None
|
||||
config_name = hydra_config.get("config_name", "config")
|
||||
plugins_path = os.path.join(
|
||||
self.repo.root_dir, hydra_config.get("plugins_path", "")
|
||||
)
|
||||
compose_and_dump(
|
||||
path,
|
||||
config_dir,
|
||||
config_module,
|
||||
config_name,
|
||||
plugins_path,
|
||||
overrides,
|
||||
)
|
||||
else:
|
||||
@@ -504,10 +489,7 @@ class BaseStashQueue(ABC):
|
||||
|
||||
@staticmethod
|
||||
@retry(180, errors=LockError, timeout=1)
|
||||
def get_stash_entry(
|
||||
exp: "Experiments",
|
||||
queue_entry: QueueEntry,
|
||||
) -> "ExpStashEntry":
|
||||
def get_stash_entry(exp: "Experiments", queue_entry: QueueEntry) -> "ExpStashEntry":
|
||||
stash = ExpStash(exp.scm, queue_entry.stash_ref)
|
||||
stash_rev = queue_entry.stash_rev
|
||||
with get_exp_rwlock(exp.repo, writes=[queue_entry.stash_ref]):
|
||||
@@ -524,7 +506,7 @@ class BaseStashQueue(ABC):
|
||||
cls,
|
||||
exp: "Experiments",
|
||||
queue_entry: QueueEntry,
|
||||
executor_cls: Type[BaseExecutor] = WorkspaceExecutor,
|
||||
executor_cls: type[BaseExecutor] = WorkspaceExecutor,
|
||||
**kwargs,
|
||||
) -> BaseExecutor:
|
||||
stash_entry = cls.get_stash_entry(exp, queue_entry)
|
||||
@@ -559,7 +541,7 @@ class BaseStashQueue(ABC):
|
||||
exp: "Experiments",
|
||||
executor: BaseExecutor,
|
||||
exec_result: "ExecutorResult",
|
||||
) -> Dict[str, str]:
|
||||
) -> dict[str, str]:
|
||||
results = {}
|
||||
|
||||
def on_diverged(ref: str):
|
||||
@@ -589,7 +571,7 @@ class BaseStashQueue(ABC):
|
||||
exp: "Experiments",
|
||||
executor: BaseExecutor,
|
||||
exec_result: "ExecutorResult",
|
||||
) -> Dict[str, str]:
|
||||
) -> dict[str, str]:
|
||||
results = cls.collect_git(exp, executor, exec_result)
|
||||
|
||||
if exec_result.ref_info is not None:
|
||||
@@ -601,11 +583,11 @@ class BaseStashQueue(ABC):
|
||||
self,
|
||||
exp_names: Collection[str],
|
||||
*entries: Iterable[Union[QueueEntry, QueueDoneResult]],
|
||||
) -> Dict[str, Optional[QueueEntry]]:
|
||||
) -> dict[str, Optional[QueueEntry]]:
|
||||
from funcy import concat
|
||||
|
||||
entry_name_dict: Dict[str, QueueEntry] = {}
|
||||
entry_rev_dict: Dict[str, QueueEntry] = {}
|
||||
entry_name_dict: dict[str, QueueEntry] = {}
|
||||
entry_rev_dict: dict[str, QueueEntry] = {}
|
||||
for entry in concat(*entries):
|
||||
if isinstance(entry, QueueDoneResult):
|
||||
queue_entry: QueueEntry = entry.entry
|
||||
@@ -620,7 +602,7 @@ class BaseStashQueue(ABC):
|
||||
entry_name_dict[name] = queue_entry
|
||||
entry_rev_dict[queue_entry.stash_rev] = queue_entry
|
||||
|
||||
result: Dict[str, Optional[QueueEntry]] = {}
|
||||
result: dict[str, Optional[QueueEntry]] = {}
|
||||
for exp_name in exp_names:
|
||||
result[exp_name] = None
|
||||
if exp_name in entry_name_dict:
|
||||
@@ -662,7 +644,7 @@ class BaseStashQueue(ABC):
|
||||
baseline_revs: Optional[Collection[str]],
|
||||
fetch_refs: bool = False,
|
||||
**kwargs,
|
||||
) -> Dict[str, List["ExpRange"]]:
|
||||
) -> dict[str, list["ExpRange"]]:
|
||||
"""Collect data for active (running) experiments.
|
||||
|
||||
Args:
|
||||
@@ -681,7 +663,7 @@ class BaseStashQueue(ABC):
|
||||
self,
|
||||
baseline_revs: Optional[Collection[str]],
|
||||
**kwargs,
|
||||
) -> Dict[str, List["ExpRange"]]:
|
||||
) -> dict[str, list["ExpRange"]]:
|
||||
"""Collect data for queued experiments.
|
||||
|
||||
Args:
|
||||
@@ -698,7 +680,7 @@ class BaseStashQueue(ABC):
|
||||
self,
|
||||
baseline_revs: Optional[Collection[str]],
|
||||
**kwargs,
|
||||
) -> Dict[str, List["ExpRange"]]:
|
||||
) -> dict[str, list["ExpRange"]]:
|
||||
"""Collect data for failed experiments.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -3,18 +3,8 @@ import locale
|
||||
import logging
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Collection,
|
||||
Dict,
|
||||
Generator,
|
||||
List,
|
||||
Mapping,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Set,
|
||||
Union,
|
||||
)
|
||||
from collections.abc import Collection, Generator, Mapping
|
||||
from typing import TYPE_CHECKING, NamedTuple, Optional, Union
|
||||
|
||||
from celery.result import AsyncResult
|
||||
from funcy import first
|
||||
@@ -42,7 +32,7 @@ if TYPE_CHECKING:
|
||||
|
||||
from dvc.repo.experiments.executor.base import ExecutorResult
|
||||
from dvc.repo.experiments.refs import ExpRefInfo
|
||||
from dvc.repo.experiments.serialize import ExpRange
|
||||
from dvc.repo.experiments.serialize import ExpExecutor, ExpRange
|
||||
from dvc_task.app import FSApp
|
||||
from dvc_task.proc.manager import ProcessManager
|
||||
from dvc_task.worker import TemporaryWorker
|
||||
@@ -88,10 +78,7 @@ class LocalCeleryQueue(BaseStashQueue):
|
||||
"dvc-exp-local",
|
||||
wdir=self.wdir,
|
||||
mkdir=True,
|
||||
include=[
|
||||
"dvc.repo.experiments.queue.tasks",
|
||||
"dvc_task.proc.tasks",
|
||||
],
|
||||
include=["dvc.repo.experiments.queue.tasks", "dvc_task.proc.tasks"],
|
||||
)
|
||||
app.conf.update({"task_acks_late": True, "result_expires": None})
|
||||
return app
|
||||
@@ -166,7 +153,7 @@ class LocalCeleryQueue(BaseStashQueue):
|
||||
"""
|
||||
|
||||
logger.debug("Spawning %s exp queue workers", count)
|
||||
active_worker: Dict = self.worker_status()
|
||||
active_worker: dict = self.worker_status()
|
||||
|
||||
started = 0
|
||||
for num in range(1, 1 + count):
|
||||
@@ -183,7 +170,7 @@ class LocalCeleryQueue(BaseStashQueue):
|
||||
def put(
|
||||
self,
|
||||
*args,
|
||||
copy_paths: Optional[List[str]] = None,
|
||||
copy_paths: Optional[list[str]] = None,
|
||||
message: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> QueueEntry:
|
||||
@@ -265,7 +252,7 @@ class LocalCeleryQueue(BaseStashQueue):
|
||||
yield QueueDoneResult(queue_entry, exp_result)
|
||||
|
||||
def reproduce(
|
||||
self, copy_paths: Optional[List[str]] = None, message: Optional[str] = None
|
||||
self, copy_paths: Optional[list[str]] = None, message: Optional[str] = None
|
||||
) -> Mapping[str, Mapping[str, str]]:
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -333,8 +320,8 @@ class LocalCeleryQueue(BaseStashQueue):
|
||||
while not self.proc.get(entry.stash_rev):
|
||||
time.sleep(sleep_interval)
|
||||
|
||||
def _get_running_task_ids(self) -> Set[str]:
|
||||
running_task_ids: Set[str] = set()
|
||||
def _get_running_task_ids(self) -> set[str]:
|
||||
running_task_ids: set[str] = set()
|
||||
active_workers = self.worker_status()
|
||||
for _, tasks in active_workers.items():
|
||||
task = first(tasks)
|
||||
@@ -343,9 +330,9 @@ class LocalCeleryQueue(BaseStashQueue):
|
||||
return running_task_ids
|
||||
|
||||
def _try_to_kill_tasks(
|
||||
self, to_kill: Dict[QueueEntry, str], force: bool
|
||||
) -> Dict[QueueEntry, str]:
|
||||
fail_to_kill_entries: Dict[QueueEntry, str] = {}
|
||||
self, to_kill: dict[QueueEntry, str], force: bool
|
||||
) -> dict[QueueEntry, str]:
|
||||
fail_to_kill_entries: dict[QueueEntry, str] = {}
|
||||
for queue_entry, rev in to_kill.items():
|
||||
try:
|
||||
if force:
|
||||
@@ -358,9 +345,9 @@ class LocalCeleryQueue(BaseStashQueue):
|
||||
return fail_to_kill_entries
|
||||
|
||||
def _mark_inactive_tasks_failure(
|
||||
self, remained_entries: Dict[QueueEntry, str]
|
||||
self, remained_entries: dict[QueueEntry, str]
|
||||
) -> None:
|
||||
remained_revs: List[str] = []
|
||||
remained_revs: list[str] = []
|
||||
running_ids = self._get_running_task_ids()
|
||||
logger.debug("Current running tasks ids: %s.", running_ids)
|
||||
for msg, entry in self._iter_processed():
|
||||
@@ -383,12 +370,9 @@ class LocalCeleryQueue(BaseStashQueue):
|
||||
if remained_revs:
|
||||
raise CannotKillTasksError(remained_revs)
|
||||
|
||||
def _kill_entries(self, entries: Dict[QueueEntry, str], force: bool) -> None:
|
||||
logger.debug(
|
||||
"Found active tasks: '%s' to kill",
|
||||
list(entries.values()),
|
||||
)
|
||||
inactive_entries: Dict[QueueEntry, str] = self._try_to_kill_tasks(
|
||||
def _kill_entries(self, entries: dict[QueueEntry, str], force: bool) -> None:
|
||||
logger.debug("Found active tasks: '%s' to kill", list(entries.values()))
|
||||
inactive_entries: dict[QueueEntry, str] = self._try_to_kill_tasks(
|
||||
entries, force
|
||||
)
|
||||
|
||||
@@ -396,12 +380,12 @@ class LocalCeleryQueue(BaseStashQueue):
|
||||
self._mark_inactive_tasks_failure(inactive_entries)
|
||||
|
||||
def kill(self, revs: Collection[str], force: bool = False) -> None:
|
||||
name_dict: Dict[str, Optional[QueueEntry]] = self.match_queue_entry_by_name(
|
||||
name_dict: dict[str, Optional[QueueEntry]] = self.match_queue_entry_by_name(
|
||||
set(revs), self.iter_active()
|
||||
)
|
||||
|
||||
missing_revs: List[str] = []
|
||||
to_kill: Dict[QueueEntry, str] = {}
|
||||
missing_revs: list[str] = []
|
||||
to_kill: dict[QueueEntry, str] = {}
|
||||
for rev, queue_entry in name_dict.items():
|
||||
if queue_entry is None:
|
||||
missing_revs.append(rev)
|
||||
@@ -417,26 +401,17 @@ class LocalCeleryQueue(BaseStashQueue):
|
||||
def shutdown(self, kill: bool = False):
|
||||
self.celery.control.shutdown()
|
||||
if kill:
|
||||
to_kill: Dict[QueueEntry, str] = {}
|
||||
to_kill: dict[QueueEntry, str] = {}
|
||||
for entry in self.iter_active():
|
||||
to_kill[entry] = entry.name or entry.stash_rev
|
||||
if to_kill:
|
||||
self._kill_entries(to_kill, True)
|
||||
|
||||
def follow(
|
||||
self,
|
||||
entry: QueueEntry,
|
||||
encoding: Optional[str] = None,
|
||||
):
|
||||
def follow(self, entry: QueueEntry, encoding: Optional[str] = None):
|
||||
for line in self.proc.follow(entry.stash_rev, encoding):
|
||||
ui.write(line, end="")
|
||||
|
||||
def logs(
|
||||
self,
|
||||
rev: str,
|
||||
encoding: Optional[str] = None,
|
||||
follow: bool = False,
|
||||
):
|
||||
def logs(self, rev: str, encoding: Optional[str] = None, follow: bool = False):
|
||||
queue_entry: Optional[QueueEntry] = self.match_queue_entry_by_name(
|
||||
{rev}, self.iter_active(), self.iter_done()
|
||||
).get(rev)
|
||||
@@ -463,12 +438,11 @@ class LocalCeleryQueue(BaseStashQueue):
|
||||
f"No output logs found for experiment '{rev}'"
|
||||
)
|
||||
with open(
|
||||
proc_info.stdout,
|
||||
encoding=encoding or locale.getpreferredencoding(),
|
||||
proc_info.stdout, encoding=encoding or locale.getpreferredencoding()
|
||||
) as fobj:
|
||||
ui.write(fobj.read())
|
||||
|
||||
def worker_status(self) -> Dict[str, List[Dict]]:
|
||||
def worker_status(self) -> dict[str, list[dict]]:
|
||||
"""Return the current active celery worker"""
|
||||
status = self.celery.control.inspect().active() or {}
|
||||
logger.debug("Worker status: %s", status)
|
||||
@@ -486,21 +460,21 @@ class LocalCeleryQueue(BaseStashQueue):
|
||||
|
||||
def get_ref_and_entry_by_names(
|
||||
self,
|
||||
exp_names: Union[str, List[str]],
|
||||
exp_names: Union[str, list[str]],
|
||||
git_remote: Optional[str] = None,
|
||||
) -> Dict[str, ExpRefAndQueueEntry]:
|
||||
) -> dict[str, ExpRefAndQueueEntry]:
|
||||
"""Find finished ExpRefInfo or queued or failed QueueEntry by name"""
|
||||
from dvc.repo.experiments.utils import resolve_name
|
||||
|
||||
if isinstance(exp_names, str):
|
||||
exp_names = [exp_names]
|
||||
results: Dict[str, ExpRefAndQueueEntry] = {}
|
||||
results: dict[str, ExpRefAndQueueEntry] = {}
|
||||
|
||||
exp_ref_match: Dict[str, Optional["ExpRefInfo"]] = resolve_name(
|
||||
exp_ref_match: dict[str, Optional["ExpRefInfo"]] = resolve_name(
|
||||
self.scm, exp_names, git_remote
|
||||
)
|
||||
if not git_remote:
|
||||
queue_entry_match: Dict[
|
||||
queue_entry_match: dict[
|
||||
str, Optional["QueueEntry"]
|
||||
] = self.match_queue_entry_by_name(
|
||||
exp_names, self.iter_queued(), self.iter_done()
|
||||
@@ -517,7 +491,7 @@ class LocalCeleryQueue(BaseStashQueue):
|
||||
baseline_revs: Optional[Collection[str]],
|
||||
fetch_refs: bool = False,
|
||||
**kwargs,
|
||||
) -> Dict[str, List["ExpRange"]]:
|
||||
) -> dict[str, list["ExpRange"]]:
|
||||
from dvc.repo import Repo
|
||||
from dvc.repo.experiments.collect import collect_exec_branch
|
||||
from dvc.repo.experiments.serialize import (
|
||||
@@ -526,7 +500,7 @@ class LocalCeleryQueue(BaseStashQueue):
|
||||
LocalExpExecutor,
|
||||
)
|
||||
|
||||
result: Dict[str, List[ExpRange]] = defaultdict(list)
|
||||
result: dict[str, list[ExpRange]] = defaultdict(list)
|
||||
for entry in self.iter_active():
|
||||
if baseline_revs and entry.baseline_rev not in baseline_revs:
|
||||
continue
|
||||
@@ -565,10 +539,8 @@ class LocalCeleryQueue(BaseStashQueue):
|
||||
return result
|
||||
|
||||
def collect_queued_data(
|
||||
self,
|
||||
baseline_revs: Optional[Collection[str]],
|
||||
**kwargs,
|
||||
) -> Dict[str, List["ExpRange"]]:
|
||||
self, baseline_revs: Optional[Collection[str]], **kwargs
|
||||
) -> dict[str, list["ExpRange"]]:
|
||||
from dvc.repo.experiments.collect import collect_rev
|
||||
from dvc.repo.experiments.serialize import (
|
||||
ExpExecutor,
|
||||
@@ -576,7 +548,7 @@ class LocalCeleryQueue(BaseStashQueue):
|
||||
LocalExpExecutor,
|
||||
)
|
||||
|
||||
result: Dict[str, List[ExpRange]] = defaultdict(list)
|
||||
result: dict[str, list[ExpRange]] = defaultdict(list)
|
||||
for entry in self.iter_queued():
|
||||
if baseline_revs and entry.baseline_rev not in baseline_revs:
|
||||
continue
|
||||
@@ -598,7 +570,7 @@ class LocalCeleryQueue(BaseStashQueue):
|
||||
self,
|
||||
baseline_revs: Optional[Collection[str]],
|
||||
**kwargs,
|
||||
) -> Dict[str, List["ExpRange"]]:
|
||||
) -> dict[str, list["ExpRange"]]:
|
||||
from dvc.repo.experiments.collect import collect_rev
|
||||
from dvc.repo.experiments.serialize import (
|
||||
ExpExecutor,
|
||||
@@ -607,7 +579,7 @@ class LocalCeleryQueue(BaseStashQueue):
|
||||
SerializableError,
|
||||
)
|
||||
|
||||
result: Dict[str, List[ExpRange]] = defaultdict(list)
|
||||
result: dict[str, list[ExpRange]] = defaultdict(list)
|
||||
for entry, _ in self.iter_failed():
|
||||
if baseline_revs and entry.baseline_rev not in baseline_revs:
|
||||
continue
|
||||
@@ -632,3 +604,32 @@ class LocalCeleryQueue(BaseStashQueue):
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
def collect_success_executors(
|
||||
self,
|
||||
baseline_revs: Optional[Collection[str]],
|
||||
**kwargs,
|
||||
) -> dict[str, "ExpExecutor"]:
|
||||
"""Map exp refs to any available successful executors."""
|
||||
from dvc.repo.experiments.serialize import ExpExecutor, LocalExpExecutor
|
||||
|
||||
result: dict[str, "ExpExecutor"] = {}
|
||||
for entry, exec_result in self.iter_success():
|
||||
if baseline_revs and entry.baseline_rev not in baseline_revs:
|
||||
continue
|
||||
if not (exec_result and exec_result.ref_info):
|
||||
continue
|
||||
proc_info = self.proc.get(entry.stash_rev)
|
||||
if proc_info:
|
||||
local_exec: Optional[LocalExpExecutor] = LocalExpExecutor(
|
||||
log=proc_info.stdout,
|
||||
pid=proc_info.pid,
|
||||
returncode=proc_info.returncode,
|
||||
task_id=entry.stash_rev,
|
||||
)
|
||||
else:
|
||||
local_exec = None
|
||||
result[str(exec_result.ref_info)] = ExpExecutor(
|
||||
"success", name="dvc-task", local=local_exec
|
||||
)
|
||||
return result
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Collection
|
||||
from collections.abc import Collection
|
||||
|
||||
from dvc.exceptions import DvcException
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import TYPE_CHECKING, Collection, Dict, Iterable, List, Set, Union
|
||||
from collections.abc import Collection, Iterable
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
from dvc.repo.experiments.exceptions import UnresolvedExpNamesError
|
||||
from dvc.repo.experiments.queue.base import QueueDoneResult
|
||||
@@ -20,11 +21,11 @@ def remove_tasks( # noqa: C901, PLR0912
|
||||
"""
|
||||
from celery.result import AsyncResult
|
||||
|
||||
stash_revs: Dict[str, "ExpStashEntry"] = {}
|
||||
failed_stash_revs: List["ExpStashEntry"] = []
|
||||
done_entry_set: Set["QueueEntry"] = set()
|
||||
stash_revs: dict[str, "ExpStashEntry"] = {}
|
||||
failed_stash_revs: list["ExpStashEntry"] = []
|
||||
done_entry_set: set["QueueEntry"] = set()
|
||||
stash_rev_all = celery_queue.stash.stash_revs
|
||||
failed_rev_all: Dict[str, "ExpStashEntry"] = {}
|
||||
failed_rev_all: dict[str, "ExpStashEntry"] = {}
|
||||
if celery_queue.failed_stash:
|
||||
failed_rev_all = celery_queue.failed_stash.stash_revs
|
||||
for entry in queue_entries:
|
||||
@@ -36,20 +37,14 @@ def remove_tasks( # noqa: C901, PLR0912
|
||||
failed_stash_revs.append(failed_rev_all[entry.stash_rev])
|
||||
|
||||
try:
|
||||
for (
|
||||
msg,
|
||||
queue_entry,
|
||||
) in celery_queue._iter_queued():
|
||||
for msg, queue_entry in celery_queue._iter_queued():
|
||||
if queue_entry.stash_rev in stash_revs and msg.delivery_tag:
|
||||
celery_queue.celery.reject(msg.delivery_tag)
|
||||
finally:
|
||||
celery_queue.stash.remove_revs(list(stash_revs.values()))
|
||||
|
||||
try:
|
||||
for (
|
||||
msg,
|
||||
queue_entry,
|
||||
) in celery_queue._iter_processed():
|
||||
for msg, queue_entry in celery_queue._iter_processed():
|
||||
if queue_entry not in done_entry_set:
|
||||
continue
|
||||
task_id = msg.headers["id"]
|
||||
@@ -64,7 +59,7 @@ def remove_tasks( # noqa: C901, PLR0912
|
||||
|
||||
|
||||
def _get_names(entries: Iterable[Union["QueueEntry", "QueueDoneResult"]]):
|
||||
names: List[str] = []
|
||||
names: list[str] = []
|
||||
for entry in entries:
|
||||
if isinstance(entry, QueueDoneResult):
|
||||
if entry.result and entry.result.ref_info:
|
||||
@@ -82,7 +77,7 @@ def celery_clear(
|
||||
queued: bool = False,
|
||||
failed: bool = False,
|
||||
success: bool = False,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
"""Remove entries from the queue.
|
||||
|
||||
Arguments:
|
||||
@@ -94,18 +89,18 @@ def celery_clear(
|
||||
Revisions which were removed.
|
||||
"""
|
||||
|
||||
removed: List[str] = []
|
||||
entry_list: List["QueueEntry"] = []
|
||||
removed: list[str] = []
|
||||
entry_list: list["QueueEntry"] = []
|
||||
if queued:
|
||||
queue_entries: List["QueueEntry"] = list(self.iter_queued())
|
||||
queue_entries: list["QueueEntry"] = list(self.iter_queued())
|
||||
entry_list.extend(queue_entries)
|
||||
removed.extend(_get_names(queue_entries))
|
||||
if failed:
|
||||
failed_tasks: List["QueueDoneResult"] = list(self.iter_failed())
|
||||
failed_tasks: list["QueueDoneResult"] = list(self.iter_failed())
|
||||
entry_list.extend([result.entry for result in failed_tasks])
|
||||
removed.extend(_get_names(failed_tasks))
|
||||
if success:
|
||||
success_tasks: List["QueueDoneResult"] = list(self.iter_success())
|
||||
success_tasks: list["QueueDoneResult"] = list(self.iter_success())
|
||||
entry_list.extend([result.entry for result in success_tasks])
|
||||
removed.extend(_get_names(success_tasks))
|
||||
|
||||
@@ -114,10 +109,7 @@ def celery_clear(
|
||||
return removed
|
||||
|
||||
|
||||
def celery_remove(
|
||||
self: "LocalCeleryQueue",
|
||||
revs: Collection[str],
|
||||
) -> List[str]:
|
||||
def celery_remove(self: "LocalCeleryQueue", revs: Collection[str]) -> list[str]:
|
||||
"""Remove the specified entries from the queue.
|
||||
|
||||
Arguments:
|
||||
@@ -131,9 +123,9 @@ def celery_remove(
|
||||
revs, self.iter_queued(), self.iter_done()
|
||||
)
|
||||
|
||||
remained: List[str] = []
|
||||
removed: List[str] = []
|
||||
entry_to_remove: List["QueueEntry"] = []
|
||||
remained: list[str] = []
|
||||
removed: list[str] = []
|
||||
entry_to_remove: list["QueueEntry"] = []
|
||||
for name, entry in match_results.items():
|
||||
if entry:
|
||||
entry_to_remove.append(entry)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from celery import shared_task
|
||||
from celery.utils.log import get_task_logger
|
||||
@@ -16,7 +16,7 @@ logger = get_task_logger(__name__)
|
||||
|
||||
|
||||
@shared_task
|
||||
def setup_exp(entry_dict: Dict[str, Any]) -> "BaseExecutor":
|
||||
def setup_exp(entry_dict: dict[str, Any]) -> "BaseExecutor":
|
||||
"""Setup an experiment.
|
||||
|
||||
Arguments:
|
||||
@@ -44,8 +44,8 @@ def setup_exp(entry_dict: Dict[str, Any]) -> "BaseExecutor":
|
||||
|
||||
@shared_task
|
||||
def collect_exp(
|
||||
proc_dict: Dict[str, Any], # noqa: ARG001
|
||||
entry_dict: Dict[str, Any],
|
||||
proc_dict: dict[str, Any], # noqa: ARG001
|
||||
entry_dict: dict[str, Any],
|
||||
) -> str:
|
||||
"""Collect results for an experiment.
|
||||
|
||||
@@ -92,8 +92,8 @@ def cleanup_exp(executor: TempDirExecutor, infofile: str) -> None:
|
||||
|
||||
@shared_task
|
||||
def run_exp(
|
||||
entry_dict: Dict[str, Any],
|
||||
copy_paths: Optional[List[str]] = None,
|
||||
entry_dict: dict[str, Any],
|
||||
copy_paths: Optional[list[str]] = None,
|
||||
message: Optional[str] = None,
|
||||
) -> None:
|
||||
"""Run a full experiment.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from typing import TYPE_CHECKING, Collection, Dict, Generator, List, Optional
|
||||
from collections.abc import Collection, Generator
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from funcy import first
|
||||
|
||||
@@ -96,11 +97,11 @@ class TempDirQueue(WorkspaceQueue):
|
||||
self,
|
||||
entry: QueueEntry,
|
||||
executor: "BaseExecutor",
|
||||
copy_paths: Optional[List[str]] = None,
|
||||
copy_paths: Optional[list[str]] = None,
|
||||
message: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> Dict[str, Dict[str, str]]:
|
||||
results: Dict[str, Dict[str, str]] = defaultdict(dict)
|
||||
) -> dict[str, dict[str, str]]:
|
||||
results: dict[str, dict[str, str]] = defaultdict(dict)
|
||||
exec_name = self._EXEC_NAME or entry.stash_rev
|
||||
infofile = self.get_infofile_path(exec_name)
|
||||
try:
|
||||
@@ -135,7 +136,7 @@ class TempDirQueue(WorkspaceQueue):
|
||||
exp: "Experiments",
|
||||
executor: "BaseExecutor",
|
||||
exec_result: "ExecutorResult",
|
||||
) -> Dict[str, str]:
|
||||
) -> dict[str, str]:
|
||||
return BaseStashQueue.collect_executor(exp, executor, exec_result)
|
||||
|
||||
def collect_active_data(
|
||||
@@ -143,7 +144,7 @@ class TempDirQueue(WorkspaceQueue):
|
||||
baseline_revs: Optional[Collection[str]],
|
||||
fetch_refs: bool = False,
|
||||
**kwargs,
|
||||
) -> Dict[str, List["ExpRange"]]:
|
||||
) -> dict[str, list["ExpRange"]]:
|
||||
from dvc.repo import Repo
|
||||
from dvc.repo.experiments.collect import collect_exec_branch
|
||||
from dvc.repo.experiments.serialize import (
|
||||
@@ -152,7 +153,7 @@ class TempDirQueue(WorkspaceQueue):
|
||||
LocalExpExecutor,
|
||||
)
|
||||
|
||||
result: Dict[str, List[ExpRange]] = defaultdict(list)
|
||||
result: dict[str, list[ExpRange]] = defaultdict(list)
|
||||
for entry in self.iter_active():
|
||||
if baseline_revs and entry.baseline_rev not in baseline_revs:
|
||||
continue
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import TYPE_CHECKING, Dict, List
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from scmrepo.exceptions import SCMError
|
||||
|
||||
@@ -16,18 +16,14 @@ if TYPE_CHECKING:
|
||||
from .base import BaseStashQueue
|
||||
|
||||
|
||||
def get_remote_executor_refs(scm: "Git", remote_url: str) -> List[str]:
|
||||
def get_remote_executor_refs(scm: "Git", remote_url: str) -> list[str]:
|
||||
"""Get result list refs from a remote repository
|
||||
|
||||
Args:
|
||||
remote_url : remote executor's url
|
||||
"""
|
||||
refs = []
|
||||
for ref in iter_remote_refs(
|
||||
scm,
|
||||
remote_url,
|
||||
base=EXPS_NAMESPACE,
|
||||
):
|
||||
for ref in iter_remote_refs(scm, remote_url, base=EXPS_NAMESPACE):
|
||||
if not ref.startswith(EXEC_NAMESPACE) and ref != EXPS_STASH:
|
||||
refs.append(ref)
|
||||
return refs
|
||||
@@ -35,7 +31,7 @@ def get_remote_executor_refs(scm: "Git", remote_url: str) -> List[str]:
|
||||
|
||||
def fetch_running_exp_from_temp_dir(
|
||||
queue: "BaseStashQueue", rev: str, fetch_refs: bool
|
||||
) -> Dict[str, Dict]:
|
||||
) -> dict[str, dict]:
|
||||
"""Fetch status of running exps out of current working directory
|
||||
|
||||
Args:
|
||||
@@ -50,7 +46,7 @@ def fetch_running_exp_from_temp_dir(
|
||||
from dvc.scm import InvalidRemoteSCMRepo
|
||||
from dvc.utils.serialize import load_json
|
||||
|
||||
result: Dict[str, Dict] = {}
|
||||
result: dict[str, dict] = {}
|
||||
infofile = queue.get_infofile_path(rev)
|
||||
try:
|
||||
info = ExecutorInfo.from_dict(load_json(infofile))
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import json
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from typing import TYPE_CHECKING, Collection, Dict, Generator, List, Optional
|
||||
from collections.abc import Collection, Generator
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
import psutil
|
||||
from funcy import first
|
||||
@@ -82,9 +83,9 @@ class WorkspaceQueue(BaseStashQueue):
|
||||
raise NotImplementedError
|
||||
|
||||
def reproduce(
|
||||
self, copy_paths: Optional[List[str]] = None, message: Optional[str] = None
|
||||
) -> Dict[str, Dict[str, str]]:
|
||||
results: Dict[str, Dict[str, str]] = defaultdict(dict)
|
||||
self, copy_paths: Optional[list[str]] = None, message: Optional[str] = None
|
||||
) -> dict[str, dict[str, str]]:
|
||||
results: dict[str, dict[str, str]] = defaultdict(dict)
|
||||
try:
|
||||
while True:
|
||||
entry, executor = self.get()
|
||||
@@ -99,11 +100,11 @@ class WorkspaceQueue(BaseStashQueue):
|
||||
|
||||
def _reproduce_entry(
|
||||
self, entry: QueueEntry, executor: "BaseExecutor", **kwargs
|
||||
) -> Dict[str, Dict[str, str]]:
|
||||
) -> dict[str, dict[str, str]]:
|
||||
kwargs.pop("copy_paths", None)
|
||||
from dvc_task.proc.process import ProcessInfo
|
||||
|
||||
results: Dict[str, Dict[str, str]] = defaultdict(dict)
|
||||
results: dict[str, dict[str, str]] = defaultdict(dict)
|
||||
exec_name = self._EXEC_NAME or entry.stash_rev
|
||||
proc_info = ProcessInfo(os.getpid(), None, None, None, None)
|
||||
proc_info_path = self._proc_info_path(exec_name)
|
||||
@@ -162,8 +163,8 @@ class WorkspaceQueue(BaseStashQueue):
|
||||
exp: "Experiments",
|
||||
executor: "BaseExecutor", # noqa: ARG004
|
||||
exec_result: "ExecutorResult",
|
||||
) -> Dict[str, str]:
|
||||
results: Dict[str, str] = {}
|
||||
) -> dict[str, str]:
|
||||
results: dict[str, str] = {}
|
||||
exp_rev = exp.scm.get_ref(EXEC_BRANCH)
|
||||
if exp_rev:
|
||||
assert exec_result.exp_hash
|
||||
@@ -181,12 +182,7 @@ class WorkspaceQueue(BaseStashQueue):
|
||||
def shutdown(self, kill: bool = False):
|
||||
raise NotImplementedError
|
||||
|
||||
def logs(
|
||||
self,
|
||||
rev: str,
|
||||
encoding: Optional[str] = None,
|
||||
follow: bool = False,
|
||||
):
|
||||
def logs(self, rev: str, encoding: Optional[str] = None, follow: bool = False):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_running_exp(self) -> Optional[str]:
|
||||
@@ -207,7 +203,7 @@ class WorkspaceQueue(BaseStashQueue):
|
||||
baseline_revs: Optional[Collection[str]],
|
||||
fetch_refs: bool = False, # noqa: ARG002
|
||||
**kwargs,
|
||||
) -> Dict[str, List["ExpRange"]]:
|
||||
) -> dict[str, list["ExpRange"]]:
|
||||
from dvc.repo.experiments.collect import collect_exec_branch
|
||||
from dvc.repo.experiments.serialize import (
|
||||
ExpExecutor,
|
||||
@@ -215,7 +211,7 @@ class WorkspaceQueue(BaseStashQueue):
|
||||
LocalExpExecutor,
|
||||
)
|
||||
|
||||
result: Dict[str, List[ExpRange]] = defaultdict(list)
|
||||
result: dict[str, list[ExpRange]] = defaultdict(list)
|
||||
pid = self._active_pid
|
||||
if pid is None:
|
||||
return result
|
||||
@@ -248,12 +244,12 @@ class WorkspaceQueue(BaseStashQueue):
|
||||
self,
|
||||
baseline_revs: Optional[Collection[str]],
|
||||
**kwargs,
|
||||
) -> Dict[str, List["ExpRange"]]:
|
||||
) -> dict[str, list["ExpRange"]]:
|
||||
raise NotImplementedError
|
||||
|
||||
def collect_failed_data(
|
||||
self,
|
||||
baseline_revs: Optional[Collection[str]],
|
||||
**kwargs,
|
||||
) -> Dict[str, List["ExpRange"]]:
|
||||
) -> dict[str, list["ExpRange"]]:
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Union
|
||||
from collections.abc import Iterable
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
|
||||
from dvc.log import logger
|
||||
from dvc.repo import locked
|
||||
@@ -23,14 +24,14 @@ logger = logger.getChild(__name__)
|
||||
@scm_context
|
||||
def remove( # noqa: C901, PLR0912
|
||||
repo: "Repo",
|
||||
exp_names: Union[None, str, List[str]] = None,
|
||||
rev: Optional[Union[List[str], str]] = None,
|
||||
exp_names: Union[None, str, list[str]] = None,
|
||||
rev: Optional[Union[list[str], str]] = None,
|
||||
all_commits: bool = False,
|
||||
num: int = 1,
|
||||
queue: bool = False,
|
||||
git_remote: Optional[str] = None,
|
||||
) -> List[str]:
|
||||
removed: List[str] = []
|
||||
) -> list[str]:
|
||||
removed: list[str] = []
|
||||
if not any([exp_names, queue, all_commits, rev]):
|
||||
return removed
|
||||
celery_queue: "LocalCeleryQueue" = repo.experiments.celery_queue
|
||||
@@ -40,13 +41,13 @@ def remove( # noqa: C901, PLR0912
|
||||
|
||||
assert isinstance(repo.scm, Git)
|
||||
|
||||
exp_ref_list: List["ExpRefInfo"] = []
|
||||
queue_entry_list: List["QueueEntry"] = []
|
||||
exp_ref_list: list["ExpRefInfo"] = []
|
||||
queue_entry_list: list["QueueEntry"] = []
|
||||
if exp_names:
|
||||
results: Dict[
|
||||
results: dict[
|
||||
str, "ExpRefAndQueueEntry"
|
||||
] = celery_queue.get_ref_and_entry_by_names(exp_names, git_remote)
|
||||
remained: List[str] = []
|
||||
remained: list[str] = []
|
||||
for name, result in results.items():
|
||||
if not result.exp_ref_info and not result.queue_entry:
|
||||
remained.append(name)
|
||||
@@ -87,13 +88,13 @@ def remove( # noqa: C901, PLR0912
|
||||
|
||||
def _resolve_exp_by_baseline(
|
||||
repo: "Repo",
|
||||
rev: List[str],
|
||||
rev: list[str],
|
||||
num: int,
|
||||
git_remote: Optional[str] = None,
|
||||
) -> Dict[str, "ExpRefInfo"]:
|
||||
) -> dict[str, "ExpRefInfo"]:
|
||||
assert isinstance(repo.scm, Git)
|
||||
|
||||
commit_ref_dict: Dict[str, "ExpRefInfo"] = {}
|
||||
commit_ref_dict: dict[str, "ExpRefInfo"] = {}
|
||||
rev_dict = iter_revs(repo.scm, rev, num)
|
||||
rev_set = set(rev_dict.keys())
|
||||
ref_info_dict = exp_refs_by_baseline(repo.scm, rev_set, git_remote)
|
||||
@@ -105,7 +106,7 @@ def _resolve_exp_by_baseline(
|
||||
|
||||
def _remove_commited_exps(
|
||||
scm: "Git", exp_refs_list: Iterable["ExpRefInfo"], remote: Optional[str]
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
if remote:
|
||||
from dvc.scm import TqdmGit
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Union
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
|
||||
from dvc.log import logger
|
||||
from dvc.repo.experiments.exceptions import (
|
||||
@@ -22,16 +22,16 @@ def rename(
|
||||
exp_name: Union[str, None] = None,
|
||||
git_remote: Optional[str] = None,
|
||||
force: bool = False,
|
||||
) -> Union[List[str], None]:
|
||||
renamed: List[str] = []
|
||||
remained: List[str] = []
|
||||
) -> Union[list[str], None]:
|
||||
renamed: list[str] = []
|
||||
remained: list[str] = []
|
||||
assert isinstance(repo.scm, Git)
|
||||
|
||||
if exp_name == new_name:
|
||||
return None
|
||||
|
||||
if exp_name:
|
||||
results: Dict[str, Union[ExpRefInfo, None]] = resolve_name(
|
||||
results: dict[str, Union[ExpRefInfo, None]] = resolve_name(
|
||||
scm=repo.scm, exp_names=exp_name, git_remote=git_remote
|
||||
)
|
||||
for name, result in results.items():
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import Dict, Iterable, Optional
|
||||
from collections.abc import Iterable
|
||||
from typing import Optional
|
||||
|
||||
from dvc.dependency.param import ParamsDependency
|
||||
from dvc.exceptions import InvalidArgumentError
|
||||
@@ -22,7 +23,7 @@ def run( # noqa: C901, PLR0912
|
||||
copy_paths: Optional[Iterable[str]] = None,
|
||||
message: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> Dict[str, str]:
|
||||
) -> dict[str, str]:
|
||||
"""Reproduce the specified targets as an experiment.
|
||||
|
||||
Accepts the same additional kwargs as Repo.reproduce.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
from typing import TYPE_CHECKING, List, Optional
|
||||
from collections.abc import Iterable
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from funcy import first
|
||||
|
||||
@@ -14,9 +15,11 @@ logger = logger.getChild(__name__)
|
||||
|
||||
def save(
|
||||
repo: "Repo",
|
||||
targets: Optional[Iterable[str]] = None,
|
||||
name: Optional[str] = None,
|
||||
recursive: bool = False,
|
||||
force: bool = False,
|
||||
include_untracked: Optional[List[str]] = None,
|
||||
include_untracked: Optional[list[str]] = None,
|
||||
message: Optional[str] = None,
|
||||
) -> Optional[str]:
|
||||
"""Save the current workspace status as an experiment.
|
||||
@@ -32,6 +35,8 @@ def save(
|
||||
try:
|
||||
save_result = executor.save(
|
||||
executor.info,
|
||||
targets=targets,
|
||||
recursive=recursive,
|
||||
force=force,
|
||||
include_untracked=include_untracked,
|
||||
message=message,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import json
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Literal, Optional
|
||||
from typing import TYPE_CHECKING, Any, Literal, Optional
|
||||
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.repo.metrics.show import _gather_metrics
|
||||
@@ -30,11 +31,11 @@ class SerializableExp:
|
||||
|
||||
rev: str
|
||||
timestamp: Optional[datetime] = None
|
||||
params: Dict[str, "FileResult"] = field(default_factory=dict)
|
||||
metrics: Dict[str, "FileResult"] = field(default_factory=dict)
|
||||
deps: Dict[str, "ExpDep"] = field(default_factory=dict)
|
||||
outs: Dict[str, "ExpOut"] = field(default_factory=dict)
|
||||
meta: Dict[str, Any] = field(default_factory=dict)
|
||||
params: dict[str, "FileResult"] = field(default_factory=dict)
|
||||
metrics: dict[str, "FileResult"] = field(default_factory=dict)
|
||||
deps: dict[str, "ExpDep"] = field(default_factory=dict)
|
||||
outs: dict[str, "ExpOut"] = field(default_factory=dict)
|
||||
meta: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_repo(
|
||||
@@ -83,7 +84,7 @@ class SerializableExp:
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def dumpd(self) -> Dict[str, Any]:
|
||||
def dumpd(self) -> dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
def as_bytes(self) -> bytes:
|
||||
@@ -112,14 +113,14 @@ class SerializableExp:
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ExpDep:
|
||||
hash: Optional[str] # noqa: A003
|
||||
hash: Optional[str]
|
||||
size: Optional[int]
|
||||
nfiles: Optional[int]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ExpOut:
|
||||
hash: Optional[str] # noqa: A003
|
||||
hash: Optional[str]
|
||||
size: Optional[int]
|
||||
nfiles: Optional[int]
|
||||
use_cache: bool
|
||||
@@ -129,9 +130,9 @@ class ExpOut:
|
||||
@dataclass(frozen=True)
|
||||
class SerializableError:
|
||||
msg: str
|
||||
type: str = "" # noqa: A003
|
||||
type: str = ""
|
||||
|
||||
def dumpd(self) -> Dict[str, Any]:
|
||||
def dumpd(self) -> dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
def as_bytes(self) -> bytes:
|
||||
@@ -154,15 +155,15 @@ class ExpState:
|
||||
name: Optional[str] = None
|
||||
data: Optional[SerializableExp] = None
|
||||
error: Optional[SerializableError] = None
|
||||
experiments: Optional[List["ExpRange"]] = None
|
||||
experiments: Optional[list["ExpRange"]] = None
|
||||
|
||||
def dumpd(self) -> Dict[str, Any]:
|
||||
def dumpd(self) -> dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExpRange:
|
||||
revs: List["ExpState"]
|
||||
revs: list["ExpState"]
|
||||
executor: Optional["ExpExecutor"] = None
|
||||
name: Optional[str] = None
|
||||
|
||||
@@ -175,7 +176,7 @@ class ExpRange:
|
||||
def __getitem__(self, index: int) -> "ExpState":
|
||||
return self.revs[index]
|
||||
|
||||
def dumpd(self) -> Dict[str, Any]:
|
||||
def dumpd(self) -> dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
from collections import Counter, defaultdict
|
||||
from collections.abc import Iterable, Iterator, Mapping
|
||||
from datetime import date, datetime
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Literal,
|
||||
Mapping,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Set,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Any, Literal, NamedTuple, Optional, Union
|
||||
|
||||
from dvc.exceptions import InvalidArgumentError
|
||||
from dvc.log import logger
|
||||
@@ -36,7 +23,7 @@ logger = logger.getChild(__name__)
|
||||
|
||||
def show(
|
||||
repo: "Repo",
|
||||
revs: Union[List[str], str, None] = None,
|
||||
revs: Union[list[str], str, None] = None,
|
||||
all_branches: bool = False,
|
||||
all_tags: bool = False,
|
||||
all_commits: bool = False,
|
||||
@@ -45,7 +32,7 @@ def show(
|
||||
hide_failed: bool = False,
|
||||
sha_only: bool = False,
|
||||
**kwargs,
|
||||
) -> List["ExpState"]:
|
||||
) -> list["ExpState"]:
|
||||
return collect(
|
||||
repo,
|
||||
revs=revs,
|
||||
@@ -65,7 +52,7 @@ def tabulate(
|
||||
fill_value: Optional[str] = "-",
|
||||
error_value: str = "!",
|
||||
**kwargs,
|
||||
) -> Tuple["TabularData", Dict[str, Iterable[str]]]:
|
||||
) -> tuple["TabularData", dict[str, Iterable[str]]]:
|
||||
"""Return table data for experiments.
|
||||
|
||||
Returns:
|
||||
@@ -90,7 +77,7 @@ def tabulate(
|
||||
"State",
|
||||
"Executor",
|
||||
]
|
||||
names = {**metrics_names, **params_names}
|
||||
names = metrics_names | params_names
|
||||
counter = Counter(flatten_list([list(a.keys()) for a in names.values()]))
|
||||
counter.update(headers)
|
||||
metrics_headers = _normalize_headers(metrics_names, counter)
|
||||
@@ -112,7 +99,7 @@ def tabulate(
|
||||
**kwargs,
|
||||
)
|
||||
)
|
||||
data_headers: Dict[str, Iterable[str]] = {
|
||||
data_headers: dict[str, Iterable[str]] = {
|
||||
"metrics": metrics_headers,
|
||||
"params": params_headers,
|
||||
"deps": deps_names,
|
||||
@@ -128,9 +115,9 @@ def _build_rows(
|
||||
sort_by: Optional[str] = None,
|
||||
sort_order: Optional[Literal["asc", "desc"]] = None,
|
||||
**kwargs,
|
||||
) -> Iterator[Tuple["CellT", ...]]:
|
||||
) -> Iterator[tuple["CellT", ...]]:
|
||||
for baseline in baseline_states:
|
||||
row: Dict[str, "CellT"] = {k: fill_value for k in all_headers}
|
||||
row: dict[str, "CellT"] = dict.fromkeys(all_headers, fill_value)
|
||||
row["Experiment"] = ""
|
||||
if baseline.name:
|
||||
row["rev"] = baseline.name
|
||||
@@ -177,10 +164,10 @@ def _sort_column( # noqa: C901
|
||||
sort_by: str,
|
||||
metric_names: Mapping[str, Iterable[str]],
|
||||
param_names: Mapping[str, Iterable[str]],
|
||||
) -> Tuple[str, str, str]:
|
||||
) -> tuple[str, str, str]:
|
||||
sep = ":"
|
||||
parts = sort_by.split(sep)
|
||||
matches: Set[Tuple[str, str, str]] = set()
|
||||
matches: set[tuple[str, str, str]] = set()
|
||||
|
||||
for split_num in range(len(parts)):
|
||||
path = sep.join(parts[:split_num])
|
||||
@@ -217,7 +204,7 @@ def _sort_exp(
|
||||
sort_name: str,
|
||||
typ: str,
|
||||
reverse: bool,
|
||||
) -> List["ExpRange"]:
|
||||
) -> list["ExpRange"]:
|
||||
from funcy import first
|
||||
|
||||
def _sort(exp_range: "ExpRange"):
|
||||
@@ -238,14 +225,14 @@ def _exp_range_rows(
|
||||
fill_value: Optional[str],
|
||||
is_base: bool = False,
|
||||
**kwargs,
|
||||
) -> Iterator[Tuple["CellT", ...]]:
|
||||
) -> Iterator[tuple["CellT", ...]]:
|
||||
from funcy import first
|
||||
|
||||
if len(exp_range.revs) > 1:
|
||||
logger.debug("Returning tip commit for legacy checkpoint exp")
|
||||
exp = first(exp_range.revs)
|
||||
if exp:
|
||||
row: Dict[str, "CellT"] = {k: fill_value for k in all_headers}
|
||||
row: dict[str, "CellT"] = dict.fromkeys(all_headers, fill_value)
|
||||
row["Experiment"] = exp.name or ""
|
||||
row["rev"] = exp.rev[:7] if Git.is_sha(exp.rev) else exp.rev
|
||||
row["typ"] = "branch_base" if is_base else "branch_commit"
|
||||
@@ -274,12 +261,12 @@ def _data_cells(
|
||||
error_value: str = "!",
|
||||
precision: Optional[int] = None,
|
||||
**kwargs,
|
||||
) -> Iterator[Tuple[str, "CellT"]]:
|
||||
) -> Iterator[tuple[str, "CellT"]]:
|
||||
def _d_cells(
|
||||
d: Mapping[str, Any],
|
||||
names: Mapping[str, Iterable[str]],
|
||||
headers: Iterable[str],
|
||||
) -> Iterator[Tuple[str, "CellT"]]:
|
||||
) -> Iterator[tuple[str, "CellT"]]:
|
||||
from dvc.compare import _format_field, with_value
|
||||
|
||||
for fname, data in d.items():
|
||||
@@ -329,9 +316,9 @@ def format_time(
|
||||
class _DataNames(NamedTuple):
|
||||
# NOTE: we use nested dict instead of set for metrics/params names to
|
||||
# preserve key ordering
|
||||
metrics: Dict[str, Dict[str, Any]]
|
||||
params: Dict[str, Dict[str, Any]]
|
||||
deps: Set[str]
|
||||
metrics: dict[str, dict[str, Any]]
|
||||
params: dict[str, dict[str, Any]]
|
||||
deps: set[str]
|
||||
|
||||
@property
|
||||
def sorted_deps(self):
|
||||
@@ -339,7 +326,7 @@ class _DataNames(NamedTuple):
|
||||
|
||||
def update(self, other: "_DataNames"):
|
||||
def _update_d(
|
||||
d: Dict[str, Dict[str, Any]], other_d: Mapping[str, Mapping[str, Any]]
|
||||
d: dict[str, dict[str, Any]], other_d: Mapping[str, Mapping[str, Any]]
|
||||
):
|
||||
for k, v in other_d.items():
|
||||
if k in d:
|
||||
@@ -355,7 +342,7 @@ class _DataNames(NamedTuple):
|
||||
def _collect_names(exp_states: Iterable["ExpState"]) -> _DataNames:
|
||||
result = _DataNames(defaultdict(dict), defaultdict(dict), set())
|
||||
|
||||
def _collect_d(result_d: Dict[str, Dict[str, Any]], data_d: Dict[str, Any]):
|
||||
def _collect_d(result_d: dict[str, dict[str, Any]], data_d: dict[str, Any]):
|
||||
for path, item in data_d.items():
|
||||
item = item.get("data", {})
|
||||
if isinstance(item, dict):
|
||||
@@ -376,7 +363,7 @@ def _collect_names(exp_states: Iterable["ExpState"]) -> _DataNames:
|
||||
|
||||
def _normalize_headers(
|
||||
names: Mapping[str, Mapping[str, Any]], count: Mapping[str, int]
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
return [
|
||||
name if count[name] == 1 else f"{path}:{name}"
|
||||
for path in names
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import re
|
||||
from collections.abc import Iterable, Iterator
|
||||
from contextlib import contextmanager
|
||||
from typing import Dict, Iterable, Iterator, NamedTuple, Optional
|
||||
from typing import NamedTuple, Optional
|
||||
|
||||
from scmrepo.git import Stash
|
||||
|
||||
@@ -42,7 +43,7 @@ class ExpStash(Stash):
|
||||
)
|
||||
|
||||
@property
|
||||
def stash_revs(self) -> Dict[str, ExpStashEntry]:
|
||||
def stash_revs(self) -> dict[str, ExpStashEntry]:
|
||||
revs = {}
|
||||
for i, entry in enumerate(self):
|
||||
msg = entry.message.decode("utf-8").strip()
|
||||
@@ -110,27 +111,19 @@ class ApplyStash(Stash):
|
||||
)
|
||||
|
||||
@property
|
||||
def stash_revs(self) -> Dict[str, ApplyStashEntry]:
|
||||
def stash_revs(self) -> dict[str, ApplyStashEntry]:
|
||||
revs = {}
|
||||
for i, entry in enumerate(self):
|
||||
msg = entry.message.decode("utf-8").strip()
|
||||
m = self.MESSAGE_RE.match(msg)
|
||||
if m:
|
||||
revs[entry.new_sha.decode("utf-8")] = ApplyStashEntry(
|
||||
i,
|
||||
m.group("head_rev"),
|
||||
m.group("rev"),
|
||||
m.group("name"),
|
||||
i, m.group("head_rev"), m.group("rev"), m.group("name")
|
||||
)
|
||||
return revs
|
||||
|
||||
@classmethod
|
||||
def format_message(
|
||||
cls,
|
||||
head_rev: str,
|
||||
rev: str,
|
||||
name: Optional[str] = None,
|
||||
) -> str:
|
||||
def format_message(cls, head_rev: str, rev: str, name: Optional[str] = None) -> str:
|
||||
return cls.MESSAGE_FORMAT.format(
|
||||
head_rev=head_rev, rev=rev, name=name if name else ""
|
||||
)
|
||||
|
||||
@@ -2,20 +2,9 @@ import os
|
||||
import random
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from collections.abc import Generator, Iterable, Mapping
|
||||
from functools import wraps
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
Dict,
|
||||
Generator,
|
||||
Iterable,
|
||||
List,
|
||||
Mapping,
|
||||
Optional,
|
||||
Set,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Callable, Optional, Union
|
||||
|
||||
from dvc.exceptions import InvalidArgumentError
|
||||
from dvc.repo.experiments.exceptions import AmbiguousExpRefInfo
|
||||
@@ -43,8 +32,8 @@ EXEC_PID_DIR = "run"
|
||||
|
||||
def get_exp_rwlock(
|
||||
repo: "Repo",
|
||||
reads: Optional[List[str]] = None,
|
||||
writes: Optional[List[str]] = None,
|
||||
reads: Optional[list[str]] = None,
|
||||
writes: Optional[list[str]] = None,
|
||||
):
|
||||
reads = reads or []
|
||||
writes = writes or []
|
||||
@@ -109,9 +98,9 @@ def exp_refs_by_rev(scm: "Git", rev: str) -> Generator[ExpRefInfo, None, None]:
|
||||
|
||||
def exp_refs_by_baseline(
|
||||
scm: "Git",
|
||||
revs: Optional[Set[str]] = None,
|
||||
revs: Optional[set[str]] = None,
|
||||
url: Optional[str] = None,
|
||||
) -> Mapping[str, List[ExpRefInfo]]:
|
||||
) -> Mapping[str, list[ExpRefInfo]]:
|
||||
"""Iterate over all experiment refs with the specified baseline."""
|
||||
all_exp_refs = exp_refs(scm, url)
|
||||
result = defaultdict(list)
|
||||
@@ -137,7 +126,7 @@ def iter_remote_refs(scm: "Git", url: str, base: Optional[str] = None, **kwargs)
|
||||
def push_refspec(
|
||||
scm: "Git",
|
||||
url: str,
|
||||
push_list=List[Tuple[Optional[str], str]],
|
||||
push_list=list[tuple[Optional[str], str]],
|
||||
force: bool = False,
|
||||
on_diverged: Optional[Callable[[str, str], bool]] = None,
|
||||
**kwargs,
|
||||
@@ -183,8 +172,8 @@ def remote_exp_refs(scm: "Git", url: str) -> Generator[ExpRefInfo, None, None]:
|
||||
|
||||
|
||||
def exp_refs_by_names(
|
||||
scm: "Git", names: Set[str], url: Optional[str] = None
|
||||
) -> Dict[str, List[ExpRefInfo]]:
|
||||
scm: "Git", names: set[str], url: Optional[str] = None
|
||||
) -> dict[str, list[ExpRefInfo]]:
|
||||
"""Iterate over all experiment refs matching the specified names."""
|
||||
resolve_results = defaultdict(list)
|
||||
ref_info_gen = exp_refs(scm, url)
|
||||
@@ -210,7 +199,7 @@ def exp_commits(
|
||||
scm: "Git", ref_infos: Optional[Iterable[ExpRefInfo]] = None
|
||||
) -> Iterable[str]:
|
||||
"""Iterate over all experiment commits."""
|
||||
shas: Set["str"] = set()
|
||||
shas: set["str"] = set()
|
||||
refs = ref_infos if ref_infos else exp_refs(scm)
|
||||
for ref_info in refs:
|
||||
shas.update(scm.branch_revs(str(ref_info), ref_info.baseline_sha))
|
||||
@@ -242,7 +231,7 @@ def resolve_name(
|
||||
scm: "Git",
|
||||
exp_names: Union[Iterable[str], str],
|
||||
git_remote: Optional[str] = None,
|
||||
) -> Dict[str, Optional[ExpRefInfo]]:
|
||||
) -> dict[str, Optional[ExpRefInfo]]:
|
||||
"""find the ref_info of specified names."""
|
||||
if isinstance(exp_names, str):
|
||||
exp_names = [exp_names]
|
||||
@@ -288,12 +277,7 @@ def fetch_all_exps(scm: "Git", url: str, progress: Optional[Callable] = None, **
|
||||
for ref in iter_remote_refs(scm, url, base=EXPS_NAMESPACE)
|
||||
if not _ignore_ref(ref)
|
||||
]
|
||||
scm.fetch_refspecs(
|
||||
url,
|
||||
refspecs,
|
||||
progress=progress,
|
||||
**kwargs,
|
||||
)
|
||||
scm.fetch_refspecs(url, refspecs, progress=progress, **kwargs)
|
||||
|
||||
|
||||
def gen_random_name():
|
||||
@@ -338,7 +322,7 @@ def to_studio_params(dvc_params):
|
||||
"params.yaml": {"foo": 1}
|
||||
}
|
||||
"""
|
||||
result: Dict = {}
|
||||
result: dict = {}
|
||||
if not dvc_params:
|
||||
return result
|
||||
for rev_data in dvc_params.values():
|
||||
@@ -353,7 +337,7 @@ def describe(
|
||||
revs: Iterable[str],
|
||||
logger,
|
||||
refs: Optional[Iterable[str]] = None,
|
||||
) -> Dict[str, Optional[str]]:
|
||||
) -> dict[str, Optional[str]]:
|
||||
"""Describe revisions using a tag, branch.
|
||||
|
||||
The first matching name will be returned for each rev. Names are preferred in this
|
||||
@@ -390,7 +374,7 @@ def describe(
|
||||
if is_branch and rev not in branches:
|
||||
branches[rev] = ref[len("refs/heads/") :]
|
||||
|
||||
names: Dict[str, Optional[str]] = {}
|
||||
names: dict[str, Optional[str]] = {}
|
||||
for rev in revs:
|
||||
if rev == head_rev and head_branch:
|
||||
names[rev] = head_branch
|
||||
|
||||
+5
-9
@@ -1,4 +1,4 @@
|
||||
from typing import TYPE_CHECKING, List, Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dvc.exceptions import DownloadError
|
||||
from dvc.log import logger
|
||||
@@ -163,11 +163,7 @@ def fetch( # noqa: PLR0913
|
||||
tokenize(sorted(idx.data_tree.hash_info.value for idx in indexes.values())),
|
||||
)
|
||||
|
||||
with ui.progress(
|
||||
desc="Collecting",
|
||||
unit="entry",
|
||||
leave=True,
|
||||
) as pb:
|
||||
with ui.progress(desc="Collecting", unit="entry", leave=True) as pb:
|
||||
data = collect(
|
||||
[idx.data["repo"] for idx in indexes.values()],
|
||||
"remote",
|
||||
@@ -205,9 +201,9 @@ def fetch( # noqa: PLR0913
|
||||
return transferred_count
|
||||
|
||||
|
||||
def _log_unversioned(data: List["DataIndex"]) -> Tuple[List["DataIndex"], int]:
|
||||
ret: List["DataIndex"] = []
|
||||
unversioned: List[str] = []
|
||||
def _log_unversioned(data: list["DataIndex"]) -> tuple[list["DataIndex"], int]:
|
||||
ret: list["DataIndex"] = []
|
||||
unversioned: list[str] = []
|
||||
for fs_index in data:
|
||||
remote = fs_index.storage_map[()].remote
|
||||
if not isinstance(remote, FileStorage) or not remote.fs.version_aware:
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
from typing import TYPE_CHECKING, List, Optional
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from dvc.exceptions import InvalidArgumentError
|
||||
from dvc.log import logger
|
||||
@@ -61,7 +61,7 @@ def gc( # noqa: PLR0913, C901
|
||||
all_experiments: bool = False,
|
||||
force: bool = False,
|
||||
jobs: Optional[int] = None,
|
||||
repos: Optional[List[str]] = None,
|
||||
repos: Optional[list[str]] = None,
|
||||
workspace: bool = False,
|
||||
commit_date: Optional[str] = None,
|
||||
rev: Optional[str] = None,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user