Compare commits

...
217 Commits
Author SHA1 Message Date
Peter Rowlands 1a3a222239 scmrepo: update to 0.0.16 2022-03-30 15:46:18 +09:00
Saugat Pachhai (सौगात) 5ff1d76826 ui: add revision info on dvc.yaml/dvc.lock/.dvc syntax and schema errors 2022-03-29 17:49:03 +05:45
dependabot[bot] 582c4afc38 build(deps): Bump scmrepo from 0.0.13 to 0.0.14
Bumps [scmrepo](https://github.com/iterative/scmrepo) from 0.0.13 to 0.0.14.
- [Release notes](https://github.com/iterative/scmrepo/releases)
- [Commits](https://github.com/iterative/scmrepo/compare/0.0.13...0.0.14)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-03-29 17:37:37 +05:45
dependabot[bot] b1284cb4af build(deps-dev): Bump hdfs from 2.6.0 to 2.7.0
Bumps [hdfs](https://github.com/mtth/hdfs) from 2.6.0 to 2.7.0.
- [Release notes](https://github.com/mtth/hdfs/releases)
- [Changelog](https://github.com/mtth/hdfs/blob/master/CHANGES)
- [Commits](https://github.com/mtth/hdfs/commits)

---
updated-dependencies:
- dependency-name: hdfs
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-03-29 16:23:01 +05:45
Saugat Pachhai (सौगात) 7d0d6a38d7 exp init: change ui prompts structure and output 2022-03-29 16:20:32 +05:45
dependabot[bot] 700654cb1e build(deps): Bump dvc-render from 0.0.3 to 0.0.4
Bumps [dvc-render](https://github.com/iterative/dvc-render) from 0.0.3 to 0.0.4.
- [Release notes](https://github.com/iterative/dvc-render/releases)
- [Commits](https://github.com/iterative/dvc-render/compare/v0.0.3...v0.0.4)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-03-29 10:02:54 +02:00
dependabot[bot] 5a7790dcd5 build(deps-dev): Bump mypy from 0.931 to 0.942
Bumps [mypy](https://github.com/python/mypy) from 0.931 to 0.942.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.931...v0.942)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Bump mypy from 0.931 to 0.942

after mypy 0.940, it disabled all class scoped imports because it will
cause crashes.

1. upgrade mypy from 0.931 to 0.942
2. Solve the error `class scoped import` in mypy 0.942
2022-03-29 10:05:08 +05:45
David de la Iglesia Castro ead7724824 render: vega_converter: make rev and filename optional.
Add `FILENAME_FIELD`.
2022-03-28 19:34:05 +02:00
David de la Iglesia Castro ddd6c97f71 render: Extract to dvc_render
Create and use separate package for rendering logic.
Closes #6944
2022-03-28 19:34:05 +02:00
Saugat Pachhai (सौगात) b71a0f30fb s3: add aiobotocore as dependency explicitly
Fixes #7257 and fixes #7475.
2022-03-28 19:48:55 +05:45
dependabot[bot] 9930551458 build(deps-dev): Bump pytest from 6.2.5 to 7.1.1
Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.2.5 to 7.1.1.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/6.2.5...7.1.1)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-03-28 18:39:25 +05:45
Saugat Pachhai 3322980ff6 bump pytest-docker to 0.11.0
This adds support for pytest>7.
See https://github.com/avast/pytest-docker/pull/74.
2022-03-28 18:17:59 +05:45
dependabot[bot] 23a86c9a15 build(deps-dev): Bump types-requests from 2.27.14 to 2.27.15
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.14 to 2.27.15.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-03-28 18:03:05 +05:45
Daniele Trifirò 57419273ea fs.http: set sock_read timeout 2022-03-28 08:20:56 +02:00
Daniele Trifirò 879c9bd7d6 fs.http: force cleanup of closed connections 2022-03-28 08:20:56 +02:00
Saugat Pachhai d09d39538a Revert "build: pin setuptools to <61"
This reverts commit c08aa4a93b.
2022-03-26 07:26:51 +05:45
Casper da Costa-Luis b684b2b25a deps: bump tqdm
- fixes #7465
2022-03-25 19:16:33 +05:45
dependabot[bot] 7de443d8bc build(deps-dev): Bump types-requests from 2.27.13 to 2.27.14
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.13 to 2.27.14.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-03-25 14:24:45 +02:00
Saugat Pachhai (सौगात) d008054ea2 objects.db: get rid of callbacks from internal _list_paths API
This removes callback from internal APIs: `_list_paths`, `list_hashes`,
and `_hashes_with_limit`. These are all iterators and hence can just be
replaced by wrapping a progressbar over those iterators in the caller
side.

We still have progressbars in `hashes_exist`, `_estimate_remote_size`,
`_list_hashes_traverse` and `list_hashes_exists`, which should gradually
lifted up and replaced.
2022-03-25 14:13:26 +02:00
Saugat Pachhai (सौगात) b92bc3e01a fs.webdav: consider url/path to be root
Webdav filesystem expects path to be relative
to the base url, instead of having full prefix.

So we are setting it to "" (root) through `_strip_protocol`
which converts fully-qualified name to the file-system
specific path.
2022-03-25 13:56:33 +02:00
Saugat Pachhai (सौगात) c08aa4a93b build: pin setuptools to <61
A workaround for https://github.com/pypa/setuptools/issues/3196.
2022-03-25 16:13:49 +05:45
Gao 81eee05995 UI problem in dvc exp push 2022-03-22 20:25:04 +09:00
karajan1001andDave Berenbaum 325ead5e8b exp remove: show removed experiments #7449
fix: #7449
`exp remove`: should show the experiments that were removed

1. experiment.remove now returns the experiment list been removed.
2. modify the functional test to these the returned list.
3. use ui to show the removed list.

Co-authored-by: Dave Berenbaum <[email protected]>
2022-03-22 17:13:44 +08:00
dberenbaum 5cf59e4ee6 exp: remove mentions of resume 2022-03-22 14:07:56 +09:00
dependabot[bot] 290a593322 build(deps): Bump actions/cache from 2.1.7 to 3
Bumps [actions/cache](https://github.com/actions/cache) from 2.1.7 to 3.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v2.1.7...v3)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-03-22 10:32:12 +08:00
Peter Rowlands 19dcba9db6 erepo/exp: use explicit fetch + merge instead of pull
- tests: use mocker.spy instead of patch(wraps=...)
2022-03-21 18:17:36 +09:00
Peter Rowlands ab54b5bdfc scmrepo: update to 0.0.13 2022-03-21 18:17:36 +09:00
Peter Rowlands 2e21be992d tests: fix incorrect git URLs 2022-03-21 18:17:36 +09:00
Saugat Pachhai (सौगात) 9d96ddfbe7 exp init: create params file by default
Note that this won't add support for tracking the params file,
it'll only create the params file. Some changes on error and
prompts are made.
2022-03-21 15:00:20 +05:45
Saugat Pachhai (सौगात) a3bab48f41 params: allow tracking file 2022-03-21 13:47:44 +05:45
dependabot[bot] a13f2770ef build(deps-dev): Bump types-tabulate from 0.8.5 to 0.8.6
Bumps [types-tabulate](https://github.com/python/typeshed) from 0.8.5 to 0.8.6.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-tabulate
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-03-18 10:28:30 +05:45
dependabot[bot] 079714772c build(deps-dev): Bump types-requests from 2.27.12 to 2.27.13
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.12 to 2.27.13.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-03-17 12:45:54 +01:00
dependabot[bot] cd65400d83 build(deps-dev): Bump google-cloud-storage from 2.2.0 to 2.2.1
Bumps [google-cloud-storage](https://github.com/googleapis/python-storage) from 2.2.0 to 2.2.1.
- [Release notes](https://github.com/googleapis/python-storage/releases)
- [Changelog](https://github.com/googleapis/python-storage/blob/main/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-storage/compare/v2.2.0...v2.2.1)

---
updated-dependencies:
- dependency-name: google-cloud-storage
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-03-16 13:09:21 +01:00
dependabot[bot] 0d06a98c67 build(deps-dev): Bump google-cloud-storage from 2.1.0 to 2.2.0
Bumps [google-cloud-storage](https://github.com/googleapis/python-storage) from 2.1.0 to 2.2.0.
- [Release notes](https://github.com/googleapis/python-storage/releases)
- [Changelog](https://github.com/googleapis/python-storage/blob/main/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-storage/compare/v2.1.0...v2.2.0)

---
updated-dependencies:
- dependency-name: google-cloud-storage
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-03-15 12:31:05 +08:00
dependabot[bot] 3fc8dcfc59 build(deps-dev): Bump types-requests from 2.27.11 to 2.27.12
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.11 to 2.27.12.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-03-14 10:44:03 +01:00
Saugat Pachhai (सौगात) e80e725fe4 tests: fix test failures with rich==12 2022-03-11 19:26:54 +05:45
karajan1001 6a6955e27c exp show/pull/push/list: help text for -n (#7444)
fix: #7444

from the discussion https://github.com/iterative/dvc.org/pull/3187#discussion_r821382100

1. Update the help message of `-n`
2022-03-10 17:11:42 +08:00
karajan1001andJorge Orpinel efc021da73 exp remove: rename --all-commits flag and unify the collection of revs (#7155)
fix: #7155
1. rename flags `-A/--all-commits` in `exp remove`
2. add new flag `-n/--num` in `exp remove`
3. unify the revision collection in `exp remove`
4. add unit and func tests for `exp remove`

Co-authored-by: Jorge Orpinel <[email protected]>
2022-03-09 18:54:15 +08:00
karajan1001 244637e299 exp push/pull: add --all-commits flag and unify the collection of revs (#7154)
fix: #7154
1. rename flags `-A/--all-commits` in the `exp pull/push`
2. add new flag "--rev" and "--num" in the `exp pull/push`
3. Unify the collection of revs in `exp push/pull`
4. add unit and func tests for `exp push/pull`
2022-03-09 17:43:26 +08:00
karajan1001andJorge Orpinel d033345860 exp list: add the new flags (#7153)
fix: #7153
Unify the flags in `exp list` to `exp show`

1. Add new `-A`, `-n` flags to `exp list` command.
2. Add unit test for the flag parse.
3. unify the revs retrive logic to `exp show`.
4. Add functional test for `exp list`
5. Update the documents.

Co-authored-by: Jorge Orpinel <[email protected]>
2022-03-08 16:17:33 +08:00
Lucas Cabral de390419a6 fix: hooks install alongside pre-commit tool 2022-03-08 13:16:16 +05:45
Peter Rowlands d80c43135f exp run: remove unsupported (repro only) arguments 2022-03-07 12:01:51 +01:00
David de la Iglesia Castro 5249207ceb live: Remove summary from tests 2022-03-07 10:09:04 +01:00
dependabot[bot] 2ebf9cb9b9 build(deps-dev): Bump dvclive[image] from 0.5.1 to 0.6.0
Bumps [dvclive[image]](https://github.com/iterative/dvclive) from 0.5.1 to 0.6.0.
- [Release notes](https://github.com/iterative/dvclive/releases)
- [Commits](https://github.com/iterative/dvclive/compare/0.5.1...0.6.0)

---
updated-dependencies:
- dependency-name: dvclive[image]
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-03-07 10:09:04 +01:00
dependabot[bot] 17899bc109 build(deps): Bump pyinstaller from 4.9 to 4.10
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 4.9 to 4.10.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/v4.10/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v4.9...v4.10)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-03-07 15:29:18 +09:00
dependabot[bot] 380c65134e build(deps): Bump scmrepo from 0.0.7 to 0.0.10
Bumps [scmrepo](https://github.com/iterative/scmrepo) from 0.0.7 to 0.0.10.
- [Release notes](https://github.com/iterative/scmrepo/releases)
- [Commits](https://github.com/iterative/scmrepo/compare/0.0.7...0.0.10)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-03-07 12:12:56 +05:45
dependabot[bot] ab024a47b3 build(deps): Bump actions/checkout from 2.4.0 to 3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2.4.0 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2.4.0...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-03-03 15:05:39 +08:00
dberenbaum fc3f3ca8d7 diff: fix missing key bug (#6720) 2022-03-02 19:10:31 +01:00
David de la Iglesia Castro 6bb3794f52 Suppress --live-no-summary help. 2022-03-02 13:30:19 +01:00
dependabot[bot] a2e25cf449 build(deps): Bump actions/setup-python from 2 to 3
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2 to 3.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-03-01 17:30:52 +09:00
Peter Rowlands 59b8498905 fs: remove unused connection pool module 2022-03-01 15:36:49 +09:00
Zhenyu Gao a378e017e1 Fix dvc add -o issue #7357 2022-02-28 15:24:16 +01:00
dependabot[bot] 4c71aadc21 build(deps-dev): Bump filelock from 3.5.1 to 3.6.0
Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.5.1 to 3.6.0.
- [Release notes](https://github.com/tox-dev/py-filelock/releases)
- [Changelog](https://github.com/tox-dev/py-filelock/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/py-filelock/compare/3.5.1...3.6.0)

---
updated-dependencies:
- dependency-name: filelock
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-28 15:21:02 +01:00
Paweł Redzyński 3b4d76df42 plots: json: add datapoints to vega plots 2022-02-25 17:12:45 +01:00
dependabot[bot] 6c04d0efd1 build(deps-dev): Bump types-requests from 2.27.10 to 2.27.11
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.10 to 2.27.11.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-23 12:31:15 +05:45
David de la Iglesia Castro f41651c97c Remove outdated SSH default jobs. 2022-02-22 12:34:25 +01:00
Saugat Pachhai (सौगात) fcddf080da exp init: refactor and simplify interactive mode
- Moves workspace tree for interactive mode after prompts. Previously
  this was displayed before the prompts.
- Remove the yaml contents and the confirmation prompt in an interactive
  mode.
- Simplify `init_interactive()`.
2022-02-22 16:59:08 +05:45
Ruslan Kuprieiev 82b0a3088f dvcfs: handle repo root
Fixes #7407
2022-02-21 04:23:43 +02:00
Ruslan Kuprieiev 30fa3fc41b fs: walk: get rid of onerror
Makes dvc/repofs and other filesystems comply with fsspec.
2022-02-18 16:17:46 +02:00
Ruslan Kuprieiev d8fa55bafa fs: git: move isfile/walk to fsspec wrapper 2022-02-18 12:48:13 +02:00
Daniele Trifirò 220c633497 status: cleanup invocation of hashes_exist 2022-02-18 02:37:48 +02:00
Daniele Trifirò 3767b03784 test_data_cloud: also check for the correct number of jobs 2022-02-18 02:37:48 +02:00
Daniele Trifirò c0f63efe99 fix passing of jobs kwarg
the `jobs` kwarg was not being passed down properly,
resulting in it being ignored in some cases (push/pull/gc)
2022-02-18 02:37:48 +02:00
dependabot[bot] 0b18efb7b7 build(deps-dev): Bump filelock from 3.4.2 to 3.5.1
Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.4.2 to 3.5.1.
- [Release notes](https://github.com/tox-dev/py-filelock/releases)
- [Changelog](https://github.com/tox-dev/py-filelock/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/py-filelock/compare/3.4.2...3.5.1)

---
updated-dependencies:
- dependency-name: filelock
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-17 15:05:37 +02:00
Saugat Pachhai (सौगात) 497eac952c s/Creating/Created 2022-02-17 14:18:08 +05:45
Saugat Pachhai (सौगात) 6eb5c31dd7 fix UI message 2022-02-17 14:18:08 +05:45
Saugat Pachhai (सौगात) 69ce69e427 exp init: update message 2022-02-17 14:18:08 +05:45
Saugat Pachhai (सौगात) f8467cf375 exp init: log created dependencies in non-interactive mode 2022-02-17 14:18:08 +05:45
Saugat Pachhai (सौगात) 2735fbac71 exp init: create workspace structure by default, show workspace tree 2022-02-17 14:18:08 +05:45
dependabot[bot] 795fe24a5f build(deps-dev): Bump types-requests from 2.27.9 to 2.27.10
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.9 to 2.27.10.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-16 12:02:54 +08:00
karajan1001 d1f7cf4540 exp show: show exp when checkpoint is running(#7329)
fix: #7329
In a previous PR, we introduce a bug which use `HEAD` as the workspace
commit, while if there is a checkpoint is running, the `HEAD` is
actually changing all the time, we should use
`refs/exps/exec/EXEC_BASELINE` instead.

1. add `fix_exp_head` transfer in `dvc/scm.py::resolve_rev`
2. remove fix_exp_head from `diff`, `metrics diff`, `params diff` and
   `plots diff`
3. modify a functional test to simulate the running conditions
2022-02-15 21:17:42 +08:00
dependabot[bot] d5b809d705 build(deps-dev): Bump types-toml from 0.10.3 to 0.10.4
Bumps [types-toml](https://github.com/python/typeshed) from 0.10.3 to 0.10.4.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-toml
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-11 08:28:58 +05:45
Daniele Trifirò 4db9d19387 destroy: also remove dvcignore file 2022-02-10 16:16:12 +01:00
David de la Iglesia Castro 31a3461465 live: Update tests 2022-02-10 16:46:32 +05:45
dependabot[bot] e6d52ac594 build(deps-dev): Bump dvclive[image] from 0.5.0 to 0.5.1
Bumps [dvclive[image]](https://github.com/iterative/dvclive) from 0.5.0 to 0.5.1.
- [Release notes](https://github.com/iterative/dvclive/releases)
- [Commits](https://github.com/iterative/dvclive/compare/0.5.0...0.5.1)

---
updated-dependencies:
- dependency-name: dvclive[image]
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-10 16:46:32 +05:45
dependabot[bot] a78d63cf58 build(deps-dev): Bump types-requests from 2.27.8 to 2.27.9
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.8 to 2.27.9.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-10 15:52:07 +08:00
David de la Iglesia Castro d89555cd31 exp show: handle renaming of metrics/params.
_extend_row did not considered partially empty `items`, which can occur when metris or params are renamed between commits.

Fixes #7070
2022-02-09 21:40:15 +01:00
Nikita Kodenko b6fa1178e7 tests: migrate unittest to pytest 2022-02-08 18:51:17 +09:00
Ruslan Kuprieiev e430693fc8 dvcfs: use paths relative to the repo root
Currently we use both absolute and relative (depend on cwd) paths, which is
terrible for an api, as their interpretation depends on external factors.
This PR makes dvcfs accept only paths relative to the repo root for regular
outputs and absolute paths for external outputs.

Pre-requisite for making repofs use same relative paths.
2022-02-07 20:12:33 +02:00
dependabot[bot] e60ab00f51 build(deps): Bump pyinstaller from 4.8 to 4.9
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 4.8 to 4.9.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/v4.9/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v4.8...v4.9)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-07 18:02:45 +05:45
Ruslan Kuprieiev cb584c6a53 dvcfs: detach from pipeline outputs
Introducing simple repo tree that combines all workspaces into one structure,
and allows us to detach data representation from pipelines.

Pre-requisite for dvc-data extraction and using relpaths in dvcfs/repofs.
2022-02-07 13:12:52 +02:00
Ruslan Kuprieiev 9b5ccb7554 Revert "ls: don't use outs"
This reverts commit 27b090709b.
2022-02-04 11:04:00 +02:00
Ruslan Kuprieiev 27b090709b ls: don't use outs
ls doesn't support subrepos, so we can simply use dvcfs in --dvc-only.
2022-02-03 23:59:29 +02:00
Ruslan Kuprieiev 48641fb41f repofs: don't use outs
Using outs directly breaks isolation and makes it really hard to understand
what's going on.

Redundant abspaths will go away in followups when moving to relpaths.
2022-02-03 23:33:37 +02:00
Ruslan Kuprieiev 8497edab93 dvcfs: use info() instead of metadata()
metadata was introduced before our migration to fsspec and no longer needed
now. This is a pre-requisite to using data index in dvcfs/repofs.

Note that a bunch of awkward flags like isdvc/isout/etc are still kept, and
will be removed in followups.
2022-02-03 22:45:10 +02:00
Saugat Pachhai dc13179759 Delete scripts/schema directory
https://github.com/iterative/dvcyaml-schema provides the canonical schema,
this is not used anywhere and is stale.
2022-02-01 20:35:07 +05:45
David de la Iglesia Castro 37cfb8e8af exp show: Sort deps columns.
Closes #7312
2022-02-01 15:23:26 +01:00
Paweł Redzyński 7bc14a5ddb plots: make templates generation explicit
Fixes: #6389
2022-02-01 15:09:25 +01:00
dependabot[bot] 22ebfd1e2f build(deps-dev): Bump pytest-mock from 3.6.1 to 3.7.0
Bumps [pytest-mock](https://github.com/pytest-dev/pytest-mock) from 3.6.1 to 3.7.0.
- [Release notes](https://github.com/pytest-dev/pytest-mock/releases)
- [Changelog](https://github.com/pytest-dev/pytest-mock/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-mock/compare/v3.6.1...v3.7.0)

---
updated-dependencies:
- dependency-name: pytest-mock
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-01 19:53:26 +05:45
pre-commit-ci[bot] d213ccb686 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2022-02-01 19:53:13 +05:45
pre-commit-ci[bot] 0ce1f982b0 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/ambv/black: 21.12b0 → 22.1.0](https://github.com/ambv/black/compare/21.12b0...22.1.0)
2022-02-01 19:53:13 +05:45
dependabot[bot] a4b3da6acc build(deps-dev): Bump types-requests from 2.27.7 to 2.27.8
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.7 to 2.27.8.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-01 19:24:56 +05:45
dependabot[bot] b58a01cfd5 build(deps-dev): Bump dvclive[image] from 0.4.6 to 0.5.0
Bumps [dvclive[image]](https://github.com/iterative/dvclive) from 0.4.6 to 0.5.0.
- [Release notes](https://github.com/iterative/dvclive/releases)
- [Commits](https://github.com/iterative/dvclive/compare/0.4.6...0.5.0)

---
updated-dependencies:
- dependency-name: dvclive[image]
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-01 14:24:23 +01:00
Daniele Trifirò 0562b4fd5f tests: update test_exceptions for rich 11.1.0 behaviour 2022-02-01 13:20:07 +01:00
tapadipti 95731e9a37 Doc URL correction for GitAuthError 2022-01-31 14:50:37 +05:45
Ruslan Kuprieiev aca6d229da output: move isexec to meta
With our staging process automatically capturing file metadata, we no longer
need to set isexec ourselves.

In followup PRs we will also use meta during checkout, so that we won't need
to use Output.set_exec by hand.
2022-01-27 18:55:32 +02:00
dependabot[bot] 60e502d361 build(deps-dev): Bump google-cloud-storage from 2.0.0 to 2.1.0
Bumps [google-cloud-storage](https://github.com/googleapis/python-storage) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/googleapis/python-storage/releases)
- [Changelog](https://github.com/googleapis/python-storage/blob/main/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-storage/compare/v2.0.0...v2.1.0)

---
updated-dependencies:
- dependency-name: google-cloud-storage
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-27 14:48:46 +05:45
David de la Iglesia Castro 8c2f8095da debug: Remove test_viztracer_internal 2022-01-27 09:36:06 +05:45
David de la Iglesia Castro 5e80fcce4f exp show: Include outs in --json output. 2022-01-26 18:45:48 +01:00
David de la Iglesia Castro 1a1e28282d exp show: Include deps columns.
Use `repo.index.deps` to collect dependencies associated with each experiment.
2022-01-26 18:45:48 +01:00
Daniele Trifirò e3b97cb3a7 ignore: move debug statetement to the proper location 2022-01-26 18:44:01 +02:00
David de la Iglesia Castro d882e21b9b debug: Remove viztracer-depth default 2022-01-25 20:28:41 +05:45
David de la Iglesia Castro 3276c76a02 tests: Add test_debug 2022-01-25 20:28:41 +05:45
Saugat Pachhai 758dc43575 Update dvc/_debug.py 2022-01-25 20:28:41 +05:45
David de la Iglesia Castro f1dc9e50a7 debug: Add support for viztracer profiler. 2022-01-25 20:28:41 +05:45
Saugat Pachhai (सौगात) 2f15f30b0e exp run: do not log message twice on workspace executor
Fixes #7276.
2022-01-25 19:01:48 +05:45
Peter Rowlands 47d10c4a8b utils: add decorator to wrap diskcache pickle errors 2022-01-25 16:21:53 +09:00
David de la Iglesia Castro 822b501069 add: Update help of --jobs 2022-01-24 21:06:47 +01:00
Saugat Pachhai (सौगात) 943481f0a3 exp init: handle exception properly on missing parameters file 2022-01-24 17:43:29 +05:45
Jeff Hale b30385885b Fix 3 README links so that they render in GitHub 2022-01-24 15:55:12 +09:00
David de la Iglesia Castro d4861d535a TabularData: Simplify drop internals.
Update `subset` type hint.
2022-01-22 09:41:29 +08:00
dberenbaum 646097a735 update exp init help text 2022-01-21 11:07:32 +05:45
David de la Iglesia Castro 43953ace53 exp show: Add --drop and --keep args
Removed `--include-metrics` / `--include-params` and
`--exclude-metrics` / `--exclude-params`.

Removed `--no-timestamp` . Can be done by `--drop Created`.

`--drop` and `--keep` operate directly on the table columns.

`--keep` does not perform any filtering. It's only used
to specify columns to keep despite the other filtering.

For example `--only-changed --keep foo` will prevent
`foo` from being removed by `--only-changed`.

Another example, `--drop train.* --keep train.dropout`
will remove all columns matching `train.*` except for
`train.dropout`.

Closes #7079
Closes #7080
Pre-requisite for #6434
2022-01-20 03:28:33 +01:00
David de la Iglesia Castro 255e6a1aec TabularData: Support protected`columns.
TabularData: Add subset arg to dropna and drop_duplicates.

Closes #7083
2022-01-20 03:28:33 +01:00
Saugat Pachhai (सौगात) 73fee513b9 cli: improve startup speed
fsspec callbacks were making us lose 35ms (~207 ms vs 170ms now).
These are now extracted from dvc.progress to dvc.fs._callback.
2022-01-19 20:38:19 +02:00
Saugat Pachhai (सौगात) b0f65cbd17 tests: increase timeout for exp init tests 2022-01-19 20:26:41 +05:45
Saugat Pachhai (सौगात) 672f079cc6 tests: clean repositories before each test runs 2022-01-19 20:21:48 +05:45
Saugat Pachhai (सौगात) 446516d788 refactor: rename dvc.command to dvc.commands 2022-01-19 19:41:40 +05:45
Saugat Pachhai (सौगात) 1b3e1251e6 cli refactor: move dvc.command.base to dvc.cli.command 2022-01-19 19:41:40 +05:45
Saugat Pachhai (सौगात) 4268b5a339 cli: don't inherit DvcParserError from DvcException, move to dvc.cli 2022-01-19 19:41:40 +05:45
Saugat Pachhai (सौगात) f5baf990cf cli: refactor into a module
By splitting dvc.cli into a module, we'll be able to share cli-related
utilities, split parser-related stuffs into smaller parts, improve
reusability and improve API for command extensions in the future.
2022-01-19 12:22:07 +02:00
dependabot[bot] 69ff760e4b build(deps-dev): Bump pytest-timeout from 2.0.2 to 2.1.0
Bumps [pytest-timeout](https://github.com/pytest-dev/pytest-timeout) from 2.0.2 to 2.1.0.
- [Release notes](https://github.com/pytest-dev/pytest-timeout/releases)
- [Commits](https://github.com/pytest-dev/pytest-timeout/compare/2.0.2...2.1.0)

---
updated-dependencies:
- dependency-name: pytest-timeout
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-19 10:57:17 +05:45
Saugat Pachhai (सौगात) 4ba504be3e repro/exp run: display stage instead of filepath on failure
Before it used to say 'failed to reproduce dvc.yaml', which is now changed
to print 'failed to reproduce train' or something similar.

For stages that are in dvc.yaml in the current working directory, it will
just print the stage names. For stages in other dvc.yaml files, it'll print

in <relative_path>:<stage_name>.
2022-01-19 06:58:40 +05:45
Ruslan Kuprieiev 377b0bc689 find_root: unify error message
We used to use that message for special external repo cases, but we don't
need it anymore.

Fixes #7281

Leftover from #7214
2022-01-19 01:39:48 +02:00
Matt Seddon 89f2d42df8 plots: update linear template so colors can be externally overwritten 2022-01-18 12:42:17 +01:00
karajan1001 57263f15fa Pull: accept multiple as input(#7196)
fix: #7196

New feature: now `dvc exp pull` can accept a list of experiment names as input.

2. add support to list input for the `exp pull`
3. add function tests for the multi exp pull
4. add unit test for the arg parse
2022-01-18 17:31:55 +08:00
karajan1001 9bb6494e55 Push: accept multiple as input(#7196)
fix: #7196

New feature: now `dvc exp push` can accept a list of experiment names as input.

1. refactor of experiment push
2. add support to list input for the `exp push`
3. add function tests for the multi exp push
4. add unit test for the arg parse
2022-01-18 17:31:55 +08:00
Ruslan Kuprieiev 13238e9716 objects: fix imports 2022-01-15 21:39:39 +02:00
Ruslan Kuprieiev 85181e396f objects: fix empty line 2022-01-15 21:20:09 +02:00
Ruslan Kuprieiev 4c3c492b90 objects: don't forget __init__.py 2022-01-15 21:16:20 +02:00
dependabot[bot] 474812195e build(deps-dev): Bump wsgidav from 3.1.1 to 4.0.1
Bumps [wsgidav](https://github.com/mar10/wsgidav) from 3.1.1 to 4.0.1.
- [Release notes](https://github.com/mar10/wsgidav/releases)
- [Changelog](https://github.com/mar10/wsgidav/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mar10/wsgidav/compare/v3.1.1...v4.0.1)

---
updated-dependencies:
- dependency-name: wsgidav
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-14 09:12:16 +05:45
Saugat Pachhai c83612be71 deps-dev: don't specify pygments explicitly
pygments is a transitive dependency for us, from `rich` and here it was for `collective.checkdocs`.
2022-01-14 09:11:20 +05:45
dependabot[bot] 161e7892ba build(deps-dev): Bump google-cloud-storage from 1.43.0 to 2.0.0
Bumps [google-cloud-storage](https://github.com/googleapis/python-storage) from 1.43.0 to 2.0.0.
- [Release notes](https://github.com/googleapis/python-storage/releases)
- [Changelog](https://github.com/googleapis/python-storage/blob/main/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-storage/compare/v1.43.0...v2.0.0)

---
updated-dependencies:
- dependency-name: google-cloud-storage
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-14 09:02:38 +05:45
dependabot[bot] 0a0e3c3f9c build(deps-dev): Bump types-requests from 2.27.5 to 2.27.7
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.5 to 2.27.7.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-14 09:02:25 +05:45
dependabot[bot] ac2feb2340 build(deps): Bump pyinstaller from 4.7 to 4.8
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 4.7 to 4.8.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES-3.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v4.7...v4.8)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-13 17:00:03 +09:00
Peter Rowlands dd5d999644 exp: add exp run --machine flag 2022-01-13 16:34:20 +09:00
Peter Rowlands 3c7695b39d exp: add SSHExecutor.reproduce via exp exec-run 2022-01-13 16:34:20 +09:00
Peter Rowlands 367e5d1429 exp: add exec-run plumbing command to run exp from ExecutorInfo 2022-01-13 16:34:20 +09:00
Peter Rowlands fb16cd06e3 machine: update behavior for exp ssh executors
- log startup script finished message
- add setup_script option for runtime env setup
2022-01-13 16:34:20 +09:00
Peter Rowlands 39eee9b5a6 exp: implement git + DVC cache collection for ssh executors 2022-01-13 16:34:20 +09:00
Peter Rowlands 84da78a62b exp: initial SSHExecutorManager implementation 2022-01-13 16:34:20 +09:00
Peter Rowlands 4b9d39c901 push/fetch: support push/fetch to specific ODB
- prerequisite for remote execution
- allow explicitly pushing imports which would normally be skipped
  (internal-use only, does not apply to `dvc push`)
2022-01-13 16:34:20 +09:00
David de la Iglesia Castro 5b8bf52eab plots: Fix --json requiring -o.
`-o` was being required even when renderers don't need output path.
2022-01-12 18:00:08 +01:00
karajan1001andPeter Rowlands b673ac24d0 exp show: add --rev flag (#7152)
fix: #7152
1. Add `--rev` flag to `dvc exp show`.
2. Add -1 support for `--num` flag`.
3. Extract revision found logic to `utils`.
4. Brancher use this revision found logic.
5. Update command unit test
6. Add a util unit test.

Co-authored-by: Peter Rowlands (변기호) <[email protected]>
2022-01-11 19:01:34 +08:00
Saugat Pachhai (सौगात) 87abeaddc2 rich-table: fix add_column args/kwargs
Rich added `vertical` kwarg in `add_column`, which linter
is now complaining (as it should). But this may keep on breaking
when rich adds/removes args/kwargs in the future.

So, this commit uses (*args, **kwargs) instead of specifying particular
parameters explicitly. This also sets `collapse` directly to the
`rich.table.Column` rather than extending through our one `Column`
implementation.
2022-01-11 11:28:54 +05:45
dependabot[bot] a3dece8236 build(deps-dev): Bump types-tabulate from 0.8.4 to 0.8.5
Bumps [types-tabulate](https://github.com/python/typeshed) from 0.8.4 to 0.8.5.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-tabulate
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-11 09:19:36 +05:45
dependabot[bot] 221adf246b build(deps-dev): Bump types-toml from 0.10.1 to 0.10.3
Bumps [types-toml](https://github.com/python/typeshed) from 0.10.1 to 0.10.3.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-toml
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-11 09:10:44 +05:45
dependabot[bot] 8681086bfb build(deps-dev): Bump mypy from 0.930 to 0.931
Bumps [mypy](https://github.com/python/mypy) from 0.930 to 0.931.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.930...v0.931)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-11 09:02:39 +05:45
dependabot[bot] 6c3b1370a3 build(deps-dev): Bump types-requests from 2.26.3 to 2.27.5
Bumps [types-requests](https://github.com/python/typeshed) from 2.26.3 to 2.27.5.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-11 08:36:12 +05:45
Ruslan Kuprieiev 9a01d98876 odb: move gc into a separate function 2022-01-11 01:52:34 +02:00
Ruslan Kuprieiev e190433249 lint: add new ignores
mypy:

```
dvc/utils/table.py: note: In member "add_column" of class "Table":
dvc/utils/table.py:24:5: error: Argument 7 of "add_column" is incompatible with
supertype "Table"; supertype defines the argument type as
"Union[Literal['top'], Literal['middle'], Literal['bottom']]"  [override]
        def add_column(  # pylint: disable=arguments-differ
        ^
dvc/utils/table.py:24:5: note: This violates the Liskov substitution principle
dvc/utils/table.py:24:5: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
dvc/utils/table.py:24:5: error: Argument 8 of "add_column" is incompatible with
supertype "Table"; supertype defines the argument type as
"Union[Literal['fold'], Literal['crop'], Literal['ellipsis'], Literal['ignore']]"
 [override]
        def add_column(  # pylint: disable=arguments-differ
        ^
dvc/utils/table.py:24:5: error: Argument 12 of "add_column" is incompatible
with supertype "Table"; supertype defines the argument type as "Optional[int]"
[override]
        def add_column(  # pylint: disable=arguments-differ
        ^
Found 3 errors in 1 file (checked 129 source files)
dvc/ui/table.py:87: note: In module imported here,
dvc/compare.py:25: note: ... from here:
dvc/utils/table.py: note: In member "add_column" of class "Table":
dvc/utils/table.py:24:5: error: Argument 7 of "add_column" is incompatible with
supertype "Table"; supertype defines the argument type as
"Union[Literal['top'], Literal['middle'], Literal['bottom']]"  [override]
        def add_column(  # pylint: disable=arguments-differ
        ^
dvc/utils/table.py:24:5: note: This violates the Liskov substitution principle
dvc/utils/table.py:24:5: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
dvc/utils/table.py:24:5: error: Argument 8 of "add_column" is incompatible with
supertype "Table"; supertype defines the argument type as
"Union[Literal['fold'], Literal['crop'], Literal['ellipsis'], Literal['ignore']]"
 [override]
        def add_column(  # pylint: disable=arguments-differ
        ^
dvc/utils/table.py:24:5: error: Argument 12 of "add_column" is incompatible
with supertype "Table"; supertype defines the argument type as "Optional[int]"
[override]
        def add_column(  # pylint: disable=arguments-differ
        ^
Found 3 errors in 1 file (checked 128 source files)
```

pylint:

```
************* Module dvc.utils.table
dvc/utils/table.py:24:4: W0237: Parameter 'vertical' has been renamed to 'overflow' in overridden 'Table.add_column' method (arguments-renamed)
dvc/utils/table.py:24:4: W0237: Parameter 'overflow' has been renamed to 'width' in overridden 'Table.add_column' method (arguments-renamed)
dvc/utils/table.py:24:4: W0237: Parameter 'width' has been renamed to 'min_width' in overridden 'Table.add_column' method (arguments-renamed)
dvc/utils/table.py:24:4: W0237: Parameter 'min_width' has been renamed to 'max_width' in overridden 'Table.add_column' method (arguments-renamed)
dvc/utils/table.py:24:4: W0237: Parameter 'max_width' has been renamed to 'ratio' in overridden 'Table.add_column' method (arguments-renamed)
dvc/utils/table.py:24:4: W0237: Parameter 'ratio' has been renamed to 'no_wrap' in overridden 'Table.add_column' method (arguments-renamed)
dvc/utils/table.py:24:4: W0237: Parameter 'no_wrap' has been renamed to 'collapse' in overridden 'Table.add_column' method (arguments-renamed)
```
2022-01-11 00:47:38 +02:00
Ruslan Kuprieiev f64018b75e dvc: separate data and objects
Initial simple separation to get the process of extracting dvc-objects
and dvc-data packages out of dvc.

dvc-objects is for:
 * defining base Object (aka HashFile) and ObjectDB
 * providing tools to work with those objects (check/transfer/etc)
 * etc

dvc-data is for:
 * defining higher level objects (e.g. Tree)
 * bulding objects from the workspace (aka staging)
 * projecting objects back into the workspace (aka checkout)
 * comparing Tree objects (aka diff)
 * defining misc object databases (e.g LocalObjectDB)
 * higher-level transfer (e.g. with Tree indexes)
 * etc

For example, the objects we use in run-cache are based on dvc-objects
and not on dvc-data, as they have absolutely nothing to do with data
management.

There are still some mixups in the code that will be addressed in the
followups.
2022-01-11 00:47:38 +02:00
Gavin Li 92f714e5ca fs: test_links: create destination directory first
`dvc.fs.utils.test_links()` fails with a CacheLinkError if the target `to_path` does not already exist, as is the case when "abc.dvc" exists (referring to a directory object) and we try to run "dvc pull abc/def". To avoid this issue, fully create `to_path` before attempting to create a link in it.
2022-01-08 13:56:51 +02:00
Jon Burdo 634246f81c add tests for Path.parts 2022-01-07 13:26:45 +02:00
Jon Burdo 05b5e9dc29 handle paths ending with sep in dvc.fs.path.Path.parts 2022-01-07 13:26:45 +02:00
Paweł Redzyński a3144482ba render: vega: data processing outside of renderers
Fixes: #6943
2022-01-07 10:39:12 +01:00
Amrit GhimireandSaugat Pachhai 44a1146915 test: fix test failure in case of different default branch (#6857)
* test: fix test failure in case of different default branch

When the git config has set global `init.defaultBranch` different than master,
a few test cases were failing.
Currently, the test case is assuming the default branch to be set as
master.

This commit will introduce **initial_branch** option to git init call
which will ensure *master* branch is used as default for out test cases.

Set default git branch to main for test CI

Since main is going to be new default branch, it will let the test case
handle the scenario where default branch is set other than master.

Introduce isolation for git configuration

Co-authored-by: Saugat Pachhai (सौगात) <[email protected]>
2022-01-07 11:13:04 +05:45
Saugat Pachhai (सौगात) ec3600eb8e output: don't raise RemoteCacheRequiredError on instantiation, make it lazy 2022-01-06 22:29:49 +02:00
Paweł Redzyński fe4fefd880 ls: handle broken symlink
Fixes: #7120
2022-01-06 22:12:17 +02:00
karajan1001andPeter Rowlands b6a2402d27 exp remove: refactor exp utils (#7196)
fix: #7196

1. Refactor exp_ref_by_name in `dvc/repo/experiments/utils.py`.

In previous the exp_ref_by_name walk throught all of the references to
match only one exp name. This makes multi exp name seeking to be a job
of `O(mn)` complexity, Walking though the exp ref and return all of the
exp ref with greatly reduce this complexity to `O(n)`

Co-authored-by: Peter Rowlands (변기호) <[email protected]>
2022-01-06 12:20:29 +08:00
Saugat Pachhai d90fe54df4 tests: remove tests.unit.scm
I forgot to remove it before during #6836.
2022-01-05 20:36:29 +05:45
Peter Rowlands 9e590edb70 odb: pin index pickle protocol version 2022-01-04 20:22:08 +02:00
karajan1001 6bb4889022 ui: remove None in CellT type
From https://github.com/iterative/dvc/pull/6893/files#r777784748
Table rendering does not support `None` for now, and we will change it
back after (#7167).
2022-01-04 15:41:18 +08:00
David de la Iglesia Castro 0a4958bafd TabularData: Decouple pcp from render. 2022-01-03 16:30:54 +01:00
David de la Iglesia Castro 5a9f861921 exp show: Rename html to pcp
Closes #7165
2022-01-03 16:30:54 +01:00
David de la Iglesia Castro fc1ab7d3a1 Update show.py 2022-01-03 16:14:37 +01:00
David de la Iglesia Castro 760c8fceb3 plots: Add auto_open hint 2022-01-03 16:07:23 +01:00
David de la Iglesia Castro d8e2c14927 config: Add plots.auto_open.
Option for enabling automatically open webbrowser with generated html.
Defaults False.

Closes #6091
2022-01-03 16:07:23 +01:00
dependabot[bot] a73accd071 build(deps-dev): Bump filelock from 3.4.0 to 3.4.2
Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.4.0 to 3.4.2.
- [Release notes](https://github.com/tox-dev/py-filelock/releases)
- [Changelog](https://github.com/tox-dev/py-filelock/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/py-filelock/compare/3.4.0...3.4.2)

---
updated-dependencies:
- dependency-name: filelock
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-03 20:04:58 +05:45
dependabot[bot] 69ac43f131 build(deps-dev): Bump pylint-plugin-utils from 0.6 to 0.7
Bumps [pylint-plugin-utils](https://github.com/PyCQA/pylint-plugin-utils) from 0.6 to 0.7.
- [Release notes](https://github.com/PyCQA/pylint-plugin-utils/releases)
- [Commits](https://github.com/PyCQA/pylint-plugin-utils/compare/0.6...0.7)

---
updated-dependencies:
- dependency-name: pylint-plugin-utils
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-03 17:36:22 +05:45
Alexander Schepanovski 9ee428ef17 Fix memory leak in Index.build_graph()
`@memoize()` memory lives as long as decorated function lives, which is
permanent in this case. Since `self` is referenced as a memory key no
`Index` will ever be freed.

Changed to use `@cached_property` whose cache lives as long as the
parent instance.
2022-01-03 13:41:10 +02:00
Peter Rowlands 0e0e1725e6 exp: handle non-exp refs in get_exact_name() 2022-01-03 15:02:19 +05:45
Saugat Pachhai (सौगात) a1609cfb10 exp: raise InvalidExpRefError properly
Before it was just left instantiated, but was not raised.
2022-01-03 15:02:19 +05:45
David de la Iglesia Castro 175ffaeb22 exp show: Remove alternating background 2022-01-03 16:38:39 +09:00
dependabot[bot] 025e15d500 build(deps-dev): Bump types-tabulate from 0.8.3 to 0.8.4
Bumps [types-tabulate](https://github.com/python/typeshed) from 0.8.3 to 0.8.4.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-tabulate
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-03 10:38:08 +05:45
dependabot[bot] 52a234c5b8 build(deps-dev): Bump pygments from 2.10.0 to 2.11.1
Bumps [pygments](https://github.com/pygments/pygments) from 2.10.0 to 2.11.1.
- [Release notes](https://github.com/pygments/pygments/releases)
- [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES)
- [Commits](https://github.com/pygments/pygments/compare/2.10.0...2.11.1)

---
updated-dependencies:
- dependency-name: pygments
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-03 10:09:53 +05:45
Ruslan Kuprieiev 8ec1af1cf4 config: remove useless os.path.join 2021-12-31 19:03:05 +02:00
Ruslan Kuprieiev 35d364a527 repo: use fs instead of scm
SCM is used for both reading and writing operations, but in the former case -
as filesystem and in the latter - as actual scm/git. So we should distinguish
those and use fs directly, which follows the overall philosophy of being able
to use Repo on any fs.

Keeping `rev=` option for `Repo` because it is a very convenient way to get
repo instance from local path but for particular branch/tag/etc.

Pre-requisite for git/dvc/repofs refactorings.
2021-12-31 02:44:21 +02:00
David de la Iglesia Castro 9a5614d326 TabularData: Add ignore_empty flag to drop_duplicates.
Configures whether to consider missing values as relevant or not.
2021-12-29 21:33:36 +01:00
David de la Iglesia Castro 12a737d905 TabularData: Fix drop_duplicates for rich_text NaN.
When filling missing values with `ui.rich_text` (i.e. in experiments show CMD), those values were not being correctly matched against `self._fill_value`)
2021-12-29 21:33:36 +01:00
Paweł Redzyński 51a6116b92 plots: dir: handle no data in cache/remote
Fixes: iterative/cse#4
2021-12-29 13:21:37 +01:00
dependabot[bot] 3566b1941c build(deps-dev): Bump types-requests from 2.26.2 to 2.26.3
Bumps [types-requests](https://github.com/python/typeshed) from 2.26.2 to 2.26.3.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-29 12:53:59 +09:00
pre-commit-ci[bot] ceeda6d307 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.0.1 → v4.1.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.0.1...v4.1.0)
2021-12-28 15:01:38 +08:00
karajan1001 98d7f785e9 exp show: remove a useless function
The function `_raise_error_if_all_disabled` is copyed wrongly to this
place during sub command refactoring. It used to be used in `exp gc`,
can be deleted from this function.
2021-12-27 20:35:07 +01:00
Ruslan Kuprieiev 04e93da0bd fs: remove unused RemoteCmdError 2021-12-23 19:02:44 +02:00
Ruslan Kuprieiev 72aaca4343 odb: move verify option to config
These options are part of the default config for each particular remote.
Plugins will define their default config on theirown, but for now we keep
those in one config schema.
2021-12-23 12:49:16 +02:00
dependabot[bot] 60cb8322d9 build(deps-dev): Bump mypy from 0.921 to 0.930
Bumps [mypy](https://github.com/python/mypy) from 0.921 to 0.930.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.921...v0.930)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-23 09:50:23 +05:45
dependabot[bot] b0b9a74207 build(deps-dev): Bump wheel from 0.37.0 to 0.37.1
Bumps [wheel](https://github.com/pypa/wheel) from 0.37.0 to 0.37.1.
- [Release notes](https://github.com/pypa/wheel/releases)
- [Changelog](https://github.com/pypa/wheel/blob/main/docs/news.rst)
- [Commits](https://github.com/pypa/wheel/compare/0.37.0...0.37.1)

---
updated-dependencies:
- dependency-name: wheel
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-23 09:50:05 +05:45
Saugat Pachhai (सौगात) 32fcc73078 deps: explicitly specify pyarrow for hdfs 2021-12-22 16:45:12 +02:00
Saugat Pachhai (सौगात) e3596178d4 deps: move requests-kerberos to webhdfs_kerberos extras instead of webhdfs 2021-12-22 16:44:52 +02:00
Saugat Pachhai (सौगात) ba40d91de6 fs.hdfs: skip port if not available 2021-12-22 16:43:56 +02:00
dependabot[bot] 02cfbcfc32 build(deps-dev): Bump mypy from 0.920 to 0.921
Bumps [mypy](https://github.com/python/mypy) from 0.920 to 0.921.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.920...v0.921)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-22 12:45:02 +02:00
dependabot[bot] 96acb64fec build(deps): Bump scmrepo from 0.0.6 to 0.0.7
Bumps [scmrepo](https://github.com/iterative/scmrepo) from 0.0.6 to 0.0.7.
- [Release notes](https://github.com/iterative/scmrepo/releases)
- [Commits](https://github.com/iterative/scmrepo/compare/0.0.6...0.0.7)

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

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

tests: fix invalid set_ref in exp func test
2021-12-22 17:52:30 +09:00
David de la Iglesia Castro 1507a6ed99 Update benchmarks.yaml 2021-12-21 09:03:19 +05:45
Saugat Pachhai 6034f0e36a s3: remove explicit dependency on aiobotocore; simplify constraints
Hoping to fix `dvc[s3]` installations in pipenv. Pipenv's resolver is in loop, backtracking ruamel.yaml from 0.17.17 to 0.17.11 continuously. I think this has to do
more with `s3`'s dependency than `ruamel.yaml` as it only fails here. And, we are specifying dependency in 1.0.1 whereas aiobotocore has released 2.0 versions which is what is being installed, so it's redundant anyway. I am hopeful that this may help resolver with the simplification in constraints.
2021-12-20 14:32:42 +05:45
Saugat Pachhai 6df1ee87ab Specify bcrypt explicitly 2021-12-20 12:16:14 +05:45
dependabot[bot] a6263cffc8 build(deps-dev): Bump types-requests from 2.26.1 to 2.26.2
Bumps [types-requests](https://github.com/python/typeshed) from 2.26.1 to 2.26.2.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-20 10:37:39 +08:00
Paweł Redzyński 8ca3a7b684 fs: repo: raise proper FileNotFoundError in metadata
Related: #7120
2021-12-17 15:48:29 +02:00
David de la Iglesia Castro 4d5be46ab4 exp show --html: Handle fill_value.
Adds new argument `fill_value` to ParallelCoordinatesRenderer.

For scalar columns, prevent converting to categorical by explictly inserting None when value is missing (value == fill_value).

For categorical columns, replace `fill_value` with `"Missing"` and ensure it is the last item so it gets rendered on top of the column.
2021-12-17 14:45:12 +09:00
David de la Iglesia Castro 5ee82d4000 exp show: Add parallel coordinates plot.
Uses `TabularData.to_parallel_coordinates`.
Adds new arguments: `html`, `out`, `open`

Reuses `--sort-by` to define colorscale.

Closes #4455
2021-12-17 14:45:12 +09:00
David de la Iglesia Castro a5b7c5ea19 TabularData: Add to_parallel_coordinates.
Uses `ParallelCoordinatesRenderer` and `dvc.render.html.write`.

pre-requisite #4455
2021-12-17 14:45:12 +09:00
David de la Iglesia Castro 19b3c82698 dvc render: Add plotly parallel coords renderer
New renderer based on plotly. Not exposed to `dvc plots`.
Generate plotly datapoints from `TabularData`.

pre-requisite #4455
2021-12-17 14:45:12 +09:00
David de la Iglesia Castro 9bd60f62d7 exp show: Add alternating row colors and update border style.
Use `box.HORIZONTALS`.

rich_table: Support `box.HORIZONTALS`

Closes #7128
2021-12-17 14:31:53 +09:00
dependabot[bot] 970cbfa64e build(deps-dev): Bump mypy from 0.910 to 0.920
Bumps [mypy](https://github.com/python/mypy) from 0.910 to 0.920.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.910...v0.920)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-16 16:04:47 +09:00
Ruslan Kuprieiev 0bf56fe29b rename master -> main 2021-12-15 22:19:36 +02:00
Ruslan Kuprieiev 30d6839a5f diff: use relpaths
Regression from path_info removal.

Also fixes #7147
2021-12-15 17:27:38 +02:00
Paweł Redzyński e24de3a0f3 plots: introduce --json option
Fixes: #7073
2021-12-15 11:05:57 +01:00
Peter Rowlands 25ef812ecf exp: use process manager dirs for serialized executors
- partially refactor existing pidfile behavior into using proc manager
- old pidfiles are now json-serialized executor info files placed in
  proc manager subdirs (alongside proc manager pidfile/logs/etc)
- move multiprocessing based (attached) execution into separate
  execution path from future "detached" execution support
2021-12-15 15:55:32 +09:00
Peter Rowlands fa819b0b70 exp: refactor workspace repro into executor classes 2021-12-15 15:55:32 +09:00
Peter Rowlands 37f8010b42 exp: initial executor manager refactor
- introduce ExecutorManager classes
- move executor init + execution from experiments.__init__ into the
  manager classes
- make executors serializable via ExecutorInfo
2021-12-15 15:55:32 +09:00
Saugat Pachhai 8319cc50c6 Update setup.cfg 2021-12-14 21:11:28 +05:45
dependabot[bot] 98c22e3277 build(deps): Bump scmrepo from 0.0.4 to 0.0.5
Bumps [scmrepo](https://github.com/iterative/scmrepo) from 0.0.4 to 0.0.5.
- [Release notes](https://github.com/iterative/scmrepo/releases)
- [Commits](https://github.com/iterative/scmrepo/compare/0.0.4...0.0.5)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-14 21:11:28 +05:45
Saugat Pachhai (सौगात) 3ca8e26169 tests: get rid of GitFS related tests
Also refactored unittest-based tests for LocalFileSystem to pytest
2021-12-14 17:55:26 +05:45
karajan1001andPeter Rowlands 0deab0118a exp: split exp sub command (#5489)
related to #5489
Current dvc/command/experiments.py is too long for switch between cmd class
and the argument parser.

Split the sub command in dvc/command/experiments.py to individual files

Co-authored-by: Peter Rowlands (변기호) <[email protected]>
2021-12-14 19:16:04 +08:00
dependabot[bot] 72af8b8605 build(deps-dev): Bump pytest-timeout from 2.0.1 to 2.0.2
Bumps [pytest-timeout](https://github.com/pytest-dev/pytest-timeout) from 2.0.1 to 2.0.2.
- [Release notes](https://github.com/pytest-dev/pytest-timeout/releases)
- [Commits](https://github.com/pytest-dev/pytest-timeout/compare/2.0.1...2.0.2)

---
updated-dependencies:
- dependency-name: pytest-timeout
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-14 16:53:58 +08:00
Saugat Pachhai (सौगात) ba4955738f tests: remove func.test_scm 2021-12-13 21:03:24 +05:45
dependabot[bot] 602713e57b build(deps-dev): Bump pytest-xdist from 2.4.0 to 2.5.0
Bumps [pytest-xdist](https://github.com/pytest-dev/pytest-xdist) from 2.4.0 to 2.5.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/v2.4.0...v2.5.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-13 13:39:50 +09:00
321 changed files with 8760 additions and 8760 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
pull_request_rules:
- name: backport patches to 1.11
conditions:
- base=master
- base=main
- label=backport-1.11
actions:
backport:
+2 -2
View File
@@ -14,10 +14,10 @@ jobs:
uses: styfle/[email protected]
with:
access_token: ${{ github.token }}
- uses: actions/setup-python@v2
- uses: actions/setup-python@v3
with:
python-version: 3.7
- uses: iterative/dvc-bench@master
- uses: iterative/dvc-bench@main
with:
pytest_options: "-k 'test_init or test_help'"
+11 -11
View File
@@ -2,7 +2,7 @@ name: Build packages
on:
push:
branches:
- master
- main
release:
types: [released, prereleased]
workflow_dispatch:
@@ -10,11 +10,11 @@ jobs:
deb:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python 3.9
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v3
with:
python-version: 3.9
- name: Set up Ruby 2.4
@@ -41,11 +41,11 @@ jobs:
rpm:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python 3.9
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v3
with:
python-version: 3.9
- name: Set up Ruby 2.4
@@ -72,11 +72,11 @@ jobs:
osxpkg:
runs-on: macos-10.15
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python 3.9
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v3
with:
python-version: 3.9
- name: Set up Ruby 2.4
@@ -106,11 +106,11 @@ jobs:
exe:
runs-on: windows-2019
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python 3.9
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v3
with:
python-version: 3.9
- name: Install deps
@@ -136,11 +136,11 @@ jobs:
pip:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python 3.7
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v3
with:
python-version: 3.7
- name: Install
+7 -11
View File
@@ -20,11 +20,11 @@ jobs:
uses: styfle/[email protected]
with:
access_token: ${{ github.token }}
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v3
with:
python-version: 3.8
- name: Install requirements
@@ -43,11 +43,11 @@ jobs:
os: [ubuntu-20.04, windows-latest, macos-latest]
pyv: ["3.7", "3.8", "3.9", "3.10"]
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.pyv }}
- name: get pip cache dir
@@ -56,7 +56,7 @@ jobs:
echo "::set-output name=dir::$(pip cache dir)"
- name: set pip cache
id: pip-cache
uses: actions/cache@v2.1.7
uses: actions/cache@v3
with:
path: ${{ steps.pip-cache-dir.outputs.dir }}
key: ${{ runner.os }}-pip-${{ hashFiles('setup.cfg') }}
@@ -67,10 +67,6 @@ jobs:
run: |
pip install --upgrade pip setuptools wheel
pip install -e ".[dev]"
- name: setup git
run: |
git config --global user.email "[email protected]"
git config --global user.name "DVC Tester"
- name: run tests
timeout-minutes: 40
run: >-
@@ -83,7 +79,7 @@ jobs:
file: ./coverage.xml
fail_ci_if_error: false
notify:
if: github.ref == 'refs/heads/master' && failure()
if: github.ref == 'refs/heads/main' && failure()
needs:
- lint
- tests
@@ -94,6 +90,6 @@ jobs:
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: 'CI Failed on master :boom:'
SLACK_MESSAGE: 'CI Failed on main :boom:'
SLACK_TITLE: CI Status
SLACK_USERNAME: DVC-CI
+2 -2
View File
@@ -6,9 +6,9 @@ repos:
- id: black
language_version: python3
repo: https://github.com/ambv/black
rev: 21.12b0
rev: 22.1.0
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
rev: v4.1.0
hooks:
- id: check-toml
- id: check-yaml
+6 -6
View File
@@ -103,7 +103,7 @@ Snap (Snapcraft/Linux)
This corresponds to the latest tagged release.
Add ``--beta`` for the latest tagged release candidate,
or ``--edge`` for the latest ``master`` version.
or ``--edge`` for the latest ``main`` version.
Choco (Chocolatey/Windows)
--------------------------
@@ -183,8 +183,8 @@ Fedora / CentOS (rpm)
Comparison to related technologies
==================================
#. Data Engineering tools such as `AirFlow <https://airflow.apache.org/>`,
`Luigi <https://github.com/spotify/luigi>`, and others - in DVC data,
#. Data Engineering tools such as `AirFlow <https://airflow.apache.org/>`_,
`Luigi <https://github.com/spotify/luigi>`_, and others - in DVC data,
model and ML pipelines represent a single ML project focused on data
scientists' experience. Data engineering tools orchestrate multiple data
projects and focus on efficient execution. A DVC project can be used from
@@ -212,7 +212,7 @@ Comparison to related technologies
#. `DVC Studio <https://studio.iterative.ai/>`_ - official online
platform for DVC projects. It can be used to manage data and models, run
and track experiments, and visualize and share results. Also, it
integrates with `CML (CI/CD for ML) <https://cml.dev/>` for training
integrates with `CML (CI/CD for ML) <https://cml.dev/>`__ for training
models in the cloud or Kubernetes.
@@ -254,7 +254,7 @@ Barrak, A., Eghan, E.E. and Adams, B. `On the Co-evolution of ML Pipelines and S
:target: https://dvc.org
:alt: DVC logo
.. |CI| image:: https://github.com/iterative/dvc/workflows/Tests/badge.svg?branch=master
.. |CI| image:: https://github.com/iterative/dvc/workflows/Tests/badge.svg?branch=main
:target: https://github.com/iterative/dvc/actions
:alt: GHA Tests
@@ -262,7 +262,7 @@ Barrak, A., Eghan, E.E. and Adams, B. `On the Co-evolution of ML Pipelines and S
:target: https://codeclimate.com/github/iterative/dvc
:alt: Code Climate
.. |Coverage| image:: https://codecov.io/gh/iterative/dvc/branch/master/graph/badge.svg
.. |Coverage| image:: https://codecov.io/gh/iterative/dvc/branch/main/graph/badge.svg
:target: https://codecov.io/gh/iterative/dvc
:alt: Codecov
+1 -1
View File
@@ -1,7 +1,7 @@
"""Main entry point for DVC command line tool."""
import sys
from dvc.main import main
from dvc.cli import main
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
+34 -2
View File
@@ -1,10 +1,32 @@
from contextlib import ExitStack, contextmanager
from datetime import datetime
from typing import TYPE_CHECKING, Callable, Union
if TYPE_CHECKING:
from argparse import Namespace
@contextmanager
def viztracer_profile(
path: Union[Callable[[], str], str],
max_stack_depth: int = -1,
):
try:
import viztracer # pylint: disable=import-error
except ImportError:
print("Failed to run profiler, viztracer is not installed")
yield
return
tracer = viztracer.VizTracer(max_stack_depth=max_stack_depth)
tracer.start()
yield
tracer.stop()
tracer.save(path() if callable(path) else path)
@contextmanager
def yappi_profile(
path: Union[Callable[[], str], str] = None, wall_clock: bool = True
@@ -100,12 +122,18 @@ def debugtools(args: "Namespace" = None, **kwargs):
if kw.get("instrument") or kw.get("instrument_open"):
stack.enter_context(instrument(kw.get("instrument_open", False)))
if kw.get("yappi"):
from datetime import datetime
output = "callgrind.dvc-{0:%Y%m%d}_{0:%H%M%S}.out"
stack.enter_context(
yappi_profile(path=lambda: output.format(datetime.now()))
)
if kw.get("viztracer") or kw.get("viztracer_depth"):
output = "viztracer.dvc-{0:%Y%m%d}_{0:%H%M%S}.json"
stack.enter_context(
viztracer_profile(
path=lambda: output.format(datetime.now()),
max_stack_depth=kw.get("viztracer_depth") or -1,
)
)
yield
@@ -118,6 +146,10 @@ def add_debugging_flags(parser):
parser.add_argument(
"--yappi", action="store_true", default=False, help=SUPPRESS
)
parser.add_argument(
"--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
+5 -4
View File
@@ -21,13 +21,14 @@ def get_url(path, repo=None, rev=None, remote=None):
with Repo.open(repo, rev=rev, subrepos=True, uninitialized=True) as _repo:
fs_path = _repo.fs.path.join(_repo.root_dir, path)
with reraise(FileNotFoundError, PathMissingError(path, repo)):
metadata = _repo.repo_fs.metadata(fs_path)
info = _repo.repo_fs.info(fs_path)
if not metadata.is_dvc:
if not info["isdvc"]:
raise OutputNotFoundError(path, repo)
cloud = metadata.repo.cloud
md5 = metadata.repo.dvcfs.info(fs_path)["md5"]
cloud = info["repo"].cloud
dvc_path = _repo.fs.path.relpath(fs_path, info["repo"].root_dir)
md5 = info["repo"].dvcfs.info(dvc_path)["md5"]
return cloud.get_url_for(remote, checksum=md5)
+131
View File
@@ -0,0 +1,131 @@
"""This module provides an entrypoint to the dvc cli and parsing utils."""
import logging
# Workaround for CPython bug. See [1] and [2] for more info.
# [1] https://github.com/aws/aws-cli/blob/1.16.277/awscli/clidriver.py#L55
# [2] https://bugs.python.org/issue29288
"".encode("idna")
logger = logging.getLogger("dvc")
class DvcParserError(Exception):
"""Base class for CLI parser errors."""
def __init__(self):
super().__init__("parser error")
def parse_args(argv=None):
"""Parses CLI arguments.
Args:
argv: optional list of arguments to parse. sys.argv is used by default.
Raises:
DvcParserError: raised for argument parsing errors.
"""
from .parser import get_main_parser
parser = get_main_parser()
args = parser.parse_args(argv)
return args
def main(argv=None): # noqa: C901
"""Main entry point for dvc CLI.
Args:
argv: optional list of arguments to parse. sys.argv is used by default.
Returns:
int: command's return code.
"""
from dvc._debug import debugtools
from dvc.config import ConfigError
from dvc.exceptions import DvcException, NotDvcRepoError
from dvc.logger import FOOTER, disable_other_loggers
args = None
disable_other_loggers()
outerLogLevel = logger.level
try:
args = parse_args(argv)
level = None
if args.quiet:
level = logging.CRITICAL
elif args.verbose == 1:
level = logging.DEBUG
elif args.verbose > 1:
level = logging.TRACE
if level is not None:
logger.setLevel(level)
logger.trace(args)
if not args.quiet:
from dvc.ui import ui
ui.enable()
with debugtools(args):
cmd = args.func(args)
ret = cmd.do_run()
except ConfigError:
logger.exception("configuration error")
ret = 251
except KeyboardInterrupt:
logger.exception("interrupted by the user")
ret = 252
except NotDvcRepoError:
logger.exception("")
ret = 253
except DvcException:
ret = 255
logger.exception("")
except DvcParserError:
ret = 254
except Exception as exc: # noqa, pylint: disable=broad-except
# pylint: disable=no-member
import errno
if isinstance(exc, OSError) and exc.errno == errno.EMFILE:
from dvc.utils import error_link
logger.exception(
"too many open files, please visit "
"{} to see how to handle this "
"problem".format(error_link("many-files")),
extra={"tb_only": True},
)
else:
from dvc.info import get_dvc_info
logger.exception("unexpected error")
dvc_info = get_dvc_info()
logger.debug("Version info for developers:\n%s", dvc_info)
logger.info(FOOTER)
ret = 255
try:
from dvc import analytics
if analytics.is_enabled():
analytics.collect_and_send_report(args, ret)
return ret
finally:
logger.setLevel(outerLogLevel)
from dvc.external_repo import clean_repos
# Remove cached repos in the end of the call, these are anonymous
# so won't be reused by any other subsequent run anyway.
clean_repos()
@@ -5,29 +5,6 @@ from abc import ABC, abstractmethod
logger = logging.getLogger(__name__)
def fix_subparsers(subparsers):
"""Workaround for bug in Python 3. See more info at:
https://bugs.python.org/issue16308
https://github.com/iterative/dvc/issues/769
Args:
subparsers: subparsers to fix.
"""
subparsers.required = True
subparsers.dest = "cmd"
def append_doc_link(help_message, path):
from dvc.utils import format_link
if not path:
return help_message
doc_base = "https://man.dvc.org/"
return "{message}\nDocumentation: {link}".format(
message=help_message, link=format_link(doc_base + path)
)
class CmdBase(ABC):
UNINITIALIZED = False
+9 -20
View File
@@ -1,11 +1,10 @@
"""DVC command line interface"""
"""Main parser for the dvc cli."""
import argparse
import logging
import os
import sys
from ._debug import add_debugging_flags
from .command import (
from dvc.commands import (
add,
cache,
check_ignore,
@@ -45,8 +44,8 @@ from .command import (
update,
version,
)
from .command.base import fix_subparsers
from .exceptions import DvcParserError
from . import DvcParserError
logger = logging.getLogger(__name__)
@@ -96,7 +95,7 @@ def _find_parser(parser, cmd_cls):
defaults = parser._defaults # pylint: disable=protected-access
if not cmd_cls or cmd_cls == defaults.get("func"):
parser.print_help()
raise DvcParserError()
raise DvcParserError
actions = parser._actions # pylint: disable=protected-access
for action in actions:
@@ -143,6 +142,8 @@ def get_parent_parser():
When overwriting `-q` or `-v`, you need to instantiate a new object
in order to prevent some weird behavior.
"""
from dvc._debug import add_debugging_flags
parent_parser = argparse.ArgumentParser(add_help=False)
add_debugging_flags(parent_parser)
log_level_group = parent_parser.add_mutually_exclusive_group()
@@ -207,23 +208,11 @@ def get_main_parser():
help="Use `dvc COMMAND --help` for command-specific help.",
)
from .utils import fix_subparsers
fix_subparsers(subparsers)
for cmd in COMMANDS:
cmd.add_parser(subparsers, parent_parser)
return parser
def parse_args(argv=None):
"""Parses CLI arguments.
Args:
argv: optional list of arguments to parse. sys.argv is used by default.
Raises:
dvc.exceptions.DvcParserError: raised for argument parsing errors.
"""
parser = get_main_parser()
args = parser.parse_args(argv)
return args
+31
View File
@@ -0,0 +1,31 @@
def fix_subparsers(subparsers):
"""Workaround for bug in Python 3. See more info at:
https://bugs.python.org/issue16308
https://github.com/iterative/dvc/issues/769
Args:
subparsers: subparsers to fix.
"""
subparsers.required = True
subparsers.dest = "cmd"
def append_doc_link(help_message, path):
from dvc.utils import format_link
if not path:
return help_message
doc_base = "https://man.dvc.org/"
return "{message}\nDocumentation: {link}".format(
message=help_message, link=format_link(doc_base + path)
)
def fix_plumbing_subparsers(subparsers):
# metavar needs to be explicitly set in order to hide plumbing subcommands
# from the 'positional arguments' choices list
# see: https://bugs.python.org/issue22848
cmds = [
cmd for cmd, parser in subparsers.choices.items() if parser.add_help
]
subparsers.metavar = "{{{}}}".format(",".join(cmds))
File diff suppressed because it is too large Load Diff
+6 -4
View File
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
logger = logging.getLogger(__name__)
@@ -101,9 +102,10 @@ def add_parser(subparsers, parent_parser):
"--jobs",
type=int,
help=(
"Number of jobs to run simultaneously. "
"Only used along with '--to-remote'. "
"Number of jobs to run simultaneously "
"when pushing data to remote."
"The default value is 4 * cpu_count(). "
"For SSH remotes, the default is 4. "
),
metavar="<number>",
)
@@ -1,8 +1,8 @@
import argparse
from dvc.command import completion
from dvc.command.base import append_doc_link, fix_subparsers
from dvc.command.config import CmdConfig
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.commands import completion
from dvc.commands.config import CmdConfig
from dvc.ui import ui
@@ -28,7 +28,7 @@ class CmdCacheDir(CmdConfig):
def add_parser(subparsers, parent_parser):
from dvc.command.config import parent_config_parser
from dvc.commands.config import parent_config_parser
CACHE_HELP = "Manage cache settings."
@@ -1,7 +1,8 @@
import argparse
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.ui import ui
@@ -1,8 +1,9 @@
import argparse
import operator
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.exceptions import CheckoutError
from dvc.ui import ui
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
logger = logging.getLogger(__name__)
@@ -3,7 +3,8 @@ import logging
import shtab
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.ui import ui
logger = logging.getLogger(__name__)
@@ -82,7 +83,7 @@ EXPERIMENT = {"bash": "_dvc_compgen_exps", "zsh": "_dvc_compadd_exps"}
class CmdCompletion(CmdBaseNoRepo):
def run(self):
from dvc.cli import get_main_parser
from dvc.cli.parser import get_main_parser
parser = get_main_parser()
shell = self.args.shell
@@ -2,7 +2,8 @@ import argparse
import logging
import os
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.ui import ui
logger = logging.getLogger(__name__)
@@ -1,5 +1,6 @@
from dvc.command import completion
from dvc.command.base import CmdBaseNoRepo, fix_subparsers
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import fix_subparsers
from dvc.commands import completion
class CmdDaemonBase(CmdBaseNoRepo):
+2 -1
View File
@@ -1,6 +1,7 @@
import argparse
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.ui import ui
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
logger = logging.getLogger(__name__)
@@ -18,7 +19,7 @@ class CmdDataBase(CmdBase):
class CmdDataPull(CmdDataBase):
def log_summary(self, stats):
from dvc.command.checkout import log_changes
from dvc.commands.checkout import log_changes
log_changes(stats)
super().log_summary(stats)
@@ -97,7 +98,7 @@ class CmdDataFetch(CmdDataBase):
def shared_parent_parser():
from dvc.cli import get_parent_parser
from dvc.cli.parser import get_parent_parser
# Parent parser used in pull/push/status
parent_parser = argparse.ArgumentParser(
@@ -110,7 +111,6 @@ def shared_parent_parser():
help=(
"Number of jobs to run simultaneously. "
"The default value is 4 * cpu_count(). "
"For SSH remotes, the default is 4. "
),
metavar="<number>",
)
@@ -127,7 +127,7 @@ def shared_parent_parser():
def add_parser(subparsers, _parent_parser):
from dvc.command.status import CmdDataStatus
from dvc.commands.status import CmdDataStatus
# Pull
PULL_HELP = "Download tracked files or directories from remote storage."
@@ -1,7 +1,8 @@
import argparse
import logging
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
logger = logging.getLogger(__name__)
+4 -3
View File
@@ -2,8 +2,9 @@ import argparse
import logging
import os
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.ui import ui
logger = logging.getLogger(__name__)
@@ -127,7 +128,7 @@ class CmdDiff(CmdBase):
show_hash = self.args.show_hash
hide_missing = self.args.b_rev or self.args.hide_missing
if hide_missing:
del diff["not in cache"]
diff.pop("not in cache", None)
for key, entries in diff.items():
entries = sorted(
+101
View File
@@ -0,0 +1,101 @@
import argparse
from dvc.cli.utils import (
append_doc_link,
fix_plumbing_subparsers,
fix_subparsers,
)
from dvc.commands.experiments import (
apply,
branch,
diff,
exec_run,
gc,
init,
ls,
pull,
push,
remove,
run,
show,
)
SUB_COMMANDS = [
apply,
branch,
diff,
exec_run,
gc,
init,
ls,
pull,
push,
remove,
run,
show,
]
def add_parser(subparsers, parent_parser):
EXPERIMENTS_HELP = "Commands to run and compare experiments."
experiments_parser = subparsers.add_parser(
"experiments",
parents=[parent_parser],
aliases=["exp"],
description=append_doc_link(EXPERIMENTS_HELP, "exp"),
formatter_class=argparse.RawDescriptionHelpFormatter,
help=EXPERIMENTS_HELP,
)
experiments_subparsers = experiments_parser.add_subparsers(
dest="cmd",
help="Use `dvc experiments CMD --help` to display "
"command-specific help.",
)
fix_subparsers(experiments_subparsers)
for cmd in SUB_COMMANDS:
cmd.add_parser(experiments_subparsers, parent_parser)
fix_plumbing_subparsers(experiments_subparsers)
def add_rev_selection_flags(
experiments_subcmd_parser, command: str, default: bool = True
):
experiments_subcmd_parser.add_argument(
"-A",
"--all-commits",
action="store_true",
default=False,
help=(
f"{command} all experiments in the repository "
"(overrides `--rev` and `--num`)."
),
)
default_msg = " (HEAD by default)" if default else ""
msg = (
f"{command} experiments derived from the specified `<commit>` as "
f"baseline{default_msg}."
)
experiments_subcmd_parser.add_argument(
"--rev",
type=str,
default=None,
help=msg,
metavar="<commit>",
)
experiments_subcmd_parser.add_argument(
"-n",
"--num",
type=int,
default=1,
dest="num",
metavar="<num>",
help=(
f"{command} experiments from the last `num` commits "
"(first parents) starting from the `--rev` baseline. "
"Give a negative value to include all first-parent commits "
"(similar to `git log -n`)."
),
)
+42
View File
@@ -0,0 +1,42 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
logger = logging.getLogger(__name__)
class CmdExperimentsApply(CmdBase):
def run(self):
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_parser = experiments_subparsers.add_parser(
"apply",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_APPLY_HELP, "exp/apply"),
help=EXPERIMENTS_APPLY_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
experiments_apply_parser.add_argument(
"--no-force",
action="store_false",
dest="force",
help="Fail if this command would overwrite conflicting changes.",
)
experiments_apply_parser.add_argument(
"experiment", help="Experiment to be applied."
).complete = completion.EXPERIMENT
experiments_apply_parser.set_defaults(func=CmdExperimentsApply)
+34
View File
@@ -0,0 +1,34 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
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",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_BRANCH_HELP, "exp/branch"),
help=EXPERIMENTS_BRANCH_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
experiments_branch_parser.add_argument(
"experiment", help="Experiment to be promoted."
)
experiments_branch_parser.add_argument(
"branch", help="Git branch name to use."
)
experiments_branch_parser.set_defaults(func=CmdExperimentsBranch)
+118
View File
@@ -0,0 +1,118 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.commands.metrics import DEFAULT_PRECISION
from dvc.exceptions import DvcException
from dvc.ui import ui
logger = logging.getLogger(__name__)
class CmdExperimentsDiff(CmdBase):
def run(self):
try:
diff = self.repo.experiments.diff(
a_rev=self.args.a_rev,
b_rev=self.args.b_rev,
all=self.args.all,
param_deps=self.args.param_deps,
)
except DvcException:
logger.exception("failed to show experiments diff")
return 1
if self.args.json:
ui.write_json(diff)
else:
from dvc.compare import show_diff
precision = self.args.precision or DEFAULT_PRECISION
diffs = [("metrics", "Metric"), ("params", "Param")]
for idx, (key, title) in enumerate(diffs):
if idx:
# we are printing tables even in `--quiet` mode
# so we should also be printing the "table" separator
ui.write(force=True)
show_diff(
diff[key],
title=title,
markdown=self.args.markdown,
no_path=self.args.no_path,
on_empty_diff="diff not supported",
precision=precision if key == "metrics" else None,
a_rev=self.args.a_rev,
b_rev=self.args.b_rev,
)
return 0
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_DIFF_HELP = (
"Show changes between experiments in the DVC repository."
)
experiments_diff_parser = experiments_subparsers.add_parser(
"diff",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_DIFF_HELP, "exp/diff"),
help=EXPERIMENTS_DIFF_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
experiments_diff_parser.add_argument(
"a_rev", nargs="?", help="Old experiment to compare (defaults to HEAD)"
).complete = completion.EXPERIMENT
experiments_diff_parser.add_argument(
"b_rev",
nargs="?",
help="New experiment to compare (defaults to the current workspace)",
).complete = completion.EXPERIMENT
experiments_diff_parser.add_argument(
"--all",
action="store_true",
default=False,
help="Show unchanged metrics/params as well.",
)
experiments_diff_parser.add_argument(
"--param-deps",
action="store_true",
default=False,
help="Show only params that are stage dependencies.",
)
experiments_diff_parser.add_argument(
"--json",
"--show-json",
action="store_true",
default=False,
help="Show output in JSON format.",
)
experiments_diff_parser.add_argument(
"--md",
"--show-md",
action="store_true",
default=False,
dest="markdown",
help="Show tabulated output in the Markdown format (GFM).",
)
experiments_diff_parser.add_argument(
"--no-path",
action="store_true",
default=False,
help="Don't show metric/param path.",
)
experiments_diff_parser.add_argument(
"--precision",
type=int,
help=(
"Round metrics/params to `n` digits precision after the decimal "
f"point. Rounds to {DEFAULT_PRECISION} digits by default."
),
metavar="<n>",
)
experiments_diff_parser.set_defaults(func=CmdExperimentsDiff)
+42
View File
@@ -0,0 +1,42 @@
import logging
from dvc.cli.command import CmdBaseNoRepo
logger = logging.getLogger(__name__)
class CmdExecutorRun(CmdBaseNoRepo):
"""Run an experiment executor."""
def run(self):
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))
BaseExecutor.reproduce(
info=info,
rev="",
queue=None,
log_level=logger.getEffectiveLevel(),
infofile=self.args.infofile,
)
return 0
def add_parser(experiments_subparsers, parent_parser):
EXEC_RUN_HELP = "Run an experiment executor."
exec_run_parser = experiments_subparsers.add_parser(
"exec-run",
parents=[parent_parser],
description=EXEC_RUN_HELP,
add_help=False,
)
exec_run_parser.add_argument(
"--infofile",
help="Path to executor info file",
default=None,
)
exec_run_parser.set_defaults(func=CmdExecutorRun)
+129
View File
@@ -0,0 +1,129 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import InvalidArgumentError
from dvc.ui import ui
logger = logging.getLogger(__name__)
def _raise_error_if_all_disabled(**kwargs):
if not any(kwargs.values()):
raise InvalidArgumentError(
"Either of `-w|--workspace`, `-a|--all-branches`, `-T|--all-tags` "
"or `--all-commits` needs to be set."
)
class CmdExperimentsGC(CmdBase):
def run(self):
_raise_error_if_all_disabled(
all_branches=self.args.all_branches,
all_tags=self.args.all_tags,
all_commits=self.args.all_commits,
workspace=self.args.workspace,
)
msg = "This will remove all experiments except those derived from "
msg += "the workspace"
if self.args.all_commits:
msg += " and all git commits"
elif self.args.all_branches and self.args.all_tags:
msg += " and all git branches and tags"
elif self.args.all_branches:
msg += " and all git branches"
elif self.args.all_tags:
msg += " and all git tags"
msg += " of the current repo."
if self.args.queued:
msg += " Run queued experiments will be preserved."
else:
msg += " Run queued experiments will be removed."
logger.warning(msg)
msg = "Are you sure you want to proceed?"
if not self.args.force and not ui.confirm(msg):
return 1
removed = self.repo.experiments.gc(
all_branches=self.args.all_branches,
all_tags=self.args.all_tags,
all_commits=self.args.all_commits,
workspace=self.args.workspace,
queued=self.args.queued,
)
if removed:
ui.write(
f"Removed {removed} experiments.",
"To remove unused cache files",
"use 'dvc gc'.",
)
else:
ui.write("No experiments to remove.")
return 0
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."
)
experiments_gc_parser = experiments_subparsers.add_parser(
"gc",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_GC_DESCRIPTION, "exp/gc"),
help=EXPERIMENTS_GC_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
experiments_gc_parser.add_argument(
"-w",
"--workspace",
action="store_true",
default=False,
help="Keep experiments derived from the current workspace.",
)
experiments_gc_parser.add_argument(
"-a",
"--all-branches",
action="store_true",
default=False,
help="Keep experiments derived from the tips of all Git branches.",
)
experiments_gc_parser.add_argument(
"-T",
"--all-tags",
action="store_true",
default=False,
help="Keep experiments derived from all Git tags.",
)
experiments_gc_parser.add_argument(
"-A",
"--all-commits",
action="store_true",
default=False,
help="Keep experiments derived from all Git commits.",
)
experiments_gc_parser.add_argument(
"--queued",
action="store_true",
default=False,
help=(
"Keep queued experiments (experiments run queue will be cleared "
"by default)."
),
)
experiments_gc_parser.add_argument(
"-f",
"--force",
action="store_true",
default=False,
help="Force garbage collection - automatically agree to all prompts.",
)
experiments_gc_parser.set_defaults(func=CmdExperimentsGC)
+202
View File
@@ -0,0 +1,202 @@
import argparse
import logging
from typing import TYPE_CHECKING, List
from funcy import compact
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import InvalidArgumentError
from dvc.ui import ui
if TYPE_CHECKING:
from dvc.dependency import Dependency
from dvc.stage import PipelineStage
logger = logging.getLogger(__name__)
class CmdExperimentsInit(CmdBase):
DEFAULT_NAME = "train"
CODE = "src"
DATA = "data"
MODELS = "models"
DEFAULT_METRICS = "metrics.json"
DEFAULT_PARAMS = "params.yaml"
PLOTS = "plots"
DVCLIVE = "dvclive"
DEFAULTS = {
"code": CODE,
"data": DATA,
"models": MODELS,
"metrics": DEFAULT_METRICS,
"params": DEFAULT_PARAMS,
"plots": PLOTS,
"live": DVCLIVE,
}
def run(self):
from dvc.commands.stage import parse_cmd
cmd = parse_cmd(self.args.command)
if not self.args.interactive and not cmd:
raise InvalidArgumentError("command is not specified")
from dvc.repo.experiments.init import init
defaults = {}
if not self.args.explicit:
config = self.repo.config["exp"]
defaults.update({**self.DEFAULTS, **config})
cli_args = compact(
{
"cmd": cmd,
"code": self.args.code,
"data": self.args.data,
"models": self.args.models,
"metrics": self.args.metrics,
"params": self.args.params,
"plots": self.args.plots,
"live": self.args.live,
}
)
initialized_stage, initialized_deps = init(
self.repo,
name=self.args.name,
type=self.args.type,
defaults=defaults,
overrides=cli_args,
interactive=self.args.interactive,
force=self.args.force,
)
self._post_init_display(initialized_stage, initialized_deps)
if self.args.run:
self.repo.experiments.run(targets=[initialized_stage.addressing])
return 0
def _post_init_display(
self, stage: "PipelineStage", new_deps: List["Dependency"]
) -> None:
from dvc.utils import humanize
path_fmt = "[green]{0}[/green]".format
if new_deps:
deps_paths = humanize.join(map(path_fmt, new_deps))
ui.write(f"Creating dependencies: {deps_paths}", styled=True)
ui.write(
f"Creating [b]{self.args.name}[/b] stage in [green]dvc.yaml[/]",
styled=True,
)
if stage.outs or not self.args.run:
# separate the above status-like messages with help/tips section
ui.write(styled=True)
if stage.outs:
outs_paths = humanize.join(map(path_fmt, stage.outs))
tips = f"Ensure your experiment command creates {outs_paths}."
ui.write(tips, styled=True)
if not self.args.run:
ui.write(
'You can now run your experiment using [b]"dvc exp run"[/].',
styled=True,
)
else:
# separate between `exp.run` output and `dvc exp init` output
ui.write(styled=True)
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",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_INIT_HELP, "exp/init"),
formatter_class=argparse.RawDescriptionHelpFormatter,
help=EXPERIMENTS_INIT_HELP,
)
experiments_init_parser.add_argument(
"command",
nargs=argparse.REMAINDER,
help="Command to execute.",
metavar="command",
)
experiments_init_parser.add_argument(
"--run",
action="store_true",
help="Run the experiment after initializing it",
)
experiments_init_parser.add_argument(
"--interactive",
"-i",
action="store_true",
help="Prompt for values that are not provided",
)
experiments_init_parser.add_argument(
"-f",
"--force",
action="store_true",
default=False,
help="Overwrite existing stage",
)
experiments_init_parser.add_argument(
"--explicit",
action="store_true",
default=False,
help="Only use the path values explicitly provided",
)
experiments_init_parser.add_argument(
"--name",
"-n",
help="Name of the stage to create (default: %(default)s)",
default=CmdExperimentsInit.DEFAULT_NAME,
)
experiments_init_parser.add_argument(
"--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})",
)
experiments_init_parser.add_argument(
"--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})",
)
experiments_init_parser.add_argument(
"--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})",
)
experiments_init_parser.add_argument(
"--live",
help="Path to log dvclive outputs for your experiments"
f" (default: {CmdExperimentsInit.DVCLIVE})",
)
experiments_init_parser.add_argument(
"--type",
choices=["default", "dl"],
default="default",
help="Select type of stage to create (default: %(default)s)",
)
experiments_init_parser.set_defaults(func=CmdExperimentsInit)
+63
View File
@@ -0,0 +1,63 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
logger = logging.getLogger(__name__)
class CmdExperimentsList(CmdBase):
def run(self):
names_only = self.args.names_only
exps = self.repo.experiments.ls(
all_commits=self.args.all_commits,
rev=self.args.rev,
num=self.args.num,
git_remote=self.args.git_remote,
)
for baseline in exps:
tag = self.repo.scm.describe(baseline)
if not tag:
branch = self.repo.scm.describe(baseline, base="refs/heads")
if branch:
tag = branch.split("/")[-1]
name = tag if tag else baseline[:7]
if not names_only:
print(f"{name}:")
for exp_name in exps[baseline]:
indent = "" if names_only else "\t"
print(f"{indent}{exp_name}")
return 0
def add_parser(experiments_subparsers, parent_parser):
from . import add_rev_selection_flags
EXPERIMENTS_LIST_HELP = "List local and remote experiments."
experiments_list_parser = experiments_subparsers.add_parser(
"list",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_LIST_HELP, "exp/list"),
help=EXPERIMENTS_LIST_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
add_rev_selection_flags(experiments_list_parser, "List")
experiments_list_parser.add_argument(
"--names-only",
action="store_true",
help="Only output experiment names (without parent commits).",
)
experiments_list_parser.add_argument(
"git_remote",
nargs="?",
default=None,
help=(
"Optional Git remote name or Git URL. "
"If provided, experiments from the specified Git repository "
" will be listed instead of local ones."
),
metavar="[<git_remote>]",
)
experiments_list_parser.set_defaults(func=CmdExperimentsList)
+117
View File
@@ -0,0 +1,117 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import InvalidArgumentError
from dvc.ui import ui
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]
):
raise InvalidArgumentError(
"Either provide an `experiment` argument, or use the "
"`--rev` or `--all-commits` flag."
)
def run(self):
self.raise_error_if_all_disabled()
pulled_exps = self.repo.experiments.pull(
self.args.git_remote,
self.args.experiment,
all_commits=self.args.all_commits,
rev=self.args.rev,
num=self.args.num,
force=self.args.force,
pull_cache=self.args.pull_cache,
dvc_remote=self.args.dvc_remote,
jobs=self.args.jobs,
run_cache=self.args.run_cache,
)
if pulled_exps:
ui.write(
f"Pulled experiment '{pulled_exps}'",
f"from Git remote '{self.args.git_remote}'.",
)
else:
ui.write("No experiments to pull.")
if not self.args.pull_cache:
ui.write(
"To pull cached outputs for this experiment"
"from DVC remote storage,"
"re-run this command without '--no-cache'."
)
return 0
def add_parser(experiments_subparsers, parent_parser):
from . import add_rev_selection_flags
EXPERIMENTS_PULL_HELP = "Pull an experiment from a Git remote."
experiments_pull_parser = experiments_subparsers.add_parser(
"pull",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_PULL_HELP, "exp/pull"),
help=EXPERIMENTS_PULL_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
add_rev_selection_flags(experiments_pull_parser, "Pull", False)
experiments_pull_parser.add_argument(
"-f",
"--force",
action="store_true",
help="Replace local experiment already exists.",
)
experiments_pull_parser.add_argument(
"--no-cache",
action="store_false",
dest="pull_cache",
help=(
"Do not pull cached outputs for this experiment from DVC remote "
"storage."
),
)
experiments_pull_parser.add_argument(
"-r",
"--remote",
dest="dvc_remote",
metavar="<name>",
help="Name of the DVC remote to use when pulling cached outputs.",
)
experiments_pull_parser.add_argument(
"-j",
"--jobs",
type=int,
metavar="<number>",
help=(
"Number of jobs to run simultaneously when pulling from DVC "
"remote storage."
),
)
experiments_pull_parser.add_argument(
"--run-cache",
action="store_true",
default=False,
help="Pull run history for all stages.",
)
experiments_pull_parser.add_argument(
"git_remote",
help="Git remote name or Git URL.",
metavar="<git_remote>",
)
experiments_pull_parser.add_argument(
"experiment",
nargs="*",
default=None,
help="Experiments to pull.",
metavar="<experiment>",
)
experiments_pull_parser.set_defaults(func=CmdExperimentsPull)
+119
View File
@@ -0,0 +1,119 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.exceptions import InvalidArgumentError
from dvc.ui import ui
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]
):
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(
self.args.git_remote,
self.args.experiment,
all_commits=self.args.all_commits,
rev=self.args.rev,
num=self.args.num,
force=self.args.force,
push_cache=self.args.push_cache,
dvc_remote=self.args.dvc_remote,
jobs=self.args.jobs,
run_cache=self.args.run_cache,
)
if pushed_exps:
ui.write(
f"Pushed experiment '{pushed_exps}'"
f"to Git remote '{self.args.git_remote}'."
)
else:
ui.write("No experiments to push.")
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'.",
)
return 0
def add_parser(experiments_subparsers, parent_parser):
from . import add_rev_selection_flags
EXPERIMENTS_PUSH_HELP = "Push a local experiment to a Git remote."
experiments_push_parser = experiments_subparsers.add_parser(
"push",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_PUSH_HELP, "exp/push"),
help=EXPERIMENTS_PUSH_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
add_rev_selection_flags(experiments_push_parser, "Push", False)
experiments_push_parser.add_argument(
"-f",
"--force",
action="store_true",
help="Replace experiment in the Git remote if it already exists.",
)
experiments_push_parser.add_argument(
"--no-cache",
action="store_false",
dest="push_cache",
help=(
"Do not push cached outputs for this experiment to DVC remote "
"storage."
),
)
experiments_push_parser.add_argument(
"-r",
"--remote",
dest="dvc_remote",
metavar="<name>",
help="Name of the DVC remote to use when pushing cached outputs.",
)
experiments_push_parser.add_argument(
"-j",
"--jobs",
type=int,
metavar="<number>",
help=(
"Number of jobs to run simultaneously when pushing to DVC remote "
"storage."
),
)
experiments_push_parser.add_argument(
"--run-cache",
action="store_true",
default=False,
help="Push run history for all stages.",
)
experiments_push_parser.add_argument(
"git_remote",
help="Git remote name or Git URL.",
metavar="<git_remote>",
)
experiments_push_parser.add_argument(
"experiment",
nargs="*",
default=None,
help="Experiments to push.",
metavar="<experiment>",
).complete = completion.EXPERIMENT
experiments_push_parser.set_defaults(func=CmdExperimentsPush)
+73
View File
@@ -0,0 +1,73 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import InvalidArgumentError
from dvc.ui import ui
logger = logging.getLogger(__name__)
class CmdExperimentsRemove(CmdBase):
def raise_error_if_all_disabled(self):
if not any(
[
self.args.experiment,
self.args.all_commits,
self.args.rev,
self.args.queue,
]
):
raise InvalidArgumentError(
"Either provide an `experiment` argument, or use the "
"`--rev` or `--all-commits` flag."
)
def run(self):
self.raise_error_if_all_disabled()
removed_list = self.repo.experiments.remove(
exp_names=self.args.experiment,
all_commits=self.args.all_commits,
rev=self.args.rev,
num=self.args.num,
queue=self.args.queue,
git_remote=self.args.git_remote,
)
removed = ",".join(removed_list)
ui.write(f"Removed experiments: {removed}")
return 0
def add_parser(experiments_subparsers, parent_parser):
from . import add_rev_selection_flags
EXPERIMENTS_REMOVE_HELP = "Remove experiments."
experiments_remove_parser = experiments_subparsers.add_parser(
"remove",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_REMOVE_HELP, "exp/remove"),
help=EXPERIMENTS_REMOVE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
remove_group = experiments_remove_parser.add_mutually_exclusive_group()
add_rev_selection_flags(experiments_remove_parser, "Remove", False)
remove_group.add_argument(
"--queue", action="store_true", help="Remove all queued experiments."
)
remove_group.add_argument(
"-g",
"--git-remote",
metavar="<git_remote>",
help="Name or URL of the Git remote to remove the experiment from",
)
experiments_remove_parser.add_argument(
"experiment",
nargs="*",
help="Experiments to remove.",
metavar="<experiment>",
)
experiments_remove_parser.set_defaults(func=CmdExperimentsRemove)
+142
View File
@@ -0,0 +1,142 @@
import argparse
import logging
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.commands.repro import CmdRepro
from dvc.commands.repro import add_arguments as add_repro_arguments
from dvc.exceptions import InvalidArgumentError
from dvc.ui import ui
logger = logging.getLogger(__name__)
class CmdExperimentsRun(CmdRepro):
def run(self):
from dvc.compare import show_metrics
if self.args.checkpoint_resume:
if self.args.reset:
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."
)
if self.args.reset:
ui.write("Any existing checkpoints will be reset and re-run.")
results = self.repo.experiments.run(
name=self.args.name,
queue=self.args.queue,
run_all=self.args.run_all,
jobs=self.args.jobs,
params=self.args.set_param,
checkpoint_resume=self.args.checkpoint_resume,
reset=self.args.reset,
tmp_dir=self.args.tmp_dir,
machine=self.args.machine,
**self._common_kwargs,
)
if self.args.metrics and results:
metrics = self.repo.metrics.show(revs=list(results))
metrics.pop("workspace", None)
show_metrics(metrics)
return 0
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_RUN_HELP = "Run or resume an experiment."
experiments_run_parser = experiments_subparsers.add_parser(
"run",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_RUN_HELP, "exp/run"),
help=EXPERIMENTS_RUN_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
_add_run_common(experiments_run_parser)
experiments_run_parser.add_argument(
"-r",
"--rev",
type=str,
dest="checkpoint_resume",
help=(
"Continue the specified checkpoint experiment. Can only be used "
"in conjunction with --queue or --temp."
),
metavar="<experiment_rev>",
).complete = completion.EXPERIMENT
experiments_run_parser.add_argument(
"--reset",
action="store_true",
help="Reset existing checkpoints and restart the experiment.",
)
experiments_run_parser.set_defaults(func=CmdExperimentsRun)
def _add_run_common(parser):
"""Add common args for 'exp run'."""
# inherit arguments from `dvc repro`
add_repro_arguments(parser)
parser.add_argument(
"-n",
"--name",
default=None,
help=(
"Human-readable experiment name. If not specified, a name will "
"be auto-generated."
),
metavar="<name>",
)
parser.add_argument(
"-S",
"--set-param",
action="append",
default=[],
help="Use the specified param value when reproducing pipelines.",
metavar="[<filename>:]<param_name>=<param_value>",
)
parser.add_argument(
"--queue",
action="store_true",
default=False,
help="Stage this experiment in the run queue for future execution.",
)
parser.add_argument(
"--run-all",
action="store_true",
default=False,
help="Execute all experiments in the run queue. Implies --temp.",
)
parser.add_argument(
"-j",
"--jobs",
type=int,
default=1,
help="Run the specified number of experiments at a time in parallel.",
metavar="<number>",
)
parser.add_argument(
"--temp",
action="store_true",
dest="tmp_dir",
help=(
"Run this experiment in a separate temporary directory instead of "
"your workspace."
),
)
parser.add_argument(
"--machine",
default=None,
help=argparse.SUPPRESS,
# help=(
# "Run this experiment on the specified 'dvc machine' instance."
# )
# metavar="<name>",
)
+647
View File
@@ -0,0 +1,647 @@
import argparse
import logging
import os
import re
from collections import Counter, OrderedDict, defaultdict
from datetime import date, datetime
from typing import TYPE_CHECKING
from funcy import lmap
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.commands.metrics import DEFAULT_PRECISION
from dvc.exceptions import DvcException, InvalidArgumentError
from dvc.ui import ui
from dvc.utils.flatten import flatten
from dvc.utils.serialize import encode_exception
if TYPE_CHECKING:
from dvc.compare import TabularData
from dvc.ui import RichText
FILL_VALUE = "-"
FILL_VALUE_ERRORED = "!"
logger = logging.getLogger(__name__)
def _update_names(names, items):
for name, item in items:
item = item.get("data", {})
if isinstance(item, dict):
item = flatten(item)
names[name].update({key: None for key in item})
def _collect_names(all_experiments, **kwargs):
metric_names = defaultdict(dict)
param_names = defaultdict(dict)
deps_names = set()
for _, experiments in all_experiments.items():
for exp_data in experiments.values():
exp = exp_data.get("data", {})
_update_names(metric_names, exp.get("metrics", {}).items())
_update_names(param_names, exp.get("params", {}).items())
for dep_name in exp.get("deps", {}):
deps_names.add(dep_name)
return metric_names, param_names, sorted(deps_names)
experiment_types = {
"checkpoint_tip": "│ ╓",
"checkpoint_commit": "│ ╟",
"checkpoint_base": "├─╨",
"branch_commit": "├──",
"branch_base": "└──",
"baseline": "",
}
def _collect_rows(
base_rev,
experiments,
all_headers,
metric_headers,
param_headers,
metric_names,
param_names,
deps_names,
precision=DEFAULT_PRECISION,
sort_by=None,
sort_order=None,
fill_value=FILL_VALUE,
iso=False,
):
from scmrepo.git 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
)
new_checkpoint = True
for i, (rev, results) in enumerate(experiments.items()):
fill_value = FILL_VALUE_ERRORED if results.get("error") else fill_value
row_dict = {k: fill_value for k in all_headers}
exp = results.get("data", {})
if exp.get("running"):
state = "Running"
elif exp.get("queued"):
state = "Queued"
else:
state = fill_value
is_baseline = rev == "baseline"
if is_baseline:
name_rev = base_rev[:7] if Git.is_sha(base_rev) else base_rev
else:
name_rev = rev[:7]
tip = exp.get("checkpoint_tip")
parent_rev = exp.get("checkpoint_parent", "")
parent_exp = experiments.get(parent_rev, {}).get("data", {})
parent_tip = parent_exp.get("checkpoint_tip")
parent = ""
if is_baseline:
typ = "baseline"
elif tip:
if tip == parent_tip:
typ = (
"checkpoint_tip" if new_checkpoint else "checkpoint_commit"
)
elif parent_rev == base_rev:
typ = "checkpoint_base"
else:
typ = "checkpoint_commit"
parent = parent_rev[:7]
elif i < len(experiments) - 1:
typ = "branch_commit"
else:
typ = "branch_base"
if not is_baseline:
new_checkpoint = not (tip and tip == parent_tip)
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["parent"] = parent
row_dict["State"] = state
row_dict["Executor"] = exp.get("executor", fill_value)
_extend_row(
row_dict,
metric_names,
metric_headers,
exp.get("metrics", {}).items(),
precision,
fill_value=fill_value,
)
_extend_row(
row_dict,
param_names,
param_headers,
exp.get("params", {}).items(),
precision,
fill_value=fill_value,
)
for dep in deps_names:
hash_info = exp.get("deps", {}).get(dep, {}).get("hash")
if hash_info is not None:
hash_info = hash_info[:7]
row_dict[dep] = hash_info
yield list(row_dict.values())
def _sort_column(sort_by, metric_names, param_names):
path, _, sort_name = sort_by.rpartition(":")
matches = set()
if path:
if path in metric_names and sort_name in metric_names[path]:
matches.add((path, sort_name, "metrics"))
if path in param_names and sort_name in param_names[path]:
matches.add((path, sort_name, "params"))
else:
for path in metric_names:
if sort_name in metric_names[path]:
matches.add((path, sort_name, "metrics"))
for path in param_names:
if sort_name in param_names[path]:
matches.add((path, sort_name, "params"))
if len(matches) == 1:
return matches.pop()
if len(matches) > 1:
raise InvalidArgumentError(
"Ambiguous sort column '{}' matched '{}'".format(
sort_by,
", ".join([f"{path}:{name}" for path, name, _ in matches]),
)
)
raise InvalidArgumentError(f"Unknown sort column '{sort_by}'")
def _sort_exp(experiments, sort_path, sort_name, typ, reverse):
def _sort(item):
rev, exp = item
exp_data = exp.get("data", {})
tip = exp_data.get("checkpoint_tip")
if tip and tip != rev:
# Sort checkpoint experiments by tip commit
return _sort((tip, experiments[tip]))
data = exp_data.get(typ, {}).get(sort_path, {}).get("data", {})
val = flatten(data).get(sort_name)
return val is None, val
ret = OrderedDict()
if "baseline" in experiments:
ret["baseline"] = experiments.pop("baseline")
ret.update(sorted(experiments.items(), key=_sort, reverse=reverse))
return ret
def _format_time(datetime_obj, fill_value=FILL_VALUE, iso=False):
if datetime_obj is None:
return fill_value
if iso:
return datetime_obj.isoformat()
if datetime_obj.date() == date.today():
fmt = "%I:%M %p"
else:
fmt = "%b %d, %Y"
return datetime_obj.strftime(fmt)
def _extend_row(row, names, headers, items, precision, fill_value=FILL_VALUE):
from dvc.compare import _format_field, with_value
for fname, data in items:
item = data.get("data", {})
item = flatten(item) if isinstance(item, dict) else {fname: item}
for name in names[fname]:
value = with_value(
item.get(name),
FILL_VALUE_ERRORED if data.get("error", None) else fill_value,
)
# wrap field data in ui.rich_text, otherwise rich may
# interpret unescaped braces from list/dict types as rich
# markup tags
value = ui.rich_text(str(_format_field(value, precision)))
if name in headers:
row[name] = value
else:
row[f"{fname}:{name}"] = value
def experiments_table(
all_experiments,
headers,
metric_headers,
metric_names,
param_headers,
param_names,
deps_names,
sort_by=None,
sort_order=None,
precision=DEFAULT_PRECISION,
fill_value=FILL_VALUE,
iso=False,
) -> "TabularData":
from funcy import lconcat
from dvc.compare import TabularData
all_headers = lconcat(headers, metric_headers, param_headers, deps_names)
td = TabularData(all_headers, fill_value=fill_value)
for base_rev, experiments in all_experiments.items():
rows = _collect_rows(
base_rev,
experiments,
all_headers,
metric_headers,
param_headers,
metric_names,
param_names,
deps_names,
sort_by=sort_by,
sort_order=sort_order,
precision=precision,
fill_value=fill_value,
iso=iso,
)
td.extend(rows)
return td
def prepare_exp_id(kwargs) -> "RichText":
exp_name = kwargs["Experiment"]
rev = kwargs["rev"]
typ = kwargs.get("typ", "baseline")
if typ == "baseline" or not exp_name:
text = ui.rich_text(exp_name or rev)
else:
text = ui.rich_text.assemble(rev, " [", (exp_name, "bold"), "]")
parent = kwargs.get("parent")
suff = f" ({parent})" if parent else ""
text.append(suff)
tree = experiment_types[typ]
pref = f"{tree} " if tree else ""
return ui.rich_text(pref) + text
def baseline_styler(typ):
return {"style": "bold"} if typ == "baseline" else {}
def show_experiments(
all_experiments,
keep=None,
drop=None,
pager=True,
csv=False,
markdown=False,
pcp=False,
**kwargs,
):
from funcy.seqs import flatten as flatten_list
metric_names, param_names, deps_names = _collect_names(all_experiments)
headers = [
"Experiment",
"rev",
"typ",
"Created",
"parent",
"State",
"Executor",
]
names = {**metric_names, **param_names}
counter = Counter(flatten_list([list(a.keys()) for a in names.values()]))
counter.update(headers)
metric_headers = _normalize_headers(metric_names, counter)
param_headers = _normalize_headers(param_names, counter)
td = experiments_table(
all_experiments,
headers,
metric_headers,
metric_names,
param_headers,
param_names,
deps_names,
kwargs.get("sort_by"),
kwargs.get("sort_order"),
kwargs.get("precision"),
kwargs.get("fill_value"),
kwargs.get("iso"),
)
if keep:
for col in td.keys():
if re.match(keep, col):
td.protect(col)
for col in ("State", "Executor"):
if td.is_empty(col):
td.drop(col)
row_styles = lmap(baseline_styler, td.column("typ"))
if not csv:
merge_headers = ["Experiment", "rev", "typ", "parent"]
td.column("Experiment")[:] = map(
prepare_exp_id, td.as_dict(merge_headers)
)
td.drop(*merge_headers[1:])
headers = {
"metrics": metric_headers,
"params": param_headers,
"deps": deps_names,
}
styles = {
"Experiment": {"no_wrap": True, "header_style": "black on grey93"},
"Created": {"header_style": "black on grey93"},
"State": {"header_style": "black on grey93"},
"Executor": {"header_style": "black on grey93"},
}
header_bg_colors = {
"metrics": "cornsilk1",
"params": "light_cyan1",
"deps": "plum2",
}
styles.update(
{
header: {
"justify": "right" if typ == "metrics" else "left",
"header_style": f"black on {header_bg_colors[typ]}",
"collapse": idx != 0,
"no_wrap": typ == "metrics",
}
for typ, hs in headers.items()
for idx, header in enumerate(hs)
}
)
if kwargs.get("only_changed", False) or pcp:
td.drop_duplicates("cols", ignore_empty=False)
cols_to_drop = set()
if drop is not None:
cols_to_drop = {col for col in td.keys() if re.match(drop, col)}
if pcp:
cols_to_drop.add("Created")
td.drop(*cols_to_drop)
if pcp:
subset = {x for x in td.keys() if x != "Experiment"}
td.dropna(
"rows",
how="all",
subset=subset,
)
td.drop_duplicates("rows", subset=subset)
td.column("Experiment")[:] = [
# remove tree characters
str(x).encode("ascii", "ignore").strip().decode()
for x in td.column("Experiment")
]
out = kwargs.get("out") or "dvc_plots"
output_file = os.path.join(out, "index.html")
ui.write(
td.to_parallel_coordinates(
output_path=os.path.abspath(output_file),
color_by=kwargs.get("sort_by") or "Experiment",
)
)
if kwargs.get("open"):
return ui.open_browser(output_file)
else:
td.render(
pager=pager,
borders="horizontals",
rich_table=True,
header_styles=styles,
row_styles=row_styles,
csv=csv,
markdown=markdown,
)
def _normalize_headers(names, count):
return [
name if count[name] == 1 else f"{path}:{name}"
for path in names
for name in names[path]
]
def _format_json(item):
if isinstance(item, (date, datetime)):
return item.isoformat()
return encode_exception(item)
class CmdExperimentsShow(CmdBase):
def run(self):
try:
all_experiments = self.repo.experiments.show(
all_branches=self.args.all_branches,
all_tags=self.args.all_tags,
all_commits=self.args.all_commits,
revs=self.args.rev,
num=self.args.num,
sha_only=self.args.sha,
param_deps=self.args.param_deps,
)
except DvcException:
logger.exception("failed to show experiments")
return 1
if self.args.json:
ui.write_json(all_experiments, default=_format_json)
else:
precision = (
self.args.precision or None
if self.args.csv
else DEFAULT_PRECISION
)
fill_value = "" if self.args.csv else FILL_VALUE
iso = True if self.args.csv else False
show_experiments(
all_experiments,
keep=self.args.keep,
drop=self.args.drop,
sort_by=self.args.sort_by,
sort_order=self.args.sort_order,
precision=precision,
fill_value=fill_value,
iso=iso,
pager=not self.args.no_pager,
csv=self.args.csv,
markdown=self.args.markdown,
only_changed=self.args.only_changed,
pcp=self.args.pcp,
out=self.args.out,
open=self.args.open,
)
return 0
def add_parser(experiments_subparsers, parent_parser):
from . import add_rev_selection_flags
EXPERIMENTS_SHOW_HELP = "Print experiments."
experiments_show_parser = experiments_subparsers.add_parser(
"show",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_SHOW_HELP, "exp/show"),
help=EXPERIMENTS_SHOW_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
add_rev_selection_flags(experiments_show_parser, "Show")
experiments_show_parser.add_argument(
"-a",
"--all-branches",
action="store_true",
default=False,
help="Show experiments derived from the tip of all Git branches.",
)
experiments_show_parser.add_argument(
"-T",
"--all-tags",
action="store_true",
default=False,
help="Show experiments derived from all Git tags.",
)
experiments_show_parser.add_argument(
"--no-pager",
action="store_true",
default=False,
help="Do not pipe output into a pager.",
)
experiments_show_parser.add_argument(
"--only-changed",
action="store_true",
default=False,
help=(
"Only show metrics/params with values varying "
"across the selected experiments."
),
)
experiments_show_parser.add_argument(
"--drop",
help="Remove the columns matching the specified regex pattern.",
metavar="<regex_pattern>",
)
experiments_show_parser.add_argument(
"--keep",
help="Preserve the columns matching the specified regex pattern.",
metavar="<regex_pattern>",
)
experiments_show_parser.add_argument(
"--param-deps",
action="store_true",
default=False,
help="Show only params that are stage dependencies.",
)
experiments_show_parser.add_argument(
"--sort-by",
help="Sort related experiments by the specified metric or param.",
metavar="<metric/param>",
)
experiments_show_parser.add_argument(
"--sort-order",
help=(
"Sort order to use with --sort-by."
" Defaults to ascending ('asc')."
),
choices=("asc", "desc"),
default="asc",
)
experiments_show_parser.add_argument(
"--sha",
action="store_true",
default=False,
help="Always show git commit SHAs instead of branch/tag names.",
)
experiments_show_parser.add_argument(
"--json",
"--show-json",
action="store_true",
default=False,
help="Print output in JSON format instead of a human-readable table.",
)
experiments_show_parser.add_argument(
"--csv",
"--show-csv",
action="store_true",
default=False,
help="Print output in csv format instead of a human-readable table.",
)
experiments_show_parser.add_argument(
"--md",
"--show-md",
action="store_true",
default=False,
dest="markdown",
help="Show tabulated output in the Markdown format (GFM).",
)
experiments_show_parser.add_argument(
"--precision",
type=int,
help=(
"Round metrics/params to `n` digits precision after the decimal "
f"point. Rounds to {DEFAULT_PRECISION} digits by default."
),
metavar="<n>",
)
experiments_show_parser.add_argument(
"--parallel-coordinates-plot",
"--pcp",
dest="pcp",
action="store_true",
default=False,
help="Generate a Parallel Coordinates Plot from the tabulated output.",
)
experiments_show_parser.add_argument(
"-o",
"--out",
default=None,
help="Destination folder to save the Parallel Coordinates Plot to",
metavar="<path>",
).complete = completion.DIR
experiments_show_parser.add_argument(
"--open",
action="store_true",
default=False,
help="Open the Parallel Coordinates Plot directly in the browser.",
)
experiments_show_parser.set_defaults(func=CmdExperimentsShow)
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
+2 -2
View File
@@ -2,7 +2,8 @@ import argparse
import logging
import os
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.ui import ui
logger = logging.getLogger(__name__)
@@ -136,7 +137,6 @@ def add_parser(subparsers, parent_parser):
help=(
"Number of jobs to run simultaneously. "
"The default value is 4 * cpu_count(). "
"For SSH remotes, the default is 4. "
),
metavar="<number>",
)
+2 -2
View File
@@ -1,10 +1,11 @@
import argparse
import logging
from dvc.cli.command import CmdBaseNoRepo
from dvc.exceptions import DvcException
from ..cli.utils import append_doc_link
from . import completion
from .base import CmdBaseNoRepo, append_doc_link
logger = logging.getLogger(__name__)
@@ -93,7 +94,6 @@ def add_parser(subparsers, parent_parser):
help=(
"Number of jobs to run simultaneously. "
"The default value is 4 * cpu_count(). "
"For SSH remotes, the default is 4. "
),
metavar="<number>",
)
@@ -1,10 +1,11 @@
import argparse
import logging
from dvc.cli.command import CmdBaseNoRepo
from dvc.exceptions import DvcException
from ..cli.utils import append_doc_link
from . import completion
from .base import CmdBaseNoRepo, append_doc_link
logger = logging.getLogger(__name__)
@@ -43,7 +44,6 @@ def add_parser(subparsers, parent_parser):
help=(
"Number of jobs to run simultaneously. "
"The default value is 4 * cpu_count(). "
"For SSH remotes, the default is 4. "
),
metavar="<number>",
)
@@ -1,7 +1,8 @@
import logging
import os
from dvc.command.base import CmdBaseNoRepo, fix_subparsers
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import fix_subparsers
from dvc.exceptions import NotDvcRepoError
logger = logging.getLogger(__name__)
@@ -25,7 +26,7 @@ class CmdHookBase(CmdBaseNoRepo):
class CmdPreCommit(CmdHookBase):
def _run(self):
from dvc.main import main
from dvc.cli import main
return main(["status"])
@@ -49,14 +50,14 @@ class CmdPostCheckout(CmdHookBase):
if os.path.isdir(os.path.join(".git", "rebase-merge")):
return 0
from dvc.main import main
from dvc.cli import main
return main(["checkout"])
class CmdPrePush(CmdHookBase):
def _run(self):
from dvc.main import main
from dvc.cli import main
return main(["push"])
+3 -3
View File
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
@@ -90,7 +91,6 @@ def add_parser(subparsers, parent_parser):
help=(
"Number of jobs to run simultaneously. "
"The default value is 4 * cpu_count(). "
"For SSH remotes, the default is 4. "
),
metavar="<number>",
)
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
@@ -90,7 +91,6 @@ def add_parser(subparsers, parent_parser):
help=(
"Number of jobs to run simultaneously. "
"The default value is 4 * cpu_count(). "
"For SSH remotes, the default is 4. "
),
metavar="<number>",
)
+2 -1
View File
@@ -4,7 +4,8 @@ import logging
import colorama
from dvc import analytics
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.utils import boxify
from dvc.utils import format_link as fmt_link
@@ -1,7 +1,8 @@
import argparse
import logging
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
+14 -6
View File
@@ -1,8 +1,8 @@
import argparse
from pathlib import Path
from dvc.command import completion
from dvc.command.base import CmdBase, fix_subparsers
from dvc.cli.command import CmdBase
from dvc.cli.utils import fix_subparsers
from dvc.commands import completion
from dvc.ui import ui
@@ -10,13 +10,21 @@ class CmdLive(CmdBase):
UNINITIALIZED = True
def _run(self, target, revs=None):
from dvc_render import render_html
from dvc.render.match import match_renderers
metrics, plots = self.repo.live.show(target=target, revs=revs)
if plots:
html_path = Path.cwd() / (self.args.target + "_html")
from dvc.render.utils import render
from pathlib import Path
index_path = render(self.repo, plots, metrics, html_path)
output = Path.cwd() / (self.args.target + "_html") / "index.html"
renderers = match_renderers(
plots, templates_dir=self.repo.plots.templates_dir
)
index_path = render_html(renderers, output, metrics)
ui.write(index_path.as_uri())
return 0
return 1
@@ -1,9 +1,10 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.command.ls.ls_colors import LsColors
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.commands.ls.ls_colors import LsColors
from dvc.exceptions import DvcException
from dvc.ui import ui
@@ -1,7 +1,8 @@
import argparse
from dvc.command.base import CmdBase, append_doc_link, fix_subparsers
from dvc.command.config import CmdConfig
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.commands.config import CmdConfig
from dvc.compare import TabularData
from dvc.config import ConfigError
from dvc.exceptions import DvcException
@@ -241,6 +242,7 @@ class CmdMachineStatus(CmdBase):
"instance_hdd_size",
"instance_gpu",
]
FILL_VALUE = "-"
def _add_row(
self,
@@ -250,7 +252,11 @@ class CmdMachineStatus(CmdBase):
):
if not all_status:
row = [name, None, "offline"]
row = [
name,
self.FILL_VALUE,
"offline",
] # back to `None` after #7167
td.append(row)
for i, status in enumerate(all_status, start=1):
row = [name, f"num_{i}", "running" if status else "offline"]
@@ -264,7 +270,7 @@ class CmdMachineStatus(CmdBase):
raise MachineDisabledError
td = TabularData(
self.INSTANCE_FIELD + self.SHOWN_FIELD, fill_value="-"
self.INSTANCE_FIELD + self.SHOWN_FIELD, fill_value=self.FILL_VALUE
)
if self.args.name:
@@ -304,7 +310,7 @@ class CmdMachineSsh(CmdBase):
def add_parser(subparsers, parent_parser):
from dvc.command.config import parent_config_parser
from dvc.commands.config import parent_config_parser
machine_HELP = "Set up and manage cloud machines."
machine_parser = subparsers.add_parser(
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link, fix_subparsers
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.commands import completion
from dvc.exceptions import DvcException
from dvc.ui import ui
from dvc.utils.serialize import encode_exception
+3 -2
View File
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link, fix_subparsers
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.commands import completion
from dvc.exceptions import DvcException
from dvc.ui import ui
+140 -24
View File
@@ -1,16 +1,30 @@
import argparse
import json
import logging
import os
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link, fix_subparsers
from funcy import first
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.commands import completion
from dvc.exceptions import DvcException
from dvc.render.utils import find_vega, render
from dvc.ui import ui
from dvc.utils import format_link
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
}
if result:
ui.write_json(result)
class CmdPlots(CmdBase):
def _func(self, *args, **kwargs):
raise NotImplementedError
@@ -25,6 +39,10 @@ class CmdPlots(CmdBase):
def run(self):
from pathlib import Path
from dvc_render import render_html
from dvc.render.match import match_renderers
if self.args.show_vega:
if not self.args.targets:
logger.error("please specify a target for `--show-vega`")
@@ -34,8 +52,15 @@ class CmdPlots(CmdBase):
"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."
)
return 1
try:
plots_data = self._func(
targets=self.args.targets, props=self._props()
)
@@ -46,26 +71,50 @@ class CmdPlots(CmdBase):
"visualization file will not be created."
)
if self.args.show_vega:
target = self.args.targets[0]
plot_json = find_vega(self.repo, plots_data, target)
if plot_json:
ui.write(plot_json)
return 0
rel: str = self.args.out or "dvc_plots"
path: Path = (Path.cwd() / rel).resolve()
index_path = render(
self.repo,
plots_data,
path=path,
html_template_path=self.args.html_template,
renderers = match_renderers(
plots_data=plots_data, out=self.args.out
)
ui.write(index_path.as_uri())
if self.args.show_vega:
renderer = first(filter(lambda r: r.TYPE == "vega", renderers))
if renderer:
ui.write_json(json.loads(renderer.get_filled_template()))
return 0
if self.args.json:
_show_json(renderers, self.args.split)
return 0
if self.args.open:
return ui.open_browser(index_path)
html_template_path = self.args.html_template
if not html_template_path:
html_template_path = self.repo.config.get("plots", {}).get(
"html_template", None
)
if html_template_path and not os.path.isabs(
html_template_path
):
html_template_path = os.path.join(
self.repo.dvc_dir, html_template_path
)
rel: str = self.args.out or "dvc_plots"
output_file: Path = (Path.cwd() / rel).resolve() / "index.html"
render_html(
renderers=renderers,
output_file=output_file,
template_path=html_template_path,
)
ui.write(output_file.as_uri())
auto_open = self.repo.config["plots"].get("auto_open", False)
if self.args.open or auto_open:
if not auto_open:
ui.write(
"To enable auto opening, you can run:\n"
"\n"
"\tdvc config plots.auto_open true"
)
return ui.open_browser(output_file)
return 0
@@ -101,6 +150,37 @@ class CmdPlotsModify(CmdPlots):
return 0
class CmdPlotsTemplates(CmdBase):
TEMPLATES_CHOICES = [
"simple",
"linear",
"confusion",
"confusion_normalized",
"scatter",
"smooth",
]
def run(self):
from dvc_render.vega_templates import dump_templates
try:
out = (
os.path.join(os.getcwd(), self.args.out)
if self.args.out
else self.repo.plots.templates_dir
)
targets = [self.args.target] if self.args.target else None
dump_templates(output=out, targets=targets)
templates_path = os.path.relpath(out, os.getcwd())
ui.write(f"Templates have been written into '{templates_path}'.")
return 0
except DvcException:
logger.exception("")
return 1
def add_parser(subparsers, parent_parser):
PLOTS_HELP = (
"Commands to visualize and compare plot metrics in structured files "
@@ -137,7 +217,8 @@ def add_parser(subparsers, parent_parser):
"Shows all plots by default.",
).complete = completion.FILE
_add_props_arguments(plots_show_parser)
_add_output_arguments(plots_show_parser)
_add_output_argument(plots_show_parser)
_add_ui_arguments(plots_show_parser)
plots_show_parser.set_defaults(func=CmdPlotsShow)
PLOTS_DIFF_HELP = (
@@ -172,7 +253,8 @@ def add_parser(subparsers, parent_parser):
"revisions", nargs="*", default=None, help="Git commits to plot from"
)
_add_props_arguments(plots_diff_parser)
_add_output_arguments(plots_diff_parser)
_add_output_argument(plots_diff_parser)
_add_ui_arguments(plots_diff_parser)
plots_diff_parser.set_defaults(func=CmdPlotsDiff)
PLOTS_MODIFY_HELP = (
@@ -198,6 +280,27 @@ def add_parser(subparsers, parent_parser):
)
plots_modify_parser.set_defaults(func=CmdPlotsModify)
TEMPLATES_HELP = (
"Write built-in plots templates to a directory "
"(.dvc/plots by default)."
)
plots_templates_parser = plots_subparsers.add_parser(
"templates",
parents=[parent_parser],
description=append_doc_link(TEMPLATES_HELP, "plots/templates"),
help=TEMPLATES_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
plots_templates_parser.add_argument(
"target",
default=None,
nargs="?",
choices=CmdPlotsTemplates.TEMPLATES_CHOICES,
help="Template to write. Writes all templates by default.",
)
_add_output_argument(plots_templates_parser, typ="templates")
plots_templates_parser.set_defaults(func=CmdPlotsTemplates)
def _add_props_arguments(parser):
parser.add_argument(
@@ -237,20 +340,33 @@ def _add_props_arguments(parser):
)
def _add_output_arguments(parser):
def _add_output_argument(parser, typ="plots"):
parser.add_argument(
"-o",
"--out",
default=None,
help="Destination path to save plots to",
help=f"Directory to save {typ} to.",
metavar="<path>",
).complete = completion.DIR
def _add_ui_arguments(parser):
parser.add_argument(
"--show-vega",
action="store_true",
default=False,
help="Show output in Vega format.",
)
parser.add_argument(
"--json",
"--show-json",
action="store_true",
default=False,
help=argparse.SUPPRESS,
)
parser.add_argument(
"--split", action="store_true", default=False, help=argparse.SUPPRESS
)
parser.add_argument(
"--open",
action="store_true",
@@ -1,7 +1,7 @@
import argparse
from dvc.command.base import append_doc_link, fix_subparsers
from dvc.command.config import CmdConfig
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.commands.config import CmdConfig
from dvc.ui import ui
from dvc.utils import format_link
@@ -151,7 +151,7 @@ class CmdRemoteRename(CmdRemote):
def add_parser(subparsers, parent_parser):
from dvc.command.config import parent_config_parser
from dvc.commands.config import parent_config_parser
REMOTE_HELP = "Set up and manage data remotes."
remote_parser = subparsers.add_parser(
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
+38 -28
View File
@@ -1,15 +1,18 @@
import argparse
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.command.status import CmdDataStatus
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.commands.status import CmdDataStatus
class CmdRepro(CmdBase):
def run(self):
from dvc.ui import ui
stages = self.repo.reproduce(**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:
@@ -26,7 +29,7 @@ class CmdRepro(CmdBase):
return 0
@property
def _repro_kwargs(self):
def _common_kwargs(self):
return {
"targets": self.args.targets,
"single_item": self.args.single_item,
@@ -35,12 +38,17 @@ class CmdRepro(CmdBase):
"interactive": self.args.interactive,
"pipeline": self.args.pipeline,
"all_pipelines": self.args.all_pipelines,
"run_cache": not self.args.no_run_cache,
"no_commit": self.args.no_commit,
"downstream": self.args.downstream,
"recursive": self.args.recursive,
"force_downstream": self.args.force_downstream,
"pull": self.args.pull,
}
@property
def _repro_kwargs(self):
return {
"run_cache": not self.args.no_run_cache,
"no_commit": self.args.no_commit,
"glob": self.args.glob,
}
@@ -109,15 +117,6 @@ def add_arguments(repro_parser):
default=False,
help="Reproduce all stages in the specified directory.",
)
repro_parser.add_argument(
"--no-run-cache",
action="store_true",
default=False,
help=(
"Execute stage commands even if they have already been run with "
"the same command/dependencies/outputs/etc before."
),
)
repro_parser.add_argument(
"--force-downstream",
action="store_true",
@@ -125,12 +124,6 @@ def add_arguments(repro_parser):
help="Reproduce all descendants of a changed stage even if their "
"direct dependencies didn't change.",
)
repro_parser.add_argument(
"--no-commit",
action="store_true",
default=False,
help="Don't put files/directories into cache.",
)
repro_parser.add_argument(
"--downstream",
action="store_true",
@@ -146,12 +139,6 @@ def add_arguments(repro_parser):
"from the run-cache."
),
)
repro_parser.add_argument(
"--glob",
action="store_true",
default=False,
help="Allows targets containing shell-style wildcards.",
)
def add_parser(subparsers, parent_parser):
@@ -165,5 +152,28 @@ def add_parser(subparsers, parent_parser):
help=REPRO_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
# repro/exp run shared args
add_arguments(repro_parser)
# repro only args
repro_parser.add_argument(
"--no-run-cache",
action="store_true",
default=False,
help=(
"Execute stage commands even if they have already been run with "
"the same command/dependencies/outputs/etc before."
),
)
repro_parser.add_argument(
"--no-commit",
action="store_true",
default=False,
help="Don't put files/directories into cache.",
)
repro_parser.add_argument(
"--glob",
action="store_true",
default=False,
help="Allows targets containing shell-style wildcards.",
)
repro_parser.set_defaults(func=CmdRepro)
+2 -1
View File
@@ -1,7 +1,8 @@
import argparse
import logging
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.utils import relpath
logger = logging.getLogger(__name__)
+4 -3
View File
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command.base import CmdBase, append_doc_link
from dvc.command.stage import parse_cmd
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands.stage import parse_cmd
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
@@ -52,7 +53,7 @@ class CmdRun(CmdBase):
def add_parser(subparsers, parent_parser):
from dvc.command.stage import _add_common_args
from dvc.commands.stage import _add_common_args
RUN_HELP = (
"Generate a dvc.yaml file from a command and execute the command."
@@ -3,8 +3,9 @@ import logging
from itertools import chain, filterfalse
from typing import TYPE_CHECKING, Dict, Iterable, List
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link, fix_subparsers
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.commands import completion
from dvc.utils.cli_parse import parse_params
from dvc.utils.humanize import truncate_text
@@ -213,7 +214,7 @@ def _add_common_args(parser):
"--live-no-summary",
action="store_true",
default=False,
help="Signal dvclive logger to not dump latest metrics file.",
help=argparse.SUPPRESS,
)
parser.add_argument(
"--live-no-html",
@@ -1,6 +1,6 @@
import logging
from dvc.command.data_sync import CmdDataBase
from dvc.commands.data_sync import CmdDataBase
from dvc.exceptions import DvcException
from dvc.ui import ui
from dvc.utils import format_link
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
@@ -70,7 +71,6 @@ def add_parser(subparsers, parent_parser):
help=(
"Number of jobs to run simultaneously. "
"The default value is 4 * cpu_count(). "
"For SSH remotes, the default is 4. "
),
metavar="<number>",
)
@@ -1,7 +1,8 @@
import argparse
import logging
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.ui import ui
logger = logging.getLogger(__name__)
+63 -9
View File
@@ -11,6 +11,7 @@ from typing import (
List,
Mapping,
MutableSequence,
Optional,
Sequence,
Set,
Tuple,
@@ -21,6 +22,7 @@ from typing import (
from funcy import reraise
if TYPE_CHECKING:
from dvc.types import StrPath
from dvc.ui.table import CellT
@@ -37,11 +39,21 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
self._columns: Dict[str, Column] = {name: Column() for name in columns}
self._keys: List[str] = list(columns)
self._fill_value = fill_value
self._protected: Set[str] = set()
@property
def columns(self) -> List[Column]:
return list(map(self.column, self.keys()))
def is_protected(self, col_name) -> bool:
return col_name in self._protected
def protect(self, *col_names: str):
self._protected.update(col_names)
def unprotect(self, *col_names: str):
self._protected = self._protected.difference(col_names)
def column(self, name: str) -> Column:
return self._columns[name]
@@ -125,9 +137,10 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
return len(self.columns), len(self)
def drop(self, *col_names: str) -> None:
for col_name in col_names:
self._keys.remove(col_name)
self._columns.pop(col_name)
for col in col_names:
if not self.is_protected(col):
self._keys.remove(col)
self._columns.pop(col)
def rename(self, from_col_name: str, to_col_name: str) -> None:
self._columns[to_col_name] = self._columns.pop(from_col_name)
@@ -153,6 +166,24 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
writer.writerow(row)
return buff.getvalue()
def to_parallel_coordinates(
self, output_path: "StrPath", color_by: str = None
) -> "StrPath":
from dvc_render.html import render_html
from dvc_render.plotly import ParallelCoordinatesRenderer
render_html(
renderers=[
ParallelCoordinatesRenderer(
self.as_dict(),
color_by=color_by,
fill_value=self._fill_value,
)
],
output_file=output_path,
)
return output_path
def add_column(self, name: str) -> None:
self._columns[name] = Column([self._fill_value] * len(self))
self._keys.append(name)
@@ -184,7 +215,12 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
{k: self._columns[k][i] for k in keys} for i in range(len(self))
]
def dropna(self, axis: str = "rows", how="any"):
def dropna(
self,
axis: str = "rows",
how="any",
subset: Optional[Iterable[str]] = None,
):
if axis not in ["rows", "cols"]:
raise ValueError(
f"Invalid 'axis' value {axis}."
@@ -196,13 +232,15 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
)
match_line: Set = set()
match = True
match_any = True
if how == "all":
match = False
match_any = False
for n_row, row in enumerate(self):
for n_col, col in enumerate(row):
if (col == self._fill_value) is match:
if subset and self.keys()[n_col] not in subset:
continue
if (col == self._fill_value) is match_any:
if axis == "rows":
match_line.add(n_row)
break
@@ -229,7 +267,12 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
else:
self.drop(*to_drop)
def drop_duplicates(self, axis: str = "rows"):
def drop_duplicates(
self,
axis: str = "rows",
subset: Optional[Iterable[str]] = None,
ignore_empty: bool = True,
):
if axis not in ["rows", "cols"]:
raise ValueError(
f"Invalid 'axis' value {axis}."
@@ -239,8 +282,12 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
if axis == "cols":
cols_to_drop: List[str] = []
for n_col, col in enumerate(self.columns):
if subset and self.keys()[n_col] not in subset:
continue
# Cast to str because Text is not hashable error
unique_vals = {str(x) for x in col if x != self._fill_value}
unique_vals = {str(x) for x in col}
if ignore_empty and self._fill_value in unique_vals:
unique_vals -= {self._fill_value}
if len(unique_vals) == 1:
cols_to_drop.append(self.keys()[n_col])
self.drop(*cols_to_drop)
@@ -249,6 +296,13 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
unique_rows = []
rows_to_drop: List[int] = []
for n_row, row in enumerate(self):
if subset:
row = [
col
for n_col, col in enumerate(row)
if self.keys()[n_col] in subset
]
tuple_row = tuple(row)
if tuple_row in unique_rows:
rows_to_drop.append(n_row)
+7 -2
View File
@@ -94,7 +94,7 @@ class Config(dict):
try:
from dvc.repo import Repo
self.dvc_dir = os.path.join(Repo.find_dvc_dir())
self.dvc_dir = Repo.find_dvc_dir()
except NotDvcRepoError:
self.dvc_dir = None
else:
@@ -266,7 +266,12 @@ class Config(dict):
"key_path": func,
}
},
"machine": {str: {"startup_script": func}},
"machine": {
str: {
"startup_script": func,
"setup_script": func,
}
},
}
return Schema(dirs_schema, extra=ALLOW_EXTRA)(conf)
+15 -2
View File
@@ -79,13 +79,13 @@ REMOTE_COMMON = {
"checksum_jobs": All(Coerce(int), Range(1)),
"jobs": All(Coerce(int), Range(1)),
Optional("no_traverse"): Bool, # obsoleted
"verify": Bool,
}
LOCAL_COMMON = {
"type": supported_cache_type,
Optional("protected", default=False): Bool, # obsoleted
"shared": All(Lower, Choices("group")),
Optional("slow_link_warning", default=True): Bool,
Optional("verify", default=False): Bool,
}
HTTP_COMMON = {
"auth": All(Lower, Choices("basic", "digest", "custom")),
@@ -95,6 +95,7 @@ HTTP_COMMON = {
"ask_password": Bool,
"ssl_verify": Any(Bool, str),
"method": str,
Optional("verify", default=False): Bool,
}
WEBDAV_COMMON = {
"user": str,
@@ -105,6 +106,7 @@ WEBDAV_COMMON = {
"key_path": str,
"timeout": Coerce(int),
"ssl_verify": Any(Bool, str),
Optional("verify", default=False): Bool,
}
SCHEMA = {
@@ -158,11 +160,13 @@ SCHEMA = {
"cache_regions": bool,
"read_timeout": Coerce(int),
"connect_timeout": Coerce(int),
Optional("verify", default=False): Bool,
**REMOTE_COMMON,
},
"gs": {
"projectname": str,
"credentialpath": str,
Optional("verify", default=False): Bool,
**REMOTE_COMMON,
},
"ssh": {
@@ -175,6 +179,7 @@ SCHEMA = {
"timeout": Coerce(int),
"gss_auth": Bool,
"allow_agent": Bool,
Optional("verify", default=False): Bool,
**REMOTE_COMMON,
},
"hdfs": {"user": str, "kerb_ticket": str, **REMOTE_COMMON},
@@ -185,6 +190,7 @@ SCHEMA = {
"ssl_verify": Any(Bool, str),
"token": str,
"use_https": Bool,
Optional("verify", default=False): Bool,
**REMOTE_COMMON,
},
"azure": {
@@ -200,12 +206,14 @@ SCHEMA = {
"exclude_visual_studio_code_credential": Bool,
"exclude_shared_token_cache_credential": Bool,
"exclude_managed_identity_credential": Bool,
Optional("verify", default=False): Bool,
**REMOTE_COMMON,
},
"oss": {
"oss_key_id": str,
"oss_key_secret": str,
"oss_endpoint": str,
Optional("verify", default=True): Bool,
**REMOTE_COMMON,
},
"gdrive": {
@@ -216,6 +224,7 @@ SCHEMA = {
"gdrive_service_account_user_email": str,
"gdrive_service_account_json_file_path": str,
Optional("gdrive_trash_only", default=False): Bool,
Optional("verify", default=True): Bool,
**REMOTE_COMMON,
},
"http": {**HTTP_COMMON, **REMOTE_COMMON},
@@ -248,6 +257,7 @@ SCHEMA = {
"instance_gpu": Lower,
"ssh_private": str,
"startup_script": str,
"setup_script": str,
},
},
# section for experimental features
@@ -256,7 +266,10 @@ SCHEMA = {
# enabled by default. It's of no use, kept for backward compatibility.
Optional("parametrization", default=True): Bool,
},
"plots": {"html_template": str},
"plots": {
"html_template": str,
Optional("auto_open", default=False): Bool,
},
"exp": {
"code": str,
"data": str,
+1 -1
View File
@@ -42,7 +42,7 @@ def _spawn_windows(cmd, env):
def _spawn_posix(cmd, env):
from dvc.main import main
from dvc.cli import main
# NOTE: using os._exit instead of sys.exit, because dvc built
# with PyInstaller has trouble with SystemExit exception and throws
+33
View File
@@ -0,0 +1,33 @@
import logging
from typing import TYPE_CHECKING, Iterator, Union
from .tree import Tree
if TYPE_CHECKING:
from dvc.hash_info import HashInfo
from dvc.objects.db import ObjectDB
from dvc.objects.file import HashFile
logger = logging.getLogger(__name__)
def check(odb: "ObjectDB", obj: "HashFile", **kwargs):
if isinstance(obj, Tree):
for _, _, oid in obj:
odb.check(oid, **kwargs)
odb.check(obj.hash_info, **kwargs)
def load(odb: "ObjectDB", hash_info: "HashInfo") -> "HashFile":
if hash_info.isdir:
return Tree.load(odb, hash_info)
return odb.get(hash_info)
def iterobjs(
obj: Union["Tree", "HashFile"]
) -> Iterator[Union["Tree", "HashFile"]]:
if isinstance(obj, Tree):
yield from (entry_obj for _, entry_obj in obj)
yield obj
@@ -5,14 +5,13 @@ from dvc import prompt
from dvc.exceptions import CacheLinkError, CheckoutError, ConfirmRemoveError
from dvc.fs.utils import test_links, transfer
from dvc.ignore import DvcIgnoreFilter
from dvc.objects.db.slow_link_detection import ( # type: ignore[attr-defined]
slow_link_guard,
)
from dvc.objects.diff import ROOT
from dvc.objects.diff import diff as odiff
from dvc.objects.stage import stage
from dvc.types import Optional
from .diff import ROOT
from .diff import diff as odiff
from .slow_link_detection import slow_link_guard # type: ignore[attr-defined]
from .stage import stage
logger = logging.getLogger(__name__)
@@ -7,20 +7,13 @@ if TYPE_CHECKING:
def get_odb(fs, fs_path, **config):
from .base import ObjectDB
from .gdrive import GDriveObjectDB
from dvc.objects.db import ObjectDB
from .local import LocalObjectDB
from .oss import OSSObjectDB
if fs.scheme == Schemes.LOCAL:
return LocalObjectDB(fs, fs_path, **config)
if fs.scheme == Schemes.GDRIVE:
return GDriveObjectDB(fs, fs_path, **config)
if fs.scheme == Schemes.OSS:
return OSSObjectDB(fs, fs_path, **config)
return ObjectDB(fs, fs_path, **config)
@@ -3,7 +3,8 @@ import os
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Iterable, Set
from ..errors import ObjectDBError
from dvc.objects.errors import ObjectDBError
from dvc.utils.decorators import with_diskcache
if TYPE_CHECKING:
from dvc.types import StrPath
@@ -88,7 +89,7 @@ class ObjectDBIndex(ObjectDBIndexBase):
tmp_dir: "StrPath",
name: str,
): # pylint: disable=super-init-not-called
from diskcache import Index
from diskcache import Cache, Index
from dvc.fs.local import LocalFileSystem
from dvc.utils.fs import makedirs
@@ -96,18 +97,28 @@ class ObjectDBIndex(ObjectDBIndexBase):
self.index_dir = os.path.join(tmp_dir, self.INDEX_DIR, name)
makedirs(self.index_dir, exist_ok=True)
self.fs = LocalFileSystem()
self.index = Index(self.index_dir)
self.index = Index.fromcache(
Cache(
self.index_dir,
disk_pickle_protocol=4,
eviction_policy="none",
)
)
@with_diskcache(name="index")
def __iter__(self):
return iter(self.index)
@with_diskcache(name="index")
def __contains__(self, hash_):
return hash_ in self.index
@with_diskcache(name="index")
def dir_hashes(self):
"""Iterate over .dir hashes stored in the index."""
yield from (hash_ for hash_, is_dir in self.index.items() if is_dir)
@with_diskcache(name="index")
def clear(self):
"""Clear this index (to force re-indexing later)."""
from diskcache import Timeout
@@ -117,6 +128,7 @@ class ObjectDBIndex(ObjectDBIndexBase):
except Timeout as exc:
raise ObjectDBError("Failed to clear ODB index") from exc
@with_diskcache(name="index")
def update(self, dir_hashes: Iterable[str], file_hashes: Iterable[str]):
"""Update this index, adding the specified hashes."""
from diskcache import Timeout
@@ -131,6 +143,7 @@ class ObjectDBIndex(ObjectDBIndexBase):
except Timeout as exc:
raise ObjectDBError("Failed to update ODB index") from exc
@with_diskcache(name="index")
def intersection(self, hashes: Set[str]):
"""Iterate over values from `hashes` which exist in the index."""
yield from hashes.intersection(self.index.keys())
@@ -6,13 +6,12 @@ from funcy import cached_property
from shortuuid import uuid
from dvc.hash_info import HashInfo
from dvc.objects.db import ObjectDB
from dvc.objects.errors import ObjectFormatError
from dvc.progress import Tqdm
from dvc.utils import relpath
from dvc.utils.fs import copyfile, remove, umask, walk_files
from .base import ObjectDB
logger = logging.getLogger(__name__)
@@ -79,7 +78,7 @@ class LocalObjectDB(ObjectDB):
return ret
def _list_paths(self, prefix=None, progress_callback=None):
def _list_paths(self, prefix=None):
assert self.fs_path is not None
if prefix:
fs_path = self.fs.path.join(self.fs_path, prefix[:2])
@@ -90,12 +89,7 @@ class LocalObjectDB(ObjectDB):
# NOTE: use utils.fs walk_files since fs.walk_files will not follow
# symlinks
if progress_callback:
for path in walk_files(fs_path):
progress_callback()
yield path
else:
yield from walk_files(fs_path)
yield from walk_files(fs_path)
def _remove_unpacked_dir(self, hash_):
hash_fs_path = self.hash_to_path(hash_)
@@ -3,11 +3,11 @@ import logging
import os
from typing import TYPE_CHECKING, Dict
from dvc.objects.db import ObjectDB
from dvc.objects.errors import ObjectFormatError
from dvc.scheme import Schemes
from ..errors import ObjectFormatError
from ..reference import ReferenceHashFile
from .base import ObjectDB
if TYPE_CHECKING:
from dvc.fs.base import FileSystem
+2 -3
View File
@@ -3,8 +3,7 @@ from typing import TYPE_CHECKING, List, Optional, Tuple
if TYPE_CHECKING:
from dvc.hash_info import HashInfo
from .file import HashFile
from dvc.objects.file import HashFile
ADD = "add"
MODIFY = "modify"
@@ -96,7 +95,7 @@ def diff(
return entry_obj.hash_info if entry_obj else None
def _in_cache(oid, cache):
from .errors import ObjectFormatError
from dvc.objects.errors import ObjectFormatError
if not oid:
return False
+40
View File
@@ -0,0 +1,40 @@
def gc(odb, used, jobs=None, cache_odb=None, shallow=True):
from dvc.data.tree import Tree
from dvc.objects.errors import ObjectDBPermissionError
if odb.read_only:
raise ObjectDBPermissionError("Cannot gc read-only ODB")
if not cache_odb:
cache_odb = odb
used_hashes = set()
for hash_info in used:
used_hashes.add(hash_info.value)
if hash_info.isdir and not shallow:
tree = Tree.load(cache_odb, hash_info)
used_hashes.update(
entry_obj.hash_info.value for _, entry_obj in tree
)
def _is_dir_hash(_hash):
from dvc.hash_info import HASH_DIR_SUFFIX
return _hash.endswith(HASH_DIR_SUFFIX)
removed = False
# hashes must be sorted to ensure we always remove .dir files first
for hash_ in sorted(
odb.all(jobs, odb.fs_path),
key=_is_dir_hash,
reverse=True,
):
if hash_ in used_hashes:
continue
fs_path = odb.hash_to_path(hash_)
if _is_dir_hash(hash_):
# backward compatibility
# pylint: disable=protected-access
odb._remove_unpacked_dir(hash_)
odb.fs.remove(fs_path)
removed = True
return removed
+17 -2
View File
@@ -1,15 +1,25 @@
from collections import OrderedDict
from dataclasses import dataclass, field
from typing import Optional
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from dvc.objects.db import ObjectDB
from dvc.objects.file import HashFile
@dataclass
class Meta:
PARAM_SIZE = "size"
PARAM_NFILES = "nfiles"
PARAM_ISEXEC = "isexec"
size: Optional[int] = field(default=None)
nfiles: Optional[int] = field(default=None)
isexec: Optional[bool] = field(default=False)
obj: Optional["HashFile"] = field(default=None)
odb: Optional["ObjectDB"] = field(default=None)
remote: Optional[str] = field(default=None)
@classmethod
def from_dict(cls, d):
@@ -18,7 +28,9 @@ class Meta:
size = d.pop(cls.PARAM_SIZE, None)
nfiles = d.pop(cls.PARAM_NFILES, None)
return cls(size=size, nfiles=nfiles)
isexec = d.pop(cls.PARAM_ISEXEC, False)
return cls(size=size, nfiles=nfiles, isexec=isexec)
def to_dict(self):
ret = OrderedDict()
@@ -29,4 +41,7 @@ class Meta:
if self.nfiles is not None:
ret[self.PARAM_NFILES] = self.nfiles
if self.isexec:
ret[self.PARAM_ISEXEC] = self.isexec
return ret
@@ -4,16 +4,15 @@ import os
import pickle
from typing import TYPE_CHECKING, Optional
from .errors import ObjectFormatError
from .file import HashFile
from dvc.objects.errors import ObjectFormatError
from dvc.objects.file import HashFile
if TYPE_CHECKING:
from dvc.fs.base import FileSystem
from dvc.hash_info import HashInfo
from dvc.objects.db import ObjectDB
from dvc.types import AnyPath
from .db.base import ObjectDB
logger = logging.getLogger(__name__)
+7 -7
View File
@@ -8,19 +8,18 @@ from typing import TYPE_CHECKING, Dict, Optional, Tuple
from dvc.exceptions import DvcIgnoreInCollectedDirError
from dvc.hash_info import HashInfo
from dvc.ignore import DvcIgnore
from dvc.objects.file import HashFile
from dvc.progress import Tqdm
from dvc.utils import file_md5
from dvc.utils import file_md5, is_exec
from .db.reference import ReferenceObjectDB
from .file import HashFile
from .meta import Meta
if TYPE_CHECKING:
from dvc.fs.base import FileSystem
from dvc.objects.db import ObjectDB
from dvc.types import AnyPath
from .db.base import ObjectDB
logger = logging.getLogger(__name__)
@@ -73,7 +72,7 @@ def _get_file_hash(fs_path, fs, name):
else:
raise NotImplementedError
meta = Meta(size=info["size"])
meta = Meta(size=info["size"], isexec=is_exec(info.get("mode", 0)))
hash_info = HashInfo(name, hash_value)
return meta, hash_info
@@ -169,7 +168,7 @@ def _build_tree(fs_path, fs, name, **kwargs):
assert key
tree.add(key, meta, obj.hash_info)
tree_meta.size += meta.size
tree_meta.size += meta.size or 0
tree_meta.nfiles += 1
return tree_meta, tree
@@ -245,8 +244,9 @@ def _get_staging(odb: "ObjectDB") -> "ObjectDB":
def _load_from_state(odb, staging, fs_path, fs, name):
from dvc.objects.errors import ObjectFormatError
from . import check, load
from .errors import ObjectFormatError
from .tree import Tree
state = odb.state
+14 -9
View File
@@ -7,9 +7,10 @@ from dvc.scheme import Schemes
from .tree import Tree
if TYPE_CHECKING:
from .db.base import ObjectDB
from dvc.objects.db import ObjectDB
from dvc.objects.file import HashFile
from .db.index import ObjectDBIndexBase
from .file import HashFile
logger = logging.getLogger(__name__)
@@ -26,14 +27,14 @@ class CompareStatusResult(NamedTuple):
deleted: Set["HashInfo"]
def _indexed_dir_hashes(odb, index, dir_objs, name, cache_odb):
def _indexed_dir_hashes(odb, index, dir_objs, name, cache_odb, jobs=None):
# Validate our index by verifying all indexed .dir hashes
# still exist on the remote
dir_hashes = set(dir_objs.keys())
indexed_dirs = set(index.dir_hashes())
indexed_dir_exists = set()
if indexed_dirs:
indexed_dir_exists.update(odb.list_hashes_exists(indexed_dirs))
indexed_dir_exists.update(odb.list_hashes_exists(indexed_dirs, jobs))
missing_dirs = indexed_dirs.difference(indexed_dir_exists)
if missing_dirs:
logger.debug(
@@ -45,7 +46,7 @@ def _indexed_dir_hashes(odb, index, dir_objs, name, cache_odb):
# Check if non-indexed (new) dir hashes exist on remote
dir_exists = dir_hashes.intersection(indexed_dir_exists)
dir_exists.update(odb.list_hashes_exists(dir_hashes - dir_exists))
dir_exists.update(odb.list_hashes_exists(dir_hashes - dir_exists, jobs))
# If .dir hash exists in the ODB, assume directory contents
# also exists
@@ -75,6 +76,7 @@ def status(
index: Optional["ObjectDBIndexBase"] = None,
cache_odb: Optional["ObjectDB"] = None,
shallow: bool = True,
jobs: Optional[int] = None,
**kwargs,
) -> "StatusResult":
"""Return status of whether or not the specified objects exist odb.
@@ -120,7 +122,9 @@ def status(
if index and hashes:
if dir_objs:
exists = hashes.intersection(
_indexed_dir_hashes(odb, index, dir_objs, name, cache_odb)
_indexed_dir_hashes(
odb, index, dir_objs, name, cache_odb, jobs=jobs
)
)
hashes.difference_update(exists)
if hashes:
@@ -128,7 +132,7 @@ def status(
hashes.difference_update(exists)
if hashes:
exists.update(odb.hashes_exist(hashes, name=odb.fs_path, **kwargs))
exists.update(odb.hashes_exist(hashes, name=odb.fs_path, jobs=jobs))
return StatusResult(
{hash_infos[hash_] for hash_ in exists},
{hash_infos[hash_] for hash_ in (hashes - exists)},
@@ -143,6 +147,7 @@ def compare_status(
check_deleted: bool = True,
src_index: Optional["ObjectDBIndexBase"] = None,
dest_index: Optional["ObjectDBIndexBase"] = None,
jobs: Optional[int] = None,
**kwargs,
) -> "CompareStatusResult":
"""Compare status for the specified objects between two ODBs.
@@ -156,13 +161,13 @@ def compare_status(
if "cache_odb" not in kwargs:
kwargs["cache_odb"] = src
dest_exists, dest_missing = status(
dest, obj_ids, index=dest_index, **kwargs
dest, obj_ids, index=dest_index, jobs=jobs, **kwargs
)
# for transfer operations we can skip src status check when all objects
# already exist in dest
if dest_missing or check_deleted:
src_exists, src_missing = status(
src, obj_ids, index=src_index, **kwargs
src, obj_ids, index=src_index, jobs=jobs, **kwargs
)
else:
src_exists = dest_exists
@@ -10,8 +10,8 @@ from dvc.utils.threadpool import ThreadPoolExecutor
if TYPE_CHECKING:
from dvc.hash_info import HashInfo
from dvc.objects.db import ObjectDB
from .db.base import ObjectDB
from .db.index import ObjectDBIndexBase
from .tree import Tree
@@ -40,7 +40,8 @@ def _log_exceptions(func):
def find_tree_by_obj_id(
odbs: Iterable[Optional["ObjectDB"]], obj_id: "HashInfo"
) -> Optional["Tree"]:
from .errors import ObjectFormatError
from dvc.objects.errors import ObjectFormatError
from .tree import Tree
for odb in odbs:
@@ -150,7 +151,9 @@ def transfer(
if src == dest:
return 0
status = compare_status(src, dest, obj_ids, check_deleted=False, **kwargs)
status = compare_status(
src, dest, obj_ids, check_deleted=False, jobs=jobs, **kwargs
)
if not status.new:
return 0
+48 -6
View File
@@ -1,22 +1,40 @@
import json
import logging
import posixpath
from typing import TYPE_CHECKING, Dict, Optional, Tuple
from typing import TYPE_CHECKING, Dict, Iterable, Optional, Tuple
from funcy import cached_property
from .errors import ObjectFormatError
from .file import HashFile
from dvc.objects.errors import ObjectFormatError
from dvc.objects.file import HashFile
from .stage import get_file_hash
if TYPE_CHECKING:
from dvc.hash_info import HashInfo
from dvc.objects.db import ObjectDB
from .meta import Meta
logger = logging.getLogger(__name__)
def _try_load(
odbs: Iterable["ObjectDB"],
hash_info: "HashInfo",
) -> Optional["HashFile"]:
for odb in odbs:
if not odb:
continue
try:
return Tree.load(odb, hash_info)
except (FileNotFoundError, ObjectFormatError):
pass
return None
class Tree(HashFile):
PARAM_RELPATH = "relpath"
@@ -25,7 +43,7 @@ class Tree(HashFile):
self._dict: Dict[Tuple[str], Tuple["Meta", "HashInfo"]] = {}
@cached_property
def trie(self):
def _trie(self):
from pygtrie import Trie
return Trie(self._dict)
@@ -51,6 +69,30 @@ class Tree(HashFile):
assert self.hash_info.value
self.hash_info.value += ".dir"
def _load(self, key, meta, hash_info):
if hash_info and hash_info.isdir and not meta.obj:
meta.obj = _try_load([meta.odb, meta.remote], hash_info)
if meta.obj:
for ikey, value in meta.obj.iteritems():
self._trie[key + ikey] = value
self._dict[key + ikey] = value
def iteritems(self, prefix=None):
kwargs = {}
if prefix:
kwargs = {"prefix": prefix}
item = self._trie.longest_prefix(prefix)
if item:
key, (meta, hash_info) = item
self._load(key, meta, hash_info)
for key, (meta, hash_info) in self._trie.iteritems(**kwargs):
self._load(key, meta, hash_info)
yield key, (meta, hash_info)
def shortest_prefix(self, *args, **kwargs):
return self._trie.shortest_prefix(*args, **kwargs)
def __len__(self):
return len(self._dict)
@@ -130,7 +172,7 @@ class Tree(HashFile):
"""
tree = Tree(self.fs_path, self.fs, self.hash_info)
try:
for key, (meta, oid) in self.trie.items(prefix):
for key, (meta, oid) in self._trie.items(prefix):
tree.add(key, meta, oid)
except KeyError:
pass
@@ -148,7 +190,7 @@ class Tree(HashFile):
tree = Tree(None, None, None)
depth = len(prefix)
try:
for key, (meta, entry_oid) in self.trie.items(prefix):
for key, (meta, entry_oid) in self._trie.items(prefix):
tree.add(key[depth:], meta, entry_oid)
except KeyError:
return None
+6 -6
View File
@@ -3,11 +3,11 @@
import logging
from typing import TYPE_CHECKING, Iterable, Optional
from dvc.objects.db import get_index
from dvc.data.db import get_index
if TYPE_CHECKING:
from dvc.hash_info import HashInfo
from dvc.objects.db.base import ObjectDB
from dvc.objects.db import ObjectDB
logger = logging.getLogger(__name__)
@@ -55,8 +55,8 @@ class DataCloud:
raise NoRemoteError(error_msg)
def _init_odb(self, name):
from dvc.data.db import get_odb
from dvc.fs import get_cloud_fs
from dvc.objects.db import get_odb
cls, config, fs_path = get_cloud_fs(self.repo, name=name)
config["tmp_dir"] = self.repo.index_db_dir
@@ -78,7 +78,7 @@ class DataCloud:
By default remote from core.remote config option is used.
odb: optional ODB to push to. Overrides remote.
"""
from dvc.objects.transfer import transfer
from dvc.data.transfer import transfer
if not odb:
odb = self.get_remote_odb(remote, "push")
@@ -107,7 +107,7 @@ class DataCloud:
By default remote from core.remote config option is used.
odb: optional ODB to pull from. Overrides remote.
"""
from dvc.objects.transfer import transfer
from dvc.data.transfer import transfer
if not odb:
odb = self.get_remote_odb(remote, "pull")
@@ -141,7 +141,7 @@ class DataCloud:
log_missing: log warning messages if file doesn't exist
neither in cache, neither in cloud.
"""
from dvc.objects.status import compare_status
from dvc.data.status import compare_status
if not odb:
odb = self.get_remote_odb(remote, "status")
+14 -1
View File
@@ -47,16 +47,29 @@ def loads_from(stage, s_list, erepo=None):
def _merge_params(s_list):
d = defaultdict(list)
default_file = ParamsDependency.DEFAULT_PARAMS_FILE
# figure out completely tracked params file, and ignore specific keys
wholly_tracked = set()
for key in s_list:
if not isinstance(key, dict):
continue
wholly_tracked.update(k for k, params in key.items() if not params)
for key in s_list:
if isinstance(key, str):
d[default_file].append(key)
if default_file not in wholly_tracked:
d[default_file].append(key)
continue
if not isinstance(key, dict):
msg = "Only list of str/dict is supported. Got: "
msg += f"'{type(key).__name__}'."
raise ValueError(msg)
for k, params in key.items():
if k in wholly_tracked:
d[k] = []
continue
if not isinstance(params, list):
msg = "Expected list of params for custom params file "
msg += f"'{k}', got '{type(params).__name__}'."
+77 -58
View File
@@ -1,6 +1,8 @@
import logging
import os
import typing
from collections import defaultdict
from typing import Dict
import dpath.util
from voluptuous import Any
@@ -18,6 +20,14 @@ class MissingParamsError(DvcException):
pass
class MissingParamsFile(DvcException):
pass
class ParamsIsADirectoryError(DvcException):
pass
class BadParamFileError(DvcException):
pass
@@ -27,51 +37,83 @@ class ParamsDependency(Dependency):
PARAM_SCHEMA = {PARAM_PARAMS: Any(dict, list, None)}
DEFAULT_PARAMS_FILE = "params.yaml"
def __init__(self, stage, path, params):
info = {}
self.params = []
if params:
if isinstance(params, list):
self.params = params
else:
assert isinstance(params, dict)
self.params = list(params.keys())
info = {self.PARAM_PARAMS: params}
super().__init__(
stage,
path
or os.path.join(stage.repo.root_dir, self.DEFAULT_PARAMS_FILE),
info=info,
def __init__(self, stage, path, params=None, repo=None):
self.params = list(params) if params else []
info = (
{self.PARAM_PARAMS: params} if isinstance(params, dict) else None
)
repo = repo or stage.repo
path = path or os.path.join(repo.root_dir, self.DEFAULT_PARAMS_FILE)
super().__init__(stage, path, info=info, repo=repo)
def dumpd(self):
ret = super().dumpd()
if not self.hash_info:
ret[self.PARAM_PARAMS] = self.params
ret[self.PARAM_PARAMS] = self.params or {}
return ret
def fill_values(self, values=None):
"""Load params values dynamically."""
if not values:
if values is None:
return
info = {}
if not self.params:
info.update(values)
for param in self.params:
if param in values:
info[param] = values[param]
self.hash_info = HashInfo(self.PARAM_PARAMS, info)
def workspace_status(self):
status = super().workspace_status()
def read_params(
self, flatten: bool = True, **kwargs: typing.Any
) -> Dict[str, typing.Any]:
try:
config = self.read_file()
except MissingParamsFile:
config = {}
if status.get(str(self)) == "deleted":
return status
if not self.params:
return config
ret = {}
if flatten:
for param in self.params:
try:
ret[param] = dpath.util.get(config, param, separator=".")
except KeyError:
continue
return ret
from dpath.util import merge
for param in self.params:
merge(
ret,
dpath.util.search(config, param, separator="."),
separator=".",
)
return ret
def workspace_status(self):
if not self.exists:
return {str(self): "deleted"}
if self.hash_info.value is None:
return {str(self): "new"}
from funcy import ldistinct
status = defaultdict(dict)
info = self.hash_info.value if self.hash_info else {}
actual = self.read_params()
for param in self.params:
if param not in actual.keys():
# NOTE: we want to preserve the order of params as specified in the
# status. In case of tracking the whole file, the order is top-level
# keys in the file and then the keys in the `info` from `dvc.lock`
# (which are alphabetically sorted).
params = self.params or ldistinct([*actual.keys(), *info.keys()])
for param in params:
if param not in actual:
st = "deleted"
elif param not in info:
st = "new"
@@ -88,12 +130,19 @@ class ParamsDependency(Dependency):
def status(self):
return self.workspace_status()
def _read(self):
def validate_filepath(self):
if not self.exists:
return {}
raise MissingParamsFile(f"Parameters file '{self}' does not exist")
if self.isdir():
raise ParamsIsADirectoryError(
f"'{self}' is a directory, expected a parameters file"
)
suffix = self.repo.fs.path.suffix(self.fs_path).lower()
loader = LOADERS[suffix]
def read_file(self):
_, ext = os.path.splitext(self.fs_path)
loader = LOADERS[ext]
self.validate_filepath()
try:
return loader(self.fs_path, fs=self.repo.fs)
except ParseError as exc:
@@ -101,29 +150,6 @@ class ParamsDependency(Dependency):
f"Unable to read parameters from '{self}'"
) from exc
def read_params_d(self, **kwargs):
config = self._read()
ret = {}
for param in self.params:
dpath.util.merge(
ret,
dpath.util.search(config, param, separator="."),
separator=".",
)
return ret
def read_params(self):
config = self._read()
ret = {}
for param in self.params:
try:
ret[param] = dpath.util.get(config, param, separator=".")
except KeyError:
pass
return ret
def get_hash(self):
info = self.read_params()
@@ -144,12 +170,5 @@ class ParamsDependency(Dependency):
if not self.isfile and not self.isdir:
raise self.IsNotFileOrDirError(self)
if self.is_empty:
logger.warning(f"'{self}' is empty.")
self.ignore()
if self.metric or self.plot:
self.verify_metric()
self.hash_info = self.get_hash()
+6 -6
View File
@@ -9,7 +9,7 @@ from .base import Dependency
if TYPE_CHECKING:
from dvc.hash_info import HashInfo
from dvc.objects.db.base import ObjectDB
from dvc.objects.db import ObjectDB
from dvc.objects.file import HashFile
@@ -61,7 +61,7 @@ class RepoDependency(Dependency):
return {self.PARAM_PATH: self.def_path, self.PARAM_REPO: self.def_repo}
def download(self, to, jobs=None):
from dvc.objects.checkout import checkout
from dvc.data.checkout import checkout
for odb, objs in self.get_used_objs().items():
self.repo.cloud.pull(objs, jobs=jobs, odb=odb)
@@ -98,9 +98,9 @@ class RepoDependency(Dependency):
self, obj_only=False, **kwargs
) -> Tuple[Dict[Optional["ObjectDB"], Set["HashInfo"]], "HashFile"]:
from dvc.config import NoRemoteError
from dvc.data.stage import stage
from dvc.data.tree import Tree
from dvc.exceptions import NoOutputOrStageError, PathMissingError
from dvc.objects.stage import stage
from dvc.objects.tree import Tree
local_odb = self.repo.odb.local
locked = kwargs.pop("locked", True)
@@ -150,10 +150,10 @@ class RepoDependency(Dependency):
return used_obj_ids, staged_obj
def _check_circular_import(self, odb, obj_ids):
from dvc.data.db.reference import ReferenceObjectDB
from dvc.data.tree import Tree
from dvc.exceptions import CircularImportError
from dvc.fs.repo import RepoFileSystem
from dvc.objects.db.reference import ReferenceObjectDB
from dvc.objects.tree import Tree
if not isinstance(odb, ReferenceObjectDB):
return
+4 -12
View File
@@ -1,4 +1,5 @@
"""Exceptions raised by the dvc."""
from dvc.utils import format_link
class DvcException(Exception):
@@ -128,13 +129,6 @@ class NotDvcRepoError(DvcException):
"""Thrown if a directory is not a DVC repo"""
class DvcParserError(DvcException):
"""Base class for CLI parser errors."""
def __init__(self):
super().__init__("parser error")
class CyclicGraphError(DvcException):
def __init__(self, stages):
assert isinstance(stages, list)
@@ -157,9 +151,9 @@ class InitError(DvcException):
class ReproductionError(DvcException):
def __init__(self, dvc_file_name):
self.path = dvc_file_name
super().__init__(f"failed to reproduce '{dvc_file_name}'")
def __init__(self, name):
self.name = name
super().__init__(f"failed to reproduce '{name}'")
class BadMetricError(DvcException):
@@ -294,7 +288,6 @@ class PathMissingError(DvcException):
class RemoteCacheRequiredError(DvcException):
def __init__(self, scheme, fs_path):
from dvc.utils import format_link
super().__init__(
(
@@ -330,7 +323,6 @@ class MergeError(DvcException):
class CacheLinkError(DvcException):
from dvc.utils import format_link
SUPPORT_LINK = "See {} for more information.".format(
format_link(
+31 -20
View File
@@ -30,6 +30,7 @@ def external_repo(
from scmrepo.git import Git
from dvc.config import NoRemoteError
from dvc.fs.git import GitFileSystem
logger.debug("Creating external repo %s@%s", url, rev)
path = _cached_clone(url, rev, for_write=for_write)
@@ -45,12 +46,18 @@ def external_repo(
config = _get_remote_config(url) if os.path.isdir(url) else {}
config.update(cache_config)
root_dir = path if for_write else os.path.realpath(path)
if for_write:
root_dir = path
fs = None
else:
root_dir = os.path.realpath(path)
scm = Git(root_dir)
fs = GitFileSystem(scm=scm, rev=rev)
repo_kwargs = dict(
root_dir=root_dir,
url=url,
scm=None if for_write else Git(root_dir),
rev=None if for_write else rev,
fs=fs,
config=config,
repo_factory=erepo_factory(url, cache_config),
**kwargs,
@@ -86,7 +93,7 @@ def erepo_factory(url, cache_config, *args, **kwargs):
def make_repo(path, **_kwargs):
_config = cache_config.copy()
if os.path.isdir(url):
rel = os.path.relpath(path, _kwargs["scm"].root_dir)
rel = os.path.relpath(path, _kwargs["fs"].fs_args["scm"].root_dir)
repo_path = os.path.join(url, rel)
_config.update(_get_remote_config(repo_path))
return Repo(path, config=_config, **_kwargs)
@@ -194,12 +201,14 @@ def _clone_default_branch(url, rev, for_write=False):
# given repo URL it is easier/safer for us to work with
# full clones in this case.
logger.debug("erepo: unshallowing clone for '%s'", url)
_unshallow(git)
git.fetch(unshallow=True)
_merge_upstream(git)
shallow = False
CLONES[url] = (clone_path, shallow)
else:
logger.debug("erepo: git pull '%s'", url)
git.pull()
git.fetch()
_merge_upstream(git)
else:
from dvc.scm import clone
@@ -210,10 +219,13 @@ def _clone_default_branch(url, rev, for_write=False):
try:
git = clone(url, clone_path, shallow_branch=rev)
shallow = True
logger.debug(
"erepo: using shallow clone for branch '%s'", rev
shallow = os.path.exists(
os.path.join(clone_path, Git.GIT_DIR, "shallow")
)
if shallow:
logger.debug(
"erepo: using shallow clone for branch '%s'", rev
)
except CloneError:
pass
if not git:
@@ -227,17 +239,16 @@ def _clone_default_branch(url, rev, for_write=False):
return clone_path, shallow
def _unshallow(git):
if git.gitpython.repo.head.is_detached:
# If this is a detached head (i.e. we shallow cloned a tag) switch to
# the default branch
origin_refs = git.gitpython.repo.remotes["origin"].refs
ref = origin_refs["HEAD"].reference
branch_name = ref.name.split("/")[-1]
branch = git.gitpython.repo.create_head(branch_name, ref)
branch.set_tracking_branch(ref)
branch.checkout()
git.pull(unshallow=True)
def _merge_upstream(git):
from scmrepo.exceptions import SCMError
try:
branch = git.active_branch()
upstream = f"refs/remotes/origin/{branch}"
if git.get_ref(upstream):
git.merge(upstream)
except SCMError:
pass
def _git_checkout(repo_path, rev):
+49
View File
@@ -0,0 +1,49 @@
import fsspec
from dvc.progress import Tqdm
class FsspecCallback(fsspec.Callback):
def __init__(self, progress_bar):
self.progress_bar = progress_bar
super().__init__()
def set_size(self, size):
if size is not None:
self.progress_bar.total = size
self.progress_bar.refresh()
super().set_size(size)
def relative_update(self, inc=1):
self.progress_bar.update(inc)
super().relative_update(inc)
def absolute_update(self, value):
self.progress_bar.update_to(value)
super().absolute_update(value)
@staticmethod
def wrap_fn(cb, fn):
def wrapped(*args, **kwargs):
res = fn(*args, **kwargs)
cb.relative_update()
return res
return wrapped
def tdqm_or_callback_wrapped(
fobj, method, total, callback=None, **pbar_kwargs
):
if callback:
from funcy import nullcontext
from tqdm.utils import CallbackIOWrapper
callback.set_size(total)
wrapper = CallbackIOWrapper(callback.relative_update, fobj, method)
return nullcontext(wrapper)
return Tqdm.wrapattr(fobj, method, total=total, bytes=True, **pbar_kwargs)
DEFAULT_CALLBACK = fsspec.callbacks.NoOpCallback()
-73
View File
@@ -1,73 +0,0 @@
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, List
from dvc.output import Output
if TYPE_CHECKING:
from dvc.repo import Repo
@dataclass
class Metadata:
"""
Container for storing metadata for a given path, similar in spirit to
`os.stat_result`.
"""
# required field
fs_path: str
repo: "Repo"
# computed fields
is_output: bool = field(init=False, default=False) # is it an output?
# is the path part of an output?
part_of_output: bool = field(init=False, default=False)
# does the path contain outputs?
contains_outputs: bool = field(init=False, default=False)
# is the path tracked by dvc? equivalent to (is_output or part_of_output)
is_dvc: bool = field(init=False, default=False)
# does dvc has any output in that path or inside it? equiv. to bool(outs)
output_exists: bool = field(init=False)
# optional fields
isdir: bool = False # is it a directory?
is_exec: bool = False # is it an executable?
outs: List[Output] = field(default_factory=list) # list of outputs
def __post_init__(self):
self.output_exists = bool(self.outs)
if not self.output_exists:
return
# it can contain multiple outputs
if len(self.outs) > 1:
self.contains_outputs = True
else:
out = self.outs[0]
# or, the path itself could be an output
self.is_output = self.fs_path == out.fs_path
# or, a directory must have an output somewhere deep inside it
if not self.is_output:
self.contains_outputs = self.repo.fs.path.isin(
out.fs_path, self.fs_path
)
# or, the path could be a part of an output, i.e. inside of an output
self.part_of_output = not self.is_output and not self.contains_outputs
self.is_dvc = self.is_output or self.part_of_output
# if it contains outputs, it must be a dir
self.isdir = self.contains_outputs
@property
def isfile(self):
# self.isdir might require additional computation, especially if it's
# a dir that's inside a dvc-tracked dir
# since, `Metadata()` does not support being used as null (i.e. when
# path does not exist), `isfile` becomes `isdir`'s complement.
return not self.isdir
def __str__(self):
return f"Metadata: {self.fs_path}"
+2 -16
View File
@@ -10,7 +10,7 @@ from funcy import cached_property
from tqdm.utils import CallbackIOWrapper
from dvc.exceptions import DvcException
from dvc.progress import DEFAULT_CALLBACK, FsspecCallback
from dvc.fs._callback import DEFAULT_CALLBACK, FsspecCallback
from dvc.ui import ui
from dvc.utils import tmp_fname
from dvc.utils.fs import makedirs, move
@@ -18,14 +18,6 @@ from dvc.utils.fs import makedirs, move
logger = logging.getLogger(__name__)
class RemoteCmdError(DvcException):
def __init__(self, remote, cmd, ret, err):
super().__init__(
"{remote} command '{cmd}' finished with non-zero return code"
" {ret}': {err}".format(remote=remote, cmd=cmd, ret=ret, err=err)
)
class RemoteActionNotImplemented(DvcException):
def __init__(self, action, scheme):
m = f"{action} is not supported for {scheme} remotes"
@@ -158,17 +150,11 @@ class FileSystem:
"""
return True
def isexec(self, path):
"""Optional: Overwrite only if the remote has a way to distinguish
between executable and non-executable file.
"""
return False
def iscopy(self, path):
"""Check if this file is an independent copy."""
return False # We can't be sure by default
def walk(self, top, topdown=True, onerror=None, **kwargs):
def walk(self, top, topdown=True, **kwargs):
"""Return a generator with (root, dirs, files)."""
raise NotImplementedError
+93 -146
View File
@@ -2,15 +2,10 @@ import logging
import os
import typing
from dvc.exceptions import OutputNotFoundError
from dvc.utils import relpath
from ..progress import DEFAULT_CALLBACK
from ._metadata import Metadata
from ._callback import DEFAULT_CALLBACK
from .base import FileSystem
if typing.TYPE_CHECKING:
from dvc.output import Output
from dvc.types import AnyPath
logger = logging.getLogger(__name__)
@@ -36,67 +31,46 @@ class DvcFileSystem(FileSystem): # pylint:disable=abstract-method
def config(self):
raise NotImplementedError
def _find_outs(self, path, *args, **kwargs):
outs = self.repo.find_outs_by_path(path, *args, **kwargs)
def _get_key(self, path):
from dvc.fs.local import LocalFileSystem
def _is_cached(out):
return out.use_cache
from . import get_cloud_fs
outs = list(filter(_is_cached, outs))
if not outs:
raise OutputNotFoundError(path, self.repo)
cls, kwargs, fs_path = get_cloud_fs(None, url=path)
return outs
if cls != LocalFileSystem or os.path.isabs(path):
fs = cls(**kwargs)
return (cls.scheme, *fs.path.parts(fs_path))
def _get_granular_hash(self, path: "AnyPath", out: "Output", remote=None):
# NOTE: use string paths here for performance reasons
key = tuple(relpath(path, out.fs_path).split(os.sep))
out.get_dir_cache(remote=remote)
if out.obj is None:
raise FileNotFoundError
(_, oid) = out.obj.trie.get(key) or (None, None)
if oid:
return oid
raise FileNotFoundError
fs_key = "repo"
key = self.path.parts(path)
if key == (".",) or key == ("",):
key = ()
return (fs_key, *key)
def _get_fs_path(self, path: "AnyPath", remote=None):
try:
outs = self._find_outs(path, strict=False)
except OutputNotFoundError as exc:
raise FileNotFoundError from exc
from dvc.config import NoRemoteError
if len(outs) != 1 or (
outs[0].is_dir_checksum and path == outs[0].fs_path
):
info = self.info(path)
if info["type"] == "directory":
raise IsADirectoryError
out = outs[0]
if not out.hash_info:
value = info.get("md5")
if not value:
raise FileNotFoundError
if out.changed_cache(filter_info=path):
from dvc.config import NoRemoteError
cache_path = self.repo.odb.local.hash_to_path(value)
try:
remote_odb = self.repo.cloud.get_remote_odb(remote)
except NoRemoteError as exc:
raise FileNotFoundError from exc
if out.is_dir_checksum:
checksum = self._get_granular_hash(path, out).value
else:
checksum = out.hash_info.value
remote_fs_path = remote_odb.hash_to_path(checksum)
return remote_odb.fs, remote_fs_path
if self.repo.odb.local.fs.exists(cache_path):
return self.repo.odb.local.fs, cache_path
if out.is_dir_checksum:
checksum = self._get_granular_hash(path, out).value
cache_path = out.odb.fs.unstrip_protocol(
out.odb.hash_to_path(checksum)
)
else:
cache_path = out.cache_path
return out.odb.fs, cache_path
try:
remote_odb = self.repo.cloud.get_remote_odb(remote)
except NoRemoteError as exc:
raise FileNotFoundError from exc
remote_fs_path = remote_odb.hash_to_path(value)
return remote_odb.fs, remote_fs_path
def open( # type: ignore
self, path: str, mode="r", encoding=None, **kwargs
@@ -106,72 +80,44 @@ class DvcFileSystem(FileSystem): # pylint:disable=abstract-method
def exists(self, path): # pylint: disable=arguments-renamed
try:
self.metadata(path)
self.info(path)
return True
except FileNotFoundError:
return False
def isdir(self, path): # pylint: disable=arguments-renamed
try:
meta = self.metadata(path)
return meta.isdir
return self.info(path)["type"] == "directory"
except FileNotFoundError:
return False
def check_isdir(self, path, outs):
if len(outs) != 1:
return True
out = outs[0]
if not out.is_dir_checksum:
return out.fs_path != path
if out.fs_path == path:
return True
try:
self._get_granular_hash(path, out)
return False
except FileNotFoundError:
return True
def isfile(self, path): # pylint: disable=arguments-renamed
try:
meta = self.metadata(path)
return meta.isfile
return self.info(path)["type"] == "file"
except FileNotFoundError:
return False
def _fetch_dir(self, out, **kwargs):
# pull dir cache if needed
out.get_dir_cache(**kwargs)
if not out.obj:
raise FileNotFoundError
def _add_dir(self, trie, out, **kwargs):
self._fetch_dir(out, **kwargs)
base = out.fs.path.parts(out.fs_path)
for key, _, _ in out.obj: # noqa: B301
trie[base + key] = None
def _walk(self, root, trie, topdown=True, **kwargs):
def _walk(self, root, topdown=True, **kwargs):
dirs = set()
files = []
root_parts = self.path.parts(root)
out = trie.get(root_parts)
if out and out.is_dir_checksum:
self._add_dir(trie, out, **kwargs)
root_parts = self._get_key(root)
root_len = len(root_parts)
try:
for key, out in trie.iteritems(prefix=root_parts): # noqa: B301
for key, (meta, hash_info) in self.repo.index.tree.iteritems(
prefix=root_parts
): # noqa: B301
if hash_info and hash_info.isdir and meta and not meta.obj:
raise FileNotFoundError
if key == root_parts:
continue
if hash_info.isdir:
continue
name = key[root_len]
if len(key) > root_len + 1 or (out and out.is_dir_checksum):
if len(key) > root_len + 1:
dirs.add(name)
continue
@@ -184,33 +130,19 @@ class DvcFileSystem(FileSystem): # pylint:disable=abstract-method
yield root, dirs, files
for dname in dirs:
yield from self._walk(self.path.join(root, dname), trie)
def walk(self, top, topdown=True, onerror=None, **kwargs):
from pygtrie import Trie
yield from self._walk(self.path.join(root, dname))
def walk(self, top, topdown=True, **kwargs):
assert topdown
root = os.path.abspath(top)
try:
meta = self.metadata(root)
info = self.info(top)
except FileNotFoundError:
if onerror is not None:
onerror(FileNotFoundError(top))
return
if not meta.isdir:
if onerror is not None:
onerror(NotADirectoryError(top))
if info["type"] != "directory":
return
trie = Trie()
for out in meta.outs:
trie[out.fs.path.parts(out.fs_path)] = out
if out.is_dir_checksum and self.path.isin_or_eq(root, out.fs_path):
self._add_dir(trie, out, **kwargs)
yield from self._walk(root, trie, topdown=topdown, **kwargs)
yield from self._walk(top, topdown=topdown, **kwargs)
def find(self, path, prefix=None):
for root, _, files in self.walk(path):
@@ -220,43 +152,58 @@ class DvcFileSystem(FileSystem): # pylint:disable=abstract-method
def isdvc(self, path, recursive=False, strict=True):
try:
meta = self.metadata(path)
info = self.info(path)
except FileNotFoundError:
return False
recurse = recursive or not strict
return meta.output_exists if recurse else meta.is_output
def isexec(self, path): # pylint: disable=unused-argument
return False
def metadata(self, fs_path):
abspath = os.path.abspath(fs_path)
try:
outs = self._find_outs(abspath, strict=False, recursive=True)
except OutputNotFoundError as exc:
raise FileNotFoundError from exc
meta = Metadata(fs_path=abspath, outs=outs, repo=self.repo)
meta.isdir = meta.isdir or self.check_isdir(meta.fs_path, meta.outs)
return meta
return bool(info.get("outs") if recurse else info.get("isout"))
def info(self, path):
meta = self.metadata(path)
ret = {"type": "directory" if meta.isdir else "file"}
if meta.is_output and len(meta.outs) == 1 and meta.outs[0].hash_info:
out = meta.outs[0]
ret["size"] = out.meta.size
ret[out.hash_info.name] = out.hash_info.value
elif meta.part_of_output:
(out,) = meta.outs
key = self.path.parts(self.path.relpath(path, out.fs_path))
(obj_meta, oid) = out.obj.trie.get(key) or (None, None)
if oid:
ret["size"] = obj_meta.size if obj_meta else 0
ret[oid.name] = oid.value
from dvc.data.meta import Meta
key = self._get_key(path)
try:
outs = list(self.repo.index.tree.iteritems(key))
except KeyError as exc:
raise FileNotFoundError from exc
ret = {
"type": "file",
"size": 0,
"isexec": False,
"isdvc": False,
"outs": outs,
}
if len(outs) > 1 and outs[0][0] != key:
shortest = self.repo.index.tree.shortest_prefix(key)
if shortest:
assert shortest[1][1].isdir
if len(shortest[0]) <= len(key):
ret["isdvc"] = True
ret["type"] = "directory"
return ret
item_key, (meta, hash_info) = outs[0]
meta = meta or Meta()
if key != item_key:
assert item_key[: len(key)] == key
ret["type"] = "directory"
return ret
ret["size"] = meta.size
ret["isexec"] = meta.isexec
ret[hash_info.name] = hash_info.value
ret["isdvc"] = True
ret["isout"] = True
ret["meta"] = meta
if hash_info and hash_info.isdir:
ret["type"] = "directory"
return ret
def get_file(
+26 -14
View File
@@ -5,8 +5,7 @@ from typing import IO, TYPE_CHECKING, Any, Dict, Iterator, Optional, overload
from funcy import cached_property
from tqdm.utils import CallbackIOWrapper
from dvc.progress import DEFAULT_CALLBACK
from ._callback import DEFAULT_CALLBACK
from .base import FileSystem
FSPath = str
@@ -50,20 +49,11 @@ class FSSpecWrapper(FileSystem):
host filesystem"""
return {}
def _isdir(self, path: AnyFSPath) -> bool:
def isdir(self, path: AnyFSPath) -> bool:
return self.fs.isdir(path)
def isdir(self, path: AnyFSPath) -> bool:
try:
return self._isdir(path)
except FileNotFoundError:
return False
def isfile(self, path: AnyFSPath) -> bool:
try:
return not self._isdir(path)
except FileNotFoundError:
return False
return self.fs.isfile(path)
def is_empty(self, path: AnyFSPath) -> bool:
entry = self.info(path)
@@ -141,9 +131,19 @@ class FSSpecWrapper(FileSystem):
self.makedirs(self.path.parent(to_info))
with self.open(to_info, "wb") as fdest:
shutil.copyfileobj(
fobj, fdest, length=getattr(fdest, "blocksize", None)
fobj,
fdest,
length=getattr(fdest, "blocksize", None), # type: ignore
)
def walk(
self,
top: AnyFSPath,
topdown: bool = True,
**kwargs: Any,
):
return self.fs.walk(top, topdown=topdown, **kwargs)
# pylint: disable=abstract-method
class ObjectFSWrapper(FSSpecWrapper):
@@ -171,6 +171,18 @@ class ObjectFSWrapper(FSSpecWrapper):
and entry["name"].endswith("/")
)
def isdir(self, path: AnyFSPath) -> bool:
try:
return self._isdir(path)
except FileNotFoundError:
return False
def isfile(self, path: AnyFSPath) -> bool:
try:
return not self._isdir(path)
except FileNotFoundError:
return False
def find(self, path, prefix=None):
if prefix:
with_prefix = self.path.parent(path)
+2 -22
View File
@@ -1,10 +1,10 @@
import os
import threading
from typing import TYPE_CHECKING, Any, Callable
from typing import TYPE_CHECKING, Any
from funcy import cached_property, wrap_prop
from .fsspec_wrapper import AnyFSPath, FSSpecWrapper
from .fsspec_wrapper import FSSpecWrapper
if TYPE_CHECKING:
from scmrepo.fs import GitFileSystem as FsspecGitFileSystem
@@ -50,23 +50,3 @@ class GitFileSystem(FSSpecWrapper): # pylint:disable=abstract-method
@property
def rev(self) -> str:
return self.fs.rev
def isexec(self, path: AnyFSPath) -> bool:
from dvc.utils import is_exec
try:
return is_exec(self.info(path)["mode"])
except FileNotFoundError:
return False
def isfile(self, path: AnyFSPath) -> bool:
return self.fs.isfile(path)
def walk(
self,
top: AnyFSPath,
topdown: bool = True,
onerror: Callable[[OSError], None] = None,
**kwargs: Any,
):
return self.fs.walk(top, topdown=topdown, onerror=onerror, **kwargs)
+3 -3
View File
@@ -27,9 +27,9 @@ class HDFSFileSystem(CallbackMixin, FSSpecWrapper):
def unstrip_protocol(self, path: str) -> str:
host = self.fs_args["host"]
port = self.fs_args["port"]
path = path.lstrip("/")
return f"hdfs://{host}:{port}/{path}"
port = self.fs_args.get("port")
netloc = host + (f":{port}" if port else "")
return "hdfs://" + netloc + "/" + path.lstrip("/")
@staticmethod
def _get_kwargs_from_urls(urlpath):
+15 -6
View File
@@ -4,9 +4,9 @@ from typing import Any
from funcy import cached_property, memoize, wrap_with
from dvc import prompt
from dvc.progress import DEFAULT_CALLBACK
from dvc.scheme import Schemes
from ._callback import DEFAULT_CALLBACK
from .fsspec_wrapper import AnyFSPath, FSSpecWrapper, NoDirectoriesMixin
@@ -81,11 +81,20 @@ class HTTPFileSystem(NoDirectoriesMixin, FSSpecWrapper):
f"Auth method {auth_method!r} is not supported."
)
# Force cleanup of closed SSL transports.
# https://github.com/iterative/dvc/issues/7414
connector_kwargs = {"enable_cleanup_closed": True}
if "ssl_verify" in config:
with fsspec_loop():
client_kwargs["connector"] = aiohttp.TCPConnector(
ssl=make_context(config["ssl_verify"])
)
connector_kwargs.update(ssl=make_context(config["ssl_verify"]))
with fsspec_loop():
client_kwargs["connector"] = aiohttp.TCPConnector(
**connector_kwargs
)
# The connector should not be owned by aiohttp.ClientSession since
# it is closed by fsspec (HTTPFileSystem.close_session)
client_kwargs["connector_owner"] = False
# Allow reading proxy configurations from the environment.
client_kwargs["trust_env"] = True
@@ -114,7 +123,7 @@ class HTTPFileSystem(NoDirectoriesMixin, FSSpecWrapper):
total=None,
connect=self.REQUEST_TIMEOUT,
sock_connect=self.REQUEST_TIMEOUT,
sock_read=None,
sock_read=self.REQUEST_TIMEOUT,
)
return RetryClient(**kwargs)

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