Compare commits

...
38 Commits
Author SHA1 Message Date
Peter Rowlands c72e1fb0b8 repo.imports: remove unused code 2024-02-20 15:08:07 +09:00
pre-commit-ci[bot] 9fbe5d929d [pre-commit.ci] pre-commit autoupdate (#10310)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.2.1 → v0.2.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.1...v0.2.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-02-20 09:09:04 +05:45
Ruslan Kuprieiev 0635b0ca15 index: check dep.hash_name for imports 2024-02-19 15:16:57 +02:00
Ruslan Kuprieiev 06abf73c22 deps: bump dvc-data >=3.13.0 2024-02-15 23:18:49 +02:00
pre-commit-ci[bot] 5e2c441d28 [pre-commit.ci] pre-commit autoupdate (#10301)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.2.0 → v0.2.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.0...v0.2.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-02-15 09:03:30 +05:45
skshetry 6efd699267 deps: bump dvc-studio-client to >=0.19,<1 (#10298) 2024-02-10 09:56:09 +05:45
skshetry f639ab50ab simplify message in studio login/logout flow (#10297) 2024-02-09 23:02:35 +05:45
dependabot[bot]andskshetry 953ae56536 build(deps-dev): update dvc-data requirement from <3.11,>=3.10 to >=3.10,<3.12 (#10295)
* build(deps-dev): update dvc-data requirement

Updates the requirements on [dvc-data](https://github.com/iterative/dvc-data) to permit the latest version.
- [Release notes](https://github.com/iterative/dvc-data/releases)
- [Commits](https://github.com/iterative/dvc-data/compare/3.10.0...3.11.0)

---
updated-dependencies:
- dependency-name: dvc-data
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>

* bump dvc-ssh to >=4,<5.

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: skshetry <[email protected]>
2024-02-08 18:00:37 +05:45
dependabot[bot] ac0397e536 build(deps-dev): update pytest requirement from <8,>=7 to >=7,<9 (#10274)
Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.0.0...8.0.0)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-08 17:20:10 +05:45
skshetry b243ccab1f tests: remove pytest-lazy-fixture (#10294)
Since it's no longer being maintained. See https://github.com/TvoroG/pytest-lazy-fixture/issues/65.
2024-02-08 17:10:58 +05:45
dependabot[bot] c31a5d9df9 build(deps): bump pre-commit/action from 3.0.0 to 3.0.1 (#10290)
Bumps [pre-commit/action](https://github.com/pre-commit/action) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/pre-commit/action/releases)
- [Commits](https://github.com/pre-commit/action/compare/v3.0.0...v3.0.1)

---
updated-dependencies:
- dependency-name: pre-commit/action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-08 17:07:25 +05:45
Matt Seddon 44b78b81b9 repo: add get_subrepo_relpath helper for studio live experiments monorepo support 2024-02-06 04:02:37 +02:00
skshetry d25cb79e0a deps: bump dvc-objects, dvc-data and scmrepo (#10280)
* deps: bump dvc-objects, dvc-data and scmrepo

* bump dvc-azure

* fix typo
2024-02-06 03:36:37 +02:00
skshetry 79238fd2a3 cli: sort commands (#10281) 2024-02-05 23:54:26 +05:45
skshetry 2bf7bd990a pep584ify: use dict merge operator (#10279)
This was introduced in Python 3.9. The change makes
dict merges 'obvious' and looks cleaner.

I haven't changed `.update()` to `|=` (update operator),
as I find the former to be more readable and "obvious".
2024-02-03 15:58:07 +05:45
skshetry 68b9ecb95c chore: update to ruff 0.2 (#10278)
update to ruff 0.2
2024-02-02 12:07:49 +05:45
Dave Berenbaum 594716ec79 status: add option to skip imports (#10277) 2024-02-01 21:49:50 +02:00
Ruslan Kuprieiev 84b2e685e2 repo: index: don't set cache storage if there is no out.cache (#10275) 2024-02-01 15:15:35 +02:00
Ruslan Kuprieievandpre-commit-ci[bot] 0bb2f3d10c repo: site_cache_dir: separate subdir from root_dir (#10273)
* repo: site_cache_dir: separate subdir from root_dir

Currently we join scm root_dir with subrepo root dir to have something that would represent that subrepo, but that directory might not exist in reality as we might be using a bare git repo. So we should just treat those as separate things.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update __init__.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-02-01 09:32:22 +02:00
skshetry f42468bcaf Revert "build(deps-dev): update pytest requirement from <8,>=7 to >=7,<9" (#10269) 2024-01-31 12:31:21 +05:45
dependabot[bot] 28545c0cc5 build(deps-dev): update pytest requirement from <8,>=7 to >=7,<9 (#10259)
Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.0.0...8.0.0)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-31 11:12:41 +05:45
dependabot[bot] 0ee752d464 build(deps-dev): update pytest-docker requirement from <3,>=1 to >=1,<4 (#10262)
Updates the requirements on [pytest-docker](https://github.com/avast/pytest-docker) to permit the latest version.
- [Release notes](https://github.com/avast/pytest-docker/releases)
- [Changelog](https://github.com/avast/pytest-docker/blob/master/CHANGELOG.md)
- [Commits](https://github.com/avast/pytest-docker/compare/v1.0.0...v3.0.0)

---
updated-dependencies:
- dependency-name: pytest-docker
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-31 11:12:32 +05:45
skshetry 9a1bcd7f1d nit: fit statement/expr in the same line instead of hanging (#10267)
The code are wrapped due to us using line-length=79 before, but now some
of the code can easily fit in 88 chars.

The formatter did not change this automatically when we changed line-length,
because of magic trailing commas that gets added when they turn into multiple lines.

Black/ruff wrapping them in parens and splitting them into multiple lines
was making it difficult to read.
2024-01-30 18:23:04 +05:45
skshetry 422caa13c4 introduce dataset dependency (#10164)
* introduce dataset dependency

* support Annotated types

* split

* drop dvc.api.datasets

* drop pydantic and typing-extensions

* drop dvc.datasets
2024-01-30 16:44:28 +05:45
skshetry 3b56c40add remove Python 3.8 compat for threadpoolexecutor.shutdown(cancel_futures=True) (#10266) 2024-01-30 15:40:38 +05:45
skshetry b647475a64 drop Python 3.8 support (#10265)
* drop Python 3.8

Note that this PR only makes typing related changes.
Other changes can be done once this gets merged, they
are relatively few in numbers.

* bump build python version

* add typing_extensions to lint group
2024-01-30 11:33:25 +02:00
skshetry 966adec21c ruff: enable RET501 (#10264) 2024-01-30 14:03:49 +05:45
skshetry 4eb073868c update ruff to 0.1.15 (#10263)
update ruff
2024-01-30 13:09:55 +05:45
Peter Rowlands 196c1c7a17 exp show: output executor info for successful exps when available 2024-01-29 16:33:48 +09:00
Ruslan Kuprieievanddberenbaum df5b582c3f fetch: handle missing/modified imports (#10220)
tests: pull after imp-url no-download

Co-authored-by: dberenbaum <[email protected]>
2024-01-25 18:34:06 +02:00
Dave Berenbaum 20edd72a43 status: always show json (#10253) 2024-01-24 03:09:02 +02:00
Ruslan Kuprieiev ba06c0504a deps: bump dvc-data to >=3.8 (#10254) 2024-01-24 02:42:07 +02:00
18a9fea4af hydra: support plugins (#10240)
* hydra: support plugins

* Update dvc/utils/hydra.py

* fix tests

* load plugins after some checks run

---------

Co-authored-by: Ruslan Kuprieiev <[email protected]>
Co-authored-by: Saugat Pachhai (सौगात) <[email protected]>
Co-authored-by: skshetry <[email protected]>
2024-01-23 19:58:14 +05:45
pre-commit-ci[bot] bfb65870a4 [pre-commit.ci] pre-commit autoupdate (#10238)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.1.11 → v0.1.14](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.11...v0.1.14)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-01-23 10:06:34 +05:45
Matt Seddon a122b80d52 update min version of dvc-render to 1.0.1 (#10250) 2024-01-23 04:29:43 +02:00
dberenbaum 71afff6db3 exp save: add recursive arg 2024-01-19 12:57:27 +09:00
dberenbaum 48ec86de28 exp save: add target arg 2024-01-19 12:57:27 +09:00
Ruslan KuprieievandDave Berenbaum 78ab929ad1 config: azure: don't forget Coerce() for timeouts (#10247)
Co-authored-by: Dave Berenbaum <[email protected]>
2024-01-19 03:19:37 +02:00
232 changed files with 1840 additions and 3052 deletions
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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:
+1 -1
View File
@@ -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]
+1 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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",
]
+3 -3
View File
@@ -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
View File
@@ -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
+5 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
]
+4 -4
View File
@@ -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",),
+6
View File
@@ -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)
+2 -2
View File
@@ -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):
+1 -5
View File
@@ -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
+16 -6
View File
@@ -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)
+3 -2
View File
@@ -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,
+4 -5
View File
@@ -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="?",
+1 -1
View File
@@ -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:
+2 -9
View File
@@ -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
View File
@@ -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(
+1 -8
View File
@@ -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):
+1 -5
View File
@@ -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)
+6 -5
View File
@@ -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:
+4 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+9 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+8 -12
View File
@@ -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
+4 -6
View File
@@ -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:
+69
View File
@@ -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
+6 -4
View File
@@ -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
View File
@@ -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()
+5 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+7 -7
View File
@@ -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,
+4 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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 []
+5 -5
View File
@@ -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
+5 -11
View File
@@ -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
+25 -36
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+8 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
):
+16 -33
View File
@@ -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:
+3 -4
View File
@@ -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:
+3 -5
View File
@@ -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
+21 -31
View File
@@ -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
+3 -6
View File
@@ -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."
+30 -33
View File
@@ -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)
+2 -11
View File
@@ -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
+3 -3
View File
@@ -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.
+6 -5
View File
@@ -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
+12 -21
View File
@@ -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
+32 -50
View File
@@ -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:
+66 -65
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
from typing import Collection
from collections.abc import Collection
from dvc.exceptions import DvcException
+19 -27
View File
@@ -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)
+6 -6
View File
@@ -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.
+8 -7
View File
@@ -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
+5 -9
View File
@@ -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))
+14 -18
View File
@@ -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
+14 -13
View File
@@ -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
+5 -5
View File
@@ -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():
+3 -2
View File
@@ -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.
+7 -2
View File
@@ -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,
+16 -15
View File
@@ -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)
+22 -35
View File
@@ -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
+6 -13
View File
@@ -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 ""
)
+15 -31
View File
@@ -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
View File
@@ -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
View File
@@ -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