Compare commits

...
116 Commits
Author SHA1 Message Date
skshetry d7ea7c358d Revert "scm: use dulwich backend when fetching exps during clone/pull" (#9041)
Revert "scm: use dulwich backend when fetching exps during clone/pull (#9023)"

This reverts commit ed1ab7fa75.
2023-02-16 18:07:45 +05:45
dependabot[bot] 3f0da8f6cc build(deps-dev): bump scmrepo from 0.1.10 to 0.1.11 (#9039)Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [scmrepo](https://github.com/iterative/scmrepo) from 0.1.10 to 0.1.11.
- [Release notes](https://github.com/iterative/scmrepo/releases)
- [Commits](https://github.com/iterative/scmrepo/compare/0.1.10...0.1.11)

---
updated-dependencies:
- dependency-name: scmrepo
  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>
2023-02-16 18:03:13 +05:45
David de la Iglesia Castro b7b7eff4a3 tests: Fix test_deleted.
Closes #6297
#9017 actually fixed this but the test was incorrect (something wrong with caplog).
2023-02-16 11:43:09 +01:00
skshetry ddeb32ed33 ui: log footer support message to standard error (#9034) 2023-02-16 11:31:31 +05:45
Ruslan Kuprieiev c7be921f44 cachemgr: don't use tmp_dir
We used to use it in gdrive before https://github.com/iterative/PyDrive2/pull/211
2023-02-16 06:43:20 +02:00
Ruslan Kuprieiev c95c3f0f7a deps: limit types-tqdm
See https://github.com/iterative/dvc/pull/9036
2023-02-16 06:43:20 +02:00
skshetry be824ede0d render: avoid deepcopying (#9033)
* render: avoid deepcopying

* remove unnecessary line
2023-02-15 16:25:45 +05:45
Ruslan Kuprieiev cbe46cccd8 data: status: fix typo
Leftover from #9031
2023-02-15 07:18:19 +02:00
Ruslan Kuprieiev 49b5157abe diff: use index 2023-02-15 06:40:02 +02:00
Ruslan Kuprieiev 6f87c14bf9 data: status: bring back ui.status 2023-02-15 05:57:50 +02:00
Ruslan Kuprieiev 0f80d16ba7 data: status: fix typing 2023-02-15 05:57:50 +02:00
Ruslan Kuprieiev 4b23b4f8ae repo: cache index for cloud versioning
This allows us to reuse cloud versioning index that we've built in other
operations like `data status`.

Note, this only works with `dvc config feature.data_index_cache true`
right now.

Also note that index is not populated for regular remotes, it will come
in a separate PR as there are some http-related issues to figure out.

Related #8962
2023-02-15 04:53:48 +02:00
Ruslan Kuprieiev 446871d84f index: build_data_index: use dvcignore
Fixes #9008
2023-02-14 21:33:43 +02:00
dependabot[bot] 99bbe15980 build(deps-dev): bump pylint from 2.16.1 to 2.16.2 (#9020)
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.16.1 to 2.16.2.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.16.1...v2.16.2)

---
updated-dependencies:
- dependency-name: pylint
  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>
2023-02-14 22:35:59 +05:45
David de la Iglesia Castro 320771551b exp: Preserve staged changes after experiments.
- Detach head after stashing workspace.
- Use reinstate_index.
2023-02-14 16:59:10 +01:00
David de la Iglesia Castro 1676d1f344 Add test_stash_exp. 2023-02-14 16:59:10 +01:00
David de la Iglesia Castro c8ae2895f0 deps: bump scmrepo==0.1.10
https://github.com/iterative/scmrepo/releases/tag/0.1.10
2023-02-14 16:59:10 +01:00
skshetry ed1ab7fa75 scm: use dulwich backend when fetching exps during clone/pull (#9023)
refspecs uses pygit2 backend by default, which does not support git credential
helpers. This PR forces to use dulwich backend during clone/pull operations.
2023-02-14 19:18:40 +05:45
Karan Jakhar 73497dae9e Handle filenames which has ":" (#9000)
* Fixes #8773 issue

* added test for #8773 issue

* skip test for windows
2023-02-14 11:21:32 +00:00
skshetry eff775c760 brancher: add trace when switching filesystem (#9022)
brancher: add trace info when switching filesystem
2023-02-14 15:27:18 +05:45
Ruslan Kuprieiev e73d662686 repo: introduce data_index
Extracting from `index.data` as a "root" data index that contains indexes
for different revisions/workspaces.

This is a setup for putting cloud versioning remote indexes and also replacing
old state.
2023-02-13 06:31:44 +02:00
Ruslan Kuprieiev 86a6fb104a index: experimental persistent data index
Related https://github.com/iterative/dvc-data/issues/208
2023-02-13 02:57:30 +02:00
Ruslan Kuprieiev 3b097dcccd deps: bump dvc-data to 0.40.1 2023-02-12 09:12:08 +02:00
David de la Iglesia Castro e910dac0dc Update pyproject.toml 2023-02-10 10:59:32 +01:00
David de la Iglesia Castro 17a71d6011 plots: Rename template_path to html_template. 2023-02-10 10:59:32 +01:00
dependabot[bot] 5a6ccc8027 build(deps-dev): bump dvc-render from 0.1.1 to 0.1.2
Bumps [dvc-render](https://github.com/iterative/dvc-render) from 0.1.1 to 0.1.2.
- [Release notes](https://github.com/iterative/dvc-render/releases)
- [Commits](https://github.com/iterative/dvc-render/compare/0.1.1...0.1.2)

---
updated-dependencies:
- dependency-name: dvc-render
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-02-10 10:59:32 +01:00
pre-commit-ci[bot] 6dccfcb3f9 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2023-02-10 04:47:57 +02:00
karajan1001 2b0c365f35 Fix some mypy error. 2023-02-10 04:47:57 +02:00
dberenbaum 611e2f4311 update rich 2023-02-09 23:18:50 +02:00
dberenbaum 561a375b48 update: drop worktree from cli 2023-02-09 23:03:50 +02:00
skshetry e51345e276 get/import: lazy import dvc.scm; reduces dvc --help by ~20% (#8996) 2023-02-09 15:04:55 +05:45
skshetry 00e61e18ae logging: do not disable other loggers (#8995)
Closes https://github.com/iterative/dvc/issues/8631
2023-02-09 15:04:29 +05:45
skshetry 5490fc00d1 logger: check isatty stream to print colored logs or not instead of depending on colorama (#8993
logger: check isatty stream to print colored logs or not
2023-02-09 13:50:38 +05:45
skshetry f1043676da [WIP] logging: change from dictConfig to equivalent code for creating logge… (#8985
logging: change from dictConfig to equivalent code for creating loggers/handlers/formatters

dictConfig clears all handlers, and potentially disables existing loggers (we disabled this functionality
though). There's much more code than necessary in dictConfig than what we need.

I have tried to keep the setup equivalent to what we had in the config, so I have not tried to cleanup
the code.
2023-02-09 09:34:14 +05:45
Ruslan Kuprieiev f7ebcbb974 deps: bump dvc-data to 0.39.3 2023-02-09 05:44:50 +02:00
dependabot[bot] c637b963e0 build(deps-dev): bump dvc-azure from 2.21.0 to 2.21.1 (#8991)
Bumps [dvc-azure](https://github.com/iterative/dvc-azure) from 2.21.0 to 2.21.1.
- [Release notes](https://github.com/iterative/dvc-azure/releases)
- [Commits](https://github.com/iterative/dvc-azure/compare/2.21.0...2.21.1)

---
updated-dependencies:
- dependency-name: dvc-azure
  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>
2023-02-09 08:31:13 +05:45
Ruslan Kuprieiev 9156bb80db output: rename odb -> cache 2023-02-09 00:44:18 +02:00
Ruslan Kuprieiev 58295ca0ab repo: rename odb -> cache
We used to have it named `cache` before, but these days it is clear that
you can have three odbs: just odb (e.g. in-memory), cache (typically local)
or remote (typically cloud). Fixing naming here before proceeding with
introducing `out.odb` and `out.cache`.

In preparation for migration to https://github.com/iterative/dvc-data/pull/304
2023-02-08 23:05:45 +02:00
skshetry 4176007493 push: check if versioning is supported or not during checkout on cloud versioning (#8949)Fixes https://github.com/iterative/dvc/issues/8824
* push: check if versioning is supported or not during checkout on cloud versioning

* bump dvc-data to 0.38.4
2023-02-08 17:05:27 +05:45
dependabot[bot]andGao 9d7883f529 build(deps-dev): bump pytest-xdist from 3.1.0 to 3.2.0 (#8981)
Bumps [pytest-xdist](https://github.com/pytest-dev/pytest-xdist) from 3.1.0 to 3.2.0.
- [Release notes](https://github.com/pytest-dev/pytest-xdist/releases)
- [Changelog](https://github.com/pytest-dev/pytest-xdist/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-xdist/compare/v3.1.0...v3.2.0)

---
updated-dependencies:
- dependency-name: pytest-xdist
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Gao <[email protected]>
2023-02-08 07:31:21 +00:00
Peter Rowlands 93c556acdd index: fix index entries for partial import stages 2023-02-08 15:34:15 +09:00
Peter Rowlands 676b1e47e9 import-url: update stage md5 after unpartialing imports 2023-02-08 15:34:15 +09:00
Peter Rowlands 0b141ac08a testing: test cloud versioned import-url --no-download 2023-02-08 15:34:15 +09:00
dependabot[bot] b4db1e06a1 build(deps-dev): bump dvc-render from 0.1.0 to 0.1.1 (#8982)
Bumps [dvc-render](https://github.com/iterative/dvc-render) from 0.1.0 to 0.1.1.
- [Release notes](https://github.com/iterative/dvc-render/releases)
- [Commits](https://github.com/iterative/dvc-render/compare/0.1.0...0.1.1)

---
updated-dependencies:
- dependency-name: dvc-render
  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>
2023-02-08 09:11:42 +05:45
dependabot[bot] 0d0fff6ddc build(deps-dev): bump dvc-data from 0.38.1 to 0.38.3 (#8983)
Bumps [dvc-data](https://github.com/iterative/dvc-data) from 0.38.1 to 0.38.3.
- [Release notes](https://github.com/iterative/dvc-data/releases)
- [Commits](https://github.com/iterative/dvc-data/compare/0.38.1...0.38.3)

---
updated-dependencies:
- dependency-name: dvc-data
  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>
2023-02-08 09:11:28 +05:45
Hagai Kariti 6c8c26f741 Support setting a exp name prefix in hydra sweeps 2023-02-07 18:49:23 +01:00
Peter Rowlands f68a12558a testing: mark 00 prefix tests xfail on fs without find() 2023-02-07 15:17:29 +09:00
Ruslan Kuprieiev a8a5b29fb7 deps: bump dvc-data to 0.38.1 (#8976) 2023-02-07 06:58:15 +02:00
skshetry 037e2e54f0 serialization: reorder files section metadata (#8950
serialization: reorder files section
2023-02-07 10:05:36 +05:45
skshetry ae80889477 logging: defer calling colorama.init() in jupyter, otherwise initialize immediately (#8969)Closes https://github.com/iterative/dvc/issues/8957
logging: init colorama before logging if outside jupyter env

Otherwise, tty detection does not work as `colorama.init()` mutates
sys.stdout/sys.stderr, and after `dictConfig()`, the handlers will hold
on to the stream.
2023-02-07 10:05:05 +05:45
Gao e1acab5113 Use pygit2 backend for fetch_refspecs (#8907)
fix: #8762
1. partly solve #8787 and #8477
2. Improve the performance
3. Bump `scmrepo` into 0.1.9
2023-02-06 16:07:00 +02:00
d84196db62 studio: Move post_live_metrics from queue to executor (#8955)
* studio: Move `post_live_metrics` from `queue/workspace` to `executor/base`.

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

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

* Update dvc/repo/experiments/utils.py

Co-authored-by: skshetry <[email protected]>

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: skshetry <[email protected]>
2023-02-06 13:31:10 +00:00
Peter Rowlands 80c2815e67 deps: bump dvc-data to 0.37.8 2023-02-06 21:18:44 +09:00
Peter Rowlands e42a01a3dd testing: re-add 00 prefix remote traverse tests 2023-02-06 21:18:44 +09:00
Ruslan Kuprieiev 8e1b75feb5 index: build_data_index: handle empty dirs (#8959)
Fixes https://github.com/iterative/dvc/issues/8958
2023-02-06 01:44:15 +02:00
Saugat Pachhai (सौगात) 4ec6d64629 add .git-blame-ignore-revs 2023-02-04 10:02:31 +05:45
skshetry a86470ccbc update black and change line-length to 88 (#8953) 2023-02-04 05:28:07 +02:00
dependabot[bot] 5d2701341b build(deps-dev): bump pylint from 2.16.0 to 2.16.1 (#8944)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-03 14:06:36 +05:45
Peter Rowlands 97589a1358 testing: add tests for pull after import-url --version-aware 2023-02-03 15:50:57 +09:00
Peter Rowlands 9e72ce53cd deps: bump dvc-data to 0.37.5 2023-02-03 15:50:57 +09:00
Peter Rowlands e34a6d3b30 update: fix meta & version ID merge on worktree update 2023-02-03 13:23:04 +09:00
Peter Rowlands 25dcc33580 output: build from files in get_obj() regardless is hash_info set 2023-02-03 13:23:04 +09:00
frederik-sandfort 18fa1a0272 Custom header for dvc webdav (#8933)Fix https://github.com/iterative/dvc/issues/8931
* include custom auth header for webdav

* Revert "include custom auth header for webdav"

This reverts commit d2f2b2889b6bcf0429e4a28076f709a04642ae3a.

* include custom auth header for dvc webdav, ran unit tests

* update dvc-webdav version
2023-02-02 16:36:11 +05:45
dependabot[bot] 49a27f4140 build(deps-dev): bump dvc-render from 0.0.17 to 0.1.0 (#8935)
Bumps [dvc-render](https://github.com/iterative/dvc-render) from 0.0.17 to 0.1.0.
- [Release notes](https://github.com/iterative/dvc-render/releases)
- [Commits](https://github.com/iterative/dvc-render/compare/0.0.17...0.1.0)

---
updated-dependencies:
- dependency-name: dvc-render
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-02 14:13:27 +05:45
dependabot[bot] 9b2aa79c18 build(deps-dev): bump pylint from 2.15.10 to 2.16.0 (#8936)
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.15.10 to 2.16.0.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.15.10...v2.16.0)

---
updated-dependencies:
- dependency-name: pylint
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-02 14:12:53 +05:45
Ruslan Kuprieiev 24023c6977 index: introduce data_keys (#8938)
`data_keys` are similar to `outs`, but they provide more granularity, so we
can use them in `build_data_index` to rebuild index views with filtered
outputs. E.g. when running `dvc diff dir/file`, workspace index should only
have `dir/file` in it.

This also could be used for standalone (non-output) data in the future.

Pre-requisite for #8930
2023-02-02 05:50:16 +02:00
Ruslan Kuprieiev 5310bcdcec deps: bump dvc-data to 0.37.3 (#8923) 2023-02-02 04:18:18 +02:00
dependabot[bot] ae92154cec build(deps-dev): bump beautifulsoup4 from 4.11.1 to 4.11.2 (#8928)
Bumps [beautifulsoup4](https://www.crummy.com/software/BeautifulSoup/bs4/) from 4.11.1 to 4.11.2.

---
updated-dependencies:
- dependency-name: beautifulsoup4
  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>
2023-02-01 09:10:50 +05:45
Ruslan Kuprieiev f661cee8f6 stage: use repo.fs directly without copying (#8929)
Otherwise when using brancher we end up in a strange situation where even
though we are collecting targets from `gitfs`, we are still using `localfs`
as `self.fs`, which make operations like `self.fs.abspath` in
`collect_granular` return invalid paths.
2023-02-01 05:21:53 +02:00
Ruslan Kuprieiev e07c85a993 index: collect_targets: introduce onerror (#8925) 2023-02-01 03:49:27 +02:00
Ruslan Kuprieiev 58d9e379fe tests: gc: use timedelta again (#8926)
Leftover from #8912
2023-02-01 03:14:22 +02:00
skshetry 524162dc3f version/info: match dashes' length below DVC version (#8921) 2023-01-31 18:32:04 +05:45
skshetry aa15b21dce logging: log pkg version/type, platform info and cli arguments (#8918)
logging: log pkg version and type, platform info and cli arguments
2023-01-31 15:46:14 +05:45
Peter Rowlands 0404967591 deps: bump dvc-gs to 2.21.1 2023-01-31 17:26:44 +09:00
Peter Rowlands 465d886c29 testing: add tests for version-aware import-url 2023-01-31 15:55:44 +09:00
Peter Rowlands 459adfb40f import-url: disable push on version aware imports before saving 2023-01-31 15:55:44 +09:00
Peter Rowlands fe9f8654a3 testing: add test for worktree update 2023-01-31 15:55:44 +09:00
skshetry 8208846003 bump ruff to 0.0.238 (#8915) 2023-01-31 11:24:03 +05:45
Peter Rowlands 73aea2d972 ci: run dvc-s3 tests on DVC pull requests 2023-01-31 14:13:55 +09:00
Ruslan Kuprieiev 10184e9370 tests: gc: use timedelta (#8912)
Bug from https://github.com/iterative/dvc/pull/8880
2023-01-31 05:18:09 +02:00
Ruslan Kuprieiev 2690f05cee pre-commit: bump isort to 5.12.0
https://github.com/PyCQA/isort/issues/2077
2023-01-31 04:02:24 +02:00
Ruslan Kuprieiev 34c2c95115 index: expose rev property (#8909)
index: add rev property

This is very useful to identify indexes that are built from git revisions
and so are describing an immutable state and can be used for caching.

We were already using this for `__repr__`.
2023-01-30 20:18:54 +02:00
skshetry 5c6c1dc719 mypy: unignore module override (#8902) 2023-01-28 15:08:52 +05:45
98bcfad8e7 build(deps-dev): bump scmrepo from 0.1.6 to 0.1.7 (#8821)
* build(deps-dev): bump scmrepo from 0.1.6 to 0.1.7

Bumps [scmrepo](https://github.com/iterative/scmrepo) from 0.1.6 to 0.1.7.
- [Release notes](https://github.com/iterative/scmrepo/releases)
- [Commits](https://github.com/iterative/scmrepo/compare/0.1.6...0.1.7)

---
updated-dependencies:
- dependency-name: scmrepo
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

* mypy: remove scmrepo from ignored modules

* Fix mypy issues

---------

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]>
Co-authored-by: Saugat Pachhai (सौगात) <[email protected]>
2023-01-28 14:26:00 +05:45
skshetry 7ab4c59d70 test: run pytest-style checks (#8901) 2023-01-28 14:10:53 +05:45
skshetry a137b2a6bc update ruff and enable flake8-logging-format and flake8-type-checking (#8894)
* update ruff and enable flake8-logging-format and flake8-type-checking check

* Update dvc/external_repo.py

* Update dvc/fs/dvc.py

* Update dvc/repo/experiments/__init__.py

* Update dvc/repo/experiments/queue/base.py

* Update pyproject.toml
2023-01-27 19:14:19 +05:45
Daniele Trifirò 6f0e8a8db5 tests: fix test_iter_revs mock 2023-01-27 12:52:46 +01:00
Daniele Trifirò d294945499 gc: fix usage with --date 2023-01-27 12:52:46 +01:00
Dave Berenbaum db70d1cc5c analytics: differentiate ci users (#8838) 2023-01-26 21:50:14 +05:45
Andrii Korzhuk ed6e11e856 Fixed typo in _format_field function 2023-01-26 13:50:40 +01:00
skshetry c1cd13c34f serialize: fix loading of cloud metadata from .dvc/dvc.lock files (#8889) 2023-01-26 16:18:03 +05:45
David de la Iglesia Castro 12dd746165 output: Pass version_aware=True to fs if files is present.
This will be ignored by filesystems that don't support `version_aware`.

Closes #8853
2023-01-26 10:48:59 +01:00
5165fb67a1 [pre-commit.ci] pre-commit autoupdate (#8869)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/charliermarsh/ruff-pre-commit: v0.0.223 → v0.0.230](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.223...v0.0.230)

* Update .pre-commit-config.yaml

* fix lint issues

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: skshetry <[email protected]>
Co-authored-by: Saugat Pachhai (सौगात) <[email protected]>
2023-01-26 14:24:09 +05:45
Peter Rowlands b2c1c4dccd data_cloud: move version_aware config check to get_remote 2023-01-26 17:21:47 +09:00
Peter Rowlands b060cadbf1 deps: bump dvc-data to 0.36.2 2023-01-26 17:21:47 +09:00
Peter Rowlands 7c55a9c7cd testing: add initial versioned remote tests 2023-01-26 17:03:50 +09:00
Peter Rowlands 037b5b2e0a testing: add cloud versioning remote fixtures 2023-01-26 17:03:50 +09:00
dependabot[bot] 99acb8e335 build(deps-dev): bump dvc-data from 0.36.0 to 0.36.1 (#8887)
Bumps [dvc-data](https://github.com/iterative/dvc-data) from 0.36.0 to 0.36.1.
- [Release notes](https://github.com/iterative/dvc-data/releases)
- [Commits](https://github.com/iterative/dvc-data/compare/0.36.0...0.36.1)

---
updated-dependencies:
- dependency-name: dvc-data
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-26 08:32:02 +05:45
Ruslan Kuprieiev b46cfbda5d deps: bump dvc-data to 0.36.0 (#8885) 2023-01-26 00:22:12 +02:00
Dave Berenbaum a8d193bf2a queue: update kill ui messages (#8798)
* queue: update kill ui messages

* queue kill: update ui, kill all valid tasks

* queue kill: update wording of ui
2023-01-25 20:08:27 +05:45
dependabot[bot] 0b1cf6c771 build(deps-dev): bump dvc-gs from 2.20.0 to 2.21.0 (#8841)
Bumps [dvc-gs](https://github.com/iterative/dvc-gs) from 2.20.0 to 2.21.0.
- [Release notes](https://github.com/iterative/dvc-gs/releases)
- [Commits](https://github.com/iterative/dvc-gs/compare/2.20.0...2.21.0)

---
updated-dependencies:
- dependency-name: dvc-gs
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 19:29:21 +05:45
skshetry b3aa698cd4 dumpd: serialize files at the end in .dvc files (#8879) 2023-01-25 18:52:15 +05:45
skshetry bb9a3a0403 import/import-url: try not to delete dvcfile before import/import-url completes (#8876)
* import/import-url: try not to delete dvcfile before import/import-url completes

* use repo.stage.create instead of internal create_stage utility
2023-01-25 18:16:45 +05:45
skshetry 3427e115eb data status: check if change.old.hash_info is None or not (#8878)
data:status: check if change.old.hash_info is None or not
2023-01-25 14:24:38 +02:00
skshetry 3d5f4dc66c add support for output-specific remotes (#8862)
Also splits metadata in `files` to cloud vs local. But this happens
during dump/load, so actual behaviour is changed. During runtime,
the metadata is merged and loaded. During dump, the metadata
are split if there is a `remote` set.
2023-01-25 18:06:12 +05:45
David de la Iglesia Castro 80633f4fad stage.save: Remove merge_versioned option.
If we want `add`, `commit` and `move` to work for cloud versioning, option must be always True so it is the same as not having it.

Closes #8828
(We were not passing `merge_versioned=True` in `dvc commit`)
2023-01-25 10:35:37 +01:00
Ruslan Kuprieiev 8bda3650c5 deps: bump dvc-data to 0.35.3 2023-01-24 23:06:34 +02:00
David de la Iglesia Castro ea2e5dc3e5 output: Don't serialize hash_info or meta in cloud versioning.
Closes #8357
2023-01-24 18:40:39 +01:00
dependabot[bot] 61afdca3a6 build(deps-dev): bump dvc-data from 0.35.1 to 0.35.2 (#8871)
Bumps [dvc-data](https://github.com/iterative/dvc-data) from 0.35.1 to 0.35.2.
- [Release notes](https://github.com/iterative/dvc-data/releases)
- [Commits](https://github.com/iterative/dvc-data/compare/0.35.1...0.35.2)

---
updated-dependencies:
- dependency-name: dvc-data
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 20:47:16 +05:45
Ruslan Kuprieiev 6a6bc7035a data: status: use data index (#8846)
Pre-requisite for #8761
2023-01-23 21:29:32 +02:00
Ruslan Kuprieiev ccc97622fa deps: bump dvc-ssh to 2.21.0 (#8865)
* deps: bump dvc-ssh to 2.21.0

* Update pyproject.toml
2023-01-23 01:36:52 +02:00
Ruslan Kuprieiev 916b5d169f deps: bump dvc-azure to 2.21.0 2023-01-23 00:46:58 +02:00
Peter Rowlands 38e9b2ba95 deps: bump dvc-data to 0.35.1 2023-01-21 03:44:02 +09:00
Dave Berenbaumandskshetry 18f039d7c7 add viztracer async option (#8856)
* add viztracer async option

* Update dvc/_debug.py

Co-authored-by: skshetry <[email protected]>
2023-01-20 22:29:11 +05:45
skshetry f1c9e9365b remove setup.py file (#8851) 2023-01-20 20:26:13 +05:45
Ruslan Kuprieiev b50d1ee674 deps: bump dvc-data to 0.35.0 2023-01-20 04:16:12 +02:00
357 changed files with 4363 additions and 5361 deletions
+3 -2
View File
@@ -11,8 +11,9 @@ ignore=
P1
# use `!r` instead of surrounding by quotes
B028
max_line_length = 79
max_line_length = 88
max-complexity = 10
select = B,C,E,F,W,T4,B902,T,P
select = B,C,E,F,W,T4,B902,T,P,TYP
show_source = true
count = true
min_python_version = 3.8.0
+10
View File
@@ -0,0 +1,10 @@
# The following commits will be ignored by the GitHub blame view.
# You can configure `git blame` to use this file as the default ignore file:
# git config blame.ignoreRevsFile .git-blame-ignore-revs
# or, you can also manually ignore commits from this file by doing:
# git blame --ignore-revs-file .git-blame-ignore-revs <filepath>
# see the `blame.markIgnoredLines` and `blame.markUnblamableLines` options as well.
# Refer to: https://git-scm.com/docs/git-blame#Documentation/git-blame.txt---ignore-revs-fileltfilegt
# update black to 2023 stable style and change line-length to 88
a86470ccbc693d6f0a4f8066cbd1357e5191c4ff
+54
View File
@@ -0,0 +1,54 @@
name: Remote Plugin Tests
on:
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
jobs:
tests:
timeout-minutes: 45
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04]
pyv: ["3.10"]
plugin: ["dvc-s3"]
steps:
- uses: actions/checkout@v3
with:
path: dvc
- uses: actions/checkout@v3
with:
repository: iterative/${{ matrix.plugin }}
ref: main
path: ${{ matrix.plugin }}
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.pyv }}
cache: pip
cache-dependency-path: |
dvc/pyproject.toml
${{ matrix.plugin }}/setup.cfg
- name: Install plugin + DVC@PR
run: |
pip install --upgrade pip wheel
pip install "./dvc[testing]"
pip install -e "./${{ matrix.plugin }}[tests]"
- name: Run plugin tests
timeout-minutes: 15
working-directory: ${{ matrix.plugin }}
run: pytest -v -n=auto
+4 -4
View File
@@ -35,18 +35,17 @@ repos:
args:
- --py38-plus
- repo: https://github.com/psf/black
rev: 22.12.0
rev: 23.1.0
hooks:
- id: black
- repo: https://github.com/PyCQA/isort
rev: 5.11.4
rev: 5.12.0
hooks:
- id: isort
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: 'v0.0.223'
rev: 'v0.0.238'
hooks:
- id: ruff
args: ["--force-exclude"]
- repo: https://github.com/codespell-project/codespell
rev: v2.2.2
hooks:
@@ -61,6 +60,7 @@ repos:
- flake8-comprehensions
- flake8-debugger
- flake8-string-format
- flake8-typing-imports
- repo: local
hooks:
- id: mypy
+20 -18
View File
@@ -8,17 +8,19 @@ if TYPE_CHECKING:
@contextmanager
def viztracer_profile(path: Union[Callable[[], str], str], depth: int = -1):
def viztracer_profile(
path: Union[Callable[[], str], str],
depth: int = -1,
log_async: bool = False,
):
try:
import viztracer # pylint: disable=import-error
except ImportError:
print( # noqa: T201
"Failed to run profiler, viztracer is not installed"
)
print("Failed to run profiler, viztracer is not installed") # noqa: T201
yield
return
tracer = viztracer.VizTracer(max_stack_depth=depth)
tracer = viztracer.VizTracer(max_stack_depth=depth, log_async=log_async)
tracer.start()
yield
@@ -76,9 +78,7 @@ def instrument(html_output=False):
try:
from pyinstrument import Profiler # pylint: disable=import-error
except ImportError:
print( # noqa: T201
"Failed to run profiler, pyinstrument is not installed"
)
print("Failed to run profiler, pyinstrument is not installed") # noqa: T201
yield
return
@@ -182,10 +182,15 @@ def debugtools(args: Optional["Namespace"] = None, **kwargs):
separate_threads=kw.get("yappi_separate_threads"),
)
)
if kw.get("viztracer") or kw.get("viztracer_depth"):
if (
kw.get("viztracer")
or kw.get("viztracer_depth")
or kw.get("viztracer_async")
):
path_func = _get_path_func("viztracer", "json")
depth = kw.get("viztracer_depth") or -1
prof = viztracer_profile(path=path_func, depth=depth)
log_async = kw.get("viztracer_async") or False
prof = viztracer_profile(path=path_func, depth=depth, log_async=log_async)
stack.enter_context(prof)
yield
@@ -193,12 +198,8 @@ def debugtools(args: Optional["Namespace"] = None, **kwargs):
def add_debugging_flags(parser):
from argparse import SUPPRESS
parser.add_argument(
"--cprofile", action="store_true", default=False, help=SUPPRESS
)
parser.add_argument(
"--yappi", action="store_true", default=False, help=SUPPRESS
)
parser.add_argument("--cprofile", action="store_true", default=False, help=SUPPRESS)
parser.add_argument("--yappi", action="store_true", default=False, help=SUPPRESS)
parser.add_argument(
"--yappi-separate-threads",
action="store_true",
@@ -209,10 +210,11 @@ def add_debugging_flags(parser):
"--viztracer", action="store_true", default=False, help=SUPPRESS
)
parser.add_argument("--viztracer-depth", type=int, help=SUPPRESS)
parser.add_argument("--cprofile-dump", help=SUPPRESS)
parser.add_argument(
"--pdb", action="store_true", default=False, help=SUPPRESS
"--viztracer-async", action="store_true", default=False, help=SUPPRESS
)
parser.add_argument("--cprofile-dump", help=SUPPRESS)
parser.add_argument("--pdb", action="store_true", default=False, help=SUPPRESS)
parser.add_argument(
"--instrument", action="store_true", default=False, help=SUPPRESS
)
+10 -4
View File
@@ -82,10 +82,9 @@ def send(path):
def _scm_in_use():
from scmrepo.noscm import NoSCM
from dvc.exceptions import NotDvcRepoError
from dvc.repo import Repo
from dvc.scm import NoSCM
from .scm import SCM, SCMError
@@ -102,17 +101,24 @@ def _runtime_info():
"""
Gather information from the environment where DVC runs to fill a report.
"""
from iterative_telemetry import find_or_create_user_id
from iterative_telemetry import _generate_ci_id, find_or_create_user_id
from dvc import __version__
from dvc.utils import is_binary
ci_id = _generate_ci_id()
if ci_id:
group_id, user_id = ci_id
else:
group_id, user_id = None, find_or_create_user_id()
return {
"dvc_version": __version__,
"is_binary": is_binary(),
"scm_class": _scm_in_use(),
"system_info": _system_info(),
"user_id": find_or_create_user_id(),
"user_id": user_id,
"group_id": group_id,
}
+1 -1
View File
@@ -12,7 +12,7 @@ class Annotation:
PARAM_META: ClassVar[str] = "meta"
desc: Optional[str] = None
type: Optional[str] = None
type: Optional[str] = None # noqa: A003
labels: List[str] = field(default_factory=list)
meta: Dict[str, Any] = field(default_factory=dict)
+3 -7
View File
@@ -27,16 +27,14 @@ def get_url(path, repo=None, rev=None, remote=None):
if not dvc_info:
raise OutputNotFoundError(path, repo)
dvc_repo = info["repo"]
dvc_repo = info["repo"] # pylint: disable=unsubscriptable-object
md5 = dvc_info["md5"]
return dvc_repo.cloud.get_url_for(remote, checksum=md5)
class _OpenContextManager(GCM):
def __init__(
self, func, args, kwds
): # pylint: disable=super-init-not-called
def __init__(self, func, args, kwds): # pylint: disable=super-init-not-called
self.gen = func(*args, **kwds)
self.func, self.args, self.kwds = ( # type: ignore[assignment]
func,
@@ -45,9 +43,7 @@ class _OpenContextManager(GCM):
)
def __getattr__(self, name):
raise AttributeError(
"dvc.api.open() should be used in a with statement."
)
raise AttributeError("dvc.api.open() should be used in a with statement.")
def open( # noqa, pylint: disable=redefined-builtin
+1 -2
View File
@@ -9,12 +9,11 @@ def _get_odb(repo, settings, fs=None):
return None
cls, config, fs_path = get_cloud_fs(repo, **settings)
config["tmp_dir"] = repo.tmp_dir
fs = fs or cls(**config)
return get_odb(fs, fs_path, state=repo.state, **config)
class ODBManager:
class CacheManager:
CACHE_DIR = "cache"
CLOUD_SCHEMES = [
Schemes.S3,
+40 -14
View File
@@ -39,12 +39,17 @@ def parse_args(argv=None):
def _log_unknown_exceptions() -> None:
from dvc.info import get_dvc_info
from dvc.logger import FOOTER
from dvc.ui import ui
from dvc.utils import colorize
logger.exception("unexpected error")
if logger.isEnabledFor(logging.DEBUG):
logger.debug("Version info for developers:\n%s", get_dvc_info())
logger.info(FOOTER)
q = colorize("Having any troubles?", "yellow")
link = colorize("https://dvc.org/support", "blue")
footer = f"\n{q} Hit us up at {link}, we are always happy to help!"
ui.error_write(footer)
def _log_exceptions(exc: Exception) -> Optional[int]:
@@ -56,9 +61,11 @@ def _log_exceptions(exc: Exception) -> Optional[int]:
if exc.errno == errno.EMFILE:
logger.exception(
"too many open files, please visit "
"{} to see how to handle this "
"problem".format(error_link("many-files")),
(
"too many open files, please visit "
"%s to see how to handle this problem"
),
error_link("many-files"),
extra={"tb_only": True},
)
else:
@@ -80,7 +87,7 @@ def _log_exceptions(exc: Exception) -> Optional[int]:
if cmd:
link = format_link("https://dvc.org/doc/install")
hint = (
f"To install dvc with those dependencies, run:\n"
"To install dvc with those dependencies, run:\n"
"\n"
f"\t{cmd}\n"
"\n"
@@ -91,8 +98,10 @@ def _log_exceptions(exc: Exception) -> Optional[int]:
hint = f"\nPlease report this bug to {link}. Thank you!"
logger.exception(
f"URL '{exc.url}' is supported but requires these missing "
f"dependencies: {exc.missing_deps}. {hint}",
"URL '%s' is supported but requires these missing dependencies: %s. %s",
exc.url,
exc.missing_deps,
hint,
extra={"tb_only": True},
)
return None
@@ -101,7 +110,9 @@ def _log_exceptions(exc: Exception) -> Optional[int]:
link = format_link("https://man.dvc.org/remote/modify")
logger.exception("configuration error")
logger.exception(
f"{exc!s}\nLearn more about configuration settings at {link}.",
"%s\nLearn more about configuration settings at %s.",
exc,
link,
extra={"tb_only": True},
)
return 251
@@ -113,9 +124,14 @@ def _log_exceptions(exc: Exception) -> Optional[int]:
directory = relpath(exc.directory)
logger.exception(
f"Could not open pickled '{exc.type}' cache.\n"
f"Remove the '{directory}' directory and then retry this command."
f"\nSee {error_link('pickle')} for more information.",
(
"Could not open pickled '%s' cache.\n"
"Remove the '%s' directory and then retry this command."
"\nSee %s for more information."
),
exc.type,
directory,
error_link("pickle"),
extra={"tb_only": True},
)
return None
@@ -142,7 +158,7 @@ def main(argv=None): # noqa: C901
from dvc._debug import debugtools
from dvc.config import ConfigError
from dvc.exceptions import DvcException, NotDvcRepoError
from dvc.logger import disable_other_loggers, set_loggers_level
from dvc.logger import set_loggers_level
# NOTE: stderr/stdout may be closed if we are running from dvc.daemon.
# On Linux we directly call cli.main after double forking and closing
@@ -155,7 +171,6 @@ def main(argv=None): # noqa: C901
logging.disable(logging.INFO)
args = None
disable_other_loggers()
outerLogLevel = logger.level
try:
@@ -172,6 +187,17 @@ def main(argv=None): # noqa: C901
if level is not None:
set_loggers_level(level)
if level and level <= logging.DEBUG:
from platform import platform, python_implementation, python_version
from dvc import __version__
from dvc.utils.pkg import PKG
pyv = " ".join([python_implementation(), python_version()])
pkg = f" ({PKG})" if PKG else ""
logger.debug("v%s%s, %s on %s", __version__, pkg, pyv, platform())
logger.debug("command: %s", " ".join(argv or sys.argv))
logger.trace(args) # type: ignore[attr-defined]
if not sys.stdout.closed and not args.quiet:
+2 -2
View File
@@ -2,7 +2,7 @@ from argparse import _AppendAction
class CommaSeparatedArgs(_AppendAction): # pylint: disable=protected-access
def __call__(self, parser, namespace, values, option_string=None):
def __call__(self, parser, namespace, values, option_string=None): # noqa: ARG002
from funcy import ldistinct
items = getattr(namespace, self.dest) or []
@@ -11,7 +11,7 @@ class CommaSeparatedArgs(_AppendAction): # pylint: disable=protected-access
class KeyValueArgs(_AppendAction):
def __call__(self, parser, namespace, values, option_string=None):
def __call__(self, parser, namespace, values, option_string=None): # noqa: ARG002
items = getattr(namespace, self.dest) or {}
for value in filter(bool, values):
key, _, value = value.partition("=")
+1 -3
View File
@@ -28,7 +28,5 @@ def hide_subparsers_from_help(subparsers):
# Need to set `add_help=False`, but avoid setting `help`
# (not even to `argparse.SUPPPRESS`).
# NOTE: The argument is the parent subparser, not the subcommand parser.
cmds = [
cmd for cmd, parser in subparsers.choices.items() if parser.add_help
]
cmds = [cmd for cmd, parser in subparsers.choices.items() if parser.add_help]
subparsers.metavar = "{{{}}}".format(",".join(cmds))
+1 -4
View File
@@ -41,10 +41,7 @@ def _add_annotating_args(parser: argparse.ArgumentParser) -> None:
class CmdAdd(CmdBase):
def run(self):
from dvc.exceptions import (
DvcException,
RecursiveAddingWhileUsingFilename,
)
from dvc.exceptions import DvcException, RecursiveAddingWhileUsingFilename
try:
if len(self.args.targets) > 1 and self.args.file:
+6 -4
View File
@@ -68,10 +68,12 @@ def add_parser(subparsers, parent_parser):
)
cache_dir_parser.add_argument(
"value",
help="Path to cache directory. Relative paths are resolved relative "
"to the current directory and saved to config relative to the "
"config file location. If no path is provided, it returns the "
"current cache directory.",
help=(
"Path to cache directory. Relative paths are resolved relative "
"to the current directory and saved to config relative to the "
"config file location. If no path is provided, it returns the "
"current cache directory."
),
nargs="?",
).complete = completion.DIR
cache_dir_parser.set_defaults(func=CmdCacheDir)
+10 -10
View File
@@ -62,9 +62,7 @@ class CmdCheckIgnore(CmdBase):
raise DvcException("cannot have both `targets` and `--stdin`")
if self.args.non_matching and not self.args.details:
raise DvcException(
"`--non-matching` is only valid with `--details`"
)
raise DvcException("`--non-matching` is only valid with `--details`")
if self.args.all and not self.args.details:
raise DvcException("`--all` is only valid with `--details`")
@@ -80,9 +78,7 @@ class CmdCheckIgnore(CmdBase):
def add_parser(subparsers, parent_parser):
ADD_HELP = (
"Check whether files or directories are excluded due to `.dvcignore`."
)
ADD_HELP = "Check whether files or directories are excluded due to `.dvcignore`."
parser = subparsers.add_parser(
"check-ignore",
@@ -103,16 +99,20 @@ def add_parser(subparsers, parent_parser):
"--all",
action="store_true",
default=False,
help="Include the target paths which don't match any pattern "
"in the `--details` list.",
help=(
"Include the target paths which don't match any pattern "
"in the `--details` list."
),
)
parser.add_argument(
"-n",
"--non-matching",
action="store_true",
default=False,
help="Include the target paths which don't match any pattern "
"in the `--details` list.",
help=(
"Include the target paths which don't match any pattern "
"in the `--details` list."
),
)
parser.add_argument(
"--stdin",
+2 -6
View File
@@ -22,9 +22,7 @@ def log_changes(stats):
continue
for entry in entries:
ui.write(
f"[{color}]{state[0].upper()}", entry, styled=True, sep="\t"
)
ui.write(f"[{color}]{state[0].upper()}", entry, styled=True, sep="\t")
class CmdCheckout(CmdBase):
@@ -46,9 +44,7 @@ class CmdCheckout(CmdBase):
if self.args.summary:
default_message = "No changes."
msg = get_summary(
sorted(stats.items(), key=operator.itemgetter(0))
)
msg = get_summary(sorted(stats.items(), key=operator.itemgetter(0)))
ui.write(msg or default_message)
else:
log_changes(stats)
+1 -5
View File
@@ -24,11 +24,7 @@ class CmdCommit(CmdBase):
force=self.args.force,
)
except DvcException:
logger.exception(
"failed to commit{}".format(
(" " + target) if target else ""
)
)
logger.exception("failed to commit%s", (" " + target) if target else "")
return 1
return 0
+3 -9
View File
@@ -17,9 +17,7 @@ def _name_type(value):
match = re.match(NAME_REGEX, value)
if not match:
raise argparse.ArgumentTypeError(
"name argument should look like "
"remote.name.option or "
"section.option"
"name argument should look like remote.name.option or section.option"
)
return (
bool(match.group("remote")),
@@ -70,9 +68,7 @@ class CmdConfig(CmdBaseNoRepo):
for level in levels:
conf = self.config.read(level)
prefix = self._config_file_prefix(
self.args.show_origin, self.config, level
)
prefix = self._config_file_prefix(self.args.show_origin, self.config, level)
configs = list(self._format_config(conf, prefix))
if configs:
ui.write("\n".join(configs))
@@ -131,9 +127,7 @@ class CmdConfig(CmdBaseNoRepo):
raise ConfigError(f"{name} '{section}' doesn't exist")
if opt and opt not in conf[section]:
raise ConfigError(
f"option '{opt}' doesn't exist in {name} '{section}'"
)
raise ConfigError(f"option '{opt}' doesn't exist in {name} '{section}'")
def _get_appropriate_levels(self, levels):
if levels:
+5 -5
View File
@@ -86,9 +86,7 @@ def _collect_targets(repo, target, outs):
targets.extend([str(out) for out in stage.outs])
continue
for out in outs_trie.itervalues( # noqa: B301
prefix=repo.fs.path.parts(path)
):
for out in outs_trie.itervalues(prefix=repo.fs.path.parts(path)): # noqa: B301
targets.extend(str(out))
return targets
@@ -204,7 +202,9 @@ def add_parser(subparsers, parent_parser):
dag_parser.add_argument(
"target",
nargs="?",
help="Stage name or output to show pipeline for. "
"Finds all stages in the workspace by default.",
help=(
"Stage name or output to show pipeline for. "
"Finds all stages in the workspace by default."
),
)
dag_parser.set_defaults(func=CmdDAG)
+10 -19
View File
@@ -8,11 +8,7 @@ from funcy import chunks, compact, log_durations
from dvc.cli import completion
from dvc.cli.actions import CommaSeparatedArgs
from dvc.cli.command import CmdBase
from dvc.cli.utils import (
append_doc_link,
fix_subparsers,
hide_subparsers_from_help,
)
from dvc.cli.utils import append_doc_link, fix_subparsers, hide_subparsers_from_help
from dvc.ui import ui
from dvc.utils import colorize
@@ -39,20 +35,19 @@ class CmdDataStatus(CmdBase):
}
HINTS = {
"not_in_cache": ('use "dvc fetch <file>..." to download files',),
"committed": (
"git commit the corresponding dvc files to update the repo",
),
"committed": ("git commit the corresponding dvc files to update the repo",),
"uncommitted": (
'use "dvc commit <file>..." to track changes',
'use "dvc checkout <file>..." to discard changes',
),
"untracked": (
'use "git add <file> ..." or '
'"dvc add <file>..." to commit to git or to dvc',
(
'use "git add <file> ..." or '
'"dvc add <file>..." to commit to git or to dvc'
),
),
"git_dirty": (
"there are {}changes not tracked by dvc, "
'use "git status" to see',
'there are {}changes not tracked by dvc, use "git status" to see',
),
}
@@ -96,8 +91,7 @@ class CmdDataStatus(CmdBase):
if isinstance(stage_status, dict):
items = [
": ".join([state, file])
for file, state in stage_status.items()
": ".join([state, file]) for file, state in stage_status.items()
]
else:
items = stage_status
@@ -180,9 +174,7 @@ class CmdDataLs(CmdBase):
filter_labels = set(self.args.labels)
filter_types = set(self.args.type)
d = ls(
self.repo, targets=self.args.targets, recursive=self.args.recursive
)
d = ls(self.repo, targets=self.args.targets, recursive=self.args.recursive)
self._show_table(
d,
filter_labels=filter_labels,
@@ -205,8 +197,7 @@ def add_parser(subparsers, parent_parser):
fix_subparsers(data_subparsers)
DATA_STATUS_HELP = (
"Show changes between the last git commit, "
"the dvcfiles and the workspace."
"Show changes between the last git commit, the dvcfiles and the workspace."
)
data_status_parser = data_subparsers.add_parser(
"status",
+13 -12
View File
@@ -262,9 +262,7 @@ def add_parser(subparsers, _parent_parser):
push_parser.set_defaults(func=CmdDataPush)
# Fetch
FETCH_HELP = (
"Download files or directories from remote storage to the cache."
)
FETCH_HELP = "Download files or directories from remote storage to the cache."
fetch_parser = subparsers.add_parser(
"fetch",
@@ -320,9 +318,7 @@ def add_parser(subparsers, _parent_parser):
fetch_parser.set_defaults(func=CmdDataFetch)
# Status
STATUS_HELP = (
"Show changed stages, compare local cache and a remote storage."
)
STATUS_HELP = "Show changed stages, compare local cache and a remote storage."
status_parser = subparsers.add_parser(
"status",
@@ -360,24 +356,29 @@ def add_parser(subparsers, _parent_parser):
"--all-branches",
action="store_true",
default=False,
help="Show status of a local cache compared to a remote repository "
"for all branches.",
help=(
"Show status of a local cache compared to a remote repository "
"for all branches."
),
)
status_parser.add_argument(
"-T",
"--all-tags",
action="store_true",
default=False,
help="Show status of a local cache compared to a remote repository "
"for all tags.",
help=(
"Show status of a local cache compared to a remote repository for all tags."
),
)
status_parser.add_argument(
"-A",
"--all-commits",
action="store_true",
default=False,
help="Show status of a local cache compared to a remote repository "
"for all commits.",
help=(
"Show status of a local cache compared to a remote repository "
"for all commits."
),
)
status_parser.add_argument(
"-d",
+9 -14
View File
@@ -112,9 +112,7 @@ class CmdDiff(CmdBase):
return
states_summary = ", ".join(
f"{summary[state]} {state}"
for state in states
if summary[state] > 0
f"{summary[state]} {state}" for state in states if summary[state] > 0
)
ui.write("files summary:", states_summary)
@@ -122,9 +120,7 @@ class CmdDiff(CmdBase):
from dvc.exceptions import DvcException
try:
diff = self.repo.diff(
self.args.a_rev, self.args.b_rev, self.args.targets
)
diff = self.repo.diff(self.args.a_rev, self.args.b_rev, self.args.targets)
show_hash = self.args.show_hash
hide_missing = self.args.b_rev or self.args.hide_missing
if hide_missing:
@@ -133,9 +129,11 @@ class CmdDiff(CmdBase):
for key, entries in diff.items():
entries = sorted(
entries,
key=lambda entry: entry["path"]["old"]
if isinstance(entry["path"], dict)
else entry["path"],
key=lambda entry: (
entry["path"]["old"]
if isinstance(entry["path"], dict)
else entry["path"]
),
)
if not show_hash:
for entry in entries:
@@ -170,10 +168,7 @@ def add_parser(subparsers, parent_parser):
diff_parser.add_argument(
"--targets",
nargs="*",
help=(
"Specific DVC-tracked files to compare. "
"Accepts one or more file paths."
),
help="Specific DVC-tracked files to compare. Accepts one or more file paths.",
metavar="<paths>",
).complete = completion.FILE
diff_parser.add_argument(
@@ -184,7 +179,7 @@ def add_parser(subparsers, parent_parser):
)
diff_parser.add_argument(
"b_rev",
help=("New Git commit to compare (defaults to the current workspace)"),
help="New Git commit to compare (defaults to the current workspace)",
nargs="?",
)
diff_parser.add_argument(
+2 -7
View File
@@ -1,10 +1,6 @@
import argparse
from dvc.cli.utils import (
append_doc_link,
fix_subparsers,
hide_subparsers_from_help,
)
from dvc.cli.utils import append_doc_link, fix_subparsers, hide_subparsers_from_help
from dvc.commands.experiments import (
apply,
branch,
@@ -54,8 +50,7 @@ def add_parser(subparsers, parent_parser):
experiments_subparsers = experiments_parser.add_subparsers(
dest="cmd",
help="Use `dvc experiments CMD --help` to display "
"command-specific help.",
help="Use `dvc experiments CMD --help` to display command-specific help.",
)
fix_subparsers(experiments_subparsers)
+2 -8
View File
@@ -10,19 +10,13 @@ logger = logging.getLogger(__name__)
class CmdExperimentsApply(CmdBase):
def run(self):
self.repo.experiments.apply(
self.args.experiment, force=self.args.force
)
self.repo.experiments.apply(self.args.experiment, force=self.args.force)
return 0
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_APPLY_HELP = (
"Apply the changes from an experiment to your workspace."
)
EXPERIMENTS_APPLY_HELP = "Apply the changes from an experiment to your workspace."
experiments_apply_parser = experiments_subparsers.add_parser(
"apply",
parents=[parent_parser],
+1 -5
View File
@@ -9,14 +9,12 @@ logger = logging.getLogger(__name__)
class CmdExperimentsBranch(CmdBase):
def run(self):
self.repo.experiments.branch(self.args.experiment, self.args.branch)
return 0
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_BRANCH_HELP = "Promote an experiment to a Git branch."
experiments_branch_parser = experiments_subparsers.add_parser(
"branch",
@@ -28,7 +26,5 @@ def add_parser(experiments_subparsers, parent_parser):
experiments_branch_parser.add_argument(
"experiment", help="Experiment to be promoted."
)
experiments_branch_parser.add_argument(
"branch", help="Name for the new Git branch"
)
experiments_branch_parser.add_argument("branch", help="Name for the new Git branch")
experiments_branch_parser.set_defaults(func=CmdExperimentsBranch)
+1 -5
View File
@@ -13,7 +13,6 @@ logger = logging.getLogger(__name__)
class CmdExperimentsDiff(CmdBase):
def run(self):
try:
diff = self.repo.experiments.diff(
a_rev=self.args.a_rev,
@@ -53,10 +52,7 @@ class CmdExperimentsDiff(CmdBase):
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_DIFF_HELP = (
"Show changes between experiments in the DVC repository."
)
EXPERIMENTS_DIFF_HELP = "Show changes between experiments in the DVC repository."
experiments_diff_parser = experiments_subparsers.add_parser(
"diff",
+1 -4
View File
@@ -9,10 +9,7 @@ class CmdExecutorRun(CmdBaseNoRepo):
"""Run an experiment executor."""
def run(self):
from dvc.repo.experiments.executor.base import (
BaseExecutor,
ExecutorInfo,
)
from dvc.repo.experiments.executor.base import BaseExecutor, ExecutorInfo
from dvc.utils.serialize import load_json
info = ExecutorInfo.from_dict(load_json(self.args.infofile))
+2 -7
View File
@@ -41,10 +41,7 @@ class CmdExperimentsGC(CmdBase):
msg += " and all git tags"
if self.args.commit_date:
msg += (
" and all git commits before date "
f"{self.args.commit_date}"
)
msg += f" and all git commits before date {self.args.commit_date}"
msg += " of the current repo."
if self.args.queued:
@@ -79,11 +76,9 @@ class CmdExperimentsGC(CmdBase):
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_GC_HELP = "Garbage collect unneeded experiments."
EXPERIMENTS_GC_DESCRIPTION = (
"Removes all experiments which are not derived from the specified"
"Git revisions."
"Removes all experiments which are not derived from the specifiedGit revisions."
)
experiments_gc_parser = experiments_subparsers.add_parser(
"gc",
+27 -18
View File
@@ -92,9 +92,7 @@ class CmdExperimentsInit(CmdBase):
if new_out_dirs:
out_dirs_paths = humanize.join(map(path_fmt, new_out_dirs))
ui.write(
f"Creating output directories: {out_dirs_paths}", styled=True
)
ui.write(f"Creating output directories: {out_dirs_paths}", styled=True)
ui.write(
f"Creating [b]{self.args.name}[/b] stage in [green]dvc.yaml[/]",
@@ -120,7 +118,6 @@ class CmdExperimentsInit(CmdBase):
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_INIT_HELP = "Quickly setup any project to use experiments."
experiments_init_parser = experiments_subparsers.add_parser(
"init",
@@ -167,35 +164,47 @@ def add_parser(experiments_subparsers, parent_parser):
)
experiments_init_parser.add_argument(
"--code",
help="Path to the source file or directory "
"which your experiments depend"
f" (default: {CmdExperimentsInit.CODE})",
help=(
"Path to the source file or directory "
"which your experiments depend"
f" (default: {CmdExperimentsInit.CODE})"
),
)
experiments_init_parser.add_argument(
"--data",
help="Path to the data file or directory "
"which your experiments depend"
f" (default: {CmdExperimentsInit.DATA})",
help=(
"Path to the data file or directory "
"which your experiments depend"
f" (default: {CmdExperimentsInit.DATA})"
),
)
experiments_init_parser.add_argument(
"--models",
help="Path to the model file or directory for your experiments"
f" (default: {CmdExperimentsInit.MODELS})",
help=(
"Path to the model file or directory for your experiments"
f" (default: {CmdExperimentsInit.MODELS})"
),
)
experiments_init_parser.add_argument(
"--params",
help="Path to the parameters file for your experiments"
f" (default: {CmdExperimentsInit.DEFAULT_PARAMS})",
help=(
"Path to the parameters file for your experiments"
f" (default: {CmdExperimentsInit.DEFAULT_PARAMS})"
),
)
experiments_init_parser.add_argument(
"--metrics",
help="Path to the metrics file for your experiments"
f" (default: {CmdExperimentsInit.DEFAULT_METRICS})",
help=(
"Path to the metrics file for your experiments"
f" (default: {CmdExperimentsInit.DEFAULT_METRICS})"
),
)
experiments_init_parser.add_argument(
"--plots",
help="Path to the plots file or directory for your experiments"
f" (default: {CmdExperimentsInit.PLOTS})",
help=(
"Path to the plots file or directory for your experiments"
f" (default: {CmdExperimentsInit.PLOTS})"
),
)
experiments_init_parser.add_argument(
"--live",
+3 -9
View File
@@ -11,9 +11,7 @@ logger = logging.getLogger(__name__)
class CmdExperimentsPull(CmdBase):
def raise_error_if_all_disabled(self):
if not any(
[self.args.experiment, self.args.all_commits, self.args.rev]
):
if not any([self.args.experiment, self.args.all_commits, self.args.rev]):
raise InvalidArgumentError(
"Either provide an `experiment` argument, or use the "
"`--rev` or `--all-commits` flag."
@@ -74,10 +72,7 @@ def add_parser(experiments_subparsers, parent_parser):
"--no-cache",
action="store_false",
dest="pull_cache",
help=(
"Do not pull cached outputs for this experiment from DVC remote "
"storage."
),
help="Do not pull cached outputs for this experiment from DVC remote storage.",
)
experiments_pull_parser.add_argument(
"-r",
@@ -92,8 +87,7 @@ def add_parser(experiments_subparsers, parent_parser):
type=int,
metavar="<number>",
help=(
"Number of jobs to run simultaneously when pulling from DVC "
"remote storage."
"Number of jobs to run simultaneously when pulling from DVC remote storage."
),
)
experiments_pull_parser.add_argument(
+7 -14
View File
@@ -12,16 +12,13 @@ logger = logging.getLogger(__name__)
class CmdExperimentsPush(CmdBase):
def raise_error_if_all_disabled(self):
if not any(
[self.args.experiment, self.args.all_commits, self.args.rev]
):
if not any([self.args.experiment, self.args.all_commits, self.args.rev]):
raise InvalidArgumentError(
"Either provide an `experiment` argument, or use the "
"`--rev` or `--all-commits` flag."
)
def run(self):
self.raise_error_if_all_disabled()
pushed_exps = self.repo.experiments.push(
@@ -47,8 +44,10 @@ class CmdExperimentsPush(CmdBase):
if not self.args.push_cache:
ui.write(
"To push cached outputs",
"for this experiment to DVC remote storage,"
"re-run this command without '--no-cache'.",
(
"for this experiment to DVC remote storage,"
"re-run this command without '--no-cache'."
),
)
return 0
@@ -76,10 +75,7 @@ def add_parser(experiments_subparsers, parent_parser):
"--no-cache",
action="store_false",
dest="push_cache",
help=(
"Do not push cached outputs for this experiment to DVC remote "
"storage."
),
help="Do not push cached outputs for this experiment to DVC remote storage.",
)
experiments_push_parser.add_argument(
"-r",
@@ -93,10 +89,7 @@ def add_parser(experiments_subparsers, parent_parser):
"--jobs",
type=int,
metavar="<number>",
help=(
"Number of jobs to run simultaneously when pushing to DVC remote "
"storage."
),
help="Number of jobs to run simultaneously when pushing to DVC remote storage.",
)
experiments_push_parser.add_argument(
"--run-cache",
-1
View File
@@ -24,7 +24,6 @@ class CmdExperimentsRemove(CmdBase):
)
def run(self):
self.check_arguments()
removed_list = self.repo.experiments.remove(
+2 -6
View File
@@ -17,13 +17,10 @@ class CmdExperimentsRun(CmdRepro):
if self.args.checkpoint_resume:
if self.args.reset:
raise InvalidArgumentError(
"--reset and --rev are mutually exclusive."
)
raise InvalidArgumentError("--reset and --rev are mutually exclusive.")
if not (self.args.queue or self.args.tmp_dir):
raise InvalidArgumentError(
"--rev can only be used in conjunction with "
"--queue or --temp."
"--rev can only be used in conjunction with --queue or --temp."
)
if self.args.reset:
@@ -51,7 +48,6 @@ class CmdExperimentsRun(CmdRepro):
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_RUN_HELP = "Run or resume an experiment."
experiments_run_parser = experiments_subparsers.add_parser(
"run",
-1
View File
@@ -11,7 +11,6 @@ logger = logging.getLogger(__name__)
class CmdExperimentsSave(CmdBase):
def run(self):
try:
ref = self.repo.experiments.save(
name=self.args.name,
+8 -23
View File
@@ -77,16 +77,14 @@ def _collect_rows( # noqa: C901
fill_value=FILL_VALUE,
iso=False,
):
from scmrepo.git import Git
from dvc.scm import Git
if sort_by:
sort_path, sort_name, sort_type = _sort_column(
sort_by, metric_names, param_names
)
reverse = sort_order == "desc"
experiments = _sort_exp(
experiments, sort_path, sort_name, sort_type, reverse
)
experiments = _sort_exp(experiments, sort_path, sort_name, sort_type, reverse)
new_checkpoint = True
for i, (rev, results) in enumerate(experiments.items()):
@@ -116,9 +114,7 @@ def _collect_rows( # noqa: C901
typ = "baseline"
elif tip:
if tip == parent_tip:
typ = (
"checkpoint_tip" if new_checkpoint else "checkpoint_commit"
)
typ = "checkpoint_tip" if new_checkpoint else "checkpoint_commit"
elif parent_rev == base_rev:
typ = "checkpoint_base"
else:
@@ -135,9 +131,7 @@ def _collect_rows( # noqa: C901
row_dict["Experiment"] = exp.get("name", "")
row_dict["rev"] = name_rev
row_dict["typ"] = typ
row_dict["Created"] = format_time(
exp.get("timestamp"), fill_value, iso
)
row_dict["Created"] = format_time(exp.get("timestamp"), fill_value, iso)
row_dict["parent"] = parent
row_dict["State"] = state
row_dict["Executor"] = exp.get("executor", fill_value)
@@ -371,9 +365,7 @@ def show_experiments( # noqa: C901
if not csv:
merge_headers = ["Experiment", "rev", "typ", "parent"]
td.column("Experiment")[:] = map(
prepare_exp_id, td.as_dict(merge_headers)
)
td.column("Experiment")[:] = map(prepare_exp_id, td.as_dict(merge_headers))
td.drop(*merge_headers[1:])
headers = {
@@ -410,9 +402,7 @@ def show_experiments( # noqa: C901
cols_to_drop = set()
if drop is not None:
cols_to_drop = {
col for col in td.keys() if re.match(drop, col) # noqa: SIM118
}
cols_to_drop = {col for col in td.keys() if re.match(drop, col)} # noqa: SIM118
if pcp:
cols_to_drop.add("Created")
td.drop(*cols_to_drop)
@@ -491,9 +481,7 @@ class CmdExperimentsShow(CmdBase):
ui.write_json(all_experiments, default=_format_json)
else:
precision = (
self.args.precision or None
if self.args.csv
else DEFAULT_PRECISION
self.args.precision or None if self.args.csv else DEFAULT_PRECISION
)
fill_value = "" if self.args.csv else FILL_VALUE
iso = self.args.csv
@@ -582,10 +570,7 @@ def add_parser(experiments_subparsers, parent_parser):
)
experiments_show_parser.add_argument(
"--sort-order",
help=(
"Sort order to use with --sort-by."
" Defaults to ascending ('asc')."
),
help="Sort order to use with --sort-by. Defaults to ascending ('asc').",
choices=("asc", "desc"),
default="asc",
)
+7 -10
View File
@@ -40,14 +40,9 @@ class CmdGC(CmdBase):
elif self.args.all_tags:
msg += " and all git tags"
if self.args.commit_date:
msg += (
" and all git commits before date "
f"{self.args.commit_date}"
)
msg += f" and all git commits before date {self.args.commit_date}"
if self.args.rev:
msg += (
f" and last {self.args.num} commits from {self.args.rev}"
)
msg += f" and last {self.args.num} commits from {self.args.rev}"
if self.args.all_experiments:
msg += " and all experiments"
@@ -198,9 +193,11 @@ def add_parser(subparsers, parent_parser):
dest="repos",
type=str,
nargs="*",
help="Keep data files required by these projects "
"in addition to the current one. "
"Useful if you share a single cache across repos.",
help=(
"Keep data files required by these projects "
"in addition to the current one. "
"Useful if you share a single cache across repos."
),
metavar="<paths>",
)
gc_parser.set_defaults(func=CmdGC)
+8 -10
View File
@@ -5,7 +5,6 @@ from dvc.cli import completion
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
from dvc.scm import CloneError
logger = logging.getLogger(__name__)
@@ -16,9 +15,7 @@ class CmdGet(CmdBaseNoRepo):
from dvc.ui import ui
try:
url = get_url(
self.args.path, repo=self.args.url, rev=self.args.rev
)
url = get_url(self.args.path, repo=self.args.url, rev=self.args.rev)
ui.write(url, force=True)
except DvcException:
logger.exception("failed to show URL")
@@ -34,6 +31,7 @@ class CmdGet(CmdBaseNoRepo):
def _get_file_from_repo(self):
from dvc.repo import Repo
from dvc.scm import CloneError
try:
Repo.get(
@@ -45,13 +43,11 @@ class CmdGet(CmdBaseNoRepo):
)
return 0
except CloneError:
logger.exception(f"failed to get '{self.args.path}'")
logger.exception("failed to get '%s'", self.args.path)
return 1
except DvcException:
logger.exception(
"failed to get '{}' from '{}'".format(
self.args.path, self.args.url
)
"failed to get '%s' from '%s'", self.args.path, self.args.url
)
return 1
@@ -87,8 +83,10 @@ def add_parser(subparsers, parent_parser):
get_parser.add_argument(
"--show-url",
action="store_true",
help="Print the storage location (URL) the target data would be "
"downloaded from, and exit.",
help=(
"Print the storage location (URL) the target data would be "
"downloaded from, and exit."
),
)
get_parser.add_argument(
"-j",
+10 -7
View File
@@ -5,13 +5,14 @@ from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
from dvc.scm import CloneError
logger = logging.getLogger(__name__)
class CmdImport(CmdBase):
def run(self):
from dvc.scm import CloneError
try:
self.repo.imp(
self.args.url,
@@ -28,13 +29,13 @@ class CmdImport(CmdBase):
jobs=self.args.jobs,
)
except CloneError:
logger.exception(f"failed to import '{self.args.path}'")
logger.exception("failed to import '%s'", self.args.path)
return 1
except DvcException:
logger.exception(
"failed to import '{}' from '{}'.".format(
self.args.path, self.args.url
)
"failed to import '%s' from '%s'.",
self.args.path,
self.args.url,
)
return 1
return 0
@@ -90,8 +91,10 @@ def add_parser(subparsers, parent_parser):
"--no-download",
action="store_true",
default=False,
help="Create .dvc file including target data hash value(s)"
" but do not actually download the file(s).",
help=(
"Create .dvc file including target data hash value(s)"
" but do not actually download the file(s)."
),
)
import_parser.add_argument(
"-j",
+23 -23
View File
@@ -29,10 +29,11 @@ class CmdImportUrl(CmdBase):
)
except DvcException:
logger.exception(
"failed to import {}. You could also try downloading "
"it manually, and adding it with `dvc add`.".format(
self.args.url
)
(
"failed to import %s. You could also try downloading "
"it manually, and adding it with `dvc add`."
),
self.args.url,
)
return 1
return 0
@@ -41,9 +42,7 @@ class CmdImportUrl(CmdBase):
def add_parser(subparsers, parent_parser):
from .add import _add_annotating_args
IMPORT_HELP = (
"Download or copy file from URL and take it under DVC control."
)
IMPORT_HELP = "Download or copy file from URL and take it under DVC control."
import_parser = subparsers.add_parser(
"import-url",
@@ -54,16 +53,18 @@ def add_parser(subparsers, parent_parser):
)
import_parser.add_argument(
"url",
help="Location of the data to download. Supported URLs:\n"
"/absolute/path/to/file/or/dir\n"
"relative/path/to/file/or/dir\n"
"C:\\\\path\\to\\file\\or\\dir\n"
"https://example.com/path/to/file\n"
"s3://bucket/key/path\n"
"gs://bucket/path/to/file/or/dir\n"
"hdfs://example.com/path/to/file\n"
"ssh://example.com/absolute/path/to/file/or/dir\n"
"remote://remote_name/path/to/file/or/dir (see `dvc remote`)",
help=(
"Location of the data to download. Supported URLs:\n"
"/absolute/path/to/file/or/dir\n"
"relative/path/to/file/or/dir\n"
"C:\\\\path\\to\\file\\or\\dir\n"
"https://example.com/path/to/file\n"
"s3://bucket/key/path\n"
"gs://bucket/path/to/file/or/dir\n"
"hdfs://example.com/path/to/file\n"
"ssh://example.com/absolute/path/to/file/or/dir\n"
"remote://remote_name/path/to/file/or/dir (see `dvc remote`)"
),
)
import_parser.add_argument(
"out", nargs="?", help="Destination path to put files to."
@@ -96,8 +97,10 @@ def add_parser(subparsers, parent_parser):
"--no-download",
action="store_true",
default=False,
help="Create .dvc file including target data hash value(s)"
" but do not actually download the file(s).",
help=(
"Create .dvc file including target data hash value(s)"
" but do not actually download the file(s)."
),
)
import_parser.add_argument(
"-j",
@@ -113,10 +116,7 @@ def add_parser(subparsers, parent_parser):
"--version-aware",
action="store_true",
default=False,
help=(
"Import using cloud versioning. Implied if the URL contains a "
"version ID."
),
help="Import using cloud versioning. Implied if the URL contains a version ID.",
)
_add_annotating_args(import_parser)
import_parser.set_defaults(func=CmdImportUrl)
+4 -4
View File
@@ -75,8 +75,9 @@ def add_parser(subparsers, parent_parser):
"--no-scm",
action="store_true",
default=False,
help="Initiate DVC in directory that is "
"not tracked by any SCM tool (e.g. Git).",
help=(
"Initiate DVC in directory that is not tracked by any SCM tool (e.g. Git)."
),
)
init_parser.add_argument(
"-f",
@@ -84,8 +85,7 @@ def add_parser(subparsers, parent_parser):
action="store_true",
default=False,
help=(
"Overwrite existing '.dvc/' directory. "
"This operation removes local cache."
"Overwrite existing '.dvc/' directory. This operation removes local cache."
),
)
init_parser.add_argument(
+4 -2
View File
@@ -31,7 +31,9 @@ def add_parser(subparsers, parent_parser):
"--use-pre-commit-tool",
action="store_true",
default=False,
help="Install DVC hooks using pre-commit "
"(https://pre-commit.com) if it is installed.",
help=(
"Install DVC hooks using pre-commit "
"(https://pre-commit.com) if it is installed."
),
)
install_parser.set_defaults(func=CmdInstall)
+1 -1
View File
@@ -20,7 +20,7 @@ class LsColors:
else:
self._codes[code] = color
def format(self, entry):
def format(self, entry): # noqa: A003
text = entry["path"]
if entry.get("isout", False) and "out" in self._codes:
+9 -28
View File
@@ -18,7 +18,6 @@ class MachineDisabledError(ConfigError):
class CmdMachineConfig(CmdConfig):
def __init__(self, args):
super().__init__(args)
if not self.config["feature"].get("machine", False):
raise MachineDisabledError
@@ -163,14 +162,11 @@ class CmdMachineRename(CmdBase):
all_config = self.config.load_config_to_level(None)
if self.args.new in all_config.get("machine", {}):
raise ConfigError(
"Rename failed. Machine '{}' already exists.".format(
self.args.new
)
f"Rename failed. Machine '{self.args.new}' already exists."
)
ui.write(f"Rename machine '{self.args.name}' to '{self.args.new}'.")
def run(self):
self._check_before_rename()
with self.config.edit(self.args.level) as conf:
@@ -209,9 +205,7 @@ class CmdMachineDefault(CmdMachineConfig):
if self.args.unset:
conf["core"].pop("machine", None)
else:
merged_conf = self.config.load_config_to_level(
self.args.level
)
merged_conf = self.config.load_config_to_level(self.args.level)
if (
self.args.name in conf["machine"]
or self.args.name in merged_conf["machine"]
@@ -219,8 +213,7 @@ class CmdMachineDefault(CmdMachineConfig):
conf["core"]["machine"] = self.args.name
else:
raise ConfigError(
"default machine must be present in machine "
"list."
"default machine must be present in machine list."
)
return 0
@@ -251,7 +244,6 @@ class CmdMachineStatus(CmdBase):
all_status: List[Dict],
td: TabularData,
):
if not all_status:
row = [
name,
@@ -343,8 +335,7 @@ def add_parser(subparsers, parent_parser):
"cloud",
help="Machine cloud. See full list of supported clouds at {}".format(
format_link(
"https://github.com/iterative/"
"terraform-provider-iterative#machine"
"https://github.com/iterative/terraform-provider-iterative#machine"
)
),
)
@@ -372,9 +363,7 @@ def add_parser(subparsers, parent_parser):
help=machine_DEFAULT_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
machine_default_parser.add_argument(
"name", nargs="?", help="Name of the machine"
)
machine_default_parser.add_argument("name", nargs="?", help="Name of the machine")
machine_default_parser.add_argument(
"-u",
"--unset",
@@ -414,9 +403,7 @@ def add_parser(subparsers, parent_parser):
formatter_class=argparse.RawDescriptionHelpFormatter,
)
machine_modify_parser.add_argument("name", help="Name of the machine")
machine_modify_parser.add_argument(
"option", help="Name of the option to modify."
)
machine_modify_parser.add_argument("option", help="Name of the option to modify.")
machine_modify_parser.add_argument(
"value", nargs="?", help="(optional) Value of the option."
)
@@ -449,9 +436,7 @@ def add_parser(subparsers, parent_parser):
help=machine_REMOVE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
machine_remove_parser.add_argument(
"name", help="Name of the machine to remove."
)
machine_remove_parser.add_argument("name", help="Name of the machine to remove.")
machine_remove_parser.set_defaults(func=CmdMachineRemove)
machine_CREATE_HELP = "Create and start a machine instance."
@@ -462,14 +447,10 @@ def add_parser(subparsers, parent_parser):
help=machine_CREATE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
machine_create_parser.add_argument(
"name", help="Name of the machine to create."
)
machine_create_parser.add_argument("name", help="Name of the machine to create.")
machine_create_parser.set_defaults(func=CmdMachineCreate)
machine_STATUS_HELP = (
"List the status of running instances for one/all machines."
)
machine_STATUS_HELP = "List the status of running instances for one/all machines."
machine_status_parser = machine_subparsers.add_parser(
"status",
parents=[parent_parser],
+2 -6
View File
@@ -14,9 +14,7 @@ class CmdMove(CmdBase):
try:
self.repo.move(self.args.src, self.args.dst)
except DvcException:
msg = "failed to move '{}' -> '{}'".format(
self.args.src, self.args.dst
)
msg = f"failed to move '{self.args.src}' -> '{self.args.dst}'"
logger.exception(msg)
return 1
return 0
@@ -40,7 +38,5 @@ def add_parser(subparsers, parent_parser):
move_parser.add_argument(
"src", help="Source path to a data file or directory."
).complete = completion.FILE
move_parser.add_argument(
"dst", help="Destination path."
).complete = completion.FILE
move_parser.add_argument("dst", help="Destination path.").complete = completion.FILE
move_parser.set_defaults(func=CmdMove)
+1 -1
View File
@@ -79,7 +79,7 @@ def add_parser(subparsers, parent_parser):
params_diff_parser.add_argument(
"b_rev",
nargs="?",
help=("New Git commit to compare (defaults to the current workspace)"),
help="New Git commit to compare (defaults to the current workspace)",
)
params_diff_parser.add_argument(
"--targets",
+14 -33
View File
@@ -18,9 +18,7 @@ logger = logging.getLogger(__name__)
def _show_json(renderers, split=False):
from dvc.render.convert import to_json
result = {
renderer.name: to_json(renderer, split) for renderer in renderers
}
result = {renderer.name: to_json(renderer, split) for renderer in renderers}
ui.write_json(result)
@@ -90,10 +88,8 @@ class CmdPlots(CmdBase):
)
if html_template_path and not os.path.isabs(html_template_path):
assert self.repo.dvc_dir
html_template_path = os.path.join(
self.repo.dvc_dir, html_template_path
)
return html_template_path
html_template_path = os.path.join(self.repo.dvc_dir, html_template_path)
return html_template_path # noqa: RET504
def run(self) -> int: # noqa: C901
from pathlib import Path
@@ -106,14 +102,11 @@ class CmdPlots(CmdBase):
logger.error("please specify a target for `--show-vega`")
return 1
if len(self.args.targets) > 1:
logger.error(
"you can only specify one target for `--show-vega`"
)
logger.error("you can only specify one target for `--show-vega`")
return 1
if self.args.json:
logger.error(
"'--show-vega' and '--json' are mutually exclusive "
"options."
"'--show-vega' and '--json' are mutually exclusive options."
)
return 1
@@ -126,17 +119,14 @@ class CmdPlots(CmdBase):
if not plots_data:
ui.error_write(
"No plots were loaded, "
"visualization file will not be created."
"No plots were loaded, visualization file will not be created."
)
out: str = self.args.out or self.repo.config.get("plots", {}).get(
"out_dir", "dvc_plots"
)
renderers_out = (
out if self.args.json else os.path.join(out, "static")
)
renderers_out = out if self.args.json else os.path.join(out, "static")
renderers = match_defs_renderers(
data=plots_data,
@@ -160,7 +150,7 @@ class CmdPlots(CmdBase):
render_html(
renderers=renderers,
output_file=output_file,
template_path=self._html_template_path(),
html_template=self._html_template_path(),
)
ui.write(output_file.as_uri())
@@ -249,8 +239,7 @@ def add_parser(subparsers, parent_parser):
fix_subparsers(plots_subparsers)
SHOW_HELP = (
"Generate plots from target files or from `plots`"
" definitions in `dvc.yaml`."
"Generate plots from target files or from `plots` definitions in `dvc.yaml`."
)
plots_show_parser = plots_subparsers.add_parser(
"show",
@@ -273,8 +262,7 @@ def add_parser(subparsers, parent_parser):
plots_show_parser.set_defaults(func=CmdPlotsShow)
PLOTS_DIFF_HELP = (
"Show multiple versions of a plot by overlaying them "
"in a single image."
"Show multiple versions of a plot by overlaying them in a single image."
)
plots_diff_parser = plots_subparsers.add_parser(
"diff",
@@ -332,9 +320,7 @@ def add_parser(subparsers, parent_parser):
)
plots_modify_parser.set_defaults(func=CmdPlotsModify)
TEMPLATES_HELP = (
"List built-in plots templates or show JSON specification for one."
)
TEMPLATES_HELP = "List built-in plots templates or show JSON specification for one."
plots_templates_parser = plots_subparsers.add_parser(
"templates",
parents=[parent_parser],
@@ -360,11 +346,8 @@ def _add_props_arguments(parser):
"--template",
nargs="?",
default=None,
help=(
"Special JSON or HTML schema file to inject with the data. "
"See {}".format(
format_link("https://man.dvc.org/plots#plot-templates")
)
help="Special JSON or HTML schema file to inject with the data. See {}".format(
format_link("https://man.dvc.org/plots#plot-templates")
),
metavar="<path>",
).complete = completion.FILE
@@ -381,9 +364,7 @@ def _add_props_arguments(parser):
default=None, # Use default None to distinguish when it's not used
help="Provided CSV or TSV datafile does not have a header.",
)
parser.add_argument(
"--title", default=None, metavar="<text>", help="Plot title."
)
parser.add_argument("--title", default=None, metavar="<text>", help="Plot title.")
parser.add_argument(
"--x-label", default=None, help="X axis label", metavar="<text>"
)
+1 -2
View File
@@ -20,8 +20,7 @@ class CmdQueueKill(CmdBase):
def add_parser(queue_subparsers, parent_parser):
QUEUE_KILL_HELP = (
"Gracefully interrupt running experiment queue tasks "
"(equivalent to Ctrl-C)"
"Gracefully interrupt running experiment queue tasks (equivalent to Ctrl-C)"
)
queue_kill_parser = queue_subparsers.add_parser(
"kill",
+1 -3
View File
@@ -34,9 +34,7 @@ def add_parser(queue_subparsers, parent_parser):
queue_logs_parser.add_argument(
"-e",
"--encoding",
help=(
"Text encoding for log output. Defaults to system locale encoding."
),
help="Text encoding for log output. Defaults to system locale encoding.",
metavar="<encoding>",
)
queue_logs_parser.add_argument(
-2
View File
@@ -28,7 +28,6 @@ class CmdQueueRemove(CmdBase):
)
def run(self):
self.check_arguments()
if self.args.all:
@@ -57,7 +56,6 @@ class CmdQueueRemove(CmdBase):
def add_parser(queue_subparsers, parent_parser):
QUEUE_REMOVE_HELP = "Remove queued and completed tasks from the queue."
queue_remove_parser = queue_subparsers.add_parser(
"remove",
+3 -11
View File
@@ -12,20 +12,15 @@ class CmdQueueStart(CmdBase):
"""Start exp queue workers."""
def run(self):
started = self.repo.experiments.celery_queue.start_workers(
self.args.jobs
)
started = self.repo.experiments.celery_queue.start_workers(self.args.jobs)
suffix = "s" if started > 1 else ""
ui.write(
f"Started '{started}' new experiments task queue worker{suffix}."
)
ui.write(f"Started '{started}' new experiments task queue worker{suffix}.")
return 0
def add_parser(queue_subparsers, parent_parser):
QUEUE_START_HELP = "Start the experiments task queue worker."
queue_start_parser = queue_subparsers.add_parser(
"start",
@@ -39,10 +34,7 @@ def add_parser(queue_subparsers, parent_parser):
"--jobs",
type=int,
default=1,
help=(
"Maximum number of concurrent queue workers to start. "
"Defaults to 1."
),
help="Maximum number of concurrent queue workers to start. Defaults to 1.",
metavar="<number>",
)
queue_start_parser.set_defaults(func=CmdQueueStart)
+2 -6
View File
@@ -39,9 +39,7 @@ class CmdQueueStatus(CmdBase):
ui.write()
worker_status = self.repo.experiments.celery_queue.worker_status()
active_count = len(
[name for name, task in worker_status.items() if task]
)
active_count = len([name for name, task in worker_status.items() if task])
idle_count = len(worker_status) - active_count
ui.write(f"Worker status: {active_count} active, {idle_count} idle")
@@ -50,9 +48,7 @@ class CmdQueueStatus(CmdBase):
def add_parser(queue_subparsers, parent_parser):
QUEUE_STATUS_HELP = (
"Show the status of experiments queue tasks and workers."
)
QUEUE_STATUS_HELP = "Show the status of experiments queue tasks and workers."
queue_status_parser = queue_subparsers.add_parser(
"status",
parents=[parent_parser],
-1
View File
@@ -26,7 +26,6 @@ class CmdQueueStop(CmdBase):
def add_parser(queue_subparsers, parent_parser):
QUEUE_STOP_HELP = "Stop all experiments task queue workers."
queue_stop_parser = queue_subparsers.add_parser(
"stop",
+4 -12
View File
@@ -95,9 +95,7 @@ class CmdRemoteDefault(CmdRemote):
if self.args.unset:
conf["core"].pop("remote", None)
else:
merged_conf = self.config.load_config_to_level(
self.args.level
)
merged_conf = self.config.load_config_to_level(self.args.level)
if (
self.args.name in conf["remote"]
or self.args.name in merged_conf["remote"]
@@ -208,9 +206,7 @@ def add_parser(subparsers, parent_parser):
help=REMOTE_DEFAULT_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
remote_default_parser.add_argument(
"name", nargs="?", help="Name of the remote"
)
remote_default_parser.add_argument("name", nargs="?", help="Name of the remote")
remote_default_parser.add_argument(
"-u",
"--unset",
@@ -229,9 +225,7 @@ def add_parser(subparsers, parent_parser):
formatter_class=argparse.RawDescriptionHelpFormatter,
)
remote_modify_parser.add_argument("name", help="Name of the remote")
remote_modify_parser.add_argument(
"option", help="Name of the option to modify."
)
remote_modify_parser.add_argument("option", help="Name of the option to modify.")
remote_modify_parser.add_argument(
"value", nargs="?", help="(optional) Value of the option."
)
@@ -262,9 +256,7 @@ def add_parser(subparsers, parent_parser):
help=REMOTE_REMOVE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
remote_remove_parser.add_argument(
"name", help="Name of the remote to remove."
)
remote_remove_parser.add_argument("name", help="Name of the remote to remove.")
remote_remove_parser.set_defaults(func=CmdRemoteRemove)
REMOTE_RENAME_HELP = "Rename a DVC remote"
remote_rename_parser = remote_subparsers.add_parser(
+1 -2
View File
@@ -22,8 +22,7 @@ class CmdRemove(CmdBase):
def add_parser(subparsers, parent_parser):
REMOVE_HELP = (
"Remove stages from dvc.yaml and/or"
" stop tracking files or directories."
"Remove stages from dvc.yaml and/or stop tracking files or directories."
)
remove_parser = subparsers.add_parser(
"remove",
+13 -18
View File
@@ -10,9 +10,7 @@ class CmdRepro(CmdBase):
def run(self):
from dvc.ui import ui
stages = self.repo.reproduce(
**self._common_kwargs, **self._repro_kwargs
)
stages = self.repo.reproduce(**self._common_kwargs, **self._repro_kwargs)
if len(stages) == 0:
ui.write(CmdDataStatus.UP_TO_DATE_MSG)
else:
@@ -55,16 +53,14 @@ def add_arguments(repro_parser):
repro_parser.add_argument(
"targets",
nargs="*",
help=(
"""\
help="""\
Stages to reproduce. 'dvc.yaml' by default.
The targets can be path to a dvc.yaml file or `.dvc` file,
or a stage name from dvc.yaml file from
current working directory. To run a stage from dvc.yaml
from other directories, the target must be a path followed by colon `:`
and then the stage name name.
"""
),
""",
).complete = completion.DVCFILES_AND_STAGE
repro_parser.add_argument(
"-f",
@@ -85,16 +81,14 @@ and then the stage name name.
"--single-item",
action="store_true",
default=False,
help="Reproduce only single data item without recursive dependencies "
"check.",
help="Reproduce only single data item without recursive dependencies check.",
)
repro_parser.add_argument(
"-p",
"--pipeline",
action="store_true",
default=False,
help="Reproduce the whole pipeline that the specified targets "
"belong to.",
help="Reproduce the whole pipeline that the specified targets belong to.",
)
repro_parser.add_argument(
"-P",
@@ -127,8 +121,10 @@ and then the stage name name.
"--force-downstream",
action="store_true",
default=False,
help="Reproduce all descendants of a changed stage even if their "
"direct dependencies didn't change.",
help=(
"Reproduce all descendants of a changed stage even if their "
"direct dependencies didn't change."
),
)
repro_parser.add_argument(
"--pull",
@@ -143,15 +139,14 @@ and then the stage name name.
"--dry",
action="store_true",
default=False,
help="Only print the commands that would be executed without "
"actually executing.",
help=(
"Only print the commands that would be executed without actually executing."
),
)
def add_parser(subparsers, parent_parser):
REPRO_HELP = (
"Reproduce complete or partial pipelines by executing their stages."
)
REPRO_HELP = "Reproduce complete or partial pipelines by executing their stages."
repro_parser = subparsers.add_parser(
"repro",
parents=[parent_parser],
+3 -7
View File
@@ -39,7 +39,7 @@ class CmdRun(CmdBase):
{
"cmd": parse_cmd(self.args.command),
"fname": kwargs.pop("file"),
"no_exec": (self.args.no_exec or bool(self.args.checkpoints)),
"no_exec": self.args.no_exec or bool(self.args.checkpoints),
"run_cache": not kwargs.pop("no_run_cache"),
}
)
@@ -55,9 +55,7 @@ class CmdRun(CmdBase):
def add_parser(subparsers, parent_parser):
from dvc.commands.stage import _add_common_args
RUN_HELP = (
"Generate a dvc.yaml file from a command and execute the command."
)
RUN_HELP = "Generate a dvc.yaml file from a command and execute the command."
run_parser = subparsers.add_parser(
"run",
parents=[parent_parser],
@@ -66,9 +64,7 @@ def add_parser(subparsers, parent_parser):
formatter_class=argparse.RawDescriptionHelpFormatter,
)
run_parser.add_argument("-n", "--name", help="Stage name.")
run_parser.add_argument(
"--file", metavar="<filename>", help=argparse.SUPPRESS
)
run_parser.add_argument("--file", metavar="<filename>", help=argparse.SUPPRESS)
run_parser.add_argument(
"--single-stage",
action="store_true",
+15 -17
View File
@@ -44,9 +44,7 @@ def generate_description(stage: "Stage") -> str:
return "; ".join(desc)
def prepare_description(
stage: "Stage", max_length: int = MAX_TEXT_LENGTH
) -> str:
def prepare_description(stage: "Stage", max_length: int = MAX_TEXT_LENGTH) -> str:
desc = stage.short_description() or generate_description(stage)
return truncate_text(desc, max_length)
@@ -57,9 +55,9 @@ def prepare_stages_data(
max_length: int = MAX_TEXT_LENGTH,
) -> Dict[str, str]:
return {
stage.addressing: prepare_description(stage, max_length=max_length)
if description
else ""
stage.addressing: (
prepare_description(stage, max_length=max_length) if description else ""
)
for stage in stages
}
@@ -75,9 +73,7 @@ class CmdStageList(CmdBase):
# removing duplicates while maintaining order
collected = chain.from_iterable(
self.repo.stage.collect(
target=target, recursive=self.args.recursive
)
self.repo.stage.collect(target=target, recursive=self.args.recursive)
for target in self.args.targets
)
return dict.fromkeys(collected).keys()
@@ -173,8 +169,7 @@ def _add_common_args(parser):
"--outs-no-cache",
action="append",
default=[],
help="Declare output file or directory "
"(do not put into DVC cache).",
help="Declare output file or directory (do not put into DVC cache).",
metavar="<filename>",
).complete = completion.FILE
parser.add_argument(
@@ -182,8 +177,10 @@ def _add_common_args(parser):
"--checkpoints",
action="append",
default=[],
help="Declare checkpoint output file or directory for 'dvc exp run'. "
"Not compatible with 'dvc repro'.",
help=(
"Declare checkpoint output file or directory for 'dvc exp run'. "
"Not compatible with 'dvc repro'."
),
metavar="<filename>",
).complete = completion.FILE
parser.add_argument(
@@ -196,16 +193,17 @@ def _add_common_args(parser):
"--outs-persist",
action="append",
default=[],
help="Declare output file or directory that will not be "
"removed upon repro.",
help="Declare output file or directory that will not be removed upon repro.",
metavar="<filename>",
)
parser.add_argument(
"--outs-persist-no-cache",
action="append",
default=[],
help="Declare output file or directory that will not be "
"removed upon repro (do not put into DVC cache).",
help=(
"Declare output file or directory that will not be "
"removed upon repro (do not put into DVC cache)."
),
metavar="<filename>",
)
parser.add_argument(
+1 -3
View File
@@ -81,9 +81,7 @@ class CmdDataStatus(CmdDataBase):
if not self.repo.index.stages:
ui.write(self.EMPTY_PROJECT_MSG)
elif self.args.cloud or self.args.remote:
remote = self.args.remote or self.repo.config["core"].get(
"remote"
)
remote = self.args.remote or self.repo.config["core"].get("remote")
ui.write(self.IN_SYNC_MSG.format(remote=remote))
else:
ui.write(self.UP_TO_DATE_MSG)
+5 -4
View File
@@ -31,8 +31,7 @@ class CmdUpdate(CmdBase):
def add_parser(subparsers, parent_parser):
UPDATE_HELP = (
"Update data artifact imported (via dvc import or dvc import-url) "
"from an external DVC repository or URL, or update data from a "
"DVC worktree remote."
"from an external DVC repository or URL."
)
update_parser = subparsers.add_parser(
"update",
@@ -61,8 +60,10 @@ def add_parser(subparsers, parent_parser):
"--no-download",
action="store_true",
default=False,
help="Update .dvc file git revision/hash value(s)"
" but do not download the file(s).",
help=(
"Update .dvc file git revision/hash value(s)"
" but do not download the file(s)."
),
)
update_parser.add_argument(
"--to-remote",
+1 -3
View File
@@ -21,9 +21,7 @@ class CmdVersion(CmdBaseNoRepo):
def add_parser(subparsers, parent_parser):
VERSION_HELP = (
"Display the DVC version and system/environment information."
)
VERSION_HELP = "Display the DVC version and system/environment information."
version_parser = subparsers.add_parser(
"version",
parents=[parent_parser],
+11 -28
View File
@@ -64,9 +64,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
def keys(self) -> List[str]:
return self._keys
def _iter_col_row(
self, row: Sequence["CellT"]
) -> Iterator[Tuple["CellT", Column]]:
def _iter_col_row(self, row: Sequence["CellT"]) -> Iterator[Tuple["CellT", Column]]:
for val, col in zip_longest(row, self.columns):
if col is None:
break
@@ -103,9 +101,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
...
@overload
def __setitem__(
self, item: slice, value: Iterable[Sequence["CellT"]]
) -> None:
def __setitem__(self, item: slice, value: Iterable[Sequence["CellT"]]) -> None:
...
def __setitem__(self, item, value) -> None:
@@ -113,8 +109,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
if isinstance(item, slice):
n = len(self.columns)
normalized_rows = (
chain(val, repeat(self._fill_value, n - len(val)))
for val in value
chain(val, repeat(self._fill_value, n - len(val))) for val in value
)
# we need to transpose those rows into columnar format
# as we work in terms of column-based arrays
@@ -212,9 +207,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
self, cols: Optional[Iterable[str]] = None
) -> Iterable[Dict[str, "CellT"]]:
keys = self.keys() if cols is None else set(cols)
return [
{k: self._columns[k][i] for k in keys} for i in range(len(self))
]
return [{k: self._columns[k][i] for k in keys} for i in range(len(self))]
def dropna( # noqa: C901
self,
@@ -224,13 +217,10 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
):
if axis not in ["rows", "cols"]:
raise ValueError(
f"Invalid 'axis' value {axis}."
"Choose one of ['rows', 'cols']"
f"Invalid 'axis' value {axis}.Choose one of ['rows', 'cols']"
)
if how not in ["any", "all"]:
raise ValueError(
f"Invalid 'how' value {how}. Choose one of ['any', 'all']"
)
raise ValueError(f"Invalid 'how' value {how}. Choose one of ['any', 'all']")
match_line: Set = set()
match_any = True
@@ -258,11 +248,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
if axis == "rows":
for name in self.keys(): # noqa: SIM118
self._columns[name] = Column(
[
x
for n, x in enumerate(self._columns[name])
if n not in to_drop
]
[x for n, x in enumerate(self._columns[name]) if n not in to_drop]
)
else:
self.drop(*to_drop)
@@ -275,8 +261,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
):
if axis not in ["rows", "cols"]:
raise ValueError(
f"Invalid 'axis' value {axis}."
"Choose one of ['rows', 'cols']"
f"Invalid 'axis' value {axis}.Choose one of ['rows', 'cols']"
)
if axis == "cols":
@@ -329,8 +314,8 @@ def _format_field(
def _format(_val):
if isinstance(_val, float) and precision:
if round_digits:
return round(val, precision)
return _normalize_float(val, precision)
return round(_val, precision)
return _normalize_float(_val, precision)
if isinstance(_val, abc.Mapping):
return {k: _format(v) for k, v in _val.items()}
if isinstance(_val, list):
@@ -362,9 +347,7 @@ def diff_table(
for item, change in sorted(diff_in_file.items()):
old_value = with_value(change.get("old"), fill_value)
new_value = with_value(change.get("new"), fill_value)
diff_value = with_value(
change.get("diff", on_empty_diff), fill_value
)
diff_value = with_value(change.get("diff", on_empty_diff), fill_value)
td.append(
[
fname,
+5 -6
View File
@@ -9,12 +9,12 @@ from typing import TYPE_CHECKING, Dict, Optional
from funcy import compact, memoize, re_find
from dvc.exceptions import DvcException, NotDvcRepoError
from dvc.types import DictStrAny, StrPath
from .utils.objects import cached_property
if TYPE_CHECKING:
from dvc.fs import FileSystem
from dvc.types import DictStrAny, StrPath
logger = logging.getLogger(__name__)
@@ -92,10 +92,10 @@ class Config(dict):
def __init__(
self,
dvc_dir: Optional[StrPath] = None,
dvc_dir: Optional["StrPath"] = None,
validate: bool = True,
fs: Optional["FileSystem"] = None,
config: Optional[DictStrAny] = None,
config: Optional["DictStrAny"] = None,
): # pylint: disable=super-init-not-called
from dvc.fs import LocalFileSystem
@@ -153,7 +153,7 @@ class Config(dict):
with open(config_file, "w+", encoding="utf-8"):
return Config(dvc_dir)
def load(self, validate: bool = True, config: Optional[DictStrAny] = None):
def load(self, validate: bool = True, config: Optional["DictStrAny"] = None):
"""Loads config from all the config files.
Raises:
@@ -374,6 +374,5 @@ def merge(into, update):
def _lower_keys(data):
return {
k.lower(): _lower_keys(v) if isinstance(v, dict) else v
for k, v in data.items()
k.lower(): _lower_keys(v) if isinstance(v, dict) else v for k, v in data.items()
}
+7 -16
View File
@@ -2,16 +2,7 @@ import os
from urllib.parse import urlparse
from funcy import walk_values
from voluptuous import (
All,
Any,
Coerce,
Invalid,
Lower,
Optional,
Range,
Schema,
)
from voluptuous import All, Any, Coerce, Invalid, Lower, Optional, Range, Schema
Bool = All(
Lower,
@@ -34,9 +25,7 @@ def supported_cache_type(types):
unsupported = set(types) - {"reflink", "hardlink", "symlink", "copy"}
if unsupported:
raise Invalid(
"Unsupported cache type(s): {}".format(", ".join(unsupported))
)
raise Invalid("Unsupported cache type(s): {}".format(", ".join(unsupported)))
return types
@@ -62,6 +51,8 @@ def ByUrl(mapping):
# Windows absolute paths should really have scheme == "" (local)
if os.name == "nt" and len(parsed.scheme) == 1 and parsed.netloc == "":
return schemas[""](data)
if parsed.netloc == "":
return schemas[""](data)
if parsed.scheme not in schemas:
raise Invalid(f"Unsupported URL type {parsed.scheme}://")
@@ -106,6 +97,7 @@ WEBDAV_COMMON = {
"password": str,
"ask_password": Bool,
"token": str,
"custom_auth_header": str,
"cert_path": str,
"key_path": str,
"timeout": Coerce(int),
@@ -257,9 +249,7 @@ SCHEMA = {
"machine": {
str: {
"cloud": All(Lower, Choices("aws", "azure")),
"region": All(
Lower, Choices("us-west", "us-east", "eu-west", "eu-north")
),
"region": All(Lower, Choices("us-west", "us-east", "eu-west", "eu-north")),
"image": str,
"spot": Bool,
"spot_price": Coerce(float),
@@ -274,6 +264,7 @@ SCHEMA = {
# section for experimental features
"feature": {
Optional("machine", default=False): Bool,
Optional("data_index_cache", default=False): Bool,
# enabled by default. It's of no use, kept for backward compatibility.
Optional("parametrization", default=True): Bool,
},
+2 -4
View File
@@ -40,9 +40,7 @@ def _spawn_windows(cmd, env):
startupinfo = STARTUPINFO()
startupinfo.dwFlags |= STARTF_USESHOWWINDOW
_popen(
cmd, env=env, creationflags=creationflags, startupinfo=startupinfo
)
_popen(cmd, env=env, creationflags=creationflags, startupinfo=startupinfo)
def _spawn_posix(cmd, env):
@@ -105,7 +103,7 @@ def daemon(args):
Args:
args (list): list of arguments to append to `dvc daemon` command.
"""
daemonize(["daemon", "-q"] + args)
daemonize(["daemon", "-q", *args])
def daemonize(cmd: List[str]):
+1 -3
View File
@@ -272,8 +272,6 @@ def draw(vertices, edges):
vertex.view.h,
)
canvas.text(
int(round(x - minx)) + 1, int(round(y - miny)) + 1, vertex.data
)
canvas.text(int(round(x - minx)) + 1, int(round(y - miny)) + 1, vertex.data)
return canvas.draw()
+31 -19
View File
@@ -8,6 +8,7 @@ from dvc.utils.objects import cached_property
from dvc_data.hashfile.db import get_index
if TYPE_CHECKING:
from dvc.fs import FileSystem
from dvc_data.hashfile.db import HashFileDB
from dvc_data.hashfile.hash_info import HashInfo
from dvc_data.hashfile.status import CompareStatusResult
@@ -17,20 +18,14 @@ logger = logging.getLogger(__name__)
class Remote:
def __init__(self, path, fs, **config):
def __init__(self, name: str, path: str, fs: "FileSystem", *, index=None, **config):
self.path = path
self.fs = fs
self.name = name
self.index = index
self.worktree = config.pop("worktree", False)
self.worktree: bool = config.pop("worktree", False)
self.config = config
if self.worktree:
version_aware = self.config.get("version_aware")
if version_aware is False:
raise RemoteConfigError(
"worktree remotes require version_aware cloud"
)
if version_aware is None:
self.fs.version_aware = True
@cached_property
def odb(self) -> "HashFileDB":
@@ -69,9 +64,23 @@ class DataCloud:
from dvc.fs import get_cloud_fs
cls, config, fs_path = get_cloud_fs(self.repo, name=name)
if config.get("worktree"):
version_aware = config.get("version_aware")
if version_aware is False:
raise RemoteConfigError(
"worktree remotes require version_aware cloud"
)
if version_aware is None:
config["version_aware"] = True
fs = cls(**config)
config["tmp_dir"] = self.repo.index_db_dir
return Remote(fs_path, fs, **config)
if self.repo.data_index is not None:
index = self.repo.data_index.view(("remotes", name))
else:
index = None
return Remote(name, fs_path, fs, index=index, **config)
if bool(self.repo.config["remote"]):
error_msg = (
@@ -107,8 +116,11 @@ class DataCloud:
for hash_info in status.missing
)
logger.warning(
"Some of the cache files do not exist neither locally "
f"nor on remote. Missing cache files:\n{missing_desc}"
(
"Some of the cache files do not exist neither locally "
"nor on remote. Missing cache files:\n%s"
),
missing_desc,
)
def transfer(
@@ -140,12 +152,12 @@ class DataCloud:
"""
odb = odb or self.get_remote_odb(remote, "push")
return self.transfer(
self.repo.odb.local,
self.repo.cache.local,
odb,
objs,
jobs=jobs,
dest_index=get_index(odb),
cache_odb=self.repo.odb.local,
cache_odb=self.repo.cache.local,
validate_status=self._log_missing,
)
@@ -168,11 +180,11 @@ class DataCloud:
odb = odb or self.get_remote_odb(remote, "pull")
return self.transfer(
odb,
self.repo.odb.local,
self.repo.cache.local,
objs,
jobs=jobs,
src_index=get_index(odb),
cache_odb=self.repo.odb.local,
cache_odb=self.repo.cache.local,
verify=odb.verify,
validate_status=self._log_missing,
)
@@ -199,12 +211,12 @@ class DataCloud:
if not odb:
odb = self.get_remote_odb(remote, "status")
return compare_status(
self.repo.odb.local,
self.repo.cache.local,
odb,
objs,
jobs=jobs,
dest_index=get_index(odb),
cache_odb=self.repo.odb.local,
cache_odb=self.repo.cache.local,
)
def get_url_for(self, remote, checksum):
+3 -5
View File
@@ -74,7 +74,7 @@ def _merge_params(s_list):
if not isinstance(key, dict):
msg = "Only list of str/dict is supported. Got: "
msg += f"'{type(key).__name__}'."
raise ValueError(msg)
raise ValueError(msg) # noqa: TRY004
for k, params in key.items():
if k in wholly_tracked:
@@ -83,13 +83,11 @@ def _merge_params(s_list):
if not isinstance(params, list):
msg = "Expected list of params for custom params file "
msg += f"'{k}', got '{type(params).__name__}'."
raise ValueError(msg)
raise ValueError(msg) # noqa: TRY004
d[k].extend(params)
return d
def loads_params(stage, s_list):
d = _merge_params(s_list)
return [
ParamsDependency(stage, path, params) for path, params in d.items()
]
return [ParamsDependency(stage, path, params) for path, params in d.items()]
+2 -6
View File
@@ -47,9 +47,7 @@ class Dependency(Output):
self.def_path = self.fs.path.version_path(
self.def_path, self.meta.version_id
)
self.fs_path = self.fs.path.version_path(
self.fs_path, self.meta.version_id
)
self.fs_path = self.fs.path.version_path(self.fs_path, self.meta.version_id)
def download(self, to, jobs=None):
fs_download(self.fs, self.fs_path, to, jobs=jobs)
@@ -57,9 +55,7 @@ class Dependency(Output):
def save(self):
super().save()
if self.fs.version_aware:
self.fs_path = self.fs.path.version_path(
self.fs_path, self.meta.version_id
)
self.fs_path = self.fs.path.version_path(self.fs_path, self.meta.version_id)
def dumpd(self, **kwargs):
if self.fs.version_aware:
+2 -5
View File
@@ -130,8 +130,7 @@ class ParamsDependency(Dependency):
and list(actual[param]) == info[param]
):
continue
else:
st = "modified"
st = "modified"
else:
continue
@@ -155,9 +154,7 @@ class ParamsDependency(Dependency):
try:
return load_path(self.fs_path, self.repo.fs)
except ParseError as exc:
raise BadParamFileError(
f"Unable to read parameters from '{self}'"
) from exc
raise BadParamFileError(f"Unable to read parameters from '{self}'") from exc
def get_hash(self):
info = self.read_params()
+16 -35
View File
@@ -1,28 +1,22 @@
import os
from collections import defaultdict
from copy import copy
from typing import (
TYPE_CHECKING,
ContextManager,
Dict,
Optional,
Set,
Tuple,
Union,
)
from typing import TYPE_CHECKING, Dict, Optional, Set, Tuple, Union
from voluptuous import Required
from dvc.prompt import confirm
from dvc_data.hashfile.meta import Meta
from .base import Dependency
if TYPE_CHECKING:
from typing import ContextManager
from dvc.output import Output
from dvc.repo import Repo
from dvc.stage import Stage
from dvc_data.hashfile.hash_info import HashInfo
from dvc_data.hashfile.meta import Meta
from dvc_data.hashfile.obj import HashFile
from dvc_objects.db import ObjectDB
@@ -41,15 +35,13 @@ class RepoDependency(Dependency):
}
}
def __init__(
self, def_repo: Dict[str, str], stage: "Stage", *args, **kwargs
):
def __init__(self, def_repo: Dict[str, str], stage: "Stage", *args, **kwargs):
self.def_repo = def_repo
self._objs: Dict[str, "HashFile"] = {}
self._meta: Dict[str, Meta] = {}
self._meta: Dict[str, "Meta"] = {}
super().__init__(stage, *args, **kwargs)
def _parse_path(self, fs, fs_path):
def _parse_path(self, fs, fs_path): # noqa: ARG002
return None
@property
@@ -88,7 +80,7 @@ class RepoDependency(Dependency):
to.fs_path,
to.fs,
obj,
self.repo.odb.local,
self.repo.cache.local,
ignore=None,
state=self.repo.state,
prompt=confirm,
@@ -106,22 +98,20 @@ class RepoDependency(Dependency):
# immutable, hence its impossible for checksum to change.
return False
def get_used_objs(
self, **kwargs
) -> Dict[Optional["ObjectDB"], Set["HashInfo"]]:
def get_used_objs(self, **kwargs) -> Dict[Optional["ObjectDB"], Set["HashInfo"]]:
used, _, _ = self._get_used_and_obj(**kwargs)
return used
def _get_used_and_obj(
self, obj_only: bool = False, **kwargs
) -> Tuple[Dict[Optional["ObjectDB"], Set["HashInfo"]], Meta, "HashFile"]:
) -> Tuple[Dict[Optional["ObjectDB"], Set["HashInfo"]], "Meta", "HashFile"]:
from dvc.config import NoRemoteError
from dvc.exceptions import NoOutputOrStageError, PathMissingError
from dvc.utils import as_posix
from dvc_data.hashfile.build import build
from dvc_data.hashfile.tree import Tree, TreeError
local_odb = self.repo.odb.local
local_odb = self.repo.cache.local
locked = kwargs.pop("locked", True)
with self._make_repo(locked=locked, cache_dir=local_odb.path) as repo:
used_obj_ids = defaultdict(set)
@@ -143,7 +133,7 @@ class RepoDependency(Dependency):
self._check_circular_import(odb, obj_ids)
used_obj_ids[odb].update(obj_ids)
except (NoRemoteError, NoOutputOrStageError):
pass
pass # noqa: S110
try:
object_store, meta, obj = build(
@@ -167,9 +157,7 @@ class RepoDependency(Dependency):
used_obj_ids[object_store].update(oid for _, _, oid in obj)
return used_obj_ids, meta, obj
def _check_circular_import(
self, odb: "ObjectDB", obj_ids: Set["HashInfo"]
) -> None:
def _check_circular_import(self, odb: "ObjectDB", obj_ids: Set["HashInfo"]) -> None:
from dvc.exceptions import CircularImportError
from dvc.fs.dvc import DVCFileSystem
from dvc_data.hashfile.db.reference import ReferenceHashFileDB
@@ -191,10 +179,7 @@ class RepoDependency(Dependency):
for obj in iter_objs():
if not isinstance(obj.fs, DVCFileSystem):
continue
if (
obj.fs.repo_url in checked_urls
or obj.fs.repo.root_dir in checked_urls
):
if obj.fs.repo_url in checked_urls or obj.fs.repo.root_dir in checked_urls:
continue
self_url = self.repo.url or self.repo.root_dir
if (
@@ -225,9 +210,7 @@ class RepoDependency(Dependency):
)
return meta
def _make_repo(
self, locked: bool = True, **kwargs
) -> "ContextManager[Repo]":
def _make_repo(self, locked: bool = True, **kwargs) -> "ContextManager[Repo]":
from dvc.external_repo import external_repo
d = self.def_repo
@@ -236,6 +219,4 @@ class RepoDependency(Dependency):
def _get_rev(self, locked: bool = True):
d = self.def_repo
return (d.get(self.PARAM_REV_LOCK) if locked else None) or d.get(
self.PARAM_REV
)
return (d.get(self.PARAM_REV_LOCK) if locked else None) or d.get(self.PARAM_REV)
+14 -29
View File
@@ -21,7 +21,6 @@ from dvc.stage.exceptions import (
StageFileDoesNotExistError,
StageFileIsNotDvcFileError,
)
from dvc.types import StrOrBytesPath
from dvc.utils import relpath
from dvc.utils.collections import apply_diff
from dvc.utils.objects import cached_property
@@ -29,6 +28,7 @@ from dvc.utils.serialize import dump_yaml, modify_yaml
if TYPE_CHECKING:
from dvc.repo import Repo
from dvc.types import StrOrBytesPath
from .parsing import DataResolver
from .stage import Stage
@@ -63,9 +63,7 @@ def is_valid_filename(path):
def is_dvc_file(path):
return os.path.isfile(path) and (
is_valid_filename(path) or is_lock_file(path)
)
return os.path.isfile(path) and (is_valid_filename(path) or is_lock_file(path))
def is_lock_file(path):
@@ -77,9 +75,7 @@ def is_git_ignored(repo, path):
from dvc.scm import NoSCMError
try:
return isinstance(repo.fs, LocalFileSystem) and repo.scm.is_ignored(
path
)
return isinstance(repo.fs, LocalFileSystem) and repo.scm.is_ignored(path)
except NoSCMError:
return False
@@ -152,9 +148,7 @@ class FileMixin:
# 4. when the file is git ignored
if not self.exists():
dvc_ignored = self.repo.dvcignore.is_ignored_file(self.path)
raise StageFileDoesNotExistError(
self.path, dvc_ignored=dvc_ignored
)
raise StageFileDoesNotExistError(self.path, dvc_ignored=dvc_ignored)
self._verify_filename()
if not self.repo.fs.isfile(self.path):
@@ -179,7 +173,8 @@ class FileMixin:
**kwargs,
)
def remove(self, force=False): # pylint: disable=unused-argument
# pylint: disable-next=unused-argument
def remove(self, force=False): # noqa: ARG002
with contextlib.suppress(FileNotFoundError):
os.unlink(self.path)
@@ -219,7 +214,8 @@ class SingleStageFile(FileMixin):
dump_yaml(self.path, serialize.to_single_stage_file(stage, **kwargs))
self.repo.scm_context.track_file(self.relpath)
def remove_stage(self, stage): # pylint: disable=unused-argument
# pylint: disable-next=unused-argument
def remove_stage(self, stage): # noqa: ARG002
self.remove()
def merge(self, ancestor, other, allowed=None):
@@ -269,9 +265,7 @@ class ProjectFile(FileMixin):
@staticmethod
def _check_if_parametrized(stage, action: str = "dump") -> None:
if stage.raw_data.parametrized:
raise ParametrizedDumpError(
f"cannot {action} a parametrized {stage}"
)
raise ParametrizedDumpError(f"cannot {action} a parametrized {stage}")
def _dump_pipeline_file(self, stage):
self._check_if_parametrized(stage)
@@ -284,9 +278,7 @@ class ProjectFile(FileMixin):
data["stages"] = data.get("stages", {})
existing_entry = stage.name in data["stages"]
action = "Modifying" if existing_entry else "Adding"
logger.info(
"%s stage '%s' in '%s'", action, stage.name, self.relpath
)
logger.info("%s stage '%s' in '%s'", action, stage.name, self.relpath)
if existing_entry:
orig_stage_data = data["stages"][stage.name]
@@ -298,9 +290,7 @@ class ProjectFile(FileMixin):
@property
def stage(self):
raise DvcException(
"ProjectFile has multiple stages. Please specify it's name."
)
raise DvcException("ProjectFile has multiple stages. Please specify it's name.")
@cached_property
def contents(self) -> Dict[str, Any]:
@@ -364,10 +354,7 @@ class ProjectFile(FileMixin):
def get_lockfile_schema(d):
from dvc.schema import (
COMPILED_LOCKFILE_V1_SCHEMA,
COMPILED_LOCKFILE_V2_SCHEMA,
)
from dvc.schema import COMPILED_LOCKFILE_V1_SCHEMA, COMPILED_LOCKFILE_V2_SCHEMA
schema = {
LOCKFILE_VERSION.V1: COMPILED_LOCKFILE_V1_SCHEMA,
@@ -420,9 +407,7 @@ class Lockfile(FileMixin):
with modify_yaml(self.path, fs=self.repo.fs) as data:
version = LOCKFILE_VERSION.from_dict(data)
if version == LOCKFILE_VERSION.V1:
logger.info(
"Migrating lock file '%s' from v1 to v2", self.relpath
)
logger.info("Migrating lock file '%s' from v1 to v2", self.relpath)
migrate_lock_v1_to_v2(data, self.latest_version_info)
else:
if not data:
@@ -465,7 +450,7 @@ class Lockfile(FileMixin):
def load_file(
repo: "Repo", path: StrOrBytesPath, **kwargs: Any
repo: "Repo", path: "StrOrBytesPath", **kwargs: Any
) -> Union[ProjectFile, SingleStageFile]:
_, ext = os.path.splitext(path)
if ext in (".yaml", ".yml"):
+9 -20
View File
@@ -150,8 +150,9 @@ class CyclicGraphError(DvcException):
class ConfirmRemoveError(DvcException):
def __init__(self, path):
super().__init__(
"unable to remove '{}' without a confirmation. Use "
"`-f` to force.".format(path)
"unable to remove '{}' without a confirmation. Use `-f` to force.".format(
path
)
)
@@ -177,9 +178,7 @@ class BadMetricError(DvcException):
class RecursiveAddingWhileUsingFilename(DvcException):
def __init__(self):
super().__init__(
"cannot use `fname` with multiple targets or `-R|--recursive`"
)
super().__init__("cannot use `fname` with multiple targets or `-R|--recursive`")
class OverlappingOutputPathsError(DvcException):
@@ -206,9 +205,7 @@ class FileMissingError(DvcException):
def __init__(self, path, hint=None):
self.path = path
hint = "" if hint is None else f". {hint}"
super().__init__(
f"Can't find '{path}' neither locally nor on remote{hint}"
)
super().__init__(f"Can't find '{path}' neither locally nor on remote{hint}")
class FileTransferError(DvcException):
@@ -250,9 +247,7 @@ class CollectCacheError(DvcException):
class NoRemoteInExternalRepoError(DvcException):
def __init__(self, url):
super().__init__(
f"No DVC remote is specified in target repository '{url}'."
)
super().__init__(f"No DVC remote is specified in target repository '{url}'.")
class NoOutputInExternalRepoError(DvcException):
@@ -277,8 +272,7 @@ class PathMissingError(DvcException):
" neither as a DVC output nor as a Git-tracked file."
)
default_msg_dvc_only = (
"The path '{}' does not exist in the target repository '{}'"
" as an DVC output."
"The path '{}' does not exist in the target repository '{}' as an DVC output."
)
def __init__(self, path, repo, dvc_only=False):
@@ -294,7 +288,6 @@ class URLMissingError(DvcException):
class RemoteCacheRequiredError(DvcException):
def __init__(self, scheme, fs_path):
super().__init__(
(
"Current operation was unsuccessful because '{}' requires "
@@ -319,8 +312,7 @@ class NoOutputOrStageError(DvcException):
def __init__(self, target, file):
super().__init__(
f"'{target}' "
f"does not exist as an output or a stage name in '{file}'"
f"'{target}' does not exist as an output or a stage name in '{file}'"
)
@@ -329,11 +321,8 @@ class MergeError(DvcException):
class CacheLinkError(DvcException):
SUPPORT_LINK = "See {} for more information.".format(
format_link(
"https://dvc.org/doc/user-guide/troubleshooting#cache-types"
)
format_link("https://dvc.org/doc/user-guide/troubleshooting#cache-types")
)
def __init__(self, fs_paths):
+11 -17
View File
@@ -17,11 +17,12 @@ from dvc.exceptions import (
)
from dvc.repo import Repo
from dvc.scm import CloneError, map_scm_exception
from dvc.types import StrPath
from dvc.utils import relpath
if TYPE_CHECKING:
from scmrepo import Git
from dvc.types import StrPath
from .scm import Git
logger = logging.getLogger(__name__)
@@ -32,14 +33,13 @@ def external_repo(
url,
rev: Optional[str] = None,
for_write: bool = False,
cache_dir: Optional[StrPath] = None,
cache_dir: Optional["StrPath"] = None,
cache_types: Optional[Collection[str]] = None,
**kwargs,
) -> Iterator["Repo"]:
from scmrepo.git import Git
from dvc.config import NoRemoteError
from dvc.fs import GitFileSystem
from dvc.scm import Git
logger.debug("Creating external repo %s@%s", url, rev)
path = _cached_clone(url, rev, for_write=for_write)
@@ -88,9 +88,7 @@ def external_repo(
raise NoRemoteInExternalRepoError(url) from exc
except OutputNotFoundError as exc:
if exc.repo is repo:
raise NoOutputInExternalRepoError(
exc.output, repo.root_dir, url
) from exc
raise NoOutputInExternalRepoError(exc.output, repo.root_dir, url) from exc
raise
except FileMissingError as exc:
raise PathMissingError(exc.path, url) from exc
@@ -151,7 +149,7 @@ def _get_remote_config(url):
name = "auto-generated-upstream"
return {
"core": {"remote": name},
"remote": {name: {"url": repo.odb.local.path}},
"remote": {name: {"url": repo.cache.local.path}},
}
# Use original remote to make sure that we are using correct url,
@@ -196,7 +194,7 @@ def _clone_default_branch(url, rev, for_write=False): # noqa: C901
The cloned is reactualized with git pull unless rev is a known sha.
"""
from scmrepo.git import Git
from dvc.scm import Git
clone_path, shallow = CLONES.get(url) or (None, False)
@@ -234,9 +232,7 @@ def _clone_default_branch(url, rev, for_write=False): # noqa: C901
os.path.join(clone_path, Git.GIT_DIR, "shallow")
)
if shallow:
logger.debug(
"erepo: using shallow clone for branch '%s'", rev
)
logger.debug("erepo: using shallow clone for branch '%s'", rev)
except CloneError:
git_dir = os.path.join(clone_path, ".git")
if os.path.exists(git_dir):
@@ -273,7 +269,7 @@ def _merge_upstream(git: "Git"):
def _git_checkout(repo_path, rev):
from scmrepo.git import Git
from dvc.scm import Git
logger.debug("erepo: git checkout %s@%s", repo_path, rev)
git = Git(repo_path)
@@ -292,8 +288,6 @@ def _remove(path):
try:
os_retry(remove)(path)
except PermissionError:
logger.warning(
"Failed to remove '%s'", relpath(path), exc_info=True
)
logger.warning("Failed to remove '%s'", relpath(path), exc_info=True)
else:
remove(path)
+1 -3
View File
@@ -72,9 +72,7 @@ def get_fs_config(config, **kwargs):
except KeyError:
from dvc.config import RemoteNotFoundError
raise RemoteNotFoundError( # noqa: B904
f"remote '{name}' doesn't exist"
)
raise RemoteNotFoundError(f"remote '{name}' doesn't exist") # noqa: B904
else:
remote_conf = kwargs
return _resolve_remote_refs(config, remote_conf)
+2 -4
View File
@@ -58,9 +58,7 @@ class RichCallback(Callback):
@cached_property
def task(self) -> "TaskID":
return self.progress.add_task(
**self._task_kwargs # type: ignore[arg-type]
)
return self.progress.add_task(**self._task_kwargs) # type: ignore[arg-type]
def __enter__(self):
return self
@@ -70,7 +68,7 @@ class RichCallback(Callback):
self.progress.clear_task(self.task)
self._stack.close()
def call(self, hook_name=None, **kwargs):
def call(self, hook_name=None, **kwargs): # noqa: ARG002
self.progress.update(
self.task,
completed=self.value,
+8 -25
View File
@@ -6,21 +6,11 @@ import os
import posixpath
import threading
from contextlib import suppress
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
Optional,
Tuple,
Type,
Union,
)
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple, Type, Union
from fsspec.spec import AbstractFileSystem
from funcy import wrap_with
from dvc.types import StrPath
from dvc_objects.fs.base import FileSystem
from dvc_objects.fs.path import Path
@@ -28,6 +18,7 @@ from .data import DataFileSystem
if TYPE_CHECKING:
from dvc.repo import Repo
from dvc.types import StrPath
logger = logging.getLogger(__name__)
@@ -126,9 +117,7 @@ class _DVCFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
super().__init__()
if repo is None:
repo = self._make_repo(
url=url, rev=rev, subrepos=subrepos, **repo_kwargs
)
repo = self._make_repo(url=url, rev=rev, subrepos=subrepos, **repo_kwargs)
assert repo is not None
# pylint: disable=protected-access
repo_factory = repo._fs_conf["repo_factory"]
@@ -144,9 +133,7 @@ class _DVCFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
relparts = ()
assert repo is not None
if repo.fs.path.isin(repo.fs.path.getcwd(), repo.root_dir):
relparts = repo.fs.path.relparts(
repo.fs.path.getcwd(), repo.root_dir
)
relparts = repo.fs.path.relparts(repo.fs.path.getcwd(), repo.root_dir)
return self.root_marker + self.sep.join(relparts)
self.path = Path(self.sep, getcwd=_getcwd)
@@ -166,7 +153,7 @@ class _DVCFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
if hasattr(repo, "dvc_dir"):
self._datafss[key] = DataFileSystem(index=repo.index.data["repo"])
def _get_key(self, path: StrPath) -> Key:
def _get_key(self, path: "StrPath") -> Key:
parts = self.repo.fs.path.relparts(path, self.repo.root_dir)
if parts == (os.curdir,):
return ()
@@ -222,9 +209,7 @@ class _DVCFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
scm=self.repo.scm,
repo_factory=self.repo_factory,
)
self._datafss[key] = DataFileSystem(
index=repo.index.data["repo"]
)
self._datafss[key] = DataFileSystem(index=repo.index.data["repo"])
self._subrepos_trie[key] = repo
def _is_dvc_repo(self, dir_path):
@@ -304,7 +289,7 @@ class _DVCFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
):
fs_infos[fs.path.name(info["name"])] = info
except (FileNotFoundError, NotADirectoryError):
pass
pass # noqa: S110
dvcfiles = kwargs.get("dvcfiles", False)
@@ -331,9 +316,7 @@ class _DVCFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
ignore_subrepos = kwargs.get("ignore_subrepos", True)
return self._info(key, path, ignore_subrepos=ignore_subrepos)
def _info( # noqa: C901
self, key, path, ignore_subrepos=True, check_ignored=True
):
def _info(self, key, path, ignore_subrepos=True, check_ignored=True): # noqa: C901
repo, dvc_fs, subkey = self._get_subrepo_info(key)
dvc_info = None
+2 -1
View File
@@ -5,9 +5,10 @@ from . import FileSystem
if TYPE_CHECKING:
from scmrepo.fs import GitFileSystem as FsspecGitFileSystem
from scmrepo.git import Git
from scmrepo.git.objects import GitTrie
from dvc.scm import Git
class GitFileSystem(FileSystem): # pylint:disable=abstract-method
"""Proxies the repo file access methods to Git objects"""
+18 -31
View File
@@ -3,15 +3,18 @@ import os
import re
from collections import namedtuple
from itertools import chain, groupby, takewhile
from typing import List, Optional
from typing import TYPE_CHECKING, List, Optional
from pathspec.patterns import GitWildMatchPattern
from pathspec.util import normalize_file
from pygtrie import Trie
from dvc.fs import AnyFSPath, FileSystem, Schemes, localfs
from dvc.fs import Schemes, localfs
from dvc.pathspec_math import PatternInfo, merge_patterns
if TYPE_CHECKING:
from dvc.fs import AnyFSPath, FileSystem
logger = logging.getLogger(__name__)
@@ -27,9 +30,7 @@ class DvcIgnorePatterns(DvcIgnore):
from pathspec.patterns.gitwildmatch import _DIR_MARK
if pattern_list and isinstance(pattern_list[0], str):
pattern_list = [
PatternInfo(pattern, "") for pattern in pattern_list
]
pattern_list = [PatternInfo(pattern, "") for pattern in pattern_list]
self.sep = sep
self.pattern_list = pattern_list
@@ -37,20 +38,14 @@ class DvcIgnorePatterns(DvcIgnore):
self.regex_pattern_list = []
for count, pattern in enumerate(pattern_list):
pattern, group = GitWildMatchPattern.pattern_to_regex(
pattern.patterns
)
pattern, group = GitWildMatchPattern.pattern_to_regex(pattern.patterns)
if pattern:
pattern = pattern.replace(
f"<{_DIR_MARK}>", f"<{_DIR_MARK}{count}>"
)
pattern = pattern.replace(f"<{_DIR_MARK}>", f"<{_DIR_MARK}{count}>")
self.regex_pattern_list.append((pattern, group))
self.ignore_spec = [
(ignore, re.compile("|".join(item[0] for item in group)))
for ignore, group in groupby(
self.regex_pattern_list, lambda x: x[1]
)
for ignore, group in groupby(self.regex_pattern_list, lambda x: x[1])
if ignore is not None
]
@@ -61,9 +56,7 @@ class DvcIgnorePatterns(DvcIgnore):
with fs.open(path, encoding="utf-8") as fobj:
path_spec_lines = [
PatternInfo(line, f"{name}:{line_no + 1}:{line}")
for line_no, line in enumerate(
map(str.strip, fobj.readlines())
)
for line_no, line in enumerate(map(str.strip, fobj.readlines()))
if line and not (line.strip().startswith("#"))
]
@@ -155,9 +148,7 @@ class DvcIgnorePatterns(DvcIgnore):
return bool(self.pattern_list)
CheckIgnoreResult = namedtuple(
"CheckIgnoreResult", ["file", "match", "patterns"]
)
CheckIgnoreResult = namedtuple("CheckIgnoreResult", ["file", "match", "patterns"])
def _no_match(path):
@@ -246,9 +237,7 @@ class DvcIgnoreFilter:
dnames = []
for dname in dnames:
self._update_sub_repo(
self.fs.path.join(dirname, dname), ignore_trie
)
self._update_sub_repo(self.fs.path.join(dirname, dname), ignore_trie)
def _update_sub_repo(self, path, ignore_trie: Trie):
from dvc.repo import Repo
@@ -306,7 +295,7 @@ class DvcIgnoreFilter:
return [fs_dict[name] for name in chain(dirs, nondirs)]
def walk(self, fs: FileSystem, path: AnyFSPath, **kwargs):
def walk(self, fs: "FileSystem", path: "AnyFSPath", **kwargs):
detail = kwargs.get("detail", False)
ignore_subrepos = kwargs.pop("ignore_subrepos", True)
if fs.protocol == Schemes.LOCAL:
@@ -330,7 +319,7 @@ class DvcIgnoreFilter:
else:
yield from fs.walk(path, **kwargs)
def find(self, fs: FileSystem, path: AnyFSPath, **kwargs):
def find(self, fs: "FileSystem", path: "AnyFSPath", **kwargs):
if fs.protocol == Schemes.LOCAL:
for root, _, files in self.walk(fs, path, **kwargs):
for file in files:
@@ -406,9 +395,7 @@ class DvcIgnoreFilter:
# https://github.com/iterative/dvc/issues/5046
full_target = self.fs.path.abspath(target)
if not self._outside_repo(full_target):
dirname, basename = self.fs.path.split(
self.fs.path.normpath(full_target)
)
dirname, basename = self.fs.path.split(self.fs.path.normpath(full_target))
pattern = self._get_trie_pattern(dirname)
if pattern:
matches = pattern.matches(
@@ -420,7 +407,7 @@ class DvcIgnoreFilter:
return _no_match(target)
def is_ignored(
self, fs: FileSystem, path: str, ignore_subrepos: bool = True
self, fs: "FileSystem", path: str, ignore_subrepos: bool = True
) -> bool:
# NOTE: can't use self.check_ignore(path).match for now, see
# https://github.com/iterative/dvc/issues/4555
@@ -430,9 +417,9 @@ class DvcIgnoreFilter:
return self.is_ignored_file(path, ignore_subrepos)
if fs.isdir(path):
return self.is_ignored_dir(path, ignore_subrepos)
return self.is_ignored_file(
return self.is_ignored_file(path, ignore_subrepos) or self.is_ignored_dir(
path, ignore_subrepos
) or self.is_ignored_dir(path, ignore_subrepos)
)
def init(path):
+10 -11
View File
@@ -22,15 +22,15 @@ SUBPROJECTS = (
"dvclive",
"scmrepo",
)
package = "" if PKG is None else f"({PKG})"
package = "" if PKG is None else f" ({PKG})"
def get_dvc_info():
dvc_version = f"DVC version: {__version__}{package}"
info = [
f"DVC version: {__version__} {package}",
"---------------------------------",
f"Platform: Python {platform.python_version()} on "
f"{platform.platform()}",
dvc_version,
"-" * len(dvc_version),
f"Platform: Python {platform.python_version()} on {platform.platform()}",
f"Subprojects:{_get_subprojects()}",
f"Supports:{_get_supported_remotes()}",
]
@@ -41,14 +41,14 @@ def get_dvc_info():
# can't auto-create it, as it might cause issues if the user
# later decides to enable shared cache mode with
# `dvc config cache.shared group`.
if os.path.exists(repo.odb.local.path):
if os.path.exists(repo.cache.local.path):
info.append(f"Cache types: {_get_linktype_support_info(repo)}")
fs_type = get_fs_type(repo.odb.local.path)
fs_type = get_fs_type(repo.cache.local.path)
info.append(f"Cache directory: {fs_type}")
else:
info.append("Cache types: " + error_link("no-dvc-cache"))
info.append(f"Caches: {_get_caches(repo.odb)}")
info.append(f"Caches: {_get_caches(repo.cache)}")
info.append(f"Remotes: {_get_remotes(repo.config)}")
root_directory = repo.root_dir
@@ -84,7 +84,7 @@ def _get_remotes(config):
def _get_linktype_support_info(repo):
odb = repo.odb.local
odb = repo.cache.local
links = generic.test_links(
["reflink", "hardlink", "symlink"],
@@ -124,8 +124,7 @@ def _get_supported_remotes():
dependencies = []
for requirement in fs_cls.REQUIRES:
dependencies.append(
f"{requirement} = "
f"{importlib_metadata.version(requirement)}"
f"{requirement} = {importlib_metadata.version(requirement)}"
)
remote_info = scheme
+4 -10
View File
@@ -20,9 +20,7 @@ FAILED_TO_LOCK_MESSAGE = (
"Unable to acquire lock. Most likely another DVC process is running or "
"was terminated abruptly. Check the page {} for other possible reasons "
"and to learn how to resolve this."
).format(
format_link("https://dvc.org/doc/user-guide/troubleshooting#lock-issue")
)
).format(format_link("https://dvc.org/doc/user-guide/troubleshooting#lock-issue"))
class LockError(DvcException):
@@ -61,9 +59,7 @@ class LockBase(ABC):
class LockNoop(LockBase):
def __init__(
self, *args, **kwargs
): # pylint: disable=super-init-not-called
def __init__(self, *args, **kwargs): # pylint: disable=super-init-not-called
self._lock = False
def lock(self):
@@ -107,10 +103,8 @@ class Lock(LockBase):
with Tqdm(
bar_format="{desc}",
disable=not self._friendly,
desc=(
"If DVC froze, see `hardlink_lock` in {}".format(
format_link("https://man.dvc.org/config#core")
)
desc="If DVC froze, see `hardlink_lock` in {}".format(
format_link("https://man.dvc.org/config#core")
),
):
self._lock = zc.lockfile.LockFile(self._lockfile)
+77 -159
View File
@@ -1,22 +1,14 @@
"""Manages logging configuration for DVC repo."""
import logging
import logging.config
import logging.handlers
import sys
import colorama
from dvc.progress import Tqdm
FOOTER = (
"\n{yellow}Having any troubles?{nc}"
" Hit us up at {blue}https://dvc.org/support{nc},"
" we are always happy to help!"
).format(
blue=colorama.Fore.BLUE,
nc=colorama.Fore.RESET,
yellow=colorama.Fore.YELLOW,
)
def addLoggingLevel(levelName, levelNum, methodName=None):
"""
@@ -66,12 +58,11 @@ class LoggingException(Exception):
super().__init__(msg)
def excludeFilter(level):
class ExcludeLevelFilter(logging.Filter):
def filter(self, record):
return record.levelno < level
def exclude_filter(level: int):
def filter_fn(record: "logging.LogRecord") -> bool:
return record.levelno < level
return ExcludeLevelFilter
return filter_fn
class ColorFormatter(logging.Formatter):
@@ -86,7 +77,8 @@ class ColorFormatter(logging.Formatter):
exception cause, just the message and the traceback.
"""
color_code = {
reset = colorama.Fore.RESET
color_codes = {
"TRACE": colorama.Fore.GREEN,
"DEBUG": colorama.Fore.BLUE,
"WARNING": colorama.Fore.YELLOW,
@@ -94,47 +86,46 @@ class ColorFormatter(logging.Formatter):
"CRITICAL": colorama.Fore.RED,
}
def format(self, record):
def __init__(self, log_colors: bool = True) -> None:
super().__init__()
self.log_colors = log_colors
def format(self, record) -> str: # noqa: A003, C901
record.message = record.getMessage()
msg = self.formatMessage(record)
if record.levelname == "INFO":
if record.levelno == logging.INFO:
return msg
if record.exc_info:
if getattr(record, "tb_only", False):
cause = ""
else:
cause = ": ".join(_iter_causes(record.exc_info[1]))
ei = record.exc_info
if ei:
cause = ""
if not getattr(record, "tb_only", False):
cause = ": ".join(_iter_causes(ei[1]))
sep = " - " if msg and cause else ""
msg = msg + sep + cause
msg = "{message}{separator}{cause}".format(
message=msg or "",
separator=" - " if msg and cause else "",
cause=cause,
)
asctime = ""
if _is_verbose():
asctime = self.formatTime(record, self.datefmt)
if ei and not record.exc_text:
record.exc_text = self.formatException(ei)
if record.exc_text:
if msg[-1:] != "\n":
msg = msg + "\n"
msg = msg + record.exc_text + "\n"
if record.stack_info:
if msg[-1:] != "\n":
msg = msg + "\n"
msg = msg + self.formatStack(record.stack_info) + "\n"
if _is_verbose():
msg += _stack_trace(record.exc_info)
return "{asctime}{color}{levelname}{nc}: {msg}".format(
asctime=self.formatTime(record, self.datefmt),
color=self.color_code[record.levelname],
nc=colorama.Fore.RESET,
levelname=record.levelname,
msg=msg,
)
def formatTime(self, record, datefmt=None):
# only show if current level is set to DEBUG
# also, skip INFO as it is used for UI
if not _is_verbose() or record.levelno == logging.INFO:
return ""
return "{green}{date}{nc} ".format(
green=colorama.Fore.GREEN,
date=super().formatTime(record, datefmt),
nc=colorama.Fore.RESET,
)
level = record.levelname
if self.log_colors:
color = self.color_codes[level]
if asctime:
asctime = color + asctime + self.reset
level = color + level + self.reset
return asctime + (" " if asctime else "") + level + ": " + msg
class LoggerHandler(logging.StreamHandler):
@@ -156,12 +147,10 @@ class LoggerHandler(logging.StreamHandler):
if not _is_verbose():
return
except Exception: # noqa, pylint: disable=broad-except
pass
pass # noqa: S110
msg = self.format(record)
Tqdm.write(
msg, file=self.stream, end=getattr(self, "terminator", "\n")
)
Tqdm.write(msg, file=self.stream, end=getattr(self, "terminator", "\n"))
self.flush()
except (BrokenPipeError, RecursionError):
raise
@@ -171,9 +160,7 @@ class LoggerHandler(logging.StreamHandler):
def _is_verbose():
return (
logging.NOTSET
< logging.getLogger("dvc").getEffectiveLevel()
<= logging.DEBUG
logging.NOTSET < logging.getLogger("dvc").getEffectiveLevel() <= logging.DEBUG
)
@@ -183,36 +170,44 @@ def _iter_causes(exc):
exc = exc.__cause__
def _stack_trace(exc_info):
import traceback
return (
"\n"
"{red}{line}{nc}\n"
"{trace}"
"{red}{line}{nc}".format(
red=colorama.Fore.RED,
line="-" * 60,
trace="".join(traceback.format_exception(*exc_info)),
nc=colorama.Fore.RESET,
)
)
def disable_other_loggers():
logging.captureWarnings(True)
loggerDict = logging.root.manager.loggerDict # pylint: disable=no-member
for logger_name, logger in loggerDict.items():
if logger_name != "dvc" and not logger_name.startswith("dvc."):
logger.disabled = True # type: ignore[union-attr]
def set_loggers_level(level: int = logging.INFO) -> None:
for name in ["dvc", "dvc_objects", "dvc_data"]:
logging.getLogger(name).setLevel(level)
def setup(level: int = logging.INFO) -> None:
def setup(level: int = logging.INFO, log_colors: bool = True) -> None:
colorama.init()
formatter = ColorFormatter(log_colors=log_colors and sys.stdout.isatty())
console_info = LoggerHandler(sys.stdout)
console_info.setLevel(logging.INFO)
console_info.setFormatter(formatter)
console_info.addFilter(exclude_filter(logging.WARNING))
console_debug = LoggerHandler(sys.stdout)
console_debug.setLevel(logging.DEBUG)
console_debug.setFormatter(formatter)
console_debug.addFilter(exclude_filter(logging.INFO))
addLoggingLevel("TRACE", logging.DEBUG - 5)
console_trace = LoggerHandler(sys.stdout)
console_trace.setLevel(logging.TRACE) # type: ignore[attr-defined]
console_trace.setFormatter(formatter)
console_trace.addFilter(exclude_filter(logging.DEBUG))
err_formatter = ColorFormatter(log_colors=log_colors and sys.stderr.isatty())
console_errors = LoggerHandler(sys.stderr)
console_errors.setLevel(logging.WARNING)
console_errors.setFormatter(err_formatter)
for name in ["dvc", "dvc_objects", "dvc_data"]:
logger = logging.getLogger(name)
logger.setLevel(level)
for handler in [console_info, console_debug, console_trace, console_errors]:
logger.addHandler(handler)
if level >= logging.DEBUG:
# Unclosed session errors for asyncio/aiohttp are only available
# on the tracing mode for extensive debug purposes. They are really
@@ -223,80 +218,3 @@ def setup(level: int = logging.INFO) -> None:
# action.
logging.getLogger("asyncio").setLevel(logging.CRITICAL)
logging.getLogger("aiohttp").setLevel(logging.CRITICAL)
addLoggingLevel("TRACE", logging.DEBUG - 5)
logging.config.dictConfig(
{
"version": 1,
"filters": {
"exclude_errors": {"()": excludeFilter(logging.WARNING)},
"exclude_info": {"()": excludeFilter(logging.INFO)},
"exclude_debug": {"()": excludeFilter(logging.DEBUG)},
},
"formatters": {"color": {"()": ColorFormatter}},
"handlers": {
"console_info": {
"class": "dvc.logger.LoggerHandler",
"level": "INFO",
"formatter": "color",
"stream": "ext://sys.stdout",
"filters": ["exclude_errors"],
},
"console_debug": {
"class": "dvc.logger.LoggerHandler",
"level": "DEBUG",
"formatter": "color",
"stream": "ext://sys.stdout",
"filters": ["exclude_info"],
},
"console_trace": {
"class": "dvc.logger.LoggerHandler",
"level": "TRACE",
"formatter": "color",
"stream": "ext://sys.stdout",
"filters": ["exclude_debug"],
},
"console_errors": {
"class": "dvc.logger.LoggerHandler",
"level": "WARNING",
"formatter": "color",
"stream": "ext://sys.stderr",
},
},
"loggers": {
"dvc": {
"level": level,
"handlers": [
"console_info",
"console_debug",
"console_trace",
"console_errors",
],
},
"dvc_objects": {
"level": level,
"handlers": [
"console_info",
"console_debug",
"console_trace",
"console_errors",
],
},
"dvc_data": {
"level": level,
"handlers": [
"console_info",
"console_debug",
"console_trace",
"console_errors",
],
},
},
"disable_existing_loggers": False,
}
)
# NOTE: colorama init needs to go after logging config to avoid potential
# conflicts with libs that modify logging (like tensorflow/abseil)
# https://github.com/iterative/dvc/issues/8387
colorama.init()
+3 -5
View File
@@ -13,10 +13,10 @@ from typing import (
)
from dvc.exceptions import DvcException
from dvc.types import StrPath
if TYPE_CHECKING:
from dvc.repo import Repo
from dvc.types import StrPath
from .backend.base import BaseMachineBackend
@@ -67,16 +67,14 @@ class MachineBackends(Mapping):
initialized = self.initialized.get(key)
if not initialized:
backend = self.backends[key]
initialized = backend(
os.path.join(self.tmp_dir, key), **self.kwargs
)
initialized = backend(os.path.join(self.tmp_dir, key), **self.kwargs)
self.initialized[key] = initialized
return initialized
def __init__(
self,
selected: Optional[Iterable[str]],
tmp_dir: StrPath,
tmp_dir: "StrPath",
**kwargs,
) -> None:
selected = selected or list(self.DEFAULT)
+1 -3
View File
@@ -21,9 +21,7 @@ class BaseMachineBackend(ABC):
"""Stop and destroy all instances of the specified machine."""
@abstractmethod
def instances(
self, name: Optional[str] = None, **config
) -> Iterator[dict]:
def instances(self, name: Optional[str] = None, **config) -> Iterator[dict]:
"""Iterate over status of all instances of the specified machine."""
def close(self): # noqa: B027
+2 -6
View File
@@ -56,12 +56,8 @@ class TerraformBackend(BaseMachineBackend):
create = partialmethod(_tpi_func, "create") # type: ignore[assignment]
destroy = partialmethod(_tpi_func, "destroy") # type: ignore[assignment]
instances = partialmethod(
_tpi_func, "instances"
) # type: ignore[assignment]
run_shell = partialmethod(
_tpi_func, "run_shell"
) # type: ignore[assignment]
instances = partialmethod(_tpi_func, "instances") # type: ignore[assignment]
run_shell = partialmethod(_tpi_func, "run_shell") # type: ignore[assignment]
def get_executor_kwargs(self, name: str, **config) -> dict:
with self.make_tpi(name) as tpi:
+142 -123
View File
@@ -1,11 +1,13 @@
import logging
import os
import posixpath
from collections import defaultdict
from operator import itemgetter
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, cast
from urllib.parse import urlparse
from funcy import collecting, project
from voluptuous import And, Any, Coerce, Length, Lower, Required, SetTo
from funcy import collecting, first, project
from voluptuous import All, And, Any, Coerce, Length, Lower, Required, SetTo
from dvc import prompt
from dvc.exceptions import (
@@ -27,20 +29,21 @@ from dvc_data.hashfile.hash_info import HashInfo
from dvc_data.hashfile.istextfile import istextfile
from dvc_data.hashfile.meta import Meta
from dvc_data.hashfile.transfer import transfer as otransfer
from dvc_data.index import DataIndexEntry
from dvc_objects.errors import ObjectFormatError
from .annotations import ANNOTATION_FIELDS, ANNOTATION_SCHEMA, Annotation
from .fs import LocalFileSystem, RemoteMissingDepsError, Schemes, get_cloud_fs
from .fs.callbacks import DEFAULT_CALLBACK, Callback
from .fs.callbacks import DEFAULT_CALLBACK
from .utils import relpath
from .utils.fs import path_isin
if TYPE_CHECKING:
from dvc_data.hashfile.db import HashFileDB
from dvc_data.hashfile.obj import HashFile
from dvc_data.index import DataIndexKey
from dvc_objects.db import ObjectDB
from .fs.callbacks import Callback
from .ignore import DvcIgnoreFilter
logger = logging.getLogger(__name__)
@@ -150,11 +153,11 @@ def _merge_data(s_list):
d[key].update({})
continue
if not isinstance(key, dict):
raise ValueError(f"'{type(key).__name__}' not supported.")
raise ValueError(f"'{type(key).__name__}' not supported.") # noqa: TRY004
for k, flags in key.items():
if not isinstance(flags, dict):
raise ValueError(
raise ValueError( # noqa: TRY004
f"Expected dict for '{k}', got: '{type(flags).__name__}'"
)
d[k].update(flags)
@@ -204,6 +207,41 @@ def load_from_pipeline(stage, data, typ="outs"):
)
def split_file_meta_from_cloud(entry: Dict) -> Dict:
if remote_name := entry.pop(Meta.PARAM_REMOTE, None):
remote_meta = {}
for key in (
S3_PARAM_CHECKSUM,
HDFS_PARAM_CHECKSUM,
Meta.PARAM_VERSION_ID,
):
if value := entry.pop(key, None):
remote_meta[key] = value
if remote_meta:
entry[Output.PARAM_CLOUD] = {remote_name: remote_meta}
return entry
def merge_file_meta_from_cloud(entry: Dict) -> Dict:
cloud_meta = entry.pop(Output.PARAM_CLOUD, {})
if remote_name := first(cloud_meta):
entry.update(cloud_meta[remote_name])
entry[Meta.PARAM_REMOTE] = remote_name
return entry
def _serialize_tree_obj_to_files(obj: "Tree") -> List[Dict[str, Any]]:
key = obj.PARAM_RELPATH
return sorted(
(
{key: posixpath.sep.join(parts), **hi.to_dict(), **meta.to_dict()}
for parts, meta, hi in obj
),
key=itemgetter(key),
)
class OutputDoesNotExistError(DvcException):
def __init__(self, path):
msg = f"output '{path}' does not exist"
@@ -258,6 +296,7 @@ class Output:
PARAM_PERSIST = "persist"
PARAM_REMOTE = "remote"
PARAM_PUSH = "push"
PARAM_CLOUD = "cloud"
METRIC_SCHEMA = Any(
None,
@@ -297,17 +336,18 @@ class Output:
desc=desc, type=type, labels=labels or [], meta=meta or {}
)
self.repo = stage.repo if not repo and stage else repo
meta = Meta.from_dict(info or {})
meta_d = merge_file_meta_from_cloud(info or {})
meta = Meta.from_dict(meta_d)
# NOTE: when version_aware is not passed into get_cloud_fs, it will be
# set based on whether or not path is versioned
fs_kwargs = {"version_aware": True} if meta.version_id else {}
fs_kwargs = {}
if meta.version_id or files:
fs_kwargs["version_aware"] = True
if fs_config is not None:
fs_kwargs.update(**fs_config)
fs_cls, fs_config, fs_path = get_cloud_fs(
self.repo, url=path, **fs_kwargs
)
fs_cls, fs_config, fs_path = get_cloud_fs(self.repo, url=path, **fs_kwargs)
self.fs = fs_cls(**fs_config)
if (
@@ -340,6 +380,9 @@ class Output:
# should be absolute and don't contain remote:// refs.
self.stage = stage
self.meta = meta
if files is not None:
files = [merge_file_meta_from_cloud(f) for f in files]
self.files = files
self.use_cache = False if self.IS_DEPENDENCY else cache
self.metric = False if self.IS_DEPENDENCY else metric
@@ -351,6 +394,7 @@ class Output:
self.fs_path = self._parse_path(self.fs, fs_path)
self.obj: Optional["HashFile"] = None
self.odb: Optional["HashFileDB"] = None
self.remote = remote
if self.fs.version_aware:
@@ -368,7 +412,23 @@ class Output:
name=self.hash_name,
value=getattr(self.meta, self.hash_name, None),
)
if self.meta.nfiles or self.hash_info and self.hash_info.isdir:
self._compute_meta_hash_info_from_files()
def _compute_meta_hash_info_from_files(self) -> None:
if self.files:
from dvc_data.hashfile.db import HashFileDB
tree = Tree.from_list(self.files, hash_name=self.hash_name)
tree.digest(with_meta=True)
self.odb = HashFileDB(tree.fs, tree.path + ".odb")
self.odb.add(tree.path, tree.fs, tree.hash_info.value)
self.hash_info = tree.hash_info
self.meta.isdir = True
self.meta.nfiles = len(self.files)
self.meta.size = sum(f.get("size") for f in self.files)
self.meta.remote = first(f.get("remote") for f in self.files)
elif self.meta.nfiles or self.hash_info and self.hash_info.isdir:
self.meta.isdir = True
if not self.hash_info and self.hash_name != "md5":
md5 = getattr(self.meta, "md5", None)
@@ -446,17 +506,17 @@ class Output:
return self.use_cache or self.stage.is_repo_import
@property
def odb(self):
def cache(self):
odb_name = "repo" if self.is_in_repo else self.protocol
odb = getattr(self.repo.odb, odb_name)
odb = getattr(self.repo.cache, odb_name)
if self.use_cache and odb is None:
raise RemoteCacheRequiredError(self.fs.protocol, self.fs_path)
return odb
@property
def cache_path(self):
return self.odb.fs.unstrip_protocol(
self.odb.oid_to_path(self.hash_info.value)
return self.cache.fs.unstrip_protocol(
self.cache.oid_to_path(self.hash_info.value)
)
def get_hash(self):
@@ -465,9 +525,9 @@ class Output:
def _get_hash_meta(self):
if self.use_cache:
odb = self.odb
odb = self.cache
else:
odb = self.repo.odb.local
odb = self.repo.cache.local
_, meta, obj = build(
odb,
self.fs_path,
@@ -509,36 +569,6 @@ class Output:
key = self.fs.path.parts(no_drive)[1:]
return workspace, key
def get_entry(self) -> "DataIndexEntry":
from dvc.config import NoRemoteError
try:
remote = self.repo.cloud.get_remote_odb(self.remote)
except NoRemoteError:
remote = None
if self.files and not self.obj:
self.obj = self.get_obj()
entry = DataIndexEntry(
meta=self.meta,
obj=self.obj,
hash_info=self.hash_info,
odb=self.odb,
cache=self.odb,
remote=remote,
)
if self.stage.is_import and not self.stage.is_repo_import:
dep = self.stage.deps[0]
entry.fs = dep.fs
entry.path = dep.fs_path
entry.meta = dep.meta
if not entry.obj:
if not dep.obj and dep.files:
dep.obj = dep.get_obj()
entry.obj = dep.obj
return entry
def changed_checksum(self):
return self.hash_info != self.get_hash()
@@ -551,7 +581,7 @@ class Output:
return True
try:
ocheck(self.odb, obj)
ocheck(self.cache, obj)
return False
except (FileNotFoundError, ObjectFormatError):
return True
@@ -640,7 +670,7 @@ class Output:
if self.use_cache:
_, self.meta, self.obj = build(
self.odb,
self.cache,
self.fs_path,
self.fs,
self.hash_name,
@@ -648,7 +678,7 @@ class Output:
)
else:
_, self.meta, self.obj = build(
self.repo.odb.local,
self.repo.cache.local,
self.fs_path,
self.fs,
self.hash_name,
@@ -656,16 +686,14 @@ class Output:
dry_run=True,
)
if not self.IS_DEPENDENCY:
logger.debug(
"Output '%s' doesn't use cache. Skipping saving.", self
)
logger.debug("Output '%s' doesn't use cache. Skipping saving.", self)
self.hash_info = self.obj.hash_info
self.files = None
def set_exec(self) -> None:
if self.isfile() and self.meta.isexec:
self.odb.set_exec(self.fs_path)
self.cache.set_exec(self.fs_path)
def _checkout(self, *args, **kwargs) -> Optional[bool]:
from dvc_data.hashfile.checkout import CheckoutError as _CheckoutError
@@ -689,15 +717,13 @@ class Output:
if self.use_cache:
granular = (
self.is_dir_checksum
and filter_info
and filter_info != self.fs_path
self.is_dir_checksum and filter_info and filter_info != self.fs_path
)
if granular:
obj = self._commit_granular_dir(filter_info)
else:
staging, _, obj = build(
self.odb,
self.cache,
filter_info or self.fs_path,
self.fs,
self.hash_name,
@@ -705,7 +731,7 @@ class Output:
)
otransfer(
staging,
self.odb,
self.cache,
{obj.hash_info},
shallow=False,
hardlink=True,
@@ -714,7 +740,7 @@ class Output:
filter_info or self.fs_path,
self.fs,
obj,
self.odb,
self.cache,
relink=True,
state=self.repo.state,
prompt=prompt.confirm,
@@ -722,11 +748,9 @@ class Output:
self.set_exec()
def _commit_granular_dir(self, filter_info) -> Optional["HashFile"]:
prefix = self.fs.path.parts(
self.fs.path.relpath(filter_info, self.fs_path)
)
prefix = self.fs.path.parts(self.fs.path.relpath(filter_info, self.fs_path))
staging, _, save_obj = build(
self.odb,
self.cache,
self.fs_path,
self.fs,
self.hash_name,
@@ -734,10 +758,10 @@ class Output:
)
save_obj = cast(Tree, save_obj)
save_obj = cast(Tree, save_obj.filter(prefix))
checkout_obj = save_obj.get_obj(self.odb, prefix)
checkout_obj = save_obj.get_obj(self.cache, prefix)
otransfer(
staging,
self.odb,
self.cache,
{save_obj.hash_info} | {oid for _, _, oid in save_obj},
shallow=True,
hardlink=True,
@@ -745,14 +769,21 @@ class Output:
return checkout_obj
def dumpd(self, **kwargs): # noqa: C901
meta = self.meta.to_dict()
meta.pop("isdir", None)
ret: Dict[str, Any] = {**self.hash_info.to_dict(), **meta}
ret: Dict[str, Any] = {}
with_files = (
(not self.IS_DEPENDENCY or self.stage.is_import)
and self.hash_info.isdir
and (kwargs.get("with_files") or self.files is not None)
)
if not with_files:
meta_d = self.meta.to_dict()
meta_d.pop("isdir", None)
ret.update(self.hash_info.to_dict())
ret.update(split_file_meta_from_cloud(meta_d))
if self.is_in_repo:
path = self.fs.path.as_posix(
relpath(self.fs_path, self.stage.wdir)
)
path = self.fs.path.as_posix(relpath(self.fs_path, self.stage.wdir))
else:
path = self.def_path
@@ -788,27 +819,19 @@ class Output:
if not self.can_push:
ret[self.PARAM_PUSH] = self.can_push
if (
(not self.IS_DEPENDENCY or self.stage.is_import)
and self.hash_info.isdir
and (kwargs.get("with_files") or self.files is not None)
):
obj: Optional["HashFile"]
if self.obj:
obj = self.obj
else:
obj = self.get_obj()
if with_files:
obj = self.obj or self.get_obj()
if obj:
obj = cast(Tree, obj)
ret[self.PARAM_FILES] = obj.as_list(with_meta=True)
obj = cast("Tree", obj)
ret[self.PARAM_FILES] = [
split_file_meta_from_cloud(f)
for f in _serialize_tree_obj_to_files(obj)
]
return ret
def verify_metric(self):
if self.fs.protocol != "local":
raise DvcException(
f"verify metric is not supported for {self.protocol}"
)
raise DvcException(f"verify metric is not supported for {self.protocol}")
if not self.metric:
return
@@ -831,16 +854,15 @@ class Output:
obj: Optional["HashFile"] = None
if self.obj:
obj = self.obj
elif self.files:
tree = Tree.from_list(self.files, hash_name=self.hash_name)
tree.digest()
obj = tree
elif self.hash_info:
if self.files:
tree = Tree.from_list(self.files, hash_name=self.hash_name)
tree.digest()
obj = tree
else:
try:
obj = oload(self.odb, self.hash_info)
except (FileNotFoundError, ObjectFormatError):
return None
try:
obj = oload(self.cache, self.hash_info)
except (FileNotFoundError, ObjectFormatError):
return None
else:
return None
@@ -849,7 +871,7 @@ class Output:
if filter_info and filter_info != self.fs_path:
prefix = fs_path.relparts(filter_info, self.fs_path)
obj = cast(Tree, obj)
obj = obj.get_obj(self.odb, prefix)
obj = obj.get_obj(self.cache, prefix)
return obj
@@ -865,9 +887,7 @@ class Output:
) -> Optional[Tuple[bool, Optional[bool]]]:
if not self.use_cache:
if progress_callback != DEFAULT_CALLBACK:
progress_callback.relative_update(
self.get_files_number(filter_info)
)
progress_callback.relative_update(self.get_files_number(filter_info))
return None
obj = self.get_obj(filter_info=filter_info)
@@ -887,7 +907,7 @@ class Output:
filter_info or self.fs_path,
self.fs,
obj,
self.odb,
self.cache,
force=force,
progress_callback=progress_callback,
relink=relink,
@@ -928,7 +948,7 @@ class Output:
self, source, odb=None, jobs=None, update=False, no_progress_bar=False
):
if odb is None:
odb = self.odb
odb = self.cache
cls, config, from_info = get_cloud_fs(self.repo, url=source)
from_fs = cls(**config)
@@ -980,15 +1000,15 @@ class Output:
def unprotect(self):
if self.exists:
self.odb.unprotect(self.fs_path)
self.cache.unprotect(self.fs_path)
def get_dir_cache(self, **kwargs):
if not self.is_dir_checksum:
raise DvcException("cannot get dir cache for file checksum")
obj = self.odb.get(self.hash_info.value)
obj = self.cache.get(self.hash_info.value)
try:
ocheck(self.odb, obj)
ocheck(self.cache, obj)
except FileNotFoundError:
if self.remote:
kwargs["remote"] = self.remote
@@ -998,7 +1018,7 @@ class Output:
return self.obj
try:
self.obj = oload(self.odb, self.hash_info)
self.obj = oload(self.cache, self.hash_info)
except (FileNotFoundError, ObjectFormatError):
self.obj = None
@@ -1017,7 +1037,7 @@ class Output:
logger.debug("failed to pull cache for '%s'", self)
try:
ocheck(self.odb, self.odb.get(self.hash_info.value))
ocheck(self.cache, self.cache.get(self.hash_info.value))
except FileNotFoundError:
msg = (
"Missing cache for directory '{}'. "
@@ -1034,9 +1054,7 @@ class Output:
obj = self.get_obj()
if filter_info and filter_info != self.fs_path:
assert obj
prefix = self.fs.path.parts(
self.fs.path.relpath(filter_info, self.fs_path)
)
prefix = self.fs.path.parts(self.fs.path.relpath(filter_info, self.fs_path))
obj = cast(Tree, obj)
return obj.filter(prefix)
return cast(Tree, obj)
@@ -1083,7 +1101,7 @@ class Output:
else:
obj = self.get_obj(filter_info=kwargs.get("filter_info"))
if not obj:
obj = self.odb.get(self.hash_info.value)
obj = self.cache.get(self.hash_info.value)
if not obj:
return {}
@@ -1144,14 +1162,10 @@ class Output:
other.pop(opt, None)
if my != other:
raise MergeError(
"unable to auto-merge outputs with different options"
)
raise MergeError("unable to auto-merge outputs with different options")
if not out.is_dir_checksum:
raise MergeError(
"unable to auto-merge outputs that are not directories"
)
raise MergeError("unable to auto-merge outputs that are not directories")
def merge(self, ancestor, other, allowed=None):
from dvc_data.hashfile.tree import MergeError as TreeMergeError
@@ -1170,7 +1184,7 @@ class Output:
try:
merged = merge(
self.odb,
self.cache,
ancestor_info,
self.hash_info,
other.hash_info,
@@ -1179,12 +1193,12 @@ class Output:
except TreeMergeError as exc:
raise MergeError(str(exc)) from exc
self.odb.add(merged.path, merged.fs, merged.oid)
self.cache.add(merged.path, merged.fs, merged.oid)
self.hash_info = merged.hash_info
self.files = None
self.meta = Meta(
size=du(self.odb, merged),
size=du(self.cache, merged),
nfiles=len(merged),
)
@@ -1226,7 +1240,8 @@ class Output:
if not self.obj or not other.hash_info.isdir:
return
other_obj = other.obj if other.obj is not None else other.get_obj()
assert isinstance(self.obj, Tree) and isinstance(other_obj, Tree)
assert isinstance(self.obj, Tree)
assert isinstance(other_obj, Tree)
updated = update_meta(self.obj, other_obj)
assert updated.hash_info == self.obj.hash_info
self.obj = updated
@@ -1240,6 +1255,8 @@ META_SCHEMA = {
Meta.PARAM_VERSION_ID: str,
}
CLOUD_SCHEMA = All({str: {**META_SCHEMA, **CHECKSUMS_SCHEMA}}, Length(max=1))
ARTIFACT_SCHEMA = {
**CHECKSUMS_SCHEMA,
**META_SCHEMA,
@@ -1247,12 +1264,14 @@ ARTIFACT_SCHEMA = {
Output.PARAM_PLOT: bool,
Output.PARAM_PERSIST: bool,
Output.PARAM_CHECKPOINT: bool,
Output.PARAM_CLOUD: CLOUD_SCHEMA,
}
DIR_FILES_SCHEMA: Dict[str, Any] = {
**CHECKSUMS_SCHEMA,
**META_SCHEMA,
Required(Tree.PARAM_RELPATH): str,
Output.PARAM_CLOUD: CLOUD_SCHEMA,
}
SCHEMA = {
+27 -33
View File
@@ -26,7 +26,6 @@ from .context import (
KeyNotInContext,
MergeError,
Node,
SeqOrMap,
VarsAlreadyLoaded,
)
from .interpolate import (
@@ -40,8 +39,10 @@ if TYPE_CHECKING:
from typing import NoReturn
from dvc.repo import Repo
from dvc.types import DictStrAny
from .context import SeqOrMap
from dvc.types import DictStrAny
logger = logging.getLogger(__name__)
@@ -71,9 +72,7 @@ def _format_preamble(msg: str, path: str, spacing: str = " ") -> str:
def format_and_raise(exc: Exception, msg: str, path: str) -> "NoReturn":
spacing = (
"\n"
if isinstance(exc, (ParseError, MergeError, VarsAlreadyLoaded))
else " "
"\n" if isinstance(exc, (ParseError, MergeError, VarsAlreadyLoaded)) else " "
)
message = _format_preamble(msg, path, spacing) + str(exc)
@@ -92,7 +91,7 @@ def _reraise_err(
def check_syntax_errors(
definition: DictStrAny, name: str, path: str, where: str = "stages"
definition: "DictStrAny", name: str, path: str, where: str = "stages"
):
for key, d in definition.items():
try:
@@ -111,12 +110,11 @@ def split_foreach_name(name: str) -> Tuple[str, Optional[str]]:
return group, first(keys)
def check_interpolations(data: DictStrAny, where: str, path: str):
def func(s: DictStrAny) -> None:
def check_interpolations(data: "DictStrAny", where: str, path: str):
def func(s: "DictStrAny") -> None:
if is_interpolated_string(s):
raise ResolveError(
_format_preamble(f"'{where}'", path)
+ "interpolating is not allowed"
_format_preamble(f"'{where}'", path) + "interpolating is not allowed"
)
return recurse(func)(data)
@@ -126,7 +124,7 @@ Definition = Union["ForeachDefinition", "EntryDefinition"]
def make_definition(
resolver: "DataResolver", name: str, definition: DictStrAny, **kwargs
resolver: "DataResolver", name: str, definition: "DictStrAny", **kwargs
) -> Definition:
args = resolver, resolver.context, name, definition
if FOREACH_KWD in definition:
@@ -187,9 +185,7 @@ class DataResolver:
def resolve(self):
"""Used for testing purposes, otherwise use resolve_one()."""
data = join(map(self.resolve_one, self.get_keys()))
logger.trace( # type: ignore[attr-defined]
"Resolved dvc.yaml:\n%s", data
)
logger.trace("Resolved dvc.yaml:\n%s", data) # type: ignore[attr-defined]
return {STAGES_KWD: data}
def has_key(self, key: str):
@@ -202,9 +198,9 @@ class DataResolver:
return False
if key:
return isinstance(
definition, ForeachDefinition
) and definition.has_member(key)
return isinstance(definition, ForeachDefinition) and definition.has_member(
key
)
return not isinstance(definition, ForeachDefinition)
@collecting
@@ -225,7 +221,7 @@ class EntryDefinition:
resolver: DataResolver,
context: Context,
name: str,
definition: DictStrAny,
definition: "DictStrAny",
where: str = STAGES_KWD,
):
self.resolver = resolver
@@ -254,7 +250,7 @@ class EntryDefinition:
except ContextError as exc:
format_and_raise(exc, f"stage '{self.name}'", self.relpath)
def resolve_stage(self, skip_checks: bool = False) -> DictStrAny:
def resolve_stage(self, skip_checks: bool = False) -> "DictStrAny":
context = self.context
name = self.name
if not skip_checks:
@@ -302,15 +298,13 @@ class EntryDefinition:
def _resolve(
self, context: "Context", value: Any, key: str, skip_checks: bool
) -> DictStrAny:
) -> "DictStrAny":
try:
return context.resolve(
value, skip_interpolation_checks=skip_checks, key=key
)
except (ParseError, KeyNotInContext) as exc:
format_and_raise(
exc, f"'{self.where}.{self.name}.{key}'", self.relpath
)
format_and_raise(exc, f"'{self.where}.{self.name}.{key}'", self.relpath)
class IterationPair(NamedTuple):
@@ -324,7 +318,7 @@ class ForeachDefinition:
resolver: DataResolver,
context: Context,
name: str,
definition: DictStrAny,
definition: "DictStrAny",
where: str = STAGES_KWD,
):
self.resolver = resolver
@@ -349,13 +343,11 @@ class ForeachDefinition:
def resolved_iterable(self):
return self._resolve_foreach_data()
def _resolve_foreach_data(self) -> SeqOrMap:
def _resolve_foreach_data(self) -> "SeqOrMap":
try:
iterable = self.context.resolve(self.foreach_data, unwrap=False)
except (ContextError, ParseError) as exc:
format_and_raise(
exc, f"'{self.where}.{self.name}.foreach'", self.relpath
)
format_and_raise(exc, f"'{self.where}.{self.name}.foreach'", self.relpath)
# foreach data can be a resolved dictionary/list.
self._check_is_map_or_seq(iterable)
@@ -380,8 +372,10 @@ class ForeachDefinition:
if warn_for:
linking_verb = "is" if len(warn_for) == 1 else "are"
logger.warning(
"%s %s already specified, "
"will be overwritten for stages generated from '%s'",
(
"%s %s already specified, "
"will be overwritten for stages generated from '%s'"
),
" and ".join(warn_for),
linking_verb,
self.name,
@@ -417,13 +411,13 @@ class ForeachDefinition:
def _generate_name(self, key: str) -> str:
return f"{self.name}{JOIN}{key}"
def resolve_all(self) -> DictStrAny:
def resolve_all(self) -> "DictStrAny":
return join(map(self.resolve_one, self.normalized_iterable))
def resolve_one(self, key: str) -> DictStrAny:
def resolve_one(self, key: str) -> "DictStrAny":
return self._each_iter(key)
def _each_iter(self, key: str) -> DictStrAny:
def _each_iter(self, key: str) -> "DictStrAny":
err_message = f"Could not find '{key}' in foreach group '{self.name}'"
with reraise(KeyError, EntryNotFound(err_message)):
value = self.normalized_iterable[key]
+8 -23
View File
@@ -48,9 +48,7 @@ class MergeError(ContextError):
self.key = key
to_node = into[key]
if not isinstance(to_node, Node) or not isinstance(new, Node):
super().__init__(
f"cannot merge '{key}' as it already exists in {into}"
)
super().__init__(f"cannot merge '{key}' as it already exists in {into}")
return
assert isinstance(to_node, Node)
@@ -138,9 +136,7 @@ class Node:
@dataclass
class Value(Node):
_value: Any
meta: Meta = field(
compare=False, default_factory=_default_meta, repr=False
)
meta: Meta = field(compare=False, default_factory=_default_meta, repr=False)
def __repr__(self):
return repr(self._value)
@@ -182,10 +178,7 @@ class Container(Node, ABC): # noqa: B024
assert meta
container = CtxDict if isinstance(value, dict) else CtxList
return container(value, meta=meta)
msg = (
"Unsupported value of type "
f"'{type(value).__name__}' in '{meta}'"
)
msg = f"Unsupported value of type '{type(value).__name__}' in '{meta}'"
raise TypeError(msg)
def __repr__(self):
@@ -219,16 +212,14 @@ class Container(Node, ABC): # noqa: B024
try:
d = self[index]
except LookupError as exc:
raise ValueError(
f"Could not find '{index}' in {self.data}"
) from exc
raise ValueError(f"Could not find '{index}' in {self.data}") from exc
if not rems:
return d
rem = rems[0]
if not isinstance(d, Container):
raise ValueError(
raise ValueError( # noqa: TRY004
f"{index} is a primitive value, cannot get '{rem}'"
)
return d.select(rem)
@@ -380,9 +371,7 @@ class Context(CtxDict):
data = {key: data[key] for key in select_keys}
except KeyError as exc:
key, *_ = exc.args
raise ParamsLoadError(
f"could not find '{key}' in '{path}'"
) from exc
raise ParamsLoadError(f"could not find '{key}' in '{path}'") from exc
meta = Meta(source=path, local=False)
ctx = cls(data, meta=meta)
@@ -480,9 +469,7 @@ class Context(CtxDict):
But for now, just `item` and `key`, this should do.
"""
# using dict to make the error messages ordered
new = dict.fromkeys(
[key for key in keys if key not in self._reserved_keys]
)
new = dict.fromkeys([key for key in keys if key not in self._reserved_keys])
self._reserved_keys.update(new)
try:
yield
@@ -542,9 +529,7 @@ class Context(CtxDict):
if is_exact_string(src, matches):
# replace "${enabled}", if `enabled` is a boolean, with it's actual
# value rather than it's string counterparts.
expr = get_expression(
matches[0], skip_checks=skip_interpolation_checks
)
expr = get_expression(matches[0], skip_checks=skip_interpolation_checks)
value = self.select(expr, unwrap=unwrap)
validate_value(value, key)
return value
+2 -7
View File
@@ -99,7 +99,6 @@ def _(obj: dict): # noqa: C901
result = ""
for k, v in flatten(obj).items():
if isinstance(v, bool):
if v:
result += f"--{k} "
@@ -115,9 +114,7 @@ def _(obj: dict): # noqa: C901
if isinstance(i, str):
i = escape_str(i)
elif isinstance(i, Iterable):
raise ParseError(
f"Cannot interpolate nested iterable in '{k}'"
)
raise ParseError(f"Cannot interpolate nested iterable in '{k}'")
if config.get("list", "nargs") == "append":
result += f"--{k} {i} "
@@ -206,9 +203,7 @@ def validate_value(value, key):
if not_primitive and not_foreach:
if isinstance(value, dict) and key == "cmd":
return True
raise ParseError(
f"Cannot interpolate data of type '{type(value).__name__}'"
)
raise ParseError(f"Cannot interpolate data of type '{type(value).__name__}'")
def str_interpolate(
+1 -3
View File
@@ -9,9 +9,7 @@ META_KWD = "meta"
def lockfile_version_schema(value):
expected = [LOCKFILE_VERSION.V2.value] # pylint: disable=no-member
msg = "invalid schema version {}, expected one of {}".format(
value, expected
)
msg = f"invalid schema version {value}, expected one of {expected}"
return validators.Any(*expected, msg=msg)(value)
+1 -3
View File
@@ -13,9 +13,7 @@ class ProcessNotFoundError(DvcException):
class TimeoutExpired(DvcException):
def __init__(self, cmd, timeout):
super().__init__(
f"'{cmd}' did not complete before timeout '{timeout}'"
)
super().__init__(f"'{cmd}' did not complete before timeout '{timeout}'")
self.cmd = cmd
self.timeout = timeout
+1 -3
View File
@@ -91,9 +91,7 @@ class ProcessManager:
raise UnsupportedSignalError(sig)
def handle_closed_process():
logging.warning(
f"Process {name} had already aborted unexpectedly."
)
logging.warning("Process %s had already aborted unexpectedly.", name)
process_info.returncode = -1
self[name] = process_info
+3 -11
View File
@@ -28,9 +28,7 @@ class Tqdm(tqdm):
"{postfix[info]}{n_fmt}/{total_fmt}"
" [{elapsed}<{remaining}, {rate_fmt:>11}]"
)
BAR_FMT_NOTOTAL = (
"{desc}{bar:b}|{postfix[info]}{n_fmt} [{elapsed}, {rate_fmt:>11}]"
)
BAR_FMT_NOTOTAL = "{desc}{bar:b}|{postfix[info]}{n_fmt} [{elapsed}, {rate_fmt:>11}]"
BYTES_DEFAULTS = {
"unit": "B",
"unit_scale": True,
@@ -75,11 +73,7 @@ class Tqdm(tqdm):
if not disable:
disable = logger.getEffectiveLevel() > level
# auto-disable based on TTY
if (
not disable
and not env2bool(DVC_IGNORE_ISATTY)
and hasattr(file, "isatty")
):
if not disable and not env2bool(DVC_IGNORE_ISATTY) and hasattr(file, "isatty"):
disable = not file.isatty()
super().__init__(
iterable=iterable,
@@ -95,9 +89,7 @@ class Tqdm(tqdm):
if bar_format is None:
if self.__len__():
self.bar_format = (
self.BAR_FMT_DEFAULT_NESTED
if self.pos
else self.BAR_FMT_DEFAULT
self.BAR_FMT_DEFAULT_NESTED if self.pos else self.BAR_FMT_DEFAULT
)
else:
self.bar_format = self.BAR_FMT_NOTOTAL
+1 -4
View File
@@ -24,10 +24,7 @@ def ask(prompt: str, limited_to: Optional[Collection[str]] = None):
if answer in limited_to:
return answer
logger.info(
"Your response must be one of: {options}. "
"Please try again.".format(options=limited_to)
)
logger.info("Your response must be one of: %s. Please try again.", limited_to)
def confirm(statement: str) -> bool:
+2 -10
View File
@@ -2,13 +2,7 @@ import json
from collections import defaultdict
from typing import Dict, List, Union
from dvc.render import (
REVISION_FIELD,
REVISIONS_KEY,
SRC_FIELD,
TYPE_KEY,
VERSION_FIELD,
)
from dvc.render import REVISION_FIELD, REVISIONS_KEY, SRC_FIELD, TYPE_KEY, VERSION_FIELD
from dvc.render.converter.image import ImageConverter
from dvc.render.converter.vega import VegaConverter
@@ -35,10 +29,8 @@ def _group_by_rev(datapoints):
def to_json(renderer, split: bool = False) -> List[Dict]:
from copy import deepcopy
if renderer.TYPE == "vega":
grouped = _group_by_rev(deepcopy(renderer.datapoints))
grouped = _group_by_rev(renderer.datapoints)
if split:
content = renderer.get_filled_template(skip_anchors=["data"])
else:
+6 -11
View File
@@ -1,4 +1,3 @@
from copy import deepcopy
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
from funcy import first, last
@@ -81,9 +80,9 @@ def _is_datapoints(lst: List[Dict]):
to unexpected behavior
"""
return all(isinstance(item, dict) for item in lst) and set(
first(lst).keys()
) == {key for keys in lst for key in keys}
return all(isinstance(item, dict) for item in lst) and set(first(lst).keys()) == {
key for keys in lst for key in keys
}
def get_datapoints(file_content: Dict):
@@ -195,7 +194,6 @@ class VegaConverter(Converter):
return "x"
def flat_datapoints(self, revision): # noqa: C901
file2datapoints, properties = self.convert()
props_update = {}
@@ -218,7 +216,7 @@ class VegaConverter(Converter):
num_ys = len(ys)
if num_xs > 1 and num_xs != num_ys:
raise DvcException(
f"Cannot have different number of x and y data sources. Found "
"Cannot have different number of x and y data sources. Found "
f"{num_xs} x and {num_ys} y data sources."
)
@@ -234,7 +232,7 @@ class VegaConverter(Converter):
for i, (y_file, y_field) in enumerate(ys):
if num_xs > 1:
x_file, x_field = xs[i]
datapoints = deepcopy(file2datapoints.get(y_file, []))
datapoints = [d.copy() for d in file2datapoints.get(y_file, [])]
if props_update.get("y", None) == "dvc_inferred_y_value":
_update_from_field(
@@ -316,9 +314,7 @@ def _update_from_field(
source_field = field
if len(source_datapoints) != len(target_datapoints):
raise IndexError(
"Source and target datapoints must have the same length"
)
raise IndexError("Source and target datapoints must have the same length")
for index, datapoint in enumerate(target_datapoints):
source_datapoint = source_datapoints[index]
@@ -332,6 +328,5 @@ def _update_from_index(datapoints: List[Dict], new_field: str):
def _update_all(datapoints: List[Dict], update_dict: Dict):
for datapoint in datapoints:
datapoint.update(update_dict)
+3 -10
View File
@@ -62,7 +62,6 @@ def match_defs_renderers(
out=None,
templates_dir: Optional["StrPath"] = None,
):
from dvc_render import ImageRenderer, VegaRenderer
plots_data = PlotsData(data)
@@ -80,9 +79,7 @@ def match_defs_renderers(
for rev, inner_id, plot_definition in group:
plot_sources = infer_data_sources(inner_id, plot_definition)
definitions_data = plots_data.get_definition_data(
plot_sources, rev
)
definitions_data = plots_data.get_definition_data(plot_sources, rev)
if ImageRenderer.matches(inner_id, None):
renderer_cls = ImageRenderer
@@ -91,9 +88,7 @@ def match_defs_renderers(
renderer_cls = VegaRenderer
renderer_id = plot_id
converter = _get_converter(
renderer_cls, inner_id, props, definitions_data
)
converter = _get_converter(renderer_cls, inner_id, props, definitions_data)
dps, rev_props = converter.flat_datapoints(rev)
if not final_props and rev_props:
@@ -103,7 +98,5 @@ def match_defs_renderers(
if "title" not in final_props:
final_props["title"] = renderer_id
if renderer_cls is not None:
renderers.append(
renderer_cls(plot_datapoints, renderer_id, **final_props)
)
renderers.append(renderer_cls(plot_datapoints, renderer_id, **final_props))
return renderers
+50 -44
View File
@@ -3,16 +3,7 @@ import os
from collections import defaultdict
from contextlib import contextmanager
from functools import wraps
from typing import (
TYPE_CHECKING,
Callable,
Iterable,
List,
Optional,
Tuple,
Union,
cast,
)
from typing import TYPE_CHECKING, Callable, Iterable, List, Optional, Tuple, Union, cast
from dvc.exceptions import FileMissingError
from dvc.exceptions import IsADirectoryError as DvcIsADirectoryError
@@ -28,10 +19,11 @@ if TYPE_CHECKING:
from dvc.fs.dvc import DVCFileSystem
from dvc.lock import LockBase
from dvc.machine import MachineManager
from dvc.scm import Base, Git, NoSCM
from dvc.scm import Git, NoSCM
from dvc.stage import Stage
from dvc.types import DictStrAny
from dvc_data.hashfile.state import StateBase
from dvc_data.index import DataIndex
from .experiments import Experiments
from .index import Index
@@ -148,9 +140,7 @@ class Repo:
import hashlib
root_dir_hash = hashlib.sha224(
self.root_dir.encode("utf-8")
).hexdigest()
root_dir_hash = hashlib.sha224(self.root_dir.encode("utf-8")).hexdigest()
db_dir = self.fs.path.join(
base_db_dir,
@@ -171,13 +161,13 @@ class Repo:
config: Optional["DictStrAny"] = None,
url: Optional[str] = None,
repo_factory: Optional[Callable] = None,
scm: Optional["Base"] = None,
scm: Optional[Union["Git", "NoSCM"]] = None,
):
from dvc.cachemgr import CacheManager
from dvc.config import Config
from dvc.data_cloud import DataCloud
from dvc.fs import GitFileSystem, LocalFileSystem, localfs
from dvc.lock import LockNoop, make_lock
from dvc.odbmgr import ODBManager
from dvc.repo.metrics import Metrics
from dvc.repo.params import Params
from dvc.repo.plots import Plots
@@ -190,6 +180,7 @@ class Repo:
self._fs_conf = {"repo_factory": repo_factory}
self._fs = fs or localfs
self._scm = scm
self._data_index = None
if rev and not fs:
self._scm = scm = SCM(root_dir or os.curdir)
@@ -212,16 +203,16 @@ class Repo:
# used by DVCFileSystem to determine if it should traverse subrepos
self.subrepos = subrepos
self.cloud = DataCloud(self)
self.cloud: "DataCloud" = DataCloud(self)
self.stage: "StageLoad" = StageLoad(self)
self.lock: "LockBase"
self.odb: ODBManager
self.cache: CacheManager
self.state: "StateBase"
if isinstance(self.fs, GitFileSystem) or not self.dvc_dir:
self.lock = LockNoop()
self.state = StateNoop()
self.odb = ODBManager(self)
self.cache = CacheManager(self)
self.tmp_dir = None
else:
self.fs.makedirs(cast(str, self.tmp_dir), exist_ok=True)
@@ -230,9 +221,7 @@ class Repo:
self.lock = make_lock(
self.fs.path.join(self.tmp_dir, "lock"),
tmp_dir=self.tmp_dir,
hardlink_lock=self.config["core"].get(
"hardlink_lock", False
),
hardlink_lock=self.config["core"].get("hardlink_lock", False),
friendly=True,
)
state_db_dir = self._get_database_dir("state")
@@ -241,7 +230,7 @@ class Repo:
self.lock = LockNoop()
self.state = StateNoop()
self.odb = ODBManager(self)
self.cache = CacheManager(self)
self.stage_cache = StageCache(self)
@@ -275,7 +264,7 @@ class Repo:
new.check_graph()
@staticmethod
def open(url, *args, **kwargs):
def open(url, *args, **kwargs): # noqa: A003
if url is None:
url = os.getcwd()
@@ -338,8 +327,7 @@ class Repo:
from dvc.machine import MachineManager
if self.tmp_dir and (
self.config["feature"].get("machine", False)
or env2bool("DVC_TEST")
self.config["feature"].get("machine", False) or env2bool("DVC_TEST")
):
return MachineManager(self)
return None
@@ -355,6 +343,31 @@ class Repo:
# fs.
self._reset()
@property
def data_index(self) -> Optional["DataIndex"]:
from appdirs import user_cache_dir
from fsspec.utils import tokenize
from dvc_data.index import DataIndex
if not self.config["feature"].get("data_index_cache"):
return None
if self._data_index is None:
cache_dir = user_cache_dir(self.config.APPNAME, self.config.APPAUTHOR)
index_dir = os.path.join(
cache_dir,
"index",
"data",
# scm.root_dir and repo.root_dir don't match for subrepos
tokenize((self.scm.root_dir, self.root_dir)),
)
os.makedirs(index_dir, exist_ok=True)
self._data_index = DataIndex.open(os.path.join(index_dir, "db.db"))
return self._data_index
def __repr__(self):
return f"{self.__class__.__name__}: '{self.root_dir}'"
@@ -393,24 +406,20 @@ class Repo:
return os.path.join(root_dir, cls.DVC_DIR)
@staticmethod
def init(
root_dir=os.curdir, no_scm=False, force=False, subdir=False
) -> "Repo":
def init(root_dir=os.curdir, no_scm=False, force=False, subdir=False) -> "Repo":
from dvc.repo.init import init
return init(
root_dir=root_dir, no_scm=no_scm, force=force, subdir=subdir
)
return init(root_dir=root_dir, no_scm=no_scm, force=force, subdir=subdir)
def unprotect(self, target):
return self.odb.repo.unprotect(target)
return self.cache.repo.unprotect(target)
def _ignore(self):
flist = [self.config.files["local"]]
if tmp_dir := self.tmp_dir:
flist.append(tmp_dir)
if path_isin(self.odb.repo.path, self.root_dir):
flist.append(self.odb.repo.path)
if path_isin(self.cache.repo.path, self.root_dir):
flist.append(self.cache.repo.path)
for file in flist:
self.scm_context.ignore(file)
@@ -530,9 +539,7 @@ class Repo:
def dvcfs(self) -> "DVCFileSystem":
from dvc.fs.dvc import DVCFileSystem
return DVCFileSystem(
repo=self, subrepos=self.subrepos, **self._fs_conf
)
return DVCFileSystem(repo=self, subrepos=self.subrepos, **self._fs_conf)
@cached_property
def index_db_dir(self):
@@ -573,18 +580,17 @@ class Repo:
def close(self):
self.scm.close()
self.state.close()
if self._data_index is not None:
self._data_index.close()
def _reset_cached_indecies(self):
def _reset(self):
self.scm._reset() # pylint: disable=protected-access
self.state.close()
self.__dict__.pop("index", None)
self.__dict__.pop("dvcignore", None)
self.__dict__.pop("dvcfs", None)
self.__dict__.pop("datafs", None)
def _reset(self):
self.state.close()
self.scm._reset() # pylint: disable=protected-access
self._reset_cached_indecies()
def __enter__(self):
return self
+3 -10
View File
@@ -15,12 +15,7 @@ from dvc.exceptions import (
)
from dvc.repo.scm_context import scm_context
from dvc.ui import ui
from dvc.utils import (
LARGE_DIR_SIZE,
glob_targets,
resolve_output,
resolve_paths,
)
from dvc.utils import LARGE_DIR_SIZE, glob_targets, resolve_output, resolve_paths
from dvc.utils.collections import ensure_list, validate
from . import locked
@@ -108,9 +103,7 @@ def translate_graph_error(stages: Stages) -> Iterator[None]:
def progress_iter(stages: Stages) -> Iterator["Stage"]:
total = len(stages)
desc = "Adding..."
with ui.progress(
stages, total=total, desc=desc, unit="file", leave=True
) as pbar:
with ui.progress(stages, total=total, desc=desc, unit="file", leave=True) as pbar:
if total == 1:
pbar.bar_format = desc
pbar.refresh()
@@ -194,7 +187,7 @@ def add( # noqa: C901
stage.transfer(source, to_remote=to_remote, odb=odb, **kwargs)
else:
try:
stage.save(merge_versioned=True)
stage.save()
if not no_commit:
stage.commit()
except CacheLinkError:
+20 -11
View File
@@ -1,7 +1,10 @@
import logging
from typing import Optional
from dvc.scm import iter_revs
logger = logging.getLogger(__name__)
def brancher( # noqa: E302
self,
@@ -32,7 +35,16 @@ def brancher( # noqa: E302
- empty string it there is no branches to iterate over
- "workspace" if there are uncommitted changes in the SCM repo
"""
if not any([revs, all_branches, all_tags, all_commits, all_experiments]):
if not any(
[
revs,
all_branches,
all_tags,
all_commits,
all_experiments,
commit_date,
]
):
yield ""
return
@@ -40,21 +52,18 @@ def brancher( # noqa: E302
repo_root_parts = ()
if self.fs.path.isin(self.root_dir, self.scm.root_dir):
repo_root_parts = self.fs.path.relparts(
self.root_dir, self.scm.root_dir
)
repo_root_parts = self.fs.path.relparts(self.root_dir, self.scm.root_dir)
cwd_parts = ()
if self.fs.path.isin(self.fs.path.getcwd(), self.scm.root_dir):
cwd_parts = self.fs.path.relparts(
self.fs.path.getcwd(), self.scm.root_dir
)
cwd_parts = self.fs.path.relparts(self.fs.path.getcwd(), self.scm.root_dir)
saved_fs = self.fs
saved_root = self.root_dir
scm = self.scm
logger.trace("switching fs to workspace") # type: ignore[attr-defined]
self.fs = LocalFileSystem(url=self.root_dir)
yield "workspace"
@@ -77,14 +86,14 @@ def brancher( # noqa: E302
from dvc.fs import GitFileSystem
for sha, names in found_revs.items():
self._reset_cached_indecies() # pylint: disable=W0212
logger.trace( # type: ignore[attr-defined]
"switching fs to revision %s [%s]", sha[:7], ", ".join(names)
)
self.fs = GitFileSystem(scm=scm, rev=sha)
self.root_dir = self.fs.path.join("/", *repo_root_parts)
if cwd_parts:
cwd = self.fs.path.join( # type: ignore[unreachable]
"/", *cwd_parts
)
cwd = self.fs.path.join("/", *cwd_parts) # type: ignore[unreachable]
self.fs.path.chdir(cwd)
# ignore revs that don't contain repo root
+3 -12
View File
@@ -2,11 +2,7 @@ import logging
import os
from typing import TYPE_CHECKING, Dict, List, Set
from dvc.exceptions import (
CheckoutError,
CheckoutErrorSuggestGit,
NoOutputOrStageError,
)
from dvc.exceptions import CheckoutError, CheckoutErrorSuggestGit, NoOutputOrStageError
from dvc.utils import relpath
from . import locked
@@ -35,18 +31,13 @@ def _remove_unused_links(repo):
def get_all_files_numbers(pairs):
return sum(
stage.get_all_files_number(filter_info) for stage, filter_info in pairs
)
return sum(stage.get_all_files_number(filter_info) for stage, filter_info in pairs)
def _collect_pairs(
self: "Repo", targets, with_deps: bool, recursive: bool
) -> Set["StageInfo"]:
from dvc.stage.exceptions import (
StageFileBadNameError,
StageFileDoesNotExistError,
)
from dvc.stage.exceptions import StageFileBadNameError, StageFileDoesNotExistError
pairs: Set["StageInfo"] = set()
for target in targets:

Some files were not shown because too many files have changed in this diff Show More