Compare commits

...
320 Commits
Author SHA1 Message Date
karajan1001 c81d841fd7 exp pull/push: better result handling in exp sharing(#7448)
fix: #7448
Current we skip on those experiments which already existed on both sides, but
do not exclude them from the result list. And if we have diverged experiment,
pulling/pushing them will stop the current progress and raise an exception, but
didn't give any information on the condition of other experiments.

1. Change the exp pull and push to match the new API in `scmrepo`.
2. Handle sync status after `exp pull/push` finished.
3. Bump scmrepo to 0.0.19

Co-authored-by:
2022-04-28 19:56:15 +08:00
Saugat Pachhai (सौगात) 127a9c74b2 fs.put_file: fix function signature for size 2022-04-28 15:54:40 +05:45
Saugat Pachhai (सौगात) e30d2fdda5 fs.put_file: don't require size to be passed 2022-04-28 15:31:28 +05:45
Saugat Pachhai (सौगात) ae4f56c5ad fs.put_file: don't require size to be passed 2022-04-28 15:31:28 +05:45
Ruslan Kuprieiev 8cdcf9b8bb utils: remove PathStringTrie 2022-04-27 22:40:38 +03:00
Ruslan Kuprieiev 55a4532f1d ignore: use Trie instead of PathStringTrie 2022-04-27 22:40:38 +03:00
Ruslan Kuprieiev 43b9cab01b repofs: use Trie instead of PathStringTrie 2022-04-27 22:40:38 +03:00
Saugat Pachhai (सौगात) 36d53bd403 dvc.stage: log time duration on stage collection walk in verbose mode 2022-04-27 18:04:14 +03:00
Saugat Pachhai (सौगात) 46fac33ba5 fs: remove unused CHUNK_SIZE var
There is no support for chunk_size kwarg in fs.open.
Also, `upload_fobj` is now able to figure out proper
chunk size from fileobj, so I don't think we need this on _upload_file.
2022-04-27 19:06:46 +05:45
David de la Iglesia Castro 8b68e4fa75 bump dvclive 0.7.3 2022-04-27 18:06:53 +05:45
Saugat Pachhai (सौगात) 4f1fdf0b0c fs.ossfs: remove unused var COPY_POLL_SECONDS 2022-04-27 18:06:24 +05:45
Saugat Pachhai (सौगात) 0d48598e4b fs.ssh: remove unused vars 2022-04-27 17:56:08 +05:45
Saugat Pachhai (सौगात) 5d64f6504f fs.s3: remove custom put_file/get_file 2022-04-27 17:53:17 +05:45
Saugat Pachhai (सौगात) 3d54039614 scm: fix progress bar, show progress and repo being cloned
Before, as we were not passing `total`, the Tqdm progress bar would not
show the bar or not show any progress on the bar. As we get that value lazily,
we should force it to always show the bar.
Also updated the progress bar to say which repo is being cloned.
2022-04-27 16:42:28 +05:45
Saugat Pachhai (सौगात) cda855b97a fs: add cat/cat_file/cat_ranges/pipe/pipe_file/touch methods 2022-04-27 15:57:17 +05:45
David de la Iglesia Castro bea7d56bd5 plots: Fix repo with removed plots
Changes in dvc-render 0.0.5 caused error.
2022-04-27 11:53:30 +02:00
Paweł Redzyński 10bcc9971d plots: tests: integration tests for studio
Related: #7549
2022-04-26 21:32:56 +02:00
Paweł Redzyński 700b648da9 plots: tests: integration test for vscode
Closes: #7549
2022-04-26 21:32:56 +02:00
Saugat Pachhai (सौगात) f84f9cf184 fs: do not pass size to underlying fs.put_file 2022-04-26 18:37:17 +05:45
Saugat Pachhai (सौगात) b53ebc1a5e build: try fixing building package 2022-04-26 17:28:36 +05:45
Saugat Pachhai (सौगात) 31e8eed402 fsspec.callbacks: add rich based callbacks
We have some issues, so we cannot enable them just yet, but will
be good to merge this so that we can play with it.
2022-04-26 12:28:32 +05:45
Saugat Pachhai (सौगात) 112ea1d448 dvc.objects: add callbacks for listing and estimating hashes
This PR gets rid of Tqdm progress bars inside `dvc.objects`.
Those are replaced with multi-phase callback in `hashes_exist`, and
simple callable that takes `completed` value in
`odb._estimate_remote_size()`.

The `odb.list_hashes_exists()` and `odb.all()` now return iterables,
hence the caller has the responsibility of handling progress.

This is now handled in `dvc.data` by using a custom `Tqdm` progressbar.
This is ugly and is temporary, but my motivation is to remove tqdm from dvc.objects
first. We can gradually get rid of these from `dvc.data` too, but
that requires more knowledge about `dvc.data` and involves more
design work. All in time, I guess. :)
2022-04-26 07:38:24 +05:45
Saugat Pachhai (सौगात) f39e35445a fs.upload/download: simplify, remove unused kwargs, callbacks
1. This PR simplifies `fs.upload`/`fs.download`/`fs.download_file`.
2. Removes unused kwargs.
3. `download` uses `dvc.utils.threadpool.ThreadPoolExecutor` which
   supports canceling futures.
4. `download` for directories supports nested/branched callbacks
   and progress bars.
2022-04-26 06:57:10 +05:45
Paweł Redzyński 7211bd02ed fsspec wrapper: remove can handle dirs 2022-04-25 19:48:20 +03:00
Saugat Pachhai (सौगात) 83795ff3df fs.put_file: add support for uploading fileobjects
Also changed the callbacks to inherit from our own so that we can
add few more utilities to it.
2022-04-25 21:23:05 +05:45
Saugat Pachhai (सौगात) 8ead29805f fs: rename getsize to size 2022-04-25 18:52:46 +05:45
Saugat Pachhai fecc81e951 Revert "dvc.objects: add callbacks for listing and estimating hashes"
This reverts commit 7599c6e20d.
2022-04-25 18:37:05 +05:45
Saugat Pachhai (सौगात) 7599c6e20d dvc.objects: add callbacks for listing and estimating hashes
This PR gets rid of Tqdm progress bars inside `dvc.objects`.
Those are replaced with multi-phase callback in `hashes_exist`, and
simple callable that takes `completed` value in
`odb._estimate_remote_size()`.

The `odb.list_hashes_exists()` and `odb.all()` now return iterables,
hence the caller has the responsibility of handling progress.

This is now handled in `dvc.data` by using a custom `Tqdm` progressbar.
This is ugly and is temporary, but my motivation is to remove tqdm from dvc.objects
first. We can gradually get rid of these from `dvc.data` too, but
that requires more knowledge about `dvc.data` and involves more
design work. All in time, I guess. :)
2022-04-25 18:36:47 +05:45
Saugat Pachhai (सौगात) 68a3cd9a13 fs: unify fs.base.FileSystem with FSSpecWrapper
Since all of our filesystems are fsspec-compliant, there's no need for compat wrapper.
2022-04-25 16:01:06 +09:00
David de la Iglesia Castro a394498067 render.match: Skip errored revisions in get_files 2022-04-22 16:44:52 +02:00
Saugat Pachhai (सौगात) 7708a8e292 gitfs: add regression test for broken stage.collect on gitfs when cwd != repo_root 2022-04-21 21:03:08 +05:45
Saugat Pachhai (सौगात) 23e62a9ff7 fs: make localfs fsspec-compliant
It is not completely fsspec compliant, but it's upto the level
required by our FSSpecWrapper.

Next thing to tackle would be to merging `fs.base.FileSystem`
and `fs.fsspec_wrapper.FSSpecWrapper` into one filesystem
that is lazily-loaded and is itself fsspec-compliant.
2022-04-21 12:44:55 +03:00
Saugat Pachhai (सौगात) fed0bfb2b3 tests: add test for dvc.api.open with remote
Covers #7341
2022-04-21 15:12:01 +05:45
Saugat Pachhai (सौगात) f22da81999 tests: make xfail-marked-tests to be strict by default 2022-04-21 15:06:47 +05:45
pre-commit-ci[bot] 22a988b113 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2022-04-21 15:06:31 +05:45
joncrall 77a8e870ff Add log statement to collect repo 2022-04-21 15:06:31 +05:45
David de la Iglesia Castro f20d6a7048 dag: Add --mermaid option. 2022-04-21 14:03:45 +05:45
Saugat Pachhai (सौगात) 6ffba62b75 executors: add support for cancel_futures=True in Python<3.9
Also adds a kwarg `cancel_on_error` on the constructor to support
cancelling futures on `shutdown`. This is not done by default to
be backward compatible with `concurrent.futures.ThreadPool` executor.
2022-04-21 14:03:10 +05:45
David de la Iglesia Castro 5c561572b0 exp init: Replace live section with metrics and plots.
Closes #7534
2022-04-21 08:55:49 +02:00
Peter Rowlands 444bf27e6f exp show: add internal --no-fetch option to disable temp/queue ref fetch 2022-04-21 15:45:59 +09:00
Peter Rowlands ba82faee53 exp show: don't acquire repo lock 2022-04-21 15:45:59 +09:00
dependabot[bot] 72d57f13bc build(deps-dev): Bump pylint from 2.13.5 to 2.13.7
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.13.5 to 2.13.7.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.13.5...v2.13.7)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-04-21 08:22:47 +05:45
Saugat Pachhai (सौगात) 02b2ab6804 stage/exp list: alias --names-only to --name-only
Fixes #7570.
2022-04-21 06:53:16 +05:45
Ruslan Kuprieiev fd440df5b3 dvcignore: use fs.path instead of os.path 2022-04-20 12:27:26 +03:00
Saugat Pachhai (सौगात) cc8a0ce134 completion: decouple completion related stuff from command
It also now uses the main parser itself that invokes the command rather than
creating a new parser object.
2022-04-20 13:29:33 +05:45
Ivan Shcheklein 5c1e7be909 bump pydrive: improves and fixes thread safety 2022-04-20 06:06:28 +05:45
David de la Iglesia Castro 179e080c2f hdfs: Fix _strip_protocol and _checksum
Match behavior previous to #6946
2022-04-19 16:22:49 +03:00
Saugat Pachhai (सौगात) f3a7ec7f48 completion: fix dvc exp completion 2022-04-19 17:28:22 +05:45
Saugat Pachhai (सौगात) c0580c740b fs: move NoDirectoriesMixin to http filesystem 2022-04-19 11:54:34 +05:45
dependabot[bot] b4dcadbc9c build(deps-dev): Bump pylint from 2.12.2 to 2.13.5 (#7518)
build(deps-dev): Bump pylint from 2.12.2 to 2.13.5
2022-04-19 11:35:10 +05:45
Ruslan Kuprieiev 55219e9089 fs: get rid of CallbackMixin in azure/gs/hdfs/webhdfs
No longer needed, as all of those filesystems already support callbacks.
2022-04-18 17:34:06 +03:00
David de la Iglesia Castro 70b57d4703 live: Remove html generation
Remove commands/live.
Remove LiveTask.
Bump dvclive.
2022-04-18 15:47:31 +02:00
Saugat Pachhai (सौगात) 5eb16ab05e parsing: use relative paths; force absolute paths to relative 2022-04-18 14:19:56 +03:00
Ruslan Kuprieiev 517f45d45f repofs: migrate to fsspec 2022-04-18 14:17:20 +03:00
Ruslan Kuprieiev 53c7f44480 repofs: rename _main_repo -> repo 2022-04-18 14:17:20 +03:00
Ruslan Kuprieiev 9d75e8261b repofs: use repo paths 2022-04-18 14:17:20 +03:00
Ruslan Kuprieiev b3b0ec1fba repofs: unify ls/walk/info/exists/etc behaviour 2022-04-18 14:17:20 +03:00
Ruslan Kuprieiev 0a3f5cbdd5 repofs: base walk on ls 2022-04-18 14:17:20 +03:00
Ruslan Kuprieiev bdd21fd481 repofs: use fs.ls 2022-04-18 14:17:20 +03:00
Ruslan Kuprieiev 394118ef3d repofs: don't assume repofs paths are the same as underlying fs paths 2022-04-18 14:17:20 +03:00
Ruslan Kuprieiev f500c66aef repofs: use fs.walk directly 2022-04-18 14:17:20 +03:00
dependabot[bot] 318235f072 build(deps): Bump pyinstaller from 4.10 to 5.0
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 4.10 to 5.0.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v4.10...v5.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-04-18 14:13:52 +08:00
Peter Rowlands ab395dfb7a exp show: prefer exp names for experiments 2022-04-15 14:02:35 +09:00
Nick SorrosandPaweł Redzyński dd9bc116a3 Update test name to be more informative
Co-authored-by: Paweł Redzyński <[email protected]>
2022-04-13 18:23:14 +02:00
Nick Sorros 0a3dd7a97f Move test to output and use mocker 2022-04-13 18:23:14 +02:00
Nick Sorros 5da72fc8ff Patch get_remote_odb instead 2022-04-13 18:23:14 +02:00
Nick SorrosandPaweł Redzyński d8dac6f50d Reraise RemoteMissingDepsError as is
Co-authored-by: Paweł Redzyński <[email protected]>
2022-04-13 18:23:14 +02:00
Nick Sorros 36c27ed17f Add test for remote missing when adding data dir 2022-04-13 18:23:14 +02:00
pre-commit-ci[bot] 43f5faa652 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2022-04-13 18:23:14 +02:00
Nick Sorros 968f9b38bf Raise informative missing s3 extra 2022-04-13 18:23:14 +02:00
pre-commit-ci[bot] aab7f8af4d [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.1.0 → v4.2.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.1.0...v4.2.0)
2022-04-12 10:46:46 +09:00
Paweł Redzyński 79c87ba119 Update setup.cfg 2022-04-09 15:45:29 +03:00
dependabot[bot] aa5cfc46ed build(deps): Bump scmrepo from 0.0.16 to 0.0.17
Bumps [scmrepo](https://github.com/iterative/scmrepo) from 0.0.16 to 0.0.17.
- [Release notes](https://github.com/iterative/scmrepo/releases)
- [Commits](https://github.com/iterative/scmrepo/compare/0.0.16...0.0.17)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-04-09 15:45:29 +03:00
Peter Rowlands 69acbe33c9 erepo: include exp refs in clones
- add test for dvc get --rev=<exp>
2022-04-09 10:27:06 +09:00
David de la Iglesia Castro 7dd0cffd49 gdrive: Add test_service_account_using_env_var 2022-04-08 21:39:03 +02:00
RCdeWit a11aac37ea Process feedback @skshetry and @karajan1001 2022-04-08 21:39:03 +02:00
Rob de WitandLaurens Duijvesteijn 6de1ddadf5 fs.gdrive: Fix GDRIVE_CREDENTIALS_DATA for service accounts
`_validate_config()` had a bug: it would not take into account that Google
Drive credentials can also be passed using `GDRIVE_CREDENTIALS_DATA` as
documented here [1].

This patches the validation logic to also accept this environment variable as a
means to provide service account credentials.

Before, commands like `dvc push` would exit with:

```
ERROR: failed to push data to the cloud - To use service account, set
  `gdrive_service_account_json_file_path`, and
  optionally`gdrive_service_account_user_email` in DVC config
Learn more at <https://man.dvc.org/remote/modify>
```

[1]: https://dvc.org/doc/user-guide/setup-google-drive-remote#authorization

Co-authored-by: Laurens Duijvesteijn <[email protected]>
2022-04-08 21:39:03 +02:00
Fabian ZillsandSaugat Pachhai 7804e6550e Update dvc/repo/index.py
Co-authored-by: Saugat Pachhai <[email protected]>
2022-04-07 21:05:47 +05:45
Fabian ZillsandSaugat Pachhai 65d935e5c7 Update dvc/repo/index.py
Co-authored-by: Saugat Pachhai <[email protected]>
2022-04-07 21:05:47 +05:45
Fabian 44af5b3b7e index: add getitem
* allow access to a stage via `getitem` in addition to iterating over the Index.
2022-04-07 21:05:47 +05:45
Paweł Redzyński 03f553c54b plots: json: show even if no results
Related: iterative/vscode-dvc#1521
2022-04-07 11:45:01 +02:00
dependabot[bot] 1e0a4a26eb build(deps): Bump codecov/codecov-action from 2.1.0 to 3
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 2.1.0 to 3.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v2.1.0...v3)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-04-06 17:54:18 +02:00
Peter Rowlands ee0a47f166 cli: check for closed stdout/stderr 2022-04-05 22:05:22 +09:00
Peter Rowlands fa7b371c74 daemon: skip setting PYTHONPATH in pyinstaller frozen binary 2022-04-05 22:05:22 +09:00
David de la Iglesia Castro 117b22d58a exp show: pcp: Allow to --drop Experiment 2022-04-04 11:48:55 +02:00
Paweł Redzyński bfb1b74923 plots: image: fix image to_json structure 2022-04-01 12:26:20 +02:00
Ruslan Kuprieiev d600eeca99 dvcfs: base on fsspec 2022-03-31 17:55:18 +03:00
Ruslan Kuprieiev 0470e164cc repofs: use dvcfs.ls
Using ls instead of walk reduces potential problems that may occur if
dvcfs.walk happens to yield results in different order than
gitfs/localfs.walk.
2022-03-31 17:55:18 +03:00
Ruslan Kuprieiev 0ab86c8fae dvcfs: implement ls
On our way to making dvcfs fsspec-compliant.
2022-03-31 17:55:18 +03:00
Saugat Pachhai (सौगात) 067442f348 webdav: consider path to be root
Remove _strip_protocol, moves to get_cloud_fs loading stage.
2022-03-31 17:35:23 +03:00
Saugat Pachhai (सौगात) b58ebff5db lint: fix variable reassignment bug while iterating from flake8 2022-03-31 13:41:25 +05:45
Saugat Pachhai (सौगात) 565a813e2a setup.cfg: tests: try to remove more dependencies 2022-03-31 13:36:43 +05:45
Saugat Pachhai (सौगात) dc0bc0c535 setup.cfg: tests: remove unnecessary dependencies 2022-03-31 12:29:29 +05:45
Jorge Orpinel Perez 65397517ca pre-commit: fix black/ click incompatibility issue
per https://github.com/iterative/dvc/pull/7524#discussion_r839189136
2022-03-31 12:14:35 +05:45
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
Paweł Redzyński 7250e1b336 dvc: fix installation from archives
Fixes: #7124
2021-12-11 05:49:28 +02:00
Ruslan Kuprieiev 896bbf1fc6 fs: get rid of unused DETAIL_FIELDS 2021-12-10 18:02:22 +02:00
Ruslan Kuprieiev 81eda7e2b7 fs: get rid of CHECKSUM_DIR_SUFFIX and is_dir_hash
These are leftovers from old remotes, that clearly have nothing
to do with filesystems.
2021-12-10 10:53:10 +02:00
Ruslan Kuprieiev 82c5caee27 setup: unpin s3fs/ossfs/gcsfs 2021-12-09 15:20:22 +02:00
Paweł Redzyński eb41b8b3e0 setup: bump gcsfs
Related: #7053
2021-12-09 15:20:22 +02:00
Ruslan Kuprieiev ea49fb95f7 setup: bump s3fs
Fixes #7053
2021-12-09 13:57:40 +02:00
dependabot[bot] e977bcc6d9 build(deps-dev): Bump dvclive[image] from 0.4.5 to 0.4.6
Bumps [dvclive[image]](https://github.com/iterative/dvclive) from 0.4.5 to 0.4.6.
- [Release notes](https://github.com/iterative/dvclive/releases)
- [Commits](https://github.com/iterative/dvclive/compare/0.4.5...0.4.6)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-08 14:32:48 +02:00
dependabot[bot] 33228b5cd4 build(deps): Bump scmrepo from 0.0.3 to 0.0.4
Bumps [scmrepo](https://github.com/iterative/scmrepo) from 0.0.3 to 0.0.4.
- [Release notes](https://github.com/iterative/scmrepo/releases)
- [Commits](https://github.com/iterative/scmrepo/compare/0.0.3...0.0.4)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-08 12:01:56 +01:00
Ruslan Kuprieiev 064fbfd0d5 webdav: fix unstrip_protocol 2021-12-07 23:59:12 +02:00
363 changed files with 11450 additions and 10576 deletions
+4
View File
@@ -0,0 +1,4 @@
/tests
/dvc
/.github
/bin
+1
View File
@@ -0,0 +1 @@
ref-names: $Format:%D$
+1
View File
@@ -0,0 +1 @@
.git_archival.txt export-subst
+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
+9 -12
View File
@@ -20,16 +20,17 @@ 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
run: |
pip install --upgrade pip setuptools wheel
pip install collective.checkdocs==0.2
pip install ".[dev]" pre-commit
- name: Check README
run: python setup.py checkdocs
@@ -43,11 +44,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 +57,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 +68,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: >-
@@ -78,12 +75,12 @@ jobs:
--cov-report=xml --cov-report=term
${{ env.extra_test_args }}
- name: upload coverage report
uses: codecov/codecov-action@v2.1.0
uses: codecov/codecov-action@v3
with:
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 +91,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
+13 -3
View File
@@ -1,10 +1,13 @@
__pycache__/
neatlynx.conf
neatlynx/__pycache__
.idea
/cache
*.pyc
.env/
.env2.7/
.env*/
.venv
env/
venv/
.python-version
.dvc.conf.lock
@@ -26,6 +29,7 @@ innosetup/config.ini
.coverage
.coverage.*
coverage.xml
*.sw?
@@ -40,3 +44,9 @@ scripts/ci/gcp-creds.json
*~
/dvc/_dvc_version.py
.mypy_cache/
.pytest_cache/
.nox/
.tox/
htmlcov/
+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.3.0
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
rev: v4.2.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
+7 -6
View File
@@ -19,16 +19,17 @@ def get_url(path, repo=None, rev=None, remote=None):
directory in the remote storage.
"""
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(path)
if not metadata.is_dvc:
dvc_info = info.get("dvc_info")
if not dvc_info:
raise OutputNotFoundError(path, repo)
cloud = metadata.repo.cloud
md5 = metadata.repo.dvcfs.info(fs_path)["md5"]
return cloud.get_url_for(remote, checksum=md5)
dvc_repo = info["repo"]
md5 = dvc_info["md5"]
return dvc_repo.cloud.get_url_for(remote, checksum=md5)
def open( # noqa, pylint: disable=redefined-builtin
+143
View File
@@ -0,0 +1,143 @@
"""This module provides an entrypoint to the dvc cli and parsing utils."""
import logging
import sys
# 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)
args.parser = parser
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
# NOTE: stderr/stdout may be closed if we are running from dvc.daemon.
# On Linux we directly call cli.main after double forking and closing
# the copied parent's standard file descriptors. If we make any logging
# calls in this state it will cause an exception due to writing to a closed
# file descriptor.
if sys.stderr.closed: # pylint: disable=using-constant-test
logging.disable()
elif sys.stdout.closed: # pylint: disable=using-constant-test
logging.disable(logging.INFO)
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 sys.stdout.closed and 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
@@ -1,17 +1,6 @@
import argparse
import logging
import shtab
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.ui import ui
logger = logging.getLogger(__name__)
FILE = shtab.FILE
DIR = shtab.DIRECTORY
PREAMBLE = {
"bash": """
BASH_PREAMBLE = """
# $1=COMP_WORDS[1]
_dvc_compgen_DVCFiles() {
compgen -d -S '/' -- $1 # recurse into subdirs
@@ -30,11 +19,12 @@ _dvc_compgen_stages_and_files() {
}
_dvc_compgen_exps() {
local _dvc_exps=($(dvc exp list -q --all --names-only))
local _dvc_exps=($(dvc exp list -q --all-commits --names-only))
compgen -W "${_dvc_exps[*]}" -- $1
}
""",
"zsh": """
"""
ZSH_PREAMBLE = """
_dvc_compadd_DVCFiles() {
_files -g '(*?.dvc|Dvcfile|dvc.yaml)'
}
@@ -63,49 +53,21 @@ _dvc_compadd_stages_and_files() {
}
_dvc_compadd_exps() {
_describe 'experiments' "($(dvc exp list -q -a --names-only))"
_describe 'experiments' "($(dvc exp list -q --all-commits --names-only))"
}
""",
"""
PREAMBLE = {
"bash": BASH_PREAMBLE,
"zsh": ZSH_PREAMBLE,
}
FILE = shtab.FILE
DIR = shtab.DIRECTORY
DVC_FILE = {"bash": "_dvc_compgen_DVCFiles", "zsh": "_dvc_compadd_DVCFiles"}
STAGE = {"bash": "_dvc_compgen_stages", "zsh": "_dvc_compadd_stages"}
DVCFILES_AND_STAGE = {
"bash": "_dvc_compgen_stages_and_files",
"zsh": "_dvc_compadd_stages_and_files",
}
EXPERIMENT = {"bash": "_dvc_compgen_exps", "zsh": "_dvc_compadd_exps"}
class CmdCompletion(CmdBaseNoRepo):
def run(self):
from dvc.cli import get_main_parser
parser = get_main_parser()
shell = self.args.shell
script = shtab.complete(parser, shell=shell, preamble=PREAMBLE)
ui.write(script, force=True)
return 0
def add_parser(subparsers, parent_parser):
COMPLETION_HELP = "Generate shell tab completion."
COMPLETION_DESCRIPTION = "Prints out shell tab completion scripts."
completion_parser = subparsers.add_parser(
"completion",
parents=[parent_parser],
description=append_doc_link(COMPLETION_DESCRIPTION, "completion"),
help=COMPLETION_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
completion_parser.add_argument(
"-s",
"--shell",
help="Shell syntax for completions.",
default="bash",
choices=["bash", "zsh"],
)
completion_parser.set_defaults(func=CmdCompletion)
+9 -22
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,
@@ -28,7 +27,6 @@ from .command import (
imp_url,
init,
install,
live,
ls,
machine,
metrics,
@@ -45,8 +43,8 @@ from .command import (
update,
version,
)
from .command.base import fix_subparsers
from .exceptions import DvcParserError
from . import DvcParserError
logger = logging.getLogger(__name__)
@@ -87,7 +85,6 @@ COMMANDS = [
stage,
experiments,
check_ignore,
live,
machine,
]
@@ -96,7 +93,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 +140,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 +206,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
-93
View File
@@ -1,93 +0,0 @@
import argparse
from pathlib import Path
from dvc.command import completion
from dvc.command.base import CmdBase, fix_subparsers
from dvc.ui import ui
class CmdLive(CmdBase):
UNINITIALIZED = True
def _run(self, target, revs=None):
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
index_path = render(self.repo, plots, metrics, html_path)
ui.write(index_path.as_uri())
return 0
return 1
class CmdLiveShow(CmdLive):
def run(self):
return self._run(self.args.target)
class CmdLiveDiff(CmdLive):
def run(self):
return self._run(self.args.target, self.args.revs)
def shared_parent_parser():
parent_parser = argparse.ArgumentParser(add_help=False)
parent_parser.add_argument(
"target", help="Logs dir to produce summary from"
).complete = completion.DIR
parent_parser.add_argument(
"-o",
"--out",
default=None,
help="Destination path to save plots to",
metavar="<path>",
).complete = completion.DIR
return parent_parser
def add_parser(subparsers, parent_parser):
LIVE_DESCRIPTION = (
"Commands to visualize and compare dvclive-produced logs."
)
live_parser = subparsers.add_parser(
"live",
parents=[parent_parser],
formatter_class=argparse.RawDescriptionHelpFormatter,
description=LIVE_DESCRIPTION,
)
live_subparsers = live_parser.add_subparsers(
dest="cmd",
help="Use `dvc live CMD --help` to display command-specific help.",
)
fix_subparsers(live_subparsers)
SHOW_HELP = "Visualize dvclive directory content."
live_show_parser = live_subparsers.add_parser(
"show",
parents=[parent_parser, shared_parent_parser()],
help=SHOW_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
live_show_parser.set_defaults(func=CmdLiveShow)
DIFF_HELP = (
"Show multiple versions of dvclive data, "
"by plotting it in single view."
)
live_diff_parser = live_subparsers.add_parser(
"diff",
parents=[parent_parser, shared_parent_parser()],
help=DIFF_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
live_diff_parser.add_argument(
"--revs",
nargs="*",
default=None,
help="Git revision (e.g. SHA, branch, tag)",
metavar="<commit>",
)
live_diff_parser.set_defaults(func=CmdLiveDiff)
+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 import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
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 import completion
from dvc.cli.utils import append_doc_link, fix_subparsers
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 import completion
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 operator
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
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 import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
logger = logging.getLogger(__name__)
+40
View File
@@ -0,0 +1,40 @@
import argparse
import logging
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.completion import PREAMBLE
from dvc.cli.utils import append_doc_link
from dvc.ui import ui
logger = logging.getLogger(__name__)
class CmdCompletion(CmdBaseNoRepo):
def run(self):
import shtab
shell = self.args.shell
parser = self.args.parser
script = shtab.complete(parser, shell=shell, preamble=PREAMBLE)
ui.write(script, force=True)
return 0
def add_parser(subparsers, parent_parser):
COMPLETION_HELP = "Generate shell tab completion."
COMPLETION_DESCRIPTION = "Prints out shell tab completion scripts."
completion_parser = subparsers.add_parser(
"completion",
parents=[parent_parser],
description=append_doc_link(COMPLETION_DESCRIPTION, "completion"),
help=COMPLETION_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
completion_parser.add_argument(
"-s",
"--shell",
help="Shell syntax for completions.",
default="bash",
choices=["bash", "zsh"],
)
completion_parser.set_defaults(func=CmdCompletion)
@@ -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 import completion
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import fix_subparsers
class CmdDaemonBase(CmdBaseNoRepo):
+44 -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
@@ -27,6 +28,32 @@ def _show_dot(G):
return dot_file.getvalue()
def _show_mermaid(G, markdown: bool = False):
from dvc.repo.graph import get_pipelines
pipelines = get_pipelines(G)
graph = "flowchart TD"
total_nodes = 0
for pipeline in pipelines:
node_ids = {}
nodes = sorted(str(x) for x in pipeline.nodes)
for node in nodes:
total_nodes += 1
node_id = f"node{total_nodes}"
graph += f"\n\t{node_id}[{node}]"
node_ids[node] = node_id
edges = sorted((str(a), str(b)) for b, a in pipeline.edges)
for a, b in edges:
graph += f"\n\t{node_ids[str(a)]}-->{node_ids[str(b)]}"
if markdown:
return f"```mermaid\n{graph}\n```"
return graph
def _collect_targets(repo, target, outs):
if not target:
return []
@@ -104,6 +131,8 @@ class CmdDAG(CmdBase):
if self.args.dot:
ui.write(_show_dot(G))
elif self.args.mermaid or self.args.markdown:
ui.write(_show_mermaid(G, self.args.markdown))
else:
with ui.pager():
ui.write(_show_ascii(G))
@@ -126,6 +155,20 @@ def add_parser(subparsers, parent_parser):
default=False,
help="Print DAG with .dot format.",
)
dag_parser.add_argument(
"--mermaid",
action="store_true",
default=False,
help="Print DAG with mermaid format.",
)
dag_parser.add_argument(
"--md",
"--show-md",
action="store_true",
default=False,
dest="markdown",
help="Print DAG with mermaid format wrapped in Markdown block.",
)
dag_parser.add_argument(
"--full",
action="store_true",
@@ -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 import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
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 import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
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 import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
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 import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
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)
+64
View File
@@ -0,0 +1,64 @@
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):
name_only = self.args.name_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 name_only:
print(f"{name}:")
for exp_name in exps[baseline]:
indent = "" if name_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(
"--name-only",
"--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 import completion
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 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 import completion
from dvc.cli.utils import append_doc_link
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>",
)
+655
View File
@@ -0,0 +1,655 @@
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 import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
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)
if "Experiment" in td:
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,
fetch_running=self.args.fetch_running,
)
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.add_argument(
"--no-fetch",
dest="fetch_running",
action="store_false",
help=argparse.SUPPRESS,
)
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 import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
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>",
)
+3 -3
View File
@@ -1,10 +1,11 @@
import argparse
import logging
from dvc.cli import completion
from dvc.cli.command import CmdBaseNoRepo
from dvc.exceptions import DvcException
from . import completion
from .base import CmdBaseNoRepo, append_doc_link
from ..cli.utils import 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 import completion
from dvc.cli.command import CmdBaseNoRepo
from dvc.exceptions import DvcException
from . import completion
from .base import CmdBaseNoRepo, append_doc_link
from ..cli.utils import 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 import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
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 import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
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__)
@@ -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 import completion
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
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 import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
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 import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
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 import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.exceptions import DvcException
from dvc.ui import ui
+142 -23
View File
@@ -1,16 +1,29 @@
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 import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
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
}
ui.write_json(result)
class CmdPlots(CmdBase):
def _func(self, *args, **kwargs):
raise NotImplementedError
@@ -25,6 +38,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 +51,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 +70,54 @@ 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
out: str = self.args.out or "dvc_plots"
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_out = (
out if self.args.json else os.path.join(out, "static")
)
renderers = match_renderers(
plots_data=plots_data, out=renderers_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
)
output_file: Path = (Path.cwd() / out).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 +153,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 +220,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 +256,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 +283,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 +343,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
@@ -113,8 +113,8 @@ class CmdRemoteDefault(CmdRemote):
class CmdRemoteList(CmdRemote):
def run(self):
conf = self.config.read(self.args.level)
for name, conf in conf["remote"].items():
ui.write(name, conf["url"], sep="\t")
for name, remote_conf in conf["remote"].items():
ui.write(name, remote_conf["url"], sep="\t")
return 0
@@ -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 import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
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 import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
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 import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.utils.cli_parse import parse_params
from dvc.utils.humanize import truncate_text
@@ -93,9 +94,7 @@ class CmdStageList(CmdBase):
self.repo.stage_collection_error_handler = log_error
stages = self._get_stages()
names_only = self.args.names_only
data = prepare_stages_data(stages, description=not names_only)
data = prepare_stages_data(stages, description=not self.args.name_only)
ui.table(data.items())
return 0
@@ -213,7 +212,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",
@@ -357,6 +356,7 @@ def add_parser(subparsers, parent_parser):
help="List all stages inside the specified directory.",
)
stage_list_parser.add_argument(
"--name-only",
"--names-only",
action="store_true",
default=False,
@@ -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 import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
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 import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
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,
+4 -3
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
@@ -106,8 +106,9 @@ def daemon(args):
cmd = ["daemon", "-q"] + args
env = fix_env()
file_path = os.path.abspath(inspect.stack()[0][1])
env["PYTHONPATH"] = os.path.dirname(os.path.dirname(file_path))
if not is_binary():
file_path = os.path.abspath(inspect.stack()[0][1])
env["PYTHONPATH"] = os.path.dirname(os.path.dirname(file_path))
env[DVC_DAEMON] = "1"
_spawn(cmd, env)
+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
+33
View File
@@ -0,0 +1,33 @@
from dvc.progress import Tqdm
class QueryingProgress(Tqdm):
def __init__(self, iterable=None, total=None, name=None, phase="Querying"):
msg_part = "cache in " + f"'{name}'" if name else "remote cache"
msg_fmt = "{phase} " + msg_part
self._estimating_msg = msg_fmt.format(phase="Estimating size of")
self._listing_msg = msg_fmt.format(phase="Querying")
self.desc = desc = msg_fmt.format(phase=phase)
super().__init__(
iterable=iterable,
desc=desc,
total=total,
unit="files",
unit_scale=False,
bar_format=self.BAR_FMT_DEFAULT,
)
def callback(self, phase, *args):
total = args[0] if args else self.total
completed = args[1] if len(args) > 1 else self.n
desc = self.desc
if phase == "estimating":
desc = self._estimating_msg
elif phase == "querying":
desc = self._listing_msg
if desc != self.desc:
# let Tqdm take care of when to update
self.set_description_str(desc, refresh=False)
self.update_to(completed, total)
@@ -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)
@@ -46,7 +39,6 @@ def get_index(odb) -> "ObjectDBIndexBase":
hashlib.sha256(
odb.fs.unstrip_protocol(odb.fs_path).encode("utf-8")
).hexdigest(),
odb.fs.CHECKSUM_DIR_SUFFIX,
)
@@ -1,9 +1,10 @@
import logging
import os
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Iterable, Optional, Set
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
@@ -14,7 +15,9 @@ logger = logging.getLogger(__name__)
class ObjectDBIndexBase(ABC):
@abstractmethod
def __init__(
self, tmp_dir: "StrPath", name: str, dir_suffix: Optional[str] = None
self,
tmp_dir: "StrPath",
name: str,
):
pass
@@ -50,7 +53,9 @@ class ObjectDBIndexNoop(ObjectDBIndexBase):
"""No-op class for ODBs which are not indexed."""
def __init__(
self, tmp_dir: "StrPath", name: str, dir_suffix: Optional[str] = None
self,
tmp_dir: "StrPath",
name: str,
): # pylint: disable=super-init-not-called
pass
@@ -80,9 +85,11 @@ class ObjectDBIndex(ObjectDBIndexBase):
INDEX_DIR = "index"
def __init__(
self, tmp_dir: "StrPath", name: str, dir_suffix: Optional[str] = None
self,
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
@@ -90,25 +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)
if not dir_suffix:
dir_suffix = self.fs.CHECKSUM_DIR_SUFFIX
self.dir_suffix = dir_suffix
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)
def is_dir_hash(self, hash_: str):
return hash_.endswith(self.dir_suffix)
@with_diskcache(name="index")
def clear(self):
"""Clear this index (to force re-indexing later)."""
from diskcache import Timeout
@@ -118,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
@@ -132,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())
@@ -1,18 +1,17 @@
import logging
import os
import stat
from functools import partial
from funcy import cached_property
from shortuuid import uuid
from dvc.hash_info import HashInfo
from dvc.objects.db import ObjectDB, noop, wrap_iter
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__)
@@ -61,15 +60,12 @@ class LocalObjectDB(ObjectDB):
return f"{self.cache_path}{os.sep}{hash_[0:2]}{os.sep}{hash_[2:]}"
def hashes_exist(
self, hashes, jobs=None, name=None
self, hashes, jobs=None, progress=noop
): # pylint: disable=unused-argument
ret = []
progress = partial(progress, "querying", len(hashes))
for hash_ in Tqdm(
hashes,
unit="file",
desc="Querying " + ("cache in " + name if name else "local cache"),
):
for hash_ in wrap_iter(hashes, progress):
hash_info = HashInfo(self.fs.PARAM_CHECKSUM, hash_)
try:
self.check(hash_info)
@@ -79,7 +75,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 +86,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_)
@@ -1,18 +1,16 @@
import io
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
from dvc.fs.base import AnyFSPath, FileSystem
from dvc.hash_info import HashInfo
from dvc.types import AnyPath
logger = logging.getLogger(__name__)
@@ -38,10 +36,10 @@ class ReferenceObjectDB(ObjectDB):
pass
fs_path = self.hash_to_path(hash_info.value)
try:
with self.fs.open(fs_path, "rb") as fobj:
ref_file = ReferenceHashFile.from_bytes(
fobj.read(), fs_cache=self._fs_cache
)
ref_file = ReferenceHashFile.from_bytes(
self.fs.cat_file(fs_path),
fs_cache=self._fs_cache,
)
except OSError:
raise FileNotFoundError
try:
@@ -55,8 +53,8 @@ class ReferenceObjectDB(ObjectDB):
def _add_file(
self,
from_fs: "FileSystem",
from_info: "AnyPath",
to_info: "AnyPath",
from_info: "AnyFSPath",
to_info: "AnyFSPath",
hash_info: "HashInfo",
hardlink: bool = False,
):
@@ -71,10 +69,8 @@ class ReferenceObjectDB(ObjectDB):
)
ref_file = ReferenceHashFile(from_info, from_fs, hash_info)
self._obj_cache[hash_info] = ref_file
ref_fobj = io.BytesIO(ref_file.to_bytes())
ref_fobj.seek(0)
try:
self.fs.upload(ref_fobj, to_info)
self.fs.pipe_file(to_info, ref_file.to_bytes())
except OSError as exc:
if isinstance(exc, FileExistsError) or (
os.name == "nt"
+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
from ._progress import QueryingProgress
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
hashes = QueryingProgress(odb.all(jobs), name=odb.fs_path)
for hash_ in sorted(hashes, 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,15 +4,13 @@ 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.fs.base import AnyFSPath, FileSystem
from dvc.hash_info import HashInfo
from dvc.types import AnyPath
from .db.base import ObjectDB
from dvc.objects.db import ObjectDB
logger = logging.getLogger(__name__)
@@ -25,7 +23,7 @@ class ReferenceHashFile(HashFile):
def __init__(
self,
fs_path: "AnyPath",
fs_path: "AnyFSPath",
fs: "FileSystem",
hash_info: "HashInfo",
checksum: Optional[str] = None,
@@ -53,17 +51,12 @@ class ReferenceHashFile(HashFile):
return self.fs.checksum(self.fs_path)
def to_bytes(self):
from dvc.fs.repo import RepoFileSystem
# NOTE: dumping reference FS's this way is insecure, as the
# fully parsed remote FS config will include credentials
#
# ReferenceHashFiles should currently only be serialized in
# memory and not to disk
fs_path = self.fs_path
if isinstance(self.fs, RepoFileSystem):
fs_path = self.fs.path.relpath(fs_path, self.fs.root_dir)
dict_ = {
self.PARAM_PATH: fs_path,
self.PARAM_HASH: self.hash_info,
@@ -78,7 +71,7 @@ class ReferenceHashFile(HashFile):
@classmethod
def from_bytes(cls, data: bytes, fs_cache: Optional[dict] = None):
from dvc.fs import get_fs_cls
from dvc.fs.repo import RepoFileSystem
from dvc.fs.repo import RepoFileSystem, _RepoFileSystem
try:
dict_ = pickle.loads(data)
@@ -95,12 +88,11 @@ class ReferenceHashFile(HashFile):
fs = fs_cache.get((scheme, config_pairs)) if fs_cache else None
if not fs:
config = dict(config_pairs)
if RepoFileSystem.PARAM_REPO_URL in config:
fs = RepoFileSystem(**config)
fs_path = fs.path.join(fs.root_dir, fs_path)
if _RepoFileSystem.PARAM_REPO_URL in config:
fs_cls = RepoFileSystem
else:
fs_cls = get_fs_cls(config, scheme=scheme)
fs = fs_cls(**config)
fs = fs_cls(**config)
return ReferenceHashFile(
fs_path,
fs,
+20 -16
View File
@@ -8,18 +8,16 @@ 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.types import AnyPath
from .db.base import ObjectDB
from dvc.fs.base import AnyFSPath, FileSystem
from dvc.objects.db import ObjectDB
logger = logging.getLogger(__name__)
@@ -27,18 +25,23 @@ logger = logging.getLogger(__name__)
_STAGING_MEMFS_PATH = "dvc-staging"
def _upload_file(from_fs_path, fs, odb, upload_odb):
def _upload_file(from_fs_path, fs, odb, upload_odb, callback=None):
from dvc.fs._callback import FsspecCallback
from dvc.utils import tmp_fname
from dvc.utils.stream import HashedStreamReader
fs_path = upload_odb.fs.path
tmp_info = fs_path.join(upload_odb.fs_path, tmp_fname())
with fs.open(from_fs_path, mode="rb", chunk_size=fs.CHUNK_SIZE) as stream:
with fs.open(from_fs_path, mode="rb") as stream:
stream = HashedStreamReader(stream)
size = fs.getsize(from_fs_path)
upload_odb.fs.upload(
stream, tmp_info, desc=fs_path.name(from_fs_path), total=size
)
size = fs.size(from_fs_path)
with FsspecCallback.as_tqdm_callback(
callback,
desc=fs_path.name(from_fs_path),
bytes=True,
total=size,
) as cb:
upload_odb.fs.upload(stream, tmp_info, size=size, callback=cb)
odb.add(tmp_info, upload_odb.fs, stream.hash_info)
meta = Meta(size=size)
@@ -73,7 +76,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 +172,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 +248,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
@@ -287,7 +291,7 @@ def _stage_external_tree_info(odb, tree, name):
def stage(
odb: "ObjectDB",
fs_path: "AnyPath",
fs_path: "AnyFSPath",
fs: "FileSystem",
name: str,
upload: bool = False,
+31 -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,20 @@ 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
from ._progress import QueryingProgress
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))
hashes = QueryingProgress(
odb.list_hashes_exists(indexed_dirs, jobs=jobs),
total=len(indexed_dirs),
)
indexed_dir_exists.update(hashes)
missing_dirs = indexed_dirs.difference(indexed_dir_exists)
if missing_dirs:
logger.debug(
@@ -45,7 +52,13 @@ 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_missing = dir_hashes - dir_exists
dir_exists.update(
QueryingProgress(
odb.list_hashes_exists(dir_missing, jobs=jobs),
total=len(dir_missing),
)
)
# If .dir hash exists in the ODB, assume directory contents
# also exists
@@ -75,6 +88,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 +134,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 +144,12 @@ def status(
hashes.difference_update(exists)
if hashes:
exists.update(odb.hashes_exist(hashes, name=odb.fs_path, **kwargs))
from ._progress import QueryingProgress
with QueryingProgress(phase="Checking", name=odb.fs_path) as pbar:
exists.update(
odb.hashes_exist(hashes, jobs=jobs, progress=pbar.callback)
)
return StatusResult(
{hash_infos[hash_] for hash_ in exists},
{hash_infos[hash_] for hash_ in (hashes - exists)},
@@ -143,6 +164,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 +178,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
+75 -9
View File
@@ -1,22 +1,44 @@
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__)
class TreeError(Exception):
pass
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 +47,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)
@@ -40,8 +62,7 @@ class Tree(HashFile):
memfs = MemoryFileSystem()
fs_path = "memory://{}".format(tmp_fname(""))
with memfs.open(fs_path, "wb") as fobj:
fobj.write(self.as_bytes())
memfs.pipe_file(fs_path, self.as_bytes())
self.fs = memfs
self.fs_path = fs_path
if hash_info:
@@ -51,6 +72,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 +175,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,18 +193,39 @@ 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
tree.digest()
return tree
def ls(self, prefix=None):
kwargs = {}
if prefix:
kwargs["prefix"] = prefix
meta, hash_info = self._trie.get(prefix, (None, None))
if hash_info and hash_info.isdir and meta and not meta.obj:
raise TreeError
ret = []
def node_factory(_, key, children, *args):
if key == prefix:
list(children)
else:
ret.append(key[-1])
self._trie.traverse(node_factory, **kwargs)
return ret
def du(odb, tree):
try:
return sum(
odb.fs.getsize(odb.hash_to_path(oid.value)) for _, _, oid in tree
odb.fs.size(odb.hash_to_path(oid.value)) for _, _, oid in tree
)
except FileNotFoundError:
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()
+13 -13
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,10 @@ 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, TreeError
from dvc.exceptions import NoOutputOrStageError, PathMissingError
from dvc.objects.stage import stage
from dvc.objects.tree import Tree
from dvc.utils import as_posix
local_odb = self.repo.odb.local
locked = kwargs.pop("locked", True)
@@ -112,11 +113,10 @@ class RepoDependency(Dependency):
if locked and self.def_repo.get(self.PARAM_REV_LOCK) is None:
self.def_repo[self.PARAM_REV_LOCK] = rev
path = os.path.abspath(os.path.join(repo.root_dir, self.def_path))
if not obj_only:
try:
for odb, obj_ids in repo.used_objs(
[path],
[os.path.join(repo.root_dir, self.def_path)],
force=True,
jobs=kwargs.get("jobs"),
recursive=True,
@@ -132,11 +132,11 @@ class RepoDependency(Dependency):
try:
staging, _, staged_obj = stage(
local_odb,
path,
as_posix(self.def_path),
repo.repo_fs,
local_odb.fs.PARAM_CHECKSUM,
)
except FileNotFoundError as exc:
except (FileNotFoundError, TreeError) as exc:
raise PathMissingError(
self.def_path, self.def_repo[self.PARAM_URL]
) from exc
@@ -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
@@ -172,17 +172,17 @@ class RepoDependency(Dependency):
continue
if (
obj.fs.repo_url in checked_urls
or obj.fs.root_dir in checked_urls
or obj.fs.repo.root_dir in checked_urls
):
continue
self_url = self.repo.url or self.repo.root_dir
if (
obj.fs.repo_url is not None
and obj.fs.repo_url == self_url
or obj.fs.root_dir == self.repo.root_dir
or obj.fs.repo.root_dir == self.repo.root_dir
):
raise CircularImportError(self, obj.fs.repo_url, self_url)
checked_urls.update([obj.fs.repo_url, obj.fs.root_dir])
checked_urls.update([obj.fs.repo_url, obj.fs.repo.root_dir])
def get_obj(self, filter_info=None, **kwargs):
locked = kwargs.get("locked", True)
+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(
+44 -22
View File
@@ -3,7 +3,7 @@ import os
import tempfile
import threading
from contextlib import contextmanager
from typing import Dict
from typing import TYPE_CHECKING, Dict
from funcy import retry, wrap_with
@@ -19,6 +19,9 @@ from dvc.repo import Repo
from dvc.scm import CloneError, map_scm_exception
from dvc.utils import relpath
if TYPE_CHECKING:
from scmrepo import Git
logger = logging.getLogger(__name__)
@@ -30,6 +33,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 +49,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 +96,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 +204,12 @@ 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)
_pull(git, unshallow=True)
shallow = False
CLONES[url] = (clone_path, shallow)
else:
logger.debug("erepo: git pull '%s'", url)
git.pull()
_pull(git)
else:
from dvc.scm import clone
@@ -210,12 +220,17 @@ 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
git_dir = os.path.join(clone_path, ".git")
if os.path.exists(git_dir):
_remove(git_dir)
if not git:
git = clone(url, clone_path)
shallow = False
@@ -227,17 +242,24 @@ 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 _pull(git: "Git", unshallow: bool = False):
from dvc.repo.experiments.utils import fetch_all_exps
git.fetch(unshallow=unshallow)
_merge_upstream(git)
fetch_all_exps(git, "origin")
def _merge_upstream(git: "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):
+6 -1
View File
@@ -114,7 +114,12 @@ def get_cloud_fs(repo, **kwargs):
remote_conf["gdrive_credentials_tmp_dir"] = repo.tmp_dir
url = remote_conf.pop("url")
fs_path = cls._strip_protocol(url) # pylint:disable=protected-access
if issubclass(cls, WebDAVFileSystem):
# For WebDAVFileSystem, provided url is the base path itself, so it
# should be treated as being a root path.
fs_path = cls.root_marker
else:
fs_path = cls._strip_protocol(url) # pylint:disable=protected-access
extras = cls._get_kwargs_from_urls(url) # pylint:disable=protected-access
conf = {**extras, **remote_conf} # remote config takes priority
+190
View File
@@ -0,0 +1,190 @@
from functools import wraps
from typing import IO, TYPE_CHECKING, Any, Optional, TypeVar, cast
import fsspec
if TYPE_CHECKING:
from typing import Callable
from typing_extensions import ParamSpec
from dvc.fs.base import FileSystem
from dvc.progress import Tqdm
from dvc.ui._rich_progress import RichTransferProgress
_P = ParamSpec("_P")
_R = TypeVar("_R")
class FsspecCallback(fsspec.Callback):
"""FsspecCallback usable as a context manager, and a few helper methods."""
def wrap_attr(self, fobj: IO, method: str = "read") -> IO:
from tqdm.utils import CallbackIOWrapper
wrapped = CallbackIOWrapper(self.relative_update, fobj, method)
return cast(IO, wrapped)
def wrap_fn(self, fn: "Callable[_P, _R]") -> "Callable[_P, _R]":
@wraps(fn)
def wrapped(*args: "_P.args", **kwargs: "_P.kwargs") -> "_R":
res = fn(*args, **kwargs)
self.relative_update()
return res
return wrapped
def wrap_and_branch(
self, fn: "Callable", fs: "FileSystem" = None
) -> "Callable":
"""
Wraps a function, and pass a new child callback to it.
When the function completes, we increment the parent callback by 1.
"""
from .local import localfs
fs = fs or localfs
wrapped = self.wrap_fn(fn)
def make_callback(path1, path2):
# pylint: disable=assignment-from-none
child = self.branch(fs.path.name(path1), path2, {})
return self.as_callback(child)
@wraps(fn)
def func(path1, path2, **kwargs):
with make_callback(path1, path2) as callback:
return wrapped(path1, path2, callback=callback, **kwargs)
return func
def __enter__(self):
return self
def __exit__(self, *exc_args):
self.close()
def close(self):
"""Handle here on exit."""
@classmethod
def as_callback(
cls, maybe_callback: Optional["FsspecCallback"] = None
) -> "FsspecCallback":
if maybe_callback is None:
return DEFAULT_CALLBACK
return maybe_callback
@classmethod
def as_tqdm_callback(
cls,
callback: Optional["FsspecCallback"] = None,
**tqdm_kwargs: Any,
) -> "FsspecCallback":
return callback or TqdmCallback(**tqdm_kwargs)
@classmethod
def as_rich_callback(
cls, callback: Optional["FsspecCallback"] = None, **rich_kwargs
):
return callback or RichCallback(**rich_kwargs)
class NoOpCallback(FsspecCallback, fsspec.callbacks.NoOpCallback):
pass
class TqdmCallback(FsspecCallback):
def __init__(self, progress_bar: "Tqdm" = None, **tqdm_kwargs):
from dvc.progress import Tqdm
self.progress_bar = progress_bar or Tqdm(**tqdm_kwargs)
super().__init__()
def __enter__(self):
self.progress_bar.__enter__()
return self
def close(self):
self.progress_bar.close()
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)
def branch(self, path_1: str, path_2: str, kwargs):
return TqdmCallback(bytes=True, total=-1, desc=path_1, **kwargs)
class RichCallback(FsspecCallback):
def __init__(
self,
progress: "RichTransferProgress" = None,
desc: str = None,
total: int = None,
bytes: bool = False, # pylint: disable=redefined-builtin
unit: str = None,
disable: bool = False,
) -> None:
from dvc.ui import ui
from dvc.ui._rich_progress import RichTransferProgress
self.progress = progress or RichTransferProgress(
transient=True,
disable=disable,
console=ui.error_console,
)
self.visible = not disable
self._newly_created = progress is None
total = 0 if total is None or total < 0 else total
self.task = self.progress.add_task(
description=desc or "",
total=total,
bytes=bytes,
visible=False,
unit=unit,
progress_type=None if bytes else "summary",
)
super().__init__()
def __enter__(self):
if self._newly_created:
self.progress.__enter__()
return self
def close(self):
if self._newly_created:
self.progress.stop()
try:
self.progress.remove_task(self.task)
except KeyError:
pass
def set_size(self, size: int = None) -> None:
if size is not None:
self.progress.update(self.task, total=size, visible=self.visible)
super().set_size(size)
def relative_update(self, inc: int = 1) -> None:
self.progress.update(self.task, advance=inc)
super().relative_update(inc)
def absolute_update(self, value: int) -> None:
self.progress.update(self.task, completed=value)
super().absolute_update(value)
def branch(self, path_1, path_2, kwargs):
return RichCallback(self.progress, desc=path_1, bytes=True, total=-1)
DEFAULT_CALLBACK = NoOpCallback()

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