Compare commits

...
945 Commits
Author SHA1 Message Date
Ruslan Kuprieiev 13d1276a12 dvc: bump to 2.6.4 2021-08-20 20:00:55 +03:00
Ruslan Kuprieiev 7da20a7c74 setup: upgrade s3fs
Fixes https://github.com/iterative/dvc/issues/6466
2021-08-20 19:43:14 +03:00
Paweł Redzyński a7175501aa ruamel.yaml: adjust to new DuplicatedKeyError (#6464) 2021-08-19 20:02:16 +03:00
GaoandPeter Rowlands 57899ee86d Remove all of the experiments in the workspace (#6394)
* Remove all of the experiments in the workspace

fix #5676
1. remove all the experiments in workspace
2. add tests for it

* Only remove baseline ones

* Add a new arguments --all

* Update dvc/repo/experiments/remove.py

Co-authored-by: Peter Rowlands (변기호) <[email protected]>

* Rename and shortcut for these commands

* Solve the issue from removing a changing list.

* Update dvc/repo/experiments/remove.py

Co-authored-by: Peter Rowlands (변기호) <[email protected]>

* Update dvc/repo/experiments/remove.py

Co-authored-by: Peter Rowlands (변기호) <[email protected]>

* delete workspace option

Co-authored-by: Peter Rowlands (변기호) <[email protected]>
2021-08-19 10:18:51 +09:00
Saugat Pachhai 4b9a4114fe exp run: pass recursive args to checkout (#6458) 2021-08-18 15:17:33 +05:45
Ruslan Kuprieiev 82e559a51a dvc: bump to 2.6.3 2021-08-17 15:17:48 +03:00
Ruslan Kuprieiev 33c6a9b19b analytics: introduce DVC_NO_ANALYTICS (#6453)
Replacing DVC_ANALYTICS. This is following our other env conventions and
will not have effect if set to false/no/etc, and will only work if set
to true/yes/y/etc. Will disable analytics even if it is explicitly
enabled in our configs.

Kudos @skshetry
2021-08-17 15:16:40 +03:00
Saugat Pachhai 14cc748139 Increase coverage target to 90%. 2021-08-17 12:23:35 +05:45
David de la Iglesia Castro 40abf34c8c Update dvc exp branch help (#6439) 2021-08-17 11:14:44 +09:00
Ruslan Kuprieiev 9fef38e778 dvc: bump to 2.6.2 2021-08-17 02:52:07 +03:00
Batuhan Taskaya b44e1c3646 fetched: correctly order mixed odbs (#6436) 2021-08-17 02:51:08 +03:00
Ruslan Kuprieiev c9b3d9dbae dvc: bump to 2.6.1 2021-08-17 01:49:06 +03:00
Ruslan Kuprieiev c2bf0dc713 analytics: add DVC_ANALYTICS env var (#6448) 2021-08-17 01:48:38 +03:00
Saugat Pachhai 451bd3ebc8 Change ui messaging for the updater (#6437) 2021-08-16 18:31:44 +03:00
Arjun Vikram 41bc81910f plots: upgrade to latest Vega library versions (#6371) 2021-08-16 16:22:09 +02:00
Ruslan Kuprieiev 0aec0e3249 updater: only notify in dvc doctor (#6247)
We've used updater for years now to notify our users ASAP about new
releases. These days dvc is more stable and we've actually been
receiving numerous complains about updater being too annoying and
even introduced a config flag to turn it off, which a lot of people do
and which defeats its purpose. Instead of introducing some complex logic
that would notify more rarely and once for the user instead of once for
every dvc project they have - this PR just turns it off for everything,
except `dvc doctor/version`, which is the command that people use when
they are running into issues and that will give them a hint to just try
out the newest version first, as it might be already fixed.

This PR is a more of a compromise, to get the most out of updater while
removing a pain point of it being too annoying, without investing much
time into it and not having to maintain complex unnecessary logic. We
might get back to introducing something more intricate in the future,
if the need arises.

Fixes #5912
2021-08-16 17:28:49 +05:45
Batuhan Taskaya 8406e605ab fs: find(..., detail=True) now casts values properly (#6432)
* fs: find(..., detail=True) now casts values properly

* bump pydrive2
2021-08-16 14:26:07 +03:00
Saugat Pachhai 1bb7f39e14 add support for Python 3.10 (#6422)
* add support for Python 3.10

pyarrow is not available, so we are skipping tests for it.
And, restricting `hdfs` extras to be not installed in Python3.10.

Pathlib removed `_init` method and `init` parameter, so just working
around that. It's not a big change.

* skip windows on 3.10; update pip/wheel/setuptools during install

pywin32 is not yet available for 3.10 Python, so skipping those for now

* install libgit2 with Python3.10 as pygit2 wheel is not available

* install libgit2

* only run on macOS, libgit2-1.1 not available on ubuntu
2021-08-16 16:24:28 +05:45
dependabot[bot] a285d2ba47 build(deps): Bump pygments from 2.9.0 to 2.10.0 (#6430)
Bumps [pygments](https://github.com/pygments/pygments) from 2.9.0 to 2.10.0.
- [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.9.0...2.10.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-16 10:03:34 +03:00
Ruslan Kuprieiev 13c1314099 dvc: bump to 2.6.0 2021-08-15 17:39:36 +03:00
Ruslan Kuprieiev 27b54198a4 pyinstaller: build: check dvc doctor output (#6425) 2021-08-15 17:30:17 +03:00
Saugat Pachhai a7f01b65a5 Unpin networkx
Networkx v2.6.2 now does not require any additional requirements. All of the other 2.6 releases have been yanked. So, it's safe for us to unpin it.
2021-08-13 14:53:10 +05:45
Gao de97a49401 Remove a special queued experiments (#6393)
* Remove a special queued experiments

fix #6157
1. dvc exp remove not accept queued experiments name
2. add some tests for this feature

* Extract tests and add revision support

1. Extract remove experiments to a new file.
2. revision can be used to remove special queued experiment

* Accept shortened revisions

* Split removing committed and queued exp functions

* Api name change

* Return to the old API name

* shorten some functions

* Error message change

* Better test cases, more corner case

* Still raise exception in a mixed case
2021-08-13 15:15:21 +08:00
dependabot[bot]andBatuhan Taskaya 4c957bf4ab build(deps): bump google-cloud-storage from 1.41.1 to 1.42.0 (#6415)
Bumps [google-cloud-storage](https://github.com/googleapis/python-storage) from 1.41.1 to 1.42.0.
- [Release notes](https://github.com/googleapis/python-storage/releases)
- [Changelog](https://github.com/googleapis/python-storage/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-storage/compare/v1.41.1...v1.42.0)

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

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

Co-authored-by: Batuhan Taskaya <[email protected]>
2021-08-12 13:15:34 +03:00
6dd435eefe Push running checkpoint to remote (#6332)
* Push running checkpoint to remote

Fix #6182

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

Co-authored-by: Peter Rowlands (변기호) <[email protected]>

* Get the branch name

* Finish this PR

1. move env name to dvc/env.py.
2. add some tests for it.

* Update after finally commit

* Some problems found in the review

1. change the behavior of self remote
2. do not use string DVC_EXP_AUTO_PUSH
3. downgrade the logger level in auto push
4. use full branch ref

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

Co-authored-by: Peter Rowlands (변기호) <[email protected]>

* add a remote check before auto push

* Split current one env into two

* Rename DVC_EXP_CHECKPOINT_PUSH to DVC_EXP_AUTO_PUSH

* value error

* Remove getenv_bool switch to env2bool

* Better on handling Dulwich exceptions

* Name changing removing prints

* Update dvc/env.py

Co-authored-by: Peter Rowlands (변기호) <[email protected]>

* Some changes in review

* Add validation to repo URL

* Add new type of SCMError

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

Co-authored-by: Peter Rowlands (변기호) <[email protected]>

* Use spy tests call counts and args

* Update dvc/scm/git/backend/base.py

Co-authored-by: Peter Rowlands (변기호) <[email protected]>

* Update dvc/scm/git/backend/dulwich.py

Co-authored-by: Peter Rowlands (변기호) <[email protected]>

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

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

* Remove validate_git_repo to validate_git_remote

Co-authored-by: Peter Rowlands (변기호) <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2021-08-12 15:17:08 +08:00
Saugat Pachhai fb96b13f13 Fix issue while sending analytics report (#6407)
* demonstrate issue while sending analytics report

When sending analytics, we are not flushing the data to the temp file
which causes failure at times, especially on Windows

* close file before calling daemonized analytics send command

This way, the contents bufferred will get a chance to be flushed.
2021-08-10 16:07:46 +03:00
Peter Rowlands (변기호) 6c646a7070 pygit: account for locked dirs on windows during checkout (#6408) 2021-08-10 15:18:32 +03:00
dependabot[bot] 955aedee13 build(deps): bump jaraco-windows from 5.5.0 to 5.6.0 (#6396)
Bumps [jaraco-windows](https://github.com/jaraco/jaraco.windows) from 5.5.0 to 5.6.0.
- [Release notes](https://github.com/jaraco/jaraco.windows/releases)
- [Changelog](https://github.com/jaraco/jaraco.windows/blob/main/CHANGES.rst)
- [Commits](https://github.com/jaraco/jaraco.windows/compare/v5.5.0...v5.6.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-10 15:16:15 +03:00
Ruslan Kuprieiev e99e6cd367 repofs: use underlying fs.download to download files (#6401) 2021-08-10 13:12:56 +03:00
Batuhan Taskaya 1f8278252a gdrive: migrate to pydrive2.fs (#6399) 2021-08-10 12:59:44 +03:00
dependabot[bot] 38cd7d01c2 build(deps): bump wheel from 0.36.2 to 0.37.0 (#6404)
Bumps [wheel](https://github.com/pypa/wheel) from 0.36.2 to 0.37.0.
- [Release notes](https://github.com/pypa/wheel/releases)
- [Changelog](https://github.com/pypa/wheel/blob/master/docs/news.rst)
- [Commits](https://github.com/pypa/wheel/compare/0.36.2...0.37.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-10 08:35:38 +05:45
Maxime RischardandRuslan Kuprieiev 63b710b7c0 More granular read-write lock requests in the run() and reproduce() methods of the Stage class (#5815)
* Remove rwlock decorator for the run() method of the Stage class.

Instead of pre-emptively grabbing read locks on the dependencies
and write locks on the outputs when calling the run() method,
delegate the locking mechanism to other methods.
This results in more selective locking that is less likely
to cause a LockError when executing steps in parallel.

* Remove rwlocked decorator from reproduce() method of Stage class.

* Reinstate read-lock (not write) for run method of Stage class

* fix bugs and typos

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2021-08-10 01:49:21 +03:00
Ruslan Kuprieiev d97a19e97d refodb: cache ref objects (#6392)
* refodb: cache ref objects

* odb: ignore FileNotFoundError when removing
2021-08-09 13:53:38 +03:00
Saugat Pachhai fd76e0991f cleanup index, and add test for used_objs (#6390) 2021-08-06 15:35:01 +05:45
Gao 3d604a8455 Show name of experiments in the queue (#6382)
* Show name of stashed experiments

* Add tests for it

* Reorder the stash and commit rev name
2021-08-06 11:02:16 +09:00
Saugat Pachhai 6c33fbb617 introduce index (#6300)
* introduce index

* migrate tests to use index

* migrate checkout/diff to use index.outs

* test for index

* fix typings

* add few more tests
2021-08-05 14:23:40 +03:00
Nikita KodenkoandPeter Rowlands 3be395b3a7 Unittest to pytest: moved few unittest tests to pytest (#6379)
* tests: rm TestGit in func/test_init.py

* tests: func: rm TestSCMGit in test_scm.py

* test: func: rm _check_dirs in test_init.py

* tests: func: minor fixes in test_scm.py

* Update tests/func/test_scm.py

Co-authored-by: Peter Rowlands (변기호) <[email protected]>

Co-authored-by: Peter Rowlands (변기호) <[email protected]>
2021-08-04 15:11:09 +03:00
Peter Rowlands (변기호) 114a07e7a4 objects: use object IDs and references instead of naive objs in status/transfer (#6360)
* objects.status: use object IDs instead of naive objects

* objects.transfer: use object IDs instead of naive objects

* objects: add ReferenceHashFile

* odb: add ReferenceObjectDB

* repo: collect used object IDs instead of naive objs

* support basic/naive fs serialization

* objects.stage: stage file objects in addition to trees

* objects: remove save(), update transfer()

* output/dep: update for osave removal

* path_info: support both url and path attributes

* status/gc: use object IDs instead of naive objs

* diff: update staging usage

* commit: update staging usage

* tests: clean staging when needed

* tests: update for save/transfer/stage changes

* dvcfs: don't allow config serialization (repofs should be used instead)

* utils: return mtime as str, size as int

* tests: update unit tests for save/stage/transfer changes

* objects: use Repo.open when needed in ReferenceHashFile

* fix ls test staging

* objects.stage: don't skip hash check when loading from state

- individual hashes will still be loaded from state if they are still
  valid

* ReferenceHashFile: serialize repo fs paths relative to repo root

* preserve state when staging

* objects.tree: skip hash computation in digest() when we have already
loaded hash from state

* always check file existence in referencehashfile

* update repofs test since stage no longer allows nonexistent files

* odb: only protect on check() if hash was verified

* staging: use unique memfs namespace for staging per dest ODB

* tests: clean full staging namespace

* refodb: preserve repofs cache dir

* repodependency: stage imports to local ODB and strict check circular
imports

* fetch: fetch staging last

* staging: make dest odb mandatory and preserve state

* refodb: cache nonlocal fs's

* fix rebase errors

* repofs: support full instantiation from fs config

* repofs: preserve subrepo factory for erepos

* tests: clarify repofs dirty file behavior
2021-08-04 13:55:52 +03:00
Peter Rowlands (변기호) 34697ef298 exp gc: fix --queued warning message (#6381) 2021-08-03 10:37:38 +00:00
Saugat Pachhai 1eccfa9d33 benedict: cleanup code and add tests (#6380) 2021-08-03 12:28:42 +05:45
dependabot[bot]andPeter Rowlands 6c14bf39dc build(deps): bump pylint from 2.8.3 to 2.9.6 (#6369)
* build(deps): bump pylint from 2.8.3 to 2.9.6

Bumps [pylint](https://github.com/PyCQA/pylint) from 2.8.3 to 2.9.6.
- [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.8.3...v2.9.6)

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

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

* pylint: fix 2.9.6 issues

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Peter Rowlands <[email protected]>
2021-08-03 10:29:55 +09:00
Saugat Pachhai 7444696dcd Fix merging of params when empty (#6378)
* Fix merging of params when empty

Recent release of python-benedict==0.24.1 started ignoring empty dictionaries
during merge. The way we expected to work was questionable and also how
benedict itself tries to handle the references to empty dictionary is
also questionable.

I consider this to be a proper way to handle this even if this gets fixed
on the benedict side.

* Revert "setup: limit python-benedict version (#6377)"

This reverts commit 15de826266.

* add comment for why passing through benedict
2021-08-02 22:45:04 +03:00
pre-commit-ci[bot] 76be53bc3e [pre-commit.ci] pre-commit autoupdate (#6376)
updates:
- [github.com/timothycrosley/isort: 5.9.2 → 5.9.3](https://github.com/timothycrosley/isort/compare/5.9.2...5.9.3)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2021-08-02 21:46:35 +03:00
Ruslan Kuprieiev 8d71c3861f fs: raise RemoteNotFoundError (#6375) 2021-08-02 21:09:55 +03:00
Ruslan Kuprieiev 15de826266 setup: limit python-benedict version (#6377)
Temporary fix for #6374 Kudos @skshetry
2021-08-02 20:44:03 +03:00
Batuhan Taskaya 9634efeb84 ssh: handle concurrency issues for raw command execution (#6370)
* ssh: handle concurrency issues for raw command execution

* setup: bump sshfs
2021-07-29 14:40:49 +03:00
Ruslan Kuprieiev e3a541aa00 setup: relax aiobotocore requirements (#6367)
Fixes #6366
2021-07-28 16:19:16 +03:00
Batuhan Taskaya 9b4b6e5b31 ssh: migrate to sshfs (#6295) 2021-07-28 12:49:53 +03:00
dependabot[bot] b294bca383 build(deps): bump codecov/codecov-action from 2.0.1 to 2.0.2 (#6361)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 2.0.1 to 2.0.2.
- [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.0.1...v2.0.2)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-28 10:21:10 +03:00
Batuhan Taskaya 4a843dbd61 odb: use regular upload() when the source filesystem is local (#6365)
* odb: use regular upload() when the source filesystem is local

* support download_file too
2021-07-27 15:55:49 +03:00
Ruslan Kuprieiev 1e43c303a7 output: cache tree objects (#6301) 2021-07-24 23:07:43 +03:00
meieraleandAlexander Meier 985c0328c7 remote: add excludes for AzureDefaultCredentials (#6359)
add additional excludes to DefaultAzureCredential
which are needed in a Windows Environment to
use az cli provided credentials, etc.

Co-authored-by: Alexander Meier <[email protected]>
2021-07-23 16:02:16 +03:00
Batuhan Taskaya d5722e3edf fsspec: use rm_file() (#6358) 2021-07-23 14:10:16 +03:00
Paweł Redzyński 67b42ebe4f plots: cleanup data extraction (#6355)
* plots: cleanup data extraction

* fixup
2021-07-23 10:28:36 +09:00
Batuhan Taskaya 1d7e754044 setup: bump flatten-dict to 0.4.1 (#6357) 2021-07-22 21:46:18 +03:00
Batuhan Taskaya c8739008d1 fsspec: implement callback mixins (#6322)
* fsspec: implement callback mixins

* drop redundant refresh() calls
2021-07-22 18:42:28 +03:00
Nikita KodenkoandSaugat Pachhai e731e35e6c tests: moved few unittest tests to pytest (#6339)
* tests: moved few unittest tests to pytest

* tests: unittest.mock -> pytest-mock in few tests

* tests: test_prompt: unittest.mock -> pytest-mock

* Update tests/unit/test_daemon.py

Co-authored-by: Saugat Pachhai <[email protected]>

Co-authored-by: Saugat Pachhai <[email protected]>
2021-07-22 18:06:54 +03:00
Peter Rowlands (변기호) cd7c95a785 ci: temporarily mark gdrive xfail (#6348) 2021-07-22 11:14:54 +03:00
dependabot[bot] 771b89ddf2 build(deps): bump google-cloud-storage from 1.41.0 to 1.41.1 (#6341)
Bumps [google-cloud-storage](https://github.com/googleapis/python-storage) from 1.41.0 to 1.41.1.
- [Release notes](https://github.com/googleapis/python-storage/releases)
- [Changelog](https://github.com/googleapis/python-storage/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-storage/compare/v1.41.0...v1.41.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-22 00:42:07 +03:00
Saugat Pachhai 64587e8b6d do not print in style by default for errors (#6344) 2021-07-21 14:42:29 +03:00
Saugat Pachhai 5faf9b3a7d update webdav4 to latest fsspec version (#6343) 2021-07-21 10:17:14 +00:00
Peter Rowlands (변기호) 12d1e3e981 objects: fix gdrive CI issue (#6338)
* fs: ensure gdrivefs.info returns integer size

* transfer: only verify on pull
2021-07-20 18:54:49 +03:00
pre-commit-ci[bot] d95bd62f8e [pre-commit.ci] pre-commit autoupdate (#6333)
updates:
- [github.com/ambv/black: 21.6b0 → 21.7b0](https://github.com/ambv/black/compare/21.6b0...21.7b0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2021-07-20 14:12:05 +03:00
dependabot[bot] 005bca1da6 build(deps): bump codecov/codecov-action from 1.5.2 to 2.0.1 (#6334)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 1.5.2 to 2.0.1.
- [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/v1.5.2...v2.0.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-20 14:06:50 +03:00
Peter Rowlands (변기호) 3d84df930b objects: migrate remote push/pull to objects.transfer (#6308)
* objects: migrate remote status to objects.status

* data_cloud: use objects.status

* objects: migrate remote._process to objects.transfer

* data_cloud: use objects.transfer

* objects: handle memfs staging objects in transfer

* fetch/import: use unified transfer()

* update get_remote and transfer exception usage

* update tests

* objects.transfer: use src ODB verify rules after xfer

* odb: move index from remote into odb

* objects.transfer: skip src status check when possible

* update tests

* remove dvc.remote

* odb.index: migrate to diskcache

* objects: fix status/transfer optimization

* update index usage

* state: remove dead sqlite related code

* update cloud tests

* drop unnecessary remove in tests

* use abstract base class in odb index

* status: load trees from local cache instead of remote odb when possible

* handle status when dir cache is explicitly removed
2021-07-20 13:30:01 +03:00
Batuhan Taskaya 66d0df2d12 setup: upgrade adlfs to 2021.7.1 (#6337)
The previous release got yanked on the PyPI, due to it containing backwards incompatible changes.
2021-07-20 08:59:45 +00:00
Christian Clauss d8b67f6f78 Upgrade to modern Python syntax (#6296) 2021-07-19 01:30:37 +03:00
dependabot[bot] 6711b647d9 build(deps): bump google-cloud-storage from 1.40.0 to 1.41.0 (#6311)
Bumps [google-cloud-storage](https://github.com/googleapis/python-storage) from 1.40.0 to 1.41.0.
- [Release notes](https://github.com/googleapis/python-storage/releases)
- [Changelog](https://github.com/googleapis/python-storage/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-storage/compare/v1.40.0...v1.41.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-19 00:41:23 +03:00
Batuhan Taskaya 1e81983d6d import-url: use the same chunk size for istextblock() calls (#6303) 2021-07-19 00:28:42 +03:00
Ruslan Kuprieiev 97809661bd setup: pathspec <0.9.0 (#6331) 2021-07-18 18:24:28 +03:00
Batuhan Taskaya b229588df3 azure: move to default fsspec loop (#6323) 2021-07-18 17:47:20 +03:00
Sayanta Banerjee cae117cfd4 Update README.rst (#6330)
* Update README.rst

More clean and simple

* Update README.rst

Rectification from the previous pull
2021-07-16 23:44:30 +03:00
Paweł Redzyński 21af7438db plots/metrics/params/experiments show: stop throwing exceptions (#5984)
* params/metrics/plots/exp show: introduce onerror

* fixup

* plots/metrics/params: removing errors

* removing more errors

* removing errors

* some more removing

* new plots ux

* new ux for metrics

* new ux for params

* fixing tests

* fixups

* minor test fixes

* minor test fixes

* commands: plots/params: fix ui imports

* working on error handlng decorator, stopped at show checkpoints

* error_handler, further dvelopment

* plots just before moving loading to collect

* appending errors to results: initial passing

* fixup

* onerror: remove onerror fixture

* fixups

* plots/metrics/params/experiments: temporarily remove error warnings

* plots: extract parsing out of class method

* metrics/experiments: provide exception serializer for show-json

* params/plots/metrics: signal errors

* rebase fixups

* fix TODOs

* fixup

* skshetry's review refactor
2021-07-16 15:36:04 +02:00
Batuhan Taskaya 27c1722b90 setup: bump ossfs==2021.7.5 (#6326) 2021-07-16 20:48:57 +08:00
Batuhan Taskaya 6c8673b158 setup: upgrade fsspec/s3fs/gcsfs to 2021.7.0 (#6317) 2021-07-15 11:33:07 +02:00
Batuhan Taskaya 6275f5d0d7 fsspec: create directories on upload/upload_fobj (#6309) 2021-07-14 15:42:51 +03:00
GaoandRuslan Kuprieiev 93f36d9351 oss: migrate to ossfs (#6307)
* Migrate to ossfs

fix 6177

* Add verification to object stored on oss

* Change the oss remote's dependency.

pass all of the tests

* Performance optimaztion and ossfs version update

* Remove the argument to set the resumable get/put

1. Experiments shows resumable get/put has little influnence on performance.
2. Just use resumable ones as default

* Use a new api for performance optimization

* Without filesize progress bar version

* With progress bar version

* Bump and add comments

1. Bump ossfs from 2021.6.2 to 2021.7.0
2. add some comments to the verification trigger

* Change the name of kwrags following parent class.

* Bump to new ossfs and rewrite upload_fobj

Because some problem with oss-emulater
1. bump to ossfs 2021.7.1
2. rewrite upload_fobj

* Optimization on rm_files

* Some optimization follow the old version.

* Update dvc/fs/oss.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>

* Update dvc/objects/db/oss.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>

* bump into ossfs 2021.7.2

* Bump into ossfs 2021.7.3OC

* Switch to real_oss_server

* Switch to a real oss server

* Use the oss bucket located in US.

* Remove some emulator related code

* Add envs to ci

* remove some oss pytest XFAIL

Because we didn't use emulator, we can remove some pytest XFAILs

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2021-07-13 15:27:34 +03:00
Batuhan Taskaya 58f0911234 azure: support directory imports with azure (#6245)
* azure: support directory imports with azure

* upgrade azurite to 3.13

* tmp: use azurite 3.12?

* upgrade to azurite 3.13.1

* bump adlfs to 2021.7.0
2021-07-13 13:39:30 +03:00
pre-commit-ci[bot] f45311e658 [pre-commit.ci] pre-commit autoupdate (#6306)
updates:
- [github.com/timothycrosley/isort: 5.9.1 → 5.9.2](https://github.com/timothycrosley/isort/compare/5.9.1...5.9.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2021-07-13 08:49:27 +05:45
Peter Rowlands (변기호) 1271db6b59 exp show: display running/queued state for experiments (#6174)
* exp: allow fetching from arbitrary executor by git URL

* exp: write info for running executors to .dvc/tmp/exp/run

currently only includes PID and git fetch URL

* exp: refactor stash/queue cleanup

* exp: add ability to collect dict of currently running experiments

* exp show: display run/queue state in the table

* exp run: unlock main repo while running tmpdir executors

* add tests for new exp show behavior

* fix workspace checkpoint run handling

* fix issue where running stash revs were shown as queued

* use "running"/"queued"

* add executor (location) column

* compare: add TabularData.is_empty

* use td.is_empty to drop state/executor

* use "workspace" and "temp" as executor names
2021-07-13 00:20:38 +09:00
Saugat Pachhai beb610c3ab add: ignore duplicated entries (#6286) 2021-07-12 20:43:41 +05:45
Batuhan Taskaya 4557de420c import-url: compute the stage hash in --to-remote (#6175)
* import-url: compute the stage hash in --to-remote
* save_deps() before calculating the md5
2021-07-12 09:59:34 +03:00
dependabot[bot] 481a411a35 build(deps): bump wsgidav from 3.1.0 to 3.1.1 (#6302)
Bumps [wsgidav](https://github.com/mar10/wsgidav) from 3.1.0 to 3.1.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.0...v3.1.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-12 11:26:14 +05:45
Ruslan Kuprieiev 52047214b7 fs: repo: handle directories with multiple artifacts (#6278)
Related to #5677
2021-07-12 02:17:07 +03:00
Peter Rowlands (변기호) 71bae7e4af objects: use separate staging ODB for staging trees (#6195)
* fs: use fsspecwrapper in memfs

* odb: add memfs staging ODB in ODBManager

* odb: add read_only attribute for odb instances

* objects: handle memfs paths when staging trees

* odb: remove git ODB

* output: use staging ODB

* RepoDependency: use staging odb for imports

* dvcfs: handle case where dir cache is unavailable

* fix circular import check

* odb: use temp local ODB for staging

* objects: use staging ODB in stage() and tree load

* objects: add typing for add/check/load/stage

* output/dep: use new staging

* add unit tests for local odb staging

* localfs: account for string paths in move()

* include local in odb.by_scheme

* tests: count dir cache like any other object in test_pull_git_imports

* odb: clear staging on gc()

* objects: add tree.get/filter/insert

* output: use obj.filter/get for granular dir commit

* squash filter logic output

* dependency: don't filter imports at the dep level

* don't init staging ODB for external cache

* use staging ODB when computing tree hash for external outs

* remove unneeded force in test granular commit

* use unique-per-manager memfs URLs for staging

* odb: make staging per-ODBManager

* objects: move state dependent staging into ODBManager

* objects: support checking multiple odbs

- staging dependent checks can be done via ODBManager.check

* checkout: do state lookup before explicit stage() in _changed()

* output: use odbmanager based staging

* RepoDependency: use odbmanager based staging

* update obj staging unit tests

* checkout: remove check(), handle individual file exceptions on link()

* diff: use odbmanager based staging

* objects: add ObjectPermissionError for read/write perm errors

* odb: move staging out of odbmanager

* objects: handle staging ODB in objects.stage()

* objects.tree: don't stage anything in tree methods

* odb: clear staged objects on gc()

* remote: don't filter objs by scheme, objs are already grouped by ODB

* diff: update objects.stage usage

* output/dep: update stage() usage

* update ODB tests

* update func tests

* remove memfs from FS_MAP

* revert unneeded objects.check() changes

* remove unused tree.insert()

* revert unused odbmanager changes

* add separate odb error classes

* stage external outs directly in ODB

* check for obj existence in main ODB before staging

* use .dvc/tmp/staging instead of per-odb staging

* gc: only gc loal staging once
2021-07-09 18:48:28 +03:00
Ruslan Kuprieiev 67c46b98dd dvc: bump to 2.5.4 2021-07-08 23:02:16 +03:00
Ruslan Kuprieiev bd5809572e setup: relax requirements for importlib-metadata 2021-07-08 23:01:48 +03:00
Ruslan Kuprieiev 05832c81fc dvc: bump to 2.5.3 2021-07-08 19:34:26 +03:00
Ruslan KuprieievandSaugat Pachhai 86df89e3a8 pkg: don't build universal wheels (#6299)
Kudos @skshetry for discovering that we've been publishing py2.py3
wheels on pypi and researching the cause.

Co-authored-by: Saugat Pachhai <[email protected]>

Co-authored-by: Saugat Pachhai <[email protected]>
2021-07-08 19:33:52 +03:00
Ruslan Kuprieiev c33776eaee dvc: bump to 2.5.2 2021-07-08 18:30:11 +03:00
kareemjanoandpre-commit-ci[bot] 22781f5ef0 fs: s3: don't forget to pass verify option (#6298)
* Update s3.py

pass verify flag when creating a session

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

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

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2021-07-08 21:12:36 +05:45
Ruslan Kuprieiev ae478ccdc0 dvc: bump to 2.5.1 2021-07-08 17:48:15 +03:00
Batuhan Taskaya 87b49fd9f9 staging: optimize the number of info() calls (#6281)
* staging: optimize the number of info() calls

* fs: make info() give details about the object
2021-07-07 22:53:43 +03:00
Batuhan Taskaya f90b9cdee1 doctor: show versions for fs requirements (#6273)
* doctor: show versions for fs requirements

* plugins => supports
2021-07-07 21:07:16 +03:00
Christian ClaussandSaugat Pachhai 40f24e5668 pre-commit: introduce codespell check
* codespell --ignore-words-list="ba,datas,fo"

* up-to-date -> uptodate

* Add pre-commit hook...

* Pre-commit needs quotes?

* Single quotes?

* Arrrgggsss

* Update .pre-commit-config.yaml

Co-authored-by: Saugat Pachhai <[email protected]>

Co-authored-by: Saugat Pachhai <[email protected]>
2021-07-07 17:14:15 +02:00
Hadas Arikandpre-commit-ci[bot] 9d9ff10be1 diff: only show states with values in summary (#6229)
* Fixed diff summary to only show states with values.

* Removed redundant comma by using join.

* Swithced for loop to generator for better readability.

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

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

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2021-07-06 21:03:32 +09:00
Batuhan Taskaya 98f7d1e481 objects: hash_file: retrieve size from hash_info (#6284)
* objects: hash_file: retrieve size from hash_info

* objects: drop Tree.size implementation
2021-07-06 10:56:13 +03:00
Saugat Pachhai 9cf22d3edc add: reduce os.fspath calls on recursive (#6287) 2021-07-06 06:43:56 +00:00
Saugat Pachhai 4b6c6a0e79 add: fix progress bar on single target (#6288) 2021-07-06 12:28:16 +05:45
Saugat Pachhai 6bccb21af6 prevent-tqdm-interleaving (#6280) 2021-07-06 13:37:37 +09:00
Saugat Pachhai beb6277d13 cleaning up add, progress bars and status (#6224)
* cleanup code

* fix progress bar updating message even when index > total

* cleanup
2021-07-06 01:08:35 +03:00
Kurian Benoyandpre-commit-ci[bot] 2485779d59 run: update -h message (#6197)
* update dvc exp run -h message

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

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

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2021-07-03 23:02:19 +03:00
Ruslan Kuprieiev 247b9c8be8 dvc: bump to 2.5.0 2021-07-03 22:34:11 +03:00
Saugat Pachhai 8d5f9ab6e5 add: do not delete stage files before add (#6239)
* add: do not delete stage files before add

Because of the way we collect stages and cache them, we were not able to
collect them for the `add` without removing them from the workspace.
As doing so, we'd have two same/similar stages - one collected from the
workspace and the other just created from the `dvc add` in-memory.

This would raise errors during graph checks, so we started to delete them
and reset them (which is very recently, see #2886 and #3349).

By deleting the file before we even do any checks, we are making DVC
fragile, and results in data loss for the users with even simple mistakes.
This should make it more reliable and robust.

And, recently, we have started to keep state of a lot of things, that by
resetting them on each stage, we waste a lot of performance, especially
on gitignores. We cache the dulwich's IgnoreManager, which when resetted
too many times, will waste a lot of our time just collecting them again
next time (see #6227).

It's hard to say how much this improves, as this very much depends on
no. of gitignores in the repo (which can be assumed to be quite in number
for a dvc repo) and the amount of files that we are adding
(eg: `-R` adding a large directory).

On a directory with 10,000 files (in a datadet-registry repo),
creating stages on `dvc add -R` went from 64 files/sec to 1.1k files/sec.

* add tests

* make the test more specific
2021-07-03 22:02:06 +03:00
Ruslan Kuprieiev 07f5f6b633 ssh: don't use path_info for connections (#6259) 2021-07-03 21:54:14 +03:00
Ruslan Kuprieiev aa255a1102 output: optimize obj collection (#6277)
* output: str only once

Calling `__str__` takes a long time and if we are dealing with a large
dataset, it will result in a very substantial time. For example,
locally, for 700K objects, it took 20sec more.

Related to #6276

* output: don't use os.path.join

Takes around 2sec for 700K objects.

* output: use fs.sep

* output: remove unused sep
2021-07-03 21:53:55 +03:00
Peter Rowlands (변기호) cfcb7e1294 pygit: handle annotated tags in resolve_refish (#6274) 2021-07-02 18:00:47 +03:00
Saugat Pachhai b8d9800e3c separate validation and transformation of args from the add func (#6220)
* separate validation and transformation of args from add func

* make tests pass for Python3.6

* cleanup

* use dict for attr-access and patch boundarguments.arguments
2021-07-01 21:15:02 +05:45
dependabot[bot] fe46d50847 build(deps): bump google-cloud-storage from 1.39.0 to 1.40.0 (#6261)
Bumps [google-cloud-storage](https://github.com/googleapis/python-storage) from 1.39.0 to 1.40.0.
- [Release notes](https://github.com/googleapis/python-storage/releases)
- [Changelog](https://github.com/googleapis/python-storage/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-storage/compare/v1.39.0...v1.40.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-01 21:14:20 +05:45
Batuhan Taskaya e3ff6d54a9 fsspec: loosen the prefix check to cover both None and False (#6246)
* fsspec: loosen the prefix check to cover both None and False

* gs: disable prefix-based search
2021-07-01 14:59:35 +03:00
Saugat Pachhai ce45ec8a28 align metrics to right and params to left in exp table (#6254)
Made a mistake during refactoring on styling before
2021-06-30 16:05:17 +05:45
Miguel Méndez cc5ba985b0 import: error message when imported file does not exist (#6240)
* Fix exception handling

* Replace exceptions with PathMissingError
2021-06-30 15:49:15 +09:00
Ruslan Kuprieiev 87cdbf8ace dvc: bump to 2.4.3 2021-06-29 17:27:21 +03:00
Tomasz Pietruszka eda8cd5846 git: Keep trailing slash when checking if directory is gitignored (#6232)
* Keep trailing slash when checking if directory is gitignored

* Calling is_ignored with correct type (could be Path-like)

* Fix test to comply to annotated types

* Fix separator issues

* Relaxed typing in is_ignored

* Added test

* Test scm

* Fix test on Windows

* Expanded test_ignored_dir_unignored_subdirs

* Even more assertions
2021-06-29 21:49:29 +09:00
pre-commit-ci[bot] 61835f6511 [pre-commit.ci] pre-commit autoupdate (#6243)
updates:
- [github.com/pre-commit/mirrors-mypy: v0.902 → v0.910](https://github.com/pre-commit/mirrors-mypy/compare/v0.902...v0.910)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2021-06-29 07:24:12 +05:45
Jon Crall e72354e98e Loosen versions for fsspec and pygtrie (#6242) 2021-06-28 23:57:49 +03:00
Ruslan Kuprieiev d1b36e36ca dvc: bump to 2.4.2 2021-06-28 20:19:42 +03:00
Batuhan Taskaya 6ec66ab3de output: don't lower the etag field on the schema when deserializing (#6238) 2021-06-28 20:15:58 +03:00
Peter Rowlands (변기호) 7c13897674 utils: normalize case in path_isin functions (#6236)
* utils: normalize case in path_isin

* utils: normalize case in contains_symlink_up_to

* split platform test
2021-06-28 19:21:44 +09:00
dependabot[bot] 1db4637f2d build(deps): bump crc32c from 2.2 to 2.2.post0 (#6234)
Bumps [crc32c](https://github.com/ICRAR/crc32c) from 2.2 to 2.2.post0.
- [Release notes](https://github.com/ICRAR/crc32c/releases)
- [Changelog](https://github.com/ICRAR/crc32c/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ICRAR/crc32c/compare/v2.2...v2.2.post0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-28 12:17:14 +03:00
dependabot[bot] ca3757b45b build(deps): bump urllib3 from 1.26.5 to 1.26.6 (#6235)
Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.5 to 1.26.6.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/1.26.6/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/1.26.5...1.26.6)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-28 08:43:13 +05:45
Ruslan Kuprieiev 4e792ae61c setup: update fsspec to 2021.6.1 (#6219)
* setup: update fsspec to 2021.6.1

* fs: local: remove temp workaround
2021-06-24 10:06:42 +05:45
dependabot[bot] 6b13f76fdb build(deps): bump mypy from 0.902 to 0.910 (#6212)
Bumps [mypy](https://github.com/python/mypy) from 0.902 to 0.910.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.902...v0.910)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-24 10:17:52 +09:00
Ruslan Kuprieiev 53e9460c65 staging: remove redundant exists() call (#6217) 2021-06-24 00:19:06 +03:00
dependabot[bot] dafe6e991c build(deps): bump google-cloud-storage from 1.38.0 to 1.39.0 (#6211)
Bumps [google-cloud-storage](https://github.com/googleapis/python-storage) from 1.38.0 to 1.39.0.
- [Release notes](https://github.com/googleapis/python-storage/releases)
- [Changelog](https://github.com/googleapis/python-storage/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-storage/compare/v1.38.0...v1.39.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-23 19:07:43 +00:00
Ruslan Kuprieiev 09946847d2 dvc: bump to 2.4.1 2021-06-23 16:08:10 +03:00
Saugat PachhaiandRuslan Kuprieiev 6e5e62857b Bump fsspec version (#6214)
* Bump fsspec version

* local: use fsspec 2021.06.0 with a temp hack

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2021-06-23 16:07:32 +03:00
Saugat Pachhai b7c6df0ece Create CODEOWNERS (#6213) 2021-06-23 11:56:37 +05:45
Ruslan Kuprieiev 6779d3d431 dvc: bump to 2.4.0 2021-06-22 20:14:46 +03:00
pre-commit-ci[bot]andRuslan Kuprieiev 5844a0cd5b [pre-commit.ci] pre-commit autoupdate (#6167)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/ambv/black: 21.5b2 → 21.6b0](https://github.com/ambv/black/compare/21.5b2...21.6b0)
- [github.com/timothycrosley/isort: 5.8.0 → 5.9.1](https://github.com/timothycrosley/isort/compare/5.8.0...5.9.1)
- [github.com/pre-commit/mirrors-mypy: v0.812 → v0.902](https://github.com/pre-commit/mirrors-mypy/compare/v0.812...v0.902)

* test_requirements: add types packages

* pre-commit: add additional dependencies for mypy

* pre-commit: fix formatting

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Ruslan Kuprieiev <[email protected]>
2021-06-22 13:59:11 +03:00
Saugat Pachhai ce3a3ab100 pass size to upload_fobj (#6187)
This allows us to select between chunked and non-chunked uploading
in Webdav, and hopefully in the http itself.
2021-06-22 13:16:15 +03:00
yehiyam d1a219df0a repo: preserve meta field on move (#6201)
fixes #6200
2021-06-22 13:12:20 +03:00
Saugat Pachhai 476af100de ui: migrating rest to the ui.write (#6176)
* complete migration to ui.write

* fix small bugs
2021-06-21 22:14:59 +03:00
Saugat Pachhai f2998630a3 env: track DVC_IGNORE_ISATTY as a env that we support (#6202) 2021-06-21 05:35:39 +00:00
Laurens Duijvesteijn 9a7b6d2a1a serialize: Wrap UnicodeDecodeErrors (#6193)
Also expose `encoding` and `path` as attributes, so programmatic
consumers can access it easily.
2021-06-19 11:35:50 +05:45
Peter Rowlands (변기호) 1d3524f144 odb: treat external repo dependencies as objects (#6109)
* objects: add ExternalRepoFile object type

* RepoDependency: implement get_obj (use ExternalRepoFile)

* drop distinction between used external and used objects

* update tests

* remove unneeded fetch from ExternalRepoFile

* wrap erepo errors as an object error

* update cloud/remote functions for new used_objs behavior

* fix update() rev/locked behavior

* update import tests

* remove ExternalRepoFile

* move get_obj and get_used_objs implementations back into repo dependency

* return objects plus associated ODB (remote) in get_used_objs

* odb: add tmp_dir field and support creating Remote from an odb

* return dict mapping odb -> objects in get_used_objs

* use dummy git odb for git imports

* fetch: support pulling from specific odb

* update cloud funcs for get_used_objs usage

* update tests for used_objs behavior

* use fetch in dep.download

* catch circular imports from local fs repos

* add tests for chained and circular imports

* move tmp_dir into odb config

* update return docstring for repo.used_objs
2021-06-17 14:41:33 +03:00
Batuhan Taskaya 1a5fa228ab azure: add allow_anonymous_login option to the schema (#6190) 2021-06-17 14:24:07 +03:00
Saugat Pachhai adeb9ef63e fix webdav tests and then add some (#6189) 2021-06-17 13:10:53 +03:00
dependabot[bot] 3e93bc286f build(deps): bump pytest-xdist from 2.2.1 to 2.3.0 (#6188)
Bumps [pytest-xdist](https://github.com/pytest-dev/pytest-xdist) from 2.2.1 to 2.3.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.2.1...v2.3.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-17 09:37:36 +05:45
Batuhan Taskaya ea89f4a102 add, import-url: preserve size for to-remote/to-cache (#6162) 2021-06-16 18:42:51 +03:00
dependabot[bot] 78299ad345 build(deps): bump pytest-docker from 0.10.1 to 0.10.3 (#6172)
Bumps [pytest-docker](https://github.com/avast/pytest-docker) from 0.10.1 to 0.10.3.
- [Release notes](https://github.com/avast/pytest-docker/releases)
- [Changelog](https://github.com/avast/pytest-docker/blob/master/CHANGELOG.md)
- [Commits](https://github.com/avast/pytest-docker/compare/v0.10.1...v0.10.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-15 14:00:38 +05:45
Batuhan Taskaya b9df4451b1 s3, gs, azure: re-enable prefix-based search optimization (#6151) 2021-06-13 16:55:36 +03:00
Saugat Pachhai df67dc2db8 webdav: bring back support for user in url (#6155)
Also added some optimizations, and merged Webdavs into the same file.
2021-06-11 19:52:42 +05:45
dependabot[bot] 37fb73606c build(deps): bump mypy from 0.901 to 0.902 (#6153)
Bumps [mypy](https://github.com/python/mypy) from 0.901 to 0.902.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.901...v0.902)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-11 15:17:38 +05:45
Batuhan Taskaya 0deddf81cf setup: bump fsspec/s3fs/gcsfs to 2021.06.0 (#6137)
* setup: bump fsspec/s3fs/gcsfs to 2021.06.0

* bump fsspec to upstream
2021-06-10 15:17:43 +03:00
Ruslan Kuprieiev 429d54e146 stage: monitor: don't forget to unprotect outs (#6134)
Fixes #6083
2021-06-10 10:53:51 +02:00
dependabot[bot] 32859763be build(deps): bump mypy from 0.812 to 0.901 (#6140)
Bumps [mypy](https://github.com/python/mypy) from 0.812 to 0.901.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.812...v0.901)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-10 10:56:39 +05:45
Saugat Pachhai 5012494213 http/webdav: support specifying certs in ssl (#6142) 2021-06-10 02:24:56 +03:00
dependabot[bot] 57c728269d build(deps): bump codecov/codecov-action from 1.5.0 to 1.5.2 (#6139)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 1.5.0 to 1.5.2.
- [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/v1.5.0...v1.5.2)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-09 23:30:31 +05:45
pre-commit-ci[bot] 3ca18cb400 [pre-commit.ci] pre-commit autoupdate (#6135)
updates:
- [github.com/ambv/black: 21.5b1 → 21.5b2](https://github.com/ambv/black/compare/21.5b1...21.5b2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2021-06-08 12:29:17 +05:45
Dmitry PetrovandIvan Shcheklein 1335c7f5e4 Add Studio to the related technologies list (#6131)
* Add Studio to the related technologies list

* Add CML to DVC Studio description

* fix k8s names a bit

Co-authored-by: Ivan Shcheklein <[email protected]>
2021-06-07 23:48:35 +03:00
Ruslan Kuprieiev 8b9b2f6907 ls: specify rev as kwarg (#6121)
Fixes #5865
2021-06-07 18:36:35 +03:00
tomrutterandTom Rutter 2f8cf8d7d3 add: prevent crash while getting relpath (#5837)
* add: prevent crash while getting relpath

Since python 3.8 os.path.realpath has resolved windows network shares
to UNC paths. This breaks the logic for relpath as the UNC and mapped
drive paths don't have an obvious common prefix. As it is not easy to go
back to the mapped drive path from UNC path, this is resolved by always
using the UNC paths for calculating relative paths in windows.

Fixes #5832

* relpath: Update changes following review

Two changes:
 - use pytest.mark.skipif rather than if statement inside test to make
   it clearer that test is windows specific
 - Use tail rather than upper_path to denote rest of path after drive
    name taken off (to match python documentation)

Pull request: #5837

* tests, linter: update to add test case and apply linter though pre-commit

Co-authored-by: Tom Rutter <[email protected]>
2021-06-07 11:33:11 +02:00
dependabot[bot] bf5c28b71a build(deps): bump rtCamp/action-slack-notify from 2.1.3 to 2.2.0 (#6128)
Bumps [rtCamp/action-slack-notify](https://github.com/rtCamp/action-slack-notify) from 2.1.3 to 2.2.0.
- [Release notes](https://github.com/rtCamp/action-slack-notify/releases)
- [Commits](https://github.com/rtCamp/action-slack-notify/compare/v2.1.3...v2.2.0)

---
updated-dependencies:
- dependency-name: rtCamp/action-slack-notify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-07 14:04:32 +05:45
Saugat Pachhai ed2a0b4282 webdav4: use webdav4 (#6099) 2021-06-04 16:42:26 +03:00
eb46c03c6c output/remote: get rid of NamedCache (#6008)
* objects: make HashInfo & HashFile hashable

* stage/output: collect objects (HashFiles) instead of NamedCache

* remote/data_cloud: use objs instead of NamedCache

* repo: use objects instead of NamedCache

* rerun black after rebase

* remove NamedCache usage from run-cache

* drop NamedCache

* update HashInfo.__hash__

* update unit tests

* revert remote_name change

* start cleaning up test failures

* fix tree obj expansion

* update missing cache logging

* fix external dict handling

* update naive odb.gc

* update state save behavior for pull

* update test_data_cloud

* update test_remote

* fix gc with multiple repos

* objects.tree: support filtering w/preserved hash_info

* fix stage cache restore

* drop fs.path_info from HashFile.__hash__

* objects: add optional name attribute

* revert missing cache logging name/suffix changes

* revert name changes in remote tests

* review fixes

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

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

* Update dvc/remote/base.py

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

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

Co-authored-by: Ruslan Kuprieiev <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2021-06-02 22:19:58 +03:00
Ruslan Kuprieiev 5fb181fcf8 odb: don't use fs.path_info (#6059)
* odb: don't use fs.path_info

* fs: get rid of fs.path_info
2021-06-02 20:43:51 +03:00
Batuhan Taskaya 0e3db0d323 ci: move to ubuntu-18 from ubuntu-16 (#6101) 2021-06-02 17:31:28 +03:00
Ruslan Kuprieiev 5e4de841d1 dvc: bump to 2.3.0 2021-06-02 15:08:23 +03:00
Batuhan Taskaya 9e822edafc fs: temporarily disable prefix-based search for fsspec-implementations (#6096)
* fs: temporarily disable prefix-based search for fsspec-implementations

* tests: add the other hash
2021-06-02 15:06:48 +03:00
dependabot[bot] 9d8b0b80a0 build(deps): bump pytest-cov from 2.12.0 to 2.12.1 (#6095)
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 2.12.0 to 2.12.1.
- [Release notes](https://github.com/pytest-dev/pytest-cov/releases)
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v2.12.0...v2.12.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-02 14:42:32 +03:00
rgvanwesepandRobert Van Wesep 59cfc2c4a4 config, remote: Made S3 CA bundle customizable (#6018)
* config, remote: Made S3 CA bundle customizable

botocore allows a path to a custom CA bundle either by passing a path
to the CA bundle file into the verify argument of
boto3.session.Session.client or passing None (the default) which will
fall back to the AWS config. Previously, the DVC config only accepted a
boolean into the ssl_verify option in the remote S3 config. This changes
the DVC config to accept both string and None in addition to boolean and
defaults to None. I also changed the default for ssl_verfiy to None in
BaseS3FileSystem. Thus, if ssl_verify is not provided, botocore will
fall back to the AWS config.

Testing

Unit tests to cover the changes to the config schema and addition
ssl_verify types that will be passed into S3FileSystem. Also, ran

dvc push -r object-store data/cifar-10-python.tar.gz

in my work environment that has a private S3 endpoint that requires a
custom CA bundle, both with and without ssl_verify specified in the
config. This was successful, showing that communication could be
established. And I ran

dvc remote modify object-store ssl_verify "$HOME/.aws/cabundle.pem"

and confirmed that the custom CA bundle path was added to the config.

Fixes #6012

* Removed default None on ssl_verify

Responding to PR comment, removed the Optional, default None on
ssl_verify since the config keys are optional by default. Rather
than a missing ssl_verify producing a None that eventually gets
filtered, it doesn't appear in the parsed config in the first place.

Co-authored-by: Robert Van Wesep <[email protected]>
2021-06-01 16:17:45 +03:00
Saugat Pachhai c7a3a000e0 try skipping local pre-commit hooks (#6090) 2021-06-01 16:03:18 +05:45
dependabot[bot] 782e2aba87 build(deps): bump pylint from 2.8.2 to 2.8.3 (#6087)
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.8.2 to 2.8.3.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Changelog](https://github.com/PyCQA/pylint/blob/master/ChangeLog)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.8.2...v2.8.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-01 08:49:12 +05:45
Ruslan Kuprieiev 488a6f8076 fs: don't automatically create buckets (#6086)
* fs: azure: don't create bucket

* fs: oss: don't create bucket
2021-06-01 01:29:17 +03:00
Batuhan Taskaya 1fbf0da3df azure: better error messages / allow_anonymous_login option (#5833)
* azure: better error messages / allow_anonymous_login option

* dont pass repo to the initializer
2021-06-01 01:17:03 +03:00
Saugat Pachhai bf5deb08e4 pin networkx to 2.5 (#6085)
I am bit hesitant to add an upper-bound here
as networkx seems to be requiring a lot of
dependencies from next version onwards.
2021-05-31 21:39:48 +03:00
Ruslan Kuprieiev 04349a50eb dvc: bump to 2.2.0 2021-05-31 13:43:57 +03:00
Saugat Pachhai a172067c7d fix errors raised by pylint (#6081)
astroid==2.5.7 has improved inference and was throwing
errors that were false negatives before. Fixing these
and ignoring some.

Also added `funcy.decorator` to the `signature-mutators`,
as it changes function signature (i.e. provides `Call` as
a first argument which is not required to pass).

Example errors that we were getting because of this:

```
tests/remotes/gdrive.py:40:11: E1120: No value for argument 'tries' in function call (no-value-for-parameter)
************* Module dvc.stage.serialize
dvc/stage/serialize.py:38:1: E1120: No value for argument 'func' in
function call (no-value-for-parameter)
************* Module dvc.fs.http
dvc/fs/http.py:19:1: E1120: No value for argument 'ctx' in function call (no-value-for-parameter)
************* Module dvc.fs.gdrive
dvc/fs/gdrive.py:66:11: E1120: No value for argument 'tries' in function call (no-value-for-parameter)
dvc/fs/gdrive.py:459:5: E1120: No value for argument 'ctx' in function call (no-value-for-parameter)
```
2021-05-31 05:46:18 +00:00
Saugat Pachhai 7700d55529 use kwargs when passing command line args to run (#6075) 2021-05-29 21:04:55 +05:45
Dmitry Petrov a55c47e89f AirFlow integration link (#6077)
* Update README.rst
* typo fix
2021-05-28 19:43:36 -07:00
dependabot[bot] efda015de4 build(deps): bump actions/cache from 2.1.5 to 2.1.6 (#6073)
Bumps [actions/cache](https://github.com/actions/cache) from 2.1.5 to 2.1.6.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v2.1.5...v2.1.6)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-28 14:43:23 +05:45
Saugat Pachhai a74855b9f7 fix empty string parsing for the command (#6074)
Fixes #6072
2021-05-28 06:51:27 +00:00
Saugat Pachhai 63dba45c14 ui: write gets a styled param for rich console printing (#6037) 2021-05-28 09:30:56 +05:45
dependabot[bot] e96f0cfed5 build(deps): bump urllib3 from 1.26.4 to 1.26.5 (#6069)
Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.4 to 1.26.5.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/1.26.4...1.26.5)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-27 13:07:14 +05:45
Saugat Pachhai d818e70bf3 output/dep: specify schema as a dictionary (#6061) 2021-05-26 14:58:51 +03:00
Saugat Pachhai e28726c057 fix mypy errors (#6062) 2021-05-26 06:01:04 +00:00
Ruslan Kuprieiev 8a7ccab82f fs: deps/outs: get rid of types (#6054)
Pre-requisite for removing fs.path_info and switching to regular strings
as paths in filesystems.

With new filesystems we no longer need the zoo of different dependency
and output types, as most of them are now just handled by the underlying
filesystem. Though deps/outs are still due for a refactor in other
aspects.
2021-05-25 19:25:09 +03:00
Batuhan Taskaya 50ec64b841 setup: bump fsspec/s3fs/gcsfs to 2021.5.0 (#6056) 2021-05-25 14:54:56 +03:00
Ruslan Kuprieiev aa672c5108 setup: bump dulwich to 0.20.23 (#6053)
Fixes #6052
2021-05-25 00:30:22 +03:00
Peter Rowlands (변기호) 9dda6bd32c git: update dulwich to 0.20.22 (#6049)
* git: update dulwich to 0.20.22

* git: disable reading system config in CI
2021-05-24 17:40:14 +09:00
Saugat Pachhai bbe06df18e ui: migrate dag (#6044) 2021-05-21 07:35:49 +00:00
Batuhan Taskaya 960ece926c fsspec: split the cloud-specific functionality from the base wrapper (#6014)
* fsspec: split the cloud-specific functionality from the base fs

* rename Cloud to Object
2021-05-19 18:12:07 +03:00
Ruslan Kuprieiev c6b4c94fde dvcfs: raise FileNotFoundError on missing version info (#6017) 2021-05-18 19:12:35 +03:00
Saugat Pachhai b9000ed926 pre-commit: update black to 21.5b1 (#6032)
* pre-commit: update black to 21.5b1

also updated others, but black is more noticeable

* remove magic trailing comma
2021-05-18 15:51:15 +00:00
Saugat Pachhai 58c465ef13 ui: migrate completion to ui.write (#6029) 2021-05-18 12:02:18 +00:00
Saugat Pachhai 789faa4f11 ui: migrate checkout to ui.write (#6028) 2021-05-18 13:55:11 +02:00
dependabot[bot] 9a5b7251b3 build(deps): bump pydocstyle from 6.0.0 to 6.1.1 (#6027)
Bumps [pydocstyle](https://github.com/PyCQA/pydocstyle) from 6.0.0 to 6.1.1.
- [Release notes](https://github.com/PyCQA/pydocstyle/releases)
- [Changelog](https://github.com/PyCQA/pydocstyle/blob/master/docs/release_notes.rst)
- [Commits](https://github.com/PyCQA/pydocstyle/compare/6.0.0...6.1.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-18 14:42:05 +05:45
Ivan Shcheklein aab5dec7f5 rwlock: add missing space to the message (#6026) 2021-05-18 12:32:35 +09:00
Saugat Pachhai e6a2b8a2ad ui: migrate cache:dir and check-ignore to ui.write (#6021) 2021-05-17 18:50:58 +03:00
dependabot[bot] 9c4a8c384a build(deps): bump pytest-cov from 2.11.1 to 2.12.0 (#6022)
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 2.11.1 to 2.12.0.
- [Release notes](https://github.com/pytest-dev/pytest-cov/releases)
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v2.11.1...v2.12.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-17 15:26:41 +05:45
Peter Rowlands (변기호) 71ea12877d stage: allow duplicate values in _read_env (#6007) 2021-05-14 15:49:41 +02:00
Saugat Pachhai c6f49bd23d ls: use ui.write (#6004) 2021-05-14 10:19:55 +05:45
Ruslan Kuprieiev a5b080645c dvcignore: refactor from_file(s) (#6002) 2021-05-13 12:23:58 +02:00
Ruslan Kuprieiev 6d941e6cb5 fs: get rid of fs_root (#6000)
* dvcignore: don't use fs_root

* fs: get rid of fs_root
2021-05-13 04:15:56 +03:00
Ruslan Kuprieiev da0f0406fb fs: fix typo 2021-05-12 22:28:58 +03:00
Ruslan Kuprieiev 89b40afee7 fs: make constructor closer to fsspec (#5911)
* fs: don't use repo for jobs/hash_jobs

* fs: local: exists: use fsspec's LocalFileSystem

* fs: make constructor comply with fsspec
2021-05-12 22:03:09 +03:00
dependabot[bot] 0ca08d2fea build(deps): bump rtCamp/action-slack-notify from 2 to 2.1.3 (#5993)
Bumps [rtCamp/action-slack-notify](https://github.com/rtCamp/action-slack-notify) from 2 to 2.1.3.
- [Release notes](https://github.com/rtCamp/action-slack-notify/releases)
- [Commits](https://github.com/rtCamp/action-slack-notify/compare/v2...v2.1.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-12 13:06:50 +05:45
dependabot[bot] 14e1511eee build(deps): bump actions/setup-ruby from 1 to 1.1.3 (#5994)
Bumps [actions/setup-ruby](https://github.com/actions/setup-ruby) from 1 to 1.1.3.
- [Release notes](https://github.com/actions/setup-ruby/releases)
- [Commits](https://github.com/actions/setup-ruby/compare/v1...v1.1.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-12 13:06:40 +05:45
b349b39d62 Decoupling dvcignore and fs (#5812)
* Decoupling dvcignore and fs

[x] remove dvcignore from fs objects
[x] attach dvcignore to repo
[x] add ignore check to functions(add, checkout, output, pipeline, etc)
[x] pass all tests
[x] final check

* Solve all of the arugments error.

* add dvcignore to dvc list

* Solve some test_ignore cases

* add dvcignore to checkout.

1. add dvcignore to 'checkout', 'add'
2. add ignore_filter to fs

* Solve two tests

* Get rid of ignore_filter

* Add ignore examine to download

* Solve a test issue

* Solve dvcignore and subrepo problems

* Solve sub repo error

* Revert some changes

* pass all tests

* Remove debug codes

* Some final exams

* dvcignore trie tree only update when needed

* reuse of the dir result in path walk.

* Some change after code review.

* Recover something from false `--force` push

* add abspath trans to dvcginore

* pass pylint

* pylint related

* Update dvc/fs/repo.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>

* Changes related to reviews.

* dvcignore call api change

from dvcignore.walk_files(fs.walk(path_info)) to dvcignore.walk_files(fs, path_info)

* Get rid of PathInfo in test_ignore.py

* fs.walk follow the arguments of os.walk

* Dvcignore as parameters

* pass dvcignore as arguments to checkouts

* Decoupling state and repo

* Update dvc/dvcfile.py

Co-authored-by: Paweł Redzyński <[email protected]>

* Update dvc/objects/stage.py

Co-authored-by: Paweł Redzyński <[email protected]>

* Solve some small problems

* Add tests to verify that dependency now not ignore

fix#5256

* Update dvc/ignore.py

* Update dvc/fs/git.py

* Update dvc/repo/__init__.py

* Some small improvements

* pass black

* Refactor is_ignored

* Update dvc/output/base.py

* repofs: use dvcignore in exists/isdir/isfile

Per https://github.com/iterative/dvc/pull/5812/files#r630604524

Co-authored-by: myname <[email protected]>
Co-authored-by: Ruslan Kuprieiev <[email protected]>
Co-authored-by: Paweł Redzyński <[email protected]>
Co-authored-by: Ruslan Kuprieiev <[email protected]>
2021-05-12 05:29:04 +03:00
Saugat Pachhai 25eb9ad8c1 RFC: respect $PAGER env vars, make -FRSX default in less (#5906) 2021-05-11 16:35:18 +00:00
Saugat Pachhai eab8146b76 ui: use sys.stdout and sys.stderr all the time (#5991)
We used to allow changing default output and error stream
but that was problematic for the testing. Now it just uses
sys, so capsys should work for the testing.
The proper way to change sys.stdout and sys.stderr is
by using 'redirect_stdout' now.
2021-05-11 11:33:22 +00:00
Batuhan Taskaya e7571ab325 setup: fixate aiobotocore/botocore/boto3 versions (#5987) 2021-05-10 18:28:11 +03:00
Batuhan Taskaya fc4e31bbf8 azure: use selector event loop for windows (#5983) 2021-05-10 16:50:58 +03:00
dependabot[bot] 674693bb86 build(deps): bump pytest-mock from 3.6.0 to 3.6.1 (#5981)
Bumps [pytest-mock](https://github.com/pytest-dev/pytest-mock) from 3.6.0 to 3.6.1.
- [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.0...v3.6.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-07 18:20:01 +05:45
Ruslan Kuprieiev 0ad1408da7 plots: handle NoRemoteError for dirs (#5976) 2021-05-07 14:35:35 +03:00
Saugat Pachhai 19f2fe52e6 tests: skip copying sqlite files in test directories (#5977) 2021-05-06 18:51:07 +05:45
Saugat Pachhai 77be01ee2d try to close dvc.state on exit (#5962)
* try to close dvc.state on exit

I have a feeling that it is causing the issues with
shutil.copytree and some PermissionError during 'init',
as we are not closing it between multiple 'main()' calls.
But I am not quite sure, and this seems like a good thing to
do anyway.

* fixup

* close dvc on exit

* close cache on tests and on init
2021-05-06 14:32:25 +03:00
Saugat Pachhai eabf6ddf27 run gdrive tests in a single job, run all on schedule (#5974) 2021-05-06 17:09:34 +05:45
Batuhan Taskaya 597f730e7d s3: respect credentialpath/configpath (#5973) 2021-05-06 12:27:29 +03:00
ciaochiaociaoandChiao-Wei Hsu 3670bf4fc3 commit,completion: Add stages completion for dvc commit (#5963)
Fixes #5960

Co-authored-by: Chiao-Wei Hsu <[email protected]>
2021-05-05 15:53:20 +03:00
Saugat Pachhai c2f1a1e162 Notify CI failures to Slack (#5970) 2021-05-05 18:37:33 +05:45
Batuhan Taskaya a234f53b76 azure: use a custom event loop for authentication (#5958)
* azure: use a custom event loop for authentication

* add test_temp_event_loop

* wrap open() too!

* ignore aiohttp warnings for normal level use-cases

* rename tests/unit/remote/test_azure to tests/unit/fs/test_azure
2021-05-05 15:41:55 +03:00
dependabot[bot] 0a0d146386 build(deps): bump pytest from 6.2.3 to 6.2.4 (#5968)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.2.3 to 6.2.4.
- [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.3...6.2.4)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-05 14:31:20 +05:45
Batuhan Taskaya d4a3df73df s3: fsspec migration (#5683)
* s3: migrate to s3fs

* s3: strip etag

* s3: disable instance cache

* tests: use the proper remote:// form in test_checkout

* tests: move/drop some tests

* setup: depend on s3fs

* tests: normalize s3 tests with some cleanups

* Use _download/_upload with old implementation

* fsspec: cache .exists() results

* setup: pin the boto version

* setup: install s3fs[boto3]

* s3: proper exception management

* s3: config improvements

* setup: reference without quotes

* setup: install boto separately

* setup: install moto server

* s3: mocking adjustments for moto_server

* s3: mark some fixtures with scope=session

* s3: s3_fake_creds_file scopes to func

* fsspec: add some docs regarding exists() optimization

* fsspec: exists() fallback

* fsspec: revert exists() optimization

* tests: ssl_verify => verify

* setup: temporarily fetch from master

* s3: add boto as a requirement

* tests: do an extra check regarding race conditions

* tests: use motoserver docker image

* tests: wrap config deletion with try/finally

* tests: disable s3 by default

* setup: upgrade fsspec
2021-05-05 11:36:10 +03:00
Peter Rowlands (변기호) 256cb06921 move: validate new stage before move (#5953)
* move: validate new stage before move

* verify that nothing was moved in tests
2021-05-05 13:09:55 +09:00
Saugat Pachhai ff56379f8a tests: rerun gdrive and gs on failures (#5959)
we just need to mark them with needs_internet marker.
Unfortunately, it's not possible to do that automatically
with the fixtures as fixtures are per-session and we need
something on more higher level (which the marker is not passed
through in the pytest).
2021-05-04 17:59:50 +03:00
Saugat Pachhai 4733757bb1 use realpath for is_in_repo calculation (#5954)
This is because, the repo root is used from a realpath, and the stage.wdir is abspath.
We might need to use same kinds of path, but this patch seems to be the easiest/fastest
way to fix the flaky tests (if all the tests agree).
2021-05-04 17:59:09 +03:00
dependabot[bot] 3e9fd100ea build(deps): bump moto from 2.0.5 to 2.0.6 (#5955)
Bumps [moto](https://github.com/spulec/moto) from 2.0.5 to 2.0.6.
- [Release notes](https://github.com/spulec/moto/releases)
- [Changelog](https://github.com/spulec/moto/blob/master/CHANGELOG.md)
- [Commits](https://github.com/spulec/moto/compare/2.0.5...2.0.6)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-04 17:45:30 +03:00
dependabot[bot] c1a49b440d build(deps): bump pygments from 2.8.1 to 2.9.0 (#5956)
Bumps [pygments](https://github.com/pygments/pygments) from 2.8.1 to 2.9.0.
- [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.8.1...2.9.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-04 17:45:16 +03:00
dependabot[bot] c2734a2cd7 build(deps): bump codecov/codecov-action from v1.4.1 to v1.5.0 (#5957)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from v1.4.1 to v1.5.0.
- [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/v1.4.1...a1ed4b322b4b38cb846afb5a0ebfa17086917d27)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-04 17:45:01 +03:00
Saugat Pachhai 71ab15771a exp show: decoupling tree and styling from row collection (#5903)
* exp show: try to split presentation logic

Trying to remove tree and the bold/row-styling part even more

* fix naming of the experiment types
2021-05-04 18:15:55 +05:45
Peter Rowlands (변기호) 53414a0618 dvcfile/stage: check if dvcfile would be gitignored on stage creation (#5907)
* dvcfile: add check for gitignored when validating filenames

* update tests

* update exception using in tests

* review fixes

- clarify method name
- use FileIsGitIgnored
2021-05-04 15:14:55 +09:00
Saugat Pachhai 708587a159 tests: mark test_stash_pop as xfail on linux (#5952)
I tried increasing max_run to very high number: 1000. Even in those
cases, it failed 947 times. So I don't really see a reason to mark
it as flaky. Anyway, we are treating it as xfail by keeping it failing
in the CI, so this PR does not change anything for us. :)
2021-05-04 11:22:57 +05:45
Ruslan Kuprieiev 3db25ea67d dvc: bump to 2.1.0 2021-05-03 16:29:49 +03:00
Saugat Pachhai d6afd26bea metrics diff/show: rollback to rounding fractional digits with precision (#5904) 2021-05-02 16:56:00 +03:00
Saugat Pachhai dba0aaeab7 accept dict for header styling (#5905)
I found that it's harder to style a header with the list using index,
they are to be applied by header name. Otherwise, we might end up using
a different header for coloring, or worse throw some error because the
header name and style don't match.
Row does have it's property of index, so it makes sense there.
2021-04-30 18:43:38 +05:45
Paweł Redzyński d509d77193 list: add --show-json option (#5888)
* list: add --show-json option

* list: add test for --show-json
2021-04-30 11:40:44 +02:00
Saugat Pachhai 102817e53a simplify experiments row collection logic (#5901) 2021-04-30 15:11:57 +05:45
Peter Rowlands (변기호) f771b1d5b0 exp run: error out when trying to resume from intermediate checkpoint w/no modifications (#5829)
* apply: allow applying exp with multiple possible branches

* default to new checkpoint branch after applying ambiguous commit

* output shas in addition to exp names by default

* update show tests

* git: allow force checkout in detach_head context

* exp run: error when trying to resume from a non-head checkpoint without
modifications

* add test for apply/resume from intermediate checkpoint

* list some possible heads when multiple matches
2021-04-30 15:12:44 +09:00
Peter Rowlands (변기호) 753803c177 pygit: force release of odb contexts after checkout/merge (#5849) 2021-04-30 10:18:37 +09:00
Ruslan Kuprieiev c9a748421a dependabot: add "maintenance" label (#5898) 2021-04-29 16:59:53 +03:00
Ruslan Kuprieiev cab4126ed4 output: relative paths outside of repo (#5893)
* output: relative paths outside of repo

Fixes #5870

* scm: enhance FileNotInRepo message
2021-04-28 16:13:01 +03:00
Batuhan Taskaya 2172add905 fs: introduce tree.find() (#5879) 2021-04-28 13:39:19 +03:00
Saugat Pachhai b2f71d459b Revert "build(deps): bump pylint-pytest from 1.0.3 to 1.1.2 (#5850)" (#5890)
This reverts commit 8930be06e8.
2021-04-27 13:32:39 +00:00
Saugat Pachhai 111c282808 exp show: separate ui representation of exp names and identifiers (#5885) 2021-04-27 18:37:35 +05:45
Saugat Pachhai 42cf394fde diff: use ui.table for --show-md (#5887) 2021-04-27 17:10:08 +05:45
dependabot[bot] 9c94b6dcaa build(deps): bump google-cloud-storage from 1.37.1 to 1.38.0 (#5883)
Bumps [google-cloud-storage](https://github.com/googleapis/python-storage) from 1.37.1 to 1.38.0.
- [Release notes](https://github.com/googleapis/python-storage/releases)
- [Changelog](https://github.com/googleapis/python-storage/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-storage/compare/v1.37.1...v1.38.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-27 16:03:08 +05:45
dependabot[bot] a05be55e7e build(deps): bump pylint from 2.8.1 to 2.8.2 (#5884)
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.8.1 to 2.8.2.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Changelog](https://github.com/PyCQA/pylint/blob/master/ChangeLog)
- [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.8.1...v2.8.2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-27 16:02:45 +05:45
Saugat Pachhai 8fd2eba9a2 refactor exp show: use new abstractions (#5880) 2021-04-27 13:14:56 +05:45
Saugat Pachhai f38d163d5c refactor diff and show to use new table ui and new data structure (#5872)
* use new table data structure for diff and show

* fix params diff showing changes

* metrics show: use default precision

* skip precision format for exp diff params

* exp diff: separate tables in --quiet mode

* change precision to signify significants digits

changed f specifier to g, so that we show significant digits
instead of fractional digits. This does changes how precision
worked for metrics/params diff/show.
2021-04-27 11:05:52 +05:45
Saugat Pachhai 0860e419e4 ui: tabular data (#5852)
* introduce tabular data structure

* ignore ellipsis
2021-04-26 14:54:23 +03:00
Saugat Pachhai 2978a5d7e9 dagascii: prune unused pager (#5876) 2021-04-26 14:52:29 +03:00
dependabot[bot] af6921e9ac build(deps): bump pre-commit/action from v2.0.2 to v2.0.3 (#5856)
Bumps [pre-commit/action](https://github.com/pre-commit/action) from v2.0.2 to v2.0.3.
- [Release notes](https://github.com/pre-commit/action/releases)
- [Commits](https://github.com/pre-commit/action/compare/v2.0.2...9b88afc9cd57fd75b655d5c71bd38146d07135fe)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-26 14:08:40 +03:00
dependabot[bot] 8930be06e8 build(deps): bump pylint-pytest from 1.0.3 to 1.1.2 (#5850)
Bumps [pylint-pytest](https://github.com/reverbc/pylint-pytest) from 1.0.3 to 1.1.2.
- [Release notes](https://github.com/reverbc/pylint-pytest/releases)
- [Changelog](https://github.com/reverbc/pylint-pytest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/reverbc/pylint-pytest/compare/pylint-pytest-1.0.3...pylint-pytest-1.1.2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-26 14:05:33 +03:00
dependabot[bot] 9eba910023 build(deps): bump pytest-mock from 3.5.1 to 3.6.0 (#5874)
Bumps [pytest-mock](https://github.com/pytest-dev/pytest-mock) from 3.5.1 to 3.6.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.5.1...v3.6.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-26 13:21:58 +03:00
dependabot[bot] 0728777494 build(deps): bump pylint from 2.7.4 to 2.8.1 (#5873)
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.7.4 to 2.8.1.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Changelog](https://github.com/PyCQA/pylint/blob/master/ChangeLog)
- [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.7.4...pylint-2.8.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-26 13:21:51 +03:00
Jan 54c0675fc6 Add periods (#5867) 2021-04-23 13:58:12 +03:00
Peter Rowlands (변기호) e0d90cdda1 exp run: dvc commit DVC-tracked data deps when stashing an experiment (#5859)
* stage,commit: add support for only committing data stages from
experiments

* exp run: `dvc commit` data deps when stashing exp changes

* add test for modified DVC-tracked data when generating experiment

* test with and without params change

* handle run without target
2021-04-23 11:59:15 +09:00
Batuhan Taskaya 17d7b06f7b output: introduce BaseOutput.transfer (#5861)
* output: introduce BaseOutput.transfer

* add: use BaseOutput.transfer for to-cache
2021-04-22 15:01:44 +03:00
dependabot[bot] 8ac26b58ee build(deps): bump codecov/codecov-action from v1.4.0 to v1.4.1 (#5857)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from v1.4.0 to v1.4.1.
- [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/v1.4.0...967e2b38a85a62bd61be5529ada27ebc109948c2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-22 13:50:46 +03:00
Batuhan Taskaya a42e363217 objects: don't recompute hash on odb.add() if verify is False (#5860) 2021-04-22 13:50:24 +03:00
Paweł Redzyński d87ace0968 html: enable providing custom html page templates (#5694)
* html: enable providing custom html page templates

* plots: add html template configuration option
2021-04-20 11:02:14 +02:00
Saugat Pachhai d275e6b7b5 cmd: stage list: use ui.table (#5847) 2021-04-20 12:04:26 +05:45
dependabot[bot] 71ee20412b build(deps): bump pyinstaller from 4.2 to 4.3 (#5843)
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 4.2 to 4.3.
- [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.2...v4.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-20 07:13:26 +03:00
Batuhan TaskayaandRuslan Kuprieiev 68897aa0de update: only update the missing hashes with --to-remote (#5773)
* objects: merge transfer() with stage()

* objects: drop streamfile

* objects: preserve the signature of get_file_hash

* objects: override hash_jobs if jobs= passed

* objects: support no progress bar directive

* tests: spy on _get_file_hash

* objects: update=... directive

* update: only transfer missing caches with --to-remote

* tests: remove local_remote from the primary test (due to it moves)

* objects: add move=True/False option

* objects: implement _get_file_obj on top of get_file_hash

* objects: use threading on save()

* objects: pass jobs= on to-cache as well

* objects: use in place tqdm

* objects: check for FileExistsError

* Update dvc/objects/__init__.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2021-04-19 18:09:35 +03:00
Batuhan Taskaya a8b7fda5a2 metrics: pass --precision to _show_metrics (#5839)
* metrics: pass --precision to _show_metrics

* tests: return empty string
2021-04-19 17:25:45 +03:00
Ruslan Kuprieiev 3472cb1a6a tests: don't set sensitive env secrets for all steps (#5823)
Due to https://about.codecov.io/security-update/ , there is a potential that we've leaked these. This PR takes a more granular approach when setting secret env vars so that this doesn't happen in the future.

We'll change these secrets after this PR is merged.
2021-04-19 16:39:37 +03:00
Peter Rowlands (변기호) 60ea24410e exp: use exp baseline for DVC commands that compare to HEAD during exp run (#5818)
* exp: add util method for getting baseline HEAD during run

* exp: use baseline for dvc commands which compare to HEAD during exp run

* add test for new util method

* update plots diff test mock
2021-04-19 14:42:58 +09:00
Naveen Sinha daf07451f8 Add support for kerberos token based auth (#5809)
* Add support for kerberos token based auth

* Added kerb_ticket as attribute to HDFSFileSystem

* Lint fix for config schema

* Updated black style changes

* Removed staticmethod decorator from hdfs
2021-04-17 10:02:58 +03:00
dependabot[bot] 48890c22b7 build(deps): bump codecov/codecov-action from v1.3.2 to v1.4.0 (#5838)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from v1.3.2 to v1.4.0.
- [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/v1.3.2...0e28ff86a50029a44d10df6ed4c308711925a6a8)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-17 11:20:46 +05:45
Jorge Orpinel 5e92bef733 dvc: add -A flag to * --all-commits (#5783) 2021-04-16 14:19:44 +03:00
Bhagawan Subedi ca7a02d522 erepo: replace copy_tree from disutils with copytree of shutil (#5827)
Fixes: #5817
2021-04-16 15:02:16 +05:45
Peter Rowlands (변기호) 29b9c5370c exp run: disallow --rev for workspace runs (#5831) 2021-04-16 18:02:13 +09:00
Peter Rowlands (변기호) b08a3a8caa exp run: suppress "finished run" output on --dry (#5830) 2021-04-16 16:36:39 +09:00
Ruslan Kuprieiev d02ab4bafd dvc: bump to 2.0.18 2021-04-15 23:19:04 +03:00
Ruslan Kuprieiev 853d304ce2 repo: handle rev without scm (#5822)
This part of logic used to be only used by external repo for cloned
repos, but since we now handle local repos without cloning, we don't
always pass scm instance. This PR automatically creates scm instance
for such cases.

Fixes #5590
2021-04-15 23:16:01 +03:00
Peter Rowlands (변기호) 3143975437 exp show: fix bug where params from commits were merged with workspace (#5813) 2021-04-14 18:02:50 +09:00
Ivan Shcheklein 57904d1fe2 Update README: use mamba instead of conda
More context is here https://github.com/iterative/dvc.org/issues/2370
2021-04-13 12:52:20 -07:00
dependabot[bot] 2472c04873 build(deps): bump actions/cache from v2.1.4 to v2.1.5 (#5805)
Bumps [actions/cache](https://github.com/actions/cache) from v2.1.4 to v2.1.5.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v2.1.4...1a9e2138d905efd099035b49d8b7a3888c653ca8)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-13 14:23:05 +03:00
dependabot[bot] c71a576a7a build(deps): bump actions/setup-python from v2.2.1 to v2.2.2 (#5804)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from v2.2.1 to v2.2.2.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v2.2.1...dc73133d4da04e56a135ae2246682783cc7c7cb6)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-13 14:22:57 +03:00
Paweł RedzyńskiandJorge Orpinel c39f91ecf4 params: read from ParamsDependency if possible (#5550)
* params: read from ParamsDependency if possible

* params: introduce include_untracked flag

* params: include untracked upon targets

* experiments: add option to show tracked params

* fixing test

* params: rename tracked to deps

* params: roll back to default params.yaml behavior

* experiments: add option to show only used params on diff

* Update dvc/command/experiments.py

Co-authored-by: Jorge Orpinel <[email protected]>

* Update dvc/command/experiments.py

Co-authored-by: Jorge Orpinel <[email protected]>

* Update dvc/command/params.py

Co-authored-by: Jorge Orpinel <[email protected]>

Co-authored-by: Jorge Orpinel <[email protected]>
2021-04-12 17:08:02 +02:00
dependabot[bot] 7e5d32c2c4 build(deps): bump moto from 2.0.4 to 2.0.5 (#5799)
Bumps [moto](https://github.com/spulec/moto) from 2.0.4 to 2.0.5.
- [Release notes](https://github.com/spulec/moto/releases)
- [Changelog](https://github.com/spulec/moto/blob/master/CHANGELOG.md)
- [Commits](https://github.com/spulec/moto/compare/2.0.4...2.0.5)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-12 15:56:39 +05:45
Batuhan Taskaya 353e4cf374 fsspec: update to 0.9 (including remotes) (#5788) 2021-04-09 14:12:25 +03:00
Saugat Pachhai fed3b2c0cc ui: unify tables (#5756) 2021-04-09 13:51:49 +03:00
Paweł Redzyński eccc320606 remote: use configured jobs number (#5747) 2021-04-08 17:52:42 +03:00
Peter Rowlands (변기호) a984f95cbd exp run: jobs should default to 1 (#5785) 2021-04-08 19:27:48 +09:00
dependabot[bot] 02d9684c33 build(deps): bump codecov/codecov-action from v1.3.1 to v1.3.2 (#5770)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from v1.3.1 to v1.3.2.
- [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/v1.3.1...9b0b9bbe2c64e9ed41413180dd7398450dfeee14)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-07 19:14:40 +05:45
dependabot[bot] 9a9e2ed274 build(deps): bump google-cloud-storage from 1.37.0 to 1.37.1 (#5771)
Bumps [google-cloud-storage](https://github.com/googleapis/python-storage) from 1.37.0 to 1.37.1.
- [Release notes](https://github.com/googleapis/python-storage/releases)
- [Changelog](https://github.com/googleapis/python-storage/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-storage/compare/v1.37.0...v1.37.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-07 18:35:54 +05:45
Peter Rowlands (변기호) f35cf7ea1d exp run: --reset no longer implies --force (#5751)
* exp run: --reset no longer implies --force

* ref update still needs to be forced on checkpoint --reset
2021-04-06 22:10:38 +09:00
Ruslan Kuprieiev 4f8cfc025b gha: remove leftovers 2021-04-06 14:19:25 +03:00
Ruslan Kuprieiev cd98761e63 Delete debian directory
Closes #2800
2021-04-06 14:06:09 +03:00
Ruslan Kuprieiev 14f05073ad gha: packages: don't build snap (#5774)
* Delete snap directory

* gha: packages: remove snap
2021-04-06 13:59:09 +03:00
f74b63806e azure: use account_name with default credentials (#5725)
* Fix DefaultAzureCredential usage

* Fix typo and printout

* Update dvc/fs/azure.py

Co-authored-by: Batuhan Taskaya <[email protected]>

* Disable logging spam

* Run precommit hook

Co-authored-by: Charlie Hewitt <[email protected]>
Co-authored-by: Batuhan Taskaya <[email protected]>
2021-04-06 12:48:15 +03:00
Batuhan Taskaya ddb1ece283 setup: fix all fsspec remote versions (#5769) 2021-04-06 10:54:13 +03:00
dependabot[bot]andSaugat Pachhai 9e7e01ce9f build(deps): bump pre-commit/action from v2.0.0 to v2.0.2 (#5767)
* build(deps): bump pre-commit/action from v2.0.0 to v2.0.1

Bumps [pre-commit/action](https://github.com/pre-commit/action) from v2.0.0 to v2.0.1.
- [Release notes](https://github.com/pre-commit/action/releases)
- [Commits](https://github.com/pre-commit/action/compare/v2.0.0...b54880c283dfc80a3fc1baf61807e8ae84471361)

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

* Update .github/workflows/tests.yaml

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Saugat Pachhai <[email protected]>
2021-04-05 22:58:34 +05:45
Saugat Pachhai 0206812d1b Fix test assertion with environment when using pyenv (#5765) 2021-04-05 22:56:05 +05:45
David de la Iglesia CastroandPeter Rowlands c634577463 exp show: Allow for wildcard patterns in include and exclude params/m… (#5720)
* exp show: Allow for wildcard patterns in include and exclude params/metrics

Fixes #5642

* Add test case for nested metrics/params

* match full string in _filter_name

* pre-commit

* Update dvc/command/experiments.py

Co-authored-by: Peter Rowlands (변기호) <[email protected]>

Co-authored-by: Peter Rowlands (변기호) <[email protected]>
2021-04-05 20:52:29 +09:00
dependabot[bot] 1a995eafce build(deps): bump pytest from 6.2.2 to 6.2.3 (#5764)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.2.2 to 6.2.3.
- [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.2...6.2.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-05 15:35:50 +05:45
Saugat PachhaiandSaugat Pachhai 19d0b2af56 use sys.executable instead of hardcoded python (#5763)
Co-authored-by: Saugat Pachhai (सौगात) <[email protected]>
2021-04-03 07:22:07 +00:00
Saugat Pachhai 7b25a9de24 Update pycryptodome to 3.10 (#5762)
3.10 has wheels for Python3.9 in Windows.
2021-04-03 12:10:24 +05:45
Ruslan Kuprieiev 2049a9dbdd dvc: bump to 2.0.17 2021-04-02 15:57:17 +03:00
Ruslan Kuprieiev ba38bb300c sign: increase timeout to 180 sec 2021-04-02 15:56:56 +03:00
Batuhan Taskaya e78375e3d9 setup: bump adlfs to 0.7 (#5760) 2021-04-02 13:35:15 +03:00
Ruslan Kuprieiev 585ff36486 dvc: bump to 2.0.16 2021-04-01 21:34:52 +03:00
Ruslan Kuprieiev b2503d5178 pyinstaller: hooks: bring back hook-googleapiclient.model.py
Got deleted by mistake in https://github.com/iterative/dvc/pull/5682/files
2021-04-01 21:34:26 +03:00
dependabot[bot] 32b342b1e4 build(deps): bump moto from 2.0.3 to 2.0.4 (#5755)
Bumps [moto](https://github.com/spulec/moto) from 2.0.3 to 2.0.4.
- [Release notes](https://github.com/spulec/moto/releases)
- [Changelog](https://github.com/spulec/moto/blob/master/CHANGELOG.md)
- [Commits](https://github.com/spulec/moto/compare/2.0.3...2.0.4)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-01 14:16:56 +05:45
Peter Rowlands (변기호) 66caa345b1 git: verify commit user is set (#5754) 2021-04-01 17:04:45 +09:00
Peter Rowlands (변기호) 17e5da7759 exp show: fix column collapse behavior (#5753) 2021-04-01 16:57:09 +09:00
Peter Rowlands (변기호) aa74712576 exp show: fix queued exp display (#5752) 2021-04-01 16:24:01 +09:00
Ruslan Kuprieiev c4622c6311 sign: increase timeout to 90 2021-04-01 02:11:28 +03:00
Ruslan Kuprieiev 2f759d60be sign: increase timeout 2021-04-01 01:41:36 +03:00
c3a61ad9cf Added SSL verification option (#5732)
* Added SSL verification option

* Changed param name to avoid conflicts

* verify_ssl now accepts strings

* Added simple unit tests

* Changed parameter name fron verify_ssl to ssl_verify so it's consistent with HTTP remotes

Forget to change 1 test

* add processing on ssl_verify param

typo

* revert back to bool param

* Update dvc/config_schema.py

Co-authored-by: Nigel <[email protected]>
Co-authored-by: Nigel <[email protected]>
Co-authored-by: Ruslan Kuprieiev <[email protected]>
2021-03-31 23:31:38 +03:00
Saugat Pachhai 7c7e73e3d2 status: collect stages only once (#5741)
This is achieved by using `lock_repo`
context manager that can be used in a recursive depth
that only resets stages when it has left any lock_repo
blocks.

Also added some ui related tests for the status which I found
missing
2021-03-31 18:31:27 +03:00
Saugat Pachhai 098a519305 webdav: use root url to check and on mkdir to find recurse depth (#5742) 2021-03-31 15:46:49 +03:00
Peter Rowlands (변기호) 4d4f9427d1 dvcignore: ignore file '.git' by default (#5745)
* dvcignore: ignore file '.git' by default

* update tests
2021-03-31 13:47:34 +03:00
Peter Rowlands (변기호) e91168812a build: fix fpm build zsh-completion path (#5743) 2021-03-31 16:45:34 +09:00
dependabot[bot] 57c082e41b build(deps): bump moto from 2.0.2 to 2.0.3 (#5744)
Bumps [moto](https://github.com/spulec/moto) from 2.0.2 to 2.0.3.
- [Release notes](https://github.com/spulec/moto/releases)
- [Changelog](https://github.com/spulec/moto/blob/master/CHANGELOG.md)
- [Commits](https://github.com/spulec/moto/compare/2.0.2...2.0.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-31 13:22:44 +05:45
Ruslan Kuprieiev 6be2efc9cf dvc: bump to 2.0.15 2021-03-31 00:06:33 +03:00
Saugat Pachhai 3b3b5c8997 parsing: resolve relative paths when loading parameters (#5734)
* parsing: resolve relative paths when loading parameters

* test with git commits as well
2021-03-30 23:50:04 +03:00
Saugat Pachhai b5f31daab0 ui: Use sys.stdout and sys.stderr dynamically (#5729)
Before, we used to set it during initialization. But this
made it hard to test using capsys as capsys changes the
reference to sys.stdout and sys.stderr with the mocked one
during the test. But since we are using dvc.ui globally and
setting it to a real sys.stdout and sys.stderr before capsys
comes into effect, testing was not possible with it.

Now, by using property hack, we will be using the values of
sys.stdout and sys.stderr during the time of write, so the mocking
would work and would be easier to test.
2021-03-30 23:44:46 +03:00
Alex Lopez 4e7f924e8b dvc: fix breakage with rich==10.0.0 (#5733)
* dvc: fix breakage with rich==10.0.0

Change in the arguments of __rich_measure__.

Fixes #5722

* dvc: Fix linting breakage from rich==10.0.0

Fixes: #5733

* dvc: set lower bound for rich dependency to 10.0.0

Fixes #5722
2021-03-30 20:57:40 +03:00
Ruslan Kuprieiev 09cb495423 dvc: bump to 2.0.14 2021-03-30 15:44:20 +03:00
Peter Rowlands (변기호) 262816c20e git: update dulwich to 0.20.21 (#5730) 2021-03-30 21:23:14 +09:00
dependabot[bot] a1aab35f99 build(deps): bump pylint from 2.7.2 to 2.7.4 (#5726)
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.7.2 to 2.7.4.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Changelog](https://github.com/PyCQA/pylint/blob/master/ChangeLog)
- [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.7.2...pylint-2.7.4)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-30 15:07:25 +05:45
Ruslan Kuprieiev 7d1a35388e dvc: bump to 2.0.13 2021-03-29 16:56:06 +03:00
Ruslan Kuprieiev 98edde06a4 sign: increase timeout 2021-03-29 16:27:05 +03:00
Ruslan Kuprieiev 30f9ad4b54 sign: add timeout and verbosity 2021-03-29 15:54:32 +03:00
Saugat Pachhai 547db15d34 Add test_requirements.txt to the manifest (#5724) 2021-03-29 12:56:02 +05:45
Saugat Pachhai f6b04c54e0 deps: limit rich version to <10 (#5723)
The recent release of rich 10.0.0 broke some internal APIs
that we were depending on for exp show.
See https://github.com/iterative/dvc/issues/5722
2021-03-29 12:09:02 +05:45
Ruslan Kuprieiev 1a25ebe3bd osxpkg: notarize: fix typo 2021-03-29 01:07:06 +03:00
Ruslan Kuprieiev 813f4d1f07 dvc: bump to 2.0.12 2021-03-27 00:13:30 +02:00
Saugat Pachhai 0a2e9f98d5 dvc: update precommit hooks (#5716)
* update pre-commit hooks

* fix mypy errors

1. Casting `columns` to the custom Column with collapse.
2. Ignoring typedef difference for RichTable._collapse_widths.
3. Suppressing error with conditional paramiko imports
   https://github.com/python/mypy/issues/1153
2021-03-26 22:41:56 +05:45
Saugat Pachhai f8c56742c5 pin urllib3 to 1.26.4 (#5714) 2021-03-26 21:29:08 +05:45
dependabot[bot]andSaugat Pachhai 7b922d0ec3 build(deps): bump pytest-mock from 1.11.2 to 3.5.1 (#5705)
* build(deps): bump pytest-mock from 1.11.2 to 3.5.1

Bumps [pytest-mock](https://github.com/pytest-dev/pytest-mock) from 1.11.2 to 3.5.1.
- [Release notes](https://github.com/pytest-dev/pytest-mock/releases)
- [Changelog](https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-mock/compare/v1.11.2...v3.5.1)

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

* change spy.return_value to spy.spy_return

This was introduced in pytest-mock 2.0.0
https://github.com/pytest-dev/pytest-mock/commit/78499644cb800f8fe65a5c17e81d168fa2a4c6b9

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Saugat Pachhai (सौगात) <[email protected]>
2021-03-26 21:19:37 +05:45
dependabot[bot]andSaugat Pachhai fae57091b0 build(deps): update pytest requirement from <6.2.2,>=6.0.1 to ==6.2.2 (#5706)
* build(deps): update pytest requirement

Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/6.0.1...6.2.2)

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

* Pin test_requirements.txt to 6.2.2

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Saugat Pachhai <[email protected]>
2021-03-26 21:02:20 +05:45
Saugat Pachhai cd9098211f remove pytest-tap (#5713) 2021-03-26 20:43:57 +05:45
dependabot[bot] 7b0ecad660 build(deps): bump google-cloud-storage from 1.19.0 to 1.37.0 (#5711)
Bumps [google-cloud-storage](https://github.com/googleapis/python-storage) from 1.19.0 to 1.37.0.
- [Release notes](https://github.com/googleapis/python-storage/releases)
- [Changelog](https://github.com/googleapis/python-storage/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-storage/compare/v1.19.0...v1.37.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-26 14:27:12 +00:00
dependabot[bot] 27c8fb2872 build(deps): update urllib3 requirement from <1.26.0 to <1.27.0 (#5707)
Updates the requirements on [urllib3](https://github.com/urllib3/urllib3) to permit the latest version.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/0.3...1.26.4)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-26 20:06:01 +05:45
dependabot[bot]andSaugat Pachhai 5ca813024f build(deps): bump moto from 1.3.16.dev122 to 2.0.2 (#5709)
* build(deps): bump moto from 1.3.16.dev122 to 2.0.3.dev12

Bumps [moto](https://github.com/spulec/moto) from 1.3.16.dev122 to 2.0.3.dev12.
- [Release notes](https://github.com/spulec/moto/releases)
- [Changelog](https://github.com/spulec/moto/blob/master/CHANGELOG.md)
- [Commits](https://github.com/spulec/moto/commits)

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

* Update test_requirements.txt

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Saugat Pachhai <[email protected]>
2021-03-26 20:05:32 +05:45
Batuhan Taskaya 042b1c7f7d pull: skip isdir() checks on downloading files (#5696)
* pull: use download_file

* tree: implement filesystem.download_file
2021-03-26 15:22:45 +02:00
dependabot[bot] 61e470b85e build(deps): bump jaraco-windows from 3.9.2 to 5.5.0 (#5700)
Bumps [jaraco-windows](https://github.com/jaraco/jaraco.windows) from 3.9.2 to 5.5.0.
- [Release notes](https://github.com/jaraco/jaraco.windows/releases)
- [Changelog](https://github.com/jaraco/jaraco.windows/blob/main/CHANGES.rst)
- [Commits](https://github.com/jaraco/jaraco.windows/compare/3.9.2...v5.5.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-26 18:41:44 +05:45
dependabot[bot] c3bf0e1cb5 build(deps): bump actions/setup-python from v2.1.4 to v2.2.1 (#5702)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from v2.1.4 to v2.2.1.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v2.1.4...3105fb18c05ddd93efea5f9e0bef7a03a6e9e7df)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-26 18:40:55 +05:45
dependabot[bot] cdb5d668da build(deps): bump actions/upload-release-asset from v1.0.1 to v1.0.2 (#5704)
Bumps [actions/upload-release-asset](https://github.com/actions/upload-release-asset) from v1.0.1 to v1.0.2.
- [Release notes](https://github.com/actions/upload-release-asset/releases)
- [Commits](https://github.com/actions/upload-release-asset/compare/v1.0.1...e8f9f06c4b078e705bd2ea027f0926603fc9b4d5)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-26 12:48:57 +00:00
dependabot[bot] 81d0d7aae7 build(deps): bump beautifulsoup4 from 4.4.0 to 4.9.3 (#5703)
Bumps [beautifulsoup4](http://www.crummy.com/software/BeautifulSoup/bs4/) from 4.4.0 to 4.9.3.

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-26 12:45:32 +00:00
dependabot[bot] f1375fd706 build(deps): bump actions/cache from v2 to v2.1.4 (#5699)
Bumps [actions/cache](https://github.com/actions/cache) from v2 to v2.1.4.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v2...26968a09c0ea4f3e233fdddbafd1166051a095f6)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-26 12:40:45 +00:00
dependabot[bot] d04aaf4ae1 build(deps): bump actions/checkout from v2.3.2 to v2.3.4 (#5698)
Bumps [actions/checkout](https://github.com/actions/checkout) from v2.3.2 to v2.3.4.
- [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.3.2...5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-26 12:40:26 +00:00
dependabot[bot] 191b8c1a6d build(deps): bump codecov/codecov-action from v1.0.13 to v1.3.1 (#5701)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from v1.0.13 to v1.3.1.
- [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/v1.0.13...fcebab03f26c7530a22baa63f06b3e0515f0c7cd)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-26 12:40:17 +00:00
Saugat Pachhai 8c90771388 Add dependabot to update github-actions and python requirements (#5697) 2021-03-26 18:20:10 +05:45
Saugat Pachhai 1931a6ec1e Move tests requirements to test_requirements.txt (#5674)
Plan is to add dependabot to update those packages required for testing
2021-03-26 14:13:43 +02:00
Ruslan Kuprieiev 4ffa0675b4 build: fix --notarize 2021-03-26 03:47:34 +02:00
Ruslan Kuprieiev c0671a2d9b gha: don't upload pkg artifacts 2021-03-26 03:18:29 +02:00
Ruslan Kuprieiev f8498d6340 gha: don't build packages for PRs 2021-03-26 03:17:22 +02:00
Ruslan Kuprieiev 27b589f1b9 dvc: bump to 2.0.11 2021-03-26 03:06:36 +02:00
Ruslan Kuprieiev 49cdc2bea3 build: add osx signing/notarization (#5695)
* build: add osx signing/notarization

* gha: temporarily enable pkg builds
2021-03-26 02:31:32 +02:00
Ruslan Kuprieiev a3a55339ab dvc: bump to 2.0.10 2021-03-25 02:21:52 +02:00
Ruslan Kuprieiev 8abffbf68b scripts: fpm: install zsh competion to install / "share"
Currently we create /usr/share/... which is not allowed by macos:

> the package is trying to install content to the system volume
2021-03-25 02:21:11 +02:00
Ruslan Kuprieiev 2e76dabbaa dvc: bump to 2.0.9 2021-03-24 20:30:48 +02:00
Ruslan Kuprieiev 8bebed93dd build: osxpkg: build lxml (#5687)
lxml wheels are built with an old SDK, which breaks osxpkg notarization:

```
	* error for path "dvc-2.0.7.pkg/dvc-2.0.7.pkg Contents/Payload/usr/local/lib/dvc/lxml/etree.cpython-37m-darwin.so": The binary uses an SDK older than the 10.9 SDK.
```
2021-03-24 20:30:03 +02:00
Ruslan Kuprieiev 58a5442d71 dvc: bump to 2.0.8 2021-03-24 19:20:04 +02:00
Saugat Pachhai 76ece8c64a plots: quote and resolve paths properly (#5670)
This commit fixes 3 things:
1. The path printed on the terminal is properly is quoted.  This was
problematic when the output is piped to `xdg-open` if the path contained
spaces or quotes or special characters.

2. The path is resolved. This is not an issue at all, but makes the
output nicer.

3. The path with which the browser is opened with uses relative path.
This is done to solve issue in WSL, that the absolute path is not
accessible outside the WSL for host programs as the linux filesystem
is available on `/wsl$` path.
2021-03-24 20:27:24 +05:45
Saugat Pachhai 8b6d655ebe use global UI object for printing (#5653) 2021-03-24 17:22:17 +05:45
Ruslan Kuprieiev f2ba67aece build: upgrade pyinstaller to 4.2 (#5682)
* build: upgrade pyinstaller to 4.2

* pyinstaller: hooks: remove hook-googleapiclient.model.py

https://github.com/pyinstaller/pyinstaller-hooks-contrib/blob/master/src/_pyinstaller_hooks_contrib/hooks/stdhooks/hook-googleapiclient.model.py

* gha: use python 3.8 for pyinstaller
2021-03-24 09:43:05 +02:00
Ruslan Kuprieiev 19cdef1675 build: separate building and packaging (#5675)
* build: separate pyinstaller and fpm/innosetup

* gha: upload deb/rpm/osxpkg/exe artifacts

* build: dynamically generate dvc version
2021-03-23 19:55:44 +02:00
Saugat Pachhai dc3fe6fbec Update pylint-pytest to 1.0.3 (#5673) 2021-03-23 15:28:33 +05:45
Ruslan Kuprieiev 619c55d399 dvc: bump to 2.0.7 2021-03-22 15:29:52 +02:00
Alex Ford b3bfa346c3 Using existing logging level if defined in addLoggingLevel (#5632)
* Use existing level if defined in addLoggingLevel

Fixes https://github.com/iterative/dvc/issues/4546

* Add addLoggingLevel test

* Format fix.
2021-03-22 15:15:16 +02:00
Saugat Pachhai 0a8927e3d8 diff: clear calculated renamed files from inverted hashmap (#5652)
* diff: clear calculated renamed files from inverted hashmap

We were trying to reuse the first encounter of file from inverted hashmap again and again, which was throwing ValueError

* refactor

* remove ordering

* add tests
2021-03-22 15:13:52 +02:00
Batuhan Taskaya 35597ed83d fsspec: skip instance cache on all filesystems (#5668) 2021-03-22 14:36:21 +02:00
Batuhan Taskaya 21fc06e947 fsspec: support raw buckets on path splitting (#5658) 2021-03-22 02:01:06 +02:00
Saugat Pachhai f2b216261e move Repo.diff related test to diff (#5654) 2021-03-19 16:58:21 +05:45
Batuhan Taskaya 6026573860 update: use unresolved path with --to-remote calls (#5631) 2021-03-17 16:48:00 +02:00
Ruslan Kuprieiev dcc55aa645 dvc: bump to 2.0.6 2021-03-17 00:01:10 +02:00
Batuhan Taskaya 862e18488c gs: process etag when it is available (#5634) 2021-03-16 14:08:59 +02:00
Saugat Pachhai 0b810fb256 Advertise Python 3.9 support in classifiers 2021-03-16 13:37:37 +05:45
Helio MachadoandPeter Rowlands 4ed55d0051 build: add pyinstaller hook for googleapiclient (#5619)
* build: add pyinstaller hook for googleapiclient

The Google API Discovery Service JSON files from
googleapiclient/discovery_cache/documents were being
excluded due to a bug and, particularly, drive.v2.json
is essential for our gdrive backend. Now we're explicitly
asking PyInstaller to embed these files on the generated packages.

Fixes #5618

* Suppress linter error for unrecognized imports

Co-authored-by: Peter Rowlands (변기호) <[email protected]>

* Apply black style after manual code review

Co-authored-by: Peter Rowlands (변기호) <[email protected]>
2021-03-14 12:56:49 +02:00
nik123 6ac7def6f1 setup: remove duplicate 'pytest-cov' dependency (#5617)
* setup: remove duplicate 'pytest-cov' dependency

* setup: returned pytest-cov version
2021-03-14 14:08:11 +05:45
Peter Rowlands (변기호) 8c1d46e502 checkpoints: completely remove checkpoint outs on exp run --reset (#5586)
* checkpoints: remove checkpoint outs on --reset

* exp run: no longer prune/reset lockfiles

* update tests

* make --queue imply --reset unless --rev is provided
2021-03-13 01:29:50 +09:00
Batuhan Taskaya 11b0581536 import-url: support importing empty directories on object storage services (#5536)
* import-url: support importing empty directories on object storage services

Empty directories are basically files with a trailing slash at the end of their names,
with empty contents on object service providers (such as azure, google storage). This
patch implements the `is_empty()` method for the `FSSpecWrapper` to determining whether
a path is empty (directory or a file) and also tweaks `.isdir()` in order to see empty
directories.

The base class is only used by azure and gcsfs for now, but if we go on the route of
migrating normal providers (like proper filesystems, not just object storages), we might
need to do these tweaks on a subclass (like `ObjectStorageFSSpecWrapper` etc), but I
don't think it is neccesary as of now.

The `is_empty` method is already there, and also used in the importing part to
warn users that they are importing something empty (https://github.com/iterative/dvc/blob/e385e59a098cd661ea1ea4257c9a5735c5c32bc6/dvc/output/base.py#L252-L254)
so this is not a new method. Just for the reference, as far as I can locate there
is no variant of this method in the fsspec.

* tests: revert custom mkdir for cloud storages

* tests: allign assingments

* add some comments
2021-03-12 18:11:08 +02:00
Batuhan Taskaya abcb5b2f6f gs: disable consistency checker (#5613) 2021-03-12 14:04:54 +02:00
Peter Rowlands (변기호) d365e6d5b4 exp run: fix issue where duplicate workspace runs would incorrectly conflict (#5611)
* exp run: gracefully handle duplicate workspace runs

* exp run: fix issue where errors could be logged twice

* move ref conflict check into its own function
2021-03-12 20:13:49 +09:00
Peter Rowlands (변기호) bd470e9586 stage add: unsuppress -c/--checkpoints help text (#5599)
* stage add: unsuppress -c/--checkpoints help text

* review fix
2021-03-12 14:47:12 +09:00
Peter Rowlands (변기호) fe4cb9c439 exp run: do nothing if result matches parent (HEAD) (#5600) 2021-03-12 14:09:03 +09:00
Ruslan Kuprieiev 2db55032c4 objects: file: remove unused load() method (#5610) 2021-03-12 05:35:48 +02:00
Peter Rowlands (변기호) 686898240c Revert "repro/exp run: use git status when reminding to git-track files (#5544)" (#5596)
This reverts commit ce2d7c65db.
2021-03-12 11:50:13 +09:00
Saugat Pachhai d245008987 plots: support opening plots file directly in the browser (#5584)
* plots: support opening plots file directly in the browser

* add tests

* fix path on windows tests
2021-03-12 04:23:07 +02:00
Ruslan Kuprieiev 29ad87ebe2 objects: split into separate files (#5609) 2021-03-12 04:12:46 +02:00
Ruslan Kuprieiev 4af3b3d20d dvc: refactor objects (#5607)
* dvc: get rid of DirInfo

DirInfo was created before we had proper objects, as a way to have a
structure that represents a staged directory. Right now we have a
dedicated Tree object that is specifically meant for that.

As a result, HashInfo no longer needs to have dir_info and we no longer
need `get_hash/get_dir_hash` helpers.

* objects: move Tree into tree.py
2021-03-12 01:10:34 +02:00
Saugat Pachhai b691b15007 Add a link to help debugging more information regarding the issue (#5601)
* Add a link to help debugging more information regarding the issue

* Update .github/ISSUE_TEMPLATE/bug_report.md
2021-03-11 16:02:53 +02:00
Saugat Pachhai fd66ef6bc1 http: fix info() if there is no content-length header (#5602) 2021-03-11 16:01:24 +02:00
Saugat Pachhai e709437cd2 update pylint and pin dependencies (#5603)
Started having some issues with pylint-pytest, pinning it as well
2021-03-11 15:24:27 +05:45
Saugat Pachhai 6c00fedb46 remove flaky-service
Looks like the project is dead: https://github.com/GoogleCloudPlatform/flaky-service.

And, the site https://flaky.dev seems to be dead.
2021-03-11 15:21:12 +05:45
Peter Rowlands (변기호) cc9ded2081 exp: subclass tempfile.TemporaryDirectory to handle cleanup (#5598)
* exp: subclass tempfile.TemporaryDirectory to handle cleanup

* override cleanup() since cls._rmtree does not exist in <3.8
2021-03-11 14:54:36 +09:00
Dmitry Petrov 5b300d8aae Add a link to Barrak&Adams citation (#5592) 2021-03-11 00:01:05 +02:00
Ruslan Kuprieiev cf1070a531 dvc: bump to 2.0.5 2021-03-10 11:33:47 +02:00
Laurens Duijvesteijn 9f224ecde3 scm: add force parameter to checkout() (#5582)
The `GitPythonBackend.checkout()` method already implicitly supported
this by forwarding `**kwargs` to the backend. `pygit2` didn't support
this yet.

This patch fixes this inconsistency.
2021-03-10 14:42:48 +09:00
Ruslan Kuprieiev 8433b36266 dvc: bump to 2.0.4 2021-03-09 17:59:26 +02:00
Batuhan Taskaya 3e75fe99e3 gdrive: change the temporary directory location to .dvc/tmp (#5583) 2021-03-09 17:44:48 +02:00
Batuhan Taskaya b221c16c89 pull: proper path join for partial pulls (#5579)
* pull: proper path join for partial pulls

* use os.path.join in the tests for windows
2021-03-09 15:36:44 +02:00
Dmitry Petrov 7ae2d37116 Add Barrak, Eghan & Adam citation (#5573) 2021-03-09 14:50:42 +02:00
Laurens Duijvesteijn e0a0342ab3 fs: ssh: pass timeout to paramiko's connect() (#5569)
Prior to this commit, DVC could hang indefinitely when connecting to
remote SSH filesystems. This should address this.
2021-03-08 22:59:26 +02:00
Peter Rowlands (변기호) d68f6d3696 pygit/dulwich: implement stash functionality (#5286)
* pygit2: disable set_ref with reflog message

* git: implement is_dirty in dulwich

* pygit2: implement _stash_apply

* pygit: update to 1.5.0

* enable set_ref(message=...) for pygit2

* git: replace backend reflog_delete in favor of _stash_drop

* pygit: support stash_push with custom refs

* update tests for backend stash_drop behavior

* pygit: raise MergeConflictError on failed stash apply

* pygit: reload index from disk before stash apply

* update stash_apply checkout strategy

* mark flaky test
2021-03-08 17:24:35 +02:00
Ruslan Kuprieiev b4232bdc7c state: use pickle v4 (#5571)
Kudos @skshetry for the discovery, research and fix.

Fixes #5563
2021-03-08 17:00:40 +02:00
Batuhan Taskaya f7d8761734 gdrive: use the normal file backend for saved credentials (#5554)
* gdrive: sync with pydrive's save backend changes

* bump pydrive version

* better temporary credentials managements for service accounts

* proper temp var management
2021-03-08 16:30:05 +02:00
Ruslan Kuprieiev 70c445d1ee utils: makedirs: ignore chmod errors (#5570)
Same as with `protect()`, these errors might happen if fs is immutable
or doesn't support chmod()-ing. We can safely ignore these.

Fixes #5552
2021-03-08 16:28:02 +02:00
Ruslan Kuprieiev 47bcb02ef1 fs: git: use pygit2 (#5559) 2021-03-06 02:54:13 +02:00
Ruslan Kuprieiev 4a75067f4c github: template: use dvc doctor instead of dvc version 2021-03-05 16:08:46 +02:00
Ruslan Kuprieiev 70f4c44ec6 dvc: bump to 2.0.3 2021-03-05 13:08:22 +02:00
af37527560 setup: rename azure.indentity to azure-identity (#5548)
* simple like that

* Update dvc/version.py

Co-authored-by: Marc-Antoine Belanger <[email protected]>
Co-authored-by: Ruslan Kuprieiev <[email protected]>
2021-03-05 01:43:32 +02:00
Ruslan Kuprieiev 71f8ffe13d dvc: bump to 2.0.2 2021-03-05 01:04:00 +02:00
Ruslan Kuprieiev 06aad3e2b3 dependency: repo: try to fetch before staging (#5549)
Related to https://github.com/iterative/dvc-bench/issues/234
2021-03-05 01:03:33 +02:00
Peter Rowlands (변기호) 1e7df0c27e dulwich: update to 0.20.20 (#5545) 2021-03-04 12:37:19 +02:00
Peter Rowlands (변기호) ce2d7c65db repro/exp run: use git status when reminding to git-track files (#5544)
* repro: skip DVC deps in SCM track stage

(DVC deps will be tracked when their own stage is repro'd)

* scm: use git status when reminding to git-track files

* sort names in reminder instead of random set order

* update unit test
2021-03-04 17:48:12 +09:00
Peter Rowlands (변기호) 2075a99233 exp diff: fix unresolved experiment names bug (#5543) 2021-03-04 15:39:21 +09:00
Ruslan Kuprieiev 98a1a92fa3 snap: debug failing tests 2021-03-03 23:11:19 +02:00
Ruslan Kuprieiev 16304b544f dvc: bump to 2.0.1 2021-03-03 17:38:26 +02:00
Ruslan Kuprieiev 803a100f32 setup: move google-cloud-storage to test requirements (#5540) 2021-03-03 17:11:30 +02:00
Ruslan Kuprieiev c3a0e8733e dvc: bump to 2.0.0 2021-03-03 12:21:03 +02:00
Peter Rowlands (변기호) 1542025afa exp: bash tab completion (#5532)
* exp list: add --names-only option

* completion: add exp name completion

* dvc exp: use exp name completion

* exp: fix bash completion
2021-03-03 15:46:11 +09:00
Batuhan Taskaya 8f6061f8cf update: clear error message for updating repo imports (#5520) 2021-03-02 20:34:59 +02:00
Paweł Redzyński 4a8cb806d2 live integration: get rid of dvc from live (#5466)
* live integration: get rid of dvc from live

* live: remove from api, add test for html generation during run

* stage: run: convert monitors to class context managers

* run: make monitors run in single thread

* run: move monitor logic out of run
2021-03-03 00:45:35 +09:00
Ruslan Kuprieiev e385e59a09 dvc: bump to 2.0.0a4 2021-03-02 13:54:16 +02:00
Batuhan Taskaya 2982e98c81 gs: Use rm() instead of delete() calls (#5535)
delete is a direct alias to rm(), though apparently it is kind of
broken on the latest release of gcsfs. This doesn't change any behavior
but only uses rm() directly instead of the given alias.
2021-03-02 13:53:44 +02:00
Batuhan Taskaya 3ebccda62c gs: use etag for the checksum parameter (#5534)
Switch from `md5` to `etag` as the checksum parameter. Due to `etag`
is not preserved while doing server side copies, this patch drops support
for external gs outputs.
2021-03-02 12:43:27 +02:00
Ruslan Kuprieiev 96092e5e27 dvc: bump to 2.0.0a3 2021-03-01 18:20:35 +02:00
Batuhan Taskaya b65dfe04f9 gs: Migrate Google Storage to FSSpec (#5526)
* azure: generalize fsspec functionality into FSSpecWrapper

* gs: migrate google storage to fsspec

* tests: disable empty_dir tests

* setup: add gcsfs to requirements

* gs: decode md5Hash into md5

* gs: speed up downloads via increasing chunk size
2021-03-01 17:15:31 +02:00
Paweł Redzyński 92b95bae9b plots: ignore binary plot files (#5523) 2021-03-01 15:00:42 +02:00
Ruslan Kuprieiev f2e0059392 dvc: bump to 2.0.0a2 2021-02-26 13:35:28 +02:00
Batuhan Taskaya a22101e0a6 azure: add account key to the config schema (#5522) 2021-02-25 19:53:47 +02:00
Peter Rowlands (변기호) fed6179992 exp run: if target is provided, only checkout target and dependencies (#5519)
* tests: test that only target and its deps are checked out on exp run

* exp run: if target is provided, only checkout target + deps
2021-02-25 15:12:27 +09:00
Peter Rowlands (변기호) b3a32809e9 exp show: fix bug where already collected exp results could be dropped (#5517) 2021-02-25 14:41:56 +09:00
Batuhan Taskaya e670644634 azure: support service principal in error messages as login_method (#5514) 2021-02-24 10:50:17 +02:00
Ruslan Kuprieiev a26f2995ef dir_info: only build trie when needed (#5512) 2021-02-24 09:15:20 +02:00
Ruslan Kuprieiev 502f0eecc9 tests: temporarily skip dvclive tests (#5513) 2021-02-24 08:52:33 +02:00
Ruslan Kuprieiev 8f568ab197 state: use diskcache (#5505)
* state: only load when needed

* state: don't use separate load/dump

* checkout: group link removal logic together

* state: use diskcache.Cache

* staging: use pbar and save to state

* state: don't pass the whole repo to __init__

* staging: explicitly pass state object

* fs: get rid of odb property

* state: don't close the connection to cache

* repo: close: close state
2021-02-23 20:51:34 +02:00
Ruslan Kuprieiev 2e01ce1974 dvc: bump to 2.0.0a1 2021-02-23 20:48:42 +02:00
Batuhan Taskaya d587d41d6c utils: proper file interface for HashedStreamReader (#5508) 2021-02-23 02:47:36 +02:00
Batuhan Taskaya 27c2401f59 azure: transfer bigger chunks of data (#5504) 2021-02-22 14:08:26 +02:00
Batuhan Taskaya 174621696d azure: migrate AzureFileSystem to fsspec (#5463)
* azure: migrate AzureFileSystem to fsspec

* client => fs

* principal auth

* better login error handling
2021-02-22 12:05:15 +02:00
Batuhan Taskaya e1a193f00b update: forbid updating repo-imports with --to-remote (#5475) 2021-02-22 11:55:00 +02:00
Saugat PachhaiandDave Berenbaum cccead3441 stage:list: update output and help text (#5499)
* stage:list: update output and help text

* Replaced 'Produces' with 'Outputs'
* Replaced 'Stage depends on' with just 'Depends on'
* Changes help text of --names-only and --fail

* Update dvc/command/stage.py

Co-authored-by: Dave Berenbaum <[email protected]>

* s/Generates/Reports

Co-authored-by: Dave Berenbaum <[email protected]>
2021-02-20 13:21:39 +05:45
Batuhan Taskaya 8102ede5cc add, import-url: always resolve the output in the local path for --to-remote (#5473)
* add, import-url: always resolve the output in the local path for --to-remote

* windows

* use the proper fixture
2021-02-19 15:22:59 +02:00
Saugat Pachhai 14944dd9e0 Minor refactoring stage:list command (#5498)
Moving text truncation to utils and adding tests for it
2021-02-19 18:27:37 +05:45
Paweł Redzyński 63f32936b2 live integration: turn on tests (#5487) 2021-02-19 09:25:28 +01:00
Peter Rowlands (변기호) 7d13d24a1f pygit/dulwich: implement create branch (#5497)
* pygit: implement create branch

* dulwich: implement create branch
2021-02-19 17:18:15 +09:00
Peter Rowlands (변기호) 9cb8187cb3 pygit: implement resolve_commit (#5496)
* git: return backend-agnostic object for resolve_commit

* use int timestamps instead of datetime

* make branch_revs Git method instead of backend

* pygit: implement resolve_commit
2021-02-19 17:00:22 +09:00
Peter Rowlands (변기호) 98590779eb pygit: implement get_refs_containing (#5495)
* git: make is_sha a Git class method instead of backend function

* pygit: implement get_refs_containing

* use merge base instead of parents
2021-02-19 16:42:04 +09:00
Batuhan Taskaya 9ae8bcdf52 gdrive: support service auth with json (#5485)
* gdrive: support service auth with json

* retry logic
2021-02-18 17:10:35 +02:00
Paweł Redzyński 14958ff85f plots: fix bug preventing usage from being displayed (#5492) 2021-02-18 14:24:47 +01:00
Peter Rowlands (변기호) 9909ee85a1 git: implement dulwich add --update (#5491)
* dulwich: implement add with --update

* add git backend test for add()

* fix list comprehension in dulwich status()

* allow adding single string path instead of list

* handle windows paths in add(update=...)
2021-02-18 17:56:56 +09:00
Saugat Pachhai 45477a2a81 dvc.lock: raise error if the file is gitignored (#5484) 2021-02-18 03:08:00 +02:00
Ruslan Kuprieiev 282e83c481 tests: temporarily exclude gdrivefs from extras (#5483)
When trying to publish to pypi, we get:

```
Invalid value for requires_dist. Error: Can't have direct dependency:
"gdrivefs @ git+https://github.com/intake/gdrivefs.git ; extra ==
'tests'"
```
2021-02-17 05:49:34 +02:00
Ruslan Kuprieiev 51988475a4 gha: packages: update for 2.0 2021-02-17 04:38:45 +02:00
Corentin Hembise 52ee995f5e Update requirements of dulwich to 0.20.19 (#5480)
This should fix #5438
2021-02-17 10:12:04 +09:00
Ruslan Kuprieiev e0842ce01e dvc: rename cache -> odb (#5481) 2021-02-16 22:49:25 +02:00
Ruslan Kuprieiev a814a49181 fs: detach state from fs (#5479)
We used to think this way, but state is not really an fs concept
(fsspec's dircache is though), as it is a cache of hashes of objects
that we are staging, and the hash doesn't depend on fs, as it is usually
computed by us by reading from fs.

Pre-requisite for diskcache-based state.
2021-02-16 17:42:50 +02:00
Saugat Pachhai d28f5675b4 remove support for double braces (#5476) 2021-02-16 17:21:47 +02:00
Sami Jawhar 66f24b8708 exp run: Separate exp param parsing from other commands (#5310)
* Separate exp param parsing from other commands

* Update exp run params metavar

* Updated experiments run param argument name
2021-02-16 23:19:56 +09:00
Ruslan Kuprieiev ae8efdeb0f state: remove unused files (#5474)
They've been used before we've moved state to .dvc/tmp
2021-02-16 14:48:48 +02:00
Ruslan Kuprieiev 53ba51b5fe objects: separate staging (#5470) 2021-02-16 02:21:54 +02:00
Ruslan Kuprieiev e90c5fdd19 fs: remove unused use_state decorator (#5469)
* fs: remove unused use_state decorator

* pylint: limit astroid version
2021-02-16 02:00:55 +02:00
Ruslan Kuprieiev a72cea67f1 build: require git >= 1.7.0 (#5467) 2021-02-15 20:54:07 +02:00
Ruslan Kuprieiev 242d346f15 gs: ls: add etag (#5465) 2021-02-15 17:06:16 +02:00
Ruslan Kuprieiev bb24131530 fs/outs/deps: remove supported() logic (#5462) 2021-02-13 22:59:20 +02:00
Ruslan Kuprieiev b0fd40f12c dvc: rename tree -> fs (#5461) 2021-02-13 15:40:44 +02:00
Ruslan Kuprieiev 9ce6bce9b6 tree: move get_hash to object id logic (#5460) 2021-02-13 04:22:29 +02:00
Batuhan TaskayaandRuslan Kuprieiev f4b14c2d4a gdrive: Implement .ls() protocol / proper tree walking with .walk_files() (#5433)
* gdrive: .ls()/.walk()/.walk_dir()

* better walking

* proper tests

* ._ls_recursive()

* use posxipath for tests

* store ids instead of raw names

* add gdrive fixture

* add dependency

* tests: temporarily disable gdrive tests

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2021-02-13 03:10:38 +02:00
Peter Rowlands (변기호) 273b02fc54 metrics/params: fix bug preventing usage from being displayed (#5453) 2021-02-12 10:46:13 +09:00
Ruslan Kuprieiev 7c5e0f2949 tree: get_hash: accept explicit hash name (#5452) 2021-02-11 22:44:28 +02:00
Saugat Pachhai ac9819eb02 introduce dvc.io module to consolidate io utilities (#5348)
* add dvc.ui module

The name of class has been changed to `Console` as the short form
name is too bad.

* add demo
2021-02-11 20:42:08 +02:00
Ruslan Kuprieiev 02cfe33cf9 output: handle missing hash_info.nfiles (#5448) 2021-02-11 14:53:40 +02:00
Ruslan Kuprieiev c635493104 tests: don't limit the number of pytest workers (#5441)
We used to do this because of gitpython leaks.
2021-02-11 12:27:00 +02:00
Ruslan Kuprieiev 039c2421d9 cache: misc cleanups (#5447)
* cache: remove duplicated use_state

* tree: move hash_to* helper to cache
2021-02-11 11:28:24 +02:00
Peter Rowlands (변기호) e3ee960e8e exp run: remove exp reset in favor of exp run --reset (#5442)
* exp run: remove `exp reset` in favor of `exp run --reset`

* update command unit test
2021-02-11 11:53:20 +09:00
Peter Rowlands (변기호) 9d82739b3a exp: add exp remove command (#5443)
* exp: add remove function for removing specific experiment(s)

* add `dvc exp remove` command

* update tests
2021-02-11 11:45:25 +09:00
Ruslan Kuprieiev fa550f91c3 cache: move unrelated helpers out of CloudCache class (#5439)
* cache: move transfer to objects

Moving it as is for now.

* cache: move link() to checkout
2021-02-10 07:49:27 +02:00
Batuhan Taskaya 384a4113ca update: support --jobs even without --to-remote (#5434) 2021-02-09 17:58:59 +02:00
Peter Rowlands (변기호) b370465209 exp show: customize rich table rendering behavior (#5381)
* exp show: use rich native pager support

* exp show: re-enable column header styles

* utils: add custom classes to override rich table collapse behavior

* set final collapsed column width to 1 to allow ellipsis
2021-02-09 23:34:04 +09:00
Peter Rowlands (변기호) 748bda8950 exp run: clean untracked lockfiles before running (#5426)
* exp run/reset: clean untracked lockfiles

* update log level for run -f/--force message
2021-02-09 21:38:46 +09:00
Ruslan Kuprieiev 47bdf523d7 cache: add: accept object id and it's path (#5431)
Unnatural leftover from previous PRs
2021-02-09 08:15:50 +02:00
Ruslan Kuprieiev 0fdb119ed5 cache: move save() to objects (#5430) 2021-02-09 04:56:45 +02:00
Ruslan Kuprieiev 226c68f8ff dvc: introduce File/Tree cache objects (#5424) 2021-02-09 04:00:06 +02:00
Saugat Pachhai 02a0d1af52 stage: implement add/create methods in stage loader (#5397)
* stage: add/create methods in stage loader

* minor fixups
2021-02-08 18:30:10 +05:45
Peter Rowlands (변기호) eeeaa9d324 exp: cleanup executor temp directories ourselves (#5425) 2021-02-08 16:30:24 +09:00
Paweł Redzyński a7da6c2a80 live: support cache and no_cache use case (#5394)
* live: add cache functionality

* live: add tests for cache use case
2021-02-07 03:51:18 +02:00
Ruslan Kuprieiev b75aab4d85 utils: don't use unnecessary exists() calls (#5423)
* utils: file_md5: only return hex

* utils: file_md5: don't use exists()

* checkout: don't call exists()
2021-02-06 16:13:58 +02:00
Ruslan Kuprieiev f30d51ca42 cache: use add() to add objects to cache (#5421)
This makes all cache objects go through the same logic, so that all of
the appropriate rules are applied to them (e.g. proper permissions).

Pre-requisite for #5337
2021-02-06 03:07:29 +02:00
Ruslan Kuprieiev 4b938c5104 gha: temporarily disable macos 11
Those workers take a very long time to start up.
2021-02-06 03:07:17 +02:00
Peter Rowlands (변기호) 550cbb5e11 git: update dulwich dependency to 0.20.18 (#5420) 2021-02-05 20:43:44 +09:00
Batuhan Taskaya 5900151ebd gs: fix test failures caused by .ls() (#5419) 2021-02-05 12:09:04 +02:00
Pavel Ditenbir e7686f90a6 term: replace 'DVC-file' and 'stage file' with correct terms (#3960) (#5280) 2021-02-05 16:01:42 +09:00
RogerandPeter Rowlands e97f8fe19c pygit2, gitpython: handle relpaths in reset/checkout_index backends (#5414)
* Make minor change that fixes things for some reason

While working on vscode-dvc, both mattseddon and I found that this change allows
us to run `dvc exp run`, and we cannot otherwise.

I don't exactly know what this change does, and I'm hoping someone more
knowledgable does- after which we can give this PR a better name.

* test relpaths in git backend funcs which take path lists

* gitpython: handle relpaths in reset and checkout_index

* use / for git relpaths on windows

Co-authored-by: Peter Rowlands <[email protected]>
2021-02-05 12:05:51 +09:00
Ruslan Kuprieiev 320715a5f8 dvc: move checkout out of cache (#5415)
Checkout is an operation that builds object from cache(aka object
database) in a particular tree(aka filesystem). Thus it shouldn't be
part of the object database itself nor of the filesystem.

Pre-requisite for #5337
2021-02-05 03:27:00 +02:00
Ruslan Kuprieiev 8676617ae0 cache: separate save() and checkout() (#5412) 2021-02-05 01:54:46 +02:00
Batuhan Taskaya e7cc7765ac config: Support ~/.aws/config parsing (#5378)
* config: Support ~/.aws/config parsing

* skip when the ~/.aws/config doesn't exist

* Expand windows paths properly
2021-02-04 14:58:42 +02:00
Saugat Pachhai 21200c14df refactor webdav to use client; extract write_text to base (#5411) 2021-02-04 16:48:25 +05:45
Batuhan Taskaya ce554b6ae7 tree: Introduce the new .ls() API (#5275)
* s3: faster directory hashing

* Introduce Tree.info() API

* tests for s3

* Implement support for azure

* New .ls() API

* remove _iter_hashes from other trees

* assert recursive

* yield strings instead of file_infos

* Add GSTree.ls()

* Add DETAIL_FIELDS to the GSTree

* WebDAVTree.ls()

* proper webdav root directory initalization

* disable arguments-differ
2021-02-04 12:10:42 +02:00
Peter Rowlands (변기호) 79dafe8310 exp gc: prune any exp/exec refs which point to gc'd experiments (#5408) 2021-02-04 17:50:04 +09:00
Peter Rowlands (변기호) 982802effc experiments: bug fixes (#5407)
* exp run: display metrics on -m/--metrics

* exp show: fix sort handling
2021-02-04 16:42:22 +09:00
Ruslan Kuprieiev b87e76aa84 tree: stop using self.path_info (#5403)
* webhdfs: don't use self.path_info

* azure: don't use self.path_info

* webdav: don't use self.path_info

* oss: don't use self.path_info

* http: don't use self.path_info

* gdrive: don't use self.path_info
2021-02-03 20:50:30 +02:00
Ruslan Kuprieiev 2c81bf1748 api: use Repo.open (#5401) 2021-02-03 18:39:40 +02:00
Batuhan Taskaya adedd19c52 gha: switch to pre-commit/action for caching (#5389) 2021-02-03 17:14:46 +02:00
Ruslan Kuprieiev 985aef883e cli: config: rename --repo to --project (#5399)
Per https://github.com/iterative/dvc.org/pull/2064

Not changing the internal terminology because we stick to Repo in a lot
of places there and adjusting all of it would be wasteful. Maybe in the
future.
2021-02-03 17:13:53 +02:00
Ruslan Kuprieiev ffaac0f088 repo: remove unneeded filter (#5393)
Leftover from #5385
2021-02-03 17:13:36 +02:00
Batuhan Taskaya fc77618ad1 update: implement --to-remote flag (#5343)
* upload: implement --to-remote flag

* unit tests

* preserve frozen= for stage
2021-02-03 12:31:16 +02:00
Saugat Pachhai b2c2929869 pyarrow support for Python3.9 (#5391) 2021-02-02 17:31:11 +02:00
Batuhan Taskaya a92bfd272d get, get-url: add --jobs option (#5362) 2021-02-02 15:49:58 +02:00
Paweł Redzyński 31e4a3325d live integration: rename report to html (#5366) 2021-02-02 13:44:38 +01:00
Saugat Pachhai 67980de44d tests: mock global config dir for Updater (#5386) 2021-02-02 17:08:09 +05:45
Ruslan Kuprieiev 8932e0be84 dvc: get rid of can_be_skipped logic (#5385) 2021-02-02 13:13:48 +02:00
Batuhan Taskaya 9ad1e0e159 ssh: show total-size when uploading/downloading on ssh remote (#5383) 2021-02-02 11:27:19 +02:00
Saugat Pachhai 4348832b44 keep stage.dump and dvcfile.dump in sync (#5382) 2021-02-02 15:11:13 +05:45
Peter Rowlands (변기호) d05be36694 exp show: cleanup UI issues (#5361)
* exp show: add -n/--num to display the last N commits from HEAD

* exp run: fix empty commit with no changes

* add test for exp show with multiple commits

* allow -n greater than num commits in repo

* review fix
2021-02-02 11:35:02 +09:00
Peter Rowlands (변기호) 2ea5ef07dd exp apply: force apply by default (#5360)
* exp apply: force apply by default

* update tests
2021-02-02 11:07:19 +09:00
Batuhan Taskaya 8205c84077 pull, fetch: optimize memory consuption by ~%35 (#5346) 2021-02-01 18:46:02 +02:00
Saugat Pachhai b020918f5d stage:add: use remainder as a command script (#5350)
* stage:add use remainder as a command

* use -c for checkpoints

* use -n for stage name, make it required, fix tests
2021-02-01 17:50:04 +02:00
Batuhan Taskaya c84833bab1 params: support unicode characters (#5363)
* params: support unicode characters

* open remove() calls with utf-8
2021-02-01 15:56:53 +02:00
Paweł Redzyński 13db267a5d collect: unify message for no file in workspace (#5339) 2021-02-01 14:25:22 +01:00
Batuhan Taskaya b337396144 cache pip directory on github actions (#5365) 2021-02-01 13:48:03 +01:00
Piotr Bielak 3feaa86696 Pass missing glob flag to pull command (#5354)
The `--glob` flag is parsed from the command line, but in the
`CmdDataPull` class in the invocation the parameter (`self.args.glob`)
is not passed to the `pull` method implementation. Hence, always the
default value (False) is used. This commit fixes this issue and sets the
`glob` parameter properly.
2021-02-01 14:21:21 +02:00
Saugat Pachhai 131eb7b3a1 don't allow loading stages without cmd (#5373)
* don't allow loading stages without `cmd`

This was loosened when introducing parametrization.
But, `foreach`..`do` should be considered first
before stage's regular structure. And, cmd is made
required (as it should have been)

Related: #5371, #5370, #5312

* fix schema
2021-02-01 13:23:40 +05:45
Peter Rowlands (변기호) b771b06b25 tests: reduce checkpoint experiment iterations (#5352)
* exp tests: reduce default checkpoint run iterations from 5 to 2

* api: reduce make_checkpoint sleep interval
2021-02-01 14:20:51 +09:00
Peter Rowlands (변기호) 08300d3bcf experiments: drop exp resume in favor of auto-resuming in exp run (#5351)
* exp: auto determine checkpoint resume rev based on workspace state

* exp: add 'dvc exp reset' for reset/restarting checkpoints

* update tests

* fix reset usage
2021-01-29 13:18:20 +09:00
Batuhan Taskaya bb46043689 add: implement saving straight to cache (#5301)
* add: implement saving straight to cache

* Adjust CLI error cases to handle -o

* make tests pass on windows

* support explicit --jobs

* simplify the implementation a bit

* polish the tests
2021-01-28 14:42:58 +02:00
Ruslan Kuprieiev fe2d913821 list: don't clone local repos (#5341)
* repo: introduce Repo.open

* ls: don't clone local repos
2021-01-28 03:05:05 +02:00
Ruslan Kuprieiev 969a85efb6 RepoTree: get rid if implicit fetching (#5324) 2021-01-27 02:01:09 +02:00
Saugat Pachhai b1d6918c5a stage collection: do not collect .gitignored dvcfiles (#5265)
* Do not collect .gitignored dvcfiles

* fix exc

* consistent error message

* fix imports

* add tests
2021-01-26 14:51:02 +02:00
Batuhan TaskayaandRuslan Kuprieiev a5e19ecd6a tree: Implement upload_fobj protocol to all trees (#5307)
* tree: Implement upload_fobj protocol to azure

* tree: Implement upload_fobj protocol to google storage

* tree: Implement upload_fobj protocol to google drive

* tree: Implement upload_fobj protocol to hdfs

* tree: Implement upload_fobj protocol to oss (alibaba cloud)

* tree: Implement upload_fobj protocol to webhdfs (also some cleanup)

* tree: Implement upload_fobj protocol to http/https

* Add _generate_download_url() to the HTTP (for tests)

* tree: Implement a temporary upload_fobj protocol to webdav/webdavs

* Unify progress bars

* Lazily evaluate the file size, for http uploads

* Remove copy_fobj protocol and _transfer_file_as_whole

* show progress bar on cache transfer

* Simplify s3 upload

* Remove CHUNK_SIZE (except for some special cases)

* remove concurrency restrictions

* Update dvc/tree/oss.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2021-01-26 13:57:51 +02:00
Ruslan Kuprieiev 434cd305e9 dvc: ignore errors on protect/set_exec (#5335)
Both of these are not fatal and could safely be ignored.

Fixes #5255
2021-01-26 03:46:14 +02:00
Ruslan Kuprieiev 8112ce36d1 dvc: move 'verify' property from tree to cache (#5334) 2021-01-26 02:00:35 +02:00
Ruslan Kuprieiev 6a1f3659f5 tests: limit pytest version by <6.2.2 (#5333) 2021-01-25 22:01:54 +02:00
Batuhan Taskaya ec82c3fff5 tests: azure directory generation (#5328) 2021-01-25 15:41:29 +02:00
Peter Rowlands (변기호) d4757120c9 exp show: cleanup data column handling (#5329)
* account for identical param/metric names in columns (disable filtering)

* update filtering
2021-01-25 22:19:20 +09:00
Saugat Pachhai 9f0b86f221 unit.scm.test_git: use pytest instead of unittest (#5325) 2021-01-25 10:23:46 +05:45
Ruslan Kuprieiev 48dbcf9163 repo: remove unused find_out_by_relpath (#5323) 2021-01-25 01:24:32 +02:00
Saugat Pachhai 3741f730e0 show git-add hint on stage:add, refactor scm_context (#5290)
* Refactor scm_context, show git-add hint on stage:add

* move tests
2021-01-24 22:43:38 +02:00
Ruslan Kuprieiev 58c97b963c hash_info: add __str__ (#5319) 2021-01-24 22:40:38 +02:00
Paweł Redzyński 6a9c0c5ce5 live/checkpoints: test continue (#5300)
* live: test resuming

* initial test, not working

* experiments: introduce DVCLIVE_RESUME env variable on resume
2021-01-24 22:06:37 +02:00
Saugat Pachhai 1d8761e0c9 Remove empty file tests/unit/command/test_commit.py (#5316)
When I opened it, I got confused for a second and had to double-check if it was really empty.
The empty file gives a false assurance. We do need to add one, which I'll add later, but for now,
we better remove it.
2021-01-23 18:27:43 +05:45
Paweł Redzyński 2277a1a655 metrics/plots/params: return ordered (#5297)
* metrics/plots/params: return ordered

* metrics/params/plots: add tests for return order
2021-01-22 20:57:43 +02:00
Ruslan Kuprieiev cbd7472079 dvc: refactor cache saving (#5314) 2021-01-22 19:07:36 +02:00
Batuhan Taskaya 7b59108379 push: Gradual task allocation for pushing big datasets (#5285) 2021-01-22 15:29:06 +02:00
Ruslan Kuprieiev c4888cbd7d dvc: get rid of ExternalRepo (#5309) 2021-01-21 23:03:53 +02:00
Ruslan Kuprieiev 9203d33a3c reqs: tests: remove awscli (#5306) 2021-01-21 18:09:35 +02:00
Timofey Yefimovandkarajan1001 60d83a74d7 dvcignore: Fix incorrect ignored output computation (#4986)
* dvcignore: Fix incorrect ignored output computation

* dvcignore: Fixed unignored patterns

Unignored patterns were shown in the dvc status output and caused Error.

Also made a bit of a refactoring in `matches` and `ignore`.

* dvcignore: Fix functional tests

Not sure if I follow, but tests assume that unignored files should trigger `check-ignore`. What was the intention?

* dvcignore: Fix stage.wdir being MagicMock

* Use `is_ignored` instead of `check_ignore`

1. use is_ignored to judge DVC ignore status.
2. use check_ignore to show ignore patterns.

* Solve tests fails

* Add a new tests

* Return to the old check_ignore logic.

* Pass the pylint

* Simplify ignore tests

Co-authored-by: karajan1001 <[email protected]>
2021-01-21 14:28:08 +02:00
Saugat Pachhai 689ae22fb4 Fix code-style issues in the parsing module (#5299) 2021-01-20 17:21:57 +05:45
Batuhan Taskaya b5068a9200 checkout: don't filter hash's if it is a file (#5298) 2021-01-20 12:36:47 +02:00
Ruslan Kuprieiev c6ce69dd30 erepo: move misc stuff out of erepo (#5296)
* erepo: move get_rev to repo

* erepo: move fetch defaults to external_repo
2021-01-20 07:08:00 +02:00
Ruslan Kuprieiev 7cbcd4a7be erepo: separate caching and repo (#5295)
* erepo: don't pass for_write

* erepo: move get_cache_dir out of ExternalRepo
2021-01-20 00:25:15 +02:00
Ruslan Kuprieiev 03fc531a5c pylint: enable no-else-return (#5294)
From https://github.com/iterative/dvc/pull/5270#discussion_r559440586
2021-01-19 22:12:46 +02:00
Ruslan Kuprieiev d038bca05e tests: add basic [web]hdfs mockers (#5279) 2021-01-19 20:03:16 +02:00
Saugat Pachhai d8982c6db7 Override __str__ implementation for StageNotFound (#5289)
This happens because KeyError quotes the error message.
We'd still like to keep StageNotFound being a KeyError
semantics, but workaround the __str__ implementation.

Mypy was having some problem with the previous `__str__`
override.
2021-01-19 18:33:18 +05:45
Saugat Pachhai 372d6f045b Introduce stage:list command with autocompletions (#5282)
* Implement stage:list

* change targets help message
2021-01-19 17:35:55 +05:45
Batuhan Taskaya 51ada6d272 Implement straight-to-remote functionality (#5198)
* Implement export-to-remote functionality

* Redistrubition of functionality from tree to remote

* Pass chunk size into the open_url

* Move functionality away from remote to cache

* Basic progress bars for both transfer types

* Proper job calculation

* Redesign .open() usage in order to work with different trees

* Align the signatures of other open() functions to accept **kwargs

* Implement chunked uploading to the ssh

* Use local cache to compute directory hash (md5)

* S3: multipart upload test

* Wrap local cache's hash_to_path with PathInfo

* Use .isdir() instead of a try/except statement for calculating the from_infos

* New CLI layout (add/import-url)

* Adjust CLI tests to accept new arguments

* tests: import_url/add func/unit tests

* Introduce cache.transfer()

* Invalid CLI combination tests

* Store dir_info key in the transfer_directory

* LocalTree.upload_fobj

* Tests for utils

* Lazy imports on some places

* --to-remote migration (and repo._transfer)

* Use proper temporary location, more efficient pbar calc

* Directory transfer logic rewrite

* Use shutil.copyfileobj()

* Rebase...

* Better tests

* Inline transfer operation to .add() / .transfer()

* Open in the 'rb' mode for the tests

* More tests, better invalid option handling at repo level

* .read() certain amount of bytes to validate the content

* Better invalid argument handling

* Better progress bars by using .getsize() API

* For fallback upload, use the from_info.name instead of temporary naem

* introduce --jobs for add/import-url
2021-01-19 12:53:32 +02:00
Saugat Pachhai 55df6b8a6a cli: remove: Show traceback on verbose (#5287) 2021-01-19 15:10:56 +05:45
Pavel Ditenbir 8b0e0b995e cli: remove: fix error message for the case when file is not under control (#4497) (#5236)
* cli: remove: fix error message for the case when file is not under control (#4497)

* cli: remove: fix error message for the case when file is not under control (#4497)

fixes after code review

* cli: remove: fix error message for the case when file is not under control (#4497)

2nd round of fixes after code review

* cli: remove: fix error message for the case when file is not under control (#4497)

3rd round of fixes after code review
2021-01-19 15:04:19 +05:45
Peter Rowlands (변기호) 407f5607a2 scm: implement pygit2 resolve rev and native checkout (#5270)
* pygit2: implement get/set/remove refs

* pygit: implement iter_refs

* pygit: implement resolve_rev

* pygit2: implement working tree checkout

* pygit2: implement reset, explicitly read/write index when used

* pygit2: checkout does not need to reset index

* pygit2: handle "-" branch shorthand in checkout

* tests: install post-checkout hook should be run via gitpython

* exp apply: use stricter exception handling for merge conflict

* pygit: fix index read/write usage

* gitpython: handle untracked file conflicts in stash apply

* fix flaky/race index issue
2021-01-18 17:41:25 +02:00
Saugat Pachhai 78e12327bc use repo.stage.create_from_cli to create stages (#5281) 2021-01-18 18:55:19 +05:45
Ruslan Kuprieiev 4f2de7a3ef tests: remove psutil checks (#5283)
Leftover from https://github.com/iterative/dvc/pull/5232
2021-01-18 13:50:21 +02:00
Asif Saif UddinandRuslan Kuprieiev f8c9797f2a add psutil to install requirement (#5232)
* add  psutil to install requirement

* info: remove unneded checks

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2021-01-18 12:30:38 +02:00
Paweł Redzyński 28f6beaf78 checkpoints/live: move execution env to stage (#5257)
* checkpoints/live: move execution env to stage

* stage: env: provide DVC_ROOT for all repo runs
2021-01-18 10:35:07 +02:00
Saugat Pachhai 628f87845c introduce stage:add command (#5263)
* Implement stage:add

* fix tests

* Add short flag -C for checkpoints
2021-01-18 10:05:36 +02:00
Ruslan Kuprieiev 273518afab hdfs: copy: don't read everything into memory (#5277)
Regression from #4973 and #4747
2021-01-16 00:57:13 +02:00
Ruslan Kuprieiev b027760e12 dvc: move protect from tree to cache (#5271) 2021-01-14 21:10:53 +02:00
Ruslan Kuprieiev a2cac8b423 tests: upgrade azurite and enable loose mode (#5272)
* tests: upgrade azurite and enable loose mode

Azure tests started failing recently. They still work with real azure, so this is on azurite side.

* tests: use cloud instead of workspace

also gdrive doesn't support it yet :(
2021-01-14 20:07:24 +02:00
Ruslan Kuprieiev 3e42e015d6 dvc: redefine is_callback as cmd and no outs/deps (#5187) 2021-01-14 17:23:20 +02:00
Saugat Pachhai 257f58450a refactor run; move to stage.utils (#5262)
* refactor run; move to stage.utils

* Update dvc/repo/run.py

* fix pylint

* skip on run-cache
2021-01-13 21:19:36 +02:00
Batuhan Taskaya c840cecc91 tree: implement getsize() (#5261)
* Implement Tree.getsize() API

* Remove azure from tests

* strip support for recursive size calculation

* _getsize() -> getsize()
2021-01-13 18:35:57 +02:00
Batuhan Taskaya e5f91f5c7f cache: don't initalize cloud cache until its needed (#5264) 2021-01-13 17:01:06 +02:00
Paweł RedzyńskiandSaugat Pachhai 2ee68b01b0 system: prevent mypy from checking windows-related method (#5260)
* system: prevent mypy from checking windows-related method

* Update dvc/system.py

Co-authored-by: Saugat Pachhai <[email protected]>

Co-authored-by: Saugat Pachhai <[email protected]>
2021-01-13 13:48:57 +02:00
Saugat Pachhai f8200ca2b8 move parsing params to utils (#5259)
* move parsing params to utils

* fix tests

* move chunk_dict and test it

* update test
2021-01-13 13:36:49 +02:00
Peter Rowlands (변기호) 228b3b16e5 experiments: exp res bug fixes (#5258)
* dvc repro: fail on checkpoint stages

* exp res: check for moved HEAD/baseline before resuming

* exp run/res: always consider last checkpoint as applied for workspace runs
2021-01-13 17:49:46 +09:00
Peter Rowlands (변기호) 1ab1abf62a git: implement merge/checkout via pygit2 (#5245)
* git: implement merge (gitpython, pygit2)

* pylint: add pygit2 as C extension

* git: s/checkout_paths/checkout_index/ and add pygit implementation

* git: support basic checkout/merge conflict strategies in checkout_index

* fix relpath issue in pygit merge

* git: add stubs for add --update

* experiments: drop remaining gitpython specific scm usage
2021-01-12 13:26:27 +02:00
Saugat Pachhai 49974c075c dvc: bump to 2.0.0a0 (#5250)
Looking at the log is confusing, as the version is stuck in `1.11.0` whereas the recent one is `1.11.10`.

As the updater is also dependent on that version, it prints an ugly update message while running too.
https://github.com/iterative/dvc/issues/5023#issuecomment-758450519
2021-01-12 09:24:57 +02:00
Saugat Pachhai a2c91d2058 scm: implement gitignore (#5243)
* scm: implement gitignore; use `pygit2` backend

* Add is_ignored implementation on `pygit2` and `gitpython` backends.
* Also implement `._reset()` on `repo.scm` which is most of the changes.

* fix tests

* use _backend_func

* minor fixups
2021-01-11 12:31:26 +02:00
Batuhan TaskayaandRuslan Kuprieiev 7fd191126e S3Tree._list_paths now recognizes the max_items limit (#5239)
* S3Tree._list_paths now recognizes the max_items limit

* Use modern approach + add a test

* refactor tests

Co-authored-by: Ruslan Kuprieiev <[email protected]>

* lint

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2021-01-11 11:54:42 +02:00
Batuhan Taskaya 5374880220 s3: Allow authentication using session token (#5240)
* S3: Allow authentication using session token

* Add a little test
2021-01-11 10:48:57 +02:00
Ruslan Kuprieiev c83a4ea406 erepo: refactor download/fetch logic (#5234)
* erepo: don't use _get_tree_for

* erepo: refactor download/fetch logic

* dvc: unify handling existing output
2021-01-10 10:48:36 +02:00
Dmitry Kropachev 6b05cc5e24 dvc.command.diff: add renamed detection feature (#5160)
fixes #5150
2021-01-09 13:49:03 +09:00
Saugat Pachhai 388eccad13 fix mypy errors; add mypy precommit hooks (#5237) 2021-01-08 16:05:52 +02:00
Saugat Pachhai ccc17917cf Make few imports lazy on dvc.main (#5233)
* Moved exception handling imports wherever they occur
* Move dvc.tree and dvc.external_repo imports to it's
  nearest use, as it is expensive for `--version`.
* Move analytics import to when it's used
* Move format_link to place where error occurs
2021-01-07 15:55:34 +02:00
Paweł Redzyński 386d0c463a live: add report switch (#5223) 2021-01-07 13:52:21 +02:00
Saugat Pachhai c04cdaf70f Split config and schema (#5229)
so as not to compile a schema unless needed
2021-01-07 13:47:14 +02:00
Yihao JiangandSaugat Pachhai fa5487ebfe show more info when successfully init dvc repo (#5190)
* show more info when successfully init dvc repo

* Ignore 'line too long' linter error

* simpler solution

* black reformat

* don't break dvc init --no-scm case

* Update dvc/repo/init.py

Co-authored-by: Saugat Pachhai <[email protected]>

* Update dvc/repo/init.py

Co-authored-by: Saugat Pachhai <[email protected]>
2021-01-07 16:05:35 +05:45
Saugat Pachhai 9331ad23e4 Misc. imports lazy on utils,stage.cache and dvc.* (#5228) 2021-01-07 15:03:34 +05:45
Saugat Pachhai 66fc988ebf Make imports on dvc.repo and dvc.repo.plots lazy (#5227) 2021-01-07 15:02:31 +05:45
Saugat Pachhai 40122cefa5 parsing: make imports lazy (#5225)
* parsing: make imports lazy

* Locally declare default params file
2021-01-07 15:00:54 +05:45
Saugat Pachhai a9ccf70439 cmd: imports lazy (#5226)
They are not a major pain point though
2021-01-07 11:45:58 +05:45
Saugat Pachhai a9d620beda utils.serialize: lazy imports (#5224) 2021-01-07 11:44:37 +05:45
Saugat Pachhai af2a12a188 Use 'schema' top-level element in the lockfile for version (#5222) 2021-01-06 20:08:14 +02:00
mbiesekandRuslan Kuprieiev 00461bb5df commit: support granularity (#4297) (#5088)
* commit: support granularity (#4297)

* commit: support granularity

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2021-01-06 19:39:04 +02:00
Saugat Pachhai 0db41b94fc parametrization: do not load parser on import (#5217)
A great benefit to this is that, if the dvc.yaml file
is never interpolated, the parser is never built, which
reduces the gap even further between 1.0 and 2.0 files
2021-01-06 21:40:19 +05:45
Saugat Pachhai 2646c0d6bc Make some imports lazy for plots (#5219) 2021-01-06 17:41:30 +02:00
Saugat Pachhai 1cc646614f Misc. imports made lazy: scm,experiments,stage (#5218) 2021-01-06 17:41:12 +02:00
Saugat Pachhai 84e9c1b0de lazy imports on repo command (#5221) 2021-01-06 17:41:01 +02:00
Saugat Pachhai 4f692b0b69 Make repotree lazy (#5220)
Also removed dvcfile related imports on dvc.tree.repo.
This was making imports slower on dvc.main
2021-01-06 17:40:31 +02:00
Batuhan TaskayaandRuslan Kuprieiev 6fb84f641c checkout: fix partial file checkouts in the same directory (#5163)
* Fix partial file checkouts in the same directory

* unittest => pytest

* Try to rationalize changed() to take filter_info

* Use path.read_text() to verify the directory structure

* Try to use specialized hash, fall back to general one if doesn't exist

* Cover subdirectories in hash specialization

* coverage: add a test for empty subdirectories

* Move out the dir_info filtering block

* hash_origin => path_info

* cleanup

* Move hashing functionality after the existence checks

* Simplify it a bit

* Update dvc/cache/base.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>

* Apply suggestions from code review

Co-authored-by: Ruslan Kuprieiev <[email protected]>

* Apply suggestions

* simplify tests

* change filenames

* Some visual changes for consistency

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2021-01-06 14:21:00 +02:00
Saugat Pachhai 9b73a745b4 Misc imports lazy (#5215) 2021-01-06 17:50:24 +05:45
Saugat Pachhai 4c53e636bc Move misc. imports from command, make them lazy (#5214) 2021-01-06 17:44:02 +05:45
Saugat Pachhai 40bd838c11 config command: lazily import (#5211) 2021-01-06 16:03:06 +05:45
Saugat Pachhai 709e121b31 package: Increment shtab's version (#5212) 2021-01-06 15:48:33 +05:45
Saugat Pachhai 0cc5227f47 Add versioning scheme in the dvc.lock (#5128)
* Add versioning scheme in the dvc.lock

* remove whitespace

* use actual size in tests assumptions
2021-01-06 15:44:19 +05:45
Saugat Pachhai 4f89db8e0d analytics: lazily import (#5210) 2021-01-06 15:32:27 +05:45
Peter Rowlands (변기호) 37f890444a diff: account for deleted workspace data (#5205)
* diff: add test for deleted data in addition to .dvc file

* diff: handle deleted data when checking for missing cache
2021-01-05 17:52:24 +09:00
Ruslan Kuprieiev b779a117ab add: handle long filenames (#5201) 2021-01-05 10:33:07 +02:00
Saugat Pachhai c0fbdb3531 Lazily load git backends (#5203) 2021-01-05 09:46:24 +02:00
Srinivas Reddy Thatiparthy (శ్రీనివాస్ రెడ్డి తాటిపర్తి) a017b87db5 Remove redundant comment (#5199) 2021-01-04 16:35:29 +02:00
Saugat Pachhai c4970a016f debug functions: have a good default value (#5202)
I had to keep providing default value before when profiling
simple functions as an API. I added a default now, and
also made `debugtools` customizable so that you could
edit the code to, for example, have `pdb` enabled by default.
2021-01-04 20:16:42 +05:45
Peter Rowlands (변기호) cffc019e1f experiments: fix logging bugs (#5195)
* experiments: use relpaths when tracking changed files

* experiments: suppress SCM hints in tempdir executors

* experiments: move multiprocessing error logging into child

* exp run: clarify help text for --temp
2021-01-04 19:25:13 +09:00
Saugat Pachhai 8010005364 Reraise exception after pdb.post_mortem() (#5197)
Otherwise, `continue` etc. would fail
2021-01-04 15:34:04 +05:45
Saugat Pachhai 261ddc947b Add pyinstrument: a statistical profiler (#5196)
* Add pyinstrument: a statistical profiler

It is easier than cprofile, in that it can show stacktrace
of where things are slow.

Also refactored/moved debugging related utils to a separate
module

* use ipdb first, then fallback to pdb

* use ipdb first, then fallback to pdb
2021-01-04 15:23:40 +05:45
Paweł RedzyńskiandRuslan Kuprieiev 862ab8de62 live logs initial (#4902)
* plots: allow dir plots

* first scenario

* separate dvclive tests

* rename index to step

* another test

* refactor

* its ok to have step in data

* sort plots

* introduce --logs

* add logs to run commands

* variable name refactoring

* fix logs option

* fixup

* cmd: run: fix unit tests

* plots: modify: fix tests

* improve dir handling

* fixup

* dvclive: logs summary

* api: introduce logs summarization method

* handle no repo case

* fix granular dir plots

* api: dvclive: simplify

* dvclive: roll back logs flag

* cmd: introduce logs

* api: adjust to logs

* test: dvclive fix

* rename logs to dvclive

* live: command: add uninitialized

* outs: dvclive type

* dvclive: support providing config via evn variables

* dvclive: treat as metrics

* dvclive integration: add support for summary on/off

* dvc integration: add temporary test for configuration export

* remove requirements

* cmd: introduce logs

* rename logs to dvclive

* cmd: introduce logs

* remove logs leftowvers

* dvclive: env: adjust to chekpoints env variables

* command: live: remove unnecessary help

* fix linter suggestions

* type hinting

* review refactor

* replace dvclive with live

* stage: loading: move live loading to separate method

* stage: serialize: allow only single live output

* live: add show/diff

* stage: serialize: roll back single live enforcing

* fixup

* rebase fixups

* output: live: force single instance

* live: command: add unit tests

* Update dvc/output/base.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>

* Update dvc/stage/__init__.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>

* Update dvc/stage/serialize.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>

* review v2

* live: fix summary tracking by experiments

* naming fixups

* api: live: add tests

* tests: api: separate live tests

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2021-01-04 11:29:41 +02:00
Jorge Orpinel 19606ba211 dvc: std/update --tragets options (#5193)
to match iterative/dvc.org/pull/2062
2021-01-04 09:16:12 +02:00
Ruslan Kuprieiev 7df53e5e26 dvc: handle multiline command as list (#5194)
Implements an approach proposed by @skshetry.

Fixes #4373
2021-01-04 09:11:57 +02:00
Michael StegemanandOLOLO ALALA 764960fbfc cli: config: show merged config with --show-origin (#5188)
* cli: config: show merged config with --show-origin

Builds on #5126 and #5184 by showing the full merged config when
listing or getting config options.

References #5126
References #5184
References iterative/dvc.org#2028
Fixes #5119

* Add additional tests.

* Add test for merged config.

* config: rename methods

Co-authored-by: OLOLO ALALA <[email protected]>
2021-01-04 06:43:31 +02:00
Ruslan Kuprieiev 948633782e run-cache: skip unhashable stages (#5185)
Fixes #5182
2020-12-31 06:28:07 +02:00
Ruslan Kuprieiev ceaba68f62 config: temporarily forbid --show-origin without level (#5184) 2020-12-31 00:54:52 +02:00
Ruslan Kuprieiev 8162bb159c config: read merged config by default (#5179)
* config: read merged config by default

* tests: move root test out of config tests

* tests: refactor config tests

* cli: fix cache dir command
2020-12-31 00:37:39 +02:00
Paweł Redzyński c28e671f47 tests: plots: fix plots call (#5183) 2020-12-30 22:59:32 +02:00
Michael StegemanandRuslan Kuprieiev 34ed545d64 cli: config: add support for --show-origin (#5126)
* cli: config: add support for --show-origin

Support the --show-origin option for config, which, similar to git,
prefixes each config option with the source file it originated from.

Fixes #5119

* Reformat with black.

* Simplify logic for local config files.

* Add test for --show-config output.

* Update tests/func/test_config.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-12-30 21:56:20 +02:00
Pavel Ditenbir 23811e028c cmd: implement '--show-md' flag for 'dvc metrics show' (#4770) (#5161) 2020-12-30 02:17:41 +02:00
Ruslan Kuprieiev 6fcafc9121 erepo: make repo configuration explicit (#5174) 2020-12-29 14:32:29 +02:00
Peter Rowlands (변기호) db555ec66b experiments: fix checkout error for unexisting outputs (#5175)
* stage: support explicit allow_missing in checkout

* exp: explicitly clear EXEC_BRANCH and checkout with allow_missing on
starting experiment run

* git: wrap stash apply errors with SCMError

* git: add `git status` to backends

* exp: reset staged git changes before workspace exp runs to avoid merge
conflicts

* test untracked (and staged) pipeline file and deps for both workspace
and tmpdir runs

* experiments: explicitly track pipeline files and uncached deps for exp
commmits

* fix exp typing errors
2020-12-29 18:07:21 +09:00
Dmitry Kropachev c64b4ce78f dvc/utils/stream: make IterStream seekable and peekable (#5084)
fixes #5021
2020-12-28 23:34:24 +02:00
Saugat Pachhai 01a4473c20 parametrization: fix error when the file in vars is a directory (#5171)
* Fix error when the file in `vars` is a directory

* Fix on windows

* mock tree only
2020-12-28 23:03:09 +02:00
Saugat Pachhai 77ff24260e Remove Sourcerer's hall of fame (#5168)
Sourcerer is gone: https://github.com/sourcerer-io/sourcerer-app/issues/632
2020-12-28 16:42:05 +02:00
Peter Rowlands (변기호) 7a92ff5e5d experiments: commit with --no-verify in executors (#5078)
* git: add no_verify flag for commit()

* experiments: commit with --no-verify in executors

* git: wrap hook exceptions with SCMError

* update hook file perms

* update dulwich to 0.20.15

* update pygit2 commit def

* skip hook tests on windows
2020-12-28 17:35:35 +09:00
Ioana Grigoropol fa4bf40eb4 api: add globbing option for pushing (#5033)
* api: add globbing option for pushing

Related: #4816

Signed-off-by: Ioana Grigoropol <[email protected]>

* api: use utility function for push command

Signed-off-by: Ioana Grigoropol <[email protected]>
2020-12-25 23:50:36 +02:00
Jorge Orpinel 11382cc891 cmd: help output updates (per docs) (#4999)
* cmd: matching help output to docs
per https://github.com/iterative/dvc.org/pull/1971#pullrequestreview-538749492
and https://github.com/iterative/dvc.org/pull/1978#pullrequestreview-540445147

* fetch: simplify help text
per https://github.com/iterative/dvc.org/pull/1978#pullrequestreview-540447055

* commit: update help output to match iterative/dvc.org/pull/1978

* fetch: update help output to match iterative/dvc.org/pull/1978

* commmit: help output update (again)

* remove: simplify help output
per https://github.com/iterative/dvc.org/pull/1971#pullrequestreview-538749492

* remove: simplify help output
per https://github.com/iterative/dvc.org/pull/1971#pullrequestreview-538749492

* commit: update -h output
per https://github.com/iterative/dvc.org/pull/1989#pullrequestreview-542464568
2020-12-25 06:05:42 +02:00
Dmitry KropachevandRuslan Kuprieiev eb8e03a465 dvc/tree/local: fix it for no repo/cache case (#5118)
* dvc/tree/local: fix it for no repo/cache case

fixes #5069

* Update dvc/output/base.py

* Update dvc/output/base.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-12-25 05:56:04 +02:00
devramx 0bb3f40be8 remove: delete .gitignore file if empty (#5148)
* remove: delete .gitignore file if empty

modified behavior to unlink empty .gitignore files.
added corresponding unit & func test cases.
updated failing test cases.

Fixes #4962

* fix failing lint checks

* integrating feedback from the code review
2020-12-23 23:04:19 +02:00
Peter Rowlands (변기호) afe96974c3 lock: fix AttributeError in dvc.lock.HardlinkLock (#5152) 2020-12-23 16:03:59 +09:00
Ruslan Kuprieiev a6151eaf9e erepo: get rid of get_checksum (#5145) 2020-12-22 15:07:32 +02:00
Peter Rowlands (변기호) 76cc70a86b experiments: cleanup minor UI issues (#5147)
* exp apply: show passed exp name in CLI output instead of resolved git sha

* exp res: warn that --name will be ignored when resuming without
modifications

* exp gc: don't re-use gc parameter check message

* exp: update help strings

* exp: warn if exp dir contains untracked files after tmpdir execution
2020-12-22 16:08:20 +09:00
Paweł Redzyński 4d36bc4de8 params: introduce --targets (#5122)
* params: add targets option

* fixup

* remove unnecessary changes

* fix isort

* params: diff: add test for --targets

* tests: roll back metric/plots unification, introduce collect test

* metrics/plots: refactoring

* missing plots test added
2020-12-22 04:19:13 +02:00
Peter Rowlands (변기호) af26d54524 git: show progress during dulwich push/fetch (#5143) 2020-12-21 17:12:50 +09:00
Saugat Pachhai 4b30b8bea6 parametrization: move comment, fix exception error msg (#5142) 2020-12-21 13:04:55 +05:45
Ruslan Kuprieiev 5db7a4cab6 tree: local: remove misc redundant logic (#5139)
Non-lazy logging, fspath, redundant exist check, etc.
2020-12-20 17:56:13 +02:00
dcea83e75b [WIP] dvc: preserve exec bit for tracked files (#5061)
* #4578 added is_user_executable to dvc file

* Addressed PR feedback and introduced test_add_executable

Also fixed all tests in test_add.py

* Added a test to checkout executable file

* Pop isexec in dvc/output/__init__.py:loadd_from

* Implement test that file is executable after checkout

* Address PR feedback

* Address PR feedback

* Changed BaseTree.isexec to use path_info as an argument

* Update tests/func/test_add.py

* Update tests/func/test_add.py

* Update tests/func/test_add.py

* Update tests/func/test_add.py

* fix formatting

* add: preserve exec

* checkout: fix setting exec

* tests: refactor checkout exec test

* tests: fix windows cases

* tests: add: fix exec test on windows

* Update dvc/tree/local.py

* Update dvc/output/base.py

* Update dvc/output/base.py

* Update dvc/cache/base.py

* Update tests/func/test_add.py

* tests: add: skip whole exec test on windows

* save isexec to lockfiles

* fix formatting

Co-authored-by: Ruslan Kuprieiev <[email protected]>
Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-12-20 02:31:06 +02:00
Ioana GrigoropolandRuslan Kuprieiev f51bef040a pull: add glob option (#5032)
* api: add glob option for pull command

Related: #4816

Signed-off-by: Ioana Grigoropol <[email protected]>

* api: add globbing utility function

Related: #4816

Signed-off-by: Ioana Grigoropol <[email protected]>

* api: use utility function for pull command

Signed-off-by: Ioana Grigoropol <[email protected]>

* Update dvc/utils/__init__.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-12-19 21:38:40 +02:00
Ruslan Kuprieiev 2ce617c642 git: add pygit2 skeleton (#5138)
Implements stuff we need for the tree, but this is more to start filling
up pygit2 with something useful.
2020-12-19 02:44:02 +02:00
Peter Rowlands (변기호) f5d46c2fdf experiments: run single experiments in user's workspace by default (#5124)
* experiments: move executor into a module

* experiments: separate executor into base/local

* rename LocalExecutor to TempDirExecutor, run in .dvc/tmp by default

* git: restore HEAD using reset not checkout in detached_head()

* experiments: support calling BaseExecutor.reproduce directly for
workspace runs

* experiments: run single experiments in local workspace by default

* update experiments tests for workspace changes

* experiments: update checkpoints for workspace runs

* update checkpoints tests

* update test_show

* add force param for apply

* exp apply: add --force option

* exp run/res: add --temp option to force tempdir execution for single
experiments

* force apply in non-apply specific tests
2020-12-18 18:55:26 +09:00
Saugat Pachhai 7e2520c02a Cleanup debug log messages when loading stages (#5125)
* Cleanup log messages when loading stages

1. Simplified callback stage debug message
2. Reduced no lock entry message to trace
   If lockfile does not have some entry, it will simply complain
   that lockfile is not uptodate once.
   If there is no lockfile or empty lockfile, it will complain that
   lockfile not found.
3. Annoying parametrization related wdir-interpolated debug message
   removed.

* Update dvc/stage/__init__.py

* black
2020-12-18 10:38:24 +02:00
Saugat Pachhai 723477ed5a Load from only two revisions in params/metrics (#5121) 2020-12-18 10:33:10 +02:00
Saugat Pachhai 76c6c54278 Fix backport branch 2020-12-18 12:02:13 +05:45
Saugat Pachhai dcce3a31a9 params diff: skip params dependency for vars (#5123)
* Skip paramdependency for vars

Previously, `configs` which was a list of PathInfo was being used
to check if the `vars` should be skipped or not.

But, the file is stored as a string that made that check redundant
which might have caused #5117 as they were being stored twice,
once as a expanded paramdependency and next as a flattened dict
from vars, which later during `flatten`, it might have panicked
from that duplication.

* Use defaultdict

* change add_vars to collect_vars
2020-12-18 12:00:06 +05:45
Ruslan Kuprieiev 64dfaffd7c [WIP] repro: simplify the logic for frozen stages (#5114)
Fixes #5082
2020-12-16 14:28:00 +02:00
Ruslan Kuprieiev aec8d65791 repro: accept multiple targets (#5111)
Currently, we iterate over targets manually in CLI, which results in a
complicated code and a DAG collection/check overhead. With this PR we
collect the DAG once, figure out all the needed targets within it and
just reproduce them in one pass.
2020-12-16 03:25:18 +02:00
Ruslan Kuprieiev 8a38611e26 config: adjust help per docs
Per https://github.com/iterative/dvc.org/pull/2022
2020-12-15 22:13:29 +02:00
Peter Rowlands (변기호) 6b9d842b26 experiments: handle exp run from within subdirs/subrepos (#5093)
* experiments: add test cases for stage in subdir & subrepo

* git: fix relpath handling in dulwich add()

* repro: move exp file git staging from stage.run into repro

* experiments: support running experiments from outside DVC root

* experiments: setup logger inside multiprocessing (executor) context

* git: fix dulwich add in submodules
2020-12-15 19:54:04 +02:00
João Rafael 84a3a59bc7 Remove version restriction from networkx (#5110) 2020-12-15 19:43:32 +02:00
Sandeep MistryandRuslan Kuprieiev 31502aee0a diff: add support for --targets (#4938)
* wip: add target support (back) to dvc diff

* Change diff param to targets and update repo.diff accordingly

* Update dvc diff --targets with PR review feedback

* Tests for dvc diff --targets

* Update dvc diff --targets with PR review feedback

* Update dvc diff --targets with PR review feedback and additional tests

* split out new diff target tests as per PR comment

* remove unnecessary cache dir removal from setup_targets_test

* Update dvc/command/diff.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-12-15 16:19:26 +02:00
Saugat Pachhai 2512df7ca1 Prevent loading non-dict vars (#5107) 2020-12-15 19:21:31 +05:45
Saugat Pachhai 11c94b10f2 disallow overriding item and key in the foreach loop (#5104)
* Allow reserving keys in the context to prevent overriding

* Use nullcontext from funcy (3.6 Python)

* Display path in error message
2020-12-15 19:06:39 +05:45
Saugat Pachhai 6cad3c95dd Remove key/value magic strings (#5106) 2020-12-15 19:06:23 +05:45
Saugat Pachhai aa4ec8acd5 Skip building graph for dependencies without path_info (#5103)
RepoDependency for example don't have any path_info

See: https://github.com/iterative/dvc/pull/4938#issuecomment-743709176
Related: #5035
2020-12-15 16:34:39 +05:45
Ruslan Kuprieiev b0b04d44b1 dvcignore: don't look forward (#5098)
Prevents us from duplicating the work by walking into directories
searching for subrepos. Saves around ~1sec (5.8 -> 4.8) in
`dvc metrics diff` in a big git-only repo.

Related to https://github.com/iterative/dvc/pull/4284#issuecomment-664033700
2020-12-14 23:32:59 +02:00
Ruslan Kuprieiev fa76c79ee9 Update mergify.yml 2020-12-14 22:21:53 +02:00
Ruslan Kuprieiev 78e0434dd3 Update mergify.yml 2020-12-14 21:01:06 +02:00
6e92352001 fix making too many ssh connections when pushing run cache (#4732) (#4733)
* fix making too many ssh connections when pushing run cache (#4732)

* reformat

* generator should exit on GeneratorExit exception

* Update dvc/tree/ssh/__init__.py

* pool: close on Exception and not BaseException

Co-authored-by: Tomasz Łakota <[email protected]>
Co-authored-by: Ruslan Kuprieiev <[email protected]>
Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-12-14 16:43:20 +02:00
Ruslan Kuprieiev 89c7f984ad Create mergify.yml 2020-12-14 16:40:54 +02:00
Saugat Pachhai 620d082b0b refactor resolver and optimize (#5077)
minor refactors, code comments

Rename interim to definitions

provide location of entry during init for definition

small fixups to set_temporarily

Refactor loop to use dict comprehension

Provide same interface for definitions

Make it work for composite lists

Check for sequence rather than a list

refactor tests

Fixes #4955
Fixes #4957
Fixes #4963
2020-12-14 19:22:15 +05:45
Ruslan Kuprieiev 62afee1e2d Delete backport.yaml
Doesn't work well because of https://github.com/tibdex/backport/issues/33
2020-12-14 12:43:20 +02:00
Ruslan Kuprieiev c5496870e5 cli: config: check name format (#5090)
Currently we don't handle bad name well:

```
(dvc-3.8.3) ➜  dvc git:(config-bug) ✗ dvc config section
ERROR: unexpected error - not enough values to unpack (expected 2, got 1)
```

With this PR, we check name to conform to either `remote.name.option` or
`section.option`, so we'll give a more friendly:

```
(dvc-3.8.3) ➜  dvc git:(config-bug) ✗ dvc config section
ERROR: argument name: name argument should look like remote.name.option or section.option
usage: dvc config [-h] [--global | --system | --local] [-q | -v] [-u] [-l] [name] [value]

Get or set config options.
Documentation: <https://man.dvc.org/config>

positional arguments:
  name           Option name (remote.name.option or section.option).
  value          Option value.

optional arguments:
  -h, --help     show this help message and exit
  --global       Use global config.
  --system       Use system config.
  --local        Use local config.
  -q, --quiet    Be quiet.
  -v, --verbose  Be verbose.
  -u, --unset    Unset option.
  -l, --list     List all defined config values.
```
2020-12-14 12:25:46 +02:00
Ruslan Kuprieiev a217e4a18b gha: add backport bot 2020-12-14 12:24:30 +02:00
Batuhan Taskaya 526f615647 Update config.py (#5087) 2020-12-13 23:11:03 +02:00
Ruslan Kuprieiev 1dafbd8361 system: _reflink_darwin: use fsencode for path (#5085)
Fixes #5083
2020-12-13 02:29:26 +02:00
Batuhan Taskaya d3acbfe2b1 Implement dvc config --list (#5080)
* Implement dvc config --list

* Add test for successful CLI exit on --list

* Use dvc.utils.flatten, remove parser groups

* Move the test
2020-12-12 13:33:38 +02:00
GaoandSaugat Pachhai 96a97ec0e4 New option jobs for dvc import (#4977)
* fix #4838

`jobs` option for `dvc import`

* option `jobs` effected

* Pass the unit tests parse

* Adding a functional test.

* Pass `jobs` to `run` and remove some pylint

* Update dvc/stage/__init__.py

Co-authored-by: Saugat Pachhai <[email protected]>

* remove unused param

* Solve test fail on windows

Co-authored-by: Saugat Pachhai <[email protected]>
2020-12-12 01:32:21 +02:00
Peter Rowlands (변기호) 7f52170465 fix remaining typing errors (#5079) 2020-12-11 16:40:19 +09:00
Ruslan Kuprieiev 1331651fe1 git: don't list all commits to check for an empty repo (#5075)
Saves us around 2sec (from ~7.5 to ~5.5) when parsing gitlab repo.
2020-12-10 14:40:27 +02:00
Saugat Pachhai 189e88f1db Run graph checks on collect/find_outs_by_path (#5035)
We try to optimize `tree.exists` calls and probably few others
in that they either look directly into the workspace or,
to the cache without running graph checks. It does not seem
to be possible just to run graph checks on `find_outs_by_path`
due to those optimizations.

So, that's why, the `collect` also does a graph check for this
reason.

Fixes #5027
Fixes #4010
2020-12-10 12:20:45 +02:00
Peter Rowlands (변기호) 9967d52885 git: fix mypy errors (#5073) 2020-12-10 18:51:26 +09:00
Peter Rowlands (변기호) e0884df99c exp show: fix --exclude-... (#5071)
* exp show: fix --exclude-...

* update exp show tests

* fix windows table divider
2020-12-10 18:11:00 +09:00
Peter Rowlands (변기호) 09cec69429 experiments: fix mypy errors (#5072) 2020-12-10 18:08:36 +09:00
Ruslan Kuprieiev 3b23dfebae git: use backend-provided trie in GitTree (#5044) 2020-12-09 23:51:23 +02:00
Johnny Chen 13bb56aa8e dvc info: follow the symlink when deciding filesystem type (#5067) 2020-12-09 16:38:52 +02:00
Peter Rowlands (변기호) 7bf9a0e805 experiments: support experiments cache collection for exp push/pull/gc (#5064)
* experiments: add utils.exp_commits for iterating over experiment commits

* repo: add support for all_experiments in brancher and used_cache

* git: make all_commits return commits in standard refs instead of --all

* gc: add --all-experiments to preserve cache for experiments

* update gc tests

* repo: support pushing and fetching specific revs internally

* exp push: push cache for experiment by default

* exp pull: pull cache for experiment by default

* fix empty git repo bug
2020-12-09 22:35:06 +09:00
Saugat Pachhai a7d665157c parametrization: disable set keyword (#5063) 2020-12-09 14:26:47 +05:45
Ruslan Kuprieiev 475fe544f0 tests: fix windows tests (#5054)
* [WIP] git: use dulwich for add/commit

* dulwich: add is_tracked implementation

* experiments: try to workaround windows cleanup() bug on py < 3.8

* tests: experiments: convert CRLF to LF before comparing params
2020-12-08 23:41:09 +02:00
Saugat Pachhai 995287bbba Pin numpy for windows build (#5055) 2020-12-08 18:07:14 +02:00
Peter Rowlands (변기호) 51fa0f5c38 experiments: add dvc exp push/pull/list (#5042)
* git: add support for on_diverged in push_refspec

* experiments: update executor for push-refspec changes

* experiments: implement push()

* tests: add tests for experiments.push

* git: add iter_remote_refs

* roughly equivalent to `git ls-remote --refs`

* experiments: add experiments.list for listing local and remote
experiments

* refactor git upstream/downstream in tests

* experiments: add experiments.pull

* experiments: add CLI commands for push/pull/list

* exp list: drop short options

* experiments: s/list/ls/

* dulwich: catch not repo errors when creating remote client

* fix remote rev resolution for list

* fix command test

* add tests for push/pull with ambiguous experiment names
2020-12-08 21:20:03 +09:00
metalglove a41e23a453 Improve OverlappingOutputsError exception message. (#5015)
* trie: updated message for OverlappingOutputsError

the message given by the exception was not verbose enough why this exception is thrown.
now gives the user more information as to why the overlapping paths should be changed.

* test_run_single_stage: fixed test TestShouldRaiseOnOverlappingOutputPaths

the test is now updated to reflect the exception message changes.

Signed-off-by: metalglove <[email protected]>
2020-12-08 17:28:53 +05:45
Peter Rowlands (변기호) 821ec0a934 git: drop scm requirement from backends (#5049)
* git: remove scm from backends, make backends configurable

* git: implement get/set/remove ref in gitpython

* git: support reflog --rewrite for stash

* tests: support testing individual backends in unit/scm/test_git

* stash: create reflog if it does not exist for gitpython backend
2020-12-08 20:37:16 +09:00
OrangeCubeandGary Yuan 6c7eccf5fd remote: fix pull error for large file with aliyun-oss (#5053)
Fixes #5051

Co-authored-by: Gary Yuan <[email protected]>
2020-12-08 13:00:20 +02:00
Peter Rowlands (변기호) b072370efb experiments: handle state where tmp args file was committed to git (#5052)
* git: fix Stash.push args typing

* experiments: handle case where args file was inadvertently committed to
git

* add test for args file being committed to git
2020-12-08 18:10:53 +09:00
Ruslan Kuprieiev c4f92314db repro: remove deprecated --cwd (#5048)
Per https://github.com/iterative/dvc/issues/2988
2020-12-08 03:26:51 +02:00
Victor f09c27e74c update issue template (#4991)
* update issue template

* try to add comments
2020-12-07 13:17:50 +02:00
Saugat Pachhai 529073967e run/repro: show command on dry-run, add ">" to script output (#5041)
* run/repro: show command on dry-run, add ">" to script output

This rollbacks the earlier commit to not show command during
the dry-run, and also prepends ">" in the command script
instead of "$" sign.

* fix tests
2020-12-07 13:16:15 +02:00
Peter Rowlands (변기호) 69eb86a06f experiments: ignore untracked files (#5029)
* git: add reset()

* experiments: exclude untracked files

* experiments: add test for untracked & staged files

* git: support checkout/reset for specific paths to HEAD state

* stage: track changed files when calling checkpoint callback

* experiments: ignore untracked files when generating exp commits

* add tests for dirty lock files

* stage: track uncached outs when generating checkpoints

* experiments: update checkpoints scm handling

* update checkpoints tests
2020-12-04 17:04:45 +09:00
Peter Rowlands (변기호) 9a9f46b2b8 scm/git: add support for resolving experiment names (#5026)
* experiments: move exp ref iterators into .utils

* git: support resolving experiment short names

* update tests

* remove change unrelated to this PR
2020-12-04 15:23:08 +09:00
Ruslan Kuprieiev 5152866634 scm: move logic to gitpython backend (#5022) 2020-12-03 20:25:20 +02:00
Saugat Pachhai f49feddb7f provide a callback to handle error during the stage collection (#5020)
also provide a callable to properly handle error messages
add tests
2020-12-03 19:05:56 +05:45
Clément WalterandRuslan Kuprieiev 94f2a1d3be Allow user to pass a list of commands for one single stage (#4934)
* Allow list type in STAGE_DEFINITION scheme and join(cmd) if list

* Remove join in Stage.__init__ and update test

* Loop over commands in cmd_run

* Improve logging of stage.run with detailed by command

* Fix test based on log message

* Update command logging with >

* Add tests for multi lines command

* Update > to $ in stage command log to prevent confusion with > operator

* Fix PR review about test: single quote and read_text

* tests: unify/clarify tests

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-12-03 12:49:30 +02:00
Peter Rowlands (변기호) 8252ae5f3c experiments: enable by default, support no_scm (#5019)
* experiments: enable by default, support no_scm

* mark experiments option obsolete
2020-12-03 10:21:19 +02:00
Saugat Pachhai bb84f5d9e2 Enable parametrization by default (#5018)
* Enable parametrization by default

* Remove a way to disable parametrization

* Add a comment for the feature flag
2020-12-03 10:20:29 +02:00
jwokatyandRuslan Kuprieiev f50f12ad61 hdfs: Replace deprecated pyarrow.hdfs.connect (#4973)
* hdfs: Replace deprecated pyarrow.hdfs.connect

Replace with pyarrow.fs.HadoopFileSystem, which has a different API.

Fixes #4905

* hdfs: fix copy/walk

* hdfs: Remove setting CLASSPATH

* Revert "hdfs: Remove setting CLASSPATH"

This reverts commit 8925a5d1d16ddbfc5e80c9b7974d96abdd4620a5.

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-12-02 21:46:35 +02:00
alex lustenberg 7e4966fbe5 increase log level of messages to trace (#5017)
* cache/base - check start, stop, assumed unchanged if RO
* command/base - assumed default
* state - fetched
* utils - assumed default
* utils/fs - path inode
2020-12-02 20:59:04 +02:00
Indhu Kumarandindh8717 2e9fac5fb3 Organize metrics as table (#4706)
* WIP fixes #4621

* changes to all_branch if condition

* flatten_dict changes

* fix one test

* minor changes

* minor changes

* add precision

* tests and added precision to arguments

Co-authored-by: indh8717 <[email protected]>
2020-12-02 19:46:48 +02:00
Ruslan Kuprieiev 2d70bc5ec4 dvc: bump to 1.11.0 2020-12-02 17:47:23 +02:00
Peter Rowlands (변기호) 176a23e291 experiments: store experiments in refs/exps instead of heads (#4910)
* experiments: commit to refs/exps/... instead of branch heads

* update exp show

* restore checkpoint_resume kwarg

* refactor new() to use experiments stash

* experiments: use refspecs to populate and collect executors

* experiments: use new stash and merge on exp checkout

* experiments: fix executor chdir

* experiments: update run and show

* skip checkout after reproduce

* checkpoints: use experiments refspecs

* check if workspace was stashed before applying

* exp show: unify checkpoint/standalone experiment collection

* git: move iter_refs to backend

* update for scm changes

* experiments: replace exp checkout in favor of exp apply

* git: handle rev_parse errors in resolve_commit

* update refs containing and baseline check

* fix sigint handling for checkpoints

* update checkpoint exists behavior

* fix checkpoint baseline behavior

* update tests

* experiments: update gc

* update show tests

* fix type checking errors

* update diff

* scm: add diff, describe

* experiments: generate human readable names

* exp run/resume: allow user specified exp name with -n/--name

* update tests

* experiments: add `exp branch` for promoting experiment to full branch

* fix test

* fix missing file

* experiments: explicitly close scm instances before cleaning up tmp dir
2020-12-02 21:20:57 +09:00
Ruslan Kuprieiev c884c94d61 tree: update hint about missing deps (#5005)
Related to #4997
Fixes #4987
2020-12-01 17:57:55 +02:00
Paweł Redzyński f6f18bc201 plots: make linear and scatter interactive (#4975) 2020-12-01 14:55:30 +01:00
Saugat Pachhai 3a3fa7cdde parametrization: convert bool to string as "true"/"false" (#5004)
Previously, Python's `str()` was being used that
resulted in boolean transformed into "True"/"False".

Fixes #4996
2020-12-01 16:05:18 +05:45
Saugat Pachhai 1e1a0999f6 parametrization: show interpolated vars in params diff (#5002)
* parametrization: show interpolated vars in params diff

* Add test for params.diff

* disable logging duration if feature is not enabled
2020-12-01 15:43:37 +05:45
Ruslan Kuprieiev 7f0537038c dvc: preserve desc on add/run/imp (#5003) 2020-12-01 11:52:31 +02:00
Ruslan Kuprieiev 7c45711e34 dvc: preserve comments/meta on add/imp/run (#5001) 2020-12-01 10:39:51 +02:00
Peter Rowlands (변기호) 1e52bc94a3 git: add support for working with refspecs and stashes (#4952)
* git: add support for dulwich Stash

* git: start adding support for custom refs

* git: add Stash and helper context managers for preserving
workspace state

* scm: cleanup refs/stash and add push_refspec

* tests: add tests for git refs and stash

* fix push_refspec

* tests: add tests for git push_refspec

* scm: add git fetch-refspecs

* add test for fetch refspecs

* scm: add test for git refs containing

* remove extraneous log msgs

* fix typing issues

* stash: only try to push if workspace is dirty

* update dulwich to 0.20.14

* scm: move dulwich into scm.backend

* move stash internals to backends

* scm: move git code into dvc/scm/git/... module

* git: use ABC for BaseGitBackend

* fix rebase issue

* git: fix refspec tests on windows

- git file:// URLs need posix style paths

* git: move get_refs_containing to backend

* scm: add callback for handling diverged refs during fetch
2020-12-01 16:56:50 +09:00
Artem Dudarev 38cbd998e1 dvc: more explict error during init when .dvc is ignored by git (#4978)
* dvc: more explict error during init when .dvc is ignored by git

Fixes #3738

* dvc: address PR feedback

* styling

* styling
2020-11-30 21:00:17 +02:00
Ruslan Kuprieiev 09a88b8ea6 stage: move is_cached logic to Stage.can_be_skipped (#4994)
Having that logic in `create_stage` is unnatural and also has some
side-effects for unsuspecting users. For example, it will check the
filesystem for existing dvcfile and will try to load a corresponding old
stage. I've adjusted it with minimal changes to better reflect the logic
behind it.

Pre-requisite for #4907
2020-11-30 15:11:35 +02:00
Saugat Pachhai bc1d10532a Move Repo.collect and collect_granular to Repo.stage (#4993) 2020-11-30 14:28:16 +05:45
Jorge Orpinel 56322d0a53 cmd: matching help output to docs (#4989)
* cmd: matching help output to docs
per https://github.com/iterative/dvc.org/pull/1971#pullrequestreview-538749492
and https://github.com/iterative/dvc.org/pull/1978#pullrequestreview-540445147

* fetch: simplify help text
per https://github.com/iterative/dvc.org/pull/1978#pullrequestreview-540447055
2020-11-30 09:09:11 +02:00
Saugat Pachhai f4f0554884 repro: support glob/foreach-group to run at once through CLI (#4976)
* repro: support regex/foreach-group to run at once

Fixes #4912
Fixes #4886
Fixes #4958

* Use `tree.isdir` rather than `os.path.isdir`

* Use glob rather than regex

* Update dvc/command/repro.py

* s/regex/glob

* disable glob on `collect_granular`

There's no need for a glob here

* add tests for `collect` and `collect_granular`
2020-11-27 19:13:38 +05:45
Saugat Pachhai 011bd18e9f parametrization: support loading vars partially (#4982) 2020-11-27 19:05:02 +05:45
Saugat Pachhai 6d745eb4f0 s3: use mocked s3 instead of the real one (#4968)
* s3: use mocked s3 instead of the real one

Not removing the fixtures just yet

* Update moto

Tests was failing, because of a change in `responses` package, which
`moto` was mocking. This was fixed on
https://github.com/spulec/moto/pull/3466 which is only available on
`dev122` release.

* Use mocked s3 in checkout tests
2020-11-26 11:30:43 +05:45
Saugat Pachhai 788d840e7d tests/ci: update docker image for Azurite (#4967)
`Azure` tests are flaky for some reason. When I investigated one
of the job [1], something seemed wrong with the Azurite or
the client lib.

On `AzureTree.blob_service`, we do the following to check if the
container exists:
```python
try:
    container_client.get_container_properties()
except ResourceNotFoundError:
    container_client.create_container()
```

When looking at the test log, I found out that the
`get_container_properties` was failing with
`ErrorCode:ContainerNotFound` whereas `create_container`
successively was failing with `ErrorCode:ContainerAlreadyExists`.

`create_container` is correct, but `get_container_properties` is
incorrect, as many of the Azure tests have already run successfully
before.

So, either the Azurite has/had bug or the client lib has a bug.
Anyway, we'll find out after updating the Azurite.

[1]: https://github.com/iterative/dvc/runs/1448864706#step:7:4029
2020-11-25 21:21:00 +02:00
Saugat Pachhai 7586c32d95 Remove libs used in pre-commit to avoid duplication (#4966) 2020-11-25 18:30:28 +05:45
Saugat Pachhai dcba0e8da4 pre-commit: update hooks (#4964) 2020-11-25 17:57:32 +05:45
Saugat Pachhai 1294450a46 parse: parse target correctly for generated stages (#4961) 2020-11-25 16:13:17 +05:45
Ruslan Kuprieiev c989b28eae dvc: bump to 1.10.2 2020-11-24 18:33:49 +02:00
Saugat Pachhai abb78ce19d parametrization: print pretty interpolation errors (#4953)
* parametrization: print pretty interpolation errors

* Fix col/loc in exception msg

* Fix tests
2020-11-24 18:26:26 +05:45
Saugat Pachhai bc4c9fe9ea parametrization: prevent dumping parametrized stages (#4950)
* parametrization: prevent dumping parametrized stages

This is a bit hackish, but does the job done.
It feels like we need some unified way to be able to load
and dump, but not sure how. Till then, this dataclass
will be helpful in communicating between those.

* s/Meta/RawData

* Update dvc/stage/loader.py
2020-11-24 17:40:47 +05:45
Saugat Pachhai d138bdd72a Add mypy config, fix type errors in a few remaining places (#4951)
* Add mypy config, fix type errors in a few random places

* Add typing_extensions

* Use setup.cfg for mypy's config
2020-11-24 14:19:55 +05:45
Saugat Pachhai 7c3c2e8123 Fix typechecking errors - 2 (#4946) 2020-11-24 11:18:11 +05:45
Saugat Pachhai 04c53601c0 types: add types to dvc/tree (#4947) 2020-11-24 11:16:28 +05:45
Saugat Pachhai 3ba70d375e types: fix type errors on dvc.collect (#4948) 2020-11-24 11:16:02 +05:45
Saugat Pachhai 85ad7358d9 types: fix type checking errors in dvc.experiments (#4949) 2020-11-24 07:22:42 +05:45
Saugat Pachhai cb56f57908 Fix typechecking errors around a few places (#4945) 2020-11-23 19:00:45 +05:45
Saugat Pachhai a7a007e4f9 Fix typechecking errors in dvc/repo (#4944) 2020-11-23 17:27:04 +05:45
Saugat Pachhai e33274723d Move flags from tests to config (#4943) 2020-11-23 16:21:24 +05:45
Saugat Pachhai f1efc8f409 tests: increase ulimit to 4096 (#4942) 2020-11-23 11:22:15 +02:00
Saugat Pachhai 1a2c91eb5a Use PathStringTrie consistently (#4941)
This one was missed for some reason.
2020-11-23 12:44:43 +05:45
Saugat Pachhai 806f8032d9 ci: reduce no. of MacOS jobs (#4940)
This was increasing the wait times of the CI.

Also enabled codecov failure to not fail the whole CI pipeline.
2020-11-23 12:12:58 +05:45
Saugat Pachhai 8cb4202292 Fix metadata tests running with module-scoped fixtures (#4939)
I ran it this way before, to make it run fast. But, it's not ideal
for running with xdist.
2020-11-23 11:45:45 +05:45
Saugat Pachhai 5faae2a6e9 test: allow selectively running tests for remotes (#4920)
Some of the tests are run by default, other requires
`--enable-{remote}` to run. The remotes that are run by default
also have a `--disable-{remote}` flag. All of the remotes can be
enabled using `--all` flag.

To use this functionality in the fixture,
`test_config` fixture has to be used, and requires to call
`test_config.requires(remote)`.
In an individual test, you can simply use `pytest.mark.remote`
to make them skip or run.
2020-11-23 11:43:39 +05:45
Saugat Pachhai 6237a665c3 tests: indices syntax for parametrization (#4930) 2020-11-20 20:19:47 +05:45
Saugat Pachhai 011bd35a6c parametrization: change foreach stage name joiner from "-" to "@" (#4929) 2020-11-20 19:49:46 +05:45
Saugat Pachhai 133018af78 s/foreach..in/foreach..do (#4928) 2020-11-20 19:47:58 +05:45
Saugat Pachhai c65fdcabae tests: add error message tests for resolving stages (#4927) 2020-11-20 19:42:59 +05:45
Saugat Pachhai e1692a6be0 parametrization: improve error messages on foreach loop (#4926) 2020-11-20 18:54:12 +05:45
Saugat Pachhai c8f37fdb88 parsing: improve error messages when resolving keys of the stages (#4925) 2020-11-20 18:16:23 +05:45
Saugat Pachhai 7f66db7039 parametrization: improve 'vars' error message (#4924)
* Catches MergeError
* Catches params file not existing error
2020-11-20 17:46:34 +05:45
Saugat Pachhai 470f411cd6 parametrization: improve set error message (#4923) 2020-11-20 17:08:05 +05:45
Saugat Pachhai d721e919e4 parametrization: improve key error message (#4922) 2020-11-20 16:38:32 +05:45
Saugat Pachhai c03fbb0056 parametrization: raise appropriate error message on merge error (#4921) 2020-11-20 16:04:33 +05:45
Saugat Pachhai cb3e52829b interpolation: support [] indices syntax (#4918)
Via pyparsing. The error message is not that great, as it's greedy.
But, it does point out where the mistakes are, correctly most of the
time. :)

Also raises appropriate error messages for ParseError
2020-11-20 14:31:16 +05:45
Saugat Pachhai 4b7d130003 Skip already used vars, even if they are in a relative path (#4917)
Previously, "dir/../params.yaml" would have thrown an OverwriteError
2020-11-20 14:10:26 +05:45
Saugat Pachhai d5312e56a1 parametrization: Move resolver to StageLoader from Dvcfile (#4916) 2020-11-20 13:38:57 +05:45
Ruslan Kuprieiev 2a6c4291ce dag: support output as target (#4908)
Fixes #4904
2020-11-20 06:57:12 +02:00
Saugat Pachhai 40c3bbd1ba Change use and vars to just vars (#4880)
The schema is changed of `vars` to accomodate the both use of
`imports` and setting variables locally.

Now:
```
vars:
  - params.yaml
  - foo: foo
    bar: bar
```

Also, same distinction occurs for the stages.
2020-11-20 09:19:13 +05:45
Sami Jawhar 92b17e3a89 Fix exp checkout queued and cleanup (#4914) 2020-11-20 09:16:12 +09:00
Sami Jawhar 153c374c02 experiments: pop checkpoint resume from kwargs (#4913) 2020-11-20 09:12:13 +09:00
Ruslan Kuprieiev 4cf2f8139b dvc: bump to 1.10.1 2020-11-19 21:21:41 +02:00
Peter Rowlands (변기호) c2ec8b27ca exp: fix bug where repro with run-cached stages may fail (#4911) 2020-11-20 00:57:34 +09:00
Arun MahapatraandRuslan Kuprieiev 6324bd8a69 dvc: include cache urls in info output. (#4848)
* dvc: include cache urls in info output.

Show cache urls for external and remote cache in the info output
to assist in diagnosis of issues.

Fixes #4574.

* Remove unnecessary constant check for generator expression.

* Add remotes and caches in version command for better diagnosis.

* Fix flaky unit test to be runtime independent by using regex search.

* Use Cache.by_scheme() for enumerating supported caches.

* Add get_cloud_schemes routine for retrieval of configured remote schemes.

Add tests for local remote and remote:// scheme for info.

* Cleanup unused code.

* info: refactor remote parsing

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-11-19 05:59:33 +02:00
Ruslan Kuprieiev 251a3269c2 dvcfile: add desc field for outs/stages (#4901)
Outs desc will be used by viewer right away. Stage desc is for people to
start using now, but we won't be using it it viewer until DAG support is
added there.
2020-11-18 22:06:52 +02:00
Ruslan Kuprieiev b340254556 deps: freeze dataclasses version for rich
https://github.com/iterative/dvc/runs/1417985315?check_suite_focus=true
2020-11-18 17:02:22 +02:00
Paweł Redzyński 7501302baa metrics/plots: common tests: check for particular exceptions (#4865) 2020-11-18 14:14:19 +02:00
Ruslan Kuprieiev d7564e9965 dvc: bump to 1.10.0 2020-11-18 03:17:12 +02:00
Ruslan Kuprieiev d06dd3caa9 readme: replace travis with gha 2020-11-18 02:55:58 +02:00
Ruslan Kuprieiev 9741f6d706 gha: rename "Tests build" -> "Tests" 2020-11-18 02:55:24 +02:00
Tomás Farías SantanaandRuslan Kuprieiev ca2e6a004c Support for WebHDFS (#4747)
* Add support for WebHDFS

* Style and linting fixes

* Implement abstract methods and disable no-member pylint errors

* Add init unit test

* Yield client and fix redefinition of hdfs

* Get dynamic hdfs Docker port

* Add support for open in WebHDFSTree

* Remove hdfscli_config fallbacks in WebHDFSTree

* Use port 50070 for webhdfs

* WebHDFSTree supports http URL and proper walking

* Fix failing unit test after refactoring

* Add overwrite argument to upload

* Add remaining func tests for webhdfs

* Remove print statements

* Allow dynamic attributes in pylint for hdfs library

* Comment error on instantiating HDFS client from config

* Remove depth argument from call to walk

* Update dvc/tree/webhdfs.py

* Remove unnecessary deque

* Check exception messages hdfs client failure from config

* Return client directly

Co-authored-by: Ruslan Kuprieiev <[email protected]>

* Split long line

* Add size to HashInfo

* Implement move via hdfs.rename

* Add missing line

* Make parent directories before moving file in WebHDFS

* Add total to progress bar

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-11-17 20:02:02 +02:00
Peter Rowlands (변기호) 40bdf613f0 experiments: preserve file permissions across tmpdir execution (#4890)
* tree: use file_mode in upload() for trees that support modes

* experiments: preserve file_mode for files in the workspace

* skip permissions tests on windows

* LocalTree: separate chmod from protect

* fix rebase issue
2020-11-18 00:47:57 +09:00
Peter Rowlands (변기호) 379ed8e6fc experiments: support branching w/modified params from intermediate checkpoint (#4898)
* experiments: Allow branching from checkpoints with modified hyper params

* experiments: initial checkpoints tree functionality for `exp show`

* go back to flat/sortable view

* add test for resume w/modified params

* tests: move experiments fixtures into conftest

* add show checkpoints test

* test checkpoint table rendering

* remove dead code

* test show for checkpoint branches
2020-11-17 21:09:46 +09:00
Ruslan Kuprieiev 2f6a8a7349 gha: upgrade checkout and setup-python 2020-11-17 05:54:33 +02:00
Ruslan Kuprieiev d5b7f7cf2c gha: upgrade setup-python to 2.1.4 2020-11-17 05:50:34 +02:00
Ruslan Kuprieiev 1788fe95dd gha: upgrade setup-python action to v2 2020-11-17 05:46:56 +02:00
Peter Rowlands (변기호) 96ead9425a ci: add macos-11 @ py3.8, py3.9 to build matrix (#4892) 2020-11-16 15:11:24 +09:00
Ruslan Kuprieiev f4349aa09b dvc: add --pdb flag (#4895)
This is a hidden flag (no docs needed) for a handy shortcut to pdb when
we need it. E.g. binaries built by pyinstaller are much easier to debug
with it.
2020-11-15 02:30:13 +02:00
Ilya PekelnyandRuslan Kuprieiev 2f620e9226 4504: configuration-based job limit. (#4872)
* Job count expected in a core config

Besides defaulted 4 * core_count number of workers sourced in the
"tree", the configuration file based value expected and prioritised.

* Multiply the configured value by cpu cores count.

* Don't multiply by the core count twice.

* A couple more usages and a question note.

* Don't multiply a number of jobs to a cpu count

Number of jobs set in a config file directly used to spawn workers.

* Remove the obsolete note.

* Disallow 0 jobs set in a config

* `jobs` config-based value is optional

* Apply suggestions from code review

Co-authored-by: Ruslan Kuprieiev <[email protected]>

* CPU count based default

* Update dvc/tree/base.py

* Happy path unit test

* Update tests/unit/remote/test_remote.py

* Fix the accidentally removed blank line

* Update dvc/tree/base.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-11-13 21:30:02 +02:00
Paweł Redzyński d3931def53 tests: real_s3 fixture: use specific path (#4884) 2020-11-12 16:36:08 +01:00
Saugat Pachhai 3ec64301b9 Send TAP result to flaky service only on master (#4888) 2020-11-12 20:05:52 +05:45
danielfleischer 8e738262ad utils/fs: checking files ownership in 'move' (#4348) (#4832)
utils/fs: checking files ownership in 'move' (#4348)
2020-11-12 14:00:48 +01:00
Peter Rowlands (변기호) 3daeeaffdb experiments: fix checkpoint/persist related checkout issues (#4885)
* dvcfile: add is_lock_file to identify pipeline lock files

* experiments: clean dirty lock files before stashing experiments
2020-11-12 19:28:28 +09:00
Guro Bokum eeba567774 dag: add --outs option (#4739)
Fixes #4532
2020-11-12 18:23:31 +09:00
Saugat Pachhai 72eb4a5737 Add test server and tests for webdav (#4827)
* Add test server and tests for webdav

* Fix webdav unittests
2020-11-12 13:52:46 +05:45
Sami Jawhar 72ec2a8541 Simpler param updates with python-benedict (#4780) 2020-11-12 16:42:28 +09:00
Peter Rowlands (변기호) 07676b2531 checkpoints: set DVC_ROOT environment variable (#4877)
* checkpoints: set DVC_ROOT environment variable

* env: add DVC_ROOT, DVC_CHECKPOINT
2020-11-12 16:26:07 +09:00
Ioana GrigoropolandRuslan Kuprieiev 556143fd7e api: add support for simple wildcards (#4864)
* api: add support for simple wildcards

Related to #4816.

Signed-off-by: Ioana Grigoropol <[email protected]>

* api: make wildcard interpretation optional

Adds a new argument for the add command `glob` that is disabled by default and when enabled it passes
the input targets through glob filtering.

Related: #4816

Signed-off-by: Ioana Grigoropol <[email protected]>

* Update dvc/repo/add.py

* Update dvc/repo/add.py

* Update dvc/repo/add.py

* Update dvc/repo/add.py

* Update dvc/repo/add.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-11-11 20:38:01 +02:00
Ioana Grigoropol b8cd02457a tests: mark azure test as flaky (#4881)
Related: #4792

Signed-off-by: Ioana Grigoropol <[email protected]>
2020-11-11 20:24:33 +02:00
Ruslan Kuprieiev de01b11483 setup.py: limit responses version for moto (#4879)
```
c:\hostedtoolcache\windows\python\3.6.8\x64\lib\site-packages\moto\core\models.py:17: in <module>
    import responses
c:\hostedtoolcache\windows\python\3.6.8\x64\lib\site-packages\responses.py:29: in <module>
    from urllib3.connection import HTTPHeaderDict
E   ImportError: cannot import name 'HTTPHeaderDict'
```
2020-11-11 17:30:27 +02:00
LucaButeraandLuca Butera 38d811035c remote: avoid chunking on webdav. Fixes #4796 (#4828)
* remote: avoid chunking on webdav. Fixes #4796

* remote: avoid chunking on webdav. Fixes #4796

* remote: avoid chunking on webdav. Fixes #4796

* remote: avoid chunking on webdav. Fixes #4796

* remote: avoid chunking on webdav. Fixes #4796

Co-authored-by: Luca Butera <[email protected]>
2020-11-11 17:59:26 +05:45
Peter Rowlands (변기호) 389efd7ae6 checkpoints: exp run and exp res[ume] refactor (#4855)
* experiments: refactor regular/checkpoint experiment separation

* fix scm_checkout pull bug

* experiments: return stages from executor.reproduce

* since executor has to move to threadpool execution anyways, we no
  longer have to work around the pickle repro result limitation

* refactor new/resume behavior

* refactor checkpoints tests

* use -f to force checkpoints reset/restart

* checkpoints: use 'exp run' and 'exp res[ume]' commands

* stage: all outs can be missing for checkpoint stages

* regular outputs may not be written at the time a checkpoint is
  generated, but subsequent checkouts of that stage should not fail due
  to the missing outs

* commit stage on checkpoint generation otherwise non-persist cached outs
are lost

* checkpoints: wait for stage proc to end after killing via terminate()

* stage: fix issues with killing checkpoint stage

* refactor signaling (use threading.Event instead of Condition)
* fix windows bug (kill all child processes via taskkill)

* experiments: refactor executor collection

* tests: move checkpoint tests into separate module

* refactor experiment tests to use stage fixture
2020-11-11 14:19:22 +09:00
Ruslan Kuprieiev f0ad1b4f93 dvc: refactor cache/remote relations (#4867) 2020-11-10 18:38:28 +02:00
Saugat Pachhai f6d40948d6 codecov: add target coverage, and threshold for patch and project (#4873)
* Reduce codecov target to be 85%

* Increase threshold for patch and project
2020-11-10 21:47:59 +05:45
Michal NowikowskiandRuslan Kuprieiev 4b27fd7c23 [#4136] fixed recalculating relative paths in config (#4802)
* [#4136] fixed recalculating relative paths in config

* Update dvc/config.py

* Update dvc/config.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-11-10 14:56:44 +02:00
Saugat Pachhai 4220baa602 parametrization: Support keys of the dictionary to be interpolated (#4869) 2020-11-10 17:32:58 +05:45
Ruslan Kuprieiev 2e9fa0607e cache: refactor cache types (#4866) 2020-11-09 21:28:46 +02:00
Ruslan Kuprieiev 685e76cfbe gitignore: add config.ini
Generated by innosetup.
2020-11-09 13:25:45 +02:00
Saugat Pachhai dca48fe8d7 Skip loading globally used params section again locally for stages (#4859)
We were still trying to load params file used in `use`
for local stages which was throwing overwrite error.
Thanks to @dmpetrov for pointing out.
2020-11-07 09:30:12 +05:45
Saugat Pachhai 9f1330a387 Fix merge_update not converting data to Node object (#4846)
We wrap every data to one of the `Node` object. But, since we were
passing `self.data` instead of `self`, it was skipping the conversion
and instead was just trying to set data as-is, which would have make
our latter assumptions incorrect, as other codebases assume it to be
a `Node`.

I have added asserts in a few places. Also, I noticed that, on a few
test assumptions, when `resolving`, we were not converting values
back to it's original form (`CtxDict` -> `dict`, `CtxList` -> `list`
and `Value` -> _). So, I have added `node.value` property, as it is
a complement of the above fix.

This caused a ripple effect for the changes in the API as `foreach`
now resolved and changed the data to the original form,
which prevents us from tracking the data later in the "field".
So, `resolve`/`select` got a real `unwrap` option that should
reliably work for all kinds of `Node` objects.
2020-11-07 03:02:24 +02:00
Paweł Redzyński f4ee0e16b4 plots/metrics: fix some communication disrepancies (#4837)
* plots/metrics: unified parsing errors communication

* plots/metrics: unify no metrics/plots messaging

* fixup

* fixup
2020-11-07 02:56:14 +02:00
Ruslan Kuprieiev 4531aa34ca dvc: use DirInfo instead of dicts/lists (#4851)
Currently we are converting dir_info to/from lists all the time.
The reason is that dir_info is stored as list of dicts in *.dir files,
but that makes it hard to work with. In addition to that, we will likely
be changing .dir file format in the near future #829, so we need to
abstract away dir_info into something that we won't care how it will be
stored on disk.

Related #3256
Related #4847
2020-11-07 02:55:05 +02:00
Peter Rowlands (변기호) 80e181f2fc experiments: add dvc exp gc (#4803)
* experiments: add `dvc exp gc`

* tests: add exp gc tests

* add exp gc command test

* fix review issue
2020-11-06 19:05:41 +09:00
Peter Rowlands (변기호) c85a46cdda experiments: add checkpoint output type (#4845)
* output: add checkpoint output type

* stage: handle checkpoint outs

* run/repro: handle checkpoint outs

* experiments: use checkpoint output type in tests

* tests: update run command tests
2020-11-06 14:44:21 +09:00
Ruslan Kuprieiev 89bf7fce13 dvc: limit aws-sam-translator version for moto (#4853)
moto has aws-sam-translator as an indirect dependency that since 1.29.0
started to have a public package
that causes conflicts in flufl.lock when it tries to use atpublic
package.

```
tests/func/test_add.py:21: in <module>
    from dvc.main import main
dvc/main.py:7: in <module>
    from dvc import analytics
dvc/analytics.py:16: in <module>
    from dvc.lock import Lock, LockError
dvc/lock.py:8: in <module>
    import flufl.lock
/opt/hostedtoolcache/Python/3.6.12/x64/lib/python3.6/site-packages/flufl/lock/__init__.py:3: in <module>
    from flufl.lock._lockfile import (
/opt/hostedtoolcache/Python/3.6.12/x64/lib/python3.6/site-packages/flufl/lock/_lockfile.py:42: in <module>
    from public import public
E   ImportError: cannot import name 'public'
```

Oddly enough, this seems to also been causing pylint issues. public is a
terrible name for a package SMH
2020-11-06 04:10:54 +02:00
Ruslan Kuprieiev 42d50f084f gs: don't forget to include file size (#4852) 2020-11-06 02:59:05 +02:00
Paweł Redzyński edd8344659 plots: fix plots path when calling from different dir (#4843) 2020-11-05 08:11:24 +01:00
Ruslan Kuprieiev 4b17685351 cache: refactor dir_info conversion (#4844) 2020-11-04 21:42:14 +02:00
Ruslan Kuprieiev e1b82c5222 dvc: add size/nfiles for deps/outs (#4836)
* dvc: add size for deps/outs

Related to #3256

* dvc: add nfiles for deps/outs

* dvc: put size/nfiles into the hash_info
2020-11-04 19:22:57 +02:00
Saugat Pachhai e9a9415fbf refactor: move interpolation logic inside the Context (#4840)
* Move interpolation logic to the Context

* Don't unwrap by default on `Context.select`
2020-11-04 17:04:40 +05:45
Sami Jawhar 6494bd0151 Plots: Better confusion matrix, and normalized version to (#4775)
* Add normalized confusion plot, and add text labels

* Impute missing XY combinations
2020-11-04 11:33:11 +01:00
Liew XunandSaugat Pachhai d1441ee16a http: raise error on unsuccessful http statuses on exists (#4785)
* http: added check for HTTP status code 404

Fixes #4288

* http: add unit test and minor change

In tests_exists, changed to raise a HTTPError from dvc instead.

Fixes #4288

* http: removed fixtures in test for HTTPTree.exists

Fixes #4288

* test: add comments on why `raw` is required on the mock

Co-authored-by: Saugat Pachhai (सौगात) <[email protected]>
2020-11-04 14:43:42 +05:45
Ruslan Kuprieiev 7da3de451f build: test built binary (#4839) 2020-11-03 23:30:41 +02:00
Ruslan Kuprieiev 4a3f06df3a oss: require pycryptodome<3.9.9
3.9.9 doesn't have windows wheels https://github.com/Legrandin/pycryptodome/issues/465
2020-11-03 20:28:44 +02:00
Ruslan Kuprieiev 2225764438 tests: set subprocess._active to None (#4818)
To better reflect https://github.com/python/cpython/pull/14360
2020-11-03 19:31:04 +02:00
Peter Rowlands (변기호) 644444b5a7 experiments: fix checkpoint checkout issues (#4823)
* checkout: add option to suppress missing file warnings

* experiments: fix checkpoints branch checkout issues

* experiments: _get_branch_containing should not change scm checkout
2020-11-03 22:21:54 +09:00
Saugat Pachhai 60b69b93cb Add flaky-service (#4834)
Use master branch for flaky-service

Generate TAP results during pytest
2020-11-03 18:41:10 +05:45
Saugat Pachhai fec1499c67 Move interpolation logic to the Context (#4833) 2020-11-03 17:42:28 +05:45
Saugat Pachhai a38941b47e Refactor context set/alias to Context class (#4831) 2020-11-03 17:06:46 +05:45
Saugat Pachhai 6a9ab9cdfb dvc.yaml: implement set keyword (#4757) 2020-11-02 20:32:07 +05:45
Saugat Pachhai e211423398 Implement foreach ... in loop in dvc.yaml (#4734)
* Implement foreach...in loop in dvc.yaml

* fix tests
2020-11-02 18:56:23 +05:45
Peter Rowlands (변기호) 96cdd073bd diff: handle empty output status (#4826) 2020-11-02 21:16:29 +09:00
simonlousky 59d5c73c5e increased http timeout to 60 seconds (#4820) 2020-11-01 13:19:55 +02:00
Kevin Lloyd Bernal 58bed0e9ba import: add --no-exec param (#4817)
To be consistent with the same --no-exec param in import-url.

Fixes #4567.
2020-10-31 20:03:33 +02:00
Ruslan Kuprieiev 92752e0392 dvc: upgrade dulwich to 0.20.11
This one has wheels for python 3.9
2020-10-30 19:16:43 +02:00
João Vitor 4a7bb49bb0 command: add dvc push instruction after dvc repro (#4737)
* command: add dvc push instruction after dvc repro

Adds a logger.info() statement to remind the user to run dvc push after
running dvc repro.

Fixes #2359

* command: change dvc push instruction for dvc repro

* command: Use backticks and else in dvc repro log
2020-10-30 15:47:00 +01:00
Peter Rowlands (변기호) 3d58f4b8ce git: move branch iteration functionality from experiments into scm.git (#4810)
* git: move branch iteration functionality from experiments into scm.git

* add branch_revs unit test
2020-10-30 19:06:35 +09:00
Peter Rowlands (변기호)andPaweł Redzyński f827d641d5 add: warn on cache link errors instead of failing (#4801)
* add: warn on cache link failure instead of error

* add test case

* fix linted string

Co-authored-by: Paweł Redzyński <[email protected]>

Co-authored-by: Paweł Redzyński <[email protected]>
2020-10-30 02:01:11 +02:00
Paweł Redzyński 9940540917 metrics/plots: test metrics collection on no target on revision (#4789)
* metrics: include revision information when collecting metrics files

* plots: test warning message on no plot on revision

* plots/metrics: move to one package, introduce common tests

* tests: fix utils placement
2020-10-30 02:00:09 +02:00
Ruslan Kuprieiev 32fa8e33c4 git: use dulwich for .gitignore (#4809)
In my tests `Git._ignore` is taking around ~65seconds for the whole test
suit(~50K invocations). The reason is that `gitpython` is launching a real
`git check-ignore` process, that takes time and resources.

Switching to `pygit2` or `dulwich` makes it drop to ~5seconds, saving us
almost a minute of the test run time and a lot of resources by not
launching any processes. I've chosen `dulwich` over `pygit2` here
because it is much more pythonic and as long as the performance is
on-par with `pygit2` it is better for us to use `dulwich`.
2020-10-30 01:01:58 +02:00
Peter Rowlands (변기호) 911534116e experiments: fix stash ordering bug (#4805) 2020-10-29 18:01:34 +09:00
Ruslan Kuprieiev 9b136f0c3f pyarrow: upgrade to 2.0.0 (#4795) 2020-10-27 19:14:30 +02:00
Ruslan Kuprieiev 411b388960 dvc: bump to 1.9.1 2020-10-27 17:33:27 +02:00
Juliano Efson Sales e6016ca202 tests: testing when overwriting files (#4738) 2020-10-27 15:52:16 +02:00
Peter Rowlands (변기호) b2495aa230 add: fix issue when adding already tracked symlinked files (#4778)
* LocalOutput: paths symlinked outside of git can't be gitignored

* utils: contains_symlink_up_to should only check parent dirs on add

* tests: update test_add symlink cases

* add: forbid adding symlinked dirs or files inside symlinked dirs

* update tests

* add troubleshooting link
2020-10-27 15:39:01 +02:00
f4522760ba --jobs now takes effect on imported data (#4765)
* `--jobs` now takes effect on imported data

fix #4692

* Tests for this change

* Solve issue on python3.6 and 3.7

* Update tests/unit/test_external_repo.py

* Update tests/unit/test_external_repo.py

* Update tests/unit/test_external_repo.py

* Update tests/unit/test_external_repo.py

* tests: fix formatting

Co-authored-by: Ruslan Kuprieiev <[email protected]>
Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-10-27 01:21:51 +02:00
Ruslan Kuprieiev 0736c64188 gha: don't create gs creds file if no creds 2020-10-27 00:23:34 +02:00
Paweł Redzyński 6cf194f85f plot: fix no metrics data detection (#4764) 2020-10-26 20:05:55 +02:00
Paweł Redzyński 5038fd225b plots/metrics: support show for no repo (#4761) 2020-10-26 20:04:49 +02:00
Ruslan Kuprieiev bfecd7d8e8 gha: add gs creds (#4791)
* gha: add gs creds

Related to #4418

* tests: gs: don't use gcloud
2020-10-26 19:20:58 +02:00
Ruslan Kuprieiev a5bdfa90f0 gha: add aws creds (#4790)
Related to #4418
2020-10-26 17:02:07 +02:00
Paweł Redzyński 4d75e75631 test_update_import: roll back skip (#4781) 2020-10-26 15:28:30 +02:00
Tomás Farías Santana f8ba5daae2 Replace dict calls for literals (#4784)
* Replace dict calls for literals

* Pin flake8-comprehensions version to 3.3.0
2020-10-25 19:51:22 +02:00
Sami Jawhar 831e85682e Experiments: allow setting of list parameters (#4769)
* Allow setting of list parameteres

Check if dict is Mapping or list before using .get()

* Added test for dvc exp run with list paramater

* Add more tests for string parameter modification
2020-10-23 14:35:17 +09:00
Saugat Pachhai ec05e6026f Display coverage report on the GHA log and measure branch coverage (#4760)
* Display coverage report on the terminal and Measure branch coverage

* Add source path

* Install wheel, run in editable mode
2020-10-22 20:28:50 +03:00
Ruslan Kuprieiev 0669d8d885 dvc: bump to 1.9.0 2020-10-22 20:22:18 +03:00
Ruslan Kuprieiev 2c7d89939c gdrive: get rid of empty file hack (#4774) 2020-10-22 20:07:55 +03:00
Saugat Pachhai e8c4924997 Make codecov push results on every run (#4754)
* Make codecov push results on every run

* codecov: do not comment on pull requests
2020-10-22 17:17:26 +03:00
Samuel Neugber 2747e77012 Escape spaces in file paths when running commands on SSH remote (#4767)
* Escape file paths for calculation of md5 hashes on ssh remote

* Add tests for hardlink and improved md5 test with PropertyMock

* Add additional special characters to check for fix of issue #4628 and fix formatting

* Fix linting errors

* Move tests to correct file and use injected ssh_connection instead of setting it up from scratch
2020-10-22 13:57:18 +03:00
Peter Rowlands (변기호) 87f1c42acf experiments: wrap column entries with rich.Text (#4771) 2020-10-22 16:40:08 +09:00
Peter Rowlands (변기호) 8a5dc6a781 init: move welcome message from Repo.init to command.init (#4759)
* repo: add quiet flag for Repo.init

* move welcome message to command/init
2020-10-22 12:49:48 +09:00
Paweł Redzyński 33ece011fb TestCmdCacheDir: roll back darwin skip (#4673) 2020-10-21 23:36:16 +03:00
Peter Rowlands (변기호) 79232dfedf checkpoints: --continue from last run experiment by default (#4758)
* checkpoints: `--continue` with no args resumes most recent experiment

* add test for --continue :last
2020-10-21 17:25:16 +09:00
Saugat Pachhai aa7b4c6340 Add tests for resolving stage data (#4753)
* Add test, and fix resolving of paths for params

Improve merging of vars and imported params, improve tracking
Resolve wdir correctly

* Fix windows relpath

* isort imports
2020-10-21 05:47:48 +05:45
Saugat Pachhai 668002cb01 Revert "Track flaky tests using Google's service (#4749)" (#4755)
This reverts commit 7527a220f6.
2020-10-20 08:19:00 -07:00
Peter Rowlands (변기호) 25efd0efe8 checkpoints: allow missing outputs on checkout and stage.run (#4750) 2020-10-20 21:44:01 +09:00
Saugat Pachhai b0ae5e2251 Support parametrization from params or vars section in dvc.yaml (#4751)
* Implement importing from params

* Change varname

* Add tests

* fix pylint issues
2020-10-20 04:16:13 -07:00
Ruslan Kuprieiev e48055077d plots: don't allow in non-dvc repo (#4745) 2020-10-20 11:31:57 +03:00
Saugat Pachhai 7527a220f6 Track flaky tests using Google's service (#4749)
* Track flaky tests using Google's service

https://flaky-dashboard.web.app
More info: https://github.com/GoogleCloudPlatform/flaky-service

* Increment version
2020-10-19 23:24:11 -07:00
Peter Rowlands (변기호) feaa5cfcb2 experiments: fix checkpoints sorting issues in dvc exp show (#4740)
* experiments: order experiment (branches) by timestamp

* checkpoints: sort checkpoint branches by tip commit values

* remove sort logic from dvc.scm
2020-10-20 14:54:43 +09:00
Saugat Pachhai 10ae13132d Use lxml wheel instead of legacy setup.py builds (#4748)
`lxml` now provides wheel for Python3.9. 

Also, enable windows test for Python3.9
2020-10-19 22:23:51 -07:00
Harsh Patel 924323f54f lock: added troubleshooting doc link in error message (#4325) (#4721)
* lock: added troubleshooting doc link in error message (#4325)

* updated lock error message (iterative#4325)
2020-10-19 20:47:08 +03:00
Alexei Tenitski 235d4c9a94 #1618 Custom message when cache and remote are in sync (#4694)
* #1618 Custom message when cache and remote are in sync

* Output the correct remote name during dvc status

* Simplify logic
2020-10-19 20:38:05 +03:00
Musa AtlıhanandPeter Rowlands 4b6ebc7527 make pull --run-cache work with local remote having relative path (#4670)
* Fixes #4637

* use PathInfo instead of Path

* catch ValueError with PathInfo.relative_to and fallback to dvc.utils.relpath

* cleanup

* add test

Co-authored-by: Peter Rowlands <[email protected]>
2020-10-19 17:53:01 +09:00
Peter Rowlands (변기호) 699602f4d0 experiments: use general format for floating pt numbers (#4730) 2020-10-19 15:26:29 +09:00
Peter Rowlands (변기호) 10a2eeb2b8 checkpoints: create final checkpoint commit after repro when needed (#4715)
* repo: make Repo.scm a cached attribute

* checkpoints: create final checkpoint after repro when needed

* use correct exception rev in _collect_and_commit
2020-10-19 15:26:06 +09:00
Peter Rowlands (변기호) b9f547637c checkpoints: add --reset option (#4731)
* experiments: add --reset option to reset/restart checkpoint experiments

* add CheckpointExistsError internal exception

* update tests
2020-10-19 15:25:08 +09:00
Juliano Efson SalesandRuslan Kuprieiev 052b15a2a8 tests/remote: adding tests for real oss (#4735)
* tests/remote: adding tests for real oss

the new test suggests #4633 is a bug in the emulator.

* Update tests/func/test_data_cloud.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>

* Update tests/remotes/oss.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>

* tests: correcting small lint issue

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-10-18 16:21:51 +03:00
Jonny Browningandbrowningjp 68220cbea0 Added 'doctor' as an alias for 'version' (#4722)
* Added 'doctor' as an alias for 'version'

* simplified doctor alias

Co-authored-by: browningjp <[email protected]>
2020-10-17 11:10:03 +03:00
Ruslan Kuprieiev 21a6be7c58 Delete .restyled.yaml
Since we've decided to stop using it. Also disabled in the repo settings.
2020-10-15 15:07:58 +03:00
Paweł Redzyński 2b90d7dfaf tests, packages: roll back environmental packages setup (#4728) 2020-10-15 14:19:25 +03:00
Peter Rowlands (변기호) 9d7e483599 experiments: fix git stash mtime race condition (#4727) 2020-10-15 13:26:17 +03:00
Paweł Redzyński 8f8c561d1b test_api: roll back xfail (#4660) 2020-10-15 10:40:53 +02:00
Ruslan Kuprieiev 32cef509b7 run-cache: don't save always_changed stages (#4719)
Fixes #4335
2020-10-14 17:43:16 +03:00
Saugat Pachhai 6a79111047 Parametrization support on dvc.yaml (#4679)
* Custom parametrization support

* log generated data

* Add tests
2020-10-14 05:32:05 -07:00
Peter Rowlands (변기호) 09e51e655d checkpoints: cleanup --continue usage (#4716)
* warn if rev provided with --continue is ambiguous
* always provide tip of branch when suggesting to use --continue
2020-10-14 19:12:57 +09:00
Peter Rowlands (변기호) 4aa8fd0fbb tests: mark test_extend_branch as flaky (#4718) 2020-10-14 19:12:32 +09:00
Peter Rowlands (변기호) 3cebd98c54 tests: fix test_remote::test_push_order (#4714)
* tests: fix test_remote::test_push_order

* review fixes
2020-10-14 18:40:04 +09:00
Ruslan Kuprieiev b11fe83828 dvc: bump to 1.8.4 2020-10-13 23:57:29 +03:00
Peter Rowlands (변기호) cc8d0ef43f experiments: support exp run --checkpoint for baseline commits (#4701)
* handle untracked files when `git reset --hard` in experiments workspace

* error on checkpoint branch already exists

* terminate stage process on checkpoint errors

* update tests

* cleanup error message
2020-10-13 23:10:08 +03:00
Paweł Redzyński 3e011d70d5 github actions: windows: fix packaging (#4711) 2020-10-13 21:31:48 +02:00
Ruslan Kuprieiev 0649242578 dvc: bump to 1.8.3 2020-10-13 13:17:22 +03:00
Paweł Redzyński 549f342db9 gha: fix github token providing for assets upload (#4703) 2020-10-12 18:18:20 +03:00
Saugat Pachhai 4e53491f5c Use released version of Python3.9 on CI (#4704) 2020-10-12 20:24:28 +05:45
Paweł Redzyński 0da1664f3f gha: get rid of unnecessary if (#4702) 2020-10-12 13:33:15 +03:00
Jorge OrpinelandRuslan Kuprieiev 3b6a1c441e term: "metrics" plural in output messages (#4699)
* api: update to latest definitions to match iterative/dvc.org/pull/908

* Update dvc/api.py

* metrics show: update -h output to match docs
per https://github.com/iterative/dvc.org/pull/1014/commits/2c3452119545987f77b764e4aa6fc94d051092a7

* api: update docstrings to match iterative/dvc.org/pull/908

* api: refactor DVC repo check in get_url, and document it

* dvc: cosmetic edits as I explored exceptions that api functions may raise

* api: copy default info to read() docstring from open()
per https://github.com/iterative/dvc/pull/3426#pullrequestreview-366949501

* api: improve open() docstring for clarity and add example
per https://github.com/iterative/dvc/pull/3426#pullrequestreview-366956856

* api: remove unnecessary info from get_url docstring
per https://github.com/iterative/dvc/pull/3426#discussion_r386203064

* api: produces->generated in open() docstring

* api: simplify open docstring
per https://github.com/iterative/dvc/pull/3426#discussion_r388218147

* term: "metrics" plural in output messages
https://github.com/iterative/dvc.org/pull/1848#pullrequestreview-506217071

* typo

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-10-12 12:51:57 +03:00
Ruslan Kuprieiev ca490f699e ls: pass repo_tree instead of repo (#4700) 2020-10-12 12:37:19 +03:00
omar.zhouandzwh 59105ec639 Fixed the bug #4251, and fixed a minor bug which causes of OS default character. (#4646)
* bug fixed
bug description:
1, environment: OS which default character is not 'UTF-8'. For example windows 10 which default character is 'gbk'.
2, operation:
python setup.py install
Traceback (most recent call last):
  File "setup.py", line 149, in <module>
    long_description=open("README.rst", "r").read(),
UnicodeDecodeError: 'gbk' codec can't decode byte 0xa2 in position 41: illegal multibyte sequence

* bug #4251 fixed
https://github.com/iterative/dvc/issues/4251

* bug #4251 fixed
https://github.com/iterative/dvc/issues/4251

* Revert "bug #4251 fixed https://github.com/iterative/dvc/issues/4251"

This reverts commit f524c727

* Revert "bug #4251 fixed https://github.com/iterative/dvc/issues/4251"

This reverts commit fa5db477

* Fix bug #4251
We can check if the source exists by using `dep.exists`

Co-authored-by: zwh <[email protected]>
2020-10-12 09:00:16 +03:00
1d550f35af command: print the current cache directory when no cmd flags are provided (#4613)
* command: print the current cache directory when no cmd flags are provided
tests: corrected the test for cache dir cmomand

comamnd 'dvc cache dir' now prints the current cache dir
when no cmd flags are provided and modified existing test for
the same.

Fixes #1998

Signed-off-by: Girish Joshi <[email protected]>

* command: updated validation for args.value and args.unset flag
according to current convention in dvc/command/cache.py

Signed-off-by: Girish Joshi <[email protected]>

* Update dvc/command/cache.py

* dont forget logger

Co-authored-by: Ruslan Kuprieiev <[email protected]>
Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-10-12 08:51:38 +03:00
Paweł Redzyński fba94a4569 tests build: buil on push/pr to master (#4680) 2020-10-10 00:22:08 +03:00
Frederik Schubert 0e257adc63 remote: add WebDAV dependency (#4690)
This adds the dependency for WebDAV and enables calls like `import-url
remote://some-webdav/data`

Related to #4374
2020-10-10 00:07:34 +03:00
Frederik Schubert 8b28a268e8 remote: set dependency name for WebDAV (#4691)
When executing `dvc import-url remote://some-webdav/data`, the name of
the WebDAV dependency in the created stage for data used to be `None`.
This resulted in the following error:
```
Traceback (most recent call last):
  File "/home/schubert/projects/dvc/dvc/main.py", line 76, in main
    ret = cmd.run()
  File "/home/schubert/projects/dvc/dvc/command/imp_url.py", line 14, in run
    self.repo.imp_url(
  File "/home/schubert/projects/dvc/dvc/repo/__init__.py", line 51, in wrapper
    return f(repo, *args, **kwargs)
  File "/home/schubert/projects/dvc/dvc/repo/scm_context.py", line 4, in run
    result = method(repo, *args, **kw)
  File "/home/schubert/projects/dvc/dvc/repo/imp_url.py", line 54, in imp_url
    stage.run()
  File "/home/schubert/miniconda3/tmp/envs/some-project/lib/python3.8/site-packages/funcy/decorators.py", line 39, in wrapper
    return deco(call, *dargs, **dkwargs)
  File "/home/schubert/projects/dvc/dvc/stage/decorators.py", line 36, in rwlocked
    return call()
  File "/home/schubert/miniconda3/tmp/envs/some-project/lib/python3.8/site-packages/funcy/decorators.py", line 60, in __call__
    return self._func(*self._args, **self._kwargs)
  File "/home/schubert/projects/dvc/dvc/stage/__init__.py", line 450, in run
    self.save()
  File "/home/schubert/projects/dvc/dvc/stage/__init__.py", line 395, in save
    self.md5 = self.compute_md5()
  File "/home/schubert/projects/dvc/dvc/stage/__init__.py", line 388, in compute_md5
    m = compute_md5(self)
  File "/home/schubert/projects/dvc/dvc/stage/utils.py", line 167, in compute_md5
    return dict_md5(
  File "/home/schubert/projects/dvc/dvc/utils/__init__.py", line 118, in dict_md5
    return dict_hash(d, "md5", **kwargs)
  File "/home/schubert/projects/dvc/dvc/utils/__init__.py", line 113, in dict_hash
    byts = json.dumps(filtered, sort_keys=True).encode("utf-8")
  File "/home/schubert/miniconda3/tmp/envs/some-project/lib/python3.8/json/__init__.py", line 234, in dumps
    return cls(
  File "/home/schubert/miniconda3/tmp/envs/some-project/lib/python3.8/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/home/schubert/miniconda3/tmp/envs/some-project/lib/python3.8/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
TypeError: '<' not supported between instances of 'str' and 'NoneType'
```
2020-10-10 00:05:18 +03:00
Paweł Redzyński 09f9506ca0 github actions: migrate to envrionmental files (#4684) 2020-10-09 11:33:06 +03:00
Peter Rowlands (변기호) f20adf5e0f experiments: add --show-json for exp show (#4689) 2020-10-09 15:07:56 +09:00
Paweł Redzyński 9f90c806d5 params, plots, metrics: unify collection (#4603)
* params, plots, metrics: unify collection

* collect: split into smaller parts
2020-10-09 06:26:11 +03:00
Peter Rowlands (변기호) c16d3fa43d experiments: migrate dvc repro -e to dvc exp run (#4677)
* experiments: migrate from `dvc repro -e` to `dvc exp run`

* tests: use experiments.run instead of reproduce

* update `repro` and `exp run` commands
2020-10-09 00:26:31 +03:00
Ruslan Kuprieiev 3ceef7dbf1 dvc: get rid of ExternalGitRepo (#4685) 2020-10-08 22:04:48 +03:00
Peter Rowlands (변기호) 34804b5636 experiments: fix checkpoints callback bug when pipeline includes data (#4678) 2020-10-08 17:47:42 +09:00
Ruslan Kuprieiev 060fc4fbb7 state: use granularly (#4676)
* state: add simple reentrance

* state: pass Repo instead of LocalCache

* state: use granularly

Users could still load state preemptively if they want to bulk up
the operations, but other than that tree/cache will just load it
when they need it.
2020-10-08 08:36:14 +03:00
Ruslan Kuprieiev 4496dbf5b5 release-drafter: add ui to enhancements 2020-10-08 03:01:38 +03:00
Ruslan Kuprieiev 334afebd31 dvc: bump to 1.8.2 2020-10-08 02:57:22 +03:00
Ruslan Kuprieiev 775e1e8374 repo: don't create .dvc in read-only repos (#4674)
* lock: use ABC

* lock: add LockNoop

* plots: remove unused code

* plots: create templates only on init

* dvc: don't create .dvc in read-only repos

* repo: explicitly init LockNoop
2020-10-07 23:51:14 +03:00
Paweł Redzyński 548c2dcc1f test_str_workdir_outside_repo: roll back skip on windows (#4672) 2020-10-07 19:15:14 +03:00
Ruslan Kuprieiev 15b1e7072b gha: don't build packages for PRs
Related to #4468
2020-10-06 20:16:37 +03:00
Peter Rowlands (변기호) f0a729df4d experiments: checkpoints proof of concept (#4591)
* stage: make runs checkpointable

* api: add make_checkpoint API call

* tests: add test for checkpointed stage

* experiments: implement checkpoints callback chain

* initial working state

* use .dvc/tmp for checkpoint signal file location

* force repro for checkpoint experiments

* checkout: add flag to allow missing persistent outputs

(i.e. not yet created)

* use allow_persist_missing on checkpoint experiment repro

* executor: force checkout before checkpoints experiment repro

* move checkpoint commands to `dvc exp run`

* support resuming checkpoint runs with --continue

* fix git bugs

* experiments: include checkpoint commits in `exp show`

* use 1sec sleep timers

* fix apply_workspace test conflict

* tests: add test for `dvc exp run`

* cleanup signal file behavior

* fix bool conversion

* tests: add tests for checkpoint and checkpoint_continue

* fix styling

* fix checkpoint stage test

* fix checkpoint monitor thread condition/notification
2020-10-06 15:34:11 +03:00
Ruslan Kuprieiev 7424f22e2a repro: support --pull (#4538)
* repro: support --pull

This option allows run-cache to pull the cache for the outputs
when restoring a stage instead of pulling everything at once during
`dvc pull --run-cache`.

Fixes #4223

* repro: adjust --pull help
2020-10-06 04:22:19 +03:00
Ruslan Kuprieiev 9ee5018c63 tests: gdrive: skip if env var is empty 2020-10-06 01:43:39 +03:00
Ruslan Kuprieiev 026047aa80 gha: set GDRIVE_CREDENTIALS_DATA 2020-10-05 23:23:02 +03:00
Ruslan Kuprieiev 3878c50ea8 yaml: wrap ruamel's DuplicateKeyError (#4668)
* yaml: wrap ruamel's DuplicateKeyError

Unfortunately ruamel's exception doesn't contain much info that we could
reuse, so we can only reasonably extract the exc.problem and have
something like:

```
ERROR: failed to obtain data status - unable to read: 'dvc.yaml', found
duplicate key "metrics" with value "[]" (original value: "[]")
```

Fixes #4664

* status: remove unneeded error prefix
2020-10-05 23:02:59 +03:00
Ruslan Kuprieiev 3ffb6320ac dvc: print version to debug log (#4666)
Related to #4664
2020-10-05 21:04:31 +03:00
Ruslan Kuprieiev 52fe76cefc metrics: diff: make table more intuitive (#4655)
* metrics: diff: make table more intuitive

Currently we have a "diff not supported" marker in the table, that is
used whenever dvc is not able to diff the values (e.g. no old one, no
new one or format error). It is quite ugly and it is much more intuitive
to just show both old and new values and now show that marker at all, as
if there is no old/new value it is intuitive that we can't calculate the
change. Next step is to add old/new "bad format"/etc markers for
unsupported types or malformed/missing metric files.

E.g., before:

```
Path          Metric    Value    Change
metrics.json  foo       1.2      diff not supported
```

after this PR:

```
Path          Metric    Old    New    Change
metrics.json  foo       None   1.2
```

* metrics/params: use em dash in diff table
2020-10-05 19:37:34 +03:00
Guillermo Blanco d0e1c88792 completions: fix run command dependencies autocompletion (#4659)
* completions: fix run command dependencies autocompletion

Fixes #4552

* completions: fix run command outputs autocompletion

Fixes #4552

* completions: fix run command parameters autocompletion

Fixes #4552
2020-10-05 15:28:59 +03:00
Peter Rowlands (변기호) fcdb503b4e diff/dvctree: optimize dir cache access (#4626)
* dvctree: use string paths for performance reasons in
_get_granular_checksum()

* dvctree: cache most recently accessed metadata

* dvctree: cache output dir cache during walk() calls

* diff: use get_file_hash directly instead of get_hash when appropriate

* dvctree: cache most recen dir_cache hashes in _get_granular_checksum

* revert metadata cache changes

- mostly unnecessary after granular checksum changes, will be better
  addressed by the future exception handling updates

* fix windows path issue

* HashInfo: store HashInfo.dir_info as a dict instead of list

* DvcTree: use hash_info.dir_info for getting granular file hashes

* use str relpath

* use correct dir_info hash type

* fix windows paths
2020-10-03 03:54:17 +03:00
Ruslan Kuprieiev 3d2493a391 dvc: bump to 1.8.1 2020-10-01 11:15:30 +03:00
Saugat PachhaiandRuslan Kuprieiev fc6999ad17 Run CI for Python3.9 (#4647)
* Run CI for Python3.9, skips hdfs tests for 3.9

Also, `dvc[hdfs]` install will be a no-op till we have
a pyarrow wheel for 3.9.

Note that this does not add an entry to the classifier
claiming it supports 3.9, that will be added when 3.9
gets released (or, wait for pyarrow to be released).

* Skip HDFS related tests on Python3.9

Also, fix the python version regex for `3.9.0b5` like versions.

* Exclude python3.9 for Windows

libxml does not have wheels for Python39.
We do compile them on Linux and Mac

* Update tests/unit/test_info.py

* Use realpath for gitignore

Windows tests for Python3.8 was failing
intermittently.

Note: Also removed the realpath wrap around self.root_dir as it's already a
realpath when `Git` is instantiated.

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-10-01 10:41:39 +03:00
Saugat Pachhai e0b3e98039 api.open: fix error if uncached file inside a dir is accessed (#4652)
Wrap path in Repo.open_by_relpath() with PathInfo.
Closes #4650
2020-10-01 10:40:56 +03:00
Casper da Costa-Luis 04bd460136 bump shtab dependency (#4651)
Fixes bash>=4.4 completion.
See https://github.com/iterative/shtab/pull/23
2020-10-01 02:43:49 +03:00
Ruslan Kuprieiev d7a35b9852 gha: enable hdfs tests (#4644) 2020-09-30 05:43:34 +03:00
Paweł Redzyński ab22118cd4 metrics, plots: update cmd targets help (#4620) 2020-09-30 04:55:00 +03:00
Raymond GasperandRuslan Kuprieiev b0dbcdcdb6 metrics show: allow falsey values (#4642)
* metrics show: allow falsey values

Fixes #4630

* metrics show: bug in logging to console. Fixes #4630

* repro: fix incorrect usage of _show_metrics

* fix formatting/styling

* metrics: add a simple func test

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-09-30 04:53:06 +03:00
gsvoltandRestyled.io fb756c0538 application_process: scm: Git: _add_entry_to_gitignore: writes unique entry to .gitignore (#4624)
* scm:  Git: _add_entry_to_gitignore: writes unique entry to .gitignore

Fixes #4327

* Restyled by black

Co-authored-by: Restyled.io <[email protected]>
2020-09-30 04:02:44 +03:00
Ruslan Kuprieiev 00267706b7 dvc: bump to 1.8.0 2020-09-30 03:39:33 +03:00
Ruslan Kuprieiev 1fcb94dee8 hdfs: add a progress bar (#4643) 2020-09-30 03:36:50 +03:00
Ruslan Kuprieiev dc6abba679 gdrive: fix comment formatting 2020-09-30 03:10:39 +03:00
Ruslan Kuprieiev fdc455a08f gdrive: use PyDrive2 issue link instead of PyDrive 2020-09-29 23:29:35 +03:00
Ruslan Kuprieiev 7b590e7c5d gdrive: revert back to the hack for uploading empty files (#4632)
* gdrive: bring back the progress workaround

* WIP cloud: test empty files
2020-09-29 13:49:49 +03:00
Ruslan Kuprieiev e3a7857b13 repo: move index_dir to RemoteIndex (#4631) 2020-09-29 03:32:33 +03:00
Ruslan Kuprieiev 644c2df5e8 plots: fix typo 2020-09-29 00:55:38 +03:00
Ruslan Kuprieiev de24794cd5 repo: move templates to plots (#4629) 2020-09-29 00:38:11 +03:00
Ruslan Kuprieiev 1ea8cc9e54 metrics/plots: support non-dvc repos (#4627) 2020-09-28 23:06:22 +03:00
Pavel DitenbirandRuslan Kuprieiev 738dfd49cf import: add '--file' option (#4622)
* import: add '--file' option

Fixes #4093

* Update dvc/command/imp.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-09-27 20:19:08 +03:00
Guillaume COTER a5ab01390f remote[ssh]: Add the parameter allow_agent (#4623)
This parameter is passed to paramiko.
See http://docs.paramiko.org/en/stable/api/client.html?highlight=allow_agent#paramiko.client.SSHClient.connect for more details.

Fixes #4186
2020-09-27 01:09:00 +03:00
David RobertsandRuslan Kuprieiev a664059eff status: Show friendly message for empty project. (#4609)
* status: Show friendly message for empty project.

When there are no stages in the repo, show a friendly message to that
effect and display a getting-started URL.

Fixes #4395.

* Fix style check failing due to line too long. (E501)

* Use format_link(…) as suggested by @skshetry, vs {blue}/{nc}.

Since I'd pulled the old syntax out of repo/init.py, I've fixed that up
as well so no one makes that mistake again. Note that this adds <>s around
the link text, unlike before:

+---------------------------------------------------------------------+
|                                                                     |
|        DVC has enabled anonymous aggregate usage analytics.         |
|     Read the analytics documentation (and how to opt-out) here:     |
|             <https://dvc.org/doc/user-guide/analytics>              |
|                                                                     |
+---------------------------------------------------------------------+

This is probably fine, since if we're going to have a standard link format
we might as well stick with it consistently. If we don't like it, we can
change format_link() I guess.

* Update dvc/command/status.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-09-25 18:05:20 +03:00
Robert Lowry c94ab359b6 cli: add --cd flag to change directories before executing commands (#4566)
* cli: add -C flag to change directories before executing commands

Fixes #4025

* add deprecation notice to dvc repro help message
2020-09-25 04:20:22 +03:00
Ruslan Kuprieiev 9643ff7229 dvc: bump to 1.7.9 2020-09-25 02:30:14 +03:00
Ruslan Kuprieiev 8da40af3e4 ssh: download: don't use self.path_info (#4619) 2020-09-25 02:27:55 +03:00
Peter Rowlands (변기호) 36935bdce2 experiments: fix --all-tags/--all-branches bug (#4612)
* brancher: add sha_only option to omit branch/tag names

* experiments: resolve branches/tags into commit objects during `exp show`
2020-09-24 22:48:57 +03:00
Ruslan Kuprieiev 2f030e870b Update packages.yaml 2020-09-24 18:53:50 +03:00
Ruslan Kuprieiev a2adc956b9 dvc: bump to 1.7.8 2020-09-24 18:46:36 +03:00
Ruslan Kuprieiev deaa27dde2 gha: don't build packages when not needed 2020-09-24 18:44:10 +03:00
Ruslan Kuprieiev b784216f68 dvc: bump to 1.7.7 2020-09-24 18:38:07 +03:00
Peter Rowlands (변기호) 32bb46f34f experiments: support cloning parent in detached-head state (#4611)
* experiments: account for cloning detached-head parent repo

* add test for detached-head parent
2020-09-24 18:36:51 +03:00
Ruslan Kuprieiev 2cbd46ba75 gha: publish packages only on released release 2020-09-24 18:31:05 +03:00
Ruslan Kuprieiev 954e4784ab dvc: bump to 1.7.6 2020-09-24 17:45:53 +03:00
Ruslan Kuprieiev 62edbaf07c ssh: install paramiko with invoke (#4617)
Paramiko has recently added support for `Match`
https://github.com/paramiko/paramiko/issues/717
which resulted in some users getting an import error about `invoke`
module, as it is not a default dependecy for paramiko.
@Pseudomanifold reported that installing `invoke` #4589 worked for
him, which means that this newly added paramiko feature works in
his case. That feature seems to not affect configs without `Match`,
so we could give this a try and install it by default.

Our conda package will be updated during the next release.

Fixes #4589
Fixes #4616
2020-09-24 17:45:06 +03:00
Alexanderandaandrusenko e928034fac add py support for ParamsDependency (#4456)
* add py support for ParamsDependency

* add py support by ast

* add support modify py params

Co-authored-by: aandrusenko <[email protected]>
2020-09-24 17:25:44 +03:00
Ruslan Kuprieiev 749fec381f dvc: bump to 1.7.5 2020-09-24 16:26:04 +03:00
Ruslan Kuprieiev 21de21fe37 gha: adjust the test workflow (#4615)
* gha: move lint check to tests workflow

* gha: tests: don't fail fast

* scripts: remove redundant check
2020-09-24 16:13:03 +03:00
Ruslan Kuprieiev 0756af5012 setup: remove wheel from install_requires (#4614)
Kudos to @skshetry for catching this one in https://github.com/iterative/dvc/pull/4606
2020-09-24 15:39:54 +03:00
Ruslan Kuprieiev 2457633990 gha: build packages (#4607) 2020-09-24 14:48:04 +03:00
David Roberts 46bc89319a install: Potential fix for (non-fatal?) setup errors on Ubuntu 20.04. (#4606)
Wheels fail to build on initial install. Install `wheel` to fix this.

Original error message:
Building wheel for pyyaml (setup.py) ... error
  ERROR: Command errored out with exit status 1:
   command: /home/david/Documents/job-hunting/dvc/.env/bin/python3 -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-install-nhmea2xe/pyyaml/setup.py'"'"'; __file__='"'"'/tmp/pip-install-nhmea2xe/pyyaml/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' bdist_wheel -d /tmp/pip-wheel-v2ewd51w
       cwd: /tmp/pip-install-nhmea2xe/pyyaml/
  Complete output (6 lines):
  usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
     or: setup.py --help [cmd1 cmd2 ...]
     or: setup.py --help-commands
     or: setup.py cmd --help

  error: invalid command 'bdist_wheel'
  ----------------------------------------
  ERROR: Failed building wheel for pyyaml
  Running setup.py clean for pyyaml

Potential fix applied from:
	https://stackoverflow.com/questions/34819221/why-is-python-setup-py-saying-invalid-command-bdist-wheel-on-travis-ci

Fixes #4605.
2020-09-24 02:02:15 +03:00
Ruslan Kuprieiev 4a82974da4 run-cache: respect cache.shared config option (#4596)
Fixes #4595
2020-09-24 01:00:53 +03:00
Ruslan Kuprieiev 688da04d4a tests: cache test directories (#4601)
* tests: ignore more hdfs tests

* tests: remove unused mark

* gha: don't limit the number of test workers

* cache created test dirs
2020-09-23 23:22:27 +03:00
Paweł Redzyński e1b34442aa plot: accept any viable target in dvc repo (#4602) 2020-09-23 17:02:12 +03:00
Ruslan Kuprieiev 38dd1c9fe7 tests: allow failure for hdfs tests (#4600) 2020-09-23 00:53:32 +03:00
Paweł Redzyński b31bd1cb30 github actions tests: initial (#4469)
* github actions tests: initial

* build: tests: add codecov action
2020-09-22 23:05:06 +03:00
Paweł Redzyński 2b3c30cb0c metrics: accept any viable target in DvcRepo (#4590) 2020-09-22 22:53:55 +03:00
ebaffd728e Restyle Do not require account-level permissions for Azure tree (#4583)
* Do not require account-level permissions for Azure tree

* Only allow the existence check to pass without error if it failed due to authorization issues

* Update dvc/tree/azure.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>

* Update dvc/tree/azure.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>

* Restyled by isort

* Update azure.py

Co-authored-by: Matt Hollingsworth <[email protected]>
Co-authored-by: Matt Hollingsworth <[email protected]>
Co-authored-by: Ruslan Kuprieiev <[email protected]>
Co-authored-by: Restyled.io <[email protected]>
2020-09-22 00:07:56 +03:00
Ruslan Kuprieiev ece7d6ba67 dvc: bump to 1.7.4 2020-09-21 23:50:53 +03:00
Ruslan Kuprieiev cf5d461f4d api: enter state when trying to get a hash (#4584)
`get_hash` is an internal api, so conventions should be respected.

Fixes #4575
2020-09-21 23:37:51 +03:00
Ruslan Kuprieiev ff3b686058 dvc: bump to 1.7.3 2020-09-21 20:24:02 +03:00
Ruslan Kuprieiev 0b9cd4c93c gdrive: don't use hacks for empty files (#4588)
This bug is fixed in PyDrive2 1.6.2.
2020-09-21 20:23:19 +03:00
Robert LowryandRuslan Kuprieiev 15488ebefd scm: add config option to stage files automatically (#4543)
* scm: add "autostage" config option to stage files automatically

Fixes #4330

* Update tests/unit/scm/test_scm.py

* Update tests/unit/scm/test_scm.py

* scm_context, git: call reset_tracked_files from scm_context, remove output message

* Update dvc/repo/scm_context.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2020-09-21 11:59:25 +03:00
Peter Rowlands (변기호) 6a3c516b71 experiments: support committing to existing experiment branches (#4573)
* experiments: stash optional branch to extend

* experiments: support commit to existing experiment branch
2020-09-19 21:49:40 +03:00
548 changed files with 41914 additions and 18963 deletions
+10 -1
View File
@@ -1 +1,10 @@
comment: false
coverage:
status:
project:
default:
target: 90
threshold: 10%
patch:
default:
threshold: 25%
+5
View File
@@ -1,11 +1,16 @@
[run]
branch = True
source = dvc
omit =
*__main__.py
[report]
exclude_lines =
if __name__ == .__main__.:
if typing.TYPE_CHECKING:
if TYPE_CHECKING:
pass
raise NotImplementedError
# pragma: no cover
# pragma: no cover
\.\.\.$
+1
View File
@@ -0,0 +1 @@
* @iterative/dvc
+50 -4
View File
@@ -3,16 +3,62 @@ name: "\U0001F41B Bug Report"
about: Create a bug report to help us improve DVC
---
## Bug Report
# Bug Report
### Please provide information about your setup
<!--
## Issue name
**Output of `dvc version`:**
Issue names must follow the pattern `command: description` where the command is the dvc command that you are trying to run. The description should describe the consequence of the bug.
Example: `repro: doesn't detect input changes`
-->
## Description
<!--
A clear and concise description of what the bug is.
-->
### Reproduce
<!--
Step list of how to reproduce the bug
-->
<!--
Example:
1. dvc init
2. Copy dataset.zip to the directory
3. dvc add dataset.zip
4. dvc run -d dataset.zip -o model ./train.sh
5. modify dataset.zip
6. dvc repro
-->
### Expected
<!--
A clear and concise description of what you expect to happen.
-->
### Environment information
<!--
This is required to ensure that we can reproduce the bug.
-->
**Output of `dvc doctor`:**
```console
$ dvc version
$ dvc doctor
```
**Additional Information (if any):**
<!--
Please check https://github.com/iterative/dvc/wiki/Debugging-DVC on ways to gather more information regarding the issue.
If applicable, please also provide a `--verbose` output of the command, eg: `dvc add --verbose`.
If the issue is regarding the performance, please attach the profiling information and the benchmark comparisons.
-->
+16
View File
@@ -0,0 +1,16 @@
version: 2
updates:
- directory: "/"
package-ecosystem: "pip"
schedule:
interval: "daily"
labels:
- "maintenance"
- directory: "/"
package-ecosystem: "github-actions"
schedule:
interval: "daily"
labels:
- "maintenance"
+9
View File
@@ -0,0 +1,9 @@
pull_request_rules:
- name: backport patches to 1.11
conditions:
- base=master
- label=backport-1.11
actions:
backport:
branches:
- "1.11"
+1
View File
@@ -6,6 +6,7 @@ categories:
labels:
- "feature"
- "enhancement"
- "ui"
- title: "💣 Breaking Changes "
labels:
- "breaking-change"
-19
View File
@@ -1,19 +0,0 @@
name: Lint Python
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up Python 3.8
uses: actions/setup-python@v1
with:
python-version: 3.8
- name: Install requirements
run: bash ./scripts/ci/install.sh
- name: Check Patch
run: ./scripts/ci/check_patch.sh
+144
View File
@@ -0,0 +1,144 @@
name: Build packages
on:
push:
branches:
- master
release:
types: [released, prereleased]
jobs:
deb:
runs-on: ubuntu-18.04
steps:
- uses: actions/[email protected]
- name: Set up Python 3.8
uses: actions/[email protected]
with:
python-version: 3.8
- name: Set up Ruby 2.4
uses: actions/[email protected]
with:
ruby-version: 2.4
- name: Install deps
run: |
pip install .[all]
pip install -r scripts/build-requirements.txt
gem install --no-document fpm
- name: Build deb
run: python scripts/build.py deb
- name: Publish deb
if: github.event_name == 'release'
uses: actions/[email protected]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: scripts/fpm/dvc_${{ github.event.release.tag_name }}_amd64.deb
asset_name: dvc_${{ github.event.release.tag_name }}_amd64.deb
asset_content_type: binary/octet-stream
rpm:
runs-on: ubuntu-18.04
steps:
- uses: actions/[email protected]
- name: Set up Python 3.8
uses: actions/[email protected]
with:
python-version: 3.8
- name: Set up Ruby 2.4
uses: actions/[email protected]
with:
ruby-version: 2.4
- name: Install deps
run: |
pip install .[all]
pip install -r scripts/build-requirements.txt
gem install --no-document fpm
- name: Build rpm
run: python scripts/build.py rpm
- name: Publish rpm
if: github.event_name == 'release'
uses: actions/[email protected]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: scripts/fpm/dvc-${{ github.event.release.tag_name }}-1.x86_64.rpm
asset_name: dvc-${{ github.event.release.tag_name }}-1.x86_64.rpm
asset_content_type: binary/octet-stream
osxpkg:
runs-on: macos-10.15
steps:
- uses: actions/[email protected]
- name: Set up Python 3.8
uses: actions/[email protected]
with:
python-version: 3.8
- name: Set up Ruby 2.4
uses: actions/[email protected]
with:
ruby-version: 2.4
- name: Install deps
run: |
# available lxml (webdav dependency) wheels are built with an old
# SDK, which breaks osxpkg notarization. Building from scratch
# instead.
pip install .[all] --no-binary lxml
pip install -r scripts/build-requirements.txt
gem install --no-document fpm
- name: Build
run: python scripts/build.py osxpkg
- name: Publish
if: github.event_name == 'release'
uses: actions/[email protected]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: scripts/fpm/dvc-${{ github.event.release.tag_name }}.pkg
asset_name: dvc-${{ github.event.release.tag_name }}.pkg
asset_content_type: binary/octet-stream
exe:
runs-on: windows-2019
steps:
- uses: actions/[email protected]
- name: Set up Python 3.8
uses: actions/[email protected]
with:
python-version: 3.8
- name: Install deps
run: |
pip install .[all]
pip install -r scripts/build-requirements.txt
- name: Pull images
run: |
dvc pull
- name: Build
run: python scripts\build.py exe
shell: cmd
- name: Publish
if: github.event_name == 'release'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: actions/[email protected]
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: scripts/innosetup/dvc-${{ github.event.release.tag_name }}.exe
asset_name: dvc-${{ github.event.release.tag_name }}.exe
asset_content_type: binary/octet-stream
pip:
runs-on: ubuntu-18.04
steps:
- uses: actions/[email protected]
- name: Set up Python 3.7
uses: actions/[email protected]
with:
python-version: 3.7
- name: Install
run: pip install ".[all,tests]"
- name: Build packages
run: ./scripts/build_package.sh
- name: Publish packages to PyPI
if: github.event_name == 'release'
uses: pypa/gh-action-pypi-publish@master
with:
user: __token__
password: ${{ secrets.PYPI_TOKEN }}
+134
View File
@@ -0,0 +1,134 @@
name: Tests
on:
pull_request:
branches:
- master
push:
branches:
- master
schedule:
- cron: '5 1 * * *' # every day at 01:05
env:
DVC_TEST: "true"
HOMEBREW_NO_AUTO_UPDATE: 1
SHELL: /bin/bash
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/[email protected]
- name: Set up Python 3.8
uses: actions/[email protected]
with:
python-version: 3.8
- name: Install requirements
run: |
pip install wheel
pip install ".[all,tests]" Pygments collective.checkdocs pre-commit
- name: install temporary dependencies
run: |
pip install git+https://github.com/isidentical/gdrivefs@service-account
pip install git+https://github.com/isidentical/pydata-google-auth@service-account
- name: Check README
run: python setup.py checkdocs
- uses: pre-commit/[email protected]
tests:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-18.04, windows-2019, macos-10.15]
pyv: ["3.6", "3.7", "3.8", "3.9"]
include:
- os: "macos-10.15"
pyv: "3.10.0-rc.1"
exclude:
- os: macos-10.15
pyv: "3.9"
- os: macos-10.15
pyv: "3.7"
steps:
- uses: actions/[email protected]
- name: Set up Python
uses: actions/[email protected]
with:
python-version: ${{ matrix.pyv }}
- name: get pip cache dir
id: pip-cache-dir
run: |
echo "::set-output name=dir::$(pip cache dir)"
- name: set pip cache
id: pip-cache
uses: actions/[email protected]
with:
path: ${{ steps.pip-cache-dir.outputs.dir }}
key: ${{ runner.os }}-pip-${{ hashFiles('setup.py') }}
restore-keys: |
${{ runner.os }}-pip-
- name: install libgit2 on Python3.10 in macOS
if: matrix.pyv == '3.10.0-rc.1' && matrix.os == 'macos-10.15'
run: |
brew install libgit2
- name: install
if: steps.cache.pip-cache-dir.cache-hit != 'true'
run: |
pip install --upgrade pip setuptools wheel
pip install -e ".[all,tests]"
- name: install temporary dependencies
run: |
pip install git+https://github.com/isidentical/gdrivefs@service-account
pip install git+https://github.com/isidentical/pydata-google-auth@service-account
- name: setup git
run: |
git config --global user.email "[email protected]"
git config --global user.name "DVC Tester"
- name: setup gs creds
shell: bash
env:
GS_CREDS_JSON: ${{ secrets.GS_CREDS_JSON }}
if: env.GS_CREDS_JSON != ''
run: |
mkdir -p scripts/ci
echo "$GS_CREDS_JSON" > scripts/ci/gcp-creds.json
- name: disable gdrive tests
if: >-
github.event_name != 'schedule' &&
(matrix.pyv != '3.8' || matrix.os != 'ubuntu-18.04')
run: |
echo "extra_test_args=--disable-gdrive" >> $GITHUB_ENV
- name: run tests
env:
GDRIVE_CREDENTIALS_DATA: ${{ secrets.GDRIVE_CREDENTIALS_DATA }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
OSS_ACCESS_KEY_ID: ${{ secrets.OSS_ACCESS_KEY_ID}}
OSS_ACCESS_KEY_SECRET: ${{ secrets.OSS_ACCESS_KEY_SECRET}}
OSS_ENDPOINT: ${{ secrets.OSS_ENDPOINT}}
run: >-
python -m tests -n=4
--cov-report=xml --cov-report=term
--all ${{ env.extra_test_args }}
- name: upload coverage report
uses: codecov/[email protected]
with:
file: ./coverage.xml
fail_ci_if_error: false
notify:
if: github.ref == 'refs/heads/master' && failure()
needs:
- lint
- tests
runs-on: ubuntu-latest
steps:
- name: Slack Notification
uses: rtCamp/[email protected]
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: 'CI Failed on master :boom:'
SLACK_TITLE: CI Status
SLACK_USERNAME: DVC-CI
+1
View File
@@ -5,6 +5,7 @@ neatlynx/__pycache__
*.pyc
.env/
.env2.7/
.python-version
.dvc.conf.lock
.DS_Store
+25 -4
View File
@@ -1,14 +1,24 @@
ci:
skip: [pylint, dvc-pre-commit]
repos:
- hooks:
- id: black
language_version: python3
repo: https://github.com/ambv/black
rev: 19.10b0
rev: 21.7b0
- hooks:
- id: codespell
args:
- --ignore-words-list
- ba,datas,fo,uptodate
repo: https://github.com/codespell-project/codespell
rev: v2.1.0
- hooks:
- id: isort
language_version: python3
repo: https://github.com/timothycrosley/isort
rev: 5.4.2
rev: 5.9.3
- hooks:
- id: flake8
language_version: python3
@@ -18,7 +28,18 @@ repos:
- flake8-debugger
- flake8-string-format
repo: https://gitlab.com/pycqa/flake8
rev: 3.8.3
rev: 3.9.2
- hooks:
- id: mypy
language_version: python3
files: ^dvc/
additional_dependencies:
- types-requests
- types-paramiko
- types-tabulate
- types-toml
repo: https://github.com/pre-commit/mirrors-mypy
rev: 'v0.910'
- repo: local
hooks:
- id: pylint
@@ -33,7 +54,7 @@ repos:
id: beautysh
language_version: python3
repo: https://github.com/lovesegfault/beautysh
rev: master
rev: v6.1.0
- hooks:
- args:
- git-hook
+17 -9
View File
@@ -3,7 +3,7 @@
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-whitelist=
extension-pkg-whitelist=pygit2
# Add files or directories to the blacklist. They should be base names, not
# paths.
@@ -15,10 +15,13 @@ ignore-patterns=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
init-hook="import sys; \
from pathlib import Path; \
from pylint.config import find_pylintrc; \
sys.path.append(Path(find_pylintrc()).parent / 'tests');"
init-hook="import sys;
from pathlib import Path;
from pylint.config import find_pylintrc;
tests_folder = Path(find_pylintrc()).parent / 'tests'
sys.path.append(str(tests_folder));
"
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
@@ -178,13 +181,17 @@ disable=format,
# we use it to speedup/optimize
import-outside-toplevel,
fixme,
# we use cause of the exception for error message output
# so some of the times we don't use `raise from` to not show the cause
# of the error
raise-missing-from,
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
enable=c-extension-no-member
enable=c-extension-no-member,
no-else-return,
[REPORTS]
@@ -265,7 +272,7 @@ contextmanager-decorators=contextlib.contextmanager
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=pytest.lazy_fixture,logging.TRACE,logger.trace,sys.getwindowsversion
generated-members=hdfs.*,pytest.lazy_fixture,logging.TRACE,logger.trace,sys.getwindowsversion
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
@@ -292,7 +299,7 @@ ignored-classes=optparse.Values,thread._local,_thread._local,Dvcfile
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
ignored-modules=azure
# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
@@ -306,6 +313,7 @@ missing-member-hint-distance=1
# showing a hint for a missing member.
missing-member-max-choices=1
signature-mutators=funcy.decorators.decorator,
[VARIABLES]
-20
View File
@@ -1,20 +0,0 @@
---
restylers:
- name: black
image: restyled/restyler-black:v19.10b0
command:
- black
arguments: []
include:
- "**/*.py"
interpreters:
- python
- name: isort
image: restyled/restyler-isort:v5.4.2
command:
- isort
arguments: []
include:
- "**/*.py"
interpreters:
- python
-162
View File
@@ -1,162 +0,0 @@
cache:
directories:
- $HOME/.cache/pip
- $HOME/.pyenv
dist: xenial
env:
global:
- HOMEBREW_NO_AUTO_UPDATE=1
- PYENV_ROOT="$HOME/.pyenv"
- PATH="$PYENV_ROOT/bin:$PATH"
- PIP_CACHE_DIR="$HOME/.cache/pip" # unify pip cache location for all platforms
stages:
- check
- test
- build
# Travis doesn't support testing python on Mac yet, so we need
# to workaround it by installing it directly with brew.
#
# Note that we specify language as ruby-2.4.3 to help us satisfy
# fpm dependencies, as travis runs el capitan with an old ruby
# version, that causes fpm to crash.
#
# Each job will use the previous job's `stage` tag if not specified.
jobs:
include:
# patch formatting checks
- name: "Check patch formatting"
stage: check
os: linux
language: python
python: 3.7
before_install: bash ./scripts/ci/install.sh
install:
script: ./scripts/ci/check_patch.sh
# test jobs
- name: "3.7 on Windows"
stage: test
os: windows # Windows 10.0.17134 N/A Build 17134
language: shell # 'language: python' is an error on Travis CI Windows
env:
- PYTHON_VERSION=3.7.5
- PATH=/c/Python37:/c/Python37/Scripts:$PATH
- name: "3.8 on Windows"
os: windows # Windows 10.0.17134 N/A Build 17134
language: shell # 'language: python' is an error on Travis CI Windows
env:
- PYTHON_VERSION=3.8.0
- PATH=/c/Python38:/c/Python38/Scripts:$PATH
- name: "3.6 on Linux"
language: python
python: 3.6
- name: "3.7 on Linux"
language: python
python: 3.7
- name: "3.8 on Linux"
language: python
python: 3.8
- name: "3.7 on Mac"
os: osx
osx_image: xcode11.3
# build jobs
- name: "Mac pkg"
stage: build
os: osx
osx_image: xcode9.4
language: ruby
rvm: 2.4.3
install:
script: ./scripts/build_posix.sh
- name: "Windows pkg"
os: windows
language: shell
install:
script: ./scripts/build_windows.cmd
env:
- PYTHON_VERSION=3.7.5
- PATH=/c/Python37:/c/Python37/Scripts:$PATH
- name: "Linux pkgs"
language: python
python: 3.7
before_install:
install:
script: ./scripts/build_posix.sh
- name: "PyPI pkgs"
language: python
python: 3.8
script: ./scripts/build_package.sh
- name: Snapcraft snap
language: python
python: 3.8
addons:
snaps:
- name: snapcraft
channel: stable
confinement: classic
- name: review-tools
channel: stable
- name: lxd
channel: stable
env:
- SNAPCRAFT_IMAGE_INFO: |
'{"build_url": "$TRAVIS_JOB_WEB_URL"}'
- SNAPCRAFT_BUILD_ENVIRONMENT: lxd
- SNAPCRAFT_BUILD_INFO: 1 # https://snapcraft.io/blog/introducing-developer-notifications-for-snap-security-updates
install:
- sudo usermod --append --groups lxd $USER
- sudo /snap/bin/lxd.migrate -yes
- sudo /snap/bin/lxd waitready
- sudo /snap/bin/lxd init --auto
script:
- ./scripts/build_snap.sh
after_failure:
- sudo journalctl -u snapd
before_install:
- bash ./scripts/ci/before_install.sh
- source env.sh
- bash ./scripts/ci/check_python.sh
install:
- bash ./scripts/ci/install.sh
script:
- bash ./scripts/ci/script.sh
deploy:
- provider: releases
api_key:
secure: nyo17gF4Xl6DFL/KVkMewC805Cv+eK99L7SiYCU+WUAYDIzzFL3YJm7fm8bznPeor7DmpO7uSLSveMnJETBtdurW6JWhCTUss4yD66yFzpk6Z5cvHf/1UA5pdMHkkMM54fRaTRt6L2Yal7RPwGEQTBECLXW1Xmf78Y6+0l8lTvXQ9jXOQSRO/63VDKI5Zww2+XytVicUFlr0hLMGKUuaCjjXcQlAPzBgQz3/5Iz6EtzeTf2o+gTPjocbRbSFPikfiBXUn8XlJ8jBj7i114XJRJffw59KxYGDzxp+enfLBULGNoW0Np2hZqkKkPUIdB08C8XVbRXlFYX8cEYBczjqhv8A05p5F5CnltnqQ0KazcoUP2awPVqn68dWNRF+kqrnMBus8euYKIZ4dy/6bVEKJRa84ESJXZVDcwXTAXRqyovSxOlXqg9ZgAImMKBfKn/fpDX2xIvQPaXcnJxKbmcePfdbC1d0HYJ5jkbNaxKE/T15VvhzIho7mJ2WAv1r8QouQUuzqtpzIGbRn/KXHRUzCtyP0k4+eMKMLFOPzg848WsDarP3v8VYiSy4nFY7ZP7DRd3KttvW5bgVbSeO4jPmTgS6TnnpRwual9BJXSG7bjbfpIMii3bNZ7jWL4zpKT9Jfm8w9RGKLcX9HqkZ3+y60+LtYdhEfBvqsIlWQoIJBXg=
file_glob: true
file:
- dvc*.rpm
- dvc*.deb
- dvc*.pkg
- dvc*.exe
skip_cleanup: true
on:
condition: "$(./scripts/ci/deploy_condition.sh dvc*.rpm dvc*.deb dvc*.pkg dvc*.exe)"
tags: true
repo: iterative/dvc
stage: build
- provider: pypi
user:
secure: "IOzNfgPoG3ICg3+s4AuZCCnawgd/KqMzeiAB9R0B9Xb6ProPQJp9O3GBZrtg5OJMHdWQQtwULeOh/LWk4sFdXMcX1iNEgm5Tb7DwO7TDtltW14jUgN9AqfY2twj4UIsW/ON8w0P8v36ZoQq1kO6Pfgjijdm3w+9h6rpps8rJ6jXYpVZVO+SSzzijWddanMkEz4GgctZ2r7U5MWlmm1UogF60kEZIZCQykz0yJnhTpP9nMFDKw6Maki/mcfVwTQ/v3RqjzttHei/aaEMGQXYr3TOX8NPYJXerBpjShDZqcgcCfqPzhi7uCwVYaPvRsMMYH5jgtMz/SO3J6ToeGKT0f1F1ihRyCdusg325TMK7Vv8yHJzL4ngeUbb/oqxWtxhLY7f77Lkx6hBwe6ROpTqiQHIfZad5qyIHihKcDcIHo4FwLHVzxR4y+Ds2js5dXwBkUBaEAx0Dmc6ZdgXqxd815GDwv+VVpzXJhtO6qXmKE36F/Oe6eCNG3dk5MdOK4C2DrM9M8MXllqeQtX6JrM2stWlBalAc9Jn4b0xxAoghX3x3G3hNsLc9Uist1VinY2+IcPozusqj66c+Vbi5YngWSbICOjlIcq2GaOlBi3xioGts396442ztf1eLjMVuvykCI3b4Yz/vJYZOZjUvG/6nLfaFQTJLSXkTvI4do3foeY4="
password:
secure: "ts/3FSPL+w4euXryip9wCJWUqUwuJ2CqHi0nEpYVM10KL0cp9aMkQXN/7C8ih+j/JZ25kaO+tis1nx0JERVnKrg/1F/fJhxHTpakCP4OSIlhKRXDt7yUUHcKKt5QdM2XCKhApptP6RTpWL1vlc36CMrhdvTcMYp7hlDOiWjdhhim24FGQzabL42Ko59ttyyCROqgcnjHqj+WlhJl7iYO+lv2PLOtslpkIYY5EMjgBLuGyOvxTqZsAXtworsASdB2CrsTE6iMRcrM1afgkyBlUqDezoiv9TwfZrZ85oRoAxPB7su4QWK7lEP0wGQV/SDz/RL7c+cNfRaauxUKVcC8I2LfaEpudv8vi205+v/08wzPbS4xSOnf89irUJnyGxUy+bYLoXPPVPMyzcQRAUnrQl+ENCmN62HU+/2pkIyiUJvt0wYYP0vp+3pIbXbYQGmAIMq7nZJd7K3rikfnQIGKI03xZsFe3PEdjzhDI9Ej/tRUbJx4pgg084QuYYca6MSYn/zJ5pqIG4A7ngCSxoxoWZT0duo8PrUWQeT17L73Q86HeEv3OzZhWfEF2fNDN3Qbsu4CllSZnbE7DXgAuysTwTYHS/na6lp7wejOqdRUXrHbidVla9Mwxyc9JdWNWzUDxALvNxAdNbV6l85mO8izy8wjQF6M1O7idqTfjQeR4pc="
skip_existing: true
file_glob: true
file:
- dist/dvc*.whl
- dist/dvc*.tar.gz
skip_cleanup: true
on:
condition: "$(./scripts/ci/deploy_condition.sh dist/dvc*.whl dist/dvc*.tar.gz)"
tags: true
repo: iterative/dvc
stage: build
- provider: script
skip_cleanup: true
script: "(echo $SNAP_TOKEN | snapcraft login --with -) && timeout 600 snapcraft push dvc_*.snap --release $SNAP_CHANNEL || echo timed out"
on:
all_branches: true
condition: "$(./scripts/ci/deploy_condition.sh dvc_*.snap) && ($TRAVIS_BRANCH = master || -n $TRAVIS_TAG) && -n $SNAP_CHANNEL"
repo: iterative/dvc
stage: build
+1
View File
@@ -1,2 +1,3 @@
include fastentrypoints.py
include LICENSE
include test_requirements.txt
+26 -40
View File
@@ -29,9 +29,9 @@ learning projects. Key features:
#. Helps manage experiments with Git tags/branches and **metrics** tracking.
**DVC** aims to replace spreadsheet and document sharing tools (such as Excel or Google Docs)
which are being used frequently as both knowledge repositories and team ledgers.
DVC also replaces both ad-hoc scripts to track, move, and deploy different model versions;
as well as ad-hoc data file suffixes and prefixes.
frequently used as both knowledge repositories and team ledgers.
DVC also replaces both ad-hoc scripts to track, move, and deploy different model versions
and ad-hoc data file suffixes and prefixes.
.. contents:: **Contents**
:backlinks: none
@@ -129,7 +129,11 @@ Conda (Anaconda)
.. code-block:: bash
conda install -c conda-forge dvc
conda install -c conda-forge mamba # installs much faster than conda
mamba install -c conda-forge dvc
Depending on the remote storage type you plan to use to keep and share your data, you might need to
install optional dependencies: `dvc-s3`, `dvc-azure`, `dvc-gdrive`, `dvc-gs`, `dvc-oss`, `dvc-ssh`.
pip (PyPI)
----------
@@ -178,6 +182,12 @@ 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, model and ML pipelines represent a single ML project and focuses on data scientist' experience while
data engineering pipeline orchestrates multiple data projects and focuses on efficient execution. DVC project can be used
from data pipeline as a single execution step. `AirFlow DVC <https://github.com/covid-genomics/airflow-dvc>`_ is an example
of such integration.
#. `Git-annex <https://git-annex.branchable.com/>`_ - DVC uses the idea of storing the content of large files (which should
not be in a Git repository) in a local key-value store, and uses file hardlinks/symlinks instead of
copying/duplicating files.
@@ -191,7 +201,12 @@ Comparison to related technologies
#. `Workflow Management Systems <https://en.wikipedia.org/wiki/Workflow_management_system>`_ - DVC is a workflow
management system designed specifically to manage machine learning experiments. DVC is built on top of Git.
#. `DAGsHub <https://dagshub.com/>`_ - This is a Github equivalent for DVC. Pushing Git+DVC based repositories to DAGsHub will produce in a high level project dashboard; including DVC pipelines and metrics visualizations, as well as links to any DVC-managed files present in cloud storage.
#. `DAGsHub <https://dagshub.com/>`_ - online service of Git+DVC repositories with pipeline and metrics visualization and
DVC-specific cloud storage.
#. `DVC Studio <https://studio.iterative.ai/>`_ - online service of DVC repositories visualisation from DVC team. Also,
integrated with `CML (CI/CD for ML) <https://cml.dev/>`_ for training models in clouds and Kubernetes.
Contributing
============
@@ -201,38 +216,6 @@ Contributing
Contributions are welcome! Please see our `Contributing Guide <https://dvc.org/doc/user-guide/contributing/core>`_ for more
details.
.. image:: https://sourcerer.io/fame/efiop/iterative/dvc/images/0
:target: https://sourcerer.io/fame/efiop/iterative/dvc/links/0
:alt: 0
.. image:: https://sourcerer.io/fame/efiop/iterative/dvc/images/1
:target: https://sourcerer.io/fame/efiop/iterative/dvc/links/1
:alt: 1
.. image:: https://sourcerer.io/fame/efiop/iterative/dvc/images/2
:target: https://sourcerer.io/fame/efiop/iterative/dvc/links/2
:alt: 2
.. image:: https://sourcerer.io/fame/efiop/iterative/dvc/images/3
:target: https://sourcerer.io/fame/efiop/iterative/dvc/links/3
:alt: 3
.. image:: https://sourcerer.io/fame/efiop/iterative/dvc/images/4
:target: https://sourcerer.io/fame/efiop/iterative/dvc/links/4
:alt: 4
.. image:: https://sourcerer.io/fame/efiop/iterative/dvc/images/5
:target: https://sourcerer.io/fame/efiop/iterative/dvc/links/5
:alt: 5
.. image:: https://sourcerer.io/fame/efiop/iterative/dvc/images/6
:target: https://sourcerer.io/fame/efiop/iterative/dvc/links/6
:alt: 6
.. image:: https://sourcerer.io/fame/efiop/iterative/dvc/images/7
:target: https://sourcerer.io/fame/efiop/iterative/dvc/links/7
:alt: 7
Mailing List
============
@@ -254,6 +237,9 @@ Citation
Iterative, *DVC: Data Version Control - Git for Data & Models* (2020)
`DOI:10.5281/zenodo.012345 <https://doi.org/10.5281/zenodo.3677553>`_.
Barrak, A., Eghan, E.E. and Adams, B. `On the Co-evolution of ML Pipelines and Source Code - Empirical Study of DVC Projects <https://mcis.cs.queensu.ca/publications/2021/saner.pdf>`_ , in Proceedings of the 28th IEEE International Conference on Software Analysis, Evolution, and Reengineering, SANER 2021. Hawaii, USA.
.. |Banner| image:: https://dvc.org/img/logo-github-readme.png
:target: https://dvc.org
:alt: DVC logo
@@ -262,9 +248,9 @@ Iterative, *DVC: Data Version Control - Git for Data & Models* (2020)
:target: https://travis-ci.com/iterative/dvc/branches
:alt: Release
.. |CI| image:: https://img.shields.io/travis/com/iterative/dvc/master?label=dev&logo=travis
:target: https://travis-ci.com/iterative/dvc/builds
:alt: Travis dev branch
.. |CI| image:: https://github.com/iterative/dvc/workflows/Tests/badge.svg?branch=master
:target: https://github.com/iterative/dvc/actions
:alt: GHA Tests
.. |Maintainability| image:: https://codeclimate.com/github/iterative/dvc/badges/gpa.svg
:target: https://codeclimate.com/github/iterative/dvc
-5
View File
@@ -1,5 +0,0 @@
dvc (0.75.b1) experimental; urgency=low
* First native Debian package version.
-- Dmitry Petrov <[email protected]> Sat, 07 Dec 2019 09:49:28 +0300
-1
View File
@@ -1 +0,0 @@
10
-33
View File
@@ -1,33 +0,0 @@
Source: dvc
Maintainer: Dmitry Petrov <[email protected]>
Section: python
Priority: optional
Build-Depends: dh-python, python3-setuptools, python3-all, debhelper (>= 9)
Standards-Version: 4.0.0
Homepage: http://dvc.org
Vcs-Git: https://github.com/iterative/dvc.git
Vcs-browser: https://github.com/iterative/dvc
Package: dvc
Architecture: all
Depends: ${misc:Depends}, ${python3:Depends}, python3-flufl.lock (>=3.2)
Recommends: python3-arrow (=0.14.0),
python3-paramiko (>=2.5.0),
python3-gssapi (>= 2.5.0),
python3-boto3 (>=1.9.86),
python3-pydrive (=1.3.1),
python3-azure-storage-blob (=12.1.0),
python3-oss2 (=2.6.1),
python3-google-cloud-storage (=1.19.0)
Description: Git for data scientists - manage your code and data together
Data Version Control or DVC is an open-source tool for data science and machine learning projects.
.
Key features:
.
1. Simple command line Git-like experience. Does not require installing and maintaining any databases. Does not depend on any proprietary online services.
.
2. Management and versioning of datasets and machine learning models. Data is saved in S3, Google cloud, Azure, Alibaba cloud, SSH server, HDFS, or even local HDD RAID.
.
3. Makes projects reproducible and shareable; helping to answer questions about how a model was built.
.
4. Helps manage experiments with Git tags/branches and metrics tracking.
-11
View File
@@ -1,11 +0,0 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: DVC
Upstream-Contact: Dmitry Petrov <[email protected]>
Source: https://github.com/iterative/dvc
Files: *
Copyright: Dmitry Petrov <[email protected]>
License: Apache 2.0
On Debian systems, the full text of the Apache 2.0
License can be found in the file
`/usr/share/common-licenses/Apache-2.0`.
-6
View File
@@ -1,6 +0,0 @@
#!/usr/bin/make -f
export PYBUILD_NAME=dvc
%:
dh $@ --with python3 --buildsystem=pybuild
-1
View File
@@ -1 +0,0 @@
3.0 (native)
-4
View File
@@ -1,4 +0,0 @@
# please also check http://pypi.debian.net/dvc/watch
version=3
opts=uversionmangle=s/(rc|a|b|c)/~$1/ \
http://pypi.debian.net/dvc/dvc-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz)))
+91
View File
@@ -0,0 +1,91 @@
from contextlib import ExitStack, contextmanager
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from argparse import Namespace
@contextmanager
def instrument(html_output=False):
"""Run a statistical profiler"""
try:
from pyinstrument import Profiler # pylint: disable=import-error
except ImportError:
print("Failed to run profiler, pyinstrument is not installed")
yield
return
profiler = Profiler()
profiler.start()
yield
profiler.stop()
if html_output:
profiler.open_in_browser()
return
print(profiler.output_text(unicode=True, color=True))
@contextmanager
def profile(dump_path: str = None):
"""Run a cprofile"""
import cProfile
prof = cProfile.Profile()
prof.enable()
yield
prof.disable()
if not dump_path:
prof.print_stats(sort="cumtime")
return
prof.dump_stats(dump_path)
@contextmanager
def debug():
try:
yield
except Exception: # pylint: disable=broad-except
try:
import ipdb as pdb # noqa: T100, pylint: disable=import-error
except ImportError:
import pdb # noqa: T100
pdb.post_mortem()
raise # prevent from jumping ahead
@contextmanager
def debugtools(args: "Namespace" = None, **kwargs):
kw = vars(args) if args else {}
kw.update(kwargs)
with ExitStack() as stack:
if kw.get("pdb"):
stack.enter_context(debug())
if kw.get("cprofile") or kw.get("cprofile_dump"):
stack.enter_context(profile(kw.get("cprofile_dump")))
if kw.get("instrument") or kw.get("instrument_open"):
stack.enter_context(instrument(kw.get("instrument_open", False)))
yield
def add_debugging_flags(parser):
from argparse import SUPPRESS
parser.add_argument(
"--cprofile", action="store_true", default=False, help=SUPPRESS
)
parser.add_argument("--cprofile-dump", help=SUPPRESS)
parser.add_argument(
"--pdb", action="store_true", default=False, help=SUPPRESS
)
parser.add_argument(
"--instrument", action="store_true", default=False, help=SUPPRESS
)
parser.add_argument(
"--instrument-open", action="store_true", default=False, help=SUPPRESS
)
+36 -21
View File
@@ -1,24 +1,8 @@
import json
import logging
import os
import platform
import sys
import tempfile
import uuid
import distro
import requests
from dvc import __version__
from dvc.config import Config, to_bool
from dvc.daemon import daemon
from dvc.exceptions import NotDvcRepoError
from dvc.lock import Lock, LockError
from dvc.repo import Repo
from dvc.scm import SCM, NoSCM
from dvc.scm.base import SCMError
from dvc.utils import env2bool, is_binary
from dvc.utils.fs import makedirs
from .env import DVC_NO_ANALYTICS
logger = logging.getLogger(__name__)
@@ -35,6 +19,10 @@ def collect_and_send_report(args=None, return_code=None):
report as a JSON, where the _collector_ generates it and the _sender_
removes it after sending it.
"""
import tempfile
from dvc.daemon import daemon
report = {}
# Include command execution information on the report only when available.
@@ -46,16 +34,22 @@ def collect_and_send_report(args=None, return_code=None):
with tempfile.NamedTemporaryFile(delete=False, mode="w") as fobj:
json.dump(report, fobj)
daemon(["analytics", fobj.name])
daemon(["analytics", fobj.name])
def is_enabled():
from dvc.config import Config, to_bool
from dvc.utils import env2bool
if env2bool("DVC_TEST"):
return False
enabled = to_bool(
Config(validate=False).get("core", {}).get("analytics", "true")
)
enabled = not os.getenv(DVC_NO_ANALYTICS)
if enabled:
enabled = to_bool(
Config(validate=False).get("core", {}).get("analytics", "true")
)
logger.debug("Analytics is {}abled.".format("en" if enabled else "dis"))
return enabled
@@ -69,6 +63,8 @@ def send(path):
`collect_and_send_report`. Sending happens on another process,
thus, the need of removing such file afterwards.
"""
import requests
url = "https://analytics.dvc.org"
headers = {"content-type": "application/json"}
@@ -86,6 +82,11 @@ def send(path):
def _scm_in_use():
from dvc.exceptions import NotDvcRepoError
from dvc.repo import Repo
from dvc.scm import SCM, NoSCM
from dvc.scm.base import SCMError
try:
scm = SCM(root_dir=Repo.find_root())
return type(scm).__name__
@@ -99,6 +100,9 @@ def _runtime_info():
"""
Gather information from the environment where DVC runs to fill a report.
"""
from dvc import __version__
from dvc.utils import is_binary
return {
"dvc_version": __version__,
"is_binary": is_binary(),
@@ -109,6 +113,11 @@ def _runtime_info():
def _system_info():
import platform
import sys
import distro
system = platform.system()
if system == "Windows":
@@ -147,6 +156,12 @@ def _find_or_create_user_id():
IDs are generated randomly with UUID.
"""
import uuid
from dvc.config import Config
from dvc.lock import Lock, LockError
from dvc.utils.fs import makedirs
config_dir = Config.get_dir("global")
fname = os.path.join(config_dir, "user_id")
lockfile = os.path.join(config_dir, "user_id.lock")
+37 -24
View File
@@ -1,15 +1,9 @@
import os
from contextlib import _GeneratorContextManager as GCM
from contextlib import contextmanager
from funcy import reraise
from dvc.exceptions import (
NotDvcRepoError,
OutputNotFoundError,
PathMissingError,
)
from dvc.external_repo import external_repo
from dvc.exceptions import OutputNotFoundError, PathMissingError
from dvc.path_info import PathInfo
from dvc.repo import Repo
@@ -20,22 +14,22 @@ def get_url(path, repo=None, rev=None, remote=None):
in a DVC repo. For Git repos, HEAD is used unless a rev argument is
supplied. The default remote is tried unless a remote argument is supplied.
Raises OutputNotFoundError if the file is not a dvc-tracked file.
Raises OutputNotFoundError if the file is not tracked by DVC.
NOTE: This function does not check for the actual existence of the file or
directory in the remote storage.
"""
with _make_repo(repo, rev=rev) as _repo:
with Repo.open(repo, rev=rev, subrepos=True, uninitialized=True) as _repo:
path_info = PathInfo(_repo.root_dir) / path
with reraise(FileNotFoundError, PathMissingError(path, repo)):
metadata = _repo.repo_tree.metadata(path_info)
metadata = _repo.repo_fs.metadata(path_info)
if not metadata.is_dvc:
raise OutputNotFoundError(path, repo)
cloud = metadata.repo.cloud
hash_info = _repo.repo_tree.get_hash(path_info)
return cloud.get_url_for(remote, checksum=hash_info.value)
md5 = metadata.repo.dvcfs.info(path_info)["md5"]
return cloud.get_url_for(remote, checksum=md5)
def open( # noqa, pylint: disable=redefined-builtin
@@ -78,7 +72,7 @@ class _OpenContextManager(GCM):
def _open(path, repo=None, rev=None, remote=None, mode="r", encoding=None):
with _make_repo(repo, rev=rev) as _repo:
with Repo.open(repo, rev=rev, subrepos=True, uninitialized=True) as _repo:
with _repo.open_by_relpath(
path, remote=remote, mode=mode, encoding=encoding
) as fd:
@@ -97,14 +91,33 @@ def read(path, repo=None, rev=None, remote=None, mode="r", encoding=None):
return fd.read()
@contextmanager
def _make_repo(repo_url=None, rev=None):
repo_url = repo_url or os.getcwd()
if rev is None and os.path.exists(repo_url):
try:
yield Repo(repo_url, subrepos=True)
return
except NotDvcRepoError:
pass # fallthrough to external_repo
with external_repo(url=repo_url, rev=rev) as repo:
yield repo
def make_checkpoint():
"""
Signal DVC to create a checkpoint experiment.
If the current process is being run from DVC, this function will block
until DVC has finished creating the checkpoint. Otherwise, this function
will return immediately.
"""
import builtins
from time import sleep
from dvc.env import DVC_CHECKPOINT, DVC_ROOT
from dvc.stage.monitor import CheckpointTask
if os.getenv(DVC_CHECKPOINT) is None:
return
root_dir = os.getenv(DVC_ROOT, Repo.find_root())
signal_file = os.path.join(
root_dir, Repo.DVC_DIR, "tmp", CheckpointTask.SIGNAL_FILE
)
with builtins.open(signal_file, "w") as fobj:
# NOTE: force flushing/writing empty file to disk, otherwise when
# run in certain contexts (pytest) file may not actually be written
fobj.write("")
fobj.flush()
os.fsync(fobj.fileno())
while os.path.exists(signal_file):
sleep(0.1)
-176
View File
@@ -1,176 +0,0 @@
"""Manages cache of a DVC repo."""
from collections import defaultdict
from funcy import cached_property
def _make_remote_property(name):
"""
The config file is stored in a way that allows you to have a
cache for each remote.
This is needed when specifying external outputs
(as they require you to have an external cache location).
Imagine a config file like the following:
['remote "dvc-storage"']
url = ssh://localhost/tmp
ask_password = true
[cache]
ssh = dvc-storage
This method creates a cached property, containing cache named `name`:
self.config == {'ssh': 'dvc-storage'}
self.ssh # a RemoteSSH instance
"""
def getter(self):
from ..tree import get_cloud_tree
from .base import CloudCache
remote = self.config.get(name)
if not remote:
return None
tree = get_cloud_tree(self.repo, name=remote)
return CloudCache(tree)
getter.__name__ = name
return cached_property(getter)
class Cache:
"""Class that manages cache locations of a DVC repo.
Args:
repo (dvc.repo.Repo): repo instance that this cache belongs to.
"""
CACHE_DIR = "cache"
def __init__(self, repo):
from ..tree import get_cloud_tree
from .local import LocalCache
self.repo = repo
self.config = config = repo.config["cache"]
local = config.get("local")
if local:
settings = {"name": local}
else:
from ..config import LOCAL_COMMON
settings = {"url": config["dir"]}
for opt in LOCAL_COMMON.keys():
if opt in config:
settings[str(opt)] = config.get(opt)
tree = get_cloud_tree(repo, **settings)
self.local = LocalCache(tree)
s3 = _make_remote_property("s3")
gs = _make_remote_property("gs")
ssh = _make_remote_property("ssh")
hdfs = _make_remote_property("hdfs")
azure = _make_remote_property("azure")
class NamedCacheItem:
def __init__(self):
self.names = set()
self.children = defaultdict(NamedCacheItem)
def __eq__(self, other):
return self.names == other.names and self.children == other.children
def child_keys(self):
for key, child in self.children.items():
yield key
yield from child.child_keys()
def child_names(self):
for key, child in self.children.items():
yield key, child.names
yield from child.child_names()
def add(self, checksum, item):
self.children[checksum].update(item)
def update(self, item, suffix=""):
if suffix:
self.names.update(n + suffix for n in item.names)
else:
self.names.update(item.names)
for checksum, child_item in item.children.items():
self.children[checksum].update(child_item)
class NamedCache:
# pylint: disable=protected-access
def __init__(self):
self._items = defaultdict(lambda: defaultdict(NamedCacheItem))
self.external = defaultdict(set)
@classmethod
def make(cls, scheme, checksum, name):
cache = cls()
cache.add(scheme, checksum, name)
return cache
def __getitem__(self, key):
return self._items[key]
def add(self, scheme, checksum, name):
"""Add a mapped name for the specified checksum."""
self._items[scheme][checksum].names.add(name)
def add_child_cache(self, checksum, cache, suffix=""):
"""Add/update child cache for the specified checksum."""
for scheme, src in cache._items.items():
dst = self._items[scheme][checksum].children
for child_checksum, item in src.items():
dst[child_checksum].update(item, suffix=suffix)
for repo_pair, files in cache.external.items():
self.external[repo_pair].update(files)
def add_external(self, url, rev, path):
self.external[url, rev].add(path)
def update(self, cache, suffix=""):
for scheme, src in cache._items.items():
dst = self._items[scheme]
for checksum, item in src.items():
dst[checksum].update(item, suffix=suffix)
for repo_pair, files in cache.external.items():
self.external[repo_pair].update(files)
def scheme_keys(self, scheme):
"""Iterate over a flat list of all keys for the specified scheme,
including children.
"""
for key, item in self._items[scheme].items():
yield key
yield from item.child_keys()
def scheme_names(self, scheme):
"""Iterate over a flat list of checksum, names items for the specified
scheme, including children.
"""
for key, item in self._items[scheme].items():
yield key, item.names
yield from item.child_names()
def dir_keys(self, scheme):
return (
key for key, item in self._items[scheme].items() if item.children
)
def child_keys(self, scheme, checksum):
return self._items[scheme][checksum].child_keys()
-694
View File
@@ -1,694 +0,0 @@
import json
import logging
from copy import copy
from operator import itemgetter
from shortuuid import uuid
import dvc.prompt as prompt
from dvc.exceptions import (
CheckoutError,
ConfirmRemoveError,
DvcException,
MergeError,
)
from dvc.hash_info import HashInfo
from dvc.path_info import WindowsPathInfo
from dvc.progress import Tqdm
from dvc.remote.slow_link_detection import slow_link_guard
from ..tree.base import RemoteActionNotImplemented
logger = logging.getLogger(__name__)
STATUS_OK = 1
STATUS_MISSING = 2
STATUS_NEW = 3
STATUS_DELETED = 4
STATUS_MAP = {
# (local_exists, remote_exists)
(True, True): STATUS_OK,
(False, False): STATUS_MISSING,
(True, False): STATUS_NEW,
(False, True): STATUS_DELETED,
}
class DirCacheError(DvcException):
def __init__(self, hash_):
super().__init__(
f"Failed to load dir cache for hash value: '{hash_}'."
)
class CloudCache:
"""Cloud cache class."""
DEFAULT_CACHE_TYPES = ["copy"]
CACHE_MODE = None
def __init__(self, tree):
self.tree = tree
self.repo = tree.repo
self.cache_types = tree.config.get("type") or copy(
self.DEFAULT_CACHE_TYPES
)
self.cache_type_confirmed = False
self._dir_info = {}
def get_dir_cache(self, hash_info):
assert hash_info
dir_info = self._dir_info.get(hash_info.value)
if dir_info:
return dir_info
try:
dir_info = self.load_dir_cache(hash_info)
except DirCacheError:
dir_info = []
self._dir_info[hash_info.value] = dir_info
return dir_info
def load_dir_cache(self, hash_info):
path_info = self.tree.hash_to_path_info(hash_info.value)
try:
with self.tree.open(path_info, "r") as fobj:
d = json.load(fobj)
except (ValueError, FileNotFoundError) as exc:
raise DirCacheError(hash_info) from exc
if not isinstance(d, list):
logger.error(
"dir cache file format error '%s' [skipping the file]",
path_info,
)
return []
if self.tree.PATH_CLS == WindowsPathInfo:
# only need to convert it for Windows
for info in d:
# NOTE: here is a BUG, see comment to .as_posix() below
relpath = info[self.tree.PARAM_RELPATH]
info[self.tree.PARAM_RELPATH] = relpath.replace(
"/", self.tree.PATH_CLS.sep
)
return d
def changed(self, path_info, hash_info):
"""Checks if data has changed.
A file is considered changed if:
- It doesn't exist on the working directory (was unlinked)
- Hash value is not computed (saving a new file)
- The hash value stored is different from the given one
- There's no file in the cache
Args:
path_info: dict with path information.
hash: expected hash value for this data.
Returns:
bool: True if data has changed, False otherwise.
"""
logger.debug(
"checking if '%s'('%s') has changed.", path_info, hash_info
)
if not self.tree.exists(path_info):
logger.debug("'%s' doesn't exist.", path_info)
return True
if not hash_info:
logger.debug("hash value for '%s' is missing.", path_info)
return True
if self.changed_cache(hash_info):
logger.debug(
"cache for '%s'('%s') has changed.", path_info, hash_info
)
return True
actual = self.tree.get_hash(path_info)
if hash_info != actual:
logger.debug(
"hash value '%s' for '%s' has changed (actual '%s').",
hash_info,
actual,
path_info,
)
return True
logger.debug("'%s' hasn't changed.", path_info)
return False
def link(self, from_info, to_info):
self._link(from_info, to_info, self.cache_types)
def _link(self, from_info, to_info, link_types):
assert self.tree.isfile(from_info)
self.tree.makedirs(to_info.parent)
self._try_links(from_info, to_info, link_types)
def _verify_link(self, path_info, link_type):
if self.cache_type_confirmed:
return
is_link = getattr(self.tree, f"is_{link_type}", None)
if is_link and not is_link(path_info):
self.tree.remove(path_info)
raise DvcException(f"failed to verify {link_type}")
self.cache_type_confirmed = True
@slow_link_guard
def _try_links(self, from_info, to_info, link_types):
while link_types:
link_method = getattr(self.tree, link_types[0])
try:
self._do_link(from_info, to_info, link_method)
self._verify_link(to_info, link_types[0])
return
except DvcException as exc:
logger.debug(
"Cache type '%s' is not supported: %s", link_types[0], exc
)
del link_types[0]
raise DvcException("no possible cache types left to try out.")
def _do_link(self, from_info, to_info, link_method):
if self.tree.exists(to_info):
raise DvcException(f"Link '{to_info}' already exists!")
link_method(from_info, to_info)
logger.debug(
"Created '%s': %s -> %s", self.cache_types[0], from_info, to_info,
)
def _save_file(self, path_info, tree, hash_info, save_link=True, **kwargs):
assert hash_info
cache_info = self.tree.hash_to_path_info(hash_info.value)
if tree == self.tree:
if self.changed_cache(hash_info):
self.tree.move(path_info, cache_info, mode=self.CACHE_MODE)
self.link(cache_info, path_info)
elif self.tree.iscopy(path_info) and self._cache_is_copy(
path_info
):
# Default relink procedure involves unneeded copy
self.tree.unprotect(path_info)
else:
self.tree.remove(path_info)
self.link(cache_info, path_info)
if save_link:
self.tree.state.save_link(path_info)
# we need to update path and cache, since in case of reflink,
# or copy cache type moving original file results in updates on
# next executed command, which causes md5 recalculation
self.tree.state.save(path_info, hash_info.value)
else:
if self.changed_cache(hash_info):
with tree.open(path_info, mode="rb") as fobj:
# if tree has fetch enabled, DVC out will be fetched on
# open and we do not need to read/copy any data
if not (
tree.isdvc(path_info, strict=False) and tree.fetch
):
self.tree.copy_fobj(fobj, cache_info)
callback = kwargs.get("download_callback")
if callback:
callback(1)
self.tree.state.save(cache_info, hash_info.value)
def _cache_is_copy(self, path_info):
"""Checks whether cache uses copies."""
if self.cache_type_confirmed:
return self.cache_types[0] == "copy"
if set(self.cache_types) <= {"copy"}:
return True
workspace_file = path_info.with_name("." + uuid())
test_cache_file = self.tree.path_info / ".cache_type_test_file"
if not self.tree.exists(test_cache_file):
with self.tree.open(test_cache_file, "wb") as fobj:
fobj.write(bytes(1))
try:
self.link(test_cache_file, workspace_file)
finally:
self.tree.remove(workspace_file)
self.tree.remove(test_cache_file)
self.cache_type_confirmed = True
return self.cache_types[0] == "copy"
def _get_dir_info_hash(self, dir_info):
import tempfile
from dvc.path_info import PathInfo
from dvc.utils import tmp_fname
# Sorting the list by path to ensure reproducibility
dir_info = sorted(dir_info, key=itemgetter(self.tree.PARAM_RELPATH))
tmp = tempfile.NamedTemporaryFile(delete=False).name
with open(tmp, "w+") as fobj:
json.dump(dir_info, fobj, sort_keys=True)
from_info = PathInfo(tmp)
to_info = self.tree.path_info / tmp_fname("")
self.tree.upload(from_info, to_info, no_progress_bar=True)
hash_info = self.tree.get_file_hash(to_info)
hash_info.value += self.tree.CHECKSUM_DIR_SUFFIX
hash_info.dir_info = dir_info
return hash_info, to_info
def save_dir_info(self, dir_info, hash_info=None):
if (
hash_info
and hash_info.name == self.tree.PARAM_CHECKSUM
and not self.changed_cache_file(hash_info)
):
return hash_info
hi, tmp_info = self._get_dir_info_hash(dir_info)
new_info = self.tree.hash_to_path_info(hi.value)
if self.changed_cache_file(hi):
self.tree.makedirs(new_info.parent)
self.tree.move(tmp_info, new_info, mode=self.CACHE_MODE)
self.tree.state.save(new_info, hi.value)
return hi
def _save_dir(self, path_info, tree, hash_info, save_link=True, **kwargs):
if not hash_info.dir_info:
hash_info.dir_info = tree.cache.get_dir_cache(hash_info)
hi = self.save_dir_info(hash_info.dir_info, hash_info)
for entry in Tqdm(
hi.dir_info, desc="Saving " + path_info.name, unit="file"
):
entry_info = path_info / entry[self.tree.PARAM_RELPATH]
entry_hash = HashInfo(
self.tree.PARAM_CHECKSUM, entry[self.tree.PARAM_CHECKSUM]
)
self._save_file(
entry_info, tree, entry_hash, save_link=False, **kwargs
)
if save_link:
self.tree.state.save_link(path_info)
if self.tree.exists(path_info):
self.tree.state.save(path_info, hi.value)
cache_info = self.tree.hash_to_path_info(hi.value)
self.tree.state.save(cache_info, hi.value)
def save(self, path_info, tree, hash_info, save_link=True, **kwargs):
if path_info.scheme != self.tree.scheme:
raise RemoteActionNotImplemented(
f"save {path_info.scheme} -> {self.tree.scheme}",
self.tree.scheme,
)
self._save(path_info, tree, hash_info, save_link, **kwargs)
def _save(self, path_info, tree, hash_info, save_link=True, **kwargs):
to_info = self.tree.hash_to_path_info(hash_info.value)
logger.debug("Saving '%s' to '%s'.", path_info, to_info)
if tree.isdir(path_info):
self._save_dir(path_info, tree, hash_info, save_link, **kwargs)
else:
self._save_file(path_info, tree, hash_info, save_link, **kwargs)
# Override to return path as a string instead of PathInfo for clouds
# which support string paths (see local)
def hash_to_path(self, hash_):
return self.tree.hash_to_path_info(hash_)
def changed_cache_file(self, hash_info):
"""Compare the given hash with the (corresponding) actual one.
- Use `State` as a cache for computed hashes
+ The entries are invalidated by taking into account the following:
* mtime
* inode
* size
* hash
- Remove the file from cache if it doesn't match the actual hash
"""
# Prefer string path over PathInfo when possible due to performance
cache_info = self.hash_to_path(hash_info.value)
if self.tree.is_protected(cache_info):
logger.debug(
"Assuming '%s' is unchanged since it is read-only", cache_info
)
return False
actual = self.tree.get_hash(cache_info)
logger.debug(
"cache '%s' expected '%s' actual '%s'",
cache_info,
hash_info,
actual,
)
if not hash_info or not actual:
return True
if actual.value.split(".")[0] == hash_info.value.split(".")[0]:
# making cache file read-only so we don't need to check it
# next time
self.tree.protect(cache_info)
return False
if self.tree.exists(cache_info):
logger.warning("corrupted cache file '%s'.", cache_info)
self.tree.remove(cache_info)
return True
def _changed_dir_cache(self, hash_info, path_info=None, filter_info=None):
if self.changed_cache_file(hash_info):
return True
for entry in self.get_dir_cache(hash_info):
entry_hash = HashInfo(
self.tree.PARAM_CHECKSUM, entry[self.tree.PARAM_CHECKSUM]
)
if path_info and filter_info:
entry_info = path_info / entry[self.tree.PARAM_RELPATH]
if not entry_info.isin_or_eq(filter_info):
continue
if self.changed_cache_file(entry_hash):
return True
return False
def changed_cache(self, hash_info, path_info=None, filter_info=None):
if hash_info.isdir:
return self._changed_dir_cache(
hash_info, path_info=path_info, filter_info=filter_info
)
return self.changed_cache_file(hash_info)
def already_cached(self, path_info):
current = self.tree.get_hash(path_info)
if not current:
return False
return not self.changed_cache(current)
def safe_remove(self, path_info, force=False):
if not self.tree.exists(path_info):
return
if not force and not self.already_cached(path_info):
msg = (
"file '{}' is going to be removed."
" Are you sure you want to proceed?".format(str(path_info))
)
if not prompt.confirm(msg):
raise ConfirmRemoveError(str(path_info))
self.tree.remove(path_info)
def _checkout_file(
self, path_info, hash_info, force, progress_callback=None, relink=False
):
"""The file is changed we need to checkout a new copy"""
added, modified = True, False
cache_info = self.tree.hash_to_path_info(hash_info.value)
if self.tree.exists(path_info):
logger.debug("data '%s' will be replaced.", path_info)
self.safe_remove(path_info, force=force)
added, modified = False, True
self.link(cache_info, path_info)
self.tree.state.save_link(path_info)
self.tree.state.save(path_info, hash_info.value)
if progress_callback:
progress_callback(str(path_info))
return added, modified and not relink
def _checkout_dir(
self,
path_info,
hash_info,
force,
progress_callback=None,
relink=False,
filter_info=None,
):
added, modified = False, False
# Create dir separately so that dir is created
# even if there are no files in it
if not self.tree.exists(path_info):
added = True
self.tree.makedirs(path_info)
dir_info = self.get_dir_cache(hash_info)
logger.debug("Linking directory '%s'.", path_info)
for entry in dir_info:
relative_path = entry[self.tree.PARAM_RELPATH]
entry_hash = entry[self.tree.PARAM_CHECKSUM]
entry_cache_info = self.tree.hash_to_path_info(entry_hash)
entry_info = path_info / relative_path
if filter_info and not entry_info.isin_or_eq(filter_info):
continue
entry_hash_info = HashInfo(self.tree.PARAM_CHECKSUM, entry_hash)
if relink or self.changed(entry_info, entry_hash_info):
modified = True
self.safe_remove(entry_info, force=force)
self.link(entry_cache_info, entry_info)
self.tree.state.save(entry_info, entry_hash)
if progress_callback:
progress_callback(str(entry_info))
modified = (
self._remove_redundant_files(path_info, dir_info, force)
or modified
)
self.tree.state.save_link(path_info)
self.tree.state.save(path_info, hash_info.value)
# relink is not modified, assume it as nochange
return added, not added and modified and not relink
def _remove_redundant_files(self, path_info, dir_info, force):
existing_files = set(self.tree.walk_files(path_info))
needed_files = {
path_info / entry[self.tree.PARAM_RELPATH] for entry in dir_info
}
redundant_files = existing_files - needed_files
for path in redundant_files:
self.safe_remove(path, force)
return bool(redundant_files)
def checkout(
self,
path_info,
hash_info,
force=False,
progress_callback=None,
relink=False,
filter_info=None,
):
if path_info.scheme not in ["local", self.tree.scheme]:
raise NotImplementedError
failed = None
skip = False
if not hash_info:
logger.warning(
"No file hash info found for '%s'. " "It won't be created.",
path_info,
)
self.safe_remove(path_info, force=force)
failed = path_info
elif not relink and not self.changed(path_info, hash_info):
logger.debug("Data '%s' didn't change.", path_info)
skip = True
elif self.changed_cache(
hash_info, path_info=path_info, filter_info=filter_info
):
logger.warning(
"Cache '%s' not found. File '%s' won't be created.",
hash_info,
path_info,
)
self.safe_remove(path_info, force=force)
failed = path_info
if failed or skip:
if progress_callback:
progress_callback(
str(path_info),
self.get_files_number(
self.tree.path_info, hash_info, filter_info
),
)
if failed:
raise CheckoutError([failed])
return
logger.debug(
"Checking out '%s' with cache '%s'.", path_info, hash_info
)
return self._checkout(
path_info,
hash_info,
force,
progress_callback,
relink,
filter_info,
)
def _checkout(
self,
path_info,
hash_info,
force=False,
progress_callback=None,
relink=False,
filter_info=None,
):
if not hash_info.isdir:
return self._checkout_file(
path_info, hash_info, force, progress_callback, relink
)
return self._checkout_dir(
path_info, hash_info, force, progress_callback, relink, filter_info
)
def get_files_number(self, path_info, hash_info, filter_info):
from funcy.py3 import ilen
if not hash_info:
return 0
if not hash_info.isdir:
return 1
if not filter_info:
return len(self.get_dir_cache(hash_info))
return ilen(
filter_info.isin_or_eq(path_info / entry[self.tree.PARAM_CHECKSUM])
for entry in self.get_dir_cache(hash_info)
)
def _to_dict(self, dir_info):
return {
entry[self.tree.PARAM_RELPATH]: entry[self.tree.PARAM_CHECKSUM]
for entry in dir_info
}
def _from_dict(self, dir_dict):
return [
{
self.tree.PARAM_RELPATH: relpath,
self.tree.PARAM_CHECKSUM: checksum,
}
for relpath, checksum in dir_dict.items()
]
@staticmethod
def _diff(ancestor, other, allow_removed=False):
from dictdiffer import diff
allowed = ["add"]
if allow_removed:
allowed.append("remove")
result = list(diff(ancestor, other))
for typ, _, _ in result:
if typ not in allowed:
raise MergeError(
"unable to auto-merge directories with diff that contains "
f"'{typ}'ed files"
)
return result
def _merge_dirs(self, ancestor_info, our_info, their_info):
from dictdiffer import patch
ancestor = self._to_dict(ancestor_info)
our = self._to_dict(our_info)
their = self._to_dict(their_info)
our_diff = self._diff(ancestor, our)
if not our_diff:
return self._from_dict(their)
their_diff = self._diff(ancestor, their)
if not their_diff:
return self._from_dict(our)
# make sure there are no conflicting files
self._diff(our, their, allow_removed=True)
merged = patch(our_diff + their_diff, ancestor, in_place=True)
# Sorting the list by path to ensure reproducibility
return sorted(
self._from_dict(merged), key=itemgetter(self.tree.PARAM_RELPATH)
)
def merge(self, ancestor_info, our_info, their_info):
assert our_info
assert their_info
if ancestor_info:
ancestor = self.get_dir_cache(ancestor_info)
else:
ancestor = []
our = self.get_dir_cache(our_info)
their = self.get_dir_cache(their_info)
merged = self._merge_dirs(ancestor, our, their)
return self.save_dir_info(merged)
def get_hash(self, tree, path_info):
hash_info = tree.get_hash(path_info)
if not hash_info.isdir:
assert hash_info.name == self.tree.PARAM_CHECKSUM
return hash_info
return self.save_dir_info(hash_info.dir_info, hash_info)
-472
View File
@@ -1,472 +0,0 @@
import errno
import logging
import os
from concurrent.futures import ThreadPoolExecutor, as_completed
from functools import partial, wraps
from funcy import cached_property, concat
from dvc.exceptions import DownloadError, UploadError
from dvc.hash_info import HashInfo
from dvc.path_info import PathInfo
from dvc.progress import Tqdm
from ..remote.base import index_locked
from ..tree.local import LocalTree
from .base import (
STATUS_DELETED,
STATUS_MAP,
STATUS_MISSING,
STATUS_NEW,
CloudCache,
)
logger = logging.getLogger(__name__)
def _log_exceptions(func, operation):
@wraps(func)
def wrapper(from_info, to_info, *args, **kwargs):
try:
func(from_info, to_info, *args, **kwargs)
return 0
except Exception as exc: # pylint: disable=broad-except
# NOTE: this means we ran out of file descriptors and there is no
# reason to try to proceed, as we will hit this error anyways.
# pylint: disable=no-member
if isinstance(exc, OSError) and exc.errno == errno.EMFILE:
raise
logger.exception(
"failed to %s '%s' to '%s'", operation, from_info, to_info
)
return 1
return wrapper
class LocalCache(CloudCache):
DEFAULT_CACHE_TYPES = ["reflink", "copy"]
CACHE_MODE = LocalTree.CACHE_MODE
def __init__(self, tree):
super().__init__(tree)
self.cache_dir = tree.config.get("url")
@property
def cache_dir(self):
return self.tree.path_info.fspath if self.tree.path_info else None
@cache_dir.setter
def cache_dir(self, value):
self.tree.path_info = PathInfo(value) if value else None
@classmethod
def supported(cls, config): # pylint: disable=unused-argument
return True
@cached_property
def cache_path(self):
return os.path.abspath(self.cache_dir)
def hash_to_path(self, hash_):
# NOTE: `self.cache_path` is already normalized so we can simply use
# `os.sep` instead of `os.path.join`. This results in this helper
# being ~5.5 times faster.
return f"{self.cache_path}{os.sep}{hash_[0:2]}{os.sep}{hash_[2:]}"
def hashes_exist(
self, hashes, jobs=None, name=None
): # pylint: disable=unused-argument
return [
hash_
for hash_ in Tqdm(
hashes,
unit="file",
desc="Querying "
+ ("cache in " + name if name else "local cache"),
)
if not self.changed_cache_file(
HashInfo(self.tree.PARAM_CHECKSUM, hash_)
)
]
def already_cached(self, path_info):
assert path_info.scheme in ["", "local"]
return super().already_cached(path_info)
def _verify_link(self, path_info, link_type):
if link_type == "hardlink" and self.tree.getsize(path_info) == 0:
return
super()._verify_link(path_info, link_type)
@index_locked
def status(
self,
named_cache,
remote,
jobs=None,
show_checksums=False,
download=False,
log_missing=True,
):
# Return flattened dict containing all status info
dir_status, file_status, _ = self._status(
named_cache,
remote,
jobs=jobs,
show_checksums=show_checksums,
download=download,
log_missing=log_missing,
)
return dict(dir_status, **file_status)
def _status(
self,
named_cache,
remote,
jobs=None,
show_checksums=False,
download=False,
log_missing=True,
):
"""Return a tuple of (dir_status_info, file_status_info, dir_contents).
dir_status_info contains status for .dir files, file_status_info
contains status for all other files, and dir_contents is a dict of
{dir_hash: set(file_hash, ...)} which can be used to map
a .dir file to its file contents.
"""
logger.debug(
f"Preparing to collect status from {remote.tree.path_info}"
)
md5s = set(named_cache.scheme_keys(self.tree.scheme))
logger.debug("Collecting information from local cache...")
local_exists = frozenset(
self.hashes_exist(md5s, jobs=jobs, name=self.cache_dir)
)
# This is a performance optimization. We can safely assume that,
# if the resources that we want to fetch are already cached,
# there's no need to check the remote storage for the existence of
# those files.
if download and local_exists == md5s:
remote_exists = local_exists
else:
logger.debug("Collecting information from remote cache...")
remote_exists = set()
dir_md5s = set(named_cache.dir_keys(self.tree.scheme))
if dir_md5s:
remote_exists.update(
self._indexed_dir_hashes(named_cache, remote, dir_md5s)
)
md5s.difference_update(remote_exists)
if md5s:
remote_exists.update(
remote.hashes_exist(
md5s, jobs=jobs, name=str(remote.tree.path_info)
)
)
return self._make_status(
named_cache,
show_checksums,
local_exists,
remote_exists,
log_missing,
)
def _make_status(
self,
named_cache,
show_checksums,
local_exists,
remote_exists,
log_missing,
):
def make_names(hash_, names):
return {"name": hash_ if show_checksums else " ".join(names)}
dir_status = {}
file_status = {}
dir_contents = {}
for hash_, item in named_cache[self.tree.scheme].items():
if item.children:
dir_status[hash_] = make_names(hash_, item.names)
dir_contents[hash_] = set()
for child_hash, child in item.children.items():
file_status[child_hash] = make_names(
child_hash, child.names
)
dir_contents[hash_].add(child_hash)
else:
file_status[hash_] = make_names(hash_, item.names)
self._fill_statuses(dir_status, local_exists, remote_exists)
self._fill_statuses(file_status, local_exists, remote_exists)
if log_missing:
self._log_missing_caches(dict(dir_status, **file_status))
return dir_status, file_status, dir_contents
def _indexed_dir_hashes(self, named_cache, remote, dir_md5s):
# Validate our index by verifying all indexed .dir hashes
# still exist on the remote
indexed_dirs = set(remote.index.dir_hashes())
indexed_dir_exists = set()
if indexed_dirs:
indexed_dir_exists.update(
remote.tree.list_hashes_exists(indexed_dirs)
)
missing_dirs = indexed_dirs.difference(indexed_dir_exists)
if missing_dirs:
logger.debug(
"Remote cache missing indexed .dir hashes '{}', "
"clearing remote index".format(", ".join(missing_dirs))
)
remote.index.clear()
# Check if non-indexed (new) dir hashes exist on remote
dir_exists = dir_md5s.intersection(indexed_dir_exists)
dir_exists.update(
remote.tree.list_hashes_exists(dir_md5s - dir_exists)
)
# If .dir hash exists on the remote, assume directory contents
# still exists on the remote
for dir_hash in dir_exists:
file_hashes = list(
named_cache.child_keys(self.tree.scheme, dir_hash)
)
if dir_hash not in remote.index:
logger.debug(
"Indexing new .dir '{}' with '{}' nested files".format(
dir_hash, len(file_hashes)
)
)
remote.index.update([dir_hash], file_hashes)
yield dir_hash
yield from file_hashes
@staticmethod
def _fill_statuses(hash_info_dir, local_exists, remote_exists):
# Using sets because they are way faster for lookups
local = set(local_exists)
remote = set(remote_exists)
for md5, info in hash_info_dir.items():
status = STATUS_MAP[(md5 in local, md5 in remote)]
info["status"] = status
def _get_plans(self, download, remote, status_info, status):
cache = []
path_infos = []
names = []
hashes = []
missing = []
for md5, info in Tqdm(
status_info.items(), desc="Analysing status", unit="file"
):
if info["status"] == status:
cache.append(self.tree.hash_to_path_info(md5))
path_infos.append(remote.tree.hash_to_path_info(md5))
names.append(info["name"])
hashes.append(md5)
elif info["status"] == STATUS_MISSING:
missing.append(md5)
if download:
to_infos = cache
from_infos = path_infos
else:
to_infos = path_infos
from_infos = cache
return (from_infos, to_infos, names, hashes), missing
def _process(
self,
named_cache,
remote,
jobs=None,
show_checksums=False,
download=False,
):
logger.debug(
"Preparing to {} '{}'".format(
"download data from" if download else "upload data to",
remote.tree.path_info,
)
)
if download:
func = partial(
_log_exceptions(remote.tree.download, "download"),
dir_mode=self.tree.dir_mode,
file_mode=self.tree.file_mode,
)
status = STATUS_DELETED
desc = "Downloading"
else:
func = _log_exceptions(remote.tree.upload, "upload")
status = STATUS_NEW
desc = "Uploading"
if jobs is None:
jobs = remote.tree.JOBS
dir_status, file_status, dir_contents = self._status(
named_cache,
remote,
jobs=jobs,
show_checksums=show_checksums,
download=download,
)
dir_plans, _ = self._get_plans(download, remote, dir_status, status)
file_plans, missing_files = self._get_plans(
download, remote, file_status, status
)
total = len(dir_plans[0]) + len(file_plans[0])
if total == 0:
return 0
with Tqdm(total=total, unit="file", desc=desc) as pbar:
func = pbar.wrap_fn(func)
with ThreadPoolExecutor(max_workers=jobs) as executor:
if download:
from_infos, to_infos, names, _ = (
d + f for d, f in zip(dir_plans, file_plans)
)
fails = sum(
executor.map(func, from_infos, to_infos, names)
)
else:
# for uploads, push files first, and any .dir files last
file_futures = {}
for from_info, to_info, name, hash_ in zip(*file_plans):
file_futures[hash_] = executor.submit(
func, from_info, to_info, name
)
dir_futures = {}
for from_info, to_info, name, dir_hash in zip(*dir_plans):
# if for some reason a file contained in this dir is
# missing both locally and in the remote, we want to
# push whatever file content we have, but should not
# push .dir file
for file_hash in missing_files:
if file_hash in dir_contents[dir_hash]:
logger.debug(
"directory '%s' contains missing files,"
"skipping .dir file upload",
name,
)
break
else:
wait_futures = {
future
for file_hash, future in file_futures.items()
if file_hash in dir_contents[dir_hash]
}
dir_futures[dir_hash] = executor.submit(
self._dir_upload,
func,
wait_futures,
from_info,
to_info,
name,
)
fails = sum(
future.result()
for future in concat(
file_futures.values(), dir_futures.values()
)
)
if fails:
if download:
remote.index.clear()
raise DownloadError(fails)
raise UploadError(fails)
if not download:
# index successfully pushed dirs
for dir_hash, future in dir_futures.items():
if future.result() == 0:
file_hashes = dir_contents[dir_hash]
logger.debug(
"Indexing pushed dir '{}' with "
"'{}' nested files".format(dir_hash, len(file_hashes))
)
remote.index.update([dir_hash], file_hashes)
return len(dir_plans[0]) + len(file_plans[0])
@staticmethod
def _dir_upload(func, futures, from_info, to_info, name):
for future in as_completed(futures):
if future.result():
# do not upload this .dir file if any file in this
# directory failed to upload
logger.debug(
"failed to upload full contents of '{}', "
"aborting .dir file upload".format(name)
)
logger.error(f"failed to upload '{from_info}' to '{to_info}'")
return 1
return func(from_info, to_info, name)
@index_locked
def push(self, named_cache, remote, jobs=None, show_checksums=False):
return self._process(
named_cache,
remote,
jobs=jobs,
show_checksums=show_checksums,
download=False,
)
@index_locked
def pull(self, named_cache, remote, jobs=None, show_checksums=False):
ret = self._process(
named_cache,
remote,
jobs=jobs,
show_checksums=show_checksums,
download=True,
)
if not remote.tree.verify:
for checksum in named_cache.scheme_keys("local"):
cache_file = self.tree.hash_to_path_info(checksum)
if self.tree.exists(cache_file):
# We can safely save here, as existing corrupted files will
# be removed upon status, while files corrupted during
# download will not be moved from tmp_file
# (see `BaseTree.download()`)
self.tree.state.save(cache_file, checksum)
return ret
@staticmethod
def _log_missing_caches(hash_info_dict):
missing_caches = [
(md5, info)
for md5, info in hash_info_dict.items()
if info["status"] == STATUS_MISSING
]
if missing_caches:
missing_desc = "\n".join(
"name: {}, md5: {}".format(info["name"], md5)
for md5, info in missing_caches
)
msg = (
"Some of the cache files do not exist neither locally "
"nor on remote. Missing cache files:\n{}".format(missing_desc)
)
logger.warning(msg)
+351
View File
@@ -0,0 +1,351 @@
import logging
from shortuuid import uuid
from dvc import prompt
from dvc.exceptions import (
CacheLinkError,
CheckoutError,
ConfirmRemoveError,
DvcException,
)
from dvc.ignore import DvcIgnoreFilter
from dvc.objects import check, load
from dvc.objects.db.slow_link_detection import ( # type: ignore[attr-defined]
slow_link_guard,
)
from dvc.objects.errors import ObjectFormatError
from dvc.objects.stage import stage
from dvc.types import Optional
logger = logging.getLogger(__name__)
def _changed(path_info, fs, obj, cache, state=None):
logger.trace("checking if '%s'('%s') has changed.", path_info, obj)
try:
check(cache, obj)
except (FileNotFoundError, ObjectFormatError):
logger.debug(
"cache for '%s'('%s') has changed.", path_info, obj.hash_info
)
return True
actual = state.get(path_info, fs) if state else None
if actual is None:
try:
actual = stage(
cache, path_info, fs, obj.hash_info.name, dry_run=True
)[1].hash_info
except FileNotFoundError:
logger.debug("'%s' doesn't exist.", path_info)
return True
if obj.hash_info != actual:
logger.debug(
"hash value '%s' for '%s' has changed (actual '%s').",
obj.hash_info,
actual,
path_info,
)
return True
logger.trace("'%s' hasn't changed.", path_info)
return False
def _remove(path_info, fs, cache, force=False):
if not fs.exists(path_info):
return
if force:
fs.remove(path_info)
return
current = stage(cache, path_info, fs, fs.PARAM_CHECKSUM, dry_run=True)[
1
].hash_info
try:
obj = load(cache, current)
check(cache, obj)
except (FileNotFoundError, ObjectFormatError):
msg = (
f"file/directory '{path_info}' is going to be removed. "
"Are you sure you want to proceed?"
)
if not prompt.confirm(msg):
raise ConfirmRemoveError(str(path_info))
fs.remove(path_info)
def _verify_link(cache, path_info, link_type):
if link_type == "hardlink" and cache.fs.getsize(path_info) == 0:
return
if cache.cache_type_confirmed:
return
is_link = getattr(cache.fs, f"is_{link_type}", None)
if is_link and not is_link(path_info):
cache.fs.remove(path_info)
raise DvcException(f"failed to verify {link_type}")
cache.cache_type_confirmed = True
def _do_link(cache, from_info, to_info, link_method):
if cache.fs.exists(to_info):
raise DvcException(f"Link '{to_info}' already exists!")
link_method(from_info, to_info)
logger.debug(
"Created '%s': %s -> %s", cache.cache_types[0], from_info, to_info
)
@slow_link_guard
def _try_links(cache, from_info, to_info, link_types):
while link_types:
link_method = getattr(cache.fs, link_types[0])
try:
_do_link(cache, from_info, to_info, link_method)
_verify_link(cache, to_info, link_types[0])
return
except DvcException as exc:
logger.debug(
"Cache type '%s' is not supported: %s", link_types[0], exc
)
del link_types[0]
raise CacheLinkError([to_info])
def _link(cache, from_info, to_info):
cache.makedirs(to_info.parent)
try:
_try_links(cache, from_info, to_info, cache.cache_types)
except FileNotFoundError as exc:
raise CheckoutError([to_info]) from exc
def _cache_is_copy(cache, path_info):
"""Checks whether cache uses copies."""
if cache.cache_type_confirmed:
return cache.cache_types[0] == "copy"
if set(cache.cache_types) <= {"copy"}:
return True
workspace_file = path_info.with_name("." + uuid())
test_cache_file = cache.path_info / ".cache_type_test_file"
if not cache.fs.exists(test_cache_file):
cache.makedirs(test_cache_file.parent)
with cache.fs.open(test_cache_file, "wb") as fobj:
fobj.write(bytes(1))
try:
_link(cache, test_cache_file, workspace_file)
finally:
cache.fs.remove(workspace_file)
cache.fs.remove(test_cache_file)
cache.cache_type_confirmed = True
return cache.cache_types[0] == "copy"
def _checkout_file(
path_info,
fs,
obj,
cache,
force,
progress_callback=None,
relink=False,
state=None,
):
"""The file is changed we need to checkout a new copy"""
modified = False
cache_info = cache.hash_to_path_info(obj.hash_info.value)
if fs.exists(path_info):
if not relink and _changed(path_info, fs, obj, cache, state=state):
modified = True
_remove(path_info, fs, cache, force=force)
_link(cache, cache_info, path_info)
else:
if fs.iscopy(path_info) and _cache_is_copy(cache, path_info):
cache.unprotect(path_info)
else:
_remove(path_info, fs, cache, force=force)
_link(cache, cache_info, path_info)
else:
_link(cache, cache_info, path_info)
modified = True
if state:
state.save(path_info, fs, obj.hash_info)
if progress_callback:
progress_callback(str(path_info))
return modified
def _remove_redundant_files(
path_info, fs, obj, cache, force, dvcignore: Optional[DvcIgnoreFilter]
):
if dvcignore:
existing_files = set(dvcignore.walk_files(fs, path_info))
else:
existing_files = set(fs.walk_files(path_info))
needed_files = {path_info.joinpath(*key) for key, _ in obj}
redundant_files = existing_files - needed_files
for path in redundant_files:
_remove(path, fs, cache, force)
return bool(redundant_files)
def _checkout_dir(
path_info,
fs,
obj,
cache,
force,
progress_callback=None,
relink=False,
dvcignore: Optional[DvcIgnoreFilter] = None,
state=None,
):
modified = False
# Create dir separately so that dir is created
# even if there are no files in it
if not fs.exists(path_info):
modified = True
fs.makedirs(path_info)
logger.debug("Linking directory '%s'.", path_info)
failed = []
for entry_key, entry_obj in obj:
try:
entry_modified = _checkout_file(
path_info.joinpath(*entry_key),
fs,
entry_obj,
cache,
force,
progress_callback,
relink,
state=None,
)
if entry_modified:
modified = True
except CheckoutError as exc:
failed.extend(exc.target_infos)
modified = (
_remove_redundant_files(
path_info, fs, obj, cache, force, dvcignore=dvcignore
)
or modified
)
if failed:
raise CheckoutError(failed)
if state:
state.save(path_info, fs, obj.hash_info)
# relink is not modified, assume it as nochange
return modified and not relink
def _checkout(
path_info,
fs,
obj,
cache,
force=False,
progress_callback=None,
relink=False,
dvcignore: Optional[DvcIgnoreFilter] = None,
state=None,
):
if not obj.hash_info.isdir:
ret = _checkout_file(
path_info, fs, obj, cache, force, progress_callback, relink, state
)
else:
ret = _checkout_dir(
path_info,
fs,
obj,
cache,
force,
progress_callback,
relink,
dvcignore=dvcignore,
state=state,
)
if state:
state.save_link(path_info, fs)
return ret
def checkout(
path_info,
fs,
obj,
cache,
force=False,
progress_callback=None,
relink=False,
quiet=False,
dvcignore: Optional[DvcIgnoreFilter] = None,
state=None,
):
if path_info.scheme not in ["local", cache.fs.scheme]:
raise NotImplementedError
failed = None
skip = False
if not obj:
if not quiet:
logger.warning(
"No file hash info found for '%s'. It won't be created.",
path_info,
)
_remove(path_info, fs, cache, force=force)
failed = path_info
elif not relink and not _changed(path_info, fs, obj, cache, state=state):
logger.trace("Data '%s' didn't change.", path_info) # type: ignore
skip = True
if failed or skip:
if progress_callback and obj:
progress_callback(str(path_info), len(obj))
if failed:
raise CheckoutError([failed])
return
logger.debug("Checking out '%s' with cache '%s'.", path_info, obj)
return _checkout(
path_info,
fs,
obj,
cache,
force,
progress_callback,
relink,
dvcignore=dvcignore,
state=state,
)
+15 -9
View File
@@ -1,8 +1,10 @@
"""DVC command line interface"""
import argparse
import logging
import os
import sys
from ._debug import add_debugging_flags
from .command import (
add,
cache,
@@ -26,6 +28,7 @@ from .command import (
imp_url,
init,
install,
live,
ls,
metrics,
move,
@@ -36,6 +39,7 @@ from .command import (
repro,
root,
run,
stage,
unprotect,
update,
version,
@@ -79,8 +83,10 @@ COMMANDS = [
update,
git_hook,
plots,
stage,
experiments,
check_ignore,
live,
]
@@ -136,15 +142,7 @@ def get_parent_parser():
in order to prevent some weird behavior.
"""
parent_parser = argparse.ArgumentParser(add_help=False)
parent_parser.add_argument(
"--cprofile",
action="store_true",
default=False,
help=argparse.SUPPRESS,
)
parent_parser.add_argument("--cprofile-dump", help=argparse.SUPPRESS)
add_debugging_flags(parent_parser)
log_level_group = parent_parser.add_mutually_exclusive_group()
log_level_group.add_argument(
"-q", "--quiet", action="count", default=0, help="Be quiet."
@@ -191,6 +189,14 @@ def get_main_parser():
help="Show program's version.",
)
parser.add_argument(
"--cd",
default=os.path.curdir,
metavar="<path>",
help="Change to directory before executing.",
type=str,
)
# Sub commands
subparsers = parser.add_subparsers(
title="Available Commands",
+57 -3
View File
@@ -3,13 +3,17 @@ import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.exceptions import DvcException, RecursiveAddingWhileUsingFilename
logger = logging.getLogger(__name__)
class CmdAdd(CmdBase):
def run(self):
from dvc.exceptions import (
DvcException,
RecursiveAddingWhileUsingFilename,
)
try:
if len(self.args.targets) > 1 and self.args.file:
raise RecursiveAddingWhileUsingFilename()
@@ -20,6 +24,12 @@ class CmdAdd(CmdBase):
no_commit=self.args.no_commit,
fname=self.args.file,
external=self.args.external,
glob=self.args.glob,
desc=self.args.desc,
out=self.args.out,
remote=self.args.remote,
to_remote=self.args.to_remote,
jobs=self.args.jobs,
)
except DvcException:
@@ -57,12 +67,56 @@ def add_parser(subparsers, parent_parser):
default=False,
help="Allow targets that are outside of the DVC repository.",
)
parser.add_argument(
"--glob",
action="store_true",
default=False,
help="Allows targets containing shell-style wildcards.",
)
parser.add_argument(
"--file",
help="Specify name of the DVC-file this command will generate.",
help="Specify name of the .dvc file this command will generate.",
metavar="<filename>",
)
parser.add_argument(
"targets", nargs="+", help="Input files/directories to add.",
"-o",
"--out",
help="Destination path to put files to.",
metavar="<path>",
)
parser.add_argument(
"--to-remote",
action="store_true",
default=False,
help="Download it directly to the remote",
)
parser.add_argument(
"-r",
"--remote",
help="Remote storage to download to",
metavar="<name>",
)
parser.add_argument(
"-j",
"--jobs",
type=int,
help=(
"Number of jobs to run simultaneously. "
"The default value is 4 * cpu_count(). "
"For SSH remotes, the default is 4. "
),
metavar="<number>",
)
parser.add_argument(
"--desc",
type=str,
metavar="<text>",
help=(
"User description of the data (optional). "
"This doesn't affect any DVC operations."
),
)
parser.add_argument(
"targets", nargs="+", help="Input files/directories to add."
).complete = completion.FILE
parser.set_defaults(func=CmdAdd)
+18 -16
View File
@@ -1,4 +1,5 @@
import logging
import os
from abc import ABC, abstractmethod
logger = logging.getLogger(__name__)
@@ -6,11 +7,11 @@ 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
https://bugs.python.org/issue16308
https://github.com/iterative/dvc/issues/769
Args:
subparsers: subparsers to fix.
Args:
subparsers: subparsers to fix.
"""
subparsers.required = True
subparsers.dest = "cmd"
@@ -28,24 +29,20 @@ def append_doc_link(help_message, path):
class CmdBase(ABC):
UNINITIALIZED = False
def __init__(self, args):
from dvc.repo import Repo
from dvc.updater import Updater
self.repo = Repo()
os.chdir(args.cd)
self.repo = Repo(uninitialized=self.UNINITIALIZED)
self.config = self.repo.config
self.args = args
hardlink_lock = self.config["core"].get("hardlink_lock", False)
updater = Updater(self.repo.tmp_dir, hardlink_lock=hardlink_lock)
updater.check()
@property
def default_targets(self):
"""Default targets for `dvc repro`."""
from dvc.dvcfile import PIPELINE_FILE
logger.debug(f"assuming default target '{PIPELINE_FILE}'.")
return [PIPELINE_FILE]
def do_run(self):
with self.repo:
return self.run()
@abstractmethod
def run(self):
@@ -55,3 +52,8 @@ class CmdBase(ABC):
class CmdBaseNoRepo(CmdBase):
def __init__(self, args): # pylint: disable=super-init-not-called
self.args = args
os.chdir(args.cd)
def do_run(self):
return self.run()
+20 -3
View File
@@ -3,12 +3,27 @@ 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.ui import ui
class CmdCacheDir(CmdConfig):
def run(self):
with self.config.edit(level=self.args.level) as edit:
edit["cache"]["dir"] = self.args.value
if self.args.value is None and not self.args.unset:
if self.args.level:
conf = self.config.read(level=self.args.level)
else:
# Use merged config with default values
conf = self.config
self._check(conf, False, "cache", "dir")
ui.write(conf["cache"]["dir"])
return 0
with self.config.edit(level=self.args.level) as conf:
if self.args.unset:
self._check(conf, False, "cache", "dir")
del conf["cache"]["dir"]
else:
self._check(conf, False, "cache")
conf["cache"]["dir"] = self.args.value
return 0
@@ -55,6 +70,8 @@ def add_parser(subparsers, parent_parser):
"value",
help="Path to cache directory. Relative paths are resolved relative "
"to the current directory and saved to config relative to the "
"config file location.",
"config file location. If no path is provided, it returns the "
"current cache directory.",
nargs="?",
).complete = completion.DIR
cache_dir_parser.set_defaults(func=CmdCacheDir)
+7 -8
View File
@@ -1,17 +1,14 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
from dvc.ui import ui
class CmdCheckIgnore(CmdBase):
def __init__(self, args):
super().__init__(args)
self.ignore_filter = self.repo.tree.dvcignore
self.ignore_filter = self.repo.dvcignore
def _show_results(self, result):
if not result.match and not self.args.non_matching:
@@ -23,9 +20,9 @@ class CmdCheckIgnore(CmdBase):
patterns = patterns[-1:]
for pattern in patterns:
logger.info("{}\t{}".format(pattern, result.file))
ui.write(pattern, result.file, sep="\t")
else:
logger.info(result.file)
ui.write(result.file)
def _check_one_file(self, target):
result = self.ignore_filter.check_ignore(target)
@@ -55,6 +52,8 @@ class CmdCheckIgnore(CmdBase):
return ret
def _check_args(self):
from dvc.exceptions import DvcException
if not self.args.stdin and not self.args.targets:
raise DvcException("`targets` or `--stdin` needed")
@@ -121,6 +120,6 @@ def add_parser(subparsers, parent_parser):
help="Read paths from standard input instead of providing `targets`.",
)
parser.add_argument(
"targets", nargs="*", help="File or directory paths to check",
"targets", nargs="*", help="File or directory paths to check"
).complete = completion.FILE
parser.set_defaults(func=CmdCheckIgnore)
+12 -21
View File
@@ -1,44 +1,35 @@
import argparse
import logging
import operator
import colorama
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.exceptions import CheckoutError
from dvc.utils.humanize import get_summary
logger = logging.getLogger(__name__)
from dvc.ui import ui
def log_changes(stats):
colors = [
("modified", colorama.Fore.YELLOW,),
("added", colorama.Fore.GREEN),
("deleted", colorama.Fore.RED,),
]
colors = {
"modified": "yellow",
"added": "green",
"deleted": "red",
}
for state, color in colors:
for state, color in colors.items():
entries = stats.get(state)
if not entries:
continue
for entry in entries:
logger.info(
"{color}{state}{nc}{spacing}{entry}".format(
color=color,
state=state[0].upper(),
nc=colorama.Fore.RESET,
spacing="\t",
entry=entry,
)
ui.write(
f"[{color}]{state[0].upper()}", entry, styled=True, sep="\t"
)
class CmdCheckout(CmdBase):
def run(self):
from dvc.utils.humanize import get_summary
stats, exc = None, None
try:
stats = self.repo.checkout(
@@ -57,7 +48,7 @@ class CmdCheckout(CmdBase):
msg = get_summary(
sorted(stats.items(), key=operator.itemgetter(0))
)
logger.info(msg or default_message)
ui.write(msg or default_message)
else:
log_changes(stats)
+9 -5
View File
@@ -3,13 +3,14 @@ import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
class CmdCommit(CmdBase):
def run(self):
from dvc.exceptions import DvcException
if not self.args.targets:
self.args.targets = [None]
@@ -32,7 +33,10 @@ class CmdCommit(CmdBase):
def add_parser(subparsers, parent_parser):
COMMIT_HELP = "Save changed data to cache and update DVC-files."
COMMIT_HELP = (
"Record changes to files or directories tracked by DVC"
" by storing the current versions in the cache."
)
commit_parser = subparsers.add_parser(
"commit",
@@ -65,7 +69,7 @@ def add_parser(subparsers, parent_parser):
commit_parser.add_argument(
"targets",
nargs="*",
help="DVC-files to commit. Optional. "
"(Finds all DVC-files in the workspace by default.)",
).complete = completion.DVC_FILE
help="stages or .dvc files to commit. Optional. "
"(Finds all DVC files in the workspace by default.)",
).complete = completion.DVCFILES_AND_STAGE
commit_parser.set_defaults(func=CmdCommit)
+61 -7
View File
@@ -4,14 +4,12 @@ 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
DVC_FILE = {
"bash": "_dvc_compgen_DVCFiles",
"zsh": "_files -g '(*?.dvc|Dvcfile|dvc.yaml)'",
}
PREAMBLE = {
"bash": """
# $1=COMP_WORDS[1]
@@ -21,9 +19,65 @@ _dvc_compgen_DVCFiles() {
compgen -f -X '!*Dvcfile' -- $1
compgen -f -X '!*dvc.yaml' -- $1
}
""",
"zsh": "",
_dvc_compgen_stages() {
local _dvc_stages=($(dvc stage list -q --names-only))
compgen -W "${_dvc_stages[*]}" -- $1
}
_dvc_compgen_stages_and_files() {
_dvc_compgen_DVCFiles $1
_dvc_compgen_stages $1
}
_dvc_compgen_exps() {
local _dvc_exps=($(dvc exp list -q --all --names-only))
compgen -W "${_dvc_exps[*]}" -- $1
}
""",
"zsh": """
_dvc_compadd_DVCFiles() {
_files -g '(*?.dvc|Dvcfile|dvc.yaml)'
}
_dvc_compadd_stages() {
# this will also show up the description of the stages
_describe 'stages' "($(_dvc_stages_output))"
}
_dvc_stages_output() {
dvc stage list -q | awk '{
# escape possible `:` on the stage name
sub(/:/, "\\\\\\\\:", $1);
# read all of the columns except the first
# reading `out` from $2, so as not to have a leading whitespace
out=$2; for(i=3;i<=NF;i++){out=out" "$i};
# print key, ":" and then single-quote the description
# colon is a delimiter used by `_describe` to separate field/description
print $1":""\\047"out"\\047"
# single quote -> \\047
}'
}
_dvc_compadd_stages_and_files() {
_dvc_compadd_DVCFiles
_dvc_compadd_stages
}
_dvc_compadd_exps() {
_describe 'experiments' "($(dvc exp list -q -a --names-only))"
}
""",
}
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):
@@ -33,7 +87,7 @@ class CmdCompletion(CmdBaseNoRepo):
parser = get_main_parser()
shell = self.args.shell
script = shtab.complete(parser, shell=shell, preamble=PREAMBLE)
print(script)
ui.write(script, force=True)
return 0
+151 -16
View File
@@ -1,33 +1,118 @@
import argparse
import logging
import os
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.config import Config, ConfigError
from dvc.ui import ui
logger = logging.getLogger(__name__)
NAME_REGEX = r"^(?P<remote>remote\.)?(?P<section>[^\.]*)\.(?P<option>[^\.]*)$"
def _name_type(value):
import re
match = re.match(NAME_REGEX, value)
if not match:
raise argparse.ArgumentTypeError(
"name argument should look like "
"remote.name.option or "
"section.option"
)
return (
bool(match.group("remote")),
match.group("section").lower(),
match.group("option").lower(),
)
class CmdConfig(CmdBaseNoRepo):
def __init__(self, args):
from dvc.config import Config
super().__init__(args)
self.config = Config(validate=False)
def run(self):
section, opt = self.args.name.lower().strip().split(".", 1)
if self.args.show_origin:
if any((self.args.value, self.args.unset)):
logger.error(
"--show-origin can't be used together with any of these "
"options: -u/--unset, value"
)
return 1
if self.args.list:
return self._list()
if self.args.name is None:
logger.error("name argument is required")
return 1
remote, section, opt = self.args.name
if self.args.value is None and not self.args.unset:
conf = self.config.load_one(self.args.level)
self._check(conf, section, opt)
logger.info(conf[section][opt])
return 0
return self._get(remote, section, opt)
return self._set(remote, section, opt)
def _list(self):
if any((self.args.name, self.args.value, self.args.unset)):
logger.error(
"-l/--list can't be used together with any of these "
"options: -u/--unset, name, value"
)
return 1
levels = [self.args.level] if self.args.level else self.config.LEVELS
for level in levels:
conf = self.config.read(level)
prefix = self._config_file_prefix(
self.args.show_origin, self.config, level
)
configs = list(self._format_config(conf, prefix))
if configs:
ui.write("\n".join(configs))
return 0
def _get(self, remote, section, opt):
from dvc.config import ConfigError
levels = (
[self.args.level] if self.args.level else self.config.LEVELS[::-1]
)
for level in levels:
conf = self.config.read(level)
if remote:
conf = conf["remote"]
try:
self._check(conf, remote, section, opt)
except ConfigError:
if self.args.level:
raise
else:
prefix = self._config_file_prefix(
self.args.show_origin, self.config, level
)
ui.write(prefix, conf[section][opt], sep="")
break
return 0
def _set(self, remote, section, opt):
with self.config.edit(self.args.level) as conf:
if remote:
conf = conf["remote"]
if self.args.unset:
self._check(conf, section, opt)
self._check(conf, remote, section, opt)
del conf[section][opt]
else:
self._check(conf, section)
self._check(conf, remote, section)
conf[section][opt] = self.args.value
if self.args.name == "cache.type":
@@ -39,14 +124,39 @@ class CmdConfig(CmdBaseNoRepo):
return 0
def _check(self, conf, section, opt=None):
def _check(self, conf, remote, section, opt=None):
from dvc.config import ConfigError
name = "remote" if remote else "section"
if section not in conf:
msg = "section {} doesn't exist"
raise ConfigError(msg.format(self.args.name))
raise ConfigError(f"{name} '{section}' doesn't exist")
if opt and opt not in conf[section]:
msg = "option {} doesn't exist"
raise ConfigError(msg.format(self.args.name))
raise ConfigError(
f"option '{opt}' doesn't exist in {name} '{section}'"
)
@staticmethod
def _format_config(config, prefix=""):
from dvc.utils.flatten import flatten
for key, value in flatten(config).items():
yield f"{prefix}{key}={value}"
@staticmethod
def _config_file_prefix(show_origin, config, level):
from dvc.repo import Repo
if not show_origin:
return ""
level = level or "repo"
fname = config.files[level]
if level in ["local", "repo"]:
fname = os.path.relpath(fname, start=Repo.find_root())
return fname + "\t"
parent_config_parser = argparse.ArgumentParser(add_help=False)
@@ -65,14 +175,21 @@ level_group.add_argument(
const="system",
help="Use system config.",
)
level_group.add_argument(
"--project",
dest="level",
action="store_const",
const="repo",
help="Use project config (.dvc/config).",
)
level_group.add_argument(
"--local",
dest="level",
action="store_const",
const="local",
help="Use local config.",
help="Use local config (.dvc/config.local).",
)
parent_config_parser.set_defaults(level="repo")
parent_config_parser.set_defaults(level=None)
def add_parser(subparsers, parent_parser):
@@ -92,6 +209,24 @@ def add_parser(subparsers, parent_parser):
action="store_true",
help="Unset option.",
)
config_parser.add_argument("name", help="Option name.")
config_parser.add_argument(
"name",
nargs="?",
type=_name_type,
help="Option name (section.option or remote.name.option).",
)
config_parser.add_argument("value", nargs="?", help="Option value.")
config_parser.add_argument(
"-l",
"--list",
default=False,
action="store_true",
help="List all defined config values.",
)
config_parser.add_argument(
"--show-origin",
default=False,
action="store_true",
help="Show the source file containing each config value.",
)
config_parser.set_defaults(func=CmdConfig)
+1 -1
View File
@@ -67,6 +67,6 @@ def add_parser(subparsers, parent_parser):
help=DAEMON_ANALYTICS_HELP,
)
daemon_analytics_parser.add_argument(
"target", help="Analytics file.",
"target", help="Analytics file."
).complete = completion.FILE
daemon_analytics_parser.set_defaults(func=CmdDaemonAnalytics)
+76 -43
View File
@@ -1,10 +1,7 @@
import argparse
import logging
from dvc.command.base import CmdBase, append_doc_link
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
from dvc.ui import ui
def _show_ascii(G):
@@ -30,58 +27,87 @@ def _show_dot(G):
return dot_file.getvalue()
def _build(G, target=None, full=False):
def _collect_targets(repo, target, outs):
if not target:
return []
pairs = repo.stage.collect_granular(target)
if not outs:
return [stage.addressing for stage, _ in pairs]
targets = []
outs_trie = repo.index.outs_trie
for stage, info in pairs:
if not info:
targets.extend([str(out) for out in stage.outs])
continue
for out in outs_trie.itervalues(prefix=info.parts): # noqa: B301
targets.extend(str(out))
return targets
def _transform(index, outs):
import networkx as nx
from dvc.repo.graph import get_pipeline, get_pipelines
from dvc.stage import Stage
if target:
H = get_pipeline(get_pipelines(G), target)
if not full:
descendants = nx.descendants(G, target)
def _relabel(node) -> str:
return node.addressing if isinstance(node, Stage) else str(node)
G = index.outs_graph if outs else index.graph
return nx.relabel_nodes(G, _relabel, copy=True)
def _filter(G, targets, full):
import networkx as nx
if not targets:
return G
H = G.copy()
if not full:
descendants = set()
for target in targets:
descendants.update(nx.descendants(G, target))
descendants.add(target)
H.remove_nodes_from(set(G.nodes()) - descendants)
else:
H = G
H.remove_nodes_from(set(G.nodes()) - descendants)
def _relabel(stage):
return stage.addressing
undirected = H.to_undirected()
connected = set()
for target in targets:
connected.update(nx.node_connected_component(undirected, target))
return nx.relabel_nodes(H, _relabel, copy=False)
H.remove_nodes_from(set(H.nodes()) - connected)
return H
def _build(repo, target=None, full=False, outs=False):
targets = _collect_targets(repo, target, outs)
G = _transform(repo.index, outs)
return _filter(G, targets, full)
class CmdDAG(CmdBase):
def run(self):
try:
target = None
if self.args.target:
stages = self.repo.collect(self.args.target)
if len(stages) > 1:
logger.error(
f"'{self.args.target}' contains more than one stage "
"{stages}, please specify one stage"
)
return 1
target = stages[0]
G = _build(
self.repo,
target=self.args.target,
full=self.args.full,
outs=self.args.outs,
)
G = _build(self.repo.graph, target=target, full=self.args.full,)
if self.args.dot:
ui.write(_show_dot(G))
else:
from dvc.utils.pager import pager
if self.args.dot:
logger.info(_show_dot(G))
else:
from dvc.utils.pager import pager
pager(_show_ascii(G))
pager(_show_ascii(G))
return 0
except DvcException:
msg = "failed to show "
if self.args.target:
msg += f"a pipeline for '{target}'"
else:
msg += "pipelines"
logger.exception(msg)
return 1
return 0
def add_parser(subparsers, parent_parser):
@@ -108,6 +134,13 @@ def add_parser(subparsers, parent_parser):
"showing DAG consisting only of ancestors."
),
)
dag_parser.add_argument(
"-o",
"--outs",
action="store_true",
default=False,
help="Print output files instead of stages.",
)
dag_parser.add_argument(
"target",
nargs="?",
+34 -12
View File
@@ -3,25 +3,29 @@ import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.command.checkout import log_changes
from dvc.exceptions import CheckoutError, DvcException
from dvc.utils.humanize import get_summary
logger = logging.getLogger(__name__)
class CmdDataBase(CmdBase):
def log_summary(self, stats):
from dvc.ui import ui
from dvc.utils.humanize import get_summary
default_msg = "Everything is up to date."
logger.info(get_summary(stats.items()) or default_msg)
ui.write(get_summary(stats.items()) or default_msg)
class CmdDataPull(CmdDataBase):
def log_summary(self, stats):
from dvc.command.checkout import log_changes
log_changes(stats)
super().log_summary(stats)
def run(self):
from dvc.exceptions import CheckoutError, DvcException
try:
stats = self.repo.pull(
targets=self.args.targets,
@@ -34,6 +38,7 @@ class CmdDataPull(CmdDataBase):
force=self.args.force,
recursive=self.args.recursive,
run_cache=self.args.run_cache,
glob=self.args.glob,
)
self.log_summary(stats)
except (CheckoutError, DvcException) as exc:
@@ -46,6 +51,8 @@ class CmdDataPull(CmdDataBase):
class CmdDataPush(CmdDataBase):
def run(self):
from dvc.exceptions import DvcException
try:
processed_files_count = self.repo.push(
targets=self.args.targets,
@@ -57,6 +64,7 @@ class CmdDataPush(CmdDataBase):
with_deps=self.args.with_deps,
recursive=self.args.recursive,
run_cache=self.args.run_cache,
glob=self.args.glob,
)
self.log_summary({"pushed": processed_files_count})
except DvcException:
@@ -67,6 +75,8 @@ class CmdDataPush(CmdDataBase):
class CmdDataFetch(CmdDataBase):
def run(self):
from dvc.exceptions import DvcException
try:
processed_files_count = self.repo.fetch(
targets=self.args.targets,
@@ -130,7 +140,7 @@ def add_parser(subparsers, _parent_parser):
formatter_class=argparse.RawDescriptionHelpFormatter,
)
pull_parser.add_argument(
"-r", "--remote", help="Remote storage to pull from", metavar="<name>",
"-r", "--remote", help="Remote storage to pull from", metavar="<name>"
)
pull_parser.add_argument(
"-a",
@@ -147,6 +157,7 @@ def add_parser(subparsers, _parent_parser):
help="Fetch cache for all tags.",
)
pull_parser.add_argument(
"-A",
"--all-commits",
action="store_true",
default=False,
@@ -179,6 +190,12 @@ def add_parser(subparsers, _parent_parser):
default=False,
help="Fetch run history for all stages.",
)
pull_parser.add_argument(
"--glob",
action="store_true",
default=False,
help="Pull cache for targets matching shell-style wildcards.",
)
pull_parser.set_defaults(func=CmdDataPull)
# Push
@@ -192,7 +209,7 @@ def add_parser(subparsers, _parent_parser):
formatter_class=argparse.RawDescriptionHelpFormatter,
)
push_parser.add_argument(
"-r", "--remote", help="Remote storage to push to", metavar="<name>",
"-r", "--remote", help="Remote storage to push to", metavar="<name>"
)
push_parser.add_argument(
"-a",
@@ -209,6 +226,7 @@ def add_parser(subparsers, _parent_parser):
help="Push cache for all tags.",
)
push_parser.add_argument(
"-A",
"--all-commits",
action="store_true",
default=False,
@@ -234,12 +252,17 @@ def add_parser(subparsers, _parent_parser):
default=False,
help="Push run history for all stages.",
)
push_parser.add_argument(
"--glob",
action="store_true",
default=False,
help="Allows targets containing shell-style wildcards.",
)
push_parser.set_defaults(func=CmdDataPush)
# Fetch
FETCH_HELP = (
"Get tracked files or directories from remote storage"
" into the cache."
"Download files or directories from remote storage to the cache."
)
fetch_parser = subparsers.add_parser(
@@ -250,10 +273,7 @@ def add_parser(subparsers, _parent_parser):
formatter_class=argparse.RawDescriptionHelpFormatter,
)
fetch_parser.add_argument(
"-r",
"--remote",
help="Remote storage to fetch from",
metavar="<name>",
"-r", "--remote", help="Remote storage to fetch from", metavar="<name>"
)
fetch_parser.add_argument(
"-a",
@@ -270,6 +290,7 @@ def add_parser(subparsers, _parent_parser):
help="Fetch cache for all tags.",
)
fetch_parser.add_argument(
"-A",
"--all-commits",
action="store_true",
default=False,
@@ -350,6 +371,7 @@ def add_parser(subparsers, _parent_parser):
"for all tags.",
)
status_parser.add_argument(
"-A",
"--all-commits",
action="store_true",
default=False,
+5 -4
View File
@@ -1,15 +1,16 @@
import argparse
import logging
import dvc.prompt as prompt
from dvc.command.base import CmdBase, append_doc_link
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
class CmdDestroy(CmdBase):
def run(self):
from dvc.exceptions import DvcException
from dvc.ui import ui
try:
statement = (
"This will destroy all information about your pipelines,"
@@ -18,7 +19,7 @@ class CmdDestroy(CmdBase):
"Are you sure you want to continue?"
)
if not self.args.force and not prompt.confirm(statement):
if not self.args.force and not ui.confirm(statement):
raise DvcException(
"cannot destroy without a confirmation from the user."
" Use `-f` to force."
@@ -32,7 +33,7 @@ class CmdDestroy(CmdBase):
def add_parser(subparsers, parent_parser):
DESTROY_HELP = "Remove DVC-files, local DVC config and data cache."
DESTROY_HELP = "Remove DVC files, local DVC config and data cache."
destroy_parser = subparsers.add_parser(
"destroy",
+50 -41
View File
@@ -3,10 +3,9 @@ import json
import logging
import os
import colorama
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.exceptions import DvcException
from dvc.ui import ui
logger = logging.getLogger(__name__)
@@ -18,31 +17,32 @@ def _digest(checksum):
def _show_md(diff, show_hash=False, hide_missing=False):
from dvc.utils.diff import table
header = ["Status", "Hash", "Path"] if show_hash else ["Status", "Path"]
headers = ["Status", "Hash", "Path"] if show_hash else ["Status", "Path"]
rows = []
statuses = ["added", "deleted", "modified"]
statuses = ["added", "deleted", "renamed", "modified"]
if not hide_missing:
statuses.append("not in cache")
for status in statuses:
entries = diff.get(status, [])
if not entries:
continue
for entry in entries:
path = entry["path"]
if isinstance(path, dict):
path = f"{path['old']} -> {path['new']}"
if show_hash:
check_sum = _digest(entry.get("hash", ""))
rows.append([status, check_sum, path])
else:
rows.append([status, path])
return table(header, rows, True)
ui.table(rows, headers=headers, markdown=True)
class CmdDiff(CmdBase):
@staticmethod
def _format(diff, hide_missing=False):
def _show_diff(diff, hide_missing=False):
"""
Given a diff structure, generate a string of paths separated
by new lines and grouped together by their state.
@@ -69,16 +69,16 @@ class CmdDiff(CmdBase):
"""
colors = {
"added": colorama.Fore.GREEN,
"modified": colorama.Fore.YELLOW,
"deleted": colorama.Fore.RED,
"not in cache": colorama.Fore.YELLOW,
"added": "green",
"modified": "yellow",
"deleted": "red",
"renamed": "green",
"not in cache": "yellow",
}
summary = {}
groups = []
states = ["added", "deleted", "modified"]
states = ["added", "deleted", "renamed", "modified"]
if not hide_missing:
states.append("not in cache")
for state in states:
@@ -88,66 +88,66 @@ class CmdDiff(CmdBase):
if not entries:
continue
content = []
header = state.capitalize()
ui.write(f"[{colors[state]}]{header}[/]:", styled=True)
for entry in entries:
path = entry["path"]
if isinstance(path, dict):
path = f"{path['old']} -> {path['new']}"
checksum = entry.get("hash")
summary[state] += 1 if not path.endswith(os.sep) else 0
content.append(
ui.write(
"{space}{checksum}{separator}{path}".format(
space=" ",
checksum=_digest(checksum) if checksum else "",
separator=" " if checksum else "",
path=entry["path"],
path=path,
)
)
groups.append(
"{color}{header}{nc}:\n{content}".format(
color=colors[state],
header=state.capitalize(),
nc=colorama.Fore.RESET,
content="\n".join(content),
)
)
ui.write()
if not sum(summary.values()):
return None
fmt = (
"files summary: {added} added, {deleted} deleted,"
" {modified} modified"
states_summary = ", ".join(
f"{summary[state]} {state}"
for state in states
if summary[state] > 0
)
if not hide_missing:
fmt += ", {not in cache} not in cache"
groups.append(fmt.format_map(summary))
return "\n\n".join(groups)
ui.write("files summary:", states_summary)
def run(self):
from dvc.exceptions import DvcException
try:
diff = self.repo.diff(self.args.a_rev, self.args.b_rev)
diff = self.repo.diff(
self.args.a_rev, self.args.b_rev, self.args.targets
)
show_hash = self.args.show_hash
hide_missing = self.args.b_rev or self.args.hide_missing
if hide_missing:
del diff["not in cache"]
for key, entries in diff.items():
entries = sorted(entries, key=lambda entry: entry["path"])
entries = sorted(
entries,
key=lambda entry: entry["path"]["old"]
if isinstance(entry["path"], dict)
else entry["path"],
)
if not show_hash:
for entry in entries:
del entry["hash"]
diff[key] = entries
if self.args.show_json:
logger.info(json.dumps(diff))
ui.write(json.dumps(diff))
elif self.args.show_md:
logger.info(_show_md(diff, show_hash, hide_missing))
_show_md(diff, show_hash, hide_missing)
elif diff:
output = self._format(diff, hide_missing)
if output:
logger.info(output)
self._show_diff(diff, hide_missing)
except DvcException:
logger.exception("failed to get diff")
@@ -167,6 +167,15 @@ def add_parser(subparsers, parent_parser):
help=DIFF_DESCRIPTION,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
diff_parser.add_argument(
"--targets",
nargs="*",
help=(
"Specific DVC-tracked files to compare. "
"Accepts one or more file paths."
),
metavar="<paths>",
).complete = completion.FILE
diff_parser.add_argument(
"a_rev",
help="Old Git commit to compare (defaults to HEAD)",
+990 -233
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -40,7 +40,7 @@ def add_parser(subparsers, parent_parser):
formatter_class=argparse.RawDescriptionHelpFormatter,
)
freeze_parser.add_argument(
"targets", nargs="+", help="Stages or .dvc files to freeze",
"targets", nargs="+", help="Stages or .dvc files to freeze"
).complete = completion.DVC_FILE
freeze_parser.set_defaults(func=CmdFreeze)
@@ -53,6 +53,6 @@ def add_parser(subparsers, parent_parser):
formatter_class=argparse.RawDescriptionHelpFormatter,
)
unfreeze_parser.add_argument(
"targets", nargs="+", help="Stages or .dvc files to unfreeze",
"targets", nargs="+", help="Stages or .dvc files to unfreeze"
).complete = completion.DVC_FILE
unfreeze_parser.set_defaults(func=CmdUnfreeze)
+12 -2
View File
@@ -2,8 +2,8 @@ import argparse
import logging
import os
import dvc.prompt as prompt
from dvc.command.base import CmdBase, append_doc_link
from dvc.ui import ui
logger = logging.getLogger(__name__)
@@ -30,6 +30,8 @@ class CmdGC(CmdBase):
msg += " and all git branches"
elif self.args.all_tags:
msg += " and all git tags"
elif self.args.all_experiments:
msg += " and all experiments"
if self.args.repos:
msg += " of the current and the following repos:"
@@ -42,13 +44,14 @@ class CmdGC(CmdBase):
logger.warning(msg)
msg = "Are you sure you want to proceed?"
if not self.args.force and not prompt.confirm(msg):
if not self.args.force and not ui.confirm(msg):
return 1
self.repo.gc(
all_branches=self.args.all_branches,
all_tags=self.args.all_tags,
all_commits=self.args.all_commits,
all_experiments=self.args.all_experiments,
cloud=self.args.cloud,
remote=self.args.remote,
force=self.args.force,
@@ -94,11 +97,18 @@ def add_parser(subparsers, parent_parser):
help="Keep data files for all Git tags.",
)
gc_parser.add_argument(
"-A",
"--all-commits",
action="store_true",
default=False,
help="Keep data files for all Git commits.",
)
gc_parser.add_argument(
"--all-experiments",
action="store_true",
default=False,
help="Keep data files for all experiments.",
)
gc_parser.add_argument(
"-c",
"--cloud",
+15 -2
View File
@@ -12,12 +12,13 @@ logger = logging.getLogger(__name__)
class CmdGet(CmdBaseNoRepo):
def _show_url(self):
from dvc.api import get_url
from dvc.ui import ui
try:
url = get_url(
self.args.path, repo=self.args.url, rev=self.args.rev
)
logger.info(url)
ui.write(url, force=True)
except DvcException:
logger.exception("failed to show URL")
return 1
@@ -39,6 +40,7 @@ class CmdGet(CmdBaseNoRepo):
path=self.args.path,
out=self.args.out,
rev=self.args.rev,
jobs=self.args.jobs,
)
return 0
except DvcException:
@@ -63,7 +65,7 @@ def add_parser(subparsers, parent_parser):
"url", help="Location of DVC or Git repository to download from"
)
get_parser.add_argument(
"path", help="Path to a file or directory within the repository",
"path", help="Path to a file or directory within the repository"
).complete = completion.FILE
get_parser.add_argument(
"-o",
@@ -84,4 +86,15 @@ def add_parser(subparsers, parent_parser):
help="Print the storage location (URL) the target data would be "
"downloaded from, and exit.",
)
get_parser.add_argument(
"-j",
"--jobs",
type=int,
help=(
"Number of jobs to run simultaneously. "
"The default value is 4 * cpu_count(). "
"For SSH remotes, the default is 4. "
),
metavar="<number>",
)
get_parser.set_defaults(func=CmdGet)
+13 -2
View File
@@ -14,7 +14,7 @@ class CmdGetUrl(CmdBaseNoRepo):
from dvc.repo import Repo
try:
Repo.get_url(self.args.url, out=self.args.out)
Repo.get_url(self.args.url, out=self.args.out, jobs=self.args.jobs)
return 0
except DvcException:
logger.exception(f"failed to get '{self.args.url}'")
@@ -34,6 +34,17 @@ def add_parser(subparsers, parent_parser):
"url", help="See `dvc import-url -h` for full list of supported URLs."
)
get_parser.add_argument(
"out", nargs="?", help="Destination path to put data to.",
"out", nargs="?", help="Destination path to put data to."
).complete = completion.DIR
get_parser.add_argument(
"-j",
"--jobs",
type=int,
help=(
"Number of jobs to run simultaneously. "
"The default value is 4 * cpu_count(). "
"For SSH remotes, the default is 4. "
),
metavar="<number>",
)
get_parser.set_defaults(func=CmdGetUrl)
+9 -12
View File
@@ -69,14 +69,13 @@ class CmdMergeDriver(CmdHookBase):
dvc = Repo()
try:
with dvc.state:
ancestor = Dvcfile(dvc, self.args.ancestor, verify=False)
our = Dvcfile(dvc, self.args.our, verify=False)
their = Dvcfile(dvc, self.args.their, verify=False)
ancestor = Dvcfile(dvc, self.args.ancestor, verify=False)
our = Dvcfile(dvc, self.args.our, verify=False)
their = Dvcfile(dvc, self.args.their, verify=False)
our.merge(ancestor, their)
our.merge(ancestor, their)
return 0
return 0
finally:
dvc.close()
@@ -106,7 +105,7 @@ def add_parser(subparsers, parent_parser):
help=PRE_COMMIT_HELP,
)
pre_commit_parser.add_argument(
"args", nargs="*", help="Arguments passed by GIT or pre-commit tool.",
"args", nargs="*", help="Arguments passed by GIT or pre-commit tool."
)
pre_commit_parser.set_defaults(func=CmdPreCommit)
@@ -118,7 +117,7 @@ def add_parser(subparsers, parent_parser):
help=POST_CHECKOUT_HELP,
)
post_checkout_parser.add_argument(
"args", nargs="*", help="Arguments passed by GIT or pre-commit tool.",
"args", nargs="*", help="Arguments passed by GIT or pre-commit tool."
)
post_checkout_parser.set_defaults(func=CmdPostCheckout)
@@ -130,7 +129,7 @@ def add_parser(subparsers, parent_parser):
help=PRE_PUSH_HELP,
)
pre_push_parser.add_argument(
"args", nargs="*", help="Arguments passed by GIT or pre-commit tool.",
"args", nargs="*", help="Arguments passed by GIT or pre-commit tool."
)
pre_push_parser.set_defaults(func=CmdPrePush)
@@ -147,9 +146,7 @@ def add_parser(subparsers, parent_parser):
help="Ancestor's version of the conflicting file.",
)
merge_driver_parser.add_argument(
"--our",
required=True,
help="Current version of the conflicting file.",
"--our", required=True, help="Current version of the conflicting file."
)
merge_driver_parser.add_argument(
"--their",
+36 -1
View File
@@ -15,7 +15,11 @@ class CmdImport(CmdBase):
self.args.url,
self.args.path,
out=self.args.out,
fname=self.args.file,
rev=self.args.rev,
no_exec=self.args.no_exec,
desc=self.args.desc,
jobs=self.args.jobs,
)
except DvcException:
logger.exception(
@@ -44,7 +48,7 @@ def add_parser(subparsers, parent_parser):
"url", help="Location of DVC or Git repository to download from"
)
import_parser.add_argument(
"path", help="Path to a file or directory within the repository",
"path", help="Path to a file or directory within the repository"
).complete = completion.FILE
import_parser.add_argument(
"-o",
@@ -59,4 +63,35 @@ def add_parser(subparsers, parent_parser):
help="Git revision (e.g. SHA, branch, tag)",
metavar="<commit>",
)
import_parser.add_argument(
"--file",
help="Specify name of the .dvc file this command will generate.",
metavar="<filename>",
)
import_parser.add_argument(
"--no-exec",
action="store_true",
default=False,
help="Only create .dvc file without actually downloading it.",
)
import_parser.add_argument(
"--desc",
type=str,
metavar="<text>",
help=(
"User description of the data (optional). "
"This doesn't affect any DVC operations."
),
)
import_parser.add_argument(
"-j",
"--jobs",
type=int,
help=(
"Number of jobs to run simultaneously. "
"The default value is 4 * cpu_count(). "
"For SSH remotes, the default is 4. "
),
metavar="<number>",
)
import_parser.set_defaults(func=CmdImport)
+39 -3
View File
@@ -16,6 +16,10 @@ class CmdImportUrl(CmdBase):
out=self.args.out,
fname=self.args.file,
no_exec=self.args.no_exec,
remote=self.args.remote,
to_remote=self.args.to_remote,
desc=self.args.desc,
jobs=self.args.jobs,
)
except DvcException:
logger.exception(
@@ -54,17 +58,49 @@ def add_parser(subparsers, parent_parser):
"remote://remote_name/path/to/file/or/dir (see `dvc remote`)",
)
import_parser.add_argument(
"out", nargs="?", help="Destination path to put files to.",
"out", nargs="?", help="Destination path to put files to."
).complete = completion.DIR
import_parser.add_argument(
"--file",
help="Specify name of the DVC-file this command will generate.",
help="Specify name of the .dvc file this command will generate.",
metavar="<filename>",
).complete = completion.DIR
import_parser.add_argument(
"--no-exec",
action="store_true",
default=False,
help="Only create DVC-file without actually downloading it.",
help="Only create .dvc file without actually downloading it.",
)
import_parser.add_argument(
"--to-remote",
action="store_true",
default=False,
help="Download it directly to the remote",
)
import_parser.add_argument(
"-r",
"--remote",
help="Remote storage to download to",
metavar="<name>",
)
import_parser.add_argument(
"-j",
"--jobs",
type=int,
help=(
"Number of jobs to run simultaneously. "
"The default value is 4 * cpu_count(). "
"For SSH remotes, the default is 4. "
),
metavar="<number>",
)
import_parser.add_argument(
"--desc",
type=str,
metavar="<text>",
help=(
"User description of the data (optional). "
"This doesn't affect any DVC operations."
),
)
import_parser.set_defaults(func=CmdImportUrl)
+33 -3
View File
@@ -1,24 +1,54 @@
import argparse
import logging
import colorama
from dvc import analytics
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.utils import boxify
from dvc.utils import format_link as fmt_link
logger = logging.getLogger(__name__)
def _welcome_message():
from dvc.ui import ui
if analytics.is_enabled():
ui.write(
boxify(
"DVC has enabled anonymous aggregate usage analytics.\n"
"Read the analytics documentation (and how to opt-out) here:\n"
+ fmt_link("https://dvc.org/doc/user-guide/analytics"),
border_color="red",
)
)
msg = (
"{yellow}What's next?{nc}\n"
"{yellow}------------{nc}\n"
f"- Check out the documentation: {fmt_link('https://dvc.org/doc')}\n"
f"- Get help and share ideas: {fmt_link('https://dvc.org/chat')}\n"
f"- Star us on GitHub: {fmt_link('https://github.com/iterative/dvc')}"
).format(yellow=colorama.Fore.YELLOW, nc=colorama.Fore.RESET)
ui.write(msg)
class CmdInit(CmdBaseNoRepo):
def run(self):
from dvc.exceptions import InitError
from dvc.repo import Repo
try:
self.repo = Repo.init(
with Repo.init(
".",
no_scm=self.args.no_scm,
force=self.args.force,
subdir=self.args.subdir,
)
self.config = self.repo.config
) as repo:
self.config = repo.config
_welcome_message()
except InitError:
logger.exception("failed to initiate DVC")
return 1
+92
View File
@@ -0,0 +1,92 @@
import argparse
import os
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 = self.args.target + ".html"
self.repo.plots.write_html(html_path, plots, metrics)
ui.write("\nfile://", os.path.abspath(html_path), sep="")
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)
+11 -4
View File
@@ -1,11 +1,11 @@
import argparse
import logging
import sys
from dvc.command import completion
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.command.ls.ls_colors import LsColors
from dvc.exceptions import DvcException
from dvc.ui import ui
logger = logging.getLogger(__name__)
@@ -34,9 +34,13 @@ class CmdList(CmdBaseNoRepo):
recursive=self.args.recursive,
dvc_only=self.args.dvc_only,
)
if entries:
entries = _prettify(entries, sys.stdout.isatty())
logger.info("\n".join(entries))
if self.args.show_json:
import json
ui.write(json.dumps(entries))
elif entries:
entries = _prettify(entries, with_color=True)
ui.write("\n".join(entries))
return 0
except DvcException:
logger.exception(f"failed to list '{self.args.url}'")
@@ -65,6 +69,9 @@ def add_parser(subparsers, parent_parser):
list_parser.add_argument(
"--dvc-only", action="store_true", help="Show only DVC outputs."
)
list_parser.add_argument(
"--show-json", action="store_true", help="Show output in JSON format."
)
list_parser.add_argument(
"--rev",
nargs="?",
+66 -106
View File
@@ -3,7 +3,9 @@ import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link, fix_subparsers
from dvc.exceptions import BadMetricError, DvcException
from dvc.exceptions import DvcException
from dvc.ui import ui
from dvc.utils.serialize import encode_exception
logger = logging.getLogger(__name__)
@@ -11,34 +13,11 @@ logger = logging.getLogger(__name__)
DEFAULT_PRECISION = 5
def show_metrics(
metrics, all_branches=False, all_tags=False, all_commits=False
):
from dvc.utils.diff import format_dict
from dvc.utils.flatten import flatten
# When `metrics` contains a `None` key, it means that some files
# specified as `targets` in `repo.metrics.show` didn't contain any metrics.
missing = metrics.pop(None, None)
for branch, val in metrics.items():
if all_branches or all_tags or all_commits:
logger.info(f"{branch}:")
for fname, metric in val.items():
if not isinstance(metric, dict):
logger.info("\t{}: {}".format(fname, str(metric)))
continue
logger.info(f"\t{fname}:")
for key, value in flatten(format_dict(metric)).items():
logger.info(f"\t\t{key}: {value}")
if missing:
raise BadMetricError(missing)
class CmdMetricsBase(CmdBase):
UNINITIALIZED = True
class CmdMetricsShow(CmdBase):
class CmdMetricsShow(CmdMetricsBase):
def run(self):
try:
metrics = self.repo.metrics.show(
@@ -48,63 +27,31 @@ class CmdMetricsShow(CmdBase):
all_commits=self.args.all_commits,
recursive=self.args.recursive,
)
if self.args.show_json:
import json
logger.info(json.dumps(metrics))
else:
show_metrics(
metrics,
self.args.all_branches,
self.args.all_tags,
self.args.all_commits,
)
except DvcException:
logger.exception("failed to show metrics")
logger.exception("")
return 1
if self.args.show_json:
import json
ui.write(json.dumps(metrics, default=encode_exception))
else:
from dvc.compare import show_metrics
show_metrics(
metrics,
markdown=self.args.show_md,
all_branches=self.args.all_branches,
all_tags=self.args.all_tags,
all_commits=self.args.all_commits,
precision=self.args.precision or DEFAULT_PRECISION,
round_digits=True,
)
return 0
def _show_diff(diff, markdown=False, no_path=False, old=False, precision=None):
from collections import OrderedDict
from dvc.utils.diff import table
if precision is None:
precision = DEFAULT_PRECISION
def _round(val):
if isinstance(val, float):
return round(val, precision)
return val
rows = []
for fname, mdiff in diff.items():
sorted_mdiff = OrderedDict(sorted(mdiff.items()))
for metric, change in sorted_mdiff.items():
row = [] if no_path else [fname]
row.append(metric)
if old:
row.append(_round(change.get("old")))
row.append(_round(change["new"]))
row.append(_round(change.get("diff", "diff not supported")))
rows.append(row)
header = [] if no_path else ["Path"]
header.append("Metric")
if old:
header.extend(["Old", "New"])
else:
header.append("Value")
header.append("Change")
return table(header, rows, markdown)
class CmdMetricsDiff(CmdBase):
class CmdMetricsDiff(CmdMetricsBase):
def run(self):
try:
diff = self.repo.metrics.diff(
@@ -114,26 +61,26 @@ class CmdMetricsDiff(CmdBase):
recursive=self.args.recursive,
all=self.args.all,
)
if self.args.show_json:
import json
logger.info(json.dumps(diff))
else:
table = _show_diff(
diff,
self.args.show_md,
self.args.no_path,
self.args.old,
precision=self.args.precision,
)
if table:
logger.info(table)
except DvcException:
logger.exception("failed to show metrics diff")
return 1
if self.args.show_json:
import json
ui.write(json.dumps(diff))
else:
from dvc.compare import show_diff
show_diff(
diff,
title="Metric",
markdown=self.args.show_md,
no_path=self.args.no_path,
precision=self.args.precision or DEFAULT_PRECISION,
round_digits=True,
)
return 0
@@ -167,8 +114,8 @@ def add_parser(subparsers, parent_parser):
"targets",
nargs="*",
help=(
"Limit command scope to these metric files. Using -R, "
"directories to search metric files in can also be given."
"Limit command scope to these metrics files. Using -R, "
"directories to search metrics files in can also be given."
),
).complete = completion.FILE
metrics_show_parser.add_argument(
@@ -186,6 +133,7 @@ def add_parser(subparsers, parent_parser):
help="Show metrics for all tags.",
)
metrics_show_parser.add_argument(
"-A",
"--all-commits",
action="store_true",
default=False,
@@ -197,6 +145,12 @@ def add_parser(subparsers, parent_parser):
default=False,
help="Show output in JSON format.",
)
metrics_show_parser.add_argument(
"--show-md",
action="store_true",
default=False,
help="Show tabulated output in the Markdown format (GFM).",
)
metrics_show_parser.add_argument(
"-R",
"--recursive",
@@ -204,9 +158,18 @@ def add_parser(subparsers, parent_parser):
default=False,
help=(
"If any target is a directory, recursively search and process "
"metric files."
"metrics files."
),
)
metrics_show_parser.add_argument(
"--precision",
type=int,
help=(
"Round metrics to `n` digits precision after the decimal point. "
f"Rounds to {DEFAULT_PRECISION} digits by default."
),
metavar="<n>",
)
metrics_show_parser.set_defaults(func=CmdMetricsShow)
METRICS_DIFF_HELP = (
@@ -232,8 +195,11 @@ def add_parser(subparsers, parent_parser):
"--targets",
nargs="*",
help=(
"Limit command scope to these metric files. Using -R, "
"directories to search metric files in can also be given."
"Specific metrics file(s) to compare "
"(even if not found as `metrics` in `dvc.yaml`). "
"Using -R, directories to search metrics files in "
"can also be given."
"Shows all tracked metrics by default."
),
metavar="<paths>",
).complete = completion.FILE
@@ -244,7 +210,7 @@ def add_parser(subparsers, parent_parser):
default=False,
help=(
"If any target is a directory, recursively search and process "
"metric files."
"metrics files."
),
)
metrics_diff_parser.add_argument(
@@ -265,12 +231,6 @@ def add_parser(subparsers, parent_parser):
default=False,
help="Show tabulated output in the Markdown format (GFM).",
)
metrics_diff_parser.add_argument(
"--old",
action="store_true",
default=False,
help="Show old metric value.",
)
metrics_diff_parser.add_argument(
"--no-path",
action="store_true",
+3 -3
View File
@@ -25,7 +25,7 @@ def add_parser(subparsers, parent_parser):
MOVE_HELP = "Rename or move a DVC controlled data file or a directory."
MOVE_DESCRIPTION = (
"Rename or move a DVC controlled data file or a directory.\n"
"It renames and modifies the corresponding DVC-file to reflect the"
"It renames and modifies the corresponding .dvc file to reflect the"
" changes."
)
@@ -37,9 +37,9 @@ def add_parser(subparsers, parent_parser):
formatter_class=argparse.RawDescriptionHelpFormatter,
)
move_parser.add_argument(
"src", help="Source path to a data file or directory.",
"src", help="Source path to a data file or directory."
).complete = completion.FILE
move_parser.add_argument(
"dst", help="Destination path.",
"dst", help="Destination path."
).complete = completion.FILE
move_parser.set_defaults(func=CmdMove)
+37 -32
View File
@@ -1,56 +1,45 @@
import argparse
import logging
from collections import OrderedDict
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link, fix_subparsers
from dvc.exceptions import DvcException
from dvc.ui import ui
logger = logging.getLogger(__name__)
def _show_diff(diff, markdown=False, no_path=False):
from dvc.utils.diff import table
rows = []
for fname, pdiff in diff.items():
sorted_pdiff = OrderedDict(sorted(pdiff.items()))
for param, change in sorted_pdiff.items():
row = [] if no_path else [fname]
row.append(param)
row.append(change["old"])
row.append(change["new"])
rows.append(row)
header = [] if no_path else ["Path"]
header.append("Param")
header.append("Old")
header.append("New")
return table(header, rows, markdown)
class CmdParamsDiff(CmdBase):
UNINITIALIZED = True
def run(self):
try:
diff = self.repo.params.diff(
a_rev=self.args.a_rev,
b_rev=self.args.b_rev,
targets=self.args.targets,
all=self.args.all,
deps=self.args.deps,
)
if self.args.show_json:
import json
logger.info(json.dumps(diff))
else:
table = _show_diff(diff, self.args.show_md, self.args.no_path)
if table:
logger.info(table)
except DvcException:
logger.exception("failed to show params diff")
return 1
if self.args.show_json:
import json
ui.write(json.dumps(diff))
else:
from dvc.compare import show_diff
show_diff(
diff,
title="Param",
markdown=self.args.show_md,
no_path=self.args.no_path,
show_changes=False,
)
return 0
@@ -91,12 +80,28 @@ def add_parser(subparsers, parent_parser):
nargs="?",
help=("New Git commit to compare (defaults to the current workspace)"),
)
params_diff_parser.add_argument(
"--targets",
nargs="*",
help=(
"Specific params file(s) to compare "
"(even if not found as `params` in `dvc.yaml`). "
"Shows all tracked params by default."
),
metavar="<paths>",
).complete = completion.FILE
params_diff_parser.add_argument(
"--all",
action="store_true",
default=False,
help="Show unchanged params as well.",
)
params_diff_parser.add_argument(
"--deps",
action="store_true",
default=False,
help="Show only params that are stage dependencies.",
)
params_diff_parser.add_argument(
"--show-json",
action="store_true",
+62 -45
View File
@@ -1,45 +1,29 @@
import argparse
import logging
import os
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link, fix_subparsers
from dvc.exceptions import DvcException
from dvc.schema import PLOT_PROPS
from dvc.ui import ui
from dvc.utils import format_link
logger = logging.getLogger(__name__)
PAGE_HTML = """<!DOCTYPE html>
<html>
<head>
<title>DVC Plot</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
</head>
<body>
{divs}
</body>
</html>"""
DIV_HTML = """<div id = "{id}"></div>
<script type = "text/javascript">
var spec = {vega_json};
vegaEmbed('#{id}', spec);
</script>"""
class CmdPlots(CmdBase):
def _func(self, *args, **kwargs):
raise NotImplementedError
def _props(self):
from dvc.schema import PLOT_PROPS
# Pass only props specified by user, to not shadow ones from plot def
props = {p: getattr(self.args, p) for p in PLOT_PROPS}
return {k: v for k, v in props.items() if v is not None}
def run(self):
from pathlib import Path
if self.args.show_vega:
if not self.args.targets:
logger.error("please specify a target for `--show-vega`")
@@ -55,36 +39,51 @@ class CmdPlots(CmdBase):
if self.args.show_vega:
target = self.args.targets[0]
logger.info(plots[target])
ui.write(plots[target])
return 0
divs = [
DIV_HTML.format(id=f"plot{i}", vega_json=plot)
for i, plot in enumerate(plots.values())
]
html = PAGE_HTML.format(divs="\n".join(divs))
path = self.args.out or "plots.html"
with open(path, "w") as fobj:
fobj.write(html)
logger.info(
"file://{}".format(os.path.join(self.repo.root_dir, path))
)
except DvcException:
logger.exception("")
return 1
if plots:
rel: str = self.args.out or "plots.html"
path: Path = (Path.cwd() / rel).resolve()
self.repo.plots.write_html(
path, plots=plots, html_template_path=self.args.html_template
)
assert (
path.is_absolute()
) # as_uri throws ValueError if not absolute
url = path.as_uri()
ui.write(url)
if self.args.open:
import webbrowser
opened = webbrowser.open(rel)
if not opened:
ui.error_write(
"Failed to open. Please try opening it manually."
)
return 1
else:
ui.error_write(
"No plots were loaded, visualization file will not be created."
)
return 0
class CmdPlotsShow(CmdPlots):
UNINITIALIZED = True
def _func(self, *args, **kwargs):
return self.repo.plots.show(*args, **kwargs)
class CmdPlotsDiff(CmdPlots):
UNINITIALIZED = True
def _func(self, *args, **kwargs):
return self.repo.plots.diff(
*args,
@@ -97,7 +96,7 @@ class CmdPlotsDiff(CmdPlots):
class CmdPlotsModify(CmdPlots):
def run(self):
self.repo.plots.modify(
self.args.target, props=self._props(), unset=self.args.unset,
self.args.target, props=self._props(), unset=self.args.unset
)
return 0
@@ -105,7 +104,7 @@ class CmdPlotsModify(CmdPlots):
def add_parser(subparsers, parent_parser):
PLOTS_HELP = (
"Commands to visualize and compare plot metrics in structured files "
"(JSON, YAML, CSV, TSV)"
"(JSON, YAML, CSV, TSV)."
)
plots_parser = subparsers.add_parser(
@@ -122,7 +121,7 @@ def add_parser(subparsers, parent_parser):
fix_subparsers(plots_subparsers)
SHOW_HELP = "Generate plots from metric files."
SHOW_HELP = "Generate plots from metrics files."
plots_show_parser = plots_subparsers.add_parser(
"show",
parents=[parent_parser],
@@ -133,7 +132,9 @@ def add_parser(subparsers, parent_parser):
plots_show_parser.add_argument(
"targets",
nargs="*",
help="Plots files to visualize. Shows all plots by default.",
help="Files to visualize (supports any file, "
"even when not found as `plots` in `dvc.yaml`). "
"Shows all plots by default.",
).complete = completion.FILE
_add_props_arguments(plots_show_parser)
_add_output_arguments(plots_show_parser)
@@ -153,8 +154,12 @@ def add_parser(subparsers, parent_parser):
plots_diff_parser.add_argument(
"--targets",
nargs="*",
help="Plots file to visualize. Shows all plots by default.",
metavar="<path>",
help=(
"Specific plots file(s) to visualize "
"(even if not found as `plots` in `dvc.yaml`). "
"Shows all tracked plots by default."
),
metavar="<paths>",
).complete = completion.FILE
plots_diff_parser.add_argument(
"-e",
@@ -164,13 +169,13 @@ def add_parser(subparsers, parent_parser):
help=argparse.SUPPRESS,
)
plots_diff_parser.add_argument(
"revisions", nargs="*", default=None, help="Git commits to plot from",
"revisions", nargs="*", default=None, help="Git commits to plot from"
)
_add_props_arguments(plots_diff_parser)
_add_output_arguments(plots_diff_parser)
plots_diff_parser.set_defaults(func=CmdPlotsDiff)
PLOTS_MODIFY_HELP = "Modify display properties of plot metric files."
PLOTS_MODIFY_HELP = "Modify display properties of plot metrics files."
plots_modify_parser = plots_subparsers.add_parser(
"modify",
parents=[parent_parser],
@@ -179,7 +184,7 @@ def add_parser(subparsers, parent_parser):
formatter_class=argparse.RawDescriptionHelpFormatter,
)
plots_modify_parser.add_argument(
"target", help="Metric file to set properties to",
"target", help="Metric file to set properties to"
).complete = completion.FILE
_add_props_arguments(plots_modify_parser)
plots_modify_parser.add_argument(
@@ -243,3 +248,15 @@ def _add_output_arguments(parser):
default=False,
help="Show output in Vega format.",
)
parser.add_argument(
"--open",
action="store_true",
default=False,
help="Open plot file directly in the browser.",
)
parser.add_argument(
"--html-template",
default=None,
help="Custom HTML template for VEGA visualization.",
metavar="<path>",
)
+19 -11
View File
@@ -1,13 +1,10 @@
import argparse
import logging
from dvc.command.base import append_doc_link, fix_subparsers
from dvc.command.config import CmdConfig
from dvc.config import ConfigError, merge
from dvc.ui import ui
from dvc.utils import format_link
logger = logging.getLogger(__name__)
class CmdRemote(CmdConfig):
def __init__(self, args):
@@ -17,14 +14,18 @@ class CmdRemote(CmdConfig):
self.args.name = self.args.name.lower()
def _check_exists(self, conf):
from dvc.config import ConfigError
if self.args.name not in conf["remote"]:
raise ConfigError(f"remote '{self.args.name}' doesn't exist.")
class CmdRemoteAdd(CmdRemote):
def run(self):
from dvc.config import ConfigError
if self.args.default:
logger.info(f"Setting '{self.args.name}' as a default remote.")
ui.write(f"Setting '{self.args.name}' as a default remote.")
with self.config.edit(self.args.level) as conf:
if self.args.name in conf["remote"] and not self.args.force:
@@ -46,13 +47,14 @@ class CmdRemoteRemove(CmdRemote):
self._check_exists(conf)
del conf["remote"][self.args.name]
up_to_level = self.args.level or "repo"
# Remove core.remote refs to this remote in any shadowing configs
for level in reversed(self.config.LEVELS):
with self.config.edit(level) as conf:
if conf["core"].get("remote") == self.args.name:
del conf["core"]["remote"]
if level == self.args.level:
if level == up_to_level:
break
return 0
@@ -60,6 +62,8 @@ class CmdRemoteRemove(CmdRemote):
class CmdRemoteModify(CmdRemote):
def run(self):
from dvc.config import merge
with self.config.edit(self.args.level) as conf:
merged = self.config.load_config_to_level(self.args.level)
merge(merged, conf)
@@ -77,13 +81,14 @@ class CmdRemoteModify(CmdRemote):
class CmdRemoteDefault(CmdRemote):
def run(self):
from dvc.config import ConfigError
if self.args.name is None and not self.args.unset:
conf = self.config.load_one(self.args.level)
conf = self.config.read(self.args.level)
try:
print(conf["core"]["remote"])
except KeyError:
logger.info("No default remote set")
ui.write("No default remote set")
return 1
else:
with self.config.edit(self.args.level) as conf:
@@ -107,9 +112,9 @@ class CmdRemoteDefault(CmdRemote):
class CmdRemoteList(CmdRemote):
def run(self):
conf = self.config.load_one(self.args.level)
conf = self.config.read(self.args.level)
for name, conf in conf["remote"].items():
logger.info("{}\t{}".format(name, conf["url"]))
ui.write(name, conf["url"], sep="\t")
return 0
@@ -119,6 +124,8 @@ class CmdRemoteRename(CmdRemote):
conf["core"]["remote"] = self.args.new
def run(self):
from dvc.config import ConfigError
all_config = self.config.load_config_to_level(None)
if self.args.new in all_config.get("remote", {}):
raise ConfigError(
@@ -133,8 +140,9 @@ class CmdRemoteRename(CmdRemote):
del conf["remote"][self.args.name]
self._rename_default(conf)
up_to_level = self.args.level or "repo"
for level in reversed(self.config.LEVELS):
if level == self.args.level:
if level == up_to_level:
break
with self.config.edit(level) as level_conf:
self._rename_default(level_conf)
+6 -3
View File
@@ -14,14 +14,15 @@ class CmdRemove(CmdBase):
try:
self.repo.remove(target, outs=self.args.outs)
except DvcException:
logger.exception(f"failed to remove '{target}'")
logger.exception("")
return 1
return 0
def add_parser(subparsers, parent_parser):
REMOVE_HELP = (
"Remove stage entry, remove .gitignore entry and unprotect outputs"
"Remove stages from dvc.yaml and/or"
" stop tracking files or directories."
)
remove_parser = subparsers.add_parser(
"remove",
@@ -37,6 +38,8 @@ def add_parser(subparsers, parent_parser):
help="Remove outputs as well.",
)
remove_parser.add_argument(
"targets", nargs="+", help="DVC-files to remove.",
"targets",
nargs="+",
help=".dvc files or stages from dvc.yaml to remove.",
).complete = completion.DVC_FILE
remove_parser.set_defaults(func=CmdRemove)
+56 -87
View File
@@ -1,83 +1,56 @@
import argparse
import logging
import os
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.command.metrics import show_metrics
from dvc.command.status import CmdDataStatus
from dvc.dvcfile import PIPELINE_FILE
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
class CmdRepro(CmdBase):
def run(self):
saved_dir = os.path.realpath(os.curdir)
if self.args.cwd:
os.chdir(self.args.cwd)
from dvc.ui import ui
# Dirty hack so the for loop below can at least enter once
if self.args.all_pipelines:
self.args.targets = [None]
elif not self.args.targets:
self.args.targets = self.default_targets
stages = self.repo.reproduce(**self._repro_kwargs)
if len(stages) == 0:
ui.write(CmdDataStatus.UP_TO_DATE_MSG)
else:
ui.write(
"Use `dvc push` to send your updates to " "remote storage."
)
ret = 0
for target in self.args.targets:
try:
stages = self.repo.reproduce(
target,
single_item=self.args.single_item,
force=self.args.force,
dry=self.args.dry,
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,
experiment=self.args.experiment,
queue=self.args.queue,
run_all=self.args.run_all,
jobs=self.args.jobs,
params=self.args.params,
)
if self.args.metrics:
from dvc.compare import show_metrics
if len(stages) == 0:
logger.info(CmdDataStatus.UP_TO_DATE_MSG)
metrics = self.repo.metrics.show()
show_metrics(metrics)
if self.args.metrics:
metrics = self.repo.metrics.show()
show_metrics(metrics)
except DvcException:
logger.exception("")
ret = 1
break
return 0
os.chdir(saved_dir)
return ret
@property
def _repro_kwargs(self):
return {
"targets": self.args.targets,
"single_item": self.args.single_item,
"force": self.args.force,
"dry": self.args.dry,
"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,
"glob": self.args.glob,
}
def add_parser(subparsers, parent_parser):
REPRO_HELP = (
"Reproduce complete or partial pipelines by executing their stages."
)
repro_parser = subparsers.add_parser(
"repro",
parents=[parent_parser],
description=append_doc_link(REPRO_HELP, "repro"),
help=REPRO_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
def add_arguments(repro_parser):
repro_parser.add_argument(
"targets",
nargs="*",
help=f"Stages to reproduce. '{PIPELINE_FILE}' by default.",
).complete = completion.DVC_FILE
help="Stages to reproduce. 'dvc.yaml' by default.",
).complete = completion.DVCFILES_AND_STAGE
repro_parser.add_argument(
"-f",
"--force",
@@ -93,13 +66,6 @@ def add_parser(subparsers, parent_parser):
help="Reproduce only single data item without recursive dependencies "
"check.",
)
repro_parser.add_argument(
"-c",
"--cwd",
default=os.path.curdir,
help="Directory within your repo to reproduce from.",
metavar="<path>",
)
repro_parser.add_argument(
"-m",
"--metrics",
@@ -126,8 +92,8 @@ def add_parser(subparsers, parent_parser):
"--pipeline",
action="store_true",
default=False,
help="Reproduce the whole pipeline that the specified stage file "
"belongs to.",
help="Reproduce the whole pipeline that the specified targets "
"belong to.",
)
repro_parser.add_argument(
"-P",
@@ -172,29 +138,32 @@ def add_parser(subparsers, parent_parser):
help="Start from the specified stages when reproducing pipelines.",
)
repro_parser.add_argument(
"-e",
"--experiment",
"--pull",
action="store_true",
default=False,
help=argparse.SUPPRESS,
help=(
"Try automatically pulling missing cache for outputs restored "
"from the run-cache."
),
)
repro_parser.add_argument(
"--params",
action="append",
default=[],
help=argparse.SUPPRESS,
metavar="[<filename>:]<params_list>",
)
repro_parser.add_argument(
"--queue", action="store_true", default=False, help=argparse.SUPPRESS
)
repro_parser.add_argument(
"--run-all",
"--glob",
action="store_true",
default=False,
help=argparse.SUPPRESS,
help="Allows targets containing shell-style wildcards.",
)
repro_parser.add_argument(
"-j", "--jobs", type=int, help=argparse.SUPPRESS, metavar="<number>"
def add_parser(subparsers, parent_parser):
REPRO_HELP = (
"Reproduce complete or partial pipelines by executing their stages."
)
repro_parser = subparsers.add_parser(
"repro",
parents=[parent_parser],
description=append_doc_link(REPRO_HELP, "repro"),
help=REPRO_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
add_arguments(repro_parser)
repro_parser.set_defaults(func=CmdRepro)
+2 -1
View File
@@ -10,8 +10,9 @@ logger = logging.getLogger(__name__)
class CmdRoot(CmdBaseNoRepo):
def run(self):
from dvc.repo import Repo
from dvc.ui import ui
logger.info(relpath(Repo.find_root()))
ui.write(relpath(Repo.find_root()))
return 0
+22 -158
View File
@@ -2,6 +2,7 @@ import argparse
import logging
from dvc.command.base import CmdBase, append_doc_link
from dvc.command.stage import parse_cmd
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
@@ -20,8 +21,9 @@ class CmdRun(CmdBase):
self.args.plots_no_cache,
self.args.outs_persist,
self.args.outs_persist_no_cache,
self.args.checkpoints,
self.args.params,
self.args.command,
self.args.cmd,
]
): # pragma: no cover
logger.error(
@@ -31,60 +33,30 @@ class CmdRun(CmdBase):
)
return 1
kwargs = vars(self.args)
kwargs.update(
{
"cmd": parse_cmd(self.args.cmd),
"fname": kwargs.pop("file"),
"no_exec": (self.args.no_exec or bool(self.args.checkpoints)),
"run_cache": not kwargs.pop("no_run_cache"),
}
)
try:
self.repo.run(
cmd=self._parsed_cmd(),
outs=self.args.outs,
outs_no_cache=self.args.outs_no_cache,
metrics=self.args.metrics,
metrics_no_cache=self.args.metrics_no_cache,
plots=self.args.plots,
plots_no_cache=self.args.plots_no_cache,
deps=self.args.deps,
params=self.args.params,
fname=self.args.file,
wdir=self.args.wdir,
no_exec=self.args.no_exec,
force=self.args.force,
run_cache=not self.args.no_run_cache,
no_commit=self.args.no_commit,
outs_persist=self.args.outs_persist,
outs_persist_no_cache=self.args.outs_persist_no_cache,
always_changed=self.args.always_changed,
name=self.args.name,
single_stage=self.args.single_stage,
external=self.args.external,
)
self.repo.run(**kwargs)
except DvcException:
logger.exception("")
return 1
return 0
def _parsed_cmd(self):
"""
We need to take into account two cases:
- ['python code.py foo bar']: Used mainly with dvc as a library
- ['echo', 'foo bar']: List of arguments received from the CLI
The second case would need quoting, as it was passed through:
dvc run echo "foo bar"
"""
if len(self.args.command) < 2:
return " ".join(self.args.command)
return " ".join(self._quote_argument(arg) for arg in self.args.command)
def _quote_argument(self, argument):
if " " not in argument or '"' in argument:
return argument
return f'"{argument}"'
def add_parser(subparsers, parent_parser):
RUN_HELP = "Generate a stage file from a command and execute the command."
from dvc.command.stage import _add_common_args
RUN_HELP = (
"Generate a dvc.yaml file from a command and execute the command."
)
run_parser = subparsers.add_parser(
"run",
parents=[parent_parser],
@@ -92,93 +64,15 @@ def add_parser(subparsers, parent_parser):
help=RUN_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
run_parser.add_argument("-n", "--name", help="Stage name.")
run_parser.add_argument(
"-d",
"--deps",
action="append",
default=[],
help="Declare dependencies for reproducible cmd.",
metavar="<path>",
)
run_parser.add_argument(
"-n", "--name", help="Stage name.",
)
run_parser.add_argument(
"-o",
"--outs",
action="append",
default=[],
help="Declare output file or directory.",
metavar="<filename>",
)
run_parser.add_argument(
"-O",
"--outs-no-cache",
action="append",
default=[],
help="Declare output file or directory "
"(do not put into DVC cache).",
metavar="<filename>",
)
run_parser.add_argument(
"-p",
"--params",
action="append",
default=[],
help="Declare parameter to use as additional dependency.",
metavar="[<filename>:]<params_list>",
)
run_parser.add_argument(
"-m",
"--metrics",
action="append",
default=[],
help="Declare output metric file.",
metavar="<path>",
)
run_parser.add_argument(
"-M",
"--metrics-no-cache",
action="append",
default=[],
help="Declare output metric file (do not put into DVC cache).",
metavar="<path>",
)
run_parser.add_argument(
"--plots",
action="append",
default=[],
help="Declare output plot file.",
metavar="<path>",
)
run_parser.add_argument(
"--plots-no-cache",
action="append",
default=[],
help="Declare output plot file (do not put into DVC cache).",
metavar="<path>",
)
run_parser.add_argument(
"--file", metavar="<filename>", help=argparse.SUPPRESS,
)
run_parser.add_argument(
"-w",
"--wdir",
help="Directory within your repo to run your command in.",
metavar="<path>",
"--file", metavar="<filename>", help=argparse.SUPPRESS
)
run_parser.add_argument(
"--no-exec",
action="store_true",
default=False,
help="Only create stage file without actually running it.",
)
run_parser.add_argument(
"-f",
"--force",
action="store_true",
default=False,
help="Overwrite existing stage",
help="Only create dvc.yaml without actually running it.",
)
run_parser.add_argument(
"--no-run-cache",
@@ -195,41 +89,11 @@ def add_parser(subparsers, parent_parser):
default=False,
help="Don't put files/directories into cache.",
)
run_parser.add_argument(
"--outs-persist",
action="append",
default=[],
help="Declare output file or directory that will not be "
"removed upon repro.",
metavar="<filename>",
)
run_parser.add_argument(
"--outs-persist-no-cache",
action="append",
default=[],
help="Declare output file or directory that will not be "
"removed upon repro (do not put into DVC cache).",
metavar="<filename>",
)
run_parser.add_argument(
"--always-changed",
action="store_true",
default=False,
help="Always consider this DVC-file as changed.",
)
run_parser.add_argument(
"--single-stage",
action="store_true",
default=False,
help=argparse.SUPPRESS,
)
run_parser.add_argument(
"--external",
action="store_true",
default=False,
help="Allow outputs that are outside of the DVC repository.",
)
run_parser.add_argument(
"command", nargs=argparse.REMAINDER, help="Command to execute."
)
_add_common_args(run_parser)
run_parser.set_defaults(func=CmdRun)
+365
View File
@@ -0,0 +1,365 @@
import argparse
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.utils.cli_parse import parse_params
from dvc.utils.humanize import truncate_text
if TYPE_CHECKING:
from dvc.output import Output
from dvc.stage import Stage
logger = logging.getLogger(__name__)
MAX_TEXT_LENGTH = 80
def generate_description(stage: "Stage") -> str:
def part_desc(outs: Iterable["Output"]) -> str:
return ", ".join(out.def_path for out in outs)
if not stage.deps and not stage.outs:
return "No outputs or dependencies"
if not stage.outs and stage.deps:
return "Depends on " + part_desc(stage.deps)
def is_plot_or_metric(out: "Output"):
return bool(out.plot) or bool(out.metric)
desc: List[str] = []
outs = list(filterfalse(is_plot_or_metric, stage.outs))
if outs:
desc.append("Outputs " + part_desc(outs))
plots_and_metrics = list(filter(is_plot_or_metric, stage.outs))
if plots_and_metrics:
desc.append("Reports " + part_desc(plots_and_metrics))
return "; ".join(desc)
def prepare_description(
stage: "Stage", max_length: int = MAX_TEXT_LENGTH
) -> str:
desc = stage.short_description() or generate_description(stage)
return truncate_text(desc, max_length)
def prepare_stages_data(
stages: Iterable["Stage"],
description: bool = True,
max_length: int = MAX_TEXT_LENGTH,
) -> Dict[str, str]:
return {
stage.addressing: prepare_description(stage, max_length=max_length)
if description
else ""
for stage in stages
}
class CmdStageList(CmdBase):
def _get_stages(self) -> Iterable["Stage"]:
if self.args.all:
stages: List["Stage"] = self.repo.index.stages # type: ignore
logger.trace( # type: ignore[attr-defined]
"%d no. of stages found", len(stages)
)
return stages
# removing duplicates while maintaining order
collected = chain.from_iterable(
self.repo.stage.collect(
target=target, recursive=self.args.recursive, accept_group=True
)
for target in self.args.targets
)
return dict.fromkeys(collected).keys()
def run(self):
from dvc.ui import ui
def log_error(relpath: str, exc: Exception):
if self.args.fail:
raise exc
logger.debug("Stages from %s failed to load", relpath)
# silence stage collection error by default
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)
ui.table(data.items())
return 0
def parse_cmd(commands: List[str]) -> str:
"""
We need to take into account two cases:
- ['python code.py foo bar']: Used mainly with dvc as a library
- ['echo', 'foo bar']: List of arguments received from the CLI
The second case would need quoting, as it was passed through:
dvc run echo "foo bar"
"""
def quote_argument(arg: str):
if not arg:
return '""'
if " " in arg and '"' not in arg:
return f'"{arg}"'
return arg
if len(commands) < 2:
return " ".join(commands)
return " ".join(map(quote_argument, commands))
class CmdStageAdd(CmdBase):
def run(self):
kwargs = vars(self.args)
kwargs.update(
{
"cmd": parse_cmd(kwargs.pop("cmd")),
"params": parse_params(self.args.params),
}
)
self.repo.stage.add(**kwargs)
return 0
def _add_common_args(parser):
parser.add_argument(
"-d",
"--deps",
action="append",
default=[],
help="Declare dependencies for reproducible cmd.",
metavar="<path>",
).complete = completion.FILE
parser.add_argument(
"-o",
"--outs",
action="append",
default=[],
help="Declare output file or directory.",
metavar="<filename>",
).complete = completion.FILE
parser.add_argument(
"-O",
"--outs-no-cache",
action="append",
default=[],
help="Declare output file or directory "
"(do not put into DVC cache).",
metavar="<filename>",
).complete = completion.FILE
parser.add_argument(
"-p",
"--params",
action="append",
default=[],
help="Declare parameter to use as additional dependency.",
metavar="[<filename>:]<params_list>",
).complete = completion.FILE
parser.add_argument(
"-m",
"--metrics",
action="append",
default=[],
help="Declare output metrics file.",
metavar="<path>",
)
parser.add_argument(
"-M",
"--metrics-no-cache",
action="append",
default=[],
help="Declare output metrics file (do not put into DVC cache).",
metavar="<path>",
)
parser.add_argument(
"--plots",
action="append",
default=[],
help="Declare output plot file.",
metavar="<path>",
)
parser.add_argument(
"--plots-no-cache",
action="append",
default=[],
help="Declare output plot file (do not put into DVC cache).",
metavar="<path>",
)
parser.add_argument(
"--live", help="Declare output as dvclive.", metavar="<path>"
)
parser.add_argument(
"--live-no-cache",
help="Declare output as dvclive (do not put into DVC cache).",
metavar="<path>",
)
parser.add_argument(
"--live-no-summary",
action="store_true",
default=False,
help="Signal dvclive logger to not dump latest metrics file.",
)
parser.add_argument(
"--live-no-html",
action="store_true",
default=False,
help="Signal dvclive logger to not produce training report.",
)
parser.add_argument(
"-w",
"--wdir",
help="Directory within your repo to run your command in.",
metavar="<path>",
)
parser.add_argument(
"-f",
"--force",
action="store_true",
default=False,
help="Overwrite existing stage",
)
parser.add_argument(
"--outs-persist",
action="append",
default=[],
help="Declare output file or directory that will not be "
"removed upon repro.",
metavar="<filename>",
)
parser.add_argument(
"--outs-persist-no-cache",
action="append",
default=[],
help="Declare output file or directory that will not be "
"removed upon repro (do not put into DVC cache).",
metavar="<filename>",
)
parser.add_argument(
"-c",
"--checkpoints",
action="append",
default=[],
help="Declare checkpoint output file or directory for 'dvc exp run'. "
"Not compatible with 'dvc repro'.",
metavar="<filename>",
).complete = completion.FILE
parser.add_argument(
"--always-changed",
action="store_true",
default=False,
help="Always consider this DVC-file as changed.",
)
parser.add_argument(
"--external",
action="store_true",
default=False,
help="Allow outputs that are outside of the DVC repository.",
)
parser.add_argument(
"--desc",
type=str,
metavar="<text>",
help=(
"User description of the stage (optional). "
"This doesn't affect any DVC operations."
),
)
parser.add_argument(
"cmd",
nargs=argparse.REMAINDER,
help="Command to execute.",
metavar="command",
)
def add_parser(subparsers, parent_parser):
STAGES_HELP = "Commands to list and create stages."
stage_parser = subparsers.add_parser(
"stage",
parents=[parent_parser],
description=append_doc_link(STAGES_HELP, "stage"),
help=STAGES_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
stage_subparsers = stage_parser.add_subparsers(
dest="cmd",
help="Use `dvc stage CMD --help` to display command-specific help.",
)
fix_subparsers(stage_subparsers)
STAGE_ADD_HELP = "Create stage"
stage_add_parser = stage_subparsers.add_parser(
"add",
parents=[parent_parser],
description=append_doc_link(STAGE_ADD_HELP, "stage/add"),
help=STAGE_ADD_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
stage_add_parser.add_argument(
"-n", "--name", help="Name of the stage to add", required=True
)
_add_common_args(stage_add_parser)
stage_add_parser.set_defaults(func=CmdStageAdd)
STAGE_LIST_HELP = "List stages."
stage_list_parser = stage_subparsers.add_parser(
"list",
parents=[parent_parser],
description=append_doc_link(STAGE_LIST_HELP, "stage/list"),
help=STAGE_LIST_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
stage_list_parser.add_argument(
"targets",
nargs="*",
default=["dvc.yaml"],
help=(
"Show stages from a dvc.yaml/.dvc file or a directory. "
"'dvc.yaml' by default"
),
)
stage_list_parser.add_argument(
"--all",
action="store_true",
default=False,
help="List all of the stages in the repo.",
)
stage_list_parser.add_argument(
"--fail",
action="store_true",
default=False,
help="Fail immediately, do not suppress any syntax errors.",
)
stage_list_parser.add_argument(
"-R",
"--recursive",
action="store_true",
default=False,
help="List all stages inside the specified directory.",
)
stage_list_parser.add_argument(
"--names-only",
action="store_true",
default=False,
help="List only stage names.",
)
stage_list_parser.set_defaults(func=CmdStageList)
+44 -21
View File
@@ -2,6 +2,8 @@ import logging
from dvc.command.data_sync import CmdDataBase
from dvc.exceptions import DvcException
from dvc.ui import ui
from dvc.utils import format_link
logger = logging.getLogger(__name__)
@@ -10,6 +12,11 @@ class CmdDataStatus(CmdDataBase):
STATUS_LEN = 20
STATUS_INDENT = "\t"
UP_TO_DATE_MSG = "Data and pipelines are up to date."
IN_SYNC_MSG = "Cache and remote '{remote}' are in sync."
EMPTY_PROJECT_MSG = (
"There are no data or pipelines tracked in this project yet.\n"
"See {link} to get started!"
).format(link=format_link("https://dvc.org/doc/start"))
def _normalize(self, s):
s += ":"
@@ -20,7 +27,7 @@ class CmdDataStatus(CmdDataBase):
ind = indent * self.STATUS_INDENT
if isinstance(status, str):
logger.info(f"{ind}{status}")
ui.write(f"{ind}{status}")
return
if isinstance(status, list):
@@ -32,25 +39,32 @@ class CmdDataStatus(CmdDataBase):
for key, value in status.items():
if isinstance(value, str):
logger.info("{}{}{}".format(ind, self._normalize(value), key))
ui.write(f"{ind}{self._normalize(value)}{key}")
elif value:
logger.info(f"{ind}{key}:")
ui.write(f"{ind}{key}:")
self._show(value, indent + 1)
def run(self):
from dvc.repo import lock_repo
indent = 1 if self.args.cloud else 0
try:
st = self.repo.status(
targets=self.args.targets,
jobs=self.args.jobs,
cloud=self.args.cloud,
remote=self.args.remote,
all_branches=self.args.all_branches,
all_tags=self.args.all_tags,
all_commits=self.args.all_commits,
with_deps=self.args.with_deps,
recursive=self.args.recursive,
)
with lock_repo(self.repo):
try:
st = self.repo.status(
targets=self.args.targets,
jobs=self.args.jobs,
cloud=self.args.cloud,
remote=self.args.remote,
all_branches=self.args.all_branches,
all_tags=self.args.all_tags,
all_commits=self.args.all_commits,
with_deps=self.args.with_deps,
recursive=self.args.recursive,
)
except DvcException:
logger.exception("")
return 1
if self.args.quiet:
return bool(st)
@@ -58,13 +72,22 @@ class CmdDataStatus(CmdDataBase):
if self.args.show_json:
import json
logger.info(json.dumps(st))
elif st:
ui.write(json.dumps(st))
return 0
if st:
self._show(st, indent)
return 0
# additional hints for the user
if not self.repo.index.stages:
ui.write(self.EMPTY_PROJECT_MSG)
elif self.args.cloud or self.args.remote:
remote = self.args.remote or self.repo.config["core"].get(
"remote"
)
ui.write(self.IN_SYNC_MSG.format(remote=remote))
else:
logger.info(self.UP_TO_DATE_MSG)
ui.write(self.UP_TO_DATE_MSG)
except DvcException:
logger.exception("failed to obtain data status")
return 1
return 0
+2 -2
View File
@@ -23,7 +23,7 @@ class CmdUnprotect(CmdBase):
def add_parser(subparsers, parent_parser):
UNPROTECT_HELP = (
"Unprotect tracked files or directories (when hardlinks or symlinks "
"have been enabled with `dvc config cache.type`)"
"have been enabled with `dvc config cache.type`)."
)
unprotect_parser = subparsers.add_parser(
"unprotect",
@@ -33,6 +33,6 @@ def add_parser(subparsers, parent_parser):
formatter_class=argparse.RawDescriptionHelpFormatter,
)
unprotect_parser.add_argument(
"targets", nargs="+", help="Data files/directories to unprotect.",
"targets", nargs="+", help="Data files/directories to unprotect."
).complete = completion.FILE
unprotect_parser.set_defaults(func=CmdUnprotect)
+27 -1
View File
@@ -16,6 +16,9 @@ class CmdUpdate(CmdBase):
targets=self.args.targets,
rev=self.args.rev,
recursive=self.args.recursive,
to_remote=self.args.to_remote,
remote=self.args.remote,
jobs=self.args.jobs,
)
except DvcException:
logger.exception("failed update data")
@@ -33,7 +36,7 @@ def add_parser(subparsers, parent_parser):
formatter_class=argparse.RawDescriptionHelpFormatter,
)
update_parser.add_argument(
"targets", nargs="+", help="DVC-files to update.",
"targets", nargs="+", help=".dvc files to update."
).complete = completion.DVC_FILE
update_parser.add_argument(
"--rev",
@@ -48,4 +51,27 @@ def add_parser(subparsers, parent_parser):
default=False,
help="Update all stages in the specified directory.",
)
update_parser.add_argument(
"--to-remote",
action="store_true",
default=False,
help="Update data directly on the remote",
)
update_parser.add_argument(
"-r",
"--remote",
help="Remote storage to perform updates to",
metavar="<name>",
)
update_parser.add_argument(
"-j",
"--jobs",
type=int,
help=(
"Number of jobs to run simultaneously. "
"The default value is 4 * cpu_count(). "
"For SSH remotes, the default is 4. "
),
metavar="<number>",
)
update_parser.set_defaults(func=CmdUpdate)
+8 -2
View File
@@ -2,15 +2,20 @@ import argparse
import logging
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.info import get_dvc_info
from dvc.ui import ui
logger = logging.getLogger(__name__)
class CmdVersion(CmdBaseNoRepo):
def run(self):
from dvc.info import get_dvc_info
from dvc.updater import notify_updates
dvc_info = get_dvc_info()
logger.info(dvc_info)
ui.write(dvc_info, force=True)
notify_updates()
return 0
@@ -24,5 +29,6 @@ def add_parser(subparsers, parent_parser):
description=append_doc_link(VERSION_HELP, "version"),
help=VERSION_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
aliases=["doctor"],
)
version_parser.set_defaults(func=CmdVersion)
+326
View File
@@ -0,0 +1,326 @@
from collections import abc
from itertools import chain, repeat, zip_longest
from operator import itemgetter
from typing import (
TYPE_CHECKING,
Any,
Dict,
ItemsView,
Iterable,
Iterator,
List,
Mapping,
MutableSequence,
Sequence,
Tuple,
Union,
overload,
)
from funcy import reraise
if TYPE_CHECKING:
from dvc.ui.table import CellT
class Column(List["CellT"]):
pass
def with_value(value, default):
return default if value is None else value
class TabularData(MutableSequence[Sequence["CellT"]]):
def __init__(self, columns: Sequence[str], fill_value: str = ""):
self._columns: Dict[str, Column] = {name: Column() for name in columns}
self._keys: List[str] = list(columns)
self._fill_value = fill_value
@property
def columns(self) -> List[Column]:
return list(map(self.column, self.keys()))
def column(self, name: str) -> Column:
return self._columns[name]
def items(self) -> ItemsView[str, Column]:
projection = {k: self.column(k) for k in self.keys()}
return projection.items()
def keys(self) -> List[str]:
return self._keys
def _iter_col_row(
self, row: Sequence["CellT"]
) -> Iterator[Tuple["CellT", Column]]:
for val, col in zip_longest(row, self.columns):
if col is None:
break
yield with_value(val, self._fill_value), col
def append(self, value: Sequence["CellT"]) -> None:
for val, col in self._iter_col_row(value):
col.append(val)
def extend(self, values: Iterable[Sequence["CellT"]]) -> None:
for row in values:
self.append(row)
def insert(self, index: int, value: Sequence["CellT"]) -> None:
for val, col in self._iter_col_row(value):
col.insert(index, val)
def __iter__(self) -> Iterator[List["CellT"]]:
return map(list, zip(*self.columns))
def __getattr__(self, item: str) -> Column:
with reraise(KeyError, AttributeError):
return self.column(item)
def __getitem__(self, item: Union[int, slice]):
func = itemgetter(item)
it = map(func, self.columns)
if isinstance(item, slice):
it = map(list, zip(*it))
return list(it)
@overload
def __setitem__(self, item: int, value: Sequence["CellT"]) -> None:
...
@overload
def __setitem__(
self, item: slice, value: Iterable[Sequence["CellT"]]
) -> None:
...
def __setitem__(self, item, value) -> None:
it = value
if isinstance(item, slice):
n = len(self.columns)
normalized_rows = (
chain(val, repeat(self._fill_value, n - len(val)))
for val in value
)
# we need to transpose those rows into columnar format
# as we work in terms of column-based arrays
it = zip(*normalized_rows)
for i, col in self._iter_col_row(it):
col[item] = i
def __delitem__(self, item: Union[int, slice]) -> None:
for col in self.columns:
del col[item]
def __len__(self) -> int:
return len(self.columns[0])
@property
def shape(self) -> Tuple[int, int]:
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)
def rename(self, from_col_name: str, to_col_name: str) -> None:
self._columns[to_col_name] = self._columns.pop(from_col_name)
self._keys[self._keys.index(from_col_name)] = to_col_name
def project(self, *col_names: str) -> None:
self.drop(*(set(self._keys) - set(col_names)))
self._keys = list(col_names)
def is_empty(self, col_name: str) -> bool:
col = self.column(col_name)
return not any(item != self._fill_value for item in col)
def to_csv(self) -> str:
import csv
from io import StringIO
buff = StringIO()
writer = csv.writer(buff)
writer.writerow(self.keys())
for row in self:
writer.writerow(row)
return buff.getvalue()
def add_column(self, name: str) -> None:
self._columns[name] = Column([self._fill_value] * len(self))
self._keys.append(name)
def row_from_dict(self, d: Mapping[str, "CellT"]) -> None:
keys = self.keys()
for key in d:
if key not in keys:
self.add_column(key)
row: List["CellT"] = [
with_value(d.get(key), self._fill_value) for key in self.keys()
]
self.append(row)
def render(self, **kwargs: Any):
from dvc.ui import ui
ui.table(self, headers=self.keys(), **kwargs)
def as_dict(
self, cols: Iterable[str] = None
) -> Iterable[Dict[str, "CellT"]]:
keys = self.keys() if cols is None else set(cols)
return [
{k: self._columns[k][i] for k in keys} for i in range(len(self))
]
def _normalize_float(val: float, precision: int):
return f"{val:.{precision}g}"
def _format_field(
val: Any, precision: int = None, round_digits: bool = False
) -> str:
def _format(_val):
if isinstance(_val, float) and precision:
func = round if round_digits else _normalize_float
return func(_val, precision)
if isinstance(_val, abc.Mapping):
return {k: _format(v) for k, v in _val.items()}
if isinstance(_val, list):
return [_format(x) for x in _val]
return _val
return str(_format(val))
def diff_table(
diff,
title: str,
old: bool = True,
no_path: bool = False,
show_changes: bool = True,
precision: int = None,
round_digits: bool = False,
on_empty_diff: str = None,
) -> TabularData:
headers: List[str] = ["Path", title, "Old", "New", "Change"]
fill_value = "-"
td = TabularData(headers, fill_value=fill_value)
for fname, diff_in_file in diff.items():
for item, change in sorted(diff_in_file.items()):
old_value = with_value(change.get("old"), fill_value)
new_value = with_value(change.get("new"), fill_value)
diff_value = with_value(
change.get("diff", on_empty_diff), fill_value
)
td.append(
[
fname,
str(item),
_format_field(old_value, precision, round_digits),
_format_field(new_value, precision, round_digits),
_format_field(diff_value, precision, round_digits),
]
)
if no_path:
td.drop("Path")
if not show_changes:
td.drop("Change")
if not old:
td.drop("Old")
td.rename("New", "Value")
return td
def show_diff(
diff,
title: str,
old: bool = True,
no_path: bool = False,
show_changes: bool = True,
precision: int = None,
round_digits: bool = False,
on_empty_diff: str = None,
markdown: bool = False,
) -> None:
td = diff_table(
diff,
title=title,
old=old,
no_path=no_path,
show_changes=show_changes,
precision=precision,
round_digits=round_digits,
on_empty_diff=on_empty_diff,
)
td.render(markdown=markdown)
def metrics_table(
metrics,
all_branches: bool = False,
all_tags: bool = False,
all_commits: bool = False,
precision: int = None,
round_digits: bool = False,
):
from dvc.utils.diff import format_dict
from dvc.utils.flatten import flatten
td = TabularData(["Revision", "Path"], fill_value="-")
for branch, val in metrics.items():
for fname, metric in val.get("data", {}).items():
row_data: Dict[str, str] = {"Revision": branch, "Path": fname}
metric = metric.get("data", {})
flattened = (
flatten(format_dict(metric))
if isinstance(metric, dict)
else {"": metric}
)
row_data.update(
{
k: _format_field(v, precision, round_digits)
for k, v in flattened.items()
}
)
td.row_from_dict(row_data)
rev, path, *metrics_headers = td.keys()
td.project(rev, path, *sorted(metrics_headers))
if not any([all_branches, all_tags, all_commits]):
td.drop("Revision")
return td
def show_metrics(
metrics,
markdown: bool = False,
all_branches: bool = False,
all_tags: bool = False,
all_commits: bool = False,
precision: int = None,
round_digits: bool = False,
) -> None:
td = metrics_table(
metrics,
all_branches=all_branches,
all_tags=all_tags,
all_commits=all_commits,
precision=precision,
round_digits=round_digits,
)
td.render(markdown=markdown)
+85 -223
View File
@@ -4,25 +4,11 @@ import os
import re
from contextlib import contextmanager
from functools import partial
from urllib.parse import urlparse
import configobj
from funcy import cached_property, compact, re_find, walk_values
from voluptuous import (
ALLOW_EXTRA,
All,
Any,
Coerce,
Invalid,
Lower,
Optional,
Range,
Schema,
)
from funcy import cached_property, compact, memoize, re_find
from dvc.exceptions import DvcException, NotDvcRepoError
from dvc.path_info import PathInfo
from dvc.utils import relpath
logger = logging.getLogger(__name__)
@@ -34,196 +20,31 @@ class ConfigError(DvcException):
super().__init__(f"config file error: {msg}")
class NoRemoteError(ConfigError):
class RemoteConfigError(ConfigError):
pass
def supported_cache_type(types):
"""Checks if link type config option consists only of valid values.
Args:
types (list/string): type(s) of links that dvc should try out.
"""
if types is None:
return None
if isinstance(types, str):
types = [typ.strip() for typ in types.split(",")]
unsupported = set(types) - {"reflink", "hardlink", "symlink", "copy"}
if unsupported:
raise Invalid(
"Unsupported cache type(s): {}".format(", ".join(unsupported))
)
return types
# Checks that value is either true or false and converts it to bool
to_bool = Bool = All(
Lower,
Any("true", "false"),
lambda v: v == "true",
msg="expected true or false",
)
def Choices(*choices):
"""Checks that value belongs to the specified set of values
Args:
*choices: pass allowed values as arguments, or pass a list or
tuple as a single argument
"""
return Any(*choices, msg="expected one of {}".format(", ".join(choices)))
def ByUrl(mapping):
schemas = walk_values(Schema, mapping)
def validate(data):
if "url" not in data:
raise Invalid("expected 'url'")
parsed = urlparse(data["url"])
# Windows absolute paths should really have scheme == "" (local)
if os.name == "nt" and len(parsed.scheme) == 1 and parsed.netloc == "":
return schemas[""](data)
if parsed.scheme not in schemas:
raise Invalid(f"Unsupported URL type {parsed.scheme}://")
return schemas[parsed.scheme](data)
return validate
class RelPath(str):
class NoRemoteError(RemoteConfigError):
pass
REMOTE_COMMON = {
"url": str,
"checksum_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,
}
HTTP_COMMON = {
"auth": All(Lower, Choices("basic", "digest", "custom")),
"custom_auth_header": str,
"user": str,
"password": str,
"ask_password": Bool,
"ssl_verify": Bool,
"method": str,
}
WEBDAV_COMMON = {
"user": str,
"password": str,
"ask_password": Bool,
"token": str,
"cert_path": str,
"key_path": str,
"timeout": Coerce(int),
}
class RemoteNotFoundError(RemoteConfigError):
pass
SCHEMA = {
"core": {
"remote": Lower,
"checksum_jobs": All(Coerce(int), Range(1)),
Optional("interactive", default=False): Bool,
Optional("analytics", default=True): Bool,
Optional("hardlink_lock", default=False): Bool,
Optional("no_scm", default=False): Bool,
Optional("experiments", default=False): Bool,
Optional("check_update", default=True): Bool,
},
"cache": {
"local": str,
"s3": str,
"gs": str,
"hdfs": str,
"ssh": str,
"azure": str,
# This is for default local cache
"dir": str,
**LOCAL_COMMON,
},
"remote": {
str: ByUrl(
{
"": {**LOCAL_COMMON, **REMOTE_COMMON},
"s3": {
"region": str,
"profile": str,
"credentialpath": str,
"endpointurl": str,
"access_key_id": str,
"secret_access_key": str,
Optional("listobjects", default=False): Bool, # obsoleted
Optional("use_ssl", default=True): Bool,
"sse": str,
"sse_kms_key_id": str,
"acl": str,
"grant_read": str,
"grant_read_acp": str,
"grant_write_acp": str,
"grant_full_control": str,
**REMOTE_COMMON,
},
"gs": {
"projectname": str,
"credentialpath": str,
**REMOTE_COMMON,
},
"ssh": {
"type": supported_cache_type,
"port": Coerce(int),
"user": str,
"password": str,
"ask_password": Bool,
"keyfile": str,
"timeout": Coerce(int),
"gss_auth": Bool,
**REMOTE_COMMON,
},
"hdfs": {"user": str, **REMOTE_COMMON},
"azure": {"connection_string": str, **REMOTE_COMMON},
"oss": {
"oss_key_id": str,
"oss_key_secret": str,
"oss_endpoint": str,
**REMOTE_COMMON,
},
"gdrive": {
"gdrive_use_service_account": Bool,
"gdrive_client_id": str,
"gdrive_client_secret": str,
"gdrive_user_credentials_file": str,
"gdrive_service_account_email": str,
"gdrive_service_account_user_email": str,
"gdrive_service_account_p12_file_path": str,
Optional("gdrive_trash_only", default=False): Bool,
**REMOTE_COMMON,
},
"http": {**HTTP_COMMON, **REMOTE_COMMON},
"https": {**HTTP_COMMON, **REMOTE_COMMON},
"webdav": {**WEBDAV_COMMON, **REMOTE_COMMON},
"webdavs": {**WEBDAV_COMMON, **REMOTE_COMMON},
"remote": {str: object}, # Any of the above options are valid
}
)
},
"state": {
"row_limit": All(Coerce(int), Range(1)),
"row_cleanup_quota": All(Coerce(int), Range(0, 100)),
},
}
COMPILED_SCHEMA = Schema(SCHEMA)
@memoize
def get_compiled_schema():
from voluptuous import Schema
from .config_schema import SCHEMA
return Schema(SCHEMA)
def to_bool(value):
from .config_schema import Bool
return Bool(value)
class Config(dict):
@@ -250,9 +71,9 @@ class Config(dict):
CONFIG_LOCAL = "config.local"
def __init__(
self, dvc_dir=None, validate=True, tree=None,
self, dvc_dir=None, validate=True, fs=None, config=None
): # pylint: disable=super-init-not-called
from dvc.tree.local import LocalTree
from dvc.fs.local import LocalFileSystem
self.dvc_dir = dvc_dir
@@ -266,10 +87,10 @@ class Config(dict):
else:
self.dvc_dir = os.path.abspath(os.path.realpath(dvc_dir))
self.wtree = LocalTree(None, {"url": self.dvc_dir})
self.tree = tree or self.wtree
self.wfs = LocalFileSystem(url=self.dvc_dir)
self.fs = fs or self.wfs
self.load(validate=validate)
self.load(validate=validate, config=config)
@classmethod
def get_dir(cls, level):
@@ -309,7 +130,7 @@ class Config(dict):
open(config_file, "w+").close()
return Config(dvc_dir)
def load(self, validate=True):
def load(self, validate=True, config=None):
"""Loads config from all the config files.
Raises:
@@ -317,6 +138,9 @@ class Config(dict):
"""
conf = self.load_config_to_level()
if config is not None:
merge(conf, config)
if validate:
conf = self.validate(conf)
@@ -327,32 +151,36 @@ class Config(dict):
if not self["cache"].get("dir") and self.dvc_dir:
self["cache"]["dir"] = os.path.join(self.dvc_dir, "cache")
def _get_tree(self, level):
# NOTE: this might be a GitTree, which doesn't see things outside of
def _get_fs(self, level):
# NOTE: this might be a Gitfs, which doesn't see things outside of
# the repo.
return self.tree if level == "repo" else self.wtree
return self.fs if level == "repo" else self.wfs
def _load_config(self, level):
filename = self.files[level]
tree = self._get_tree(level)
from configobj import ConfigObj
if tree.exists(filename, use_dvcignore=False):
with tree.open(filename) as fobj:
conf_obj = configobj.ConfigObj(fobj)
filename = self.files[level]
fs = self._get_fs(level)
if fs.exists(filename):
with fs.open(filename) as fobj:
conf_obj = ConfigObj(fobj)
else:
conf_obj = configobj.ConfigObj()
conf_obj = ConfigObj()
return _parse_remotes(_lower_keys(conf_obj.dict()))
def _save_config(self, level, conf_dict):
from configobj import ConfigObj
filename = self.files[level]
tree = self._get_tree(level)
fs = self._get_fs(level)
logger.debug(f"Writing '{filename}'.")
tree.makedirs(os.path.dirname(filename))
fs.makedirs(os.path.dirname(filename))
config = configobj.ConfigObj(_pack_remotes(conf_dict))
with tree.open(filename, "wb") as fobj:
config = ConfigObj(_pack_remotes(conf_dict))
with fs.open(filename, "wb") as fobj:
config.write(fobj)
config.filename = filename
@@ -361,7 +189,7 @@ class Config(dict):
conf = self._load_paths(conf, self.files[level])
# Auto-verify sections
for key in COMPILED_SCHEMA.schema:
for key in get_compiled_schema().schema:
conf.setdefault(key, {})
return conf
@@ -371,14 +199,26 @@ class Config(dict):
abs_conf_dir = os.path.abspath(os.path.dirname(filename))
def resolve(path):
from .config_schema import RelPath
if os.path.isabs(path) or re.match(r"\w+://", path):
return path
# on windows convert slashes to backslashes
# to have path compatible with abs_conf_dir
if os.path.sep == "\\" and "/" in path:
path = path.replace("/", "\\")
return RelPath(os.path.join(abs_conf_dir, path))
return Config._map_dirs(conf, resolve)
@staticmethod
def _to_relpath(conf_dir, path):
from dvc.utils import relpath
from .config_schema import RelPath
if re.match(r"\w+://", path):
return path
@@ -396,10 +236,20 @@ class Config(dict):
@staticmethod
def _map_dirs(conf, func):
from voluptuous import ALLOW_EXTRA, Schema
dirs_schema = {
"cache": {"dir": func},
"remote": {
str: {"url": func, "gdrive_user_credentials_file": func}
str: {
"url": func,
"gdrive_user_credentials_file": func,
"gdrive_service_account_json_file_path": func,
"credentialpath": func,
"keyfile": func,
"cert_path": func,
"key_path": func,
}
},
}
return Schema(dirs_schema, extra=ALLOW_EXTRA)(conf)
@@ -413,8 +263,16 @@ class Config(dict):
merge(merged_conf, self.load_one(merge_level))
return merged_conf
def read(self, level=None):
# NOTE: we read from a merged config by default, same as git config
if level is None:
return self.load_config_to_level()
return self.load_one(level)
@contextmanager
def edit(self, level="repo"):
def edit(self, level=None, validate=True):
# NOTE: we write to repo config by default, same as git config
level = level or "repo"
if level in {"repo", "local"} and self.dvc_dir is None:
raise ConfigError("Not inside a DVC repo")
@@ -425,15 +283,19 @@ class Config(dict):
merged_conf = self.load_config_to_level(level)
merge(merged_conf, conf)
self.validate(merged_conf)
if validate:
self.validate(merged_conf)
self._save_config(level, conf)
self.load()
self.load(validate=validate)
@staticmethod
def validate(data):
from voluptuous import Invalid
try:
return COMPILED_SCHEMA(data)
return get_compiled_schema()(data)
except Invalid as exc:
raise ConfigError(str(exc)) from None
+233
View File
@@ -0,0 +1,233 @@
import os
from urllib.parse import urlparse
from funcy import walk_values
from voluptuous import (
All,
Any,
Coerce,
Invalid,
Lower,
Optional,
Range,
Schema,
)
Bool = All(
Lower,
Any("true", "false"),
lambda v: v == "true",
msg="expected true or false",
)
def supported_cache_type(types):
"""Checks if link type config option consists only of valid values.
Args:
types (list/string): type(s) of links that dvc should try out.
"""
if types is None:
return None
if isinstance(types, str):
types = [typ.strip() for typ in types.split(",")]
unsupported = set(types) - {"reflink", "hardlink", "symlink", "copy"}
if unsupported:
raise Invalid(
"Unsupported cache type(s): {}".format(", ".join(unsupported))
)
return types
def Choices(*choices):
"""Checks that value belongs to the specified set of values
Args:
*choices: pass allowed values as arguments, or pass a list or
tuple as a single argument
"""
return Any(*choices, msg="expected one of {}".format(", ".join(choices)))
def ByUrl(mapping):
schemas = walk_values(Schema, mapping)
def validate(data):
if "url" not in data:
raise Invalid("expected 'url'")
parsed = urlparse(data["url"])
# Windows absolute paths should really have scheme == "" (local)
if os.name == "nt" and len(parsed.scheme) == 1 and parsed.netloc == "":
return schemas[""](data)
if parsed.scheme not in schemas:
raise Invalid(f"Unsupported URL type {parsed.scheme}://")
return schemas[parsed.scheme](data)
return validate
class RelPath(str):
pass
REMOTE_COMMON = {
"url": str,
"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,
}
HTTP_COMMON = {
"auth": All(Lower, Choices("basic", "digest", "custom")),
"custom_auth_header": str,
"user": str,
"password": str,
"ask_password": Bool,
"ssl_verify": Any(Bool, str),
"method": str,
}
WEBDAV_COMMON = {
"user": str,
"password": str,
"ask_password": Bool,
"token": str,
"cert_path": str,
"key_path": str,
"timeout": Coerce(int),
"ssl_verify": Any(Bool, str),
}
SCHEMA = {
"core": {
"remote": Lower,
"checksum_jobs": All(Coerce(int), Range(1)),
Optional("interactive", default=False): Bool,
Optional("analytics", default=True): Bool,
Optional("hardlink_lock", default=False): Bool,
Optional("no_scm", default=False): Bool,
Optional("autostage", default=False): Bool,
Optional("experiments"): Bool, # obsoleted
Optional("check_update", default=True): Bool,
},
"cache": {
"local": str,
"s3": str,
"gs": str,
"hdfs": str,
"webhdfs": str,
"ssh": str,
"azure": str,
# This is for default local cache
"dir": str,
**LOCAL_COMMON,
},
"remote": {
str: ByUrl(
{
"": {**LOCAL_COMMON, **REMOTE_COMMON},
"s3": {
"region": str,
"profile": str,
"credentialpath": str,
"configpath": str,
"endpointurl": str,
"access_key_id": str,
"secret_access_key": str,
"session_token": str,
Optional("listobjects", default=False): Bool, # obsoleted
Optional("use_ssl", default=True): Bool,
"ssl_verify": Any(Bool, str),
"sse": str,
"sse_kms_key_id": str,
"acl": str,
"grant_read": str,
"grant_read_acp": str,
"grant_write_acp": str,
"grant_full_control": str,
**REMOTE_COMMON,
},
"gs": {
"projectname": str,
"credentialpath": str,
**REMOTE_COMMON,
},
"ssh": {
"type": supported_cache_type,
"port": Coerce(int),
"user": str,
"password": str,
"ask_password": Bool,
"keyfile": str,
"timeout": Coerce(int),
"gss_auth": Bool,
"allow_agent": Bool,
**REMOTE_COMMON,
},
"hdfs": {"user": str, "kerb_ticket": str, **REMOTE_COMMON},
"webhdfs": {
"hdfscli_config": str,
"webhdfs_token": str,
"user": str,
"webhdfs_alias": str,
**REMOTE_COMMON,
},
"azure": {
"connection_string": str,
"sas_token": str,
"account_name": str,
"account_key": str,
"tenant_id": str,
"client_id": str,
"client_secret": str,
"allow_anonymous_login": Bool,
"exclude_environment_credential": Bool,
"exclude_visual_studio_code_credential": Bool,
"exclude_shared_token_cache_credential": Bool,
"exclude_managed_identity_credential": Bool,
**REMOTE_COMMON,
},
"oss": {
"oss_key_id": str,
"oss_key_secret": str,
"oss_endpoint": str,
**REMOTE_COMMON,
},
"gdrive": {
"gdrive_use_service_account": Bool,
"gdrive_client_id": str,
"gdrive_client_secret": str,
"gdrive_user_credentials_file": str,
"gdrive_service_account_user_email": str,
"gdrive_service_account_json_file_path": str,
Optional("gdrive_trash_only", default=False): Bool,
**REMOTE_COMMON,
},
"http": {**HTTP_COMMON, **REMOTE_COMMON},
"https": {**HTTP_COMMON, **REMOTE_COMMON},
"webdav": {**WEBDAV_COMMON, **REMOTE_COMMON},
"webdavs": {**WEBDAV_COMMON, **REMOTE_COMMON},
"remote": {str: object}, # Any of the above options are valid
}
)
},
"state": {
"row_limit": All(Coerce(int), Range(1)), # obsoleted
"row_cleanup_quota": All(Coerce(int), Range(0, 100)), # obsoleted
},
# section for experimental features
"feature": {
# enabled by default. It's of no use, kept for backward compatibility.
Optional("parametrization", default=True): Bool
},
"plots": {"html_template": str},
}
+1 -1
View File
@@ -33,7 +33,7 @@ def _spawn_windows(cmd, env):
startupinfo.dwFlags |= STARTF_USESHOWWINDOW
_popen(
cmd, env=env, creationflags=creationflags, startupinfo=startupinfo,
cmd, env=env, creationflags=creationflags, startupinfo=startupinfo
).communicate()
+10 -48
View File
@@ -3,52 +3,14 @@
import logging
import math
import os
import pydoc
import sys
from grandalf.graphs import Edge, Graph, Vertex
from grandalf.layouts import SugiyamaLayout
from grandalf.routing import EdgeViewer, route_with_lines
from dvc.env import DVC_PAGER
from dvc.utils import format_link
logger = logging.getLogger(__name__)
DEFAULT_PAGER = "less"
DEFAULT_PAGER_FORMATTED = "{} --chop-long-lines --clear-screen".format(
DEFAULT_PAGER
)
def make_pager(cmd):
def pager(text):
return pydoc.tempfilepager(pydoc.plain(text), cmd)
return pager
def find_pager():
if not sys.stdout.isatty():
return pydoc.plainpager
env_pager = os.getenv(DVC_PAGER)
if env_pager:
return make_pager(env_pager)
if os.system(f"({DEFAULT_PAGER}) 2>{os.devnull}") == 0:
return make_pager(DEFAULT_PAGER_FORMATTED)
logger.warning(
"Unable to find `less` in the PATH. Check out "
"{} for more info.".format(
format_link("https://man.dvc.org/pipeline/show")
)
)
return pydoc.plainpager
class VertexViewer:
"""Class to define vertex box boundaries that will be accounted for during
graph building by grandalf.
@@ -201,7 +163,7 @@ class AsciiCanvas:
self.point(x0 + width, y0 + height, "+")
def _build_sugiyama_layout(vertexes, edges):
def _build_sugiyama_layout(vertices, edges):
#
# Just a reminder about naming conventions:
# +------------X
@@ -212,17 +174,17 @@ def _build_sugiyama_layout(vertexes, edges):
# Y
#
vertexes = {v: Vertex(f" {v} ") for v in vertexes}
vertices = {v: Vertex(f" {v} ") for v in vertices}
# NOTE: reverting edges to correctly orientate the graph
edges = [Edge(vertexes[e], vertexes[s]) for s, e in edges]
vertexes = vertexes.values()
graph = Graph(vertexes, edges)
edges = [Edge(vertices[e], vertices[s]) for s, e in edges]
vertices = vertices.values()
graph = Graph(vertices, edges)
for vertex in vertexes:
for vertex in vertices:
vertex.view = VertexViewer(vertex.data)
# NOTE: determine min box length to create the best layout
minw = min(v.view.w for v in vertexes)
minw = min(v.view.w for v in vertices)
for edge in edges:
edge.view = EdgeViewer()
@@ -242,11 +204,11 @@ def _build_sugiyama_layout(vertexes, edges):
return sug
def draw(vertexes, edges):
def draw(vertices, edges):
"""Build a DAG and draw it in ASCII.
Args:
vertexes (list): list of graph vertexes.
vertices (list): list of graph vertices.
edges (list): list of graph edges.
"""
# pylint: disable=too-many-locals
@@ -255,7 +217,7 @@ def draw(vertexes, edges):
Xs = [] # pylint: disable=invalid-name
Ys = [] # pylint: disable=invalid-name
sug = _build_sugiyama_layout(vertexes, edges)
sug = _build_sugiyama_layout(vertices, edges)
for vertex in sug.g.sV:
# NOTE: moving boxes w/2 to the left
+82 -42
View File
@@ -1,9 +1,13 @@
"""Manages dvc remotes that user can use with push/pull/status commands."""
import logging
from typing import TYPE_CHECKING, Iterable, Optional
from dvc.config import NoRemoteError
from dvc.remote import get_remote
from dvc.objects.db import get_index
if TYPE_CHECKING:
from dvc.hash_info import HashInfo
from dvc.objects.db.base import ObjectDB
logger = logging.getLogger(__name__)
@@ -22,12 +26,18 @@ class DataCloud:
def __init__(self, repo):
self.repo = repo
def get_remote(self, name=None, command="<command>"):
def get_remote_odb(
self,
name: Optional[str] = None,
command: str = "<command>",
) -> "ObjectDB":
from dvc.config import NoRemoteError
if not name:
name = self.repo.config["core"].get("remote")
if name:
return self._init_remote(name)
return self._init_odb(name)
if bool(self.repo.config["remote"]):
error_msg = (
@@ -44,77 +54,107 @@ class DataCloud:
raise NoRemoteError(error_msg)
def _init_remote(self, name):
return get_remote(self.repo, name=name)
def _init_odb(self, name):
from dvc.fs import get_cloud_fs
from dvc.objects.db import get_odb
cls, config, path_info = get_cloud_fs(self.repo, name=name)
config["tmp_dir"] = self.repo.tmp_dir
return get_odb(cls(**config), path_info, **config)
def push(
self, cache, jobs=None, remote=None, show_checksums=False,
self,
objs: Iterable["HashInfo"],
jobs: Optional[int] = None,
remote: Optional[str] = None,
odb: Optional["ObjectDB"] = None,
):
"""Push data items in a cloud-agnostic way.
Args:
cache (NamedCache): named checksums to push to the cloud.
jobs (int): number of jobs that can be running simultaneously.
remote (dvc.remote.base.BaseRemote): optional remote to push to.
objs: objects to push to the cloud.
jobs: number of jobs that can be running simultaneously.
remote: optional name of remote to push to.
By default remote from core.remote config option is used.
show_checksums (bool): show checksums instead of file names in
information messages.
odb: optional ODB to push to. Overrides remote.
"""
remote = self.get_remote(remote, "push")
from dvc.objects.transfer import transfer
return self.repo.cache.local.push(
cache, jobs=jobs, remote=remote, show_checksums=show_checksums,
if not odb:
odb = self.get_remote_odb(remote, "push")
return transfer(
self.repo.odb.local,
odb,
objs,
jobs=jobs,
dest_index=get_index(odb),
cache_odb=self.repo.odb.local,
)
def pull(
self, cache, jobs=None, remote=None, show_checksums=False,
self,
objs: Iterable["HashInfo"],
jobs: Optional[int] = None,
remote: Optional[str] = None,
odb: Optional["ObjectDB"] = None,
):
"""Pull data items in a cloud-agnostic way.
Args:
cache (NamedCache): named checksums to pull from the cloud.
jobs (int): number of jobs that can be running simultaneously.
remote (dvc.remote.base.BaseRemote): optional remote to pull from.
objs: objects to pull from the cloud.
jobs: number of jobs that can be running simultaneously.
remote: optional name of remote to pull from.
By default remote from core.remote config option is used.
show_checksums (bool): show checksums instead of file names in
information messages.
odb: optional ODB to pull from. Overrides remote.
"""
remote = self.get_remote(remote, "pull")
from dvc.objects.transfer import transfer
return self.repo.cache.local.pull(
cache, jobs=jobs, remote=remote, show_checksums=show_checksums
if not odb:
odb = self.get_remote_odb(remote, "pull")
return transfer(
odb,
self.repo.odb.local,
objs,
jobs=jobs,
src_index=get_index(odb),
cache_odb=self.repo.odb.local,
verify=odb.verify,
)
def status(
self,
cache,
jobs=None,
remote=None,
show_checksums=False,
log_missing=True,
objs: Iterable["HashInfo"],
jobs: Optional[int] = None,
remote: Optional[str] = None,
odb: Optional["ObjectDB"] = None,
log_missing: bool = True,
):
"""Check status of data items in a cloud-agnostic way.
Args:
cache (NamedCache): named checksums to check status for.
jobs (int): number of jobs that can be running simultaneously.
remote (dvc.remote.base.BaseRemote): optional remote to compare
objs: objects to check status for.
jobs: number of jobs that can be running simultaneously.
remote: optional remote to compare
cache to. By default remote from core.remote config option
is used.
show_checksums (bool): show checksums instead of file names in
information messages.
log_missing (bool): log warning messages if file doesn't exist
odb: optional ODB to check status from. Overrides remote.
log_missing: log warning messages if file doesn't exist
neither in cache, neither in cloud.
"""
remote = self.get_remote(remote, "status")
return self.repo.cache.local.status(
cache,
from dvc.objects.status import compare_status
if not odb:
odb = self.get_remote_odb(remote, "status")
return compare_status(
self.repo.odb.local,
odb,
objs,
jobs=jobs,
remote=remote,
show_checksums=show_checksums,
log_missing=log_missing,
dest_index=get_index(odb),
cache_odb=self.repo.odb.local,
)
def get_url_for(self, remote, checksum):
remote = self.get_remote(remote)
return str(remote.tree.hash_to_path_info(checksum))
remote_odb = self.get_remote_odb(remote)
return str(remote_odb.hash_to_path_info(checksum))
+12 -53
View File
@@ -1,62 +1,24 @@
from collections import defaultdict
from urllib.parse import urlparse
from typing import Any, Mapping
import dvc.output as output
from dvc.dependency.azure import AzureDependency
from dvc.dependency.gs import GSDependency
from dvc.dependency.hdfs import HDFSDependency
from dvc.dependency.http import HTTPDependency
from dvc.dependency.https import HTTPSDependency
from dvc.dependency.local import LocalDependency
from dvc.dependency.param import ParamsDependency
from dvc.dependency.s3 import S3Dependency
from dvc.dependency.ssh import SSHDependency
from dvc.output.base import BaseOutput
from dvc.scheme import Schemes
from dvc.output import ARTIFACT_SCHEMA, Output
from ..tree import get_cloud_tree
from .base import Dependency
from .param import ParamsDependency
from .repo import RepoDependency
DEPS = [
AzureDependency,
GSDependency,
HDFSDependency,
HTTPDependency,
HTTPSDependency,
S3Dependency,
SSHDependency,
# NOTE: LocalDependency is the default choice
]
DEP_MAP = {
Schemes.LOCAL: LocalDependency,
Schemes.SSH: SSHDependency,
Schemes.S3: S3Dependency,
Schemes.AZURE: AzureDependency,
Schemes.GS: GSDependency,
Schemes.HDFS: HDFSDependency,
Schemes.HTTP: HTTPDependency,
Schemes.HTTPS: HTTPSDependency,
}
# NOTE: schema for dependencies is basically the same as for outputs, but
# without output-specific entries like 'cache' (whether or not output is
# cached, see -o and -O flags for `dvc run`) and 'metric' (whether or not
# output is a metric file and how to parse it, see `-M` flag for `dvc run`).
SCHEMA = output.SCHEMA.copy()
del SCHEMA[BaseOutput.PARAM_CACHE]
del SCHEMA[BaseOutput.PARAM_METRIC]
SCHEMA.update(RepoDependency.REPO_SCHEMA)
SCHEMA.update(ParamsDependency.PARAM_SCHEMA)
# output is a metrics file and how to parse it, see `-M` flag for `dvc run`).
SCHEMA: Mapping[str, Any] = {
**ARTIFACT_SCHEMA,
**RepoDependency.REPO_SCHEMA,
**ParamsDependency.PARAM_SCHEMA,
}
def _get(stage, p, info):
parsed = urlparse(p) if p else None
if parsed and parsed.scheme == "remote":
tree = get_cloud_tree(stage.repo, name=parsed.netloc)
return DEP_MAP[tree.scheme](stage, p, info, tree=tree)
if info and info.get(RepoDependency.PARAM_REPO):
repo = info.pop(RepoDependency.PARAM_REPO)
return RepoDependency(repo, stage, p, info)
@@ -65,16 +27,13 @@ def _get(stage, p, info):
params = info.pop(ParamsDependency.PARAM_PARAMS)
return ParamsDependency(stage, p, params)
for d in DEPS:
if d.supported(p):
return d(stage, p, info)
return LocalDependency(stage, p, info)
return Dependency(stage, p, info)
def loadd_from(stage, d_list):
ret = []
for d in d_list:
p = d.pop(BaseOutput.PARAM_PATH, None)
p = d.pop(Output.PARAM_PATH, None)
ret.append(_get(stage, p, d))
return ret
-8
View File
@@ -1,8 +0,0 @@
from dvc.dependency.base import BaseDependency
from dvc.output.base import BaseOutput
from ..tree.azure import AzureTree
class AzureDependency(BaseDependency, BaseOutput):
TREE_CLS = AzureTree
+10 -5
View File
@@ -1,4 +1,7 @@
from typing import Type
from dvc.exceptions import DvcException
from dvc.output import Output
class DependencyDoesNotExistError(DvcException):
@@ -15,15 +18,17 @@ class DependencyIsNotFileOrDirError(DvcException):
class DependencyIsStageFileError(DvcException):
def __init__(self, path):
super().__init__(f"Stage file '{path}' cannot be a dependency.")
super().__init__(f"DVC file '{path}' cannot be a dependency.")
class BaseDependency:
class Dependency(Output):
IS_DEPENDENCY = True
DoesNotExistError = DependencyDoesNotExistError
IsNotFileOrDirError = DependencyIsNotFileOrDirError
IsStageFileError = DependencyIsStageFileError
DoesNotExistError = DependencyDoesNotExistError # type: Type[DvcException]
IsNotFileOrDirError = (
DependencyIsNotFileOrDirError
) # type: Type[DvcException]
IsStageFileError = DependencyIsStageFileError # type: Type[DvcException]
def update(self, rev=None):
pass
-6
View File
@@ -1,6 +0,0 @@
from dvc.dependency.base import BaseDependency
from dvc.output.gs import GSOutput
class GSDependency(BaseDependency, GSOutput):
pass
-6
View File
@@ -1,6 +0,0 @@
from dvc.dependency.base import BaseDependency
from dvc.output.hdfs import HDFSOutput
class HDFSDependency(BaseDependency, HDFSOutput):
pass
-8
View File
@@ -1,8 +0,0 @@
from dvc.dependency.base import BaseDependency
from dvc.output.base import BaseOutput
from ..tree.http import HTTPTree
class HTTPDependency(BaseDependency, BaseOutput):
TREE_CLS = HTTPTree
-6
View File
@@ -1,6 +0,0 @@
from ..tree.https import HTTPSTree
from .http import HTTPDependency
class HTTPSDependency(HTTPDependency):
TREE_CLS = HTTPSTree
-6
View File
@@ -1,6 +0,0 @@
from dvc.dependency.base import BaseDependency
from dvc.output.local import LocalOutput
class LocalDependency(BaseDependency, LocalOutput):
pass
+21 -5
View File
@@ -4,11 +4,12 @@ from collections import defaultdict
import dpath.util
from voluptuous import Any
from dvc.dependency.local import LocalDependency
from dvc.exceptions import DvcException
from dvc.hash_info import HashInfo
from dvc.utils.serialize import LOADERS, ParseError
from .base import Dependency
class MissingParamsError(DvcException):
pass
@@ -18,7 +19,7 @@ class BadParamFileError(DvcException):
pass
class ParamsDependency(LocalDependency):
class ParamsDependency(Dependency):
PARAM_PARAMS = "params"
PARAM_SCHEMA = {PARAM_PARAMS: Any(dict, list, None)}
DEFAULT_PARAMS_FILE = "params.yaml"
@@ -84,19 +85,34 @@ class ParamsDependency(LocalDependency):
def status(self):
return self.workspace_status()
def read_params(self):
def _read(self):
if not self.exists:
return {}
suffix = self.path_info.suffix.lower()
loader = LOADERS[suffix]
try:
config = loader(self.path_info, tree=self.repo.tree)
return loader(self.path_info, fs=self.repo.fs)
except ParseError as exc:
raise BadParamFileError(
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:
@@ -112,7 +128,7 @@ class ParamsDependency(LocalDependency):
if missing_params:
raise MissingParamsError(
"Parameters '{}' are missing from '{}'.".format(
", ".join(missing_params), self,
", ".join(missing_params), self
)
)
+153 -34
View File
@@ -1,9 +1,21 @@
import os
from collections import defaultdict
from copy import copy
from typing import TYPE_CHECKING, Dict, Optional, Set, Tuple
from voluptuous import Required
from .local import LocalDependency
from dvc.path_info import PathInfo
from .base import Dependency
if TYPE_CHECKING:
from dvc.hash_info import HashInfo
from dvc.objects.db.base import ObjectDB
from dvc.objects.file import HashFile
class RepoDependency(LocalDependency):
class RepoDependency(Dependency):
PARAM_REPO = "repo"
PARAM_URL = "url"
PARAM_REV = "rev"
@@ -19,40 +31,22 @@ class RepoDependency(LocalDependency):
def __init__(self, def_repo, stage, *args, **kwargs):
self.def_repo = def_repo
self._staged_objs: Dict[str, "HashFile"] = {}
super().__init__(stage, *args, **kwargs)
def _parse_path(self, tree, path):
def _parse_path(self, fs, path_info):
return None
@property
def is_in_repo(self):
return False
@property
def repo_pair(self):
d = self.def_repo
rev = d.get(self.PARAM_REV_LOCK) or d.get(self.PARAM_REV)
return d[self.PARAM_URL], rev
def __str__(self):
return "{} ({})".format(self.def_path, self.def_repo[self.PARAM_URL])
def _make_repo(self, *, locked=True, **kwargs):
from dvc.external_repo import external_repo
d = self.def_repo
rev = (d.get("rev_lock") if locked else None) or d.get("rev")
return external_repo(d["url"], rev=rev, **kwargs)
def _get_hash(self, locked=True):
# we want stream but not fetch, so DVC out directories are
# walked, but dir contents is not fetched
with self._make_repo(locked=locked, fetch=False, stream=True) as repo:
return repo.get_checksum(self.def_path)
return f"{self.def_path} ({self.def_repo[self.PARAM_URL]})"
def workspace_status(self):
current = self._get_hash(locked=True)
updated = self._get_hash(locked=False)
current = self.get_obj(locked=True).hash_info
updated = self.get_obj(locked=False).hash_info
if current != updated:
return {str(self): "update available"}
@@ -68,21 +62,25 @@ class RepoDependency(LocalDependency):
def dumpd(self):
return {self.PARAM_PATH: self.def_path, self.PARAM_REPO: self.def_repo}
def download(self, to):
cache = self.repo.cache.local
def download(self, to, jobs=None):
from dvc.checkout import checkout
with self._make_repo(cache_dir=cache.cache_dir) as repo:
if self.def_repo.get(self.PARAM_REV_LOCK) is None:
self.def_repo[self.PARAM_REV_LOCK] = repo.get_rev()
for odb, objs in self.get_used_objs().items():
self.repo.cloud.pull(objs, jobs=jobs, odb=odb)
_, _, cache_infos = repo.fetch_external([self.def_path])
cache.checkout(to.path_info, cache_infos[0])
obj = self.get_obj()
checkout(
to.path_info,
to.fs,
obj,
self.repo.odb.local,
dvcignore=None,
state=self.repo.state,
)
def update(self, rev=None):
if rev:
self.def_repo[self.PARAM_REV] = rev
with self._make_repo(locked=False) as repo:
self.def_repo[self.PARAM_REV_LOCK] = repo.get_rev()
@@ -91,3 +89,124 @@ class RepoDependency(LocalDependency):
# origin project url and rev_lock, and it makes RepoDependency
# immutable, hence its impossible for checksum to change.
return False
def get_used_objs(
self, **kwargs
) -> Dict[Optional["ObjectDB"], Set["HashInfo"]]:
used, _ = self._get_used_and_obj(**kwargs)
return used
def _get_used_and_obj(
self, obj_only=False, **kwargs
) -> Tuple[Dict[Optional["ObjectDB"], Set["HashInfo"]], "HashFile"]:
from dvc.config import NoRemoteError
from dvc.exceptions import NoOutputOrStageError, PathMissingError
from dvc.objects.stage import stage
from dvc.objects.tree import Tree
local_odb = self.repo.odb.local
locked = kwargs.pop("locked", True)
with self._make_repo(
locked=locked, cache_dir=local_odb.cache_dir
) as repo:
used_obj_ids = defaultdict(set)
rev = repo.get_rev()
if locked and self.def_repo.get(self.PARAM_REV_LOCK) is None:
self.def_repo[self.PARAM_REV_LOCK] = rev
path_info = PathInfo(repo.root_dir) / str(self.def_path)
if not obj_only:
try:
for odb, obj_ids in repo.used_objs(
[os.fspath(path_info)],
force=True,
jobs=kwargs.get("jobs"),
recursive=True,
).items():
if odb is None:
odb = repo.cloud.get_remote_odb()
odb.read_only = True
self._check_circular_import(odb, obj_ids)
used_obj_ids[odb].update(obj_ids)
except (NoRemoteError, NoOutputOrStageError):
pass
try:
staging, staged_obj = stage(
local_odb,
path_info,
repo.repo_fs,
local_odb.fs.PARAM_CHECKSUM,
)
except FileNotFoundError as exc:
raise PathMissingError(
self.def_path, self.def_repo[self.PARAM_URL]
) from exc
staging = copy(staging)
staging.read_only = True
self._staged_objs[rev] = staged_obj
used_obj_ids[staging].add(staged_obj.hash_info)
if isinstance(staged_obj, Tree):
used_obj_ids[staging].update(
entry.hash_info for _, entry in staged_obj
)
return used_obj_ids, staged_obj
def _check_circular_import(self, odb, obj_ids):
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
def iter_objs():
for hash_info in obj_ids:
if hash_info.isdir:
tree = Tree.load(odb, hash_info)
yield from (odb.get(entry.hash_info) for _, entry in tree)
else:
yield odb.get(hash_info)
checked_urls = set()
for obj in iter_objs():
if not isinstance(obj.fs, RepoFileSystem):
continue
if (
obj.fs.repo_url in checked_urls
or obj.fs.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
):
raise CircularImportError(self, obj.fs.repo_url, self_url)
checked_urls.update([obj.fs.repo_url, obj.fs.root_dir])
def get_obj(self, filter_info=None, **kwargs):
locked = kwargs.get("locked", True)
rev = self._get_rev(locked=locked)
if rev in self._staged_objs:
return self._staged_objs[rev]
_, obj = self._get_used_and_obj(
obj_only=True, filter_info=filter_info, **kwargs
)
return obj
def _make_repo(self, locked=True, **kwargs):
from dvc.external_repo import external_repo
d = self.def_repo
rev = self._get_rev(locked=locked)
return external_repo(d[self.PARAM_URL], rev=rev, **kwargs)
def _get_rev(self, locked=True):
d = self.def_repo
return (d.get(self.PARAM_REV_LOCK) if locked else None) or d.get(
self.PARAM_REV
)
-6
View File
@@ -1,6 +0,0 @@
from dvc.dependency.base import BaseDependency
from dvc.output.s3 import S3Output
class S3Dependency(BaseDependency, S3Output):
pass
-6
View File
@@ -1,6 +0,0 @@
from dvc.dependency.base import BaseDependency
from dvc.output.ssh import SSHOutput
class SSHDependency(BaseDependency, SSHOutput):
pass
+175 -55
View File
@@ -2,10 +2,12 @@ import collections
import contextlib
import logging
import os
from typing import TYPE_CHECKING, Any, Union
from voluptuous import MultipleInvalid
from dvc.exceptions import DvcException
from dvc.parsing.versions import LOCKFILE_VERSION, SCHEMA_KWD
from dvc.stage import serialize
from dvc.stage.exceptions import (
StageFileBadNameError,
@@ -13,17 +15,19 @@ from dvc.stage.exceptions import (
StageFileFormatError,
StageFileIsNotDvcFileError,
)
from dvc.stage.loader import SingleStageLoader, StageLoader
from dvc.types import AnyPath
from dvc.utils import relpath
from dvc.utils.collections import apply_diff
from dvc.utils.serialize import (
dump_yaml,
load_yaml,
modify_yaml,
parse_yaml,
parse_yaml_for_update,
)
if TYPE_CHECKING:
from dvc.repo import Repo
logger = logging.getLogger(__name__)
DVC_FILE = "Dvcfile"
@@ -32,10 +36,23 @@ PIPELINE_FILE = "dvc.yaml"
PIPELINE_LOCK = "dvc.lock"
class FileIsGitIgnored(DvcException):
def __init__(self, path, pipeline_file=False):
super().__init__(
"{}'{}' is git-ignored.".format(
"bad DVC file name " if pipeline_file else "", path
)
)
class LockfileCorruptedError(DvcException):
pass
class ParametrizedDumpError(DvcException):
pass
def is_valid_filename(path):
return path.endswith(DVC_FILE_SUFFIX) or os.path.basename(path) in [
DVC_FILE,
@@ -45,19 +62,38 @@ def is_valid_filename(path):
def is_dvc_file(path):
return os.path.isfile(path) and (
is_valid_filename(path) or os.path.basename(path) == PIPELINE_LOCK
is_valid_filename(path) or is_lock_file(path)
)
def check_dvc_filename(path):
def is_lock_file(path):
return os.path.basename(path) == PIPELINE_LOCK
def is_git_ignored(repo, path):
from dvc.fs.local import LocalFileSystem
from dvc.scm.base import NoSCMError
try:
return isinstance(repo.fs, LocalFileSystem) and repo.scm.is_ignored(
path
)
except NoSCMError:
return False
def check_dvcfile_path(repo, path):
if not is_valid_filename(path):
raise StageFileBadNameError(
"bad DVC-file name '{}'. DVC-files should be named "
"'Dvcfile' or have a '.dvc' suffix (e.g. '{}.dvc').".format(
relpath(path), os.path.basename(path)
"bad DVC file name '{}'. DVC files should be named "
"'{}' or have a '.dvc' suffix (e.g. '{}.dvc').".format(
relpath(path), PIPELINE_FILE, os.path.basename(path)
)
)
if is_git_ignored(repo, path):
raise FileIsGitIgnored(relpath(path), True)
class FileMixin:
SCHEMA = None
@@ -88,31 +124,47 @@ class FileMixin:
return relpath(self.path)
def exists(self):
return self.repo.tree.exists(self.path)
is_ignored = self.repo.dvcignore.is_ignored_file(self.path)
return self.repo.fs.exists(self.path) and not is_ignored
def _is_git_ignored(self):
return is_git_ignored(self.repo, self.path)
def _verify_filename(self):
if self.verify:
check_dvcfile_path(self.repo, self.path)
def _check_gitignored(self):
if self._is_git_ignored():
raise FileIsGitIgnored(self.path)
def _load(self):
# it raises the proper exceptions by priority:
# 1. when the file doesn't exists
# 2. filename is not a DVC-file
# 2. filename is not a DVC file
# 3. path doesn't represent a regular file
# 4. when the file is git ignored
if not self.exists():
raise StageFileDoesNotExistError(self.path)
if self.verify:
check_dvc_filename(self.path)
if not self.repo.tree.isfile(self.path):
dvc_ignored = self.repo.dvcignore.is_ignored_file(self.path)
raise StageFileDoesNotExistError(
self.path, dvc_ignored=dvc_ignored
)
self._verify_filename()
if not self.repo.fs.isfile(self.path):
raise StageFileIsNotDvcFileError(self.path)
with self.repo.tree.open(self.path) as fd:
self._check_gitignored()
with self.repo.fs.open(self.path, encoding="utf-8") as fd:
stage_text = fd.read()
d = parse_yaml(stage_text, self.path)
self.validate(d, self.relpath)
return d, stage_text
return self.validate(d, self.relpath), stage_text
@classmethod
def validate(cls, d, fname=None):
assert isinstance(cls.SCHEMA, collections.abc.Callable)
try:
cls.SCHEMA(d) # pylint: disable=not-callable
return cls.SCHEMA(d) # pylint: disable=not-callable
except MultipleInvalid as exc:
raise StageFileFormatError(f"'{fname}' format error: {exc}")
@@ -129,16 +181,17 @@ class FileMixin:
class SingleStageFile(FileMixin):
from dvc.schema import COMPILED_SINGLE_STAGE_SCHEMA as SCHEMA
from dvc.stage.loader import SingleStageLoader as LOADER
@property
def stage(self):
data, raw = self._load()
return SingleStageLoader.load_stage(self, data, raw)
return self.LOADER.load_stage(self, data, raw)
@property
def stages(self):
data, raw = self._load()
return SingleStageLoader(self, data, raw)
return self.LOADER(self, data, raw)
def dump(self, stage, **kwargs):
"""Dumps given stage appropriately in the dvcfile."""
@@ -146,10 +199,8 @@ class SingleStageFile(FileMixin):
assert not isinstance(stage, PipelineStage)
if self.verify:
check_dvc_filename(self.path)
logger.debug(
"Saving information to '{file}'.".format(file=relpath(self.path))
)
check_dvcfile_path(self.repo, self.path)
logger.debug(f"Saving information to '{relpath(self.path)}'.")
dump_yaml(self.path, serialize.to_single_stage_file(stage))
self.repo.scm.track_file(self.relpath)
@@ -169,6 +220,7 @@ class PipelineFile(FileMixin):
"""Abstraction for pipelines file, .yaml + .lock combined."""
from dvc.schema import COMPILED_MULTI_STAGE_SCHEMA as SCHEMA
from dvc.stage.loader import StageLoader as LOADER
@property
def _lockfile(self):
@@ -182,7 +234,7 @@ class PipelineFile(FileMixin):
assert isinstance(stage, PipelineStage)
if self.verify:
check_dvc_filename(self.path)
check_dvcfile_path(self.repo, self.path)
if update_pipeline and not stage.is_data_source:
self._dump_pipeline_file(stage)
@@ -193,10 +245,16 @@ class PipelineFile(FileMixin):
def _dump_lockfile(self, stage):
self._lockfile.dump(stage)
@staticmethod
def _check_if_parametrized(stage):
if stage.raw_data.parametrized:
raise ParametrizedDumpError(f"cannot dump a parametrized {stage}")
def _dump_pipeline_file(self, stage):
self._check_if_parametrized(stage)
stage_data = serialize.to_pipeline_file(stage)
with modify_yaml(self.path, tree=self.repo.tree) as data:
with modify_yaml(self.path, fs=self.repo.fs) as data:
if not data:
logger.info("Creating '%s'", self.relpath)
@@ -209,8 +267,6 @@ class PipelineFile(FileMixin):
if existing_entry:
orig_stage_data = data["stages"][stage.name]
if "meta" in orig_stage_data:
stage_data[stage.name]["meta"] = orig_stage_data["meta"]
apply_diff(stage_data[stage.name], orig_stage_data)
else:
data["stages"].update(stage_data)
@@ -227,7 +283,7 @@ class PipelineFile(FileMixin):
def stages(self):
data, _ = self._load()
lockfile_data = self._lockfile.load()
return StageLoader(self, data.get("stages", {}), lockfile_data)
return self.LOADER(self, data, lockfile_data)
def remove(self, force=False):
if not force:
@@ -242,7 +298,7 @@ class PipelineFile(FileMixin):
if not self.exists():
return
with open(self.path, "r") as f:
with open(self.path, encoding="utf-8") as f:
d = parse_yaml_for_update(f.read(), self.path)
self.validate(d, self.path)
@@ -261,35 +317,90 @@ class PipelineFile(FileMixin):
raise NotImplementedError
def get_lockfile_schema(d):
from dvc.schema import (
COMPILED_LOCKFILE_V1_SCHEMA,
COMPILED_LOCKFILE_V2_SCHEMA,
)
schema = {
LOCKFILE_VERSION.V1: COMPILED_LOCKFILE_V1_SCHEMA,
LOCKFILE_VERSION.V2: COMPILED_LOCKFILE_V2_SCHEMA,
}
version = LOCKFILE_VERSION.from_dict(d)
return schema[version]
def migrate_lock_v1_to_v2(d, version_info):
stages = {k: v for k, v in d.items()}
for key in stages:
d.pop(key)
# forcing order, meta should always be at the top
d.update(version_info)
d["stages"] = stages
class Lockfile(FileMixin):
from dvc.schema import COMPILED_LOCKFILE_SCHEMA as SCHEMA
def load(self):
if not self.exists():
return {}
data = load_yaml(self.path, tree=self.repo.tree)
@classmethod
def validate(cls, d, fname=None):
schema = get_lockfile_schema(d)
try:
self.validate(data, fname=self.relpath)
except StageFileFormatError:
return schema(d)
except MultipleInvalid as exc:
raise StageFileFormatError(f"'{fname}' format error: {exc}")
def _verify_filename(self):
pass # lockfile path is hardcoded, so no need to verify here
def _load(self):
try:
return super()._load()
except StageFileDoesNotExistError:
# we still need to account for git-ignored dvc.lock file
# even though it may not exist or have been .dvcignored
self._check_gitignored()
return {}, ""
except StageFileFormatError as exc:
raise LockfileCorruptedError(
f"Lockfile '{self.relpath}' is corrupted."
)
return data
) from exc
def load(self):
d, _ = self._load()
return d
@property
def latest_version_info(self):
version = LOCKFILE_VERSION.V2.value # pylint:disable=no-member
return {SCHEMA_KWD: version}
def dump(self, stage, **kwargs):
stage_data = serialize.to_lockfile(stage)
with modify_yaml(self.path, tree=self.repo.tree) as data:
if not data:
logger.info("Generating lock file '%s'", self.relpath)
with modify_yaml(self.path, fs=self.repo.fs) as data:
version = LOCKFILE_VERSION.from_dict(data)
if version == LOCKFILE_VERSION.V1:
logger.info(
"Migrating lock file '%s' from v1 to v2", self.relpath
)
migrate_lock_v1_to_v2(data, self.latest_version_info)
else:
if not data:
data.update(self.latest_version_info)
# order is important, meta should always be at the top
logger.info("Generating lock file '%s'", self.relpath)
modified = data.get(stage.name, {}) != stage_data.get(
data["stages"] = data.get("stages", {})
modified = data["stages"].get(stage.name, {}) != stage_data.get(
stage.name, {}
)
if modified:
logger.info("Updating lock file '%s'", self.relpath)
data.update(stage_data)
data["stages"].update(stage_data)
if modified:
self.repo.scm.track_file(self.relpath)
@@ -298,17 +409,19 @@ class Lockfile(FileMixin):
if not self.exists():
return
with open(self.path) as f:
with open(self.path, encoding="utf-8") as f:
d = parse_yaml_for_update(f.read(), self.path)
self.validate(d, self.path)
if stage.name not in d:
version = LOCKFILE_VERSION.from_dict(d)
data = d if version == LOCKFILE_VERSION.V1 else d.get("stages", {})
if stage.name not in data:
return
logger.debug("Removing '%s' from '%s'", stage.name, self.path)
del d[stage.name]
del data[stage.name]
if d:
if data:
dump_yaml(self.path, d)
else:
self.remove()
@@ -318,12 +431,19 @@ class Lockfile(FileMixin):
class Dvcfile:
def __new__(cls, repo, path, **kwargs):
def __new__(cls, repo: "Repo", path: AnyPath, **kwargs: Any):
assert path
assert repo
_, ext = os.path.splitext(path)
if ext in [".yaml", ".yml"]:
return PipelineFile(repo, path, **kwargs)
# fallback to single stage file for better error messages
return SingleStageFile(repo, path, **kwargs)
return make_dvcfile(repo, path, **kwargs)
DVCFile = Union["PipelineFile", "SingleStageFile"]
def make_dvcfile(repo: "Repo", path: AnyPath, **kwargs: Any) -> DVCFile:
_, ext = os.path.splitext(str(path))
if ext in [".yaml", ".yml"]:
return PipelineFile(repo, path, **kwargs)
# fallback to single stage file for better error messages
return SingleStageFile(repo, path, **kwargs)
+10
View File
@@ -1,2 +1,12 @@
DVC_CHECKPOINT = "DVC_CHECKPOINT"
DVC_DAEMON = "DVC_DAEMON"
DVC_PAGER = "DVC_PAGER"
DVC_ROOT = "DVC_ROOT"
DVCLIVE_PATH = "DVCLIVE_PATH"
DVCLIVE_SUMMARY = "DVCLIVE_SUMMARY"
DVCLIVE_HTML = "DVCLIVE_HTML"
DVCLIVE_RESUME = "DVCLIVE_RESUME"
DVC_IGNORE_ISATTY = "DVC_IGNORE_ISATTY"
DVC_EXP_GIT_REMOTE = "DVC_EXP_GIT_REMOTE"
DVC_EXP_AUTO_PUSH = "DVC_EXP_AUTO_PUSH"
DVC_NO_ANALYTICS = "DVC_NO_ANALYTICS"
+53 -27
View File
@@ -1,7 +1,4 @@
"""Exceptions raised by the dvc."""
from funcy import first
from dvc.utils import error_link, format_link, relpath
class DvcException(Exception):
@@ -9,6 +6,7 @@ class DvcException(Exception):
def __init__(self, msg, *args):
assert msg
self.msg = msg
super().__init__(msg, *args)
@@ -26,6 +24,8 @@ class OutputDuplicationError(DvcException):
"""
def __init__(self, output, stages):
from funcy import first
assert isinstance(output, str)
assert all(hasattr(stage, "relpath") for stage in stages)
if len(stages) == 1:
@@ -34,7 +34,7 @@ class OutputDuplicationError(DvcException):
)
else:
msg = "output '{}' is already specified in stages:\n{}".format(
output, "\n".join(f"\t- {s.addressing}" for s in stages),
output, "\n".join(f"\t- {s.addressing}" for s in stages)
)
super().__init__(msg)
self.stages = stages
@@ -42,17 +42,19 @@ class OutputDuplicationError(DvcException):
class OutputNotFoundError(DvcException):
"""Thrown if a file/directory not found in repository pipelines.
"""Thrown if a file/directory is not found as an output in any pipeline.
Args:
output (unicode): path to the file/directory.
"""
def __init__(self, output, repo=None):
from dvc.utils import relpath
self.output = output
self.repo = repo
super().__init__(
"Unable to find DVC-file with output '{path}'".format(
"Unable to find DVC file with output '{path}'".format(
path=relpath(self.output)
)
)
@@ -105,7 +107,7 @@ class ArgumentDuplicationError(DvcException):
class MoveNotDataSourceError(DvcException):
"""Thrown if attempted to move a file/directory that is not an output
"""Thrown when trying to move a file/directory that is not an output
in a data source stage.
Args:
@@ -164,24 +166,12 @@ class BadMetricError(DvcException):
def __init__(self, paths):
super().__init__(
"the following metrics do not exist, "
"are not metric files or are malformed: {paths}".format(
"are not metrics files or are malformed: {paths}".format(
paths=", ".join(f"'{path}'" for path in paths)
)
)
class NoMetricsError(DvcException):
pass
class NoPlotsError(DvcException):
def __init__(self):
super().__init__(
"no plots in this repository. Use `--plots/--plots-no-cache` "
"options for `dvc run` to mark stage outputs as plots."
)
class RecursiveAddingWhileUsingFilename(DvcException):
def __init__(self):
super().__init__(
@@ -228,6 +218,8 @@ class DvcIgnoreInCollectedDirError(DvcException):
class GitHookAlreadyExistsError(DvcException):
def __init__(self, hook_name):
from dvc.utils import format_link
super().__init__(
"Hook '{}' already exists. Please refer to {} for more "
"info.".format(
@@ -236,29 +228,34 @@ class GitHookAlreadyExistsError(DvcException):
)
class DownloadError(DvcException):
class FileTransferError(DvcException):
_METHOD = "transfer"
def __init__(self, amount):
self.amount = amount
super().__init__(f"{amount} files failed to download")
super().__init__(f"{amount} files failed to {self._METHOD}")
class UploadError(DvcException):
def __init__(self, amount):
self.amount = amount
class DownloadError(FileTransferError):
_METHOD = "download"
super().__init__(f"{amount} files failed to upload")
class UploadError(FileTransferError):
_METHOD = "upload"
class CheckoutError(DvcException):
def __init__(self, target_infos, stats=None):
from dvc.utils import error_link
self.target_infos = target_infos
self.stats = stats
targets = [str(t) for t in target_infos]
m = (
"Checkout failed for following targets:\n{}\nIs your "
"cache up to date?\n{}".format(
"\n".join(targets), error_link("missing-files"),
"\n".join(targets), error_link("missing-files")
)
)
super().__init__(m)
@@ -277,6 +274,8 @@ class NoRemoteInExternalRepoError(DvcException):
class NoOutputInExternalRepoError(DvcException):
def __init__(self, path, external_repo_path, external_repo_url):
from dvc.utils import relpath
super().__init__(
"Output '{}' not found in target repository '{}'".format(
relpath(path, external_repo_path), external_repo_url
@@ -307,6 +306,8 @@ class PathMissingError(DvcException):
class RemoteCacheRequiredError(DvcException):
def __init__(self, path_info):
from dvc.utils import format_link
super().__init__(
(
"Current operation was unsuccessful because '{}' requires "
@@ -338,3 +339,28 @@ class NoOutputOrStageError(DvcException):
class MergeError(DvcException):
pass
class CacheLinkError(DvcException):
from dvc.utils import format_link
SUPPORT_LINK = "See {} for more information.".format(
format_link(
"https://dvc.org/doc/user-guide/troubleshooting#cache-types"
)
)
def __init__(self, path_infos):
msg = "No possible cache link types for '{}'. {}".format(
", ".join([str(path) for path in path_infos]), self.SUPPORT_LINK
)
super().__init__(msg)
self.path_infos = path_infos
class CircularImportError(DvcException):
def __init__(self, dep, a, b):
super().__init__(
f"'{dep}' contains invalid circular import. "
f"DVC repo '{a}' already imports from '{b}'."
)
+84 -278
View File
@@ -3,40 +3,31 @@ import os
import tempfile
import threading
from contextlib import contextmanager
from distutils.dir_util import copy_tree
from typing import Iterable
from typing import Dict
from funcy import cached_property, reraise, retry, wrap_with
from funcy import retry, wrap_with
from dvc.cache import Cache
from dvc.config import NoRemoteError, NotDvcRepoError
from dvc.exceptions import (
DvcException,
FileMissingError,
NoOutputInExternalRepoError,
NoRemoteInExternalRepoError,
NotDvcRepoError,
OutputNotFoundError,
PathMissingError,
)
from dvc.path_info import PathInfo
from dvc.repo import Repo
from dvc.scm.base import CloneError
from dvc.scm.git import Git
from dvc.state import StateNoop
from dvc.tree.local import LocalTree
from dvc.tree.repo import RepoTree
from dvc.utils import relpath
from dvc.utils.fs import remove
logger = logging.getLogger(__name__)
class IsADVCRepoError(DvcException):
"""Raised when it is not expected to be a dvc repo."""
@contextmanager
def external_repo(url, rev=None, for_write=False, **kwargs):
def external_repo(
url, rev=None, for_write=False, cache_dir=None, cache_types=None, **kwargs
):
from dvc.config import NoRemoteError
from dvc.scm.git import Git
logger.debug("Creating external repo %s@%s", url, rev)
path = _cached_clone(url, rev, for_write=for_write)
# Local HEAD points to the tip of whatever branch we first cloned from
@@ -44,19 +35,31 @@ def external_repo(url, rev=None, for_write=False, **kwargs):
# the tip of the default branch
rev = rev or "refs/remotes/origin/HEAD"
cache_config = {
"cache": {"dir": cache_dir or _get_cache_dir(url), "type": cache_types}
}
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)
conf = dict(
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,
for_write=for_write,
config=config,
repo_factory=erepo_factory(url, cache_config),
**kwargs,
)
try:
repo = ExternalRepo(**conf)
except NotDvcRepoError:
repo = ExternalGitRepo(**conf)
if "subrepos" not in repo_kwargs:
repo_kwargs["subrepos"] = True
if "uninitialized" not in repo_kwargs:
repo_kwargs["uninitialized"] = True
repo = Repo(**repo_kwargs)
try:
yield repo
@@ -76,8 +79,29 @@ def external_repo(url, rev=None, for_write=False, **kwargs):
_remove(path)
CLONES = {}
CACHE_DIRS = {}
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)
repo_path = os.path.join(url, rel)
_config.update(_get_remote_config(repo_path))
return Repo(path, config=_config, **_kwargs)
return make_repo
CLONES: Dict[str, str] = {}
CACHE_DIRS: Dict[str, str] = {}
@wrap_with(threading.Lock())
def _get_cache_dir(url):
try:
cache_dir = CACHE_DIRS[url]
except KeyError:
cache_dir = CACHE_DIRS[url] = tempfile.mkdtemp("dvc-cache")
return cache_dir
def clean_repos():
@@ -90,256 +114,28 @@ def clean_repos():
_remove(path)
class BaseExternalRepo:
# pylint: disable=no-member
def _get_remote_config(url):
try:
repo = Repo(url)
except NotDvcRepoError:
return {}
def __str__(self):
return self.url
try:
name = repo.config["core"].get("remote")
if not name:
# Fill the empty upstream entry with a new remote pointing to the
# original repo's cache location.
name = "auto-generated-upstream"
return {
"core": {"remote": name},
"remote": {name: {"url": repo.odb.local.cache_dir}},
}
@cached_property
def repo_tree(self):
return self._get_tree_for(
self, subrepos=not self.for_write, repo_factory=self.make_repo
)
def get_rev(self):
assert self.scm
if isinstance(self.tree, LocalTree):
return self.scm.get_rev()
return self.tree.rev
def _fetch_to_cache(self, path_info, repo, callback):
# don't support subrepo traversal as it might fail due to difference
# in remotes
tree = self._get_tree_for(repo)
cache = repo.cache.local
hash_info = tree.get_hash(path_info, download_callback=callback)
cache.save(
path_info,
tree,
hash_info,
save_link=False,
download_callback=callback,
)
return hash_info
def fetch_external(self, paths: Iterable, **kwargs):
"""Fetch specified external repo paths into cache.
Returns 3-tuple in the form
(downloaded, failed, list(cache_infos))
where cache_infos can be used as checkout targets for the
fetched paths.
"""
download_results = []
failed = 0
root = PathInfo(self.root_dir)
paths = [root / path for path in paths]
def download_update(result):
download_results.append(result)
hash_infos = []
for path in paths:
with reraise(FileNotFoundError, PathMissingError(path, self.url)):
metadata = self.repo_tree.metadata(path)
self._check_repo(path, metadata.repo)
repo = metadata.repo
hash_info = self._fetch_to_cache(path, repo, download_update)
hash_infos.append(hash_info)
return sum(download_results), failed, hash_infos
def _check_repo(self, path_info, repo):
if not repo:
return
repo_path = PathInfo(repo.root_dir)
if path_info == repo_path and isinstance(repo, Repo):
message = "Cannot fetch a complete DVC repository"
if repo.root_dir != self.root_dir:
rel = relpath(repo.root_dir, self.root_dir)
message += f" '{rel}'"
raise IsADVCRepoError(message)
def get_external(self, path, dest):
"""Convenience wrapper for fetch_external and checkout."""
path_info = PathInfo(self.root_dir) / path
with reraise(FileNotFoundError, PathMissingError(path, self.url)):
metadata = self.repo_tree.metadata(path_info)
self._check_repo(path_info, metadata.repo)
if metadata.output_exists:
repo = metadata.repo
cache = repo.cache.local
# fetch DVC and git files to tmpdir cache, then checkout
save_info = self._fetch_to_cache(path_info, repo, None)
cache.checkout(PathInfo(dest), save_info)
else:
# git-only folder, just copy files directly to dest
tree = self._get_tree_for(metadata.repo) # ignore subrepos
tree.copytree(path_info, dest)
def _get_tree_for(self, repo, **kwargs):
"""
Provides a combined tree of a single repo with dvc + git/local tree.
"""
kw = {**self.tree_confs, **kwargs}
if "fetch" not in kw:
kw["fetch"] = True
return RepoTree(repo, **kw)
def get_checksum(self, path):
path_info = PathInfo(self.root_dir) / path
with reraise(FileNotFoundError, PathMissingError(path, self.url)):
metadata = self.repo_tree.metadata(path_info)
# skip subrepos to check for
tree = self._get_tree_for(metadata.repo)
return tree.get_hash(path_info)
@staticmethod
def _fix_local_remote(orig_repo, src_repo, remote_name):
# If a remote URL is relative to the source repo,
# it will have changed upon config load and made
# relative to this new repo. Restore the old one here.
new_remote = orig_repo.config["remote"][remote_name]
old_remote = src_repo.config["remote"][remote_name]
if new_remote["url"] != old_remote["url"]:
new_remote["url"] = old_remote["url"]
@staticmethod
def _add_upstream(orig_repo, src_repo):
# Fill the empty upstream entry with a new remote pointing to the
# original repo's cache location.
cache_dir = src_repo.cache.local.cache_dir
orig_repo.config["remote"]["auto-generated-upstream"] = {
"url": cache_dir
}
orig_repo.config["core"]["remote"] = "auto-generated-upstream"
def make_repo(self, path):
repo = Repo(path, scm=self.scm, rev=self.get_rev())
self._setup_cache(repo)
self._fix_upstream(repo)
return repo
def _setup_cache(self, repo):
repo.cache.local.cache_dir = self.cache_dir
if self.cache_types:
repo.cache.local.cache_types = self.cache_types
def _fix_upstream(self, repo):
if not os.path.isdir(self.url):
return
try:
rel_path = os.path.relpath(repo.root_dir, self.root_dir)
src_repo = Repo(PathInfo(self.url) / rel_path)
except NotDvcRepoError:
return
try:
remote_name = repo.config["core"].get("remote")
if remote_name:
self._fix_local_remote(repo, src_repo, remote_name)
else:
self._add_upstream(repo, src_repo)
finally:
src_repo.close()
@wrap_with(threading.Lock())
def _get_cache_dir(self):
try:
cache_dir = CACHE_DIRS[self.url]
except KeyError:
cache_dir = CACHE_DIRS[self.url] = tempfile.mkdtemp("dvc-cache")
return cache_dir
class ExternalRepo(BaseExternalRepo, Repo):
def __init__(
self,
root_dir,
url,
scm=None,
rev=None,
for_write=False,
cache_dir=None,
cache_types=None,
**kwargs,
):
super().__init__(root_dir, scm=scm, rev=rev)
self.url = url
self.for_write = for_write
self.cache_dir = cache_dir or self._get_cache_dir()
self.cache_types = cache_types
self._setup_cache(self)
self._fix_upstream(self)
self.tree_confs = kwargs
class ExternalGitRepo(BaseExternalRepo):
def __init__(
self,
root_dir,
url,
scm=None,
rev=None,
for_write=False,
cache_dir=None,
cache_types=None,
**kwargs,
):
self.root_dir = os.path.realpath(root_dir)
self.scm = scm
self.url = url
self.for_write = for_write
self.cache_dir = cache_dir or self._get_cache_dir()
self.cache_types = cache_types
self.rev = rev
self.tree_confs = kwargs
self.config = {"cache": {"dir": self.cache_dir}}
self.cache = Cache(self)
if cache_types:
self.cache.local.cache_types = cache_types
self.state = StateNoop()
@cached_property
def tree(self):
if self.scm:
return self.scm.get_tree(self.rev)
return LocalTree(self, {"url": self.root_dir})
def close(self):
if self.scm:
self.scm.close()
def find_out_by_relpath(self, path):
raise OutputNotFoundError(path, self)
@contextmanager
def open_by_relpath(self, path, mode="r", encoding=None, **kwargs):
"""Opens a specified resource as a file object."""
path_info = PathInfo(self.root_dir) / path
try:
with self.repo_tree.open(
path_info, mode=mode, encoding=encoding, **kwargs
) as fobj:
yield fobj
except FileNotFoundError as exc:
raise PathMissingError(path, self.url) from exc
# Use original remote to make sure that we are using correct url,
# credential paths, etc if they are relative to the config location.
return {"remote": {name: repo.config["remote"][name]}}
finally:
repo.close()
def _cached_clone(url, rev, for_write=False):
@@ -349,6 +145,8 @@ def _cached_clone(url, rev, for_write=False):
revision checked out. If for_write is set prevents reusing this dir via
cache.
"""
from shutil import copytree
# even if we have already cloned this repo, we may need to
# fetch/fast-forward to get specified rev
clone_path, shallow = _clone_default_branch(url, rev, for_write=for_write)
@@ -359,7 +157,7 @@ def _cached_clone(url, rev, for_write=False):
# Copy to a new dir to keep the clone clean
repo_path = tempfile.mkdtemp("dvc-erepo")
logger.debug("erepo: making a copy of %s clone", url)
copy_tree(clone_path, repo_path)
copytree(clone_path, repo_path)
# Check out the specified revision
if for_write:
@@ -375,6 +173,8 @@ def _clone_default_branch(url, rev, for_write=False):
The cloned is reactualized with git pull unless rev is a known sha.
"""
from dvc.scm.git import Git
clone_path, shallow = CLONES.get(url, (None, False))
git = None
@@ -402,6 +202,8 @@ def _clone_default_branch(url, rev, for_write=False):
clone_path = tempfile.mkdtemp("dvc-clone")
if not for_write and rev and not Git.is_sha(rev):
# If rev is a tag or branch name try shallow clone first
from dvc.scm.base import CloneError
try:
git = Git.clone(url, clone_path, shallow_branch=rev)
shallow = True
@@ -422,19 +224,21 @@ def _clone_default_branch(url, rev, for_write=False):
def _unshallow(git):
if git.repo.head.is_detached:
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.repo.remotes["origin"].refs
origin_refs = git.gitpython.repo.remotes["origin"].refs
ref = origin_refs["HEAD"].reference
branch_name = ref.name.split("/")[-1]
branch = git.repo.create_head(branch_name, ref)
branch = git.gitpython.repo.create_head(branch_name, ref)
branch.set_tracking_branch(ref)
branch.checkout()
git.pull(unshallow=True)
def _git_checkout(repo_path, rev):
from dvc.scm.git import Git
logger.debug("erepo: git checkout %s@%s", repo_path, rev)
git = Git(repo_path)
try:
@@ -444,6 +248,8 @@ def _git_checkout(repo_path, rev):
def _remove(path):
from dvc.utils.fs import remove
if os.name == "nt":
# git.exe may hang for a while not permitting to remove temp dir
os_retry = retry(5, errors=OSError, timeout=0.1)
+121
View File
@@ -0,0 +1,121 @@
import posixpath
from urllib.parse import urlparse
from ..scheme import Schemes
from .azure import AzureFileSystem
from .gdrive import GDriveFileSystem
from .gs import GSFileSystem
from .hdfs import HDFSFileSystem
from .http import HTTPFileSystem
from .https import HTTPSFileSystem
from .local import LocalFileSystem
from .oss import OSSFileSystem
from .s3 import S3FileSystem
from .ssh import SSHFileSystem
from .webdav import WebDAVFileSystem, WebDAVSFileSystem
from .webhdfs import WebHDFSFileSystem
FS_MAP = {
Schemes.AZURE: AzureFileSystem,
Schemes.GDRIVE: GDriveFileSystem,
Schemes.GS: GSFileSystem,
Schemes.HDFS: HDFSFileSystem,
Schemes.WEBHDFS: WebHDFSFileSystem,
Schemes.HTTP: HTTPFileSystem,
Schemes.HTTPS: HTTPSFileSystem,
Schemes.S3: S3FileSystem,
Schemes.SSH: SSHFileSystem,
Schemes.OSS: OSSFileSystem,
Schemes.WEBDAV: WebDAVFileSystem,
Schemes.WEBDAVS: WebDAVSFileSystem,
# NOTE: LocalFileSystem is the default
}
def get_fs_cls(remote_conf, scheme=None):
if not scheme:
scheme = urlparse(remote_conf["url"]).scheme
return FS_MAP.get(scheme, LocalFileSystem)
def get_fs_config(config, **kwargs):
name = kwargs.get("name")
if name:
try:
remote_conf = config["remote"][name.lower()]
except KeyError:
from dvc.config import RemoteNotFoundError
raise RemoteNotFoundError(f"remote '{name}' doesn't exist")
else:
remote_conf = kwargs
return _resolve_remote_refs(config, remote_conf)
def _resolve_remote_refs(config, remote_conf):
# Support for cross referenced remotes.
# This will merge the settings, shadowing base ref with remote_conf.
# For example, having:
#
# dvc remote add server ssh://localhost
# dvc remote modify server user root
# dvc remote modify server ask_password true
#
# dvc remote add images remote://server/tmp/pictures
# dvc remote modify images user alice
# dvc remote modify images ask_password false
# dvc remote modify images password asdf1234
#
# Results on a config dictionary like:
#
# {
# "url": "ssh://localhost/tmp/pictures",
# "user": "alice",
# "password": "asdf1234",
# "ask_password": False,
# }
parsed = urlparse(remote_conf["url"])
if parsed.scheme != "remote":
return remote_conf
base = get_fs_config(config, name=parsed.netloc)
url = posixpath.join(base["url"], parsed.path.lstrip("/"))
return {**base, **remote_conf, "url": url}
def get_cloud_fs(repo, **kwargs):
from dvc.config import ConfigError
from dvc.config_schema import SCHEMA, Invalid
repo_config = repo.config if repo else {}
core_config = repo_config.get("core", {})
remote_conf = get_fs_config(repo_config, **kwargs)
try:
remote_conf = SCHEMA["remote"][str](remote_conf)
except Invalid as exc:
raise ConfigError(str(exc)) from None
if "jobs" not in remote_conf:
jobs = core_config.get("jobs")
if jobs:
remote_conf["jobs"] = jobs
if "checksum_jobs" not in remote_conf:
checksum_jobs = core_config.get("checksum_jobs")
if checksum_jobs:
remote_conf["checksum_jobs"] = checksum_jobs
cls = get_fs_cls(remote_conf)
if cls == GDriveFileSystem and repo:
remote_conf["gdrive_credentials_tmp_dir"] = repo.tmp_dir
url = remote_conf.pop("url")
path_info = cls.PATH_CLS(
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
return cls, conf, path_info
@@ -1,7 +1,7 @@
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, List
from dvc.output import BaseOutput
from dvc.output import Output
from dvc.path_info import PathInfo
if TYPE_CHECKING:
@@ -33,7 +33,7 @@ class Metadata:
# optional fields
isdir: bool = False # is it a directory?
is_exec: bool = False # is it an executable?
outs: List[BaseOutput] = field(default_factory=list) # list of outputs
outs: List[Output] = field(default_factory=list) # list of outputs
def __post_init__(self):
self.output_exists = bool(self.outs)
+161
View File
@@ -0,0 +1,161 @@
import logging
import os
import threading
from fsspec.asyn import fsspec_loop
from fsspec.utils import infer_storage_options
from funcy import cached_property, memoize, wrap_prop
from dvc.exceptions import DvcException
from dvc.path_info import CloudURLInfo
from dvc.scheme import Schemes
from dvc.utils import format_link
from .fsspec_wrapper import ObjectFSWrapper
logger = logging.getLogger(__name__)
_DEFAULT_CREDS_STEPS = (
"https://azuresdkdocs.blob.core.windows.net/$web/python/"
"azure-identity/1.4.0/azure.identity.html#azure.identity"
".DefaultAzureCredential"
)
class AzureAuthError(DvcException):
pass
@memoize
def _az_config():
# NOTE: ideally we would've used get_default_cli().config from
# azure.cli.core, but azure-cli-core has a lot of conflicts with other
# dependencies. So instead we are just use knack directly
from knack.config import CLIConfig
config_dir = os.getenv(
"AZURE_CONFIG_DIR", os.path.expanduser(os.path.join("~", ".azure"))
)
return CLIConfig(config_dir=config_dir, config_env_var_prefix="AZURE")
# pylint:disable=abstract-method
class AzureFileSystem(ObjectFSWrapper):
scheme = Schemes.AZURE
PATH_CLS = CloudURLInfo
PARAM_CHECKSUM = "etag"
DETAIL_FIELDS = frozenset(("etag", "size"))
REQUIRES = {
"adlfs": "adlfs",
"knack": "knack",
"azure-identity": "azure.identity",
}
@classmethod
def _strip_protocol(cls, path: str):
bucket = infer_storage_options(path).get("host")
if bucket:
return path
bucket = _az_config().get("storage", "container_name", None)
return f"azure://{bucket}"
@staticmethod
def _get_kwargs_from_urls(urlpath):
ops = infer_storage_options(urlpath)
if "host" in ops:
return {"bucket": ops["host"]}
return {}
def _prepare_credentials(self, **config):
from azure.identity.aio import DefaultAzureCredential
# Disable spam from failed cred types for DefaultAzureCredential
logging.getLogger("azure.identity.aio").setLevel(logging.ERROR)
login_info = {}
login_info["connection_string"] = config.get(
"connection_string",
_az_config().get("storage", "connection_string", None),
)
login_info["account_name"] = config.get(
"account_name", _az_config().get("storage", "account", None)
)
login_info["account_key"] = config.get(
"account_key", _az_config().get("storage", "key", None)
)
login_info["sas_token"] = config.get(
"sas_token", _az_config().get("storage", "sas_token", None)
)
login_info["tenant_id"] = config.get("tenant_id")
login_info["client_id"] = config.get("client_id")
login_info["client_secret"] = config.get("client_secret")
if not (login_info["account_name"] or login_info["connection_string"]):
raise AzureAuthError(
"Authentication to Azure Blob Storage requires either "
"account_name or connection_string.\nLearn more about "
"configuration settings at "
+ format_link("https://man.dvc.org/remote/modify")
)
any_secondary = any(
value for key, value in login_info.items() if key != "account_name"
)
if (
login_info["account_name"]
and not any_secondary
and not config.get("allow_anonymous_login", False)
):
with fsspec_loop():
login_info["credential"] = DefaultAzureCredential(
exclude_interactive_browser_credential=False,
exclude_environment_credential=config.get(
"exclude_environment_credential", False
),
exclude_visual_studio_code_credential=config.get(
"exclude_visual_studio_code_credential", False
),
exclude_shared_token_cache_credential=config.get(
"exclude_shared_token_cache_credential", False
),
exclude_managed_identity_credential=config.get(
"exclude_managed_identity_credential", False
),
)
for login_method, required_keys in [ # noqa
("connection string", ["connection_string"]),
(
"AD service principal",
["tenant_id", "client_id", "client_secret"],
),
("account key", ["account_name", "account_key"]),
("SAS token", ["account_name", "sas_token"]),
(
f"default credentials ({_DEFAULT_CREDS_STEPS})",
["account_name", "credential"],
),
("anonymous login", ["account_name"]),
]:
if all(login_info.get(key) is not None for key in required_keys):
break
else:
login_method = None
self.login_method = login_method
return login_info
@wrap_prop(threading.Lock())
@cached_property
def fs(self):
from adlfs import AzureBlobFileSystem
from azure.core.exceptions import AzureError
try:
return AzureBlobFileSystem(**self.fs_args)
except (ValueError, AzureError) as e:
raise AzureAuthError(
f"Authentication to Azure Blob Storage via {self.login_method}"
" failed.\nLearn more about configuration settings at"
f" {format_link('https://man.dvc.org/remote/modify')}"
) from e
+348
View File
@@ -0,0 +1,348 @@
import logging
from concurrent.futures import ThreadPoolExecutor, as_completed
from functools import partial, partialmethod
from multiprocessing import cpu_count
from typing import Any, ClassVar, Dict, FrozenSet, Optional
from dvc.exceptions import DvcException
from dvc.path_info import URLInfo
from dvc.progress import Tqdm
from dvc.utils import tmp_fname
from dvc.utils.fs import makedirs, move
from dvc.utils.http import open_url
logger = logging.getLogger(__name__)
class RemoteCmdError(DvcException):
def __init__(self, remote, cmd, ret, err):
super().__init__(
"{remote} command '{cmd}' finished with non-zero return code"
" {ret}': {err}".format(remote=remote, cmd=cmd, ret=ret, err=err)
)
class RemoteActionNotImplemented(DvcException):
def __init__(self, action, scheme):
m = f"{action} is not supported for {scheme} remotes"
super().__init__(m)
class RemoteMissingDepsError(DvcException):
pass
class BaseFileSystem:
sep = "/"
scheme = "base"
REQUIRES: ClassVar[Dict[str, str]] = {}
PATH_CLS = URLInfo # type: Any
_JOBS = 4 * cpu_count()
CHECKSUM_DIR_SUFFIX = ".dir"
HASH_JOBS = max(1, min(4, cpu_count() // 2))
LIST_OBJECT_PAGE_SIZE = 1000
TRAVERSE_WEIGHT_MULTIPLIER = 5
TRAVERSE_PREFIX_LEN = 3
TRAVERSE_THRESHOLD_SIZE = 500000
CAN_TRAVERSE = True
# Needed for some providers, and http open()
CHUNK_SIZE = 64 * 1024 * 1024 # 64 MiB
PARAM_CHECKSUM: ClassVar[Optional[str]] = None
DETAIL_FIELDS: FrozenSet[str] = frozenset()
def __init__(self, **kwargs):
self._check_requires(**kwargs)
self.jobs = kwargs.get("jobs") or self._JOBS
self.hash_jobs = kwargs.get("checksum_jobs") or self.HASH_JOBS
self._config = kwargs
@property
def config(self):
return self._config
@classmethod
def _strip_protocol(cls, path: str):
return path
@staticmethod
def _get_kwargs_from_urls(urlpath): # pylint:disable=unused-argument
return {}
@classmethod
def get_missing_deps(cls):
import importlib
missing = []
for package, module in cls.REQUIRES.items():
try:
importlib.import_module(module)
except ImportError:
missing.append(package)
return missing
def _check_requires(self, **kwargs):
from ..scheme import Schemes
from ..utils import format_link
from ..utils.pkg import PKG
missing = self.get_missing_deps()
if not missing:
return
url = kwargs.get("url", f"{self.scheme}://")
scheme = self.scheme
if scheme == Schemes.WEBDAVS:
scheme = Schemes.WEBDAV
by_pkg = {
"pip": f"pip install 'dvc[{scheme}]'",
"conda": f"conda install -c conda-forge dvc-{scheme}",
}
cmd = by_pkg.get(PKG)
if cmd:
link = format_link("https://dvc.org/doc/install")
hint = (
f"To install dvc with those dependencies, run:\n"
"\n"
f"\t{cmd}\n"
"\n"
f"See {link} for more info."
)
else:
link = format_link("https://github.com/iterative/dvc/issues")
hint = f"Please report this bug to {link}. Thank you!"
raise RemoteMissingDepsError(
f"URL '{url}' is supported but requires these missing "
f"dependencies: {missing}. {hint}"
)
def open(self, path_info, mode: str = "r", encoding: str = None, **kwargs):
if hasattr(self, "_generate_download_url"):
# pylint:disable=no-member
func = self._generate_download_url # type: ignore[attr-defined]
get_url = partial(func, path_info)
return open_url(get_url, mode=mode, encoding=encoding, **kwargs)
raise RemoteActionNotImplemented("open", self.scheme)
def exists(self, path_info) -> bool:
raise NotImplementedError
# pylint: disable=unused-argument
def isdir(self, path_info):
"""Optional: Overwrite only if the remote has a way to distinguish
between a directory and a file.
"""
return False
def isfile(self, path_info):
"""Optional: Overwrite only if the remote has a way to distinguish
between a directory and a file.
"""
return True
def isexec(self, path_info):
"""Optional: Overwrite only if the remote has a way to distinguish
between executable and non-executable file.
"""
return False
def iscopy(self, path_info):
"""Check if this file is an independent copy."""
return False # We can't be sure by default
def walk(self, top, topdown=True, onerror=None, **kwargs):
"""Return a generator with (root, dirs, files)."""
raise NotImplementedError
def walk_files(self, path_info, **kwargs):
"""Return a generator with `PathInfo`s to all the files.
Optional kwargs:
prefix (bool): If true `path_info` will be treated as a prefix
rather than directory path.
"""
raise NotImplementedError
def ls(self, path_info, detail=False):
raise RemoteActionNotImplemented("ls", self.scheme)
def find(self, path_info, detail=False, prefix=None):
raise RemoteActionNotImplemented("find", self.scheme)
def is_empty(self, path_info):
return False
def info(self, path_info):
raise NotImplementedError
def getsize(self, path_info):
return self.info(path_info).get("size")
def remove(self, path_info):
raise RemoteActionNotImplemented("remove", self.scheme)
def makedirs(self, path_info, **kwargs):
"""Optional: Implement only if the remote needs to create
directories before copying/linking/moving data
"""
def move(self, from_info, to_info):
self.copy(from_info, to_info)
self.remove(from_info)
def copy(self, from_info, to_info):
raise RemoteActionNotImplemented("copy", self.scheme)
def symlink(self, from_info, to_info):
raise RemoteActionNotImplemented("symlink", self.scheme)
def hardlink(self, from_info, to_info):
raise RemoteActionNotImplemented("hardlink", self.scheme)
def reflink(self, from_info, to_info):
raise RemoteActionNotImplemented("reflink", self.scheme)
# pylint: enable=unused-argument
@classmethod
def is_dir_hash(cls, hash_):
if not hash_:
return False
return hash_.endswith(cls.CHECKSUM_DIR_SUFFIX)
def upload(self, from_info, to_info, name=None, no_progress_bar=False):
if not hasattr(self, "_upload"):
raise RemoteActionNotImplemented("upload", self.scheme)
if to_info.scheme != self.scheme:
raise NotImplementedError
if from_info.scheme != "local":
raise NotImplementedError
logger.debug("Uploading '%s' to '%s'", from_info, to_info)
name = name or from_info.name
self._upload( # noqa, pylint: disable=no-member
from_info.fspath,
to_info,
name=name,
no_progress_bar=no_progress_bar,
)
def upload_fobj(
self, fobj, to_info, no_progress_bar=False, size=None, **pbar_args
):
if not hasattr(self, "_upload_fobj"):
raise RemoteActionNotImplemented("upload_fobj", self.scheme)
with Tqdm.wrapattr(
fobj,
"read",
disable=no_progress_bar,
bytes=True,
total=size,
**pbar_args,
) as wrapped:
self._upload_fobj( # pylint: disable=no-member
wrapped, to_info, size=size
)
def download(
self,
from_info,
to_info,
name=None,
no_progress_bar=False,
jobs=None,
_only_file=False,
**kwargs,
):
if not hasattr(self, "_download"):
raise RemoteActionNotImplemented("download", self.scheme)
if from_info.scheme != self.scheme:
raise NotImplementedError
if to_info.scheme == self.scheme != "local":
self.copy(from_info, to_info)
return 0
if to_info.scheme != "local":
raise NotImplementedError
if not _only_file and self.isdir(from_info):
return self._download_dir(
from_info, to_info, name, no_progress_bar, jobs, **kwargs
)
return self._download_file(from_info, to_info, name, no_progress_bar)
download_file = partialmethod(download, _only_file=True)
def _download_dir(
self, from_info, to_info, name, no_progress_bar, jobs, **kwargs
):
from_infos = list(self.walk_files(from_info, **kwargs))
if not from_infos:
makedirs(to_info, exist_ok=True)
return None
to_infos = (
to_info / info.relative_to(from_info) for info in from_infos
)
with Tqdm(
total=len(from_infos),
desc="Downloading directory",
unit="Files",
disable=no_progress_bar,
) as pbar:
download_files = pbar.wrap_fn(
partial(self._download_file, name=name, no_progress_bar=True)
)
max_workers = jobs or self.jobs
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = [
executor.submit(download_files, from_info, to_info)
for from_info, to_info in zip(from_infos, to_infos)
]
# NOTE: unlike pulling/fetching cache, where we need to
# download everything we can, not raising an error here might
# turn very ugly, as the user might think that he has
# downloaded a complete directory, while having a partial one,
# which might cause unexpected results in his pipeline.
for future in as_completed(futures):
# NOTE: executor won't let us raise until all futures that
# it has are finished, so we need to cancel them ourselves
# before re-raising.
exc = future.exception()
if exc:
for entry in futures:
entry.cancel()
raise exc
def _download_file(self, from_info, to_info, name, no_progress_bar):
makedirs(to_info.parent, exist_ok=True)
logger.debug("Downloading '%s' to '%s'", from_info, to_info)
name = name or to_info.name
tmp_file = tmp_fname(to_info)
self._download( # noqa, pylint: disable=no-member
from_info, tmp_file, name=name, no_progress_bar=no_progress_bar
)
move(tmp_file, to_info)
+263
View File
@@ -0,0 +1,263 @@
import logging
import os
import typing
from dvc.exceptions import OutputNotFoundError
from dvc.path_info import PathInfo
from dvc.utils import relpath
from ._metadata import Metadata
from .base import BaseFileSystem
if typing.TYPE_CHECKING:
from dvc.output import Output
logger = logging.getLogger(__name__)
class DvcFileSystem(BaseFileSystem): # pylint:disable=abstract-method
"""DVC repo fs.
Args:
repo: DVC repo.
"""
sep = os.sep
scheme = "local"
PARAM_CHECKSUM = "md5"
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.repo = kwargs["repo"]
@property
def config(self):
raise NotImplementedError
def _find_outs(self, path, *args, **kwargs):
outs = self.repo.find_outs_by_path(path, *args, **kwargs)
def _is_cached(out):
return out.use_cache
outs = list(filter(_is_cached, outs))
if not outs:
raise OutputNotFoundError(path, self.repo)
return outs
def _get_granular_hash(
self, path_info: PathInfo, out: "Output", remote=None
):
assert isinstance(path_info, PathInfo)
# NOTE: use string paths here for performance reasons
key = tuple(relpath(path_info, out.path_info).split(os.sep))
out.get_dir_cache(remote=remote)
if out.obj is None:
raise FileNotFoundError
obj = out.obj.trie.get(key)
if obj:
return obj.hash_info
raise FileNotFoundError
def _get_fs_path(self, path: PathInfo, remote=None):
try:
outs = self._find_outs(path, strict=False)
except OutputNotFoundError as exc:
raise FileNotFoundError from exc
if len(outs) != 1 or (
outs[0].is_dir_checksum and path == outs[0].path_info
):
raise IsADirectoryError
out = outs[0]
if not out.hash_info:
raise FileNotFoundError
if out.changed_cache(filter_info=path):
from dvc.config import NoRemoteError
try:
remote_odb = self.repo.cloud.get_remote_odb(remote)
except NoRemoteError as exc:
raise FileNotFoundError from exc
if out.is_dir_checksum:
checksum = self._get_granular_hash(path, out).value
else:
checksum = out.hash_info.value
remote_info = remote_odb.hash_to_path_info(checksum)
return remote_odb.fs, remote_info
if out.is_dir_checksum:
checksum = self._get_granular_hash(path, out).value
cache_path = out.odb.hash_to_path_info(checksum).url
else:
cache_path = out.cache_path
return out.odb.fs, cache_path
def open( # type: ignore
self, path: PathInfo, mode="r", encoding=None, **kwargs
): # pylint: disable=arguments-renamed
fs, fspath = self._get_fs_path(path, **kwargs)
return fs.open(fspath, mode=mode, encoding=encoding)
def exists(self, path): # pylint: disable=arguments-renamed
try:
self.metadata(path)
return True
except FileNotFoundError:
return False
def isdir(self, path): # pylint: disable=arguments-renamed
try:
meta = self.metadata(path)
return meta.isdir
except FileNotFoundError:
return False
def check_isdir(self, path_info, outs):
if len(outs) != 1:
return True
out = outs[0]
if not out.is_dir_checksum:
return out.path_info != path_info
if out.path_info == path_info:
return True
try:
self._get_granular_hash(path_info, out)
return False
except FileNotFoundError:
return True
def isfile(self, path): # pylint: disable=arguments-renamed
try:
meta = self.metadata(path)
return meta.isfile
except FileNotFoundError:
return False
def _fetch_dir(self, out, **kwargs):
# pull dir cache if needed
out.get_dir_cache(**kwargs)
if not out.obj:
raise FileNotFoundError
def _add_dir(self, trie, out, **kwargs):
self._fetch_dir(out, **kwargs)
base = out.path_info.parts
for key, _ in out.obj: # noqa: B301
trie[base + key] = None
def _walk(self, root, trie, topdown=True, **kwargs):
dirs = set()
files = []
out = trie.get(root.parts)
if out and out.is_dir_checksum:
self._add_dir(trie, out, **kwargs)
root_len = len(root.parts)
for key, out in trie.iteritems(prefix=root.parts): # noqa: B301
if key == root.parts:
continue
name = key[root_len]
if len(key) > root_len + 1 or (out and out.is_dir_checksum):
dirs.add(name)
continue
files.append(name)
assert topdown
dirs = list(dirs)
yield root.fspath, dirs, files
for dname in dirs:
yield from self._walk(root / dname, trie)
def walk(self, top, topdown=True, onerror=None, **kwargs):
from pygtrie import Trie
assert topdown
root = PathInfo(os.path.abspath(top))
try:
meta = self.metadata(root)
except FileNotFoundError:
if onerror is not None:
onerror(FileNotFoundError(top))
return
if not meta.isdir:
if onerror is not None:
onerror(NotADirectoryError(top))
return
trie = Trie()
for out in meta.outs:
trie[out.path_info.parts] = out
if out.is_dir_checksum and root.isin_or_eq(out.path_info):
self._add_dir(trie, out, **kwargs)
yield from self._walk(root, trie, topdown=topdown, **kwargs)
def walk_files(self, path_info, **kwargs):
for root, _, files in self.walk(path_info):
for fname in files:
# NOTE: os.path.join is ~5.5 times slower
yield PathInfo(f"{root}{os.sep}{fname}")
def isdvc(self, path, recursive=False, strict=True):
try:
meta = self.metadata(path)
except FileNotFoundError:
return False
recurse = recursive or not strict
return meta.output_exists if recurse else meta.is_output
def isexec(self, path_info): # pylint: disable=unused-argument
return False
def metadata(self, path_info):
path_info = PathInfo(os.path.abspath(path_info))
try:
outs = self._find_outs(path_info, strict=False, recursive=True)
except OutputNotFoundError as exc:
raise FileNotFoundError from exc
meta = Metadata(path_info=path_info, outs=outs, repo=self.repo)
meta.isdir = meta.isdir or self.check_isdir(meta.path_info, meta.outs)
return meta
def info(self, path_info):
meta = self.metadata(path_info)
ret = {"type": "directory" if meta.isdir else "file"}
if meta.is_output and len(meta.outs) == 1 and meta.outs[0].hash_info:
hash_info = meta.outs[0].hash_info
ret["size"] = hash_info.size
ret[hash_info.name] = hash_info.value
elif meta.part_of_output:
(out,) = meta.outs
key = path_info.relative_to(out.path_info).parts
obj = out.obj.trie.get(key)
if obj:
ret["size"] = obj.size
ret[obj.hash_info.name] = obj.hash_info.value
return ret
def _download(self, from_info, to_file, **kwargs):
fs, path = self._get_fs_path(from_info)
fs._download( # pylint: disable=protected-access
path, to_file, **kwargs
)
+271
View File
@@ -0,0 +1,271 @@
import os
import shutil
from functools import lru_cache
from funcy import cached_property
from dvc.progress import Tqdm
from .base import BaseFileSystem
from .local import LocalFileSystem
# pylint: disable=no-member
class FSSpecWrapper(BaseFileSystem):
TRAVERSE_PREFIX_LEN = 2
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.fs_args = {"skip_instance_cache": True}
self.fs_args.update(self._prepare_credentials(**kwargs))
@cached_property
def fs(self):
raise NotImplementedError
def _with_bucket(self, path):
return str(path)
def _strip_bucket(self, entry):
return entry
@staticmethod
def _get_kwargs_from_urls(urlpath):
from fsspec.utils import infer_storage_options
options = infer_storage_options(urlpath)
options.pop("path", None)
options.pop("protocol", None)
return options
def _strip_buckets(self, entries, detail=False):
for entry in entries:
if detail:
entry = self._entry_hook(entry.copy())
entry["name"] = self._strip_bucket(entry["name"])
else:
entry = self._strip_bucket(entry)
yield entry
def _entry_hook(self, entry):
"""Simple hook method to be overridden when wanted to process
entries within info() and ls(detail=True) calls"""
return entry
def _prepare_credentials(
self, **config
): # pylint: disable=unused-argument
"""Prepare the arguments for authentication to the
host filesystem"""
return {}
def _isdir(self, path_info):
return self.fs.isdir(self._with_bucket(path_info))
def isdir(self, path_info):
try:
return self._isdir(path_info)
except FileNotFoundError:
return False
def isfile(self, path_info):
try:
return not self._isdir(path_info)
except FileNotFoundError:
return False
def is_empty(self, path_info):
entry = self.info(path_info)
if entry["type"] == "directory":
return not self.fs.ls(self._with_bucket(path_info))
return entry["size"] == 0
def open(
self, path_info, mode="r", **kwargs
): # pylint: disable=arguments-differ
return self.fs.open(self._with_bucket(path_info), mode=mode)
def copy(self, from_info, to_info):
self.makedirs(to_info.parent)
self.fs.copy(self._with_bucket(from_info), self._with_bucket(to_info))
def exists(self, path_info) -> bool:
return self.fs.exists(self._with_bucket(path_info))
def ls(self, path_info, detail=False):
path = self._with_bucket(path_info)
files = self.fs.ls(path, detail=detail)
yield from self._strip_buckets(files, detail=detail)
# pylint: disable=unused-argument
def find(self, path_info, detail=False, prefix=None):
path = self._with_bucket(path_info)
files = self.fs.find(path, detail=detail)
if detail:
files = files.values()
yield from self._strip_buckets(files, detail=detail)
def walk_files(self, path_info, **kwargs):
for file in self.find(path_info, **kwargs):
yield path_info.replace(path=file)
def move(self, from_info, to_info):
self.fs.move(self._with_bucket(from_info), self._with_bucket(to_info))
def remove(self, path_info):
self.fs.rm_file(self._with_bucket(path_info))
def info(self, path_info):
info = self.fs.info(self._with_bucket(path_info)).copy()
info = self._entry_hook(info)
info["name"] = self._strip_bucket(info["name"])
return info
def makedirs(self, path_info, **kwargs):
self.fs.makedirs(
self._with_bucket(path_info), exist_ok=kwargs.pop("exist_ok", True)
)
def _upload_fobj(self, fobj, to_info, size=None):
self.makedirs(to_info.parent)
with self.open(to_info, "wb") as fdest:
shutil.copyfileobj(fobj, fdest, length=fdest.blocksize)
def _upload(
self, from_file, to_info, name=None, no_progress_bar=False, **kwargs
):
self.makedirs(to_info.parent)
size = os.path.getsize(from_file)
with open(from_file, "rb") as fobj:
self.upload_fobj(
fobj,
to_info,
size=size,
desc=name,
no_progress_bar=no_progress_bar,
)
self.fs.invalidate_cache(self._with_bucket(to_info.parent))
def _download(
self, from_info, to_file, name=None, no_progress_bar=False, **pbar_args
):
total = self.getsize(self._with_bucket(from_info))
with self.open(from_info, "rb") as fobj:
with Tqdm.wrapattr(
fobj,
"read",
desc=name,
disable=no_progress_bar,
bytes=True,
total=total,
**pbar_args,
) as wrapped:
with open(to_file, "wb") as fdest:
shutil.copyfileobj(wrapped, fdest, length=fobj.blocksize)
# pylint: disable=abstract-method
class ObjectFSWrapper(FSSpecWrapper):
TRAVERSE_PREFIX_LEN = 3
@lru_cache(512)
def _with_bucket(self, path):
if isinstance(path, self.PATH_CLS):
return f"{path.bucket}/{path.path}"
return path
def _strip_bucket(self, entry):
try:
bucket, path = entry.split("/", 1)
except ValueError:
# If there is no path attached, only returns
# the bucket (top-level).
bucket, path = entry, None
return path or bucket
def makedirs(self, path_info, **kwargs):
# For object storages make this method a no-op. The original
# fs.makedirs() method will only check if the bucket exists
# and create if it doesn't though we don't want to support
# that behavior, and the check will cost some time so we'll
# simply ignore all mkdir()/makedirs() calls.
return None
def _isdir(self, path_info):
# Directory in object storages are interpreted differently
# among different fsspec providers, so this logic is a temporary
# measure for us to adapt as of now. It checks whether it is a
# directory (as in a prefix with contents) or whether it is an empty
# file where it's name ends with a forward slash
entry = self.info(path_info)
return entry["type"] == "directory" or (
entry["size"] == 0
and entry["type"] == "file"
and entry["name"].endswith("/")
)
def find(self, path_info, detail=False, prefix=None):
if prefix:
path = self._with_bucket(path_info.parent)
files = self.fs.find(
path, detail=detail, prefix=path_info.parts[-1]
)
else:
path = self._with_bucket(path_info)
files = self.fs.find(path, detail=detail)
if detail:
files = list(files.values())
# When calling find() on a file, it returns the same file in a list.
# For object-based storages, the same behavior applies to empty
# directories since they are represented as files. This condition
# checks whether we should yield an empty list (if it is an empty
# directory) or just yield the file itself.
if len(files) == 1 and files[0] == path and self.isdir(path_info):
return None
yield from self._strip_buckets(files, detail=detail)
_LOCAL_FS = LocalFileSystem()
class CallbackMixin:
"""Use the native ``get_file()``/``put_file()`` APIs
if the target filesystem supports callbacks."""
def _upload(
self, from_file, to_info, name=None, no_progress_bar=False, **pbar_args
):
with Tqdm(
desc=name,
disable=no_progress_bar,
bytes=True,
total=-1,
**pbar_args,
) as pbar:
self.fs.put_file(
os.fspath(from_file),
self._with_bucket(to_info),
callback=pbar.as_callback(_LOCAL_FS, from_file),
)
self.fs.invalidate_cache(self._with_bucket(to_info.parent))
def _download(
self, from_info, to_file, name=None, no_progress_bar=False, **pbar_args
):
with Tqdm(
desc=name,
disable=no_progress_bar,
bytes=True,
total=-1,
**pbar_args,
) as pbar:
self.fs.get_file(
self._with_bucket(from_info),
os.fspath(to_file),
callback=pbar.as_callback(self, from_info),
)
+304
View File
@@ -0,0 +1,304 @@
import logging
import os
import posixpath
import re
import threading
from urllib.parse import urlparse
from funcy import cached_property, retry, wrap_prop
from dvc.exceptions import DvcException
from dvc.path_info import CloudURLInfo
from dvc.scheme import Schemes
from dvc.utils import format_link, tmp_fname
from .fsspec_wrapper import CallbackMixin, FSSpecWrapper
logger = logging.getLogger(__name__)
FOLDER_MIME_TYPE = "application/vnd.google-apps.folder"
class GDriveAuthError(DvcException):
def __init__(self, cred_location):
if cred_location:
message = (
"GDrive remote auth failed with credentials in '{}'.\n"
"Backup first, remove or fix them, and run DVC again.\n"
"It should do auth again and refresh the credentials.\n\n"
"Details:".format(cred_location)
)
else:
message = "Failed to authenticate GDrive remote"
super().__init__(message)
def _gdrive_retry(func):
def should_retry(exc):
from pydrive2.files import ApiRequestError
if not isinstance(exc, ApiRequestError):
return False
error_code = exc.error.get("code", 0)
result = False
if 500 <= error_code < 600:
result = True
if error_code == 403:
result = exc.GetField("reason") in [
"userRateLimitExceeded",
"rateLimitExceeded",
]
if result:
logger.debug(f"Retrying GDrive API call, error: {exc}.")
return result
# 16 tries, start at 0.5s, multiply by golden ratio, cap at 20s
return retry(
16,
timeout=lambda a: min(0.5 * 1.618 ** a, 20),
filter_errors=should_retry,
)(func)
class GDriveURLInfo(CloudURLInfo):
def __init__(self, url):
super().__init__(url)
# GDrive URL host part is case sensitive,
# we are restoring it here.
p = urlparse(url)
self.host = p.netloc
assert self.netloc == self.host
# Normalize path. Important since we have a cache (path to ID)
# and don't want to deal with different variations of path in it.
self._spath = re.sub("/{2,}", "/", self._spath.rstrip("/"))
class GDriveFileSystem(
CallbackMixin, FSSpecWrapper
): # pylint:disable=abstract-method
scheme = Schemes.GDRIVE
PATH_CLS = GDriveURLInfo
PARAM_CHECKSUM = "checksum"
REQUIRES = {"pydrive2": "pydrive2"}
# Always prefer traverse for GDrive since API usage quotas are a concern.
TRAVERSE_WEIGHT_MULTIPLIER = 1
TRAVERSE_PREFIX_LEN = 2
GDRIVE_CREDENTIALS_DATA = "GDRIVE_CREDENTIALS_DATA"
DEFAULT_USER_CREDENTIALS_FILE = "gdrive-user-credentials.json"
DEFAULT_GDRIVE_CLIENT_ID = "710796635688-iivsgbgsb6uv1fap6635dhvuei09o66c.apps.googleusercontent.com" # noqa: E501
DEFAULT_GDRIVE_CLIENT_SECRET = "a1Fz59uTpVNeG_VGuSKDLJXv"
def __init__(self, **config):
super().__init__(**config)
self.path_info = self.PATH_CLS(config["url"])
if not self.path_info.bucket:
raise DvcException(
"Empty GDrive URL '{}'. Learn more at {}".format(
config["url"],
format_link("https://man.dvc.org/remote/add"),
)
)
self._bucket = self.path_info.bucket
self._path = self.path_info.path
self._trash_only = config.get("gdrive_trash_only")
self._use_service_account = config.get("gdrive_use_service_account")
self._service_account_user_email = config.get(
"gdrive_service_account_user_email"
)
self._service_account_json_file_path = config.get(
"gdrive_service_account_json_file_path"
)
self._client_id = config.get("gdrive_client_id")
self._client_secret = config.get("gdrive_client_secret")
self._validate_config()
tmp_dir = config["gdrive_credentials_tmp_dir"]
assert tmp_dir
self._gdrive_service_credentials_path = tmp_fname(
os.path.join(tmp_dir, "")
)
self._gdrive_user_credentials_path = (
tmp_fname(os.path.join(tmp_dir, ""))
if os.getenv(GDriveFileSystem.GDRIVE_CREDENTIALS_DATA)
else config.get(
"gdrive_user_credentials_file",
os.path.join(tmp_dir, self.DEFAULT_USER_CREDENTIALS_FILE),
)
)
@staticmethod
def _get_kwargs_from_urls(urlpath):
return {"url": urlpath}
def _validate_config(self):
# Validate Service Account configuration
if (
self._use_service_account
and not self._service_account_json_file_path
):
raise DvcException(
"To use service account, set "
"`gdrive_service_account_json_file_path`, and optionally"
"`gdrive_service_account_user_email` in DVC config\n"
"Learn more at {}".format(
format_link("https://man.dvc.org/remote/modify")
)
)
# Validate OAuth 2.0 Client ID configuration
if not self._use_service_account:
if bool(self._client_id) != bool(self._client_secret):
raise DvcException(
"Please specify GDrive's client ID and secret in "
"DVC config or omit both to use the defaults.\n"
"Learn more at {}".format(
format_link("https://man.dvc.org/remote/modify")
)
)
@cached_property
def credentials_location(self):
"""
Helper to determine where will GDrive remote read credentials from.
Useful for tests, exception messages, etc. Returns either env variable
name if it's set or actual path to the credentials file.
"""
if os.getenv(GDriveFileSystem.GDRIVE_CREDENTIALS_DATA):
return GDriveFileSystem.GDRIVE_CREDENTIALS_DATA
if os.path.exists(self._gdrive_user_credentials_path):
return self._gdrive_user_credentials_path
return None
@staticmethod
def _validate_credentials(auth, settings):
"""
Detects discrepancy in DVC config and cached credentials file.
Usually happens when a second remote is added and it is using
the same credentials default file. Or when someones decides to change
DVC config client id or secret but forgets to remove the cached
credentials file.
"""
if not os.getenv(GDriveFileSystem.GDRIVE_CREDENTIALS_DATA):
if (
settings["client_config"]["client_id"]
!= auth.credentials.client_id
or settings["client_config"]["client_secret"]
!= auth.credentials.client_secret
):
logger.warning(
"Client ID and secret configured do not match the "
"actual ones used\nto access the remote. Do you "
"use multiple GDrive remotes and forgot to\nset "
"`gdrive_user_credentials_file` for one or more of them? "
"Learn more at\n{}.\n".format(
format_link("https://man.dvc.org/remote/modify")
)
)
@wrap_prop(threading.RLock())
@cached_property
def fs(self):
from pydrive2.auth import GoogleAuth
from pydrive2.fs import GDriveFileSystem as _GDriveFileSystem
temporary_save_path = self._gdrive_user_credentials_path
is_credentials_temp = os.getenv(
GDriveFileSystem.GDRIVE_CREDENTIALS_DATA
)
if self._use_service_account:
temporary_save_path = self._gdrive_service_credentials_path
if is_credentials_temp:
with open(temporary_save_path, "w") as cred_file:
cred_file.write(
os.getenv(GDriveFileSystem.GDRIVE_CREDENTIALS_DATA)
)
auth_settings = {
"client_config_backend": "settings",
"save_credentials": True,
"save_credentials_backend": "file",
"save_credentials_file": self._gdrive_user_credentials_path,
"get_refresh_token": True,
"oauth_scope": [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/drive.appdata",
],
}
if self._use_service_account:
auth_settings["service_config"] = {
"client_user_email": self._service_account_user_email,
"client_json_file_path": self._service_account_json_file_path,
}
if is_credentials_temp:
auth_settings["service_config"][
"client_json_file_path"
] = temporary_save_path
else:
auth_settings["client_config"] = {
"client_id": self._client_id or self.DEFAULT_GDRIVE_CLIENT_ID,
"client_secret": self._client_secret
or self.DEFAULT_GDRIVE_CLIENT_SECRET,
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"revoke_uri": "https://oauth2.googleapis.com/revoke",
"redirect_uri": "",
}
GoogleAuth.DEFAULT_SETTINGS.update(auth_settings)
# Pass non existent settings path to force DEFAULT_SETTINGS loadings
gauth = GoogleAuth(settings_file="")
try:
logger.debug(
"GDrive remote auth with config '{}'.".format(
GoogleAuth.DEFAULT_SETTINGS
)
)
if self._use_service_account:
gauth.ServiceAuth()
else:
gauth.CommandLineAuth()
GDriveFileSystem._validate_credentials(gauth, auth_settings)
# Handle AuthenticationError, RefreshError and other auth failures
# It's hard to come up with a narrow exception, since PyDrive throws
# a lot of different errors - broken credentials file, refresh token
# expired, flow failed, etc.
except Exception as exc:
raise GDriveAuthError(self.credentials_location) from exc
finally:
if is_credentials_temp:
os.remove(temporary_save_path)
return _GDriveFileSystem(
self._with_bucket(self.path_info),
gauth,
trash_only=self._trash_only,
)
def _with_bucket(self, path):
if isinstance(path, self.PATH_CLS):
return posixpath.join(path.bucket, path.path)
return super()._with_bucket(path)
def _upload_fobj(self, fobj, to_info, size: int = None):
rpath = self._with_bucket(to_info)
self.makedirs(os.path.dirname(rpath))
return self.fs.upload_fobj(fobj, rpath, size=size)
+142
View File
@@ -0,0 +1,142 @@
import errno
import os
import stat
from dvc.utils import is_exec, relpath
from .base import BaseFileSystem
class GitFileSystem(BaseFileSystem): # pylint:disable=abstract-method
"""Proxies the repo file access methods to Git objects"""
sep = os.sep
scheme = "local"
def __init__(self, root_dir, trie):
super().__init__()
self._root = root_dir
self.trie = trie
@property
def rev(self):
return self.trie.rev
def _get_key(self, path):
if isinstance(path, str):
if not os.path.isabs(path):
relparts = path.split(os.sep)
else:
relparts = relpath(path, self._root).split(os.sep)
else:
relparts = path.relative_to(self._root).parts
if relparts == ["."]:
return ()
return tuple(relparts)
def open(
self, path, mode="r", encoding=None, **kwargs
): # pylint: disable=arguments-renamed
# NOTE: this is incorrect, we need to use locale to determine default
# encoding.
encoding = encoding or "utf-8"
key = self._get_key(path)
try:
return self.trie.open(key, mode=mode, encoding=encoding)
except KeyError as exc:
msg = os.strerror(errno.ENOENT) + f"in branch '{self.rev}'"
raise FileNotFoundError(errno.ENOENT, msg, path) from exc
except IsADirectoryError as exc:
raise IsADirectoryError(
errno.EISDIR, os.strerror(errno.EISDIR), path
) from exc
def exists(self, path_info) -> bool:
key = self._get_key(path_info)
return self.trie.exists(key)
def isdir(self, path_info) -> bool:
key = self._get_key(path_info)
return self.trie.isdir(key)
def isfile(self, path_info) -> bool:
key = self._get_key(path_info)
return self.trie.isfile(key)
def walk(self, top, topdown=True, onerror=None, **kwargs):
"""Directory tree generator.
See `os.walk` for the docs. Differences:
- no support for symlinks
"""
if not self.isdir(top):
if onerror:
if self.exists(top):
exc = NotADirectoryError(
errno.ENOTDIR, os.strerror(errno.ENOTDIR), top
)
else:
exc = FileNotFoundError(
errno.ENOENT, os.strerror(errno.ENOENT), top
)
onerror(exc)
return []
key = self._get_key(top)
for prefix, dirs, files in self.trie.walk(key, topdown=topdown):
if prefix:
root = os.path.join(self._root, os.sep.join(prefix))
else:
root = self._root
yield root, dirs, files
def isexec(self, path_info):
if not self.exists(path_info):
return False
mode = self.stat(path_info).st_mode
return is_exec(mode)
def info(self, path_info):
key = self._get_key(path_info)
try:
st = self.trie.stat(key)
except KeyError:
raise FileNotFoundError(
errno.ENOENT, os.strerror(errno.ENOENT), path_info
)
return {
"size": st.st_size,
"type": "directory" if stat.S_ISDIR(st.st_mode) else "file",
}
def stat(self, path):
key = self._get_key(path)
try:
return self.trie.stat(key)
except KeyError:
raise FileNotFoundError(
errno.ENOENT, os.strerror(errno.ENOENT), path
)
def walk_files(self, path_info, **kwargs):
for root, _, files in self.walk(path_info, **kwargs):
for file in files:
# NOTE: os.path.join is ~5.5 times slower
yield f"{root}{os.sep}{file}"
def _download(
self, from_info, to_file, name=None, no_progress_bar=False, **kwargs
):
import shutil
from dvc.progress import Tqdm
with open(to_file, "wb+") as to_fobj:
with Tqdm.wrapattr(
to_fobj, "write", desc=name, disable=no_progress_bar
) as wrapped:
with self.open(from_info, "rb", **kwargs) as from_fobj:
shutil.copyfileobj(from_fobj, wrapped)

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