Compare commits

...
547 Commits
Author SHA1 Message Date
Ruslan Kuprieiev 30f4551283 dvc: bump to 0.76.0 2019-12-13 03:20:43 +02:00
Ruslan Kuprieiev 602a34dad2 Merge pull request #2918 from efiop/lock
dvc: make flufl.lock opt-in and use zc.lockfile
2019-12-13 03:17:25 +02:00
Ruslan Kuprieiev 25807f2690 dvc: make flufl.lock opt-in and use zc.lockfile
As it turned out (see issue numbers down below), we can't really take
hardlinks for granted, so `flufl.lock` is not a panacea for all
filesystems. Considering that the vast majority of filesystems that our
users use support `zc.lockfile`(flock-based) and it has benefits like
more reliable mechanism, auto-delete when process exits, more sturdy
implementation, etc, it makes more sense to bring it back and use by
default again. For filesystems that don't support `flock()`, users will
be able to manually enable `flufl.lock` use through the config option.
It would be ideal if we could auto-detect that flock is not supported,
but in the real world, it turned out to be non-trivial, as it might hang
forever in a kernel context, which makes the implementation way too
complex for our purposes. So what we're doing instead is showing a
message before locking with `zc.lockfile` that, under normal
circumstances will disappear once the lock is taken or failed, otherwise
it will point users to the related documentation where they can learn
about how to opt-in for `flufl.lock`.

Fixes #2831
Fixes #2897
Related #2860
2019-12-13 02:25:57 +02:00
Ruslan Kuprieiev 6ab2322cde Merge pull request #2938 from Suor/fix-dir-helpers
test: a couple of dir helpers fixes
2019-12-12 23:20:46 +02:00
Ruslan Kuprieiev fd62ba840d Merge pull request #2934 from pared/2896_checkout
checkout: test: refactor to dir helpers
2019-12-12 04:42:31 +02:00
Ruslan Kuprieiev e2d783aafd Merge pull request #2932 from pared/2896_commit
commit: test: refactor to dir helpers
2019-12-11 20:18:30 +02:00
Ruslan Kuprieiev 08347c06ee Merge pull request #2939 from casperdcl/snap
snap: remove plugs (incompatible with classic)
2019-12-11 20:04:51 +02:00
Casper da Costa-Luis ed95000bb4 remove plugs (incompatible with classic)
Details: <https://launchpad.net/bugs/1655369>
2019-12-11 17:38:09 +00:00
pawel 5f826dd2e6 commit: test: refactor to dir helpers 2019-12-11 14:23:44 +01:00
Alexander Schepanovski b8cf906edb test: stop erepo_dir from undoing parent chdir 2019-12-11 20:19:38 +07:00
Alexander Schepanovski a1643d576d test: fix run_copy fixture when used with scm 2019-12-11 20:19:02 +07:00
pawel 26ef77cf96 checkout: test: refactor to dir helpers 2019-12-11 13:54:44 +01:00
Ruslan Kuprieiev 53c33a22d9 Merge pull request #2915 from maykulkarni/import-external-repo-fix
use cache dir as a default remote when importing from local repo
2019-12-11 08:45:22 +02:00
Mayur Kularni e1e0cbd038 moved default check out of RemoteConfig 2019-12-11 10:54:59 +05:30
Ruslan Kuprieiev 6296e12f48 setup: update pyarrow version
https://github.com/apache/arrow/pull/4907
2019-12-11 06:24:49 +02:00
Ruslan Kuprieiev 5cc92a9b4c Merge pull request #2907 from efiop/import_get
import-url: add directories to examples
2019-12-11 03:23:50 +02:00
Ruslan Kuprieiev d2f40703cd Merge pull request #2926 from pared/2896_test_move
test: move: convert to dir helpers
2019-12-11 02:44:14 +02:00
Ruslan Kuprieiev 8a7cf747e9 dvc: bump to 0.75.1 2019-12-10 23:04:39 +02:00
Ruslan Kuprieiev 6bf8d487da Merge pull request #2921 from pared/2896_add_3
add: test: convert to dir helpers 3
2019-12-10 19:58:15 +02:00
Ruslan Kuprieiev a9e829ae4e Merge pull request #2925 from Suor/dani-cleaup
get: refactor, clean up and fix `dvc get` implementation
2019-12-10 19:20:54 +02:00
Mayur Kularni e6285f70c5 typo in doc fix 2019-12-10 15:38:54 +05:30
Mayur Kularni 40c392d312 type and doc fix 2019-12-10 15:31:58 +05:30
Mayur Kularni a1ae3578c0 Fix caplog test case and move call to CmdRemoteAdd 2019-12-10 11:49:19 +05:30
Ruslan Kuprieiev 9c2c2b307d s3: dynamically import botocore
https://github.com/iterative/dvc/issues/2927
2019-12-10 01:02:50 +02:00
Paweł Redzyński 267ac9b3ff test: move: convert to dir helpers 2019-12-09 19:27:28 +01:00
Alexander Schepanovski 9607e5f873 get: refactor, clean up and fix dvc get implementation
Things changed:
- both no output and no git file result into PathMissingError now
- fail on missing cache or cache download error loudly
- removed sqlite bypass, since we use nolock now anyway

Refactoring:
- streamlined logic and made it similar to Repo.open() one
- moved exclusive exception classes to dvc.repo.get
2019-12-09 21:02:49 +03:00
Ruslan Kuprieiev b3853b0b2b Merge pull request #2924 from Abrosimov-a-a/master
build_deb: Debian directory appended. Issue #2800
2019-12-09 19:21:40 +02:00
Ruslan Kuprieiev 26a9702a4a Merge pull request #2837 from danihodovic/feat/2515
get: copy/download files tracked by Git
2019-12-09 18:57:52 +02:00
Anton Abrosimov 93280c937b build_deb: Debian directory appended. Issue #2800 2019-12-09 19:14:53 +03:00
Ruslan Kuprieiev 237704ef2b Merge pull request #2908 from Suor/better-fixtures
test: write a usage text for dir_helpers
2019-12-09 18:01:33 +02:00
Ruslan Kuprieiev 8890daf703 Merge pull request #2826 from mroutis/refactor-analytics
analytics: refactor into a module
2019-12-09 17:59:51 +02:00
pawel c4565ce2ac add: test: convert to dir helpers 3
TestAddUnsupportedFile
TestAdd
TestAddDirectory
2019-12-09 16:44:56 +01:00
Ruslan Kuprieiev 24fc8beaeb Update test_ignore.py 2019-12-09 15:27:54 +02:00
Ruslan Kuprieiev 302b016d65 Merge pull request #2913 from Suor/migrate-test-ignore
test: migrate test_ignore
2019-12-09 15:25:46 +02:00
Mayur Kularni b271368845 Moved remote_config_exists into RemoteConfig 2019-12-09 14:14:49 +05:30
Alexander SchepanovskiandMr. Outis 1da133b9ed test: update dir_helpers usage
Co-Authored-By: Mr. Outis <[email protected]>
2019-12-09 11:34:10 +03:00
Alexander SchepanovskiandMr. Outis a59041aeab test: update dir_helpers usage
Co-Authored-By: Mr. Outis <[email protected]>
2019-12-09 11:33:35 +03:00
Alexander SchepanovskiandMr. Outis 0cd6106189 Update tests/dir_helpers.py
Co-Authored-By: Mr. Outis <[email protected]>
2019-12-09 11:24:41 +03:00
Mayur Kularni 5b9daea2b3 fixed test case 2019-12-09 12:11:49 +05:30
Mayur Kularni 84616e8e5e removed commit logic 2019-12-08 20:25:24 +05:30
Mayur Kularni 17d47ce153 merging master 2019-12-08 19:03:25 +05:30
Mayur Kularni 610e43a0ab removed original repo ops 2019-12-08 18:57:19 +05:30
Mayur Kularni c6050efbe3 codeclimate recommendation fix 2019-12-08 18:57:19 +05:30
Mayur Kularni ca20a1c93a incorporated feedback 2019-12-08 18:56:38 +05:30
Mayur Kulkarni e1c5f6c5c0 external_repo: fix for local file import fail
If the URL is local dvc repo, the import/get should fetch from the source project cache.

Fixes #2599
2019-12-08 18:52:35 +05:30
Mayur Kularni 779a0a1b15 codeclimate recommendation fix 2019-12-08 14:45:15 +05:30
Mayur Kularni 9e24be14f0 incorporated feedback 2019-12-08 14:02:44 +05:30
Ruslan Kuprieiev 3796c4182b Merge pull request #2906 from pared/2896_add_2
add: test: convert to dir helpers 2
2019-12-08 05:46:17 +02:00
Mr. Outis 5dd6300878 💅 black 2019-12-07 21:19:54 -06:00
Mr. Outis 6e4c3d6acd tests: require keys on analytics report schema 2019-12-07 21:18:28 -06:00
Mr. Outis 3244f10e7f 💅 add dot to the end of the comment 2019-12-07 21:15:49 -06:00
Mr. Outis c9f958dd00 analytics: AttributeError -> KeyError 2019-12-07 21:15:38 -06:00
Mr. Outis 146e75be3e analytics: raise error when collecting a not supported os 2019-12-07 21:15:11 -06:00
Mr. Outis c50bd17bb4 analytics: collect execution info only when available 2019-12-07 21:14:31 -06:00
Alexander Schepanovski 6c12a2e555 test: fix unicode test_ignore issue under Windows Py2 2019-12-07 17:17:18 +01:00
Mayur Kulkarni 0dd310d387 external_repo: fix for local file import fail
If the URL is local dvc repo, the import/get should fetch from the source project cache.

Fixes #2599
2019-12-07 18:39:32 +05:30
Ruslan Kuprieiev 36336f389d dvc: bump to 0.75.0 2019-12-07 14:54:36 +02:00
Alexander Schepanovski 79902988fc test: fix files set comparisons in dvcignore tests under windows 2019-12-07 13:17:08 +01:00
Alexander Schepanovski 77fcd75b7d test: use utf-8 in tmp_dir.gen() 2019-12-07 13:16:42 +01:00
Alexander Schepanovski 42096c5bfc test: migrate test_ignore to new helpers 2019-12-07 11:09:52 +01:00
Alexander Schepanovski c6c67ace20 test: create empty dirs via tmp_dir.gen() 2019-12-07 10:32:37 +01:00
Ruslan Kuprieiev 82dd1ac7c1 Merge pull request #2815 from xliiv/2807-use-pager
dvc/dagascii: Use pager instead of AsciiCanvas._do_draw
2019-12-07 01:16:16 +02:00
Tymoteusz Jankowski 44df59f96c Fix test_dagascii tests 2019-12-06 23:36:21 +01:00
Mr. Outis 2145daa130 analytics: define private methods 2019-12-06 16:26:10 -06:00
Tymoteusz Jankowski ffefab152e Remove make_pager.cmd attribute 2019-12-06 22:17:44 +01:00
Ruslan Kuprieiev 0a7abf13d5 dvc: bump to 0.74.0 2019-12-06 23:15:53 +02:00
Tymoteusz Jankowski ed4e951f22 Use monkeypatch in test_dagascii.py 2019-12-06 21:58:56 +01:00
Dani Hodovic 29b34bacc2 fixup! fixes 2019-12-06 21:55:54 +01:00
Tymoteusz Jankowski 6cfc831879 Use mocker fixture in test_dagascii.py 2019-12-06 21:54:40 +01:00
Mr. Outis 5a8033415d tests: use str, builtin_str in schema 2019-12-06 13:54:17 -06:00
Tymoteusz Jankowski 8fb754a4f4 Simplify if in find_pager 2019-12-06 20:42:10 +01:00
Ruslan Kuprieiev eb200db442 Merge pull request #2909 from iterative/efiop-patch-1
remote: base: download: don't walk dir twice
2019-12-06 21:37:19 +02:00
Dani Hodovic d59bceb71a fixes 2019-12-06 20:20:25 +01:00
Ruslan Kuprieiev cf171f7ec3 fixup 2019-12-06 21:09:53 +02:00
Ruslan Kuprieiev 52511714c0 remote: base: download: don't walk dir twice 2019-12-06 20:49:45 +02:00
Mr. Outis da064e2ede analytics: remove the report after sending it 2019-12-06 12:28:06 -06:00
Dani Hodovic 811855ade2 fixes 2019-12-06 18:56:19 +01:00
Alexander Schepanovski 5bec700761 test: accept Paths in tmp_dir.gen() 2019-12-06 18:36:17 +01:00
Alexander Schepanovski 659eb04705 test: write a usage text for dir_helpers 2019-12-06 18:34:05 +01:00
Mr. Outis f704e5468b tests: stringify tmp_global_config since it doesnt return a pathlike object 2019-12-06 10:20:30 -06:00
Ruslan Kuprieiev 9e6e430935 import-url: add directories to examples
https://github.com/iterative/dvc/pull/2894
2019-12-06 16:56:12 +02:00
pawel b39ebc8591 add: test: convert to dir helpers
test_should_relink_on_repeated_add
test_should_protect_on_repeated_add
2019-12-06 15:35:32 +01:00
Ruslan Kuprieiev 9b6644a97e Merge pull request #2898 from pared/2896_add_1
add: test: convert to dir helpers 1
2019-12-06 16:25:27 +02:00
Ruslan Kuprieiev 8fa26825ee Merge pull request #2894 from odd-industries/master
import-url: support directories
2019-12-06 16:12:04 +02:00
Vera Sativa 896b08c11d fix: test requirements 2019-12-06 10:55:18 -03:00
Vera Sativa b2e3fd2dbd fix: test imports 2019-12-06 10:20:53 -03:00
Vera Sativa 3603da89ec fix: respect no_progress_bar and remove human else 2019-12-06 10:08:02 -03:00
Vera Sativa 120f087f1c fix: tqdm reading futures 2019-12-06 10:04:05 -03:00
pawel 551f5af561 add: test: convert to dir helpers
test_add_file_in_dir
test_readding_dir_should_not_unprotect_all
test_should_not_checkout_when_adding_cached_copy
2019-12-06 09:07:50 +01:00
Mr. Outis c82d9dcbc1 compat: bring back unicode literals 2019-12-05 23:18:44 -06:00
Mr. Outis bc2471cd30 tests: adjust scm_class schema 2019-12-05 19:56:45 -06:00
Mr. Outis 7e8bdbd603 remove pathlib / fspath changes related to the patch 2019-12-05 19:52:03 -06:00
Ruslan Kuprieiev 13f6101e70 Merge pull request #2902 from mroutis/fix-2570
setup: fix compatibility errors between dependencies
2019-12-06 02:11:27 +02:00
Ruslan Kuprieiev d6ecad0b7c Update version.py 2019-12-06 02:10:34 +02:00
Ruslan Kuprieiev d2faa0f547 travis: don't forget "on" for deployment 2019-12-06 02:10:19 +02:00
Mr. Outis 2237ab6a51 setup: fix only mayor minor version for pyyaml 2019-12-05 17:47:01 -06:00
Vera Sativa 2ea4d26f46 Merge branch 'master' of github.com:odd-industries/dvc 2019-12-05 18:03:13 -03:00
Vera Sativa a0f18696dc refactor: partial into function var 2019-12-05 18:03:03 -03:00
VeraandRuslan Kuprieiev 4e53f17948 Update tests/unit/remote/test_remote_dir.py
Co-Authored-By: Ruslan Kuprieiev <[email protected]>
2019-12-05 17:41:17 -03:00
Vera Sativa 6f649db7f4 import-url: support directories: code cleanup 2019-12-05 17:39:38 -03:00
Dani Hodovic 7debaca22e fixup! fix error message for git files 2019-12-05 21:05:38 +01:00
Dani Hodovic 9ae4ab88bf fix error message for git files 2019-12-05 21:05:04 +01:00
Dani Hodovic b17d1c5b04 fixup 2019-12-05 21:05:03 +01:00
Dani Hodovic 20e7696da3 return early 2019-12-05 21:05:03 +01:00
Dani Hodovic 3c2633e33f Pass all tests 2019-12-05 21:05:03 +01:00
Dani Hodovic 38bed227c6 fixup 2019-12-05 21:05:03 +01:00
Dani Hodovic 9930e96390 fixup! ensure absolute paths for dvc files work but not for git 2019-12-05 21:05:03 +01:00
Dani Hodovic 97793b4fb3 underscore private function 2019-12-05 21:05:03 +01:00
Dani Hodovic f829338e1e if instead of try / catch 2019-12-05 21:05:02 +01:00
Dani Hodovic 9ece4ed8ad ensure absolute paths for dvc files work but not for git 2019-12-05 21:05:02 +01:00
Dani Hodovic ed10497854 Remove unused var 2019-12-05 21:05:02 +01:00
Dani Hodovic cadbf10e64 dont use absolute paths in tests 2019-12-05 21:05:02 +01:00
Dani Hodovic 74f3851117 Disallow absolute paths in dvc get 2019-12-05 21:05:02 +01:00
Dani Hodovic 1f5da3ffef fix CLI docs 2019-12-05 21:05:01 +01:00
Dani Hodovic 7e1423d13c fix tests 2019-12-05 21:05:01 +01:00
Dani Hodovic 26b62331c7 CLI --help changes 2019-12-05 21:05:01 +01:00
Dani Hodovic 8c3ffe816d fixup! Make tree.walk() output deterministic 2019-12-05 21:05:01 +01:00
Dani Hodovic 97862a3d5f Make tree.walk() output deterministic 2019-12-05 21:05:01 +01:00
Dani Hodovic 4d14256995 fixup! get: copy regular files 2019-12-05 21:05:01 +01:00
Dani Hodovic af2f216c5e get: copy regular files
Allows `dvc get` to copy regular files or directories.

fixes: #2515
2019-12-05 21:05:00 +01:00
Vera Sativa f7e0b575bd tests: add download_dir 2019-12-05 16:51:24 -03:00
VeraandRuslan Kuprieiev 164ded6453 Update dvc/remote/base.py
Co-Authored-By: Ruslan Kuprieiev <[email protected]>
2019-12-05 16:45:43 -03:00
VeraandRuslan Kuprieiev 3af4f85310 Update dvc/remote/base.py
Co-Authored-By: Ruslan Kuprieiev <[email protected]>
2019-12-05 16:45:13 -03:00
Vera Sativa cf6da7bd8c import-url: support directories: multi-thread working 2019-12-05 16:28:35 -03:00
Mr. Outis 4fd701317b setup: fix compatibility errors between dependencies
fix #2570
2019-12-05 13:19:23 -06:00
Vera Sativa f6c719a6f3 feat: [WIP] dir download working single-thread 2019-12-05 14:53:01 -03:00
Ruslan Kuprieiev 4917a6e818 dvc: bump to 0.72.0 2019-12-05 16:25:14 +02:00
Ruslan Kuprieiev 73e69be067 Merge pull request #2882 from Suor/revert-checkout
dvc: revert checkout to be silly and fast, relink on dvc add though
2019-12-05 16:07:52 +02:00
Vera Sativa 9d0cfcafdf feat: [WIP] remote dir download 2019-12-05 11:05:47 -03:00
Ruslan Kuprieiev c328075c8b Merge pull request #2868 from Suor/better-fixtures
test: refactor tmp dir helper fixtures
2019-12-05 16:00:37 +02:00
Ruslan Kuprieiev baee120729 Merge pull request #2892 from efiop/2885
state: quote % in path
2019-12-05 15:59:24 +02:00
Ruslan Kuprieiev 57b0596bfe state: quote % in path
Fixes #2885
2019-12-05 15:25:21 +02:00
Mr. Outis 71ead3f62e analytics: use a tempfile for inter-process communication 2019-12-04 19:47:06 -06:00
Mr. Outis 05a68684fd tests: isolate global config from analytics tests
fix #2806
2019-12-04 19:44:12 -06:00
Mr. Outis 7a3aae1c7e tests: add unit test for analytics.system_info 2019-12-04 19:43:00 -06:00
Mr. Outis ede010384b py2: use convert_to_unicode instead of str 2019-12-04 19:31:34 -06:00
Tymoteusz Jankowski 128f60ef68 Add test_dagascii.py + make find_pager() use DVC_PAGER 2019-12-04 20:51:24 +01:00
Tymoteusz Jankowski c864ca9384 Move is_exec_found(..) to dvc/dagascii.py 2019-12-04 20:15:49 +01:00
Ruslan Kuprieiev 516e3b14bc Merge pull request #2852 from pared/2848_get_no_dvc_master
external repo: checkout revision before initializing dvc repo
2019-12-04 16:20:00 +02:00
pawel ca852170c1 get: test: from non-dvc master: refactor 2019-12-04 14:34:50 +01:00
Paweł Redzyński f1d59dd64d conftest: empty dir is tmp_path now 2019-12-04 13:10:06 +01:00
Paweł Redzyński 97e4b024c1 external repo: move revision check out of checkout function 2019-12-04 13:10:06 +01:00
pawel 5e5c82652b test: get: inline erepo with non-dvc master 2019-12-04 13:10:06 +01:00
Paweł Redzyński 5c0c4239ec rename TestDirFixtureBase 2019-12-04 13:10:06 +01:00
Paweł Redzyński b9d5933f5d external repo: checkout rev before initializing repo 2019-12-04 13:10:06 +01:00
Paweł Redzyński b788a991f9 conftest: create empty dir fixture 2019-12-04 13:10:06 +01:00
Paweł Redzyński 06d6c2c25c test env: extract empty dir fixture to class 2019-12-04 13:10:06 +01:00
Mr. Outis 02eaa9a14f py35: support for fspath(pathlib.Path()) 2019-12-03 18:38:15 -06:00
Mr. Outis 285620d96e tests: mock analytics.collect while testing daemon 2019-12-03 09:41:23 -06:00
Ruslan Kuprieiev e63bc01e53 Merge pull request #2883 from casperdcl/readme-native
much grammar such fix
2019-12-03 15:24:20 +02:00
Ruslan Kuprieiev 17788790a0 Merge pull request #2857 from casperdcl/short-progress
lengthen bars unless nested/threaded
2019-12-03 14:56:31 +02:00
Ruslan Kuprieiev f142188373 Merge pull request #2875 from casperdcl/stream-progress
Stream progress
2019-12-03 14:55:56 +02:00
Casper da Costa-Luis 5aa2127bfe such syntax much wow 2019-12-03 11:13:43 +00:00
Mr. Outis 624cbb2d91 💅 more specific comment about analytics 2019-12-02 23:46:04 -06:00
Mr. Outis 8a32b28b54 analytics: collect and send on daemon 2019-12-02 22:54:16 -06:00
Mr. Outis 8b5ca253ec analytics: move report schema to tests 2019-12-02 22:48:09 -06:00
Mr. Outis e0d47a1382 💅 formatting 2019-12-02 21:57:06 -06:00
Mr. Outis ca485db240 analytics: define report schema and use it on tests 2019-12-02 21:52:00 -06:00
Mr. Outis 2c8e1495b8 analytics: use fspath instead of str 2019-12-02 21:27:31 -06:00
Casper da Costa-Luis 27d2e71460 fix rst syntax 2019-12-03 01:36:04 +00:00
Casper da Costa-Luis 464bcad82e much grammar such fix 2019-12-03 00:55:26 +00:00
Mr. Outis 55960154c5 analytics: document why tmp_global_config is needed 2019-12-02 17:51:35 -06:00
Mr. Outis 946dfc1400 analytics: send report without loading it to memory
shout-out to @Suor for the suggestion
2019-12-02 17:51:32 -06:00
Jorge Orpinel f91aa0665f Merge pull request #2881 from pared/2711_3
NoRemoteInExternalRepoError: fix message
2019-12-02 17:15:58 -06:00
c27988a042 test: minor fixture refactor fixes from @pared and @efiop
Co-Authored-By: Ruslan Kuprieiev <[email protected]>
Co-Authored-By: Paweł Redzyński <[email protected]>
2019-12-02 23:33:44 +01:00
Alexander Schepanovski 0385810662 test: fix python 2, 3.5 and mac issues with new fixtures 2019-12-02 23:32:11 +01:00
Alexander Schepanovski fcebad821e test: refactor tmp dir helper fixtures
- `tmp_dir` descends from Path
- convenient template methods to generate files and dirs, add them to
dvc and git and commit to git
- independent of old basic_env structures
- can go in any order
- modular: start with empty `tmp_dir`, initialize git with `scm`, dvc
repo with `dvc` add "repo template" with `repo_template`, ...
2019-12-02 23:31:54 +01:00
Alexander Schepanovski d1cf770a3c dvc: don't make an unneeded copy on commit
Fix #2016 without all the complications of #2358.
2019-12-02 23:08:45 +01:00
Ruslan Kuprieiev c2ffff0a0f updater: add an entry for chocolatey package
https://github.com/iterative/chocolatey-dvc/issues/1
2019-12-02 22:52:22 +02:00
Tymoteusz Jankowski 8977958371 Make pager info box to be a simple warning 2019-12-02 20:36:51 +01:00
Tymoteusz Jankowski ab3ccf0243 Use os.devnull + do not check if system exists for os module 2019-12-02 20:27:20 +01:00
Alexander Schepanovski 30d58e14a0 Revert "remote: base: don't checkout existing file (#2358)"
This reverts commit 5ec1d274ae.

Save tests to try make them work, and `_get_cache_type()` to be used
later.
2019-12-02 19:06:35 +01:00
Casper da Costa-Luis c622287cd3 truncate long desc 2019-12-02 17:43:11 +00:00
Casper da Costa-Luis 7318a6d120 safer download notification 2019-12-02 17:35:29 +00:00
Ruslan Kuprieiev d42a04e647 Merge pull request #2778 from casperdcl/snap
Snap
2019-12-02 19:09:25 +02:00
Casper da Costa-Luis 52c48b56a2 add snap approval request to readme 2019-12-02 16:44:12 +00:00
Casper da Costa-Luis 0acb1e8e3e revert devmode
- didn't work
- almost completely reverts c94acdf2a9
2019-12-02 16:39:18 +00:00
Alexander Schepanovski 4ab4e43606 dvc: return to using .changed() shortcut in .checkout() 2019-12-02 14:38:39 +01:00
Paweł Redzyński 9aec435cac NoRemoteInExternalRepoError: fix message 2019-12-02 11:58:09 +01:00
Tymoteusz Jankowski f79711d155 Fix code checks 2019-12-01 19:48:56 +01:00
Tymoteusz Jankowski a6a3cbcffa Reduce nested ifs in find_pager 2019-12-01 19:45:06 +01:00
Tymoteusz Jankowski 3e163f1934 Rename and move DVC_PAGER_ENV_NAME to env.py file 2019-12-01 19:13:56 +01:00
Tymoteusz Jankowski cade3b396c Add is_exec_found to utils and use it in dagascii module 2019-12-01 19:04:04 +01:00
Ruslan Kuprieiev 3d70a1efef Merge pull request #2853 from skshetry/gs-external-deps
Support adding directories in google cloud storage remote
2019-12-01 14:49:35 +02:00
Tymoteusz Jankowski c17cf2b2f7 dvc/dagascii: Use less instead of AsciiCanvas._do_draw
Uses less CLI tool to paging the output in the interactive mode while
doing e.g.  `dvc pipeline show ..`.

Fixes #2807
2019-12-01 13:23:09 +01:00
Saugat Pachhai 6ba7b1d9a3 gs: adjust tests 2019-12-01 17:26:07 +05:45
Ruslan Kuprieiev 24d3a87708 Merge pull request #2873 from skshetry/fix-2871
s3: Check for all files in given path to match
2019-12-01 13:24:50 +02:00
Saugat Pachhai 37fcced4ad s3: raise exception if not 404 status code 2019-12-01 16:28:31 +05:45
Saugat Pachhai 4f011f62c7 use yield and staticmethod 2019-12-01 16:24:22 +05:45
Saugat Pachhai 22c0a644e5 address @efiop's suggestions 2019-12-01 16:08:53 +05:45
Saugat Pachhai 4821aa2efa s3: catch specific exception when head_object() 2019-12-01 14:47:56 +05:45
Saugat Pachhai da0d9e8599 s3: implement isfile() 2019-12-01 10:58:53 +05:45
Saugat Pachhai f4f836e17c Merge branch 'master' into gs-external-deps
* master:
  travis: windows: use python 3.7.5
  test: skip non supported remotes fast in api tests
2019-12-01 09:01:47 +05:45
Saugat Pachhai 7e62a7d7e5 gs: implement isfile 2019-12-01 08:56:07 +05:45
Casper da Costa-Luis f54460f2cb merge unneeded function 2019-12-01 02:54:01 +00:00
Saugat Pachhai 25f0e0765c gs: use Blob.exists() instead of _list_paths to check for blob 2019-12-01 08:25:50 +05:45
Casper da Costa-Luis 75f8b9cd8b start to clean up gdrive 2019-12-01 02:31:57 +00:00
Casper da Costa-Luis ecdc608e13 clean up gs progress using Tqdm.wrapattr
- fixes #2829
2019-12-01 02:22:04 +00:00
Ruslan Kuprieiev f09517ad09 travis: windows: use python 3.7.5 2019-11-30 20:11:11 +02:00
Casper da Costa-Luis c94acdf2a9 use --devmode as it looks less scary 2019-11-30 17:26:48 +00:00
Casper da Costa-Luis a0ac129d5f cleanup things to revert after classic
https://forum.snapcraft.io/t/classic-confinement-request-for-dvc/14124
2019-11-30 17:23:57 +00:00
Casper da Costa-Luis 7fcb4bff9c back to --classic 2019-11-30 17:22:12 +00:00
Saugat Pachhai 2e1f3114cd dvc: write tests 2019-11-30 19:47:30 +05:45
Saugat Pachhai 13e5dbafd0 s3: check for all files in given path 2019-11-30 19:20:32 +05:45
Saugat Pachhai 433d2f8017 test: remove nested contextmanagers, return False on exists() 2019-11-30 19:12:35 +05:45
Ruslan Kuprieiev 30848306e8 Merge pull request #2870 from Suor/test-api-fast-skip
test: skip non supported remotes fast in api tests
2019-11-30 15:15:00 +02:00
Saugat Pachhai 8a62e2f92b gs: check if the path is directory when checking for exists 2019-11-30 18:03:41 +05:45
Saugat Pachhai 42991d3b75 Move more stuff from data_cloud to tests/remotes.py 2019-11-30 10:04:45 +05:45
Saugat Pachhai 63cf117ee9 Move to tests/remotes.py 2019-11-30 08:58:43 +05:45
Saugat Pachhai 4714929b5b Merge branch 'master' into gs-external-deps
* master:
  address @efiop's suggestions
  s3: add support for top level empty directories
  remote: http: calculate length basing on response and not head call
  test: remote: http: clear test file name
  HTTPError: reformat error message
  Update dvc/remote/http.py
  remote: http: rename http error, refactor test
  remote: http: raise exception when download response with error status code
  Support GDrive as remote
  setup: don't forget contextlib2
  remote: refactor ssh ask password code
  remote: protect all remote client/session creation code with locks
  test: refactor & remove redundant test fixtures
  NoRemoteInExternalRepoError: dont pass cause of exception
  perf: optimize cache listing for local, ssh and hdfs
  remote: small .save_info()/.get_checksum() cleanup
2019-11-30 08:39:56 +05:45
Saugat Pachhai caa67c725e dvc: refactor tests 2019-11-30 08:22:39 +05:45
Casper da Costa-Luis ae274cbab7 try devmode 2019-11-30 01:46:48 +00:00
Casper da Costa-Luis f6e92184e3 ready for merge 2019-11-30 01:19:13 +00:00
Casper da Costa-Luis 8fa7d7cfd7 fix git 2019-11-30 01:01:20 +00:00
Casper da Costa-Luis f60d795709 document connecting plugs before approval 2019-11-30 00:35:41 +00:00
Casper da Costa-Luis ebe80cd60a attempt to use layout 2019-11-30 00:28:56 +00:00
Ruslan Kuprieiev 0404a2324e Merge pull request #2551 from MaxRis/gdrive-support
GDrive remote support
2019-11-30 01:21:31 +02:00
Alexander Schepanovski 865ed6f8ea test: skip non supported remotes fast in api tests 2019-11-29 23:42:26 +01:00
Casper da Costa-Luis b36a49120b attempt to fix dvc config --system 2019-11-29 21:49:53 +00:00
Casper da Costa-Luis d0cb359060 try strict confinement 2019-11-29 21:19:44 +00:00
Casper da Costa-Luis b713a62be9 potential git discovery for removable media fix 2019-11-29 21:17:03 +00:00
Casper da Costa-Luis 537d51b535 use strict-confinement-compatible git 2019-11-29 21:16:33 +00:00
Casper da Costa-Luis 577ad548b0 move snapcraft to subdir 2019-11-29 19:41:41 +00:00
Ruslan Kuprieiev 94f803e043 Merge pull request #2683 from mroutis/fix-2678
s3: ignore empty directories while walking files
2019-11-29 21:00:39 +02:00
Casper da Costa-Luis c0058a6f66 actually use PKG to determine package type 2019-11-29 18:46:57 +00:00
Casper da Costa-Luis f616265c95 create dedicated SNAP env var
only for update skipping for now, instead of piggybacking off $CI
2019-11-29 18:39:05 +00:00
Mr. Outis 1f9a0f0e0f address @efiop's suggestions 2019-11-29 12:38:12 -06:00
Mr. Outis 845cd352f7 s3: add support for top level empty directories 2019-11-29 11:46:28 -06:00
Ruslan Kuprieiev 6ad278f54c Merge pull request #2794 from pared/2510
remote: http: raise exception when response with error status code
2019-11-29 16:58:47 +02:00
pawel 03c27192a6 remote: http: calculate length basing on response and not head call 2019-11-29 15:31:04 +01:00
pawel f7846a5bed test: remote: http: clear test file name 2019-11-29 15:30:55 +01:00
pawel c102a781c2 HTTPError: reformat error message 2019-11-29 15:30:55 +01:00
Paweł RedzyńskiandAlexander Schepanovski e084b427e3 Update dvc/remote/http.py
Co-Authored-By: Alexander Schepanovski <[email protected]>
2019-11-29 15:30:55 +01:00
Paweł Redzyński 7218ff202c remote: http: rename http error, refactor test 2019-11-29 15:30:55 +01:00
Paweł Redzyński 72abfb0572 remote: http: raise exception when download response with error status code 2019-11-29 15:30:55 +01:00
Max Risuhin e7227898e4 Support GDrive as remote 2019-11-29 01:21:10 -08:00
Ruslan Kuprieiev 510ff8a9da Merge pull request #2863 from Suor/locks
remote: protect all remote client/session creation code with locks
2019-11-29 11:18:56 +02:00
Saugat Pachhai ee69ac835d gs: make it work with empty dir 2019-11-29 07:24:11 +05:45
Ruslan Kuprieiev 593ab17d25 setup: don't forget contextlib2 2019-11-29 02:14:25 +02:00
Mr. Outis 656f986542 💅 correct wording "disenabled" -> "disabled" 2019-11-28 17:47:14 -06:00
Ruslan Kuprieiev 6b4242a904 Merge pull request #2861 from danihodovic/refactor/remove-redundant-vars
test: refactor & remove redundant test fixtures
2019-11-29 01:24:27 +02:00
Alexander Schepanovski 8af1aeefb4 remote: refactor ssh ask password code 2019-11-28 21:10:35 +01:00
Alexander Schepanovski 73eac3b6cc remote: protect all remote client/session creation code with locks
With @cached_property last write wins, so all threads will use the same
session. However, several might be created concurrently wasting
resources and even potentially asking something from the user.

This change guarantees only one client instance will be created for a
remote.
2019-11-28 21:05:20 +01:00
Dani Hodovic 78c505f6cb test: refactor & remove redundant test fixtures
https://github.com/iterative/dvc/pull/2837#discussion_r351318487
2019-11-28 19:04:56 +01:00
Ruslan Kuprieiev 838efb293f Merge pull request #2836 from Suor/lazy-cache-paths
perf: optimize cache listing for local, ssh and hdfs
2019-11-28 13:23:46 +02:00
Ruslan Kuprieiev 00744bc794 Merge pull request #2835 from Suor/opt-checkout
remote: small .save_info()/.get_checksum() cleanup
2019-11-28 08:55:25 +02:00
Saugat Pachhai 54dd9890dc gs: return relative path in list_cache_paths 2019-11-28 07:37:14 +05:45
Mr. Outis e69fffa7c7 py2 compat issues 🤷 2019-11-27 18:39:01 -06:00
Saugat Pachhai db12416eac remove extra args 2019-11-28 06:05:30 +05:45
Saugat Pachhai fe248aa130 dvc: use replace instead of posixpath 2019-11-28 05:47:04 +05:45
Saugat Pachhai 0c08c09e5d dvc: append slash at the end for dir path_info 2019-11-28 05:45:30 +05:45
Ruslan Kuprieiev 78942ca4da Merge pull request #2854 from pared/2711_2
RemoteNotSpecifiedInExternalRepoError: dont pass cause of exception
2019-11-27 22:30:05 +02:00
Mr. Outis 787ea9cd31 tests: set temporary global config 2019-11-27 12:37:11 -06:00
Saugat Pachhai 5e2150d509 remote/gs: set max_results to improve performance when checking exists/isdir 2019-11-28 00:07:49 +05:45
Saugat Pachhai e750496123 remote/gs: support adding directories 2019-11-28 00:07:49 +05:45
Paweł Redzyński 31aed9b27b NoRemoteInExternalRepoError: dont pass cause of exception 2019-11-27 10:05:16 +01:00
Mr. Outis e9d56a969e 💅 sort imports 2019-11-26 19:39:31 -06:00
Mr. Outis 2a5cac5570 💅 deepsource 2019-11-26 19:34:49 -06:00
Mr. Outis f9952046fd tests: functional and unit tests for analytics 2019-11-26 19:29:20 -06:00
Mr. Outis 6bc24039db 💅 black 2019-11-26 18:33:16 -06:00
Mr. Outis 48b4cbad45 💅 change naming and docstring 2019-11-26 18:32:39 -06:00
Mr. Outis 467b110929 py2: FileNotFoundError -> IOError 2019-11-26 18:32:02 -06:00
Casper da Costa-Luis 36fc2b05ad partiallundo reorder 2019-11-26 21:59:44 +00:00
Casper da Costa-Luis 78c9e32312 reorder desc & bar 2019-11-26 21:56:25 +00:00
Casper da Costa-Luis 857c01d075 lengthen bars unless nested/threaded
Partially fixes #2487
2019-11-26 21:29:24 +00:00
Mr. Outis 5261ed7148 tests: analytics.find_or_create_user_id 2019-11-26 13:40:11 -06:00
Ruslan Kuprieiev f9d4ef8abd Update version.py 2019-11-26 20:55:49 +02:00
Ruslan Kuprieiev 5703b17190 Merge pull request #2856 from peper0/master
Possible fix for #2850.
2019-11-26 20:48:07 +02:00
Ruslan Kuprieiev a1cc2e66e1 Merge pull request #2821 from kaiogu/master
remote: fix remote default call with no arguments
2019-11-26 20:37:31 +02:00
Ruslan Kuprieiev 424931f54a Merge pull request #2855 from skshetry/fix-typo
Fix typos
2019-11-26 20:28:44 +02:00
Mr. Outis 34d3004779 :face_palm: fix wrong module 2019-11-26 12:07:18 -06:00
Kaio Giurizatto Utsch 82a535b97e Change logging to print
Python logging recommends using print instead of logging for ordinary
output of command line program.

Also changed the test to reflect this.
2019-11-26 15:02:03 -03:00
Tomasz Łakota a986c6a09d Possible fix for #2850. 2019-11-26 18:25:36 +01:00
Kaio Giurizatto Utsch 95924a34e6 Undo last commit
Turns out that the argument was needed after all
2019-11-26 14:19:09 -03:00
Mr. Outis d958870edf analytics: exit_code -> return_code
shout-out to @efiop for the suggestion
2019-11-26 10:45:21 -06:00
Saugat Pachhai 461a79e1e8 Fix typos 2019-11-26 20:07:20 +05:45
Mr. Outis 610d1ee28e analytics: go back to daemon implementation
Lack of a better way to spawn a detached process on both windows an linux
2019-11-25 22:30:16 -06:00
Mr. Outis 5cb437b5ef analytics: move to single file
shout-out to @Suor
2019-11-25 22:30:05 -06:00
Mr. Outis 2e7a6d3fe5 💅 sort methods 2019-11-25 22:30:05 -06:00
Mr. Outis 64d102caaa analytics: move user_id module to a method
shout-out to @Suor for the suggestion
2019-11-25 22:30:03 -06:00
Mr. Outis 7d3cbd789c analytics: collect system info under the same module 2019-11-25 21:41:17 -06:00
Mr. Outis b618d03cfd analytics: return OS when system info not supported 2019-11-25 21:30:25 -06:00
Kaio Giurizatto Utsch 9611107896 Remove unused argument 2019-11-25 17:51:49 -03:00
Kaio Giurizatto Utsch c2def64dc7 remote: add test case to show default 2019-11-25 17:35:45 -03:00
Ruslan Kuprieiev 38be142497 Merge pull request #2842 from skshetry/fix-typo
Fix typo and grammar
2019-11-24 12:58:03 +02:00
Saugat Pachhai 7a678f3322 dvc: fix grammar in PR template 2019-11-24 15:44:42 +05:45
Saugat Pachhai 2859b53cae dvc: fix typo in data_cloud.py module docstring 2019-11-24 15:38:54 +05:45
Casper da Costa-Luis ba12e1c457 test eveything apart from gssapi 2019-11-24 00:20:37 +00:00
Casper da Costa-Luis 5f13e2215d bash completion 2019-11-23 23:53:52 +00:00
Casper da Costa-Luis 8e58849f0b suppress update check, add comments 2019-11-23 23:25:42 +00:00
Casper da Costa-Luis d82bb976e6 full installation 2019-11-23 23:24:01 +00:00
Alexander Schepanovski 8681cc3cd1 perf: optimize cache listing for local, ssh and hdfs
- make all three lazy
- simplify local one
- use deque instead of list for hdfs
2019-11-22 14:43:01 +01:00
Alexander Schepanovski 8da0390c57 remote: small .save_info()/.get_checksum() cleanup 2019-11-22 12:58:08 +01:00
Ruslan Kuprieiev 3de46dd25f Merge pull request #2832 from skshetry/refactor-startswith-pathisin
Refactor `startswith` with `path_isin` util
2019-11-22 05:08:48 +02:00
saugat pachhai 9ae74073f2 dvc: remove extra newline 2019-11-22 05:36:06 +05:45
Casper da Costa-Luis 18eeb2cd84 fix channel name 2019-11-21 22:18:26 +00:00
Saugat Pachhai 751bdb2197 dvc: refactor startswith usage with path_isin util 2019-11-21 22:22:08 +05:45
Ruslan Kuprieiev f5c8d5d73e Merge pull request #2827 from Suor/bc-remnants
dvc: remove unused config paths
2019-11-21 17:45:10 +02:00
Ruslan Kuprieiev 7bc7a572f2 Merge pull request #2809 from casperdcl/gs-progress
GS progress for push & pull
2019-11-21 05:38:35 +02:00
Ruslan Kuprieiev 5c5d1eda1d Merge pull request #2817 from casperdcl/si-progress
use SI prefixes for progress
2019-11-21 05:10:49 +02:00
Alexander Schepanovski 0c2a475e93 dvc: remove unused config paths
These are remnants from old style remotes configuration
2019-11-20 20:33:46 +01:00
Mr. Outis ef1f038c32 analytics: refactor into a module 2019-11-20 13:31:44 -06:00
Kaio Utsch 142eb47557 remote: fix default --unset break of last commit 2019-11-20 10:29:10 +00:00
Ruslan Kuprieiev 91ac75d023 Merge pull request #2798 from skshetry/fix-str-rel-repo
Display path relative to temporary repo when get/import-ing files
2019-11-20 11:34:31 +02:00
Kaio Utsch 67ceed0d25 remote: fix remote default call with no arguments
remote default command with no arguments now returns default remote instead of
setting default remote to None.

Fixes #2813
2019-11-20 09:21:41 +00:00
Ruslan Kuprieiev 2bae5e4622 stage: remove unused import 2019-11-20 11:04:49 +02:00
Ruslan Kuprieiev c7c852a42f Merge pull request #2796 from Suor/opt-validate
perf: switch schema validation library
2019-11-20 11:00:39 +02:00
Ruslan Kuprieiev a1433ba356 Merge branch 'master' into opt-validate 2019-11-20 11:00:28 +02:00
Ruslan Kuprieiev f563892760 dvc: bump to 0.70.0 2019-11-20 08:29:12 +02:00
Ruslan Kuprieiev 1683511da5 Merge pull request #2777 from pared/2602
import: intercept and rephrase OutputNotFound message
2019-11-20 06:27:58 +02:00
Casper da Costa-Luis 43da704083 use SI prefixes for progress 2019-11-19 20:28:37 +00:00
Casper da Costa-Luis b2f7c841ef fix deepsource issues 2019-11-19 17:49:32 +00:00
Casper da Costa-Luis b811a773c6 update dynamic chunk tests 2019-11-19 17:44:01 +00:00
Ruslan Kuprieiev 404620e267 test: update: use dvc_repo fixture
To fix test failure.
2019-11-19 19:12:49 +02:00
Ruslan Kuprieiev 7fae34ee5d test: freeze pytest-mock version
To address failing tests.
2019-11-19 18:52:10 +02:00
Casper da Costa-Luis 28b99dc4c7 expose and use name, no_progress_bar
Fixes https://github.com/iterative/dvc/pull/2809#discussion_r347180388
2019-11-19 16:19:54 +00:00
Saugat Pachhai 83c8ab5c26 test: Refactor tests 2019-11-19 21:55:13 +05:45
Saugat Pachhai 58624e0230 utils/fs: use fspath_py35 instead of fspath 2019-11-19 21:55:01 +05:45
Casper da Costa-Luis 5eefacdf2c back to gs multiple
Fixes https://github.com/iterative/dvc/pull/2809#discussion_r347954033
2019-11-19 14:39:31 +00:00
Paweł Redzyński d1094c41b1 external repo: make NoOutputInExternalRepo path-aware 2019-11-19 14:58:16 +01:00
Casper da Costa-Luis eb5a0a92ce fix gs chunks
Fixes https://github.com/iterative/dvc/pull/2809#discussion_r347835334
2019-11-19 13:57:23 +00:00
Ruslan Kuprieiev af0d913760 Merge pull request #2775 from Suor/opt-yaml
perf: optimize yaml parsing for stages
2019-11-19 15:51:34 +02:00
Paweł Redzyński 5a92c46033 import: just inform that output not found 2019-11-19 14:48:28 +01:00
pawel e5aaf98776 import/get: make extarnal repo handle OutputNotFound 2019-11-19 14:48:28 +01:00
Ruslan Kuprieiev 689dd95817 Merge pull request #2790 from shcheklein/fix-2789
makedirs: fix mode flag is being ignored starting from Python 3.7
2019-11-19 15:47:37 +02:00
pawel f575bc7a1d import: intercept and rephrase OutputNotFound message 2019-11-19 14:42:18 +01:00
Ivan Shcheklein 1c752a9548 tests: makedirs permissions test simplify with tmpdir fixture 2019-11-19 00:32:09 -08:00
Ivan Shcheklein aa6a77285d makedirs: add comment on why do have to use umask 2019-11-19 00:17:27 -08:00
Saugat Pachhai f07a70a052 get/import: display path relative to temporary repo 2019-11-19 05:36:11 +05:45
Saugat Pachhai 68c7366ef0 utils: implement path_isin to compare child and parent paths 2019-11-19 05:34:33 +05:45
Alexander Schepanovski 3bffe9dbff dvc: drop schema from requirements 2019-11-18 17:24:40 +01:00
Alexander Schepanovski 2aa750cb48 perf: switch schema validation library for config 2019-11-18 17:22:05 +01:00
Ruslan Kuprieiev 0ac18fb051 dvc: bump to 0.69.0 2019-11-18 15:13:19 +02:00
Ruslan Kuprieiev b9d9df06e9 Merge pull request #2808 from algomaster99/test-arg-types-tmp_fname
Write test for `tmp_fname` to check if it accepts Path-like and str objects
2019-11-18 08:47:35 +02:00
Aman Sharma 3edad21b41 Write test for tmp_fname to check if it accepts Path-like and str objects 2019-11-18 11:49:40 +05:30
Casper da Costa-Luis 934850f23c neaten default chunks
Fixes https://github.com/iterative/dvc/pull/2809#discussion_r347178103
2019-11-18 02:10:27 +00:00
Casper da Costa-Luis 32c7b605bf reduce gs upload chunk size 2019-11-18 01:53:18 +00:00
Casper da Costa-Luis 321a46bd91 add gs stats 2019-11-18 01:45:34 +00:00
Casper da Costa-Luis 1f442a4732 gs download idea 2019-11-17 23:29:45 +00:00
Casper da Costa-Luis 6b388edbe5 py2/3 fix 2019-11-17 23:19:19 +00:00
Casper da Costa-Luis 6fdba1dddf initial upload idea
- fixes #1566
2019-11-17 23:08:48 +00:00
Casper da Costa-Luis 47e1b49e2a PKG = "snap"
Fixes https://github.com/iterative/dvc/pull/2778#issuecomment-554651335
2019-11-16 21:03:54 +00:00
Ruslan Kuprieiev 38c210068b Merge pull request #2795 from algomaster99/test-arg-types-remove
Ensure `remove` accepts str and Path-like objects
2019-11-16 21:02:04 +02:00
Ruslan Kuprieiev 276621bbf2 Merge pull request #2786 from Suor/convert-to-unicode
perf: a small stage validation optimization
2019-11-16 19:23:38 +02:00
Aman Sharma d9cd9ece8b Move _chmod to fs.py 2019-11-16 00:44:20 +05:30
Alexander Schepanovski 3943b0eb57 perf: a small stage validation optimization 2019-11-15 16:31:46 +01:00
Ivan Shcheklein ecfbc3a6de compat: fix py2 mkdirs implementation, bytes do not exist on py2 2019-11-14 14:33:36 -08:00
Ivan Shcheklein 303d52e4ac tests: fix mkdirs permissions test to cleanup after failure 2019-11-14 14:32:52 -08:00
Alexander Schepanovski 78fad73f2b perf: switch schema validation library
voluptuous is compiling schema to python function, which makes it
faster.
2019-11-14 22:33:28 +01:00
Aman Sharma 636d850c84 Ensure remove accepts str and Path-like objects 2019-11-15 01:54:40 +05:30
Ruslan Kuprieiev 6147b93d24 Merge pull request #2765 from algomaster99/improve-lock-error-message
Improve `LockError` message
2019-11-14 11:26:33 +02:00
Aman Sharma 13ddcd4b2f Rephrase error message 2019-11-14 09:21:35 +05:30
Ivan Shcheklein eec7618f49 tests: makedirs permissions on intermediate dirs 2019-11-13 16:58:51 -08:00
Ivan Shcheklein 2b934470b4 makedirs: fix mode flag is being ignored starting from Python 3.7 2019-11-13 14:12:01 -08:00
Ruslan Kuprieiev ac68e6c70d Merge pull request #2787 from efiop/reformat-imports-bot
dvc: remove reorder_python_imports formatter
2019-11-13 22:40:19 +02:00
Ruslan Kuprieiev 97a7f582a4 dvc: remove reorder_python_imports formatter 2019-11-13 21:45:44 +02:00
Aman Sharma 1ca054c2fb Move error message to a constant 2019-11-13 23:55:39 +05:30
Aman Sharma f6a6d2eef9 Remove unreachable except block 2019-11-13 23:55:39 +05:30
Aman Sharma 7f12ef8539 Improve error message when LockError is raised 2019-11-13 23:55:39 +05:30
Ruslan Kuprieiev c1bbd8dcc0 Merge pull request #2759 from pared/2711
get/import: more meaningful message on NoRemoteError
2019-11-13 20:04:57 +02:00
Alexander Schepanovski 329d072c22 perf: explain why two yaml parsers are used 2019-11-13 16:35:05 +01:00
Paweł RedzyńskiandRuslan Kuprieiev 4a6a1b662d Update dvc/external_repo.py
Co-Authored-By: Ruslan Kuprieiev <[email protected]>
2019-11-13 11:57:53 +01:00
Ruslan Kuprieiev cfc579929c Merge pull request #2785 from efiop/2387
test: try using azurite 3.3.0-preview
2019-11-13 03:47:28 +02:00
Ruslan Kuprieiev e4c3dbf49b test: try using azurite 3.3.0-preview
Azurite was updated [2] to support new SAS tokens, so we no longer need
to downgrade azure-storage-blob for our tests.

[1] https://github.com/iterative/dvc/issues/2387
[2] https://github.com/Azure/Azurite/issues/250
2019-11-13 03:25:06 +02:00
Casper da Costa-Luis f944ce2ecd documnet --classic 2019-11-12 23:15:16 +00:00
Casper da Costa-Luis c23b8007c4 potential deploy catch 2019-11-12 23:01:32 +00:00
Casper da Costa-Luis 9137ad5f18 add minor comment abous stage tags
Fixes https://github.com/iterative/dvc/pull/2778#discussion_r345424578
2019-11-12 22:48:14 +00:00
Casper da Costa-Luis 31b3bcc0e2 PyPI and GH on tags, Snap on master and tags 2019-11-12 22:43:57 +00:00
Casper da Costa-Luis 73f9f3c9d5 fix connectivity and permissions => back to --classic 2019-11-12 20:12:19 +00:00
Casper da Costa-Luis 8f06e76aac document and add badge 2019-11-12 17:25:34 +00:00
Casper da Costa-Luis 424bdbe286 stable and edge releases 2019-11-12 15:18:30 +00:00
Paweł RedzyńskiandRuslan Kuprieiev 48d16f6cc5 Update dvc/exceptions.py
Co-Authored-By: Ruslan Kuprieiev <[email protected]>
2019-11-12 15:29:40 +01:00
Casper da Costa-Luis c2c98b007a minor tidy 2019-11-12 13:47:52 +00:00
Paweł Redzyński 76fb661655 get/import: handle NoRepoError with external_repo 2019-11-12 13:23:14 +01:00
Casper da Costa-Luis d2a6b65711 fix permissions 2019-11-12 02:45:18 +00:00
Casper da Costa-Luis 8212dc7992 travis snap tidy 2019-11-12 01:33:49 +00:00
Casper da Costa-Luis 4c3bf028e9 fix snap build 2019-11-12 01:24:28 +00:00
Casper da Costa-Luis 8c17ec14f9 initial snap support 2019-11-12 00:05:58 +00:00
Casper da Costa-Luis e18fe609cb remove deprecated sudo 2019-11-11 23:43:05 +00:00
Casper da Costa-Luis 226b598dea remove unneeded stage names 2019-11-11 23:42:43 +00:00
Alexander Schepanovski 58c9e5557c perf: optimize yaml parsing for stages 2019-11-11 13:41:50 +01:00
pawel deba7e95da get/import: more meaningful message on NoRemoteError 2019-11-11 12:53:24 +01:00
Ruslan Kuprieiev 947b39f47a dvc: bump to 0.68.1 2019-11-11 03:32:17 +02:00
Ruslan Kuprieiev 4bde281b4e build: windows: remove unneeded quotes
Per https://github.com/iterative/dvc/pull/2773#discussion_r344529924
2019-11-11 03:31:48 +02:00
Ruslan Kuprieiev 4d51f78074 dvc: bump to 0.68.0 2019-11-11 01:53:58 +02:00
Ruslan Kuprieiev 204aa9c6c3 Merge pull request #2773 from n3hrox/build_patch
Add better heuristic for build detection
2019-11-11 01:33:06 +02:00
n3hrox 53f4ad2943 add build cleanup 2019-11-10 23:56:28 +01:00
n3hrox 448ae95604 fix windows echo 2019-11-10 22:22:25 +01:00
n3hrox 3ba4b8bd4d remove unnecessary pip check 2019-11-10 22:10:19 +01:00
n3hrox 172ad71553 fix typo 2019-11-10 21:46:36 +01:00
n3hrox 1156763e84 add pip fallback 2019-11-10 21:40:46 +01:00
n3hrox a0efedcd31 remove arch support from readme 2019-11-10 21:02:07 +01:00
n3hrox 707cce9bd2 remove unnecessary line 2019-11-10 20:24:26 +01:00
n3hrox c68adc9a6d remove windows patch in favor of echo 2019-11-10 20:23:14 +01:00
n3hrox 4fdf269450 fix dvc build path 2019-11-10 20:06:19 +01:00
n3hrox c48c84261d remove patches in favor of echo 2019-11-10 20:03:32 +01:00
n3hrox 36f75dd519 Remove unnecessary file 2019-11-10 19:48:15 +01:00
n3hrox e1dfb75055 Refactor get_package_manager 2019-11-10 19:46:48 +01:00
n3hrox f4ee4b0434 Add better heuristic for build detection 2019-11-10 18:04:41 +01:00
Ruslan Kuprieiev 387a471857 dvc: bump to 0.67.1 2019-11-10 18:14:09 +02:00
Ruslan Kuprieiev f246ec1187 pyinstaller: add win32timezone hidden import
Fixes #2772
2019-11-10 18:13:31 +02:00
Ruslan Kuprieiev cfc442e932 Merge pull request #2769 from algomaster99/test-arg-types-move
Ensure `move` accepts str and Path-like objects
2019-11-09 23:27:31 +02:00
Aman Sharma 9427bbe8d0 Move move to filesystem utility 2019-11-09 23:57:57 +05:30
Ruslan Kuprieiev 1fd4ab2655 dvc: bump to 0.67.0 2019-11-09 19:40:55 +02:00
Ruslan Kuprieiev 663eaa0c36 Merge pull request #2767 from casperdcl/locking
non-blocking intermediate progress IO
2019-11-09 19:07:31 +02:00
Aman Sharma 44268251b4 Ensure move accepts str and Path-like objects 2019-11-09 18:47:01 +05:30
Casper da Costa-Luis 2e1360a34f non-blocking intermediate progress IO
- fixes #2766
2019-11-09 01:43:47 +00:00
Ruslan Kuprieiev 143cbdc828 dvc: bump to 0.66.11 2019-11-09 02:08:50 +02:00
Ruslan Kuprieiev 49e437501d Merge pull request #2764 from efiop/2754
win: use SuppressibleMsgBox instead of MsgBox
2019-11-09 02:08:02 +02:00
Ruslan Kuprieiev add0958019 win: inno: use SuppressibleMsgBox instead of MsgBox 2019-11-09 01:47:49 +02:00
Ruslan Kuprieiev 0d057ef24b Merge pull request #2763 from efiop/2754
pyinstaller: add pywin32/win32file hook
2019-11-09 00:24:28 +02:00
Ruslan Kuprieiev a956090d6d pyinstaller: add pywin32/win32file hook
Also added a simple test to `dvc-test` repo [1].

[1] https://github.com/iterative/dvc-test/commit/5748744fa1ebb4e265f08fedcddc66a17e8bd914

Fixes #2754
2019-11-08 22:11:17 +02:00
Ruslan Kuprieiev a56a6a1bd2 dvc: bump to 0.66.10 2019-11-08 21:04:22 +02:00
Ruslan Kuprieiev 183b699dee Merge pull request #2761 from iterative/efiop-patch-1
dvc: add workaround for `LookupError: unknown encoding: idna`
2019-11-08 20:56:08 +02:00
Ruslan Kuprieiev 1411f08121 Merge pull request #2762 from iterative/efiop-patch-1-restyled
Restyle dvc: add workaround for `LookupError: unknown encoding: idna`
2019-11-08 20:26:26 +02:00
Restyled.io 43cc308078 Restyled by black 2019-11-08 18:22:27 +00:00
Ruslan Kuprieiev a0b6c38fa5 dvc: add workaround for LookupError: unknown encoding: idna
Fixes #2749
2019-11-08 20:20:22 +02:00
Ruslan Kuprieiev 54760c5604 Merge pull request #2758 from kss682/test_2672
Test for issue #2672 added.
2019-11-08 19:42:24 +02:00
kss682 7e336a8e7a test for issue 2672 --typo correction 2019-11-09 21:12:24 +05:30
kss682 4409c60468 Test for issue #2672 added. 2019-11-09 20:41:40 +05:30
Ruslan Kuprieiev 5ad7d9802e Merge pull request #2757 from algomaster99/test-arg-types-makedirs
Ensure `makedirs` accepts str and Path-like objects
2019-11-08 17:56:40 +02:00
Aman Sharma 204cfcb879 Ensure makedirs accepts str and Path-like objects 2019-11-08 20:37:32 +05:30
Ruslan Kuprieiev ec44a614b0 Merge pull request #2753 from efiop/analytics
analytics: don't verify config from repo
2019-11-08 14:38:02 +02:00
Ruslan Kuprieiev 1058df0f77 analytics: don't forget to convert to bool 2019-11-08 05:21:47 +02:00
Ruslan Kuprieiev 82be22a2ad Merge pull request #2752 from algomaster99/test-arg-types-copyfile
Ensure `copyfile` accepts str and Path-like objects
2019-11-08 02:54:37 +02:00
Ruslan Kuprieiev 910a67b35f analytics: don't verify config from repo
Otherwise this will raise an exception from analytics worker:
```
dvc config cache.type sometypo
```
But running that same command with `--system` or `--global` flag outside
of repo won't raise that.

Also need to better handle ConfigObjError, but that is a separate issue.
2019-11-08 02:37:11 +02:00
Aman Sharma d4aa27d6ab Ensure copyfile accepts str and Path-like objects 2019-11-08 01:51:49 +05:30
Ruslan Kuprieiev 7387760755 Merge pull request #2751 from pared/2742
ignore: make DvcIgnoreFilter use tree
2019-11-07 19:45:04 +02:00
Ruslan Kuprieiev c4040401c5 Merge pull request #2750 from efiop/2671
checkout: don't cleanup links when checking out specific targets
2019-11-07 19:40:35 +02:00
pawel 4acfaa293e ignore: add test for dvcignore on branch 2019-11-07 17:51:00 +01:00
pawel f14aca630f ignore: make DvcIgnoreFilter use tree 2019-11-07 15:37:24 +01:00
Ruslan Kuprieiev 1af608bf64 checkout: don't cleanup links when checking out specific targets
Related to #2671
2019-11-07 13:10:16 +02:00
Ruslan Kuprieiev c60b82d31d dvc: bump to 0.66.9 2019-11-07 00:25:28 +02:00
Ruslan Kuprieiev 471add516e Merge pull request #2748 from efiop/logger
system: use logging instead of dvc.logger
2019-11-07 00:24:32 +02:00
Ruslan Kuprieiev 21b7d1f7ec system: use logging instead of dvc.logger
A leftover from an old logger refactoring.
2019-11-06 23:24:46 +02:00
Ruslan Kuprieiev 0a9e6944b0 updater: fix typo 2019-11-06 16:03:59 +02:00
Ruslan Kuprieiev fb8315997e Merge pull request #2746 from algomaster99/test-arg-types-file_md5
Ensure `file_md5` accept str and Path-like objects
2019-11-06 14:01:20 +02:00
Aman Sharma 34a868b420 Ensure file_md5 accept str and Path-like objects 2019-11-06 09:25:35 +05:30
Ruslan Kuprieiev b6102f8d30 dvc: bump to 0.66.8 2019-11-06 03:09:04 +02:00
Ruslan Kuprieiev ab0ba77ab2 Merge pull request #2745 from shcheklein/fix-2744
dvc get leaves dangling symlinks if source repo is set to use symlinks as its cache type
2019-11-06 03:08:37 +02:00
Ivan Shcheklein ffd8c2cf6e tests: add dvc import test to check that local cache type is enforced 2019-11-05 16:40:24 -08:00
Ivan Shcheklein c25715dc72 get: fix override external repo cache type 2019-11-05 16:27:01 -08:00
Ivan Shcheklein 73f95ee6b4 tests: minor style and typo fixes in dvc gc tests 2019-11-05 16:25:38 -08:00
Ruslan Kuprieiev 418b0b6523 dvc: bump to 0.66.7 2019-11-06 02:13:10 +02:00
Ruslan Kuprieiev e30d3545cd Merge pull request #2743 from efiop/2737
dvcignore: don't forget to use dvcignore when collecting dvcignores
2019-11-06 02:11:11 +02:00
Ruslan Kuprieiev 7caf28ca11 dvcignore: don't forget to use dvcignore when collecting dvcignores
Fixes #2737
2019-11-06 01:39:31 +02:00
Ruslan Kuprieiev 5e2fa3742b Merge pull request #2741 from algomaster99/remove-get-parent-dirs
Remove `get_parent_dirs_up_to`
2019-11-05 23:01:31 +02:00
Ruslan Kuprieiev 9145dcd6a8 Merge pull request #2740 from algomaster99/test-arg-types-symlink
Ensure `contains_symlink_up_to` accept both str and Path-like objects
2019-11-05 22:42:06 +02:00
Aman Sharma 8491ddf43f Remove get_parent_dirs_up_to 2019-11-06 02:03:47 +05:30
Aman Sharma f642ff76a5 Ensure contains_symlink_up_to accept both str and Path-like objects 2019-11-06 00:24:10 +05:30
Ruslan Kuprieiev a4c57bbb55 Merge pull request #2739 from mroutis/sftp-move-fix
sftp: only remove src after an atomic copy during move
2019-11-05 19:01:35 +02:00
Mr. Outis 0103f3330f sftp: only remove src after an atomic copy during move
Shout-out to @Suor for pointing this out
2019-11-05 10:02:41 -06:00
Ruslan Kuprieiev 62c5e12d43 dvc: bump to 0.66.6 2019-11-05 03:20:24 +02:00
Ruslan Kuprieiev ee70822bd4 Merge pull request #2736 from efiop/master
dvc: automatically reorder imports
2019-11-05 03:02:32 +02:00
Ruslan Kuprieiev 95fe1578bb dvc: automatically reorder imports 2019-11-05 02:34:40 +02:00
Ruslan Kuprieiev ba8360d4fd Merge pull request #2735 from efiop/win
system: win: use win32file for _getdirinfo
2019-11-05 02:19:23 +02:00
Ruslan Kuprieiev b718b839bf system: win: use win32file for _getdirinfo
Current implementation is triggering some non-obvious gc bug, that
causes memory leaks. Using win32file simplifies the code and gets rid of
this bug.

To reproduce it, run:

```
from dvc.system import System

while True:
    System.inode(__file__)
```

and see your Windows machine run out of memory pretty quickly.
2019-11-05 01:46:21 +02:00
Ivan Shcheklein 9af4b964eb update Github PR template: completely get rid of newlines to make formatting sane 2019-11-04 15:43:05 -08:00
Ivan Shcheklein bc543ac4b0 update PR Github template, Github does not respect soft line breaks in certain cases
More info: https://github.com/isaacs/github/issues/1013
2019-11-04 15:39:24 -08:00
Ruslan Kuprieiev 197a802c61 Merge pull request #2734 from shcheklein/fix-get-npe
fix NPE when we do get that does not provide progress callback
2019-11-05 00:17:41 +02:00
Ivan Shcheklein 8f403c0704 fix NPE when we do get that does not provide progress callback 2019-11-04 13:01:28 -08:00
Ruslan Kuprieiev 522579be1e dvc: bump to 0.66.5 2019-11-04 22:55:08 +02:00
Ruslan Kuprieiev a5d317eaa2 Merge pull request #2679 from n3hrox/enhance/conda_detection
updater: detect conda
2019-11-04 22:54:05 +02:00
n3hrox 83429a5abc refactor is_conda placement 2019-11-04 21:29:56 +01:00
n3hrox b97865e165 fix test 2019-11-04 21:18:07 +01:00
n3hrox 8e2d0bdcd6 Merge branch 'master' of github.com:iterative/dvc into enhance/conda_detection 2019-11-04 21:10:57 +01:00
n3hrox e3417f78be revert changes 2019-11-04 21:02:51 +01:00
n3hrox 4da42d188c use patch heuristic to determine conda 2019-11-04 21:00:09 +01:00
n3hrox 952fee57c1 resolve conflicts 2019-11-04 20:52:24 +01:00
Ruslan Kuprieiev 35507dfc61 Update .restyled.yaml 2019-11-04 20:31:20 +02:00
Ruslan Kuprieiev 8b4cc01110 add restyled.io 2019-11-04 20:20:22 +02:00
Ruslan Kuprieiev 3974862f87 github: add config for release-drafter bot 2019-11-04 19:13:39 +02:00
Ruslan Kuprieiev e0fe8a8f94 Merge pull request #2723 from shcheklein/fix-pr-template
fix PR template to mention recommendatory nature of CC and DS
2019-11-04 11:36:03 +02:00
Ivan Shcheklein cbb58e02d6 PR template: change wording for the docs checkbox 2019-11-03 17:02:11 -08:00
Ivan Shcheklein 8b05eb5d36 PR template: minor sentence with emoji style change 2019-11-03 16:19:05 -08:00
Ivan Shcheklein 60bdd41cbd PR template: address review, update DS and CC checks item 2019-11-03 16:17:23 -08:00
Ivan Shcheklein d0e62a57ab fix PR template to mention recommendatory nature of CC and DS 2019-11-03 15:15:23 -08:00
Ruslan Kuprieiev 5227fc58d5 dvc: bump to 0.66.4 2019-11-04 00:38:10 +02:00
Ruslan Kuprieiev 4efd048796 test: try removing get_url test 2019-11-04 00:05:44 +02:00
Ruslan Kuprieiev 46391cb4f9 test: fix formatting 2019-11-03 23:47:34 +02:00
Ruslan Kuprieiev 71f8d25ab3 test: get-url: try using with mock_s3() instead of a decorator 2019-11-03 23:42:44 +02:00
Ruslan Kuprieiev 2df7eda159 Merge pull request #2674 from n3hrox/enhance/footnote_deduplication
logger: output footer once for multithreaded push/pull errors
2019-11-03 23:37:13 +02:00
n3hrox d25282ba28 revert print to logger 2019-11-03 22:14:59 +01:00
n3hrox 2893c9acad move footer to better place and change logging to stderr 2019-11-03 22:07:27 +01:00
Ruslan Kuprieiev 46d221fade test: s3: try using boto3.client 2019-11-03 22:38:48 +02:00
Ruslan Kuprieiev 17512feb52 Merge pull request #2722 from kss682/2672_1
Added package manager to dvc version output
2019-11-03 21:56:01 +02:00
kss682 3421c6c6e4 Added get_package_manager from dvc.utils.pkg.
Solves issue #2672
2019-11-05 00:58:27 +05:30
kss682 8903dbbc03 Deleted duplicate from updater
Solves issue #2672
2019-11-05 00:31:55 +05:30
kss682 0786a70697 Package added with get_package_manager function and other utils.
import changed accordingly in version
Solves issue #2672
2019-11-05 00:15:56 +05:30
kss682 bb7a9a567e Added package manager to dvc version output.
Solves issue #2672
2019-11-04 20:29:39 +05:30
Ruslan Kuprieiev e75c52a170 setup: freeze awscli version 2019-11-03 15:09:29 +02:00
Ruslan Kuprieiev 3514cc5b5a test: freeze moto version 2019-11-03 14:49:36 +02:00
Ruslan Kuprieiev 0fece74d00 test: get-url: don't use get_aws_url 2019-11-03 14:18:25 +02:00
Ruslan Kuprieiev 1b54c7fa6a Merge pull request #2715 from ATR-oCiTy/master
Fixed issue #2680 and Simple typo rectified
2019-11-03 12:51:43 +02:00
Ruslan Kuprieiev 6400eba83f Merge pull request #2716 from eshaan-jeckyll/master
typos corrected
2019-11-03 12:48:55 +02:00
Ruslan Kuprieiev 6559bae0c9 Merge pull request #2717 from johntharian/master
typo changed for existance->existence
2019-11-03 12:48:50 +02:00
Ruslan Kuprieiev a65e5c37fb Merge pull request #2714 from Pandemic179/master
Rectified simple typos
2019-11-03 12:44:56 +02:00
Ruslan Kuprieiev c71e5562bb Merge pull request #2713 from GeorgeSabu-mec/master
fix typo(correct spelling of existence)
2019-11-03 12:44:03 +02:00
Ruslan Kuprieiev 601a77ed4b Merge pull request #2712 from the-thirteenth-star/master
fixed typo
2019-11-03 12:42:37 +02:00
johntharian 55d819494a typo changed for existance->existaence 2019-11-03 15:51:37 +05:30
GeorgeSabu-MEC 1e22fabece fix typo(correct spelling of existence) 2019-11-03 15:42:07 +05:30
Naina 1afc40c1ec Update fetch.py 2019-11-03 15:40:27 +05:30
ATR-oCiTy 2c0b209e38 Rectified install: Cask 'dvc' is not installed. error #2680 2019-11-03 15:25:30 +05:30
ATR-oCiTy e26517ec36 Simple typos mectified 2019-11-03 15:04:42 +05:30
eshaan-jeckyll 97b4800d2e typos corrected 2019-11-03 15:03:36 +05:30
Pandemic179 4007da0298 Rectified simple typos 2019-11-03 15:02:30 +05:30
Ruslan Kuprieiev 372e1070ac Merge pull request #2703 from kurianbenoy/master
Remove multi-threaded prefix before progress bar(#2686)
2019-11-03 00:34:19 +02:00
Ruslan Kuprieiev e21fc34c84 Merge pull request #2698 from algomaster99/test-argument-types-get_mtime_and_size
Verify if `get_mtime_and_size` can take both Path objects and strings
2019-11-03 00:25:47 +02:00
Aman Sharma cffc9c8ed9 Remove unnecessary wrapping of path 2019-11-03 02:38:50 +05:30
Aman Sharma d454cb8637 Modify test to check for directory as well 2019-11-03 02:23:46 +05:30
Casper da Costa-Luis 440b323e39 TqdmThreadPoolExecutor => ThreadPoolExecutor
- Fixes https://github.com/iterative/dvc/pull/2703#discussion_r341763150
2019-11-02 15:22:46 +00:00
Casper da Costa-Luis 82dae6c25b remove special TqdmThreadPoolExecutor logic
- fixes #2686
- closes #2703
2019-11-02 15:22:46 +00:00
kurianbenoy 306c916626 Change import order in progress file 2019-11-02 15:22:46 +00:00
kurianbenoy 21bafccc96 Remove multi-threaded prefix before progress bar(#2686) 2019-11-02 15:22:46 +00:00
Ruslan Kuprieiev 2e6485f24b Merge pull request #2709 from mroutis/fix-2704
sftp: make move across drives possible
2019-11-02 14:07:46 +02:00
Ruslan Kuprieiev 91a7af03e7 Merge pull request #2707 from Suor/urlinfo
perf: refactor URLInfo to not use ParseResult inside
2019-11-02 13:49:49 +02:00
Mr. Outis 16164e65ad sftp: move -> use atomic copying 2019-11-01 17:01:50 -06:00
Ruslan Kuprieiev 809a85a340 dvc: bump to 0.66.3 2019-11-01 23:58:17 +02:00
Ruslan Kuprieiev ce8e019dd1 Merge pull request #2705 from shcheklein/fix-2702
get-url: fix NPE when src non local and dst not absolute
2019-11-01 23:54:53 +02:00
Ruslan Kuprieiev 7ecbdab564 Merge pull request #2693 from sadielbartholomew/i2685-pull-cmd-output
pull, at import stage: replace warning with info report
2019-11-01 23:15:23 +02:00
Aman Sharma f4f9f3aac1 Replace path with path_info 2019-11-02 01:15:17 +05:30
Mr. Outis 8f7000056b sftp: move -> fallback to copy when sftp.rename fails 2019-11-01 13:26:03 -06:00
Mr. Outis 283f848822 sftp: make move an atomic operation 2019-11-01 13:17:22 -06:00
Mr. Outis ab1ead881b sftp: make move across drives possible
Fix #2704
2019-11-01 12:40:16 -06:00
n3hrox 89502bee88 fix tests 2019-11-01 18:05:27 +01:00
Ivan Shcheklein 3660d9eac4 local output: address PR comment, fix style a bit 2019-11-01 09:49:56 -07:00
Alexander Schepanovski b83564d80e perf: refactor URLInfo to not use ParseResult inside
This permites URLInfo construction from parts without building/reparsing
string, which makes it around 20% faster. Code is also simpler and a bit
shorter now.

Other improvements:
- added URLInfo.replace(path=...)
- URLInfo._path now stringifies properly
- protected against passing path not starting with /
- do not allow query, params, fragments and passwords in urls (they were
silently ignored previously)
2019-11-01 17:33:00 +01:00
n3hrox 8ae1165a9f fix typo upload -> download 2019-11-01 16:38:23 +01:00
n3hrox 3bf810a832 move footer from logger to main 2019-11-01 16:36:31 +01:00
Ruslan Kuprieiev c999b5d270 remove info message 2019-11-01 16:45:41 +02:00
Ruslan Kuprieiev 5c8a312002 fix order 2019-11-01 16:41:22 +02:00
Ruslan Kuprieiev 0dd4d2361e fix formatting 2019-11-01 16:40:06 +02:00
Sadie Bartholomew 31551e6e2e CLI: change return output for 'checkout' and 'pull' commands
Change messages output after return of some commands:

* Remove warning for 'dvc checkout' & commands calling it under the hood
* Add an informational message output for 'dvc pull' command

Fixes #2685.
2019-10-31 23:01:24 +00:00
Ivan Shcheklein 98750c95c5 get-url: fix NPE when src non local and dst not absolute 2019-10-31 09:58:07 -07:00
Ruslan Kuprieiev d932405ee1 Merge pull request #2695 from mroutis/test-s3-etag
tests: preserve the same ETag across buckets during copy
2019-10-31 01:55:17 +02:00
Aman Sharma b9a17752e3 Test acceptable path types of get_mtime_and_size 2019-10-31 00:30:54 +05:30
Mr. Outis 732ed904ce tests: same ETag across buckets 2019-10-30 11:42:39 -06:00
n3hrox 82b69a5abd remove cask check 2019-10-27 22:31:25 +01:00
n3hrox 752e584a9b refactor function and add test 2019-10-27 22:25:27 +01:00
n3hrox 03a2f284e2 refactor 2019-10-27 18:28:38 +01:00
n3hrox 225d538069 reduce amount of returns 2019-10-27 18:19:45 +01:00
n3hrox 795db730fe switch subprocess to popen 2019-10-27 18:15:16 +01:00
n3hrox cf9037932d change method to static 2019-10-27 18:03:33 +01:00
n3hrox a9b9d52853 updater: detect conda 2019-10-27 17:59:04 +01:00
n3hrox 98bafa9968 Change f-string to old format 2019-10-26 23:01:34 +02:00
n3hrox 2e12af095e Fix improper variable name 2019-10-26 22:21:58 +02:00
n3hrox a768fd13ff logger: output footer once for multithreaded push/pull errors 2019-10-26 22:14:14 +02:00
234 changed files with 4399 additions and 3051 deletions
+6 -7
View File
@@ -1,9 +1,8 @@
* [ ] Have you followed the guidelines in our
[Contributing document](https://dvc.org/doc/user-guide/contributing/core)?
* [ ] ❗ Have you followed the guidelines in the [Contributing to DVC](https://dvc.org/doc/user-guide/contributing/core) list?
* [ ] Does your PR affect documented changes or does it add new functionality
that should be documented? If yes, have you created a PR for
[dvc.org](https://github.com/iterative/dvc.org) documenting it or at
least opened an issue for it? If so, please add a link to it.
* [ ] 📖 Check this box if this PR **does not** require [documentation](https://dvc.org/doc) updates, or if it does **and** you have created a separate PR in [dvc.org](https://github.com/iterative/dvc.org) with such updates (or at least opened an issue about it in that repo). Please link below to your PR (or issue) in the [dvc.org](https://github.com/iterative/dvc.org) repo.
* [ ] ❌ Have you checked DeepSource, CodeClimate, and other sanity checks below? We consider their findings recommendatory and don't expect everything to be addressed. Please review them carefully and fix those that actually improve code or fix bugs.
Thank you for the contribution - we'll try to review it as soon as possible. 🙏
-----
+5
View File
@@ -0,0 +1,5 @@
# Config for https://github.com/apps/release-drafter
branches:
- master
template: |
$CHANGES
+1
View File
@@ -4,6 +4,7 @@ neatlynx/__pycache__
cache
*.pyc
.env/
.env2.7/
cache
.dvc.conf.lock
+11
View File
@@ -0,0 +1,11 @@
---
restylers:
- name: black
image: restyled/restyler-black:v19.3b0
command:
- black
arguments: []
include:
- "**/*.py"
interpreters:
- python
+25 -13
View File
@@ -2,7 +2,6 @@ cache:
directories:
- $HOME/.cache/pip
- $HOME/.pyenv
sudo: true
dist: xenial
env:
global:
@@ -15,18 +14,20 @@ stages:
- test
- build
- name: deploy
if: tag is present
if: (branch = master OR tag is present) AND repo = iterative/dvc
# 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"
stage: check
os: linux
language: python
python: 3.7
@@ -41,35 +42,28 @@ jobs:
env:
- PY2=1
- 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
- name: "2.7 on Linux"
stage: test
language: python
python: 2.7
env:
- PY2=1
- name: "3.5 on Linux"
stage: test
language: python
python: 3.5
- name: "3.6 on Linux"
stage: test
language: python
python: 3.6
- name: "3.7 on Linux"
stage: test
language: python
python: 3.7
- name: "2.7 on Mac"
stage: test
os: osx
osx_image: xcode9.4
env:
- PY2=1
- name: "3.6 on Mac"
stage: test
os: osx
osx_image: xcode9.4
# build jobs
@@ -82,25 +76,42 @@ jobs:
install:
script: ./scripts/build_posix.sh
- name: "Windows pkg"
stage: build
os: windows
language: shell
install:
script: ./scripts/build_windows.cmd
- name: "Linux pkgs"
stage: build
language: python
python: 3.7
before_install:
install:
script: ./scripts/build_posix.sh
- name: "Pypi pkgs"
stage: build
language: python
python: 3.7
script: ./scripts/build_package.sh
- name: Snapcraft snap
addons:
snaps:
- name: snapcraft
channel: stable
confinement: classic
- name: lxd
channel: stable
env:
- SNAPCRAFT_IMAGE_INFO: '{"build_url": "$TRAVIS_BUILD_URL"}'
before_install:
- sudo /snap/bin/lxd.migrate -yes
- sudo /snap/bin/lxd waitready
- sudo /snap/bin/lxd init --auto
install:
script:
- sudo snapcraft --use-lxd
after_failure:
- sudo journalctl -u snapd
# deploy jobs
- name: "Pypi pkgs"
if: tag is present
stage: deploy
language: python
python: 3.7
@@ -140,6 +151,7 @@ deploy:
- dvc*.deb
- dvc*.pkg
- dvc*.exe
- dvc*.snap
skip_cleanup: true
on:
tags: true
+61 -51
View File
@@ -30,25 +30,30 @@
:target: https://anaconda.org/conda-forge/dvc
:alt: Conda-forge
.. image:: https://img.shields.io/badge/snap-install-82BEA0.svg?logo=snapcraft
:target: https://snapcraft.io/dvc
:alt: Snapcraft
|
**Data Version Control** or **DVC** is an **open-source** tool for data science and machine
learning projects. Key features:
#. simple **command line** Git-like experience. Does not require installing and maintaining
any databases. Does not depend on any proprietary online services;
#. Simple **command line** Git-like experience. Does not require installing and maintaining
any databases. Does not depend on any proprietary online services.
#. it manages and versions **datasets** and **machine learning models**. Data is saved in
S3, Google cloud, Azure, Alibaba cloud, SSH server, HDFS or even local HDD RAID;
#. 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.
#. it makes projects **reproducible** and **shareable**, it helps answering question how
the model was build;
#. Makes projects **reproducible** and **shareable**; helping to answer questions about how
a model was built.
#. it helps manage experiments with Git tags or branches and **metrics** tracking;
#. Helps manage experiments with Git tags/branches and **metrics** tracking.
**DVC** aims to replace tools like Excel and Google Docs that are being commonly used as a knowledge repo and
a ledger for the team, ad-hoc scripts to track and move deploy different model versions, ad-hoc
data file suffixes and prefixes.
**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.
.. contents:: **Contents**
:backlinks: none
@@ -56,34 +61,33 @@ data file suffixes and prefixes.
How DVC works
=============
We encourage you to read our `Get Started <https://dvc.org/doc/get-started>`_ to better understand what DVC
is and how does it fit your scenarios.
We encourage you to read our `Get Started <https://dvc.org/doc/get-started>`_ guide to better understand what DVC
is and how it can fit your scenarios.
The easiest (but not perfect!) *analogy* to describe it: DVC is Git (or Git-lfs to be precise) + ``makefiles``
The easiest (but not perfect!) *analogy* to describe it: DVC is Git (or Git-LFS to be precise) & Makefiles
made right and tailored specifically for ML and Data Science scenarios.
#. ``Git/Git-lfs`` part - DVC helps you storing and sharing data artifacts, models. It connects them with your
Git repository.
#. ``Makefiles`` part - DVC describes how one data or model artifact was build from another data.
#. ``Git/Git-LFS`` part - DVC helps store and share data artifacts and models, connecting them with a Git repository.
#. ``Makefile``\ s part - DVC describes how one data or model artifact was built from other data and code.
DVC usually runs along with Git. Git is used as usual to store and version code and DVC meta-files. DVC helps
to store data and model files seamlessly out of Git while preserving almost the same user experience as if they
were stored in Git itself. To store and share data files cache DVC supports remotes - any cloud (S3, Azure,
DVC usually runs along with Git. Git is used as usual to store and version code (including DVC meta-files). DVC helps
to store data and model files seamlessly out of Git, while preserving almost the same user experience as if they
were stored in Git itself. To store and share the data cache, DVC supports multiple remotes - any cloud (S3, Azure,
Google Cloud, etc) or any on-premise network storage (via SSH, for example).
.. image:: https://dvc.org/static/img/flow.gif
:target: https://dvc.org/static/img/flow.gif
:alt: how_dvc_works
DVC pipelines (aka computational graph) feature connects code and data together. In a very explicit way you can
specify, run, and save information that a certain command with certain dependencies needs to be run to produce
a model. See the quick start section below or check `Get Started <https://dvc.org/doc/get-started>`_ tutorial to
learn more.
The DVC pipelines (computational graph) feature connects code and data together. It is possible to explicitly
specify all steps required to produce a model: input dependencies including data, commands to run,
and output information to be saved. See the quick start section below or
the `Get Started <https://dvc.org/doc/get-started>`_ tutorial to learn more.
Quick start
===========
Please read `Get Started <https://dvc.org/doc/get-started>`_ for the full version. Common workflow commands include:
Please read `Get Started <https://dvc.org/doc/get-started>`_ guide for a full version. Common workflow commands include:
+-----------------------------------+-------------------------------------------------------------------+
| Step | Command |
@@ -108,8 +112,8 @@ Please read `Get Started <https://dvc.org/doc/get-started>`_ for the full versio
Installation
============
Read this `instruction <https://dvc.org/doc/get-started/install>`_ to get more details. There are four
options to install DVC: ``pip``, Homebrew, Conda (Anaconda) or an OS-specific package:
There are four options to install DVC: ``pip``, Homebrew, Conda (Anaconda) or an OS-specific package.
Full instructions are `available here <https://dvc.org/doc/get-started/install>`_.
pip (PyPI)
----------
@@ -120,8 +124,8 @@ pip (PyPI)
Depending on the remote storage type you plan to use to keep and share your data, you might need to specify
one of the optional dependencies: ``s3``, ``gs``, ``azure``, ``oss``, ``ssh``. Or ``all`` to include them all.
The command should look like this: ``pip install dvc[s3]`` - it installs the ``boto3`` library along with
DVC to support the AWS S3 storage.
The command should look like this: ``pip install dvc[s3]`` (in this case AWS S3 dependencies such as ``boto3``
will be installed automatically).
To install the development version, run:
@@ -144,13 +148,28 @@ Conda (Anaconda)
conda install -c conda-forge dvc
Currently, it supports only python version 2.7, 3.6 and 3.7.
Currently, this includes support for Python versions 2.7, 3.6 and 3.7.
Snap (Snapcraft)
----------------
Download the latest ``dvc_*.snap`` from the
GitHub `releases page <https://github.com/iterative/dvc/releases>`_.
.. code-block:: bash
snap install dvc_*.snap --dangerous --classic
Once ``dvc`` is released on the snap store
(`pending request <https://forum.snapcraft.io/t/classic-confinement-request-for-dvc/14124>`_)
there will be no need to download ``dvc_*.snap`` or use ``--dangerous``
(``snap install dvc --classic`` would suffice).
Package
-------
Self-contained packages for Windows, Linux, Mac are available. The latest version of the packages can be found at
GitHub `releases page <https://github.com/iterative/dvc/releases>`_.
Self-contained packages for Linux, Windows, and Mac are available. The latest version of the packages
can be found on the GitHub `releases page <https://github.com/iterative/dvc/releases>`_.
Ubuntu / Debian (deb)
^^^^^^^^^^^^^^^^^^^^^
@@ -168,32 +187,23 @@ Fedora / CentOS (rpm)
sudo yum update
sudo yum install dvc
Arch Linux (AUR)
^^^^^^^^^^^^^^^^
*Unofficial package*, any inquiries regarding the AUR package,
`refer to the maintainer <https://github.com/mroutis/pkgbuilds>`_.
Comparison to related technologies
==================================
.. code-block:: bash
yay -S dvc
Related technologies
====================
#. `Git-annex <https://git-annex.branchable.com/>`_ - DVC uses the idea of storing the content of large files (that you
don't want to see in your Git repository) in a local key-value store and uses file hardlinks/symlinks instead of the
copying actual files.
#. `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.
#. `Git-LFS <https://git-lfs.github.com/>`_ - DVC is compatible with any remote storage (S3, Google Cloud, Azure, SSH,
etc). DVC utilizes reflinks or hardlinks to avoid copy operation on checkouts which makes much more efficient for
large data files.
etc). DVC also uses reflinks or hardlinks to avoid copy operations on checkouts; thus handling large data files
much more efficiently.
#. *Makefile* (and its analogues). DVC tracks dependencies (DAG).
#. *Makefile* (and analogues including ad-hoc scripts) - DVC tracks dependencies (in a directed acyclic graph).
#. `Workflow Management Systems <https://en.wikipedia.org/wiki/Workflow_management_system>`_. DVC is a workflow
#. `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/>`_ Is a Github equivalent for DVC - pushing your Git+DVC based repo to DAGsHub will give you a high level dashboard of your project, including DVC pipeline and metrics visualizations, as well as links to DVC managed files if they are in cloud storage.
#. `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.
Contributing
============
@@ -242,5 +252,5 @@ Copyright
This project is distributed under the Apache license version 2.0 (see the LICENSE file in the project root).
By submitting a pull request for this project, you agree to license your contribution under the Apache license version
By submitting a pull request to this project, you agree to license your contribution under the Apache license version
2.0 to this project.
+5
View File
@@ -0,0 +1,5 @@
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
@@ -0,0 +1 @@
10
+33
View File
@@ -0,0 +1,33 @@
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
@@ -0,0 +1,11 @@
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`.
Vendored Executable
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/make -f
export PYBUILD_NAME=dvc
%:
dh $@ --with python3 --buildsystem=pybuild
+1
View File
@@ -0,0 +1 @@
3.0 (native)
+4
View File
@@ -0,0 +1,4 @@
# 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)))
-2
View File
@@ -3,13 +3,11 @@ DVC
----
Make your data science projects reproducible and shareable.
"""
from __future__ import unicode_literals
import warnings
import dvc.logger
from dvc.version import __version__ # noqa: F401
+1 -2
View File
@@ -1,5 +1,4 @@
"""Main entry point for dvc command line tool."""
from __future__ import unicode_literals
import sys
@@ -7,4 +6,4 @@ import sys
from dvc.main import main
if __name__ == "__main__":
main(sys.argv[1:])
sys.exit(main(sys.argv[1:]))
+138 -255
View File
@@ -1,284 +1,167 @@
"""Collect and send usage analytics"""
from __future__ import unicode_literals
from dvc.utils.compat import str
import os
import json
import errno
import logging
import os
import platform
import requests
import sys
import tempfile
import uuid
import distro
from dvc import __version__
from dvc.utils import env2bool
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
from dvc.utils import env2bool, is_binary, makedirs
from dvc.utils.compat import str, FileNotFoundError
logger = logging.getLogger(__name__)
class Analytics(object):
"""Class for collecting and sending usage analytics.
Args:
info (dict): optional existing analytics report.
def collect_and_send_report(args=None, return_code=None):
"""
Collect information from the runtime/environment and the command
being executed into a report and send it over the network.
URL = "https://analytics.dvc.org"
TIMEOUT_POST = 5
To prevent analytics from blocking the execution of the main thread,
sending the report is done in a separate process.
USER_ID_FILE = "user_id"
The inter-process communication happens through a file containing the
report as a JSON, where the _collector_ generates it and the _sender_
removes it after sending it.
"""
report = _runtime_info()
PARAM_DVC_VERSION = "dvc_version"
PARAM_USER_ID = "user_id"
PARAM_SYSTEM_INFO = "system_info"
# Include command execution information on the report only when available.
if args and hasattr(args, "func"):
report.update({"cmd_class": args.func.__name__})
PARAM_OS = "os"
if return_code is not None:
report.update({"cmd_return_code": return_code})
PARAM_WINDOWS_VERSION_MAJOR = "windows_version_major"
PARAM_WINDOWS_VERSION_MINOR = "windows_version_minor"
PARAM_WINDOWS_VERSION_BUILD = "windows_version_build"
PARAM_WINDOWS_VERSION_SERVICE_PACK = "windows_version_service_pack"
with tempfile.NamedTemporaryFile(delete=False, mode="w") as fobj:
json.dump(report, fobj)
daemon(["analytics", fobj.name])
PARAM_MAC_VERSION = "mac_version"
PARAM_LINUX_DISTRO = "linux_distro"
PARAM_LINUX_DISTRO_VERSION = "linux_distro_version"
PARAM_LINUX_DISTRO_LIKE = "linux_distro_like"
def is_enabled():
if env2bool("DVC_TEST"):
return False
PARAM_SCM_CLASS = "scm_class"
PARAM_IS_BINARY = "is_binary"
PARAM_CMD_CLASS = "cmd_class"
PARAM_CMD_RETURN_CODE = "cmd_return_code"
enabled = to_bool(
Config(validate=False)
.config.get(Config.SECTION_CORE, {})
.get(Config.SECTION_CORE_ANALYTICS, "true")
)
def __init__(self, info=None):
from dvc.config import Config
from dvc.lock import Lock
logger.debug("Analytics is {}abled.".format("en" if enabled else "dis"))
if info is None:
info = {}
return enabled
self.info = info
cdir = Config.get_global_config_dir()
try:
os.makedirs(cdir)
except OSError as exc:
if exc.errno != errno.EEXIST:
raise
def send(report):
"""
Side effect: Removes the report after sending it.
self.user_id_file = os.path.join(cdir, self.USER_ID_FILE)
self.user_id_file_lock = Lock(self.user_id_file + ".lock")
The report is generated and stored in a temporary file, see:
`collect_and_send_report`. Sending happens on another process,
thus, the need of removing such file afterwards.
"""
url = "https://analytics.dvc.org"
headers = {"content-type": "application/json"}
@staticmethod
def load(path):
"""Loads analytics report from json file specified by path.
with open(report, "rb") as fobj:
requests.post(url, data=fobj, headers=headers, timeout=5)
Args:
path (str): path to json file with analytics report.
"""
with open(path, "r") as fobj:
analytics = Analytics(info=json.load(fobj))
os.unlink(path)
return analytics
os.remove(report)
def _write_user_id(self):
import uuid
with open(self.user_id_file, "w+") as fobj:
user_id = str(uuid.uuid4())
info = {self.PARAM_USER_ID: user_id}
json.dump(info, fobj)
def _scm_in_use():
try:
scm = SCM(root_dir=Repo.find_root())
return type(scm).__name__
except NotDvcRepoError:
pass
def _runtime_info():
"""
Gather information from the environment where DVC runs to fill a report.
"""
return {
"dvc_version": __version__,
"is_binary": is_binary(),
"scm_class": _scm_in_use(),
"system_info": _system_info(),
"user_id": _find_or_create_user_id(),
}
def _system_info():
system = platform.system()
if system == "Windows":
version = sys.getwindowsversion()
return {
"os": "windows",
"windows_version_build": version.build,
"windows_version_major": version.major,
"windows_version_minor": version.minor,
"windows_version_service_pack": version.service_pack,
}
if system == "Darwin":
return {"os": "mac", "mac_version": platform.mac_ver()[0]}
if system == "Linux":
return {
"os": "linux",
"linux_distro": distro.id(),
"linux_distro_like": distro.like(),
"linux_distro_version": distro.version(),
}
# We don't collect data for any other system.
raise NotImplementedError
def _find_or_create_user_id():
"""
The user's ID is stored on a file under the global config directory.
The file should contain a JSON with a "user_id" key:
{"user_id": "16fd2706-8baf-433b-82eb-8c7fada847da"}
IDs are generated randomly with UUID.
"""
config_dir = Config.get_global_config_dir()
fname = os.path.join(config_dir, "user_id")
lockfile = os.path.join(config_dir, "user_id.lock")
# Since the `fname` and `lockfile` are under the global config,
# we need to make sure such directory exist already.
makedirs(config_dir, exist_ok=True)
try:
with Lock(lockfile):
try:
with open(fname, "r") as fobj:
user_id = json.load(fobj)["user_id"]
except (FileNotFoundError, ValueError, KeyError):
user_id = str(uuid.uuid4())
with open(fname, "w") as fobj:
json.dump({"user_id": user_id}, fobj)
return user_id
def _read_user_id(self):
if not os.path.exists(self.user_id_file):
return None
with open(self.user_id_file, "r") as fobj:
try:
info = json.load(fobj)
except ValueError as exc:
logger.debug("Failed to load user_id: {}".format(exc))
return None
return info[self.PARAM_USER_ID]
def _get_user_id(self):
from dvc.lock import LockError
try:
with self.user_id_file_lock:
user_id = self._read_user_id()
if user_id is None:
user_id = self._write_user_id()
return user_id
except LockError:
msg = "Failed to acquire '{}'"
logger.debug(msg.format(self.user_id_file_lock.lockfile))
def _collect_windows(self):
import sys
version = sys.getwindowsversion() # pylint: disable=no-member
info = {}
info[self.PARAM_OS] = "windows"
info[self.PARAM_WINDOWS_VERSION_MAJOR] = version.major
info[self.PARAM_WINDOWS_VERSION_MINOR] = version.minor
info[self.PARAM_WINDOWS_VERSION_BUILD] = version.build
info[self.PARAM_WINDOWS_VERSION_SERVICE_PACK] = version.service_pack
return info
def _collect_darwin(self):
import platform
info = {}
info[self.PARAM_OS] = "mac"
info[self.PARAM_MAC_VERSION] = platform.mac_ver()[0]
return info
def _collect_linux(self):
import distro
info = {}
info[self.PARAM_OS] = "linux"
info[self.PARAM_LINUX_DISTRO] = distro.id()
info[self.PARAM_LINUX_DISTRO_VERSION] = distro.version()
info[self.PARAM_LINUX_DISTRO_LIKE] = distro.like()
return info
def _collect_system_info(self):
import platform
system = platform.system()
if system == "Windows":
return self._collect_windows()
if system == "Darwin":
return self._collect_darwin()
if system == "Linux":
return self._collect_linux()
raise NotImplementedError
def collect(self):
"""Collect analytics report."""
from dvc.scm import SCM
from dvc.utils import is_binary
from dvc.repo import Repo
from dvc.exceptions import NotDvcRepoError
self.info[self.PARAM_DVC_VERSION] = __version__
self.info[self.PARAM_IS_BINARY] = is_binary()
self.info[self.PARAM_USER_ID] = self._get_user_id()
self.info[self.PARAM_SYSTEM_INFO] = self._collect_system_info()
try:
scm = SCM(root_dir=Repo.find_root())
self.info[self.PARAM_SCM_CLASS] = type(scm).__name__
except NotDvcRepoError:
pass
def collect_cmd(self, args, ret):
"""Collect analytics info from a CLI command."""
from dvc.command.daemon import CmdDaemonAnalytics
assert isinstance(ret, int) or ret is None
if ret is not None:
self.info[self.PARAM_CMD_RETURN_CODE] = ret
if args is not None and hasattr(args, "func"):
assert args.func != CmdDaemonAnalytics
self.info[self.PARAM_CMD_CLASS] = args.func.__name__
def dump(self):
"""Save analytics report to a temporary file.
Returns:
str: path to the temporary file that contains the analytics report.
"""
import tempfile
with tempfile.NamedTemporaryFile(delete=False, mode="w") as fobj:
json.dump(self.info, fobj)
return fobj.name
@staticmethod
def _is_enabled_config(config):
from dvc.config import Config
core = config.config.get(Config.SECTION_CORE, {})
return core.get(Config.SECTION_CORE_ANALYTICS, True)
@staticmethod
def _get_current_config():
from dvc.config import Config
from dvc.repo import Repo
from dvc.exceptions import NotDvcRepoError
try:
dvc_dir = Repo.find_dvc_dir()
config = Config(dvc_dir)
except NotDvcRepoError:
config = Config(validate=False)
return config
@staticmethod
def is_enabled(cmd=None):
from dvc.command.daemon import CmdDaemonBase
if env2bool("DVC_TEST"):
return False
if isinstance(cmd, CmdDaemonBase):
return False
config = (
Analytics._get_current_config()
if cmd is None or not hasattr(cmd, "config")
else cmd.config
)
assert config is not None
enabled = Analytics._is_enabled_config(config)
logger.debug(
"Analytics is {}.".format("enabled" if enabled else "disabled")
)
return enabled
@staticmethod
def send_cmd(cmd, args, ret):
"""Collect and send analytics for CLI command.
Args:
args (list): parsed args for the CLI command.
ret (int): return value of the CLI command.
"""
from dvc.daemon import daemon
if not Analytics.is_enabled(cmd):
return
analytics = Analytics()
analytics.collect_cmd(args, ret)
daemon(["analytics", analytics.dump()])
def send(self):
"""Collect and send analytics."""
import requests
if not self.is_enabled():
return
self.collect()
logger.debug("Sending analytics: {}".format(self.info))
try:
requests.post(self.URL, json=self.info, timeout=self.TIMEOUT_POST)
except requests.exceptions.RequestException as exc:
logger.debug("Failed to send analytics: {}".format(str(exc)))
except LockError:
logger.debug("Failed to acquire {lockfile}".format(lockfile=lockfile))
+1 -1
View File
@@ -1,5 +1,5 @@
from contextlib import contextmanager
import os
from contextlib import contextmanager
try:
from contextlib import _GeneratorContextManager as GCM
+2 -2
View File
@@ -1,13 +1,13 @@
"""Manages cache of a dvc repo."""
from __future__ import unicode_literals
import os
from collections import defaultdict
from funcy import cached_property
from dvc.utils.compat import builtin_str
from dvc.config import Config
from dvc.utils.compat import builtin_str
class CacheConfig(object):
+23 -24
View File
@@ -1,42 +1,41 @@
"""Command line interface for dvc."""
from __future__ import print_function
from __future__ import unicode_literals
import sys
import argparse
import logging
import sys
from dvc.command.base import fix_subparsers
import dvc.command.init as init
import dvc.command.add as add
import dvc.command.cache as cache
import dvc.command.checkout as checkout
import dvc.command.commit as commit
import dvc.command.config as config
import dvc.command.daemon as daemon
import dvc.command.data_sync as data_sync
import dvc.command.destroy as destroy
import dvc.command.diff as diff
import dvc.command.gc as gc
import dvc.command.get as get
import dvc.command.get_url as get_url
import dvc.command.destroy as destroy
import dvc.command.remove as remove
import dvc.command.move as move
import dvc.command.unprotect as unprotect
import dvc.command.run as run
import dvc.command.repro as repro
import dvc.command.data_sync as data_sync
import dvc.command.gc as gc
import dvc.command.add as add
import dvc.command.imp as imp
import dvc.command.imp_url as imp_url
import dvc.command.config as config
import dvc.command.checkout as checkout
import dvc.command.remote as remote
import dvc.command.cache as cache
import dvc.command.metrics as metrics
import dvc.command.init as init
import dvc.command.install as install
import dvc.command.root as root
import dvc.command.lock as lock
import dvc.command.metrics as metrics
import dvc.command.move as move
import dvc.command.pipeline as pipeline
import dvc.command.daemon as daemon
import dvc.command.commit as commit
import dvc.command.remote as remote
import dvc.command.remove as remove
import dvc.command.repro as repro
import dvc.command.root as root
import dvc.command.run as run
import dvc.command.tag as tag
import dvc.command.diff as diff
import dvc.command.version as version
import dvc.command.unprotect as unprotect
import dvc.command.update as update
import dvc.command.version as version
from dvc.command.base import fix_subparsers
from dvc.exceptions import DvcParserError
@@ -106,7 +105,7 @@ def get_parent_parser():
"""Create instances of a parser containing common arguments shared among
all the commands.
When overwritting `-q` or `-v`, you need to instantiate a new object
When overwriting `-q` or `-v`, you need to instantiate a new object
in order to prevent some weird behavior.
"""
parent_parser = argparse.ArgumentParser(add_help=False)
+2 -1
View File
@@ -3,8 +3,9 @@ from __future__ import unicode_literals
import argparse
import logging
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
from dvc.exceptions import DvcException
from dvc.command.base import CmdBase, append_doc_link
from dvc.exceptions import RecursiveAddingWhileUsingFilename
+6 -8
View File
@@ -1,6 +1,5 @@
from __future__ import unicode_literals
import colorama
import logging
@@ -23,15 +22,13 @@ def fix_subparsers(subparsers):
def append_doc_link(help_message, path):
from dvc.utils import format_link
if not path:
return help_message
doc_base = "https://man.dvc.org/"
return "{message}\nDocumentation: <{blue}{base}{path}{nc}>".format(
message=help_message,
base=doc_base,
path=path,
blue=colorama.Fore.CYAN,
nc=colorama.Fore.RESET,
return "{message}\nDocumentation: {link}".format(
message=help_message, link=format_link(doc_base + path)
)
@@ -43,7 +40,8 @@ class CmdBase(object):
self.repo = Repo()
self.config = self.repo.config
self.args = args
updater = Updater(self.repo.dvc_dir)
hardlink_lock = self.config.config["core"].get("hardlink_lock", False)
updater = Updater(self.repo.dvc_dir, hardlink_lock=hardlink_lock)
updater.check()
@property
+3 -2
View File
@@ -2,9 +2,10 @@ from __future__ import unicode_literals
import argparse
from dvc.command.base import fix_subparsers, append_doc_link
from dvc.command.config import CmdConfig
from dvc.cache import CacheConfig
from dvc.command.base import append_doc_link
from dvc.command.base import fix_subparsers
from dvc.command.config import CmdConfig
class CmdCacheDir(CmdConfig):
+2 -1
View File
@@ -2,7 +2,8 @@ from __future__ import unicode_literals
import argparse
from dvc.command.base import CmdBase, append_doc_link
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
class CmdCheckout(CmdBase):
+2 -1
View File
@@ -3,8 +3,9 @@ from __future__ import unicode_literals
import argparse
import logging
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
from dvc.exceptions import DvcException
from dvc.command.base import CmdBase, append_doc_link
logger = logging.getLogger(__name__)
+2 -1
View File
@@ -3,7 +3,8 @@ from __future__ import unicode_literals
import argparse
import logging
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBaseNoRepo
from dvc.config import Config
+10 -5
View File
@@ -1,6 +1,7 @@
from __future__ import unicode_literals
from dvc.command.base import CmdBaseNoRepo, fix_subparsers
from dvc.command.base import CmdBaseNoRepo
from dvc.command.base import fix_subparsers
class CmdDaemonBase(CmdBaseNoRepo):
@@ -11,11 +12,16 @@ class CmdDaemonUpdater(CmdDaemonBase):
def run(self):
import os
from dvc.repo import Repo
from dvc.config import Config
from dvc.updater import Updater
root_dir = Repo.find_root()
dvc_dir = os.path.join(root_dir, Repo.DVC_DIR)
updater = Updater(dvc_dir)
config = Config(dvc_dir, verify=False)
hardlink_lock = config.config.get("core", {}).get(
"hardlink_lock", False
)
updater = Updater(dvc_dir, hardlink_lock=hardlink_lock)
updater.fetch(detach=False)
return 0
@@ -23,10 +29,9 @@ class CmdDaemonUpdater(CmdDaemonBase):
class CmdDaemonAnalytics(CmdDaemonBase):
def run(self):
from dvc.analytics import Analytics
from dvc import analytics
analytics = Analytics.load(self.args.target)
analytics.send()
analytics.send(self.args.target)
return 0
+2 -1
View File
@@ -3,7 +3,8 @@ from __future__ import unicode_literals
import argparse
import logging
from dvc.command.base import CmdBase, append_doc_link
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
from dvc.exceptions import DvcException
+2 -1
View File
@@ -4,7 +4,8 @@ import argparse
import logging
import dvc.prompt as prompt
from dvc.command.base import CmdBase, append_doc_link
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
from dvc.exceptions import DvcException
+3 -2
View File
@@ -1,14 +1,15 @@
from __future__ import unicode_literals
import argparse
import logging
import humanize
import inflect
import logging
from funcy import compact
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
from dvc.exceptions import DvcException
from dvc.command.base import CmdBase, append_doc_link
logger = logging.getLogger(__name__)
+4 -3
View File
@@ -1,11 +1,12 @@
from __future__ import unicode_literals
import argparse
import os
import dvc.prompt as prompt
import logging
import os
from dvc.command.base import CmdBase, append_doc_link
import dvc.prompt as prompt
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
logger = logging.getLogger(__name__)
+10 -4
View File
@@ -3,8 +3,9 @@ from __future__ import unicode_literals
import argparse
import logging
from .base import append_doc_link
from .base import CmdBaseNoRepo
from dvc.exceptions import DvcException
from .base import CmdBaseNoRepo, append_doc_link
logger = logging.getLogger(__name__)
@@ -32,7 +33,7 @@ class CmdGet(CmdBaseNoRepo):
def add_parser(subparsers, parent_parser):
GET_HELP = "Download data from DVC repository."
GET_HELP = "Download/copy files or directories from DVC repository."
get_parser = subparsers.add_parser(
"get",
parents=[parent_parser],
@@ -43,9 +44,14 @@ def add_parser(subparsers, parent_parser):
get_parser.add_argument(
"url", help="URL of Git repository with DVC project to download from."
)
get_parser.add_argument("path", help="Path to data within DVC repository.")
get_parser.add_argument(
"-o", "--out", nargs="?", help="Destination path to put data to."
"path", help="Path to a file or directory within a DVC repository."
)
get_parser.add_argument(
"-o",
"--out",
nargs="?",
help="Destination path to copy/download files to.",
)
get_parser.add_argument(
"--rev", nargs="?", help="DVC repository git revision."
+2 -1
View File
@@ -3,8 +3,9 @@ from __future__ import unicode_literals
import argparse
import logging
from .base import append_doc_link
from .base import CmdBaseNoRepo
from dvc.exceptions import DvcException
from .base import CmdBaseNoRepo, append_doc_link
logger = logging.getLogger(__name__)
+2 -1
View File
@@ -3,8 +3,9 @@ from __future__ import unicode_literals
import argparse
import logging
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
from dvc.exceptions import DvcException
from dvc.command.base import CmdBase, append_doc_link
logger = logging.getLogger(__name__)
+7 -1
View File
@@ -3,8 +3,9 @@ from __future__ import unicode_literals
import argparse
import logging
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
from dvc.exceptions import DvcException
from dvc.command.base import CmdBase, append_doc_link
logger = logging.getLogger(__name__)
@@ -43,12 +44,17 @@ def add_parser(subparsers, parent_parser):
"url",
help="Supported urls:\n"
"/path/to/file\n"
"/path/to/directory\n"
"C:\\\\path\\to\\file\n"
"C:\\\\path\\to\\directory\n"
"https://example.com/path/to/file\n"
"s3://bucket/path/to/file\n"
"s3://bucket/path/to/directory\n"
"gs://bucket/path/to/file\n"
"gs://bucket/path/to/directory\n"
"hdfs://example.com/path/to/file\n"
"ssh://example.com:/path/to/file\n"
"ssh://example.com:/path/to/directory\n"
"remote://myremote/path/to/file (see `dvc remote`)",
)
import_parser.add_argument(
+2 -1
View File
@@ -3,7 +3,8 @@ from __future__ import unicode_literals
import argparse
import logging
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBaseNoRepo
logger = logging.getLogger(__name__)
+2 -1
View File
@@ -3,7 +3,8 @@ from __future__ import unicode_literals
import argparse
import logging
from dvc.command.base import CmdBase, append_doc_link
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
logger = logging.getLogger(__name__)
+2 -1
View File
@@ -3,8 +3,9 @@ from __future__ import unicode_literals
import argparse
import logging
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
from dvc.exceptions import DvcException
from dvc.command.base import CmdBase, append_doc_link
logger = logging.getLogger(__name__)
+5 -2
View File
@@ -3,8 +3,11 @@ from __future__ import unicode_literals
import argparse
import logging
from dvc.exceptions import DvcException, BadMetricError
from dvc.command.base import CmdBase, fix_subparsers, append_doc_link
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
from dvc.command.base import fix_subparsers
from dvc.exceptions import BadMetricError
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
+2 -1
View File
@@ -3,8 +3,9 @@ from __future__ import unicode_literals
import argparse
import logging
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
from dvc.exceptions import DvcException
from dvc.command.base import CmdBase, append_doc_link
logger = logging.getLogger(__name__)
+5 -4
View File
@@ -1,13 +1,14 @@
from __future__ import unicode_literals
from dvc.utils import relpath
from dvc.utils.compat import str
import argparse
import logging
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
from dvc.command.base import fix_subparsers
from dvc.exceptions import DvcException
from dvc.command.base import CmdBase, fix_subparsers, append_doc_link
from dvc.utils import relpath
from dvc.utils.compat import str
logger = logging.getLogger(__name__)
+13 -4
View File
@@ -3,7 +3,8 @@ from __future__ import unicode_literals
import argparse
import logging
from dvc.command.base import append_doc_link, fix_subparsers
from dvc.command.base import append_doc_link
from dvc.command.base import fix_subparsers
from dvc.command.config import CmdConfig
from dvc.remote.config import RemoteConfig
@@ -18,6 +19,10 @@ class CmdRemoteConfig(CmdConfig):
class CmdRemoteAdd(CmdRemoteConfig):
def run(self):
if self.args.default:
logger.info(
"Setting '{}' as a default remote.".format(self.args.name)
)
self.remote_config.add(
self.args.name,
self.args.url,
@@ -47,9 +52,13 @@ class CmdRemoteModify(CmdRemoteConfig):
class CmdRemoteDefault(CmdRemoteConfig):
def run(self):
self.remote_config.set_default(
self.args.name, unset=self.args.unset, level=self.args.level
)
if self.args.name is None and not self.args.unset:
name = self.remote_config.get_default(level=self.args.level)
print(name)
else:
self.remote_config.set_default(
self.args.name, unset=self.args.unset, level=self.args.level
)
return 0
+2 -1
View File
@@ -4,8 +4,9 @@ import argparse
import logging
import dvc.prompt as prompt
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
from dvc.exceptions import DvcException
from dvc.command.base import CmdBase, append_doc_link
logger = logging.getLogger(__name__)
+3 -2
View File
@@ -1,10 +1,11 @@
from __future__ import unicode_literals
import argparse
import os
import logging
import os
from dvc.command.base import CmdBase, append_doc_link
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
from dvc.command.metrics import show_metrics
from dvc.command.status import CmdDataStatus
from dvc.exceptions import DvcException
+2 -1
View File
@@ -3,7 +3,8 @@ from __future__ import unicode_literals
import argparse
import logging
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBaseNoRepo
from dvc.utils import relpath
logger = logging.getLogger(__name__)
+2 -1
View File
@@ -3,7 +3,8 @@ from __future__ import unicode_literals
import argparse
import logging
from dvc.command.base import CmdBase, append_doc_link
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
from dvc.exceptions import DvcException
+4 -2
View File
@@ -1,8 +1,10 @@
import logging
from dvc.utils import to_yaml_string
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
from dvc.command.base import fix_subparsers
from dvc.exceptions import DvcException
from dvc.command.base import CmdBase, fix_subparsers, append_doc_link
from dvc.utils import to_yaml_string
logger = logging.getLogger(__name__)
+2 -1
View File
@@ -3,8 +3,9 @@ from __future__ import unicode_literals
import argparse
import logging
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
from dvc.exceptions import DvcException
from dvc.command.base import CmdBase, append_doc_link
logger = logging.getLogger(__name__)
+2 -1
View File
@@ -3,8 +3,9 @@ from __future__ import unicode_literals
import argparse
import logging
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
from dvc.exceptions import DvcException
from dvc.command.base import CmdBase, append_doc_link
logger = logging.getLogger(__name__)
+7 -6
View File
@@ -1,11 +1,11 @@
from __future__ import unicode_literals
import argparse
import itertools
import logging
import os
import platform
import argparse
import logging
import uuid
import itertools
try:
import psutil
@@ -18,7 +18,7 @@ from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.version import __version__
from dvc.exceptions import DvcException, NotDvcRepoError
from dvc.system import System
from dvc.utils.pkg import PKG
logger = logging.getLogger(__name__)
@@ -31,17 +31,18 @@ class CmdVersion(CmdBaseNoRepo):
python_version = platform.python_version()
platform_type = platform.platform()
binary = is_binary()
info = (
"DVC version: {dvc_version}\n"
"Python version: {python_version}\n"
"Platform: {platform_type}\n"
"Binary: {binary}\n"
"Package: {package}\n"
).format(
dvc_version=dvc_version,
python_version=python_version,
platform_type=platform_type,
binary=binary,
package=PKG,
)
try:
@@ -92,7 +93,7 @@ class CmdVersion(CmdBaseNoRepo):
for parent in itertools.chain([path], path.parents):
if parent in partition:
return partition[parent]
return ("unkown", "none")
return ("unknown", "none")
@staticmethod
def get_linktype_support_info(repo):
+100 -165
View File
@@ -1,18 +1,19 @@
"""DVC config objects."""
from __future__ import unicode_literals
from dvc.utils.compat import str, open
import os
import re
import copy
import errno
import configobj
import logging
import os
import re
from schema import Schema, Optional, And, Use, Regex, SchemaError
from dvc.exceptions import DvcException, NotDvcRepoError
import configobj
from voluptuous import Schema, Required, Optional, Invalid
from voluptuous import All, Any, Lower, Range, Coerce, Match
from dvc.exceptions import DvcException
from dvc.exceptions import NotDvcRepoError
from dvc.utils.compat import open, str
logger = logging.getLogger(__name__)
@@ -44,82 +45,43 @@ class NoRemoteError(ConfigError):
def supported_cache_type(types):
"""Checks if link type config option has a valid value.
"""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(",")]
for typ in types:
if typ not in ["reflink", "hardlink", "symlink", "copy"]:
return False
return True
unsupported = set(types) - {"reflink", "hardlink", "symlink", "copy"}
if unsupported:
raise Invalid(
"Unsupported cache type(s): {}".format(", ".join(unsupported))
)
return types
def is_bool(val):
"""Checks that value is a boolean.
Args:
val (str): string value verify.
Returns:
bool: True if value stands for boolean, False otherwise.
"""
return val.lower() in ["true", "false"]
# Checks that value is either true or false and converts it to bool
Bool = All(
Lower,
Any("true", "false"),
lambda v: v == "true",
msg="expected true or false",
)
to_bool = Schema(Bool)
def to_bool(val):
"""Converts value to boolean.
Args:
val (str): string to convert to boolean.
Returns:
bool: True if value.lower() == 'true', False otherwise.
"""
return val.lower() == "true"
def is_whole(val):
"""Checks that value is a whole integer.
Args:
val (str): number string to verify.
Returns:
bool: True if val is a whole number, False otherwise.
"""
return int(val) >= 0
def is_percent(val):
"""Checks that value is a percent.
Args:
val (str): number string to verify.
Returns:
bool: True if 0<=value<=100, False otherwise.
"""
return int(val) >= 0 and int(val) <= 100
class Choices(object):
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
"""
def __init__(self, *choices):
if len(choices) == 1 and isinstance(choices[0], (list, tuple)):
choices = choices[0]
self.choices = choices
def __call__(self, value):
return value in self.choices
return Any(*choices, msg="expected one of {}".format(",".join(choices)))
class Config(object): # pylint: disable=too-many-instance-attributes
@@ -147,33 +109,30 @@ class Config(object): # pylint: disable=too-many-instance-attributes
CONFIG = "config"
CONFIG_LOCAL = "config.local"
CREDENTIALPATH = "credentialpath"
LEVEL_LOCAL = 0
LEVEL_REPO = 1
LEVEL_GLOBAL = 2
LEVEL_SYSTEM = 3
BOOL_SCHEMA = And(str, is_bool, Use(to_bool))
SECTION_CORE = "core"
SECTION_CORE_LOGLEVEL = "loglevel"
SECTION_CORE_LOGLEVEL_SCHEMA = And(
Use(str.lower), Choices("info", "debug", "warning", "error")
SECTION_CORE_LOGLEVEL_SCHEMA = All(
Lower, Choices("info", "debug", "warning", "error")
)
SECTION_CORE_REMOTE = "remote"
SECTION_CORE_INTERACTIVE_SCHEMA = BOOL_SCHEMA
SECTION_CORE_INTERACTIVE = "interactive"
SECTION_CORE_ANALYTICS = "analytics"
SECTION_CORE_ANALYTICS_SCHEMA = BOOL_SCHEMA
SECTION_CORE_CHECKSUM_JOBS = "checksum_jobs"
SECTION_CORE_CHECKSUM_JOBS_SCHEMA = And(Use(int), lambda x: x > 0)
SECTION_CORE_HARDLINK_LOCK = "hardlink_lock"
SECTION_CACHE = "cache"
SECTION_CACHE_DIR = "dir"
SECTION_CACHE_TYPE = "type"
SECTION_CACHE_TYPE_SCHEMA = supported_cache_type
SECTION_CACHE_PROTECTED = "protected"
SECTION_CACHE_SHARED = "shared"
SECTION_CACHE_SHARED_SCHEMA = And(Use(str.lower), Choices("group"))
SECTION_CACHE_SHARED_SCHEMA = All(Lower, Choices("group"))
SECTION_CACHE_LOCAL = "local"
SECTION_CACHE_S3 = "s3"
SECTION_CACHE_GS = "gs"
@@ -182,40 +141,31 @@ class Config(object): # pylint: disable=too-many-instance-attributes
SECTION_CACHE_AZURE = "azure"
SECTION_CACHE_SLOW_LINK_WARNING = "slow_link_warning"
SECTION_CACHE_SCHEMA = {
Optional(SECTION_CACHE_LOCAL): str,
Optional(SECTION_CACHE_S3): str,
Optional(SECTION_CACHE_GS): str,
Optional(SECTION_CACHE_HDFS): str,
Optional(SECTION_CACHE_SSH): str,
Optional(SECTION_CACHE_AZURE): str,
Optional(SECTION_CACHE_DIR): str,
Optional(SECTION_CACHE_TYPE, default=None): SECTION_CACHE_TYPE_SCHEMA,
Optional(SECTION_CACHE_PROTECTED, default=False): BOOL_SCHEMA,
Optional(SECTION_CACHE_SHARED): SECTION_CACHE_SHARED_SCHEMA,
Optional(PRIVATE_CWD): str,
Optional(SECTION_CACHE_SLOW_LINK_WARNING, default=True): BOOL_SCHEMA,
SECTION_CACHE_LOCAL: str,
SECTION_CACHE_S3: str,
SECTION_CACHE_GS: str,
SECTION_CACHE_HDFS: str,
SECTION_CACHE_SSH: str,
SECTION_CACHE_AZURE: str,
SECTION_CACHE_DIR: str,
SECTION_CACHE_TYPE: supported_cache_type,
Optional(SECTION_CACHE_PROTECTED, default=False): Bool,
SECTION_CACHE_SHARED: SECTION_CACHE_SHARED_SCHEMA,
PRIVATE_CWD: str,
Optional(SECTION_CACHE_SLOW_LINK_WARNING, default=True): Bool,
}
SECTION_CORE_SCHEMA = {
Optional(SECTION_CORE_LOGLEVEL): And(
str, Use(str.lower), SECTION_CORE_LOGLEVEL_SCHEMA
),
Optional(SECTION_CORE_REMOTE, default=""): And(str, Use(str.lower)),
Optional(
SECTION_CORE_INTERACTIVE, default=False
): SECTION_CORE_INTERACTIVE_SCHEMA,
Optional(
SECTION_CORE_ANALYTICS, default=True
): SECTION_CORE_ANALYTICS_SCHEMA,
Optional(
SECTION_CORE_CHECKSUM_JOBS, default=None
): SECTION_CORE_CHECKSUM_JOBS_SCHEMA,
SECTION_CORE_LOGLEVEL: SECTION_CORE_LOGLEVEL_SCHEMA,
SECTION_CORE_REMOTE: Lower,
Optional(SECTION_CORE_INTERACTIVE, default=False): Bool,
Optional(SECTION_CORE_ANALYTICS, default=True): Bool,
SECTION_CORE_CHECKSUM_JOBS: All(Coerce(int), Range(1)),
Optional(SECTION_CORE_HARDLINK_LOCK, default=False): Bool,
}
# backward compatibility
SECTION_AWS = "aws"
SECTION_AWS_STORAGEPATH = "storagepath"
SECTION_AWS_CREDENTIALPATH = "credentialpath"
# aws specific options
SECTION_AWS_CREDENTIALPATH = CREDENTIALPATH
SECTION_AWS_ENDPOINT_URL = "endpointurl"
SECTION_AWS_LIST_OBJECTS = "listobjects"
SECTION_AWS_REGION = "region"
@@ -223,39 +173,24 @@ class Config(object): # pylint: disable=too-many-instance-attributes
SECTION_AWS_USE_SSL = "use_ssl"
SECTION_AWS_SSE = "sse"
SECTION_AWS_ACL = "acl"
SECTION_AWS_SCHEMA = {
SECTION_AWS_STORAGEPATH: str,
Optional(SECTION_AWS_REGION): str,
Optional(SECTION_AWS_PROFILE): str,
Optional(SECTION_AWS_CREDENTIALPATH): str,
Optional(SECTION_AWS_ENDPOINT_URL): str,
Optional(SECTION_AWS_LIST_OBJECTS, default=False): BOOL_SCHEMA,
Optional(SECTION_AWS_USE_SSL, default=True): BOOL_SCHEMA,
Optional(SECTION_AWS_SSE): str,
Optional(SECTION_AWS_ACL): str,
}
# backward compatibility
SECTION_GCP = "gcp"
SECTION_GCP_STORAGEPATH = SECTION_AWS_STORAGEPATH
SECTION_GCP_CREDENTIALPATH = SECTION_AWS_CREDENTIALPATH
# gcp specific options
SECTION_GCP_CREDENTIALPATH = CREDENTIALPATH
SECTION_GCP_PROJECTNAME = "projectname"
SECTION_GCP_SCHEMA = {
SECTION_GCP_STORAGEPATH: str,
Optional(SECTION_GCP_PROJECTNAME): str,
}
# backward compatibility
SECTION_LOCAL = "local"
SECTION_LOCAL_STORAGEPATH = SECTION_AWS_STORAGEPATH
SECTION_LOCAL_SCHEMA = {SECTION_LOCAL_STORAGEPATH: str}
# azure specific option
SECTION_AZURE_CONNECTION_STRING = "connection_string"
# Alibabacloud oss options
SECTION_OSS_ACCESS_KEY_ID = "oss_key_id"
SECTION_OSS_ACCESS_KEY_SECRET = "oss_key_secret"
SECTION_OSS_ENDPOINT = "oss_endpoint"
# GDrive options
SECTION_GDRIVE_CLIENT_ID = "gdrive_client_id"
SECTION_GDRIVE_CLIENT_SECRET = "gdrive_client_secret"
SECTION_GDRIVE_USER_CREDENTIALS_FILE = "gdrive_user_credentials_file"
SECTION_REMOTE_REGEX = r'^\s*remote\s*"(?P<name>.*)"\s*$'
SECTION_REMOTE_FMT = 'remote "{}"'
SECTION_REMOTE_URL = "url"
@@ -268,51 +203,51 @@ class Config(object): # pylint: disable=too-many-instance-attributes
SECTION_REMOTE_GSS_AUTH = "gss_auth"
SECTION_REMOTE_NO_TRAVERSE = "no_traverse"
SECTION_REMOTE_SCHEMA = {
SECTION_REMOTE_URL: str,
Optional(SECTION_AWS_REGION): str,
Optional(SECTION_AWS_PROFILE): str,
Optional(SECTION_AWS_CREDENTIALPATH): str,
Optional(SECTION_AWS_ENDPOINT_URL): str,
Optional(SECTION_AWS_LIST_OBJECTS, default=False): BOOL_SCHEMA,
Optional(SECTION_AWS_USE_SSL, default=True): BOOL_SCHEMA,
Optional(SECTION_AWS_SSE): str,
Optional(SECTION_AWS_ACL): str,
Optional(SECTION_GCP_PROJECTNAME): str,
Optional(SECTION_CACHE_TYPE): SECTION_CACHE_TYPE_SCHEMA,
Optional(SECTION_CACHE_PROTECTED, default=False): BOOL_SCHEMA,
Optional(SECTION_REMOTE_USER): str,
Optional(SECTION_REMOTE_PORT): Use(int),
Optional(SECTION_REMOTE_KEY_FILE): str,
Optional(SECTION_REMOTE_TIMEOUT): Use(int),
Optional(SECTION_REMOTE_PASSWORD): str,
Optional(SECTION_REMOTE_ASK_PASSWORD): BOOL_SCHEMA,
Optional(SECTION_REMOTE_GSS_AUTH): BOOL_SCHEMA,
Optional(SECTION_AZURE_CONNECTION_STRING): str,
Optional(SECTION_OSS_ACCESS_KEY_ID): str,
Optional(SECTION_OSS_ACCESS_KEY_SECRET): str,
Optional(SECTION_OSS_ENDPOINT): str,
Optional(PRIVATE_CWD): str,
Optional(SECTION_REMOTE_NO_TRAVERSE, default=True): BOOL_SCHEMA,
Required(SECTION_REMOTE_URL): str,
SECTION_AWS_REGION: str,
SECTION_AWS_PROFILE: str,
SECTION_AWS_CREDENTIALPATH: str,
SECTION_AWS_ENDPOINT_URL: str,
Optional(SECTION_AWS_LIST_OBJECTS, default=False): Bool,
Optional(SECTION_AWS_USE_SSL, default=True): Bool,
SECTION_AWS_SSE: str,
SECTION_AWS_ACL: str,
SECTION_GCP_PROJECTNAME: str,
SECTION_CACHE_TYPE: supported_cache_type,
Optional(SECTION_CACHE_PROTECTED, default=False): Bool,
SECTION_REMOTE_USER: str,
SECTION_REMOTE_PORT: Coerce(int),
SECTION_REMOTE_KEY_FILE: str,
SECTION_REMOTE_TIMEOUT: Coerce(int),
SECTION_REMOTE_PASSWORD: str,
SECTION_REMOTE_ASK_PASSWORD: Bool,
SECTION_REMOTE_GSS_AUTH: Bool,
SECTION_AZURE_CONNECTION_STRING: str,
SECTION_OSS_ACCESS_KEY_ID: str,
SECTION_OSS_ACCESS_KEY_SECRET: str,
SECTION_OSS_ENDPOINT: str,
SECTION_GDRIVE_CLIENT_ID: str,
SECTION_GDRIVE_CLIENT_SECRET: str,
SECTION_GDRIVE_USER_CREDENTIALS_FILE: str,
PRIVATE_CWD: str,
Optional(SECTION_REMOTE_NO_TRAVERSE, default=True): Bool,
}
SECTION_STATE = "state"
SECTION_STATE_ROW_LIMIT = "row_limit"
SECTION_STATE_ROW_CLEANUP_QUOTA = "row_cleanup_quota"
SECTION_STATE_SCHEMA = {
Optional(SECTION_STATE_ROW_LIMIT): And(Use(int), is_whole),
Optional(SECTION_STATE_ROW_CLEANUP_QUOTA): And(Use(int), is_percent),
SECTION_STATE_ROW_LIMIT: All(Coerce(int), Range(1)),
SECTION_STATE_ROW_CLEANUP_QUOTA: All(Coerce(int), Range(0, 100)),
}
SCHEMA = {
Optional(SECTION_CORE, default={}): SECTION_CORE_SCHEMA,
Optional(Regex(SECTION_REMOTE_REGEX)): SECTION_REMOTE_SCHEMA,
Match(SECTION_REMOTE_REGEX): SECTION_REMOTE_SCHEMA,
Optional(SECTION_CACHE, default={}): SECTION_CACHE_SCHEMA,
Optional(SECTION_STATE, default={}): SECTION_STATE_SCHEMA,
# backward compatibility
Optional(SECTION_AWS, default={}): SECTION_AWS_SCHEMA,
Optional(SECTION_GCP, default={}): SECTION_GCP_SCHEMA,
Optional(SECTION_LOCAL, default={}): SECTION_LOCAL_SCHEMA,
}
COMPILED_SCHEMA = Schema(SCHEMA)
def __init__(self, dvc_dir=None, validate=True):
self.dvc_dir = dvc_dir
@@ -451,9 +386,9 @@ class Config(object): # pylint: disable=too-many-instance-attributes
d = self.config.dict()
try:
d = Schema(self.SCHEMA).validate(d)
except SchemaError as exc:
raise ConfigError("config format error", cause=exc)
d = self.COMPILED_SCHEMA(d)
except Invalid as exc:
raise ConfigError(str(exc), cause=exc)
self.config = configobj.ConfigObj(d, write_empty_values=True)
def save(self, config=None):
+5 -5
View File
@@ -1,15 +1,15 @@
"""Launch `dvc daemon` command in a separate detached process."""
from __future__ import unicode_literals
import os
import sys
import inspect
import logging
import os
import sys
from subprocess import Popen
from dvc.env import DVC_DAEMON
from dvc.utils import is_binary, fix_env
from dvc.utils import fix_env
from dvc.utils import is_binary
from dvc.utils.compat import cast_bytes_py2
@@ -39,7 +39,7 @@ def _spawn_windows(cmd, env):
def _spawn_posix(cmd, env):
# NOTE: using os._exit instead of sys.exit, because dvc built
# with PyInstaller has trouble with SystemExit exeption and throws
# with PyInstaller has trouble with SystemExit exception and throws
# errors such as "[26338] Failed to execute script __main__"
try:
pid = os.fork()
+50 -98
View File
@@ -1,14 +1,55 @@
"""Draws DAG in ASCII."""
from __future__ import unicode_literals
from __future__ import print_function
from __future__ import unicode_literals
import sys
import logging
import math
import os
import pydoc
import sys
from grandalf.graphs import Vertex, Edge, Graph
from grandalf.graphs import Edge
from grandalf.graphs import Graph
from grandalf.graphs import Vertex
from grandalf.layouts import SugiyamaLayout
from grandalf.routing import route_with_lines, EdgeViewer
from grandalf.routing import EdgeViewer
from grandalf.routing import route_with_lines
from dvc.env import DVC_PAGER
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("({}) 2>{}".format(DEFAULT_PAGER, os.devnull)) == 0:
return make_pager(DEFAULT_PAGER_FORMATTED)
logger.warning(
"Unable to find `less` in the PATH. Check out "
"man.dvc.org/doc/command-reference/pipeline/show for more info."
)
return pydoc.plainpager
class VertexViewer(object):
@@ -58,99 +99,10 @@ class AsciiCanvas(object):
def draw(self):
"""Draws ASCII canvas on the screen."""
if sys.stdout.isatty(): # pragma: no cover
from asciimatics.screen import Screen
Screen.wrapper(self._do_draw)
else:
for line in self.canvas:
print("".join(line))
def _do_draw(self, screen): # pragma: no cover
# pylint: disable=too-many-locals
# pylint: disable=too-many-branches, too-many-statements
from dvc.system import System
from asciimatics.event import KeyboardEvent
offset_x = 0
offset_y = 0
smaxrow, smaxcol = screen.dimensions
assert smaxrow > 1
assert smaxcol > 1
smaxrow -= 1
smaxcol -= 1
if self.lines + 1 > smaxrow:
max_y = self.lines + 1 - smaxrow
else:
max_y = 0
if self.cols + 1 > smaxcol:
max_x = self.cols + 1 - smaxcol
else:
max_x = 0
while True:
for y in range(smaxrow + 1):
y_index = offset_y + y
line = []
for x in range(smaxcol + 1):
x_index = offset_x + x
if (
len(self.canvas) > y_index
and len(self.canvas[y_index]) > x_index
):
line.append(self.canvas[y_index][x_index])
else:
line.append(" ")
assert len(line) == (smaxcol + 1)
screen.print_at("".join(line), 0, y)
screen.refresh()
# NOTE: get_event() doesn't block by itself,
# so we have to do the blocking ourselves.
#
# NOTE: using this workaround while waiting for PR [1]
# to get merged and released. After that need to adjust
# asciimatics version requirements.
#
# [1] https://github.com/peterbrittain/asciimatics/pull/188
System.wait_for_input(self.TIMEOUT)
event = screen.get_event()
if not isinstance(event, KeyboardEvent):
continue
k = event.key_code
if k == screen.KEY_DOWN or k == ord("s"):
offset_y += 1
elif k == screen.KEY_PAGE_DOWN or k == ord("S"):
offset_y += smaxrow
elif k == screen.KEY_UP or k == ord("w"):
offset_y -= 1
elif k == screen.KEY_PAGE_UP or k == ord("W"):
offset_y -= smaxrow
elif k == screen.KEY_RIGHT or k == ord("d"):
offset_x += 1
elif k == ord("D"):
offset_x += smaxcol
elif k == screen.KEY_LEFT or k == ord("a"):
offset_x -= 1
elif k == ord("A"):
offset_x -= smaxcol
elif k == ord("q") or k == ord("Q"):
break
if offset_y > max_y:
offset_y = max_y
elif offset_y < 0:
offset_y = 0
if offset_x > max_x:
offset_x = max_x
elif offset_x < 0:
offset_x = 0
pager = find_pager()
lines = map("".join, self.canvas)
joined_lines = os.linesep.join(lines)
pager(joined_lines)
def point(self, x, y, char):
"""Create a point on ASCII canvas.
+3 -3
View File
@@ -1,10 +1,10 @@
"""Manages dvc remotes that user can use with push/pull/status comamnds."""
"""Manages dvc remotes that user can use with push/pull/status commands."""
from __future__ import unicode_literals
import logging
from dvc.config import Config, NoRemoteError
from dvc.config import Config
from dvc.config import NoRemoteError
from dvc.remote import Remote
+9 -13
View File
@@ -1,21 +1,17 @@
from __future__ import unicode_literals
import schema
from dvc.scheme import Schemes
import dvc.output as output
from dvc.output.base import OutputBase
from dvc.dependency.s3 import DependencyS3
from dvc.dependency.gs import DependencyGS
from dvc.dependency.local import DependencyLOCAL
from dvc.dependency.hdfs import DependencyHDFS
from dvc.dependency.ssh import DependencySSH
from dvc.dependency.http import DependencyHTTP
from dvc.dependency.https import DependencyHTTPS
from .repo import DependencyREPO
from dvc.dependency.local import DependencyLOCAL
from dvc.dependency.s3 import DependencyS3
from dvc.dependency.ssh import DependencySSH
from dvc.output.base import OutputBase
from dvc.remote import Remote
from dvc.scheme import Schemes
from .repo import DependencyREPO
DEPS = [
@@ -44,9 +40,9 @@ DEP_MAP = {
# 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[schema.Optional(OutputBase.PARAM_CACHE)]
del SCHEMA[schema.Optional(OutputBase.PARAM_METRIC)]
SCHEMA[schema.Optional(DependencyREPO.PARAM_REPO)] = DependencyREPO.REPO_SCHEMA
del SCHEMA[OutputBase.PARAM_CACHE]
del SCHEMA[OutputBase.PARAM_METRIC]
SCHEMA[DependencyREPO.PARAM_REPO] = DependencyREPO.REPO_SCHEMA
def _get(stage, p, info):
+1 -1
View File
@@ -1,7 +1,7 @@
from __future__ import unicode_literals
from dvc.output.gs import OutputGS
from dvc.dependency.base import DependencyBase
from dvc.output.gs import OutputGS
class DependencyGS(DependencyBase, OutputGS):
+1 -1
View File
@@ -1,7 +1,7 @@
from __future__ import unicode_literals
from dvc.output.hdfs import OutputHDFS
from dvc.dependency.base import DependencyBase
from dvc.output.hdfs import OutputHDFS
class DependencyHDFS(DependencyBase, OutputHDFS):
+1 -1
View File
@@ -1,8 +1,8 @@
from __future__ import unicode_literals
from dvc.dependency.base import DependencyBase
from dvc.output.base import OutputBase
from dvc.remote.http import RemoteHTTP
from dvc.dependency.base import DependencyBase
class DependencyHTTP(DependencyBase, OutputBase):
+1 -1
View File
@@ -1,7 +1,7 @@
from __future__ import unicode_literals
from dvc.remote.https import RemoteHTTPS
from .http import DependencyHTTP
from dvc.remote.https import RemoteHTTPS
class DependencyHTTPS(DependencyHTTP):
+4 -10
View File
@@ -1,15 +1,13 @@
from __future__ import unicode_literals
import copy
from funcy import merge
from schema import Optional
from contextlib import contextmanager
from dvc.external_repo import external_repo
from dvc.utils.compat import str
from funcy import merge
from .local import DependencyLOCAL
from dvc.external_repo import external_repo
from dvc.utils.compat import str
class DependencyREPO(DependencyLOCAL):
@@ -18,11 +16,7 @@ class DependencyREPO(DependencyLOCAL):
PARAM_REV = "rev"
PARAM_REV_LOCK = "rev_lock"
REPO_SCHEMA = {
Optional(PARAM_URL): str,
Optional(PARAM_REV): str,
Optional(PARAM_REV_LOCK): str,
}
REPO_SCHEMA = {PARAM_URL: str, PARAM_REV: str, PARAM_REV_LOCK: str}
def __init__(self, def_repo, stage, *args, **kwargs):
self.def_repo = def_repo
+1 -1
View File
@@ -1,7 +1,7 @@
from __future__ import unicode_literals
from dvc.output.s3 import OutputS3
from dvc.dependency.base import DependencyBase
from dvc.output.s3 import OutputS3
class DependencyS3(DependencyBase, OutputS3):
+1 -1
View File
@@ -1,7 +1,7 @@
from __future__ import unicode_literals
from dvc.output.ssh import OutputSSH
from dvc.dependency.base import DependencyBase
from dvc.output.ssh import OutputSSH
class DependencySSH(DependencyBase, OutputSSH):
+1
View File
@@ -1 +1,2 @@
DVC_DAEMON = "DVC_DAEMON"
DVC_PAGER = "DVC_PAGER"
+27 -13
View File
@@ -1,12 +1,11 @@
"""Exceptions raised by the dvc."""
from __future__ import unicode_literals
from dvc.utils.compat import str, builtin_str
import traceback
from dvc.utils import relpath
from dvc.utils.compat import builtin_str
from dvc.utils.compat import str
class DvcException(Exception):
@@ -60,10 +59,12 @@ class OutputNotFoundError(DvcException):
output (unicode): path to the file/directory.
"""
def __init__(self, output):
def __init__(self, output, repo=None):
self.output = output
self.repo = repo
super(OutputNotFoundError, self).__init__(
"unable to find DVC-file with output '{path}'".format(
path=relpath(output)
path=relpath(self.output)
)
)
@@ -282,14 +283,6 @@ class UrlNotDvcRepoError(DvcException):
)
class GetDVCFileError(DvcException):
def __init__(self):
super(GetDVCFileError, self).__init__(
"the given path is a DVC-file, you must specify a data file "
"or a directory"
)
class GitHookAlreadyExistsError(DvcException):
def __init__(self, hook_name):
super(GitHookAlreadyExistsError, self).__init__(
@@ -329,3 +322,24 @@ class CheckoutError(DvcException):
class CollectCacheError(DvcException):
pass
class NoRemoteInExternalRepoError(DvcException):
def __init__(self, url):
super(NoRemoteInExternalRepoError, self).__init__(
"No DVC remote is specified in target repository '{}'.".format(url)
)
class NoOutputInExternalRepoError(DvcException):
def __init__(self, path, external_repo_path, external_repo_url):
super(NoOutputInExternalRepoError, self).__init__(
"Output '{}' not found in target repository '{}'".format(
relpath(path, external_repo_path), external_repo_url
)
)
class HTTPError(DvcException):
def __init__(self, code, reason):
super(HTTPError, self).__init__("'{} {}'".format(code, reason))
+62 -5
View File
@@ -2,12 +2,17 @@ from __future__ import unicode_literals
import os
import tempfile
from contextlib import contextmanager
from distutils.dir_util import copy_tree
from contextlib import contextmanager
from funcy import retry
from dvc.utils import remove
from dvc.config import NoRemoteError, ConfigError
from dvc.exceptions import NoRemoteInExternalRepoError
from dvc.remote import RemoteConfig
from dvc.exceptions import NoOutputInExternalRepoError
from dvc.exceptions import OutputNotFoundError
from dvc.utils.fs import remove
REPO_CACHE = {}
@@ -19,7 +24,14 @@ def external_repo(url=None, rev=None, rev_lock=None, cache_dir=None):
path = _external_repo(url=url, rev=rev_lock or rev, cache_dir=cache_dir)
repo = Repo(path)
yield repo
try:
yield repo
except NoRemoteError:
raise NoRemoteInExternalRepoError(url)
except OutputNotFoundError as exc:
if exc.repo is repo:
raise NoOutputInExternalRepoError(exc.output, repo.root_dir, url)
raise
repo.close()
@@ -50,10 +62,29 @@ def _external_repo(url=None, rev=None, cache_dir=None):
REPO_CACHE[url, None, None] = clean_clone_path
# Adjust new clone/copy to fit rev and cache_dir
# Checkout needs to be done first because current branch might not be
# DVC repository
if rev is not None:
_git_checkout(new_path, rev)
repo = Repo(new_path)
try:
if rev is not None:
repo.scm.checkout(rev)
# check if the URL is local and no default remote is present
# add default remote pointing to the original repo's cache location
if os.path.isdir(url):
rconfig = RemoteConfig(repo.config)
if not _default_remote_set(rconfig):
original_repo = Repo(url)
try:
rconfig.add(
"auto-generated-upstream",
original_repo.cache.local.cache_dir,
default=True,
level=Config.LEVEL_LOCAL,
)
finally:
original_repo.close()
if cache_dir is not None:
cache_config = CacheConfig(repo.config)
@@ -66,6 +97,16 @@ def _external_repo(url=None, rev=None, cache_dir=None):
return new_path
def _git_checkout(repo_path, revision):
from dvc.scm import Git
git = Git(repo_path)
try:
git.checkout(revision)
finally:
git.close()
def clean_repos():
# Outside code should not see cache while we are removing
repo_paths = list(REPO_CACHE.values())
@@ -89,3 +130,19 @@ def _clone_repo(url, path):
git = Git.clone(url, path)
git.close()
def _default_remote_set(rconfig):
"""
Checks if default remote config is present.
Args:
rconfig: a remote config
Returns:
True if the default remote config is set, else False
"""
try:
rconfig.get_default()
return True
except ConfigError:
return False
+20 -8
View File
@@ -2,13 +2,12 @@ from __future__ import unicode_literals
import logging
import os
from pathspec import PathSpec
from pathspec.patterns import GitWildMatchPattern
from dvc.utils import relpath
from dvc.utils.compat import open
logger = logging.getLogger(__name__)
@@ -20,13 +19,13 @@ class DvcIgnore(object):
class DvcIgnorePatterns(DvcIgnore):
def __init__(self, ignore_file_path):
def __init__(self, ignore_file_path, tree):
assert os.path.isabs(ignore_file_path)
self.ignore_file_path = ignore_file_path
self.dirname = os.path.normpath(os.path.dirname(ignore_file_path))
with open(ignore_file_path, encoding="utf-8") as fobj:
with tree.open(ignore_file_path, encoding="utf-8") as fobj:
self.ignore_spec = PathSpec.from_lines(GitWildMatchPattern, fobj)
def __call__(self, root, dirs, files):
@@ -47,6 +46,9 @@ class DvcIgnorePatterns(DvcIgnore):
return hash(self.ignore_file_path)
def __eq__(self, other):
if not isinstance(other, DvcIgnorePatterns):
return NotImplemented
return self.ignore_file_path == other.ignore_file_path
@@ -59,19 +61,29 @@ class DvcIgnoreDirs(DvcIgnore):
return dirs, files
def __hash__(self):
return hash(tuple(self.basenames))
def __eq__(self, other):
if not isinstance(other, DvcIgnoreDirs):
return NotImplemented
return self.basenames == other.basenames
class DvcIgnoreFilter(object):
def __init__(self, root_dir):
def __init__(self, root_dir, tree):
self.tree = tree
self.ignores = {DvcIgnoreDirs([".git", ".hg", ".dvc"])}
self._update(root_dir)
for root, dirs, _ in os.walk(root_dir):
for root, dirs, _ in self.tree.walk(root_dir, dvcignore=self):
for d in dirs:
self._update(os.path.join(root, d))
def _update(self, dirname):
ignore_file_path = os.path.join(dirname, DvcIgnore.DVCIGNORE_FILE)
if os.path.exists(ignore_file_path):
self.ignores.add(DvcIgnorePatterns(ignore_file_path))
if self.tree.exists(ignore_file_path):
self.ignores.add(DvcIgnorePatterns(ignore_file_path, self.tree))
def __call__(self, root, dirs, files):
for ignore in self.ignores:
+2 -2
View File
@@ -1,8 +1,8 @@
"""Use heuristics to guess if it is a text file or a binary file."""
from __future__ import unicode_literals
from dvc.utils.compat import is_py3, open
from dvc.utils.compat import is_py3
from dvc.utils.compat import open
# Based on https://eli.thegreenplace.net/2011/10/19/
# perls-guess-if-file-is-text-or-binary-implemented-in-python
+74 -54
View File
@@ -1,30 +1,86 @@
"""Manages dvc lock file."""
from __future__ import unicode_literals
import os
import hashlib
import os
import time
from datetime import timedelta
import zc.lockfile
from funcy.py3 import lkeep
from dvc.exceptions import DvcException
from dvc.utils import makedirs
from dvc.utils.compat import is_py3
from dvc.utils import makedirs, format_link
from dvc.utils.compat import is_py3, is_py2
from dvc.progress import Tqdm
DEFAULT_TIMEOUT = 5
FAILED_TO_LOCK_MESSAGE = (
"cannot perform the command because another DVC process seems to be "
"running on this project. If that is not the case, manually remove "
"`.dvc/lock` and try again."
)
class LockError(DvcException):
"""Thrown when unable to acquire the lock for dvc repo."""
class Lock(object):
"""Class for dvc repo lock.
Uses zc.lockfile as backend.
"""
def __init__(self, lockfile, friendly=False, **kwargs):
self._friendly = friendly
self.lockfile = lockfile
self._lock = None
@property
def files(self):
return [self.lockfile]
def _do_lock(self):
try:
with Tqdm(
bar_format="{desc}",
disable=not self._friendly,
desc=(
"If DVC froze, see `hardlink_lock` in {}".format(
format_link("man.dvc.org/config#core")
)
),
):
self._lock = zc.lockfile.LockFile(self.lockfile)
except zc.lockfile.LockError:
raise LockError(FAILED_TO_LOCK_MESSAGE)
def lock(self):
try:
self._do_lock()
return
except LockError:
time.sleep(DEFAULT_TIMEOUT)
self._do_lock()
def unlock(self):
self._lock.close()
self._lock = None
def __enter__(self):
self.lock()
def __exit__(self, typ, value, tbck):
self.unlock()
if is_py3:
import flufl.lock
class Lock(flufl.lock.Lock):
class HardlinkLock(flufl.lock.Lock):
"""Class for dvc repo lock.
Args:
@@ -33,7 +89,7 @@ if is_py3:
tmp_dir (str): a directory to store claim files.
"""
def __init__(self, lockfile, tmp_dir=None):
def __init__(self, lockfile, tmp_dir=None, **kwargs):
import socket
self._tmp_dir = tmp_dir
@@ -72,10 +128,7 @@ if is_py3:
try:
super(Lock, self).lock(timedelta(seconds=DEFAULT_TIMEOUT))
except flufl.lock.TimeOutError:
raise LockError(
"cannot perform the cmd since DVC is busy and "
"locked. Please retry the cmd later."
)
raise LockError(FAILED_TO_LOCK_MESSAGE)
def _set_claimfile(self, pid=None):
super(Lock, self)._set_claimfile(pid)
@@ -99,47 +152,14 @@ if is_py3:
pass
else:
import zc.lockfile
def make_lock(lockfile, tmp_dir=None, friendly=False, hardlink_lock=False):
if hardlink_lock and is_py2:
raise DvcException(
"Hardlink locks are not supported on Python <3.5. "
"See `hardlink_lock` in {}".format(
format_link("man.dvc.org/config#core")
)
)
class Lock(object):
"""Class for dvc repo lock.
Uses zc.lockfile as backend.
"""
def __init__(self, lockfile, tmp_dir=None):
self.lockfile = lockfile
self._lock = None
@property
def files(self):
return [self.lockfile]
def _do_lock(self):
try:
self._lock = zc.lockfile.LockFile(self.lockfile)
except zc.lockfile.LockError:
raise LockError(
"cannot perform the cmd since DVC is busy and "
"locked. Please retry the cmd later."
)
def lock(self):
try:
self._do_lock()
return
except LockError:
time.sleep(DEFAULT_TIMEOUT)
self._do_lock()
def unlock(self):
self._lock.close()
self._lock = None
def __enter__(self):
self.lock()
def __exit__(self, typ, value, tbck):
self.unlock()
cls = HardlinkLock if hardlink_lock else Lock
return cls(lockfile, tmp_dir=tmp_dir, friendly=friendly)
+21 -23
View File
@@ -1,15 +1,27 @@
"""Manages logging configuration for dvc repo."""
from __future__ import unicode_literals
from dvc.utils.compat import str, StringIO, RecursionError
from dvc.progress import Tqdm
import logging
import logging.handlers
import logging.config
import logging.handlers
import colorama
from dvc.progress import Tqdm
from dvc.utils.compat import RecursionError
from dvc.utils.compat import str
from dvc.utils.compat import StringIO
FOOTER = (
"\n{yellow}Having any troubles?{nc}"
" Hit us up at {blue}https://dvc.org/support{nc},"
" we are always happy to help!"
).format(
blue=colorama.Fore.BLUE,
nc=colorama.Fore.RESET,
yellow=colorama.Fore.YELLOW,
)
class LoggingException(Exception):
def __init__(self, record):
@@ -37,7 +49,7 @@ class ColorFormatter(logging.Formatter):
__ https://pypi.python.org/pypi/colorama
For records containing `exc_info`, it will use a custom `_walk_exc` to
retrieve the whole tracebak.
retrieve the whole traceback.
"""
color_code = {
@@ -48,16 +60,6 @@ class ColorFormatter(logging.Formatter):
"CRITICAL": colorama.Fore.RED,
}
footer = (
"{yellow}Having any troubles?{nc}"
" Hit us up at {blue}https://dvc.org/support{nc},"
" we are always happy to help!"
).format(
blue=colorama.Fore.BLUE,
nc=colorama.Fore.RESET,
yellow=colorama.Fore.YELLOW,
)
def format(self, record):
if record.levelname == "INFO":
return record.msg
@@ -66,10 +68,7 @@ class ColorFormatter(logging.Formatter):
exception, stack_trace = self._parse_exc(record.exc_info)
return (
"{color}{levelname}{nc}: {description}"
"{stack_trace}\n"
"\n"
"{footer}"
"{color}{levelname}{nc}: {description}" "{stack_trace}\n"
).format(
color=self.color_code.get(record.levelname, ""),
nc=colorama.Fore.RESET,
@@ -77,7 +76,6 @@ class ColorFormatter(logging.Formatter):
description=self._description(record.msg, exception),
msg=record.msg,
stack_trace=stack_trace,
footer=self.footer,
)
return "{color}{levelname}{nc}: {msg}".format(
@@ -156,7 +154,7 @@ class LoggerHandler(logging.StreamHandler):
raise LoggingException(record)
def emit(self, record):
"""Write to Tqdm's stream so as to not break progressbars"""
"""Write to Tqdm's stream so as to not break progress-bars"""
try:
msg = self.format(record)
Tqdm.write(
+16 -9
View File
@@ -1,19 +1,25 @@
"""Main entry point for dvc CLI."""
from __future__ import unicode_literals
import logging
from dvc import analytics
from dvc.cli import parse_args
from dvc.lock import LockError
from dvc.config import ConfigError
from dvc.analytics import Analytics
from dvc.exceptions import NotDvcRepoError, DvcParserError
from dvc.remote.pool import close_pools
from dvc.exceptions import DvcParserError
from dvc.exceptions import NotDvcRepoError
from dvc.external_repo import clean_repos
from dvc.logger import FOOTER
from dvc.remote.pool import close_pools
from dvc.utils.compat import is_py2
# Workaround for CPython bug. See [1] and [2] for more info.
# [1] https://github.com/aws/aws-cli/blob/1.16.277/awscli/clidriver.py#L55
# [2] https://bugs.python.org/issue29288
"".encode("idna")
logger = logging.getLogger("dvc")
@@ -41,9 +47,6 @@ def main(argv=None):
cmd = args.func(args)
ret = cmd.run()
except LockError:
logger.exception("failed to lock before running a command")
ret = 250
except ConfigError:
logger.exception("configuration error")
ret = 251
@@ -75,6 +78,10 @@ def main(argv=None):
# so won't be reused by any other subsequent run anyway.
clean_repos()
Analytics().send_cmd(cmd, args, ret)
if ret != 0:
logger.info(FOOTER)
if analytics.is_enabled():
analytics.collect_and_send_report(args, ret)
return ret
+16 -17
View File
@@ -1,21 +1,20 @@
from __future__ import unicode_literals
import schema
from dvc.scheme import Schemes
from dvc.utils.compat import urlparse, str
from voluptuous import Any, Required
from dvc.output.base import OutputBase
from dvc.output.s3 import OutputS3
from dvc.output.gs import OutputGS
from dvc.output.local import OutputLOCAL
from dvc.output.hdfs import OutputHDFS
from dvc.output.local import OutputLOCAL
from dvc.output.s3 import OutputS3
from dvc.output.ssh import OutputSSH
from dvc.remote import Remote
from dvc.remote.s3 import RemoteS3
from dvc.remote.hdfs import RemoteHDFS
from dvc.remote.local import RemoteLOCAL
from dvc.remote.s3 import RemoteS3
from dvc.scheme import Schemes
from dvc.utils.compat import str
from dvc.utils.compat import urlparse
OUTS = [
OutputHDFS,
@@ -42,19 +41,19 @@ OUTS_MAP = {
# so when a few types of outputs share the same name, we only need
# specify it once.
CHECKSUM_SCHEMA = {
schema.Optional(RemoteLOCAL.PARAM_CHECKSUM): schema.Or(str, None),
schema.Optional(RemoteS3.PARAM_CHECKSUM): schema.Or(str, None),
schema.Optional(RemoteHDFS.PARAM_CHECKSUM): schema.Or(str, None),
RemoteLOCAL.PARAM_CHECKSUM: Any(str, None),
RemoteS3.PARAM_CHECKSUM: Any(str, None),
RemoteHDFS.PARAM_CHECKSUM: Any(str, None),
}
TAGS_SCHEMA = {schema.Optional(str): CHECKSUM_SCHEMA}
TAGS_SCHEMA = {str: CHECKSUM_SCHEMA}
SCHEMA = CHECKSUM_SCHEMA.copy()
SCHEMA[OutputBase.PARAM_PATH] = str
SCHEMA[schema.Optional(OutputBase.PARAM_CACHE)] = bool
SCHEMA[schema.Optional(OutputBase.PARAM_METRIC)] = OutputBase.METRIC_SCHEMA
SCHEMA[schema.Optional(OutputBase.PARAM_TAGS)] = TAGS_SCHEMA
SCHEMA[schema.Optional(OutputBase.PARAM_PERSIST)] = bool
SCHEMA[Required(OutputBase.PARAM_PATH)] = str
SCHEMA[OutputBase.PARAM_CACHE] = bool
SCHEMA[OutputBase.PARAM_METRIC] = OutputBase.METRIC_SCHEMA
SCHEMA[OutputBase.PARAM_TAGS] = TAGS_SCHEMA
SCHEMA[OutputBase.PARAM_PERSIST] = bool
def _get(stage, p, info, cache, metric, persist=False, tags=None):
+12 -14
View File
@@ -3,13 +3,15 @@ from __future__ import unicode_literals
import logging
from copy import copy
from schema import Or, Optional
from voluptuous import Any
from dvc.cache import NamedCache
import dvc.prompt as prompt
from dvc.exceptions import DvcException, CollectCacheError
from dvc.utils.compat import str, urlparse
from dvc.cache import NamedCache
from dvc.exceptions import CollectCacheError
from dvc.exceptions import DvcException
from dvc.remote.base import RemoteBASE
from dvc.utils.compat import str
from dvc.utils.compat import urlparse
logger = logging.getLogger(__name__)
@@ -52,12 +54,12 @@ class OutputBase(object):
PARAM_METRIC_XPATH = "xpath"
PARAM_PERSIST = "persist"
METRIC_SCHEMA = Or(
METRIC_SCHEMA = Any(
None,
bool,
{
Optional(PARAM_METRIC_TYPE): Or(str, None),
Optional(PARAM_METRIC_XPATH): Or(str, None),
PARAM_METRIC_TYPE: Any(str, None),
PARAM_METRIC_XPATH: Any(str, None),
},
)
@@ -168,12 +170,7 @@ class OutputBase(object):
return self.remote.exists(self.path_info)
def changed_checksum(self):
return (
self.checksum
!= self.remote.save_info(self.path_info)[
self.remote.PARAM_CHECKSUM
]
)
return self.checksum != self.remote.get_checksum(self.path_info)
def changed_cache(self):
if not self.use_cache or not self.checksum:
@@ -287,7 +284,8 @@ class OutputBase(object):
def checkout(self, force=False, progress_callback=None, tag=None):
if not self.use_cache:
progress_callback(str(self.path_info), self.get_files_number())
if progress_callback:
progress_callback(str(self.path_info), self.get_files_number())
return None
if tag:
+1 -1
View File
@@ -1,7 +1,7 @@
from __future__ import unicode_literals
from dvc.remote.gs import RemoteGS
from dvc.output.s3 import OutputS3
from dvc.remote.gs import RemoteGS
class OutputGS(OutputS3):
+17 -6
View File
@@ -1,13 +1,17 @@
from __future__ import unicode_literals
import os
import logging
import os
from dvc.utils.compat import urlparse, fspath_py35, str
from dvc.istextfile import istextfile
from dvc.exceptions import DvcException
from dvc.remote.local import RemoteLOCAL
from dvc.istextfile import istextfile
from dvc.output.base import OutputBase
from dvc.remote.local import RemoteLOCAL
from dvc.utils import relpath
from dvc.utils.compat import fspath_py35
from dvc.utils.compat import str
from dvc.utils.compat import urlparse
from dvc.utils.fs import path_isin
logger = logging.getLogger(__name__)
@@ -26,7 +30,7 @@ class OutputLOCAL(OutputBase):
# so we should expect both posix and windows style paths.
# PathInfo accepts both, i.e. / works everywhere, \ only on win.
#
# FIXME: if we have Windows path containig / or posix one with \
# FIXME: if we have Windows path containing / or posix one with \
# then we have #2059 bug and can't really handle that.
p = self.REMOTE.path_cls(path)
if not p.is_absolute():
@@ -36,7 +40,14 @@ class OutputLOCAL(OutputBase):
return self.REMOTE.path_cls(abs_p)
def __str__(self):
return str(self.path_info)
if not self.is_in_repo:
return str(self.def_path)
cur_dir = os.getcwd()
if path_isin(cur_dir, self.repo.root_dir):
return relpath(self.path_info, cur_dir)
return relpath(self.path_info, self.repo.root_dir)
@property
def fspath(self):
+1 -1
View File
@@ -1,7 +1,7 @@
from __future__ import unicode_literals
from dvc.remote.s3 import RemoteS3
from dvc.output.base import OutputBase
from dvc.remote.s3 import RemoteS3
class OutputS3(OutputBase):
+91 -93
View File
@@ -1,20 +1,24 @@
from __future__ import unicode_literals, absolute_import
from __future__ import absolute_import
from __future__ import unicode_literals
import sys
import os
import posixpath
import sys
from funcy import cached_property
from dvc.utils.compat import str, builtin_str, basestring, is_py2
from dvc.utils.compat import pathlib, urlparse
from dvc.utils import relpath
from dvc.utils.compat import basestring
from dvc.utils.compat import builtin_str
from dvc.utils.compat import is_py2
from dvc.utils.compat import pathlib
from dvc.utils.compat import str
from dvc.utils.compat import urlparse
# On Python 2.7/Windows sys.getfilesystemencoding() is set to mbcs,
# which is lossy, thus we can't use that,
# see https://github.com/mcmtroffaes/pathlib2/issues/56.
from dvc.utils import relpath
if is_py2:
fs_encoding = "utf-8"
@@ -39,7 +43,7 @@ class PathInfo(pathlib.PurePath):
def as_posix(self):
f = self._flavour
# Unlike original implementation [1] that uses `str()` we actually need
# to use `fspath`, because we've overriden `__str__` method to return
# to use `fspath`, because we've overridden `__str__` method to return
# relative paths, which will break original `as_posix`.
#
# [1] https://github.com/python/cpython/blob/v3.7.0/Lib/pathlib.py#L692
@@ -112,52 +116,80 @@ class PosixPathInfo(PathInfo, pathlib.PurePosixPath):
pass
class _URLPathInfo(PosixPathInfo):
def __str__(self):
return self.__fspath__()
__unicode__ = __str__
class _URLPathParents(object):
def __init__(self, pathcls, scheme, netloc, path):
self._scheme = scheme
self._netloc = netloc
self._parents = path.parents
self._pathcls = pathcls
def __init__(self, src):
self.src = src
self._parents = self.src._path.parents
def __len__(self):
return len(self._parents)
def __getitem__(self, idx):
return self._pathcls.from_parts(
scheme=self._scheme,
netloc=self._netloc,
path=self._parents[idx].fspath,
)
return self.src.replace(path=self._parents[idx])
def __repr__(self):
return "<{}.parents>".format(self._pathcls.__name__)
return "<{}.parents>".format(self.src)
class URLInfo(object):
DEFAULT_PORTS = {"http": 80, "https": 443, "ssh": 22, "hdfs": 0}
def __init__(self, url):
self.parsed = urlparse(url)
assert self.parsed.scheme != "remote"
p = urlparse(url)
assert not p.query and not p.params and not p.fragment
assert p.password is None
self.fill_parts(p.scheme, p.hostname, p.username, p.port, p.path)
@classmethod
def from_parts(
cls, scheme=None, netloc=None, host=None, user=None, port=None, path=""
cls, scheme=None, host=None, user=None, port=None, path="", netloc=None
):
assert scheme and (bool(host) ^ bool(netloc))
assert bool(host) ^ bool(netloc)
if netloc is None:
netloc = host
if user:
netloc = user + "@" + host
if port:
netloc += ":" + str(port)
return cls("{}://{}{}".format(scheme, netloc, path))
if netloc is not None:
return cls("{}://{}{}".format(scheme, netloc, path))
obj = cls.__new__(cls)
obj.fill_parts(scheme, host, user, port, path)
return obj
def fill_parts(self, scheme, host, user, port, path):
assert scheme != "remote"
assert isinstance(path, (basestring, _URLPathInfo))
self.scheme, self.host, self.user = scheme, host, user
self.port = int(port) if port else self.DEFAULT_PORTS.get(self.scheme)
if isinstance(path, _URLPathInfo):
self._spath = builtin_str(path)
self._path = path
else:
if path and path[0] != "/":
path = "/" + path
self._spath = path
@property
def _base_parts(self):
return (self.scheme, self.host, self.user, self.port)
@property
def parts(self):
return self._base_parts + self._path.parts
def replace(self, path=None):
return self.from_parts(*self._base_parts, path=path)
@cached_property
def url(self):
p = self.parsed
return "{}://{}{}".format(p.scheme, self.netloc, p.path)
return "{}://{}{}".format(self.scheme, self.netloc, self._spath)
def __str__(self):
return self.url
@@ -170,92 +202,60 @@ class URLInfo(object):
other = self.__class__(other)
return (
self.__class__ == other.__class__
and self.scheme == other.scheme
and self.netloc == other.netloc
and self._base_parts == other._base_parts
and self._path == other._path
)
def __hash__(self):
return hash(self.url)
return hash(self.parts)
def __div__(self, other):
p = self.parsed
new_path = posixpath.join(p.path, str(other))
if not new_path.startswith("/"):
new_path = "/" + new_path
new_url = "{}://{}{}".format(p.scheme, p.netloc, new_path)
return self.__class__(new_url)
return self.replace(path=posixpath.join(self._spath, other))
__truediv__ = __div__
def __getattr__(self, name):
# When deepcopy is called, it creates and object without __init__,
# self.parsed is not initialized and it causes infinite recursion.
# More on this special casing here:
# https://stackoverflow.com/a/47300262/298182
if name.startswith("__"):
raise AttributeError(name)
return getattr(self.parsed, name)
@cached_property
def netloc(self):
p = self.parsed
netloc = p.hostname
if p.username:
netloc = p.username + "@" + netloc
if p.port and int(p.port) != self.DEFAULT_PORTS.get(p.scheme):
netloc += ":" + str(p.port)
return netloc
@property
def port(self):
return self.parsed.port or self.DEFAULT_PORTS.get(self.parsed.scheme)
@property
def host(self):
return self.parsed.hostname
@property
def user(self):
return self.parsed.username
def path(self):
return self._spath
@cached_property
def _path(self):
return PosixPathInfo(self.parsed.path)
return _URLPathInfo(self._spath)
@property
def name(self):
return self._path.name
@property
def parts(self):
return (self.scheme, self.netloc) + self._path.parts
@cached_property
def netloc(self):
netloc = self.host
if self.user:
netloc = self.user + "@" + netloc
if self.port and int(self.port) != self.DEFAULT_PORTS.get(self.scheme):
netloc += ":" + str(self.port)
return netloc
@property
def bucket(self):
return self.parsed.netloc
return self.netloc
@property
def parent(self):
return self.from_parts(
scheme=self.scheme,
netloc=self.parsed.netloc,
path=self._path.parent.fspath,
)
return self.replace(path=self._path.parent)
@property
def parents(self):
return _URLPathParents(
type(self), self.scheme, self.parsed.netloc, self._path
)
return _URLPathParents(self)
def relative_to(self, other):
if isinstance(other, str):
other = URLInfo(other)
if self.scheme != other.scheme or self.netloc != other.netloc:
raise ValueError(
"'{}' does not start with '{}'".format(self, other)
)
if isinstance(other, basestring):
other = self.__class__(other)
if self.__class__ != other.__class__:
msg = "'{}' has incompatible class with '{}'".format(self, other)
raise ValueError(msg)
if self._base_parts != other._base_parts:
msg = "'{}' does not start with '{}'".format(self, other)
raise ValueError(msg)
return self._path.relative_to(other._path)
def isin(self, other):
@@ -263,14 +263,12 @@ class URLInfo(object):
other = self.__class__(other)
elif self.__class__ != other.__class__:
return False
return (
self.scheme == other.scheme
and self.netloc == other.netloc
and self._path.isin(other._path)
return self._base_parts == other._base_parts and self._path.isin(
other._path
)
class CloudURLInfo(URLInfo):
@property
def path(self):
return self.parsed.path.lstrip("/")
return self._spath.lstrip("/")
+21 -28
View File
@@ -1,35 +1,17 @@
"""Manages progress bars for dvc repo."""
from __future__ import print_function
import logging
import sys
from tqdm import tqdm
from concurrent.futures import ThreadPoolExecutor
from threading import RLock
from funcy import merge
from tqdm import tqdm
from dvc.utils import env2bool
logger = logging.getLogger(__name__)
class TqdmThreadPoolExecutor(ThreadPoolExecutor):
"""
Ensure worker progressbars are cleared away properly.
"""
def __enter__(self):
"""
Creates a blank initial dummy progress bar if needed so that workers
are forced to create "nested" bars.
"""
blank_bar = Tqdm(bar_format="Multi-Threaded:", leave=False)
if blank_bar.pos > 0:
# already nested - don't need a placeholder bar
blank_bar.close()
self.bar = blank_bar
return super(TqdmThreadPoolExecutor, self).__enter__()
def __exit__(self, *a, **k):
super(TqdmThreadPoolExecutor, self).__exit__(*a, **k)
self.bar.close()
tqdm.set_lock(RLock())
class Tqdm(tqdm):
@@ -38,12 +20,18 @@ class Tqdm(tqdm):
"""
BAR_FMT_DEFAULT = (
"{percentage:3.0f}%|{bar:10}|"
"{desc:{ncols_desc}.{ncols_desc}}{n}/{total}"
"{percentage:3.0f}% {desc}|{bar}|"
"{n_fmt}/{total_fmt}"
" [{elapsed}<{remaining}, {rate_fmt:>11}{postfix}]"
)
# nested bars should have fixed bar widths to align nicely
BAR_FMT_DEFAULT_NESTED = (
"{percentage:3.0f}%|{bar:10}|{desc:{ncols_desc}.{ncols_desc}}"
"{n_fmt}/{total_fmt}"
" [{elapsed}<{remaining}, {rate_fmt:>11}{postfix}]"
)
BAR_FMT_NOTOTAL = (
"{desc:{ncols_desc}.{ncols_desc}}{n}"
"{desc:{ncols_desc}.{ncols_desc}}{n_fmt}"
" [{elapsed}<??:??, {rate_fmt:>11}{postfix}]"
)
@@ -96,11 +84,16 @@ class Tqdm(tqdm):
leave=leave,
desc=desc,
bar_format="!",
lock_args=(False,),
**kwargs
)
if bar_format is None:
if self.__len__():
self.bar_format = self.BAR_FMT_DEFAULT
self.bar_format = (
self.BAR_FMT_DEFAULT_NESTED
if self.pos
else self.BAR_FMT_DEFAULT
)
else:
self.bar_format = self.BAR_FMT_NOTOTAL
else:
+2 -3
View File
@@ -1,10 +1,9 @@
"""Manages user prompts."""
from __future__ import unicode_literals
from __future__ import print_function
from __future__ import unicode_literals
import sys
import logging
import sys
from getpass import getpass
from dvc.utils.compat import input
+6 -5
View File
@@ -1,20 +1,21 @@
from __future__ import unicode_literals
from .config import RemoteConfig
from dvc.remote.azure import RemoteAZURE
from dvc.remote.gdrive import RemoteGDrive
from dvc.remote.gs import RemoteGS
from dvc.remote.hdfs import RemoteHDFS
from dvc.remote.local import RemoteLOCAL
from dvc.remote.s3 import RemoteS3
from dvc.remote.ssh import RemoteSSH
from dvc.remote.http import RemoteHTTP
from dvc.remote.https import RemoteHTTPS
from dvc.remote.local import RemoteLOCAL
from dvc.remote.oss import RemoteOSS
from .config import RemoteConfig
from dvc.remote.s3 import RemoteS3
from dvc.remote.ssh import RemoteSSH
REMOTES = [
RemoteAZURE,
RemoteGDrive,
RemoteGS,
RemoteHDFS,
RemoteHTTP,
+8 -7
View File
@@ -1,20 +1,20 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import logging
import os
import re
import logging
from datetime import datetime, timedelta
import threading
from funcy import cached_property
from funcy import cached_property, wrap_prop
from dvc.scheme import Schemes
from dvc.utils.compat import urlparse
from dvc.progress import Tqdm
from dvc.config import Config
from dvc.remote.base import RemoteBASE
from dvc.path_info import CloudURLInfo
from dvc.progress import Tqdm
from dvc.remote.base import RemoteBASE
from dvc.scheme import Schemes
from dvc.utils.compat import urlparse
logger = logging.getLogger(__name__)
@@ -64,6 +64,7 @@ class RemoteAZURE(RemoteBASE):
else self.path_cls.from_parts(scheme=self.scheme, netloc=bucket)
)
@wrap_prop(threading.Lock())
@cached_property
def blob_service(self):
from azure.storage.blob import BlockBlobService
+123 -67
View File
@@ -1,31 +1,36 @@
from __future__ import unicode_literals
from operator import itemgetter
from multiprocessing import cpu_count
import itertools
import json
import logging
import tempfile
import itertools
from functools import partial
from concurrent.futures import ThreadPoolExecutor
from copy import copy
from functools import partial
from multiprocessing import cpu_count
from operator import itemgetter
from dvc.ignore import DvcIgnore
from dvc.utils.compat import str, basestring, urlparse, FileNotFoundError
from dvc.remote.slow_link_detection import slow_link_guard
from shortuuid import uuid
import dvc.prompt as prompt
from dvc.config import Config
from dvc.exceptions import (
DvcException,
ConfirmRemoveError,
DvcIgnoreInCollectedDirError,
)
from dvc.exceptions import ConfirmRemoveError
from dvc.exceptions import DvcException
from dvc.exceptions import DvcIgnoreInCollectedDirError
from dvc.ignore import DvcIgnore
from dvc.path_info import PathInfo
from dvc.path_info import URLInfo
from dvc.progress import Tqdm
from dvc.utils import tmp_fname, move, relpath, makedirs
from dvc.remote.slow_link_detection import slow_link_guard
from dvc.state import StateNoop
from dvc.path_info import PathInfo, URLInfo
from dvc.utils import makedirs
from dvc.utils import relpath
from dvc.utils import tmp_fname
from dvc.utils.compat import basestring
from dvc.utils.compat import FileNotFoundError
from dvc.utils.compat import str
from dvc.utils.compat import urlparse
from dvc.utils.fs import move
from dvc.utils.http import open_url
logger = logging.getLogger(__name__)
@@ -290,6 +295,8 @@ class RemoteBASE(object):
return checksum.endswith(cls.CHECKSUM_DIR_SUFFIX)
def get_checksum(self, path_info):
assert path_info.scheme == self.scheme
if not self.exists(path_info):
return None
@@ -319,7 +326,6 @@ class RemoteBASE(object):
return checksum
def save_info(self, path_info):
assert path_info.scheme == self.scheme
return {self.PARAM_CHECKSUM: self.get_checksum(path_info)}
def changed(self, path_info, checksum_info):
@@ -360,7 +366,7 @@ class RemoteBASE(object):
)
return True
actual = self.save_info(path_info)[self.PARAM_CHECKSUM]
actual = self.get_checksum(path_info)
if checksum != actual:
logger.debug(
"checksum '{}'(actual '{}') for '{}' has changed.".format(
@@ -421,10 +427,16 @@ class RemoteBASE(object):
cache_info = self.checksum_to_path_info(checksum)
if self.changed_cache(checksum):
self.move(path_info, cache_info)
self.link(cache_info, path_info)
elif self.iscopy(path_info) and self._cache_is_copy(path_info):
# Default relink procedure involves unneeded copy
if self.protected:
self.protect(path_info)
else:
self.unprotect(path_info)
else:
self.remove(path_info)
self.link(cache_info, path_info)
self.link(cache_info, path_info)
if save_link:
self.state.save_link(path_info)
@@ -435,6 +447,28 @@ class RemoteBASE(object):
self.state.save(path_info, checksum)
self.state.save(cache_info, checksum)
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.path_info / ".cache_type_test_file"
if not self.exists(test_cache_file):
with self.open(test_cache_file, "wb") as fobj:
fobj.write(bytes(1))
try:
self.link(test_cache_file, workspace_file)
finally:
self.remove(workspace_file)
self.remove(test_cache_file)
self.cache_type_confirmed = True
return self.cache_types[0] == "copy"
def _save_dir(self, path_info, checksum):
cache_info = self.checksum_to_path_info(checksum)
dir_info = self.get_dir_cache(checksum)
@@ -463,6 +497,10 @@ class RemoteBASE(object):
"""
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_files(self, path_info):
"""Return a generator with `PathInfo`s to all the files"""
raise NotImplementedError
@@ -479,10 +517,6 @@ class RemoteBASE(object):
)
checksum = checksum_info[self.PARAM_CHECKSUM]
if not self.changed_cache(checksum):
self._checkout(path_info, checksum)
return
self._save(path_info, checksum)
def _save(self, path_info, checksum):
@@ -543,11 +577,48 @@ class RemoteBASE(object):
if to_info.scheme != "local":
raise NotImplementedError
logger.debug("Downloading '{}' to '{}'".format(from_info, to_info))
if self.isdir(from_info):
return self._download_dir(
from_info, to_info, name, no_progress_bar, file_mode, dir_mode
)
return self._download_file(
from_info, to_info, name, no_progress_bar, file_mode, dir_mode
)
def _download_dir(
self, from_info, to_info, name, no_progress_bar, file_mode, dir_mode
):
from_infos = list(self.walk_files(from_info))
to_infos = (
to_info / info.relative_to(from_info) for info in from_infos
)
with ThreadPoolExecutor(max_workers=self.JOBS) as executor:
download_files = partial(
self._download_file,
name=name,
no_progress_bar=True,
file_mode=file_mode,
dir_mode=dir_mode,
)
futures = executor.map(download_files, from_infos, to_infos)
with Tqdm(
futures,
total=len(from_infos),
desc="Downloading directory",
unit="Files",
disable=no_progress_bar,
) as futures:
return sum(futures)
def _download_file(
self, from_info, to_info, name, no_progress_bar, file_mode, dir_mode
):
makedirs(to_info.parent, exist_ok=True, mode=dir_mode)
logger.debug("Downloading '{}' to '{}'".format(from_info, to_info))
name = name or to_info.name
makedirs(to_info.parent, exist_ok=True, mode=dir_mode)
tmp_file = tmp_fname(to_info)
try:
@@ -754,45 +825,21 @@ class RemoteBASE(object):
self.remove(path_info)
def _checkout_file(
self,
path_info,
checksum,
force,
progress_callback=None,
save_link=True,
self, path_info, checksum, force, progress_callback=None
):
# NOTE: In case if path_info is already cached and path_info's
# link type matches cache link type, we would like to avoid
# relinking.
if self.changed(
path_info, {self.PARAM_CHECKSUM: checksum}
) or not self._link_matches(path_info):
"""The file is changed we need to checkout a new copy"""
cache_info = self.checksum_to_path_info(checksum)
if self.exists(path_info):
msg = "data '{}' exists. Removing before checkout."
logger.warning(msg.format(str(path_info)))
self.safe_remove(path_info, force=force)
cache_info = self.checksum_to_path_info(checksum)
self.link(cache_info, path_info)
if save_link:
self.state.save_link(path_info)
self.state.save(path_info, checksum)
else:
# NOTE: performing (un)protection costs us +/- the same as checking
# if path_info is protected. Instead of implementing logic,
# just (un)protect according to self.protected.
if self.protected:
self.protect(path_info)
else:
# NOTE dont allow copy, because we checked before that link
# type matches cache, and we don't want data duplication
self.unprotect(path_info, allow_copy=False)
self.link(cache_info, path_info)
self.state.save_link(path_info)
self.state.save(path_info, checksum)
if progress_callback:
progress_callback(str(path_info))
def _link_matches(self, path_info):
return True
def makedirs(self, path_info):
"""Optional: Implement only if the remote needs to create
directories before copying/linking/moving data
@@ -814,14 +861,17 @@ class RemoteBASE(object):
for entry in dir_info:
relative_path = entry[self.PARAM_RELPATH]
entry_checksum = entry[self.PARAM_CHECKSUM]
entry_cache_info = self.checksum_to_path_info(entry_checksum)
entry_info = path_info / relative_path
self._checkout_file(
entry_info,
entry_checksum,
force,
progress_callback,
save_link=False,
)
entry_checksum_info = {self.PARAM_CHECKSUM: entry_checksum}
if self.changed(entry_info, entry_checksum_info):
if self.exists(entry_info):
self.safe_remove(entry_info, force=force)
self.link(entry_cache_info, entry_info)
self.state.save(entry_info, entry_checksum)
if progress_callback:
progress_callback(str(entry_info))
self._remove_redundant_files(path_info, dir_info, force)
@@ -846,6 +896,7 @@ class RemoteBASE(object):
checksum = checksum_info.get(self.PARAM_CHECKSUM)
failed = None
skip = False
if not checksum:
logger.warning(
"No checksum info found for '{}'. "
@@ -854,13 +905,18 @@ class RemoteBASE(object):
self.safe_remove(path_info, force=force)
failed = path_info
elif not self.changed(path_info, checksum_info):
msg = "Data '{}' didn't change."
logger.debug(msg.format(str(path_info)))
skip = True
elif self.changed_cache(checksum):
msg = "Cache '{}' not found. File '{}' won't be created."
logger.warning(msg.format(checksum, str(path_info)))
self.safe_remove(path_info, force=force)
failed = path_info
if failed:
if failed or skip:
if progress_callback:
progress_callback(
str(path_info), self.get_files_number(checksum)
@@ -894,7 +950,7 @@ class RemoteBASE(object):
return 1
@staticmethod
def unprotect(path_info, allow_copy=True):
def unprotect(path_info):
pass
def _get_unpacked_dir_names(self, checksums):
+8 -3
View File
@@ -1,10 +1,11 @@
import os
import logging
import os
import posixpath
from dvc.config import Config
from dvc.config import ConfigError
from dvc.utils import relpath
from dvc.utils.compat import urlparse
from dvc.config import Config, ConfigError
logger = logging.getLogger(__name__)
@@ -104,7 +105,6 @@ class RemoteConfig(object):
level=level,
)
if default:
logger.info("Setting '{}' as a default remote.".format(name))
self.config.set(
Config.SECTION_CORE,
Config.SECTION_CORE_REMOTE,
@@ -150,3 +150,8 @@ class RemoteConfig(object):
self.config.set(
Config.SECTION_CORE, Config.SECTION_CORE_REMOTE, name, level=level
)
def get_default(self, level=None):
return self.config.get(
Config.SECTION_CORE, Config.SECTION_CORE_REMOTE, level=level
)
+306
View File
@@ -0,0 +1,306 @@
from __future__ import unicode_literals
import os
import posixpath
import logging
import threading
from funcy import retry, compose, decorator, wrap_with
from funcy.py3 import cat
from dvc.progress import Tqdm
from dvc.scheme import Schemes
from dvc.path_info import CloudURLInfo
from dvc.remote.base import RemoteBASE
from dvc.config import Config
from dvc.exceptions import DvcException
from dvc.utils import tmp_fname
logger = logging.getLogger(__name__)
FOLDER_MIME_TYPE = "application/vnd.google-apps.folder"
class GDriveRetriableError(DvcException):
def __init__(self, msg):
super(GDriveRetriableError, self).__init__(msg)
@decorator
def _wrap_pydrive_retriable(call):
from apiclient import errors
from pydrive.files import ApiRequestError
try:
result = call()
except (ApiRequestError, errors.HttpError) as exception:
retry_codes = ["403", "500", "502", "503", "504"]
if any(
"HttpError {}".format(code) in str(exception)
for code in retry_codes
):
raise GDriveRetriableError(msg="Google API request failed")
raise
return result
gdrive_retry = compose(
# 8 tries, start at 0.5s, multiply by golden ratio, cap at 10s
retry(
8, GDriveRetriableError, timeout=lambda a: min(0.5 * 1.618 ** a, 10)
),
_wrap_pydrive_retriable,
)
class RemoteGDrive(RemoteBASE):
scheme = Schemes.GDRIVE
path_cls = CloudURLInfo
REQUIRES = {"pydrive": "pydrive"}
GDRIVE_USER_CREDENTIALS_DATA = "GDRIVE_USER_CREDENTIALS_DATA"
DEFAULT_USER_CREDENTIALS_FILE = ".dvc/tmp/gdrive-user-credentials.json"
def __init__(self, repo, config):
super(RemoteGDrive, self).__init__(repo, config)
self.no_traverse = False
self.path_info = self.path_cls(config[Config.SECTION_REMOTE_URL])
self.config = config
self.init_drive()
def init_drive(self):
self.client_id = self.config.get(Config.SECTION_GDRIVE_CLIENT_ID, None)
self.client_secret = self.config.get(
Config.SECTION_GDRIVE_CLIENT_SECRET, None
)
if not self.client_id or not self.client_secret:
raise DvcException(
"Please specify Google Drive's client id and "
"secret in DVC's config. Learn more at "
"https://man.dvc.org/remote/add."
)
self.gdrive_user_credentials_path = (
tmp_fname(".dvc/tmp/")
if os.getenv(RemoteGDrive.GDRIVE_USER_CREDENTIALS_DATA)
else self.config.get(
Config.SECTION_GDRIVE_USER_CREDENTIALS_FILE,
self.DEFAULT_USER_CREDENTIALS_FILE,
)
)
@gdrive_retry
def gdrive_upload_file(
self, args, no_progress_bar=True, from_file="", progress_name=""
):
item = self.drive.CreateFile(
{"title": args["title"], "parents": [{"id": args["parent_id"]}]}
)
with open(from_file, "rb") as fobj:
total = os.path.getsize(from_file)
with Tqdm.wrapattr(
fobj,
"read",
desc=progress_name,
total=total,
disable=no_progress_bar,
) as wrapped:
# PyDrive doesn't like content property setting for empty files
# https://github.com/gsuitedevs/PyDrive/issues/121
if total:
item.content = wrapped
item.Upload()
return item
@gdrive_retry
def gdrive_download_file(
self, file_id, to_file, progress_name, no_progress_bar
):
gdrive_file = self.drive.CreateFile({"id": file_id})
bar_format = (
"Donwloading {desc:{ncols_desc}.{ncols_desc}}... "
+ Tqdm.format_sizeof(int(gdrive_file["fileSize"]), "B", 1024)
)
with Tqdm(
bar_format=bar_format, desc=progress_name, disable=no_progress_bar
):
gdrive_file.GetContentFile(to_file)
def gdrive_list_item(self, query):
file_list = self.drive.ListFile({"q": query, "maxResults": 1000})
# Isolate and decorate fetching of remote drive items in pages
get_list = gdrive_retry(lambda: next(file_list, None))
# Fetch pages until None is received, lazily flatten the thing
return cat(iter(get_list, None))
def cache_root_dirs(self):
cached_dirs = {}
cached_ids = {}
for dir1 in self.gdrive_list_item(
"'{}' in parents and trashed=false".format(self.root_id)
):
remote_path = posixpath.join(self.path_info.path, dir1["title"])
cached_dirs.setdefault(remote_path, []).append(dir1["id"])
cached_ids[dir1["id"]] = dir1["title"]
return cached_dirs, cached_ids
@property
def cached_dirs(self):
if not hasattr(self, "_cached_dirs"):
self.drive
return self._cached_dirs
@property
def cached_ids(self):
if not hasattr(self, "_cached_ids"):
self.drive
return self._cached_ids
@property
@wrap_with(threading.RLock())
def drive(self):
if not hasattr(self, "_gdrive"):
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
if os.getenv(RemoteGDrive.GDRIVE_USER_CREDENTIALS_DATA):
with open(
self.gdrive_user_credentials_path, "w"
) as credentials_file:
credentials_file.write(
os.getenv(RemoteGDrive.GDRIVE_USER_CREDENTIALS_DATA)
)
GoogleAuth.DEFAULT_SETTINGS["client_config_backend"] = "settings"
GoogleAuth.DEFAULT_SETTINGS["client_config"] = {
"client_id": self.client_id,
"client_secret": self.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["save_credentials"] = True
GoogleAuth.DEFAULT_SETTINGS["save_credentials_backend"] = "file"
GoogleAuth.DEFAULT_SETTINGS[
"save_credentials_file"
] = self.gdrive_user_credentials_path
GoogleAuth.DEFAULT_SETTINGS["get_refresh_token"] = True
GoogleAuth.DEFAULT_SETTINGS["oauth_scope"] = [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/drive.appdata",
]
# Pass non existent settings path to force DEFAULT_SETTINGS loading
gauth = GoogleAuth(settings_file="")
gauth.CommandLineAuth()
if os.getenv(RemoteGDrive.GDRIVE_USER_CREDENTIALS_DATA):
os.remove(self.gdrive_user_credentials_path)
self._gdrive = GoogleDrive(gauth)
self.root_id = self.get_remote_id(self.path_info, create=True)
self._cached_dirs, self._cached_ids = self.cache_root_dirs()
return self._gdrive
@gdrive_retry
def create_remote_dir(self, parent_id, title):
item = self.drive.CreateFile(
{
"title": title,
"parents": [{"id": parent_id}],
"mimeType": FOLDER_MIME_TYPE,
}
)
item.Upload()
return item
@gdrive_retry
def get_remote_item(self, name, parents_ids):
if not parents_ids:
return None
query = " or ".join(
"'{}' in parents".format(parent_id) for parent_id in parents_ids
)
query += " and trashed=false and title='{}'".format(name)
# Limit found remote items count to 1 in response
item_list = self.drive.ListFile(
{"q": query, "maxResults": 1}
).GetList()
return next(iter(item_list), None)
def resolve_remote_item_from_path(self, path_parts, create):
parents_ids = ["root"]
current_path = ""
for path_part in path_parts:
current_path = posixpath.join(current_path, path_part)
remote_ids = self.get_remote_id_from_cache(current_path)
if remote_ids:
parents_ids = remote_ids
continue
item = self.get_remote_item(path_part, parents_ids)
if not item and create:
item = self.create_remote_dir(parents_ids[0], path_part)
elif not item:
return None
parents_ids = [item["id"]]
return item
def get_remote_id_from_cache(self, remote_path):
if hasattr(self, "_cached_dirs"):
return self.cached_dirs.get(remote_path, [])
return []
def get_remote_id(self, path_info, create=False):
remote_ids = self.get_remote_id_from_cache(path_info.path)
if remote_ids:
return remote_ids[0]
file1 = self.resolve_remote_item_from_path(
path_info.path.split("/"), create
)
return file1["id"] if file1 else ""
def exists(self, path_info):
return self.get_remote_id(path_info) != ""
def _upload(self, from_file, to_info, name, no_progress_bar):
dirname = to_info.parent
if dirname:
parent_id = self.get_remote_id(dirname, True)
else:
parent_id = to_info.bucket
self.gdrive_upload_file(
{"title": to_info.name, "parent_id": parent_id},
no_progress_bar,
from_file,
name,
)
def _download(self, from_info, to_file, name, no_progress_bar):
file_id = self.get_remote_id(from_info)
self.gdrive_download_file(file_id, to_file, name, no_progress_bar)
def all(self):
if not self.cached_ids:
return
query = " or ".join(
"'{}' in parents".format(dir_id) for dir_id in self.cached_ids
)
query += " and trashed=false"
for file1 in self.gdrive_list_item(query):
parent_id = file1["parents"][0]["id"]
path = posixpath.join(self.cached_ids[parent_id], file1["title"])
try:
yield self.path_to_checksum(path)
except ValueError:
# We ignore all the non-cache looking files
logger.debug('Ignoring path as "non-cache looking"')
+88 -27
View File
@@ -1,17 +1,21 @@
from __future__ import unicode_literals
from __future__ import unicode_literals, division
import logging
from datetime import timedelta
from functools import wraps
import io
import os.path
import threading
from funcy import cached_property
from funcy import cached_property, wrap_prop
from dvc.utils.compat import FileNotFoundError # skipcq: PYL-W0622
from dvc.remote.base import RemoteBASE
from dvc.config import Config
from dvc.exceptions import DvcException
from dvc.path_info import CloudURLInfo
from dvc.progress import Tqdm
from dvc.remote.base import RemoteBASE
from dvc.scheme import Schemes
from dvc.utils.compat import FileNotFoundError # skipcq: PYL-W0622
logger = logging.getLogger(__name__)
@@ -20,25 +24,24 @@ def dynamic_chunk_size(func):
@wraps(func)
def wrapper(*args, **kwargs):
import requests
from google.cloud.storage.blob import Blob, _DEFAULT_CHUNKSIZE
from google.cloud.storage.blob import Blob
# Default chunk size for gs is 100M, which might be too much for
# particular network (see [1]). So if we are getting ConnectionError,
# we should try lowering the chunk size until we reach the minimum
# allowed chunk size of 256K. Also note that `chunk_size` must be a
# multiple of 256K per the API specification.
# `ConnectionError` may be due to too large `chunk_size`
# (see [#2572]) so try halving on error.
# Note: start with 40 * [default: 256K] = 10M.
# Note: must be multiple of 256K.
#
# [1] https://github.com/iterative/dvc/issues/2572
# [#2572]: https://github.com/iterative/dvc/issues/2572
# skipcq: PYL-W0212
multiplier = int(_DEFAULT_CHUNKSIZE / Blob._CHUNK_SIZE_MULTIPLE)
multiplier = 40
while True:
try:
# skipcq: PYL-W0212
chunk_size = Blob._CHUNK_SIZE_MULTIPLE * multiplier
return func(*args, chunk_size=chunk_size, **kwargs)
except requests.exceptions.ConnectionError:
multiplier = int(multiplier / 2)
multiplier //= 2
if not multiplier:
raise
@@ -46,9 +49,24 @@ def dynamic_chunk_size(func):
@dynamic_chunk_size
def _upload_to_bucket(bucket, from_file, to_info, **kwargs):
blob = bucket.blob(to_info.path, **kwargs)
blob.upload_from_filename(from_file)
def _upload_to_bucket(
bucket,
from_file,
to_info,
chunk_size=None,
name=None,
no_progress_bar=True,
):
blob = bucket.blob(to_info.path, chunk_size=chunk_size)
with io.open(from_file, mode="rb") as fobj:
with Tqdm.wrapattr(
fobj,
"read",
desc=name or to_info.path,
total=os.path.getsize(from_file),
disable=no_progress_bar,
) as wrapped:
blob.upload_from_file(wrapped)
class RemoteGS(RemoteBASE):
@@ -60,13 +78,13 @@ class RemoteGS(RemoteBASE):
def __init__(self, repo, config):
super(RemoteGS, self).__init__(repo, config)
storagepath = "gs://" + config.get(Config.SECTION_GCP_STORAGEPATH, "/")
url = config.get(Config.SECTION_REMOTE_URL, storagepath)
url = config.get(Config.SECTION_REMOTE_URL, "gs:///")
self.path_info = self.path_cls(url)
self.projectname = config.get(Config.SECTION_GCP_PROJECTNAME, None)
self.credentialpath = config.get(Config.SECTION_GCP_CREDENTIALPATH)
@wrap_prop(threading.Lock())
@cached_property
def gs(self):
from google.cloud.storage import Client
@@ -112,25 +130,68 @@ class RemoteGS(RemoteBASE):
blob.delete()
def _list_paths(self, bucket, prefix):
for blob in self.gs.bucket(bucket).list_blobs(prefix=prefix):
def _list_paths(self, path_info, max_items=None):
for blob in self.gs.bucket(path_info.bucket).list_blobs(
prefix=path_info.path, max_results=max_items
):
yield blob.name
def list_cache_paths(self):
return self._list_paths(self.path_info.bucket, self.path_info.path)
return self._list_paths(self.path_info)
def walk_files(self, path_info):
for fname in self._list_paths(path_info / ""):
# skip nested empty directories
if fname.endswith("/"):
continue
yield path_info.replace(fname)
def makedirs(self, path_info):
self.gs.bucket(path_info.bucket).blob(
(path_info / "").path
).upload_from_string("")
def isdir(self, path_info):
dir_path = path_info / ""
return bool(list(self._list_paths(dir_path, max_items=1)))
def isfile(self, path_info):
if path_info.path.endswith("/"):
return False
blob = self.gs.bucket(path_info.bucket).blob(path_info.path)
return blob.exists()
def exists(self, path_info):
paths = set(self._list_paths(path_info.bucket, path_info.path))
return any(path_info.path == path for path in paths)
"""Check if the blob exists. If it does not exist,
it could be a part of a directory path.
def _upload(self, from_file, to_info, **_kwargs):
eg: if `data/file.txt` exists, check for `data` should return True
"""
return self.isfile(path_info) or self.isdir(path_info)
def _upload(self, from_file, to_info, name=None, no_progress_bar=True):
bucket = self.gs.bucket(to_info.bucket)
_upload_to_bucket(bucket, from_file, to_info)
_upload_to_bucket(
bucket,
from_file,
to_info,
name=name,
no_progress_bar=no_progress_bar,
)
def _download(self, from_info, to_file, **_kwargs):
def _download(self, from_info, to_file, name=None, no_progress_bar=True):
bucket = self.gs.bucket(from_info.bucket)
blob = bucket.get_blob(from_info.path)
blob.download_to_filename(to_file)
with io.open(to_file, mode="wb") as fobj:
with Tqdm.wrapattr(
fobj,
"write",
desc=name or from_info.path,
total=blob.size,
disable=no_progress_bar,
) as wrapped:
blob.download_to_file(wrapped)
def _generate_download_url(self, path_info, expires=3600):
expiration = timedelta(seconds=int(expires))
+16 -20
View File
@@ -1,22 +1,21 @@
from __future__ import unicode_literals
import os
import re
from dvc.utils.compat import FileNotFoundError, urlparse
import io
import posixpath
import logging
from subprocess import Popen, PIPE
from contextlib import contextmanager, closing
import os
import posixpath
import re
from collections import deque
from contextlib import closing, contextmanager
import subprocess
from .base import RemoteBASE, RemoteCmdError
from .pool import get_connection
from dvc.config import Config
from dvc.scheme import Schemes
from dvc.utils.compat import urlparse, FileNotFoundError
from dvc.utils import fix_env, tmp_fname
from .pool import get_connection
from .base import RemoteBASE, RemoteCmdError
logger = logging.getLogger(__name__)
@@ -65,15 +64,15 @@ class RemoteHDFS(RemoteBASE):
close_fds = os.name != "nt"
executable = os.getenv("SHELL") if os.name != "nt" else None
p = Popen(
p = subprocess.Popen(
cmd,
shell=True,
close_fds=close_fds,
executable=executable,
env=fix_env(os.environ),
stdin=PIPE,
stdout=PIPE,
stderr=PIPE,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
out, err = p.communicate()
if p.returncode != 0:
@@ -153,10 +152,9 @@ class RemoteHDFS(RemoteBASE):
def list_cache_paths(self):
if not self.exists(self.path_info):
return []
return
files = []
dirs = [self.path_info.path]
dirs = deque([self.path_info.path])
with self.hdfs(self.path_info) as hdfs:
while dirs:
@@ -164,6 +162,4 @@ class RemoteHDFS(RemoteBASE):
if entry["kind"] == "directory":
dirs.append(urlparse(entry["name"]).path)
elif entry["kind"] == "file":
files.append(urlparse(entry["name"]).path)
return files
yield urlparse(entry["name"]).path
+15 -16
View File
@@ -1,15 +1,17 @@
from __future__ import unicode_literals
import logging
import threading
from funcy import cached_property
from funcy import cached_property, wrap_prop
from dvc.config import Config
from dvc.config import ConfigError
from dvc.exceptions import DvcException, HTTPError
from dvc.progress import Tqdm
from dvc.remote.base import RemoteBASE
from dvc.scheme import Schemes
from dvc.utils.compat import open
from dvc.progress import Tqdm
from dvc.exceptions import DvcException
from dvc.config import Config, ConfigError
from dvc.remote.base import RemoteBASE
logger = logging.getLogger(__name__)
@@ -35,30 +37,26 @@ class RemoteHTTP(RemoteBASE):
)
def _download(self, from_info, to_file, name=None, no_progress_bar=False):
request = self._request("GET", from_info.url, stream=True)
response = self._request("GET", from_info.url, stream=True)
if response.status_code != 200:
raise HTTPError(response.status_code, response.reason)
with Tqdm(
total=None if no_progress_bar else self._content_length(from_info),
total=None if no_progress_bar else self._content_length(response),
leave=False,
bytes=True,
desc=from_info.url if name is None else name,
disable=no_progress_bar,
) as pbar:
with open(to_file, "wb") as fd:
for chunk in request.iter_content(chunk_size=self.CHUNK_SIZE):
for chunk in response.iter_content(chunk_size=self.CHUNK_SIZE):
fd.write(chunk)
fd.flush()
pbar.update(len(chunk))
def exists(self, path_info):
return bool(self._request("HEAD", path_info.url))
def _content_length(self, url_or_request):
headers = getattr(
url_or_request,
"headers",
self._request("HEAD", url_or_request).headers,
)
res = headers.get("Content-Length")
def _content_length(self, response):
res = response.headers.get("Content-Length")
return int(res) if res else None
def get_file_checksum(self, path_info):
@@ -80,6 +78,7 @@ class RemoteHTTP(RemoteBASE):
return etag
@wrap_prop(threading.Lock())
@cached_property
def _session(self):
import requests
+1 -2
View File
@@ -1,8 +1,7 @@
from __future__ import unicode_literals
from dvc.scheme import Schemes
from .http import RemoteHTTP
from dvc.scheme import Schemes
class RemoteHTTPS(RemoteHTTP):
+55 -100
View File
@@ -1,37 +1,38 @@
from __future__ import unicode_literals
import os
import stat
import errno
import logging
import os
import stat
from concurrent.futures import ThreadPoolExecutor
from functools import partial
from shortuuid import uuid
from dvc.scheme import Schemes
from dvc.utils.compat import str, fspath_py35, open
from dvc.system import System
from dvc.remote.base import (
RemoteBASE,
STATUS_MAP,
STATUS_NEW,
STATUS_DELETED,
STATUS_MISSING,
)
from dvc.utils import (
remove,
move,
copyfile,
tmp_fname,
file_md5,
walk_files,
relpath,
makedirs,
)
from dvc.config import Config
from dvc.exceptions import DvcException, DownloadError, UploadError
from dvc.progress import Tqdm, TqdmThreadPoolExecutor
from dvc.exceptions import DownloadError
from dvc.exceptions import DvcException
from dvc.exceptions import UploadError
from dvc.path_info import PathInfo
from dvc.progress import Tqdm
from dvc.remote.base import RemoteBASE
from dvc.remote.base import STATUS_DELETED
from dvc.remote.base import STATUS_MAP
from dvc.remote.base import STATUS_MISSING
from dvc.remote.base import STATUS_NEW
from dvc.scheme import Schemes
from dvc.system import System
from dvc.utils import copyfile
from dvc.utils import file_md5
from dvc.utils import makedirs
from dvc.utils import relpath
from dvc.utils import tmp_fname
from dvc.utils import walk_files
from dvc.utils.compat import fspath_py35
from dvc.utils.compat import open
from dvc.utils.compat import str
from dvc.utils.fs import move
from dvc.utils.fs import remove
logger = logging.getLogger(__name__)
@@ -59,9 +60,7 @@ class RemoteLOCAL(RemoteBASE):
# cache files are set to be read-only for everyone
self._file_mode = stat.S_IREAD | stat.S_IRGRP | stat.S_IROTH
# A clunky way to detect cache dir
storagepath = config.get(Config.SECTION_LOCAL_STORAGEPATH, None)
cache_dir = config.get(Config.SECTION_REMOTE_URL, storagepath)
cache_dir = config.get(Config.SECTION_REMOTE_URL)
if cache_dir is not None and not os.path.isabs(cache_dir):
cwd = config[Config.PRIVATE_CWD]
@@ -84,17 +83,7 @@ class RemoteLOCAL(RemoteBASE):
def list_cache_paths(self):
assert self.path_info is not None
clist = []
for entry in os.listdir(fspath_py35(self.path_info)):
subdir = self.path_info / entry
if not os.path.isdir(fspath_py35(subdir)):
continue
clist.extend(
subdir / cache for cache in os.listdir(fspath_py35(subdir))
)
return clist
return walk_files(self.path_info, None)
def get(self, md5):
if not md5:
@@ -139,6 +128,11 @@ class RemoteLOCAL(RemoteBASE):
def isdir(path_info):
return os.path.isdir(fspath_py35(path_info))
def iscopy(self, path_info):
return not (
System.is_symlink(path_info) or System.is_hardlink(path_info)
)
@staticmethod
def getsize(path_info):
return os.path.getsize(fspath_py35(path_info))
@@ -148,7 +142,7 @@ class RemoteLOCAL(RemoteBASE):
yield PathInfo(fname)
def get_file_checksum(self, path_info):
return file_md5(fspath_py35(path_info))[0]
return file_md5(path_info)[0]
def remove(self, path_info):
if path_info.scheme != "local":
@@ -184,7 +178,7 @@ class RemoteLOCAL(RemoteBASE):
# will get something like: `too many links error`
#
# This is because all those empty files will have the same checksum
# (i.e. 68b329da9893e34099c7d8ad5cb9c940), therfore, they will be
# (i.e. 68b329da9893e34099c7d8ad5cb9c940), therefore, they will be
# linked to the same file in the cache.
#
# From https://en.wikipedia.org/wiki/Hard_link
@@ -223,7 +217,7 @@ class RemoteLOCAL(RemoteBASE):
def _upload(
self, from_file, to_info, name=None, no_progress_bar=False, **_kwargs
):
makedirs(fspath_py35(to_info.parent), exist_ok=True)
makedirs(to_info.parent, exist_ok=True)
tmp_file = tmp_fname(to_info)
copyfile(
@@ -235,10 +229,7 @@ class RemoteLOCAL(RemoteBASE):
self, from_info, to_file, name=None, no_progress_bar=False, **_kwargs
):
copyfile(
fspath_py35(from_info),
to_file,
no_progress_bar=no_progress_bar,
name=name,
from_info, to_file, no_progress_bar=no_progress_bar, name=name
)
@staticmethod
@@ -263,7 +254,7 @@ class RemoteLOCAL(RemoteBASE):
# 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 existance of
# there's no need to check the remote storage for the existence of
# those files.
if download and sorted(local_exists) == sorted(md5s):
remote_exists = local_exists
@@ -359,7 +350,7 @@ class RemoteLOCAL(RemoteBASE):
return 0
if jobs > 1:
with TqdmThreadPoolExecutor(max_workers=jobs) as executor:
with ThreadPoolExecutor(max_workers=jobs) as executor:
fails = sum(executor.map(func, *plans))
else:
fails = sum(map(func, *plans))
@@ -408,22 +399,19 @@ class RemoteLOCAL(RemoteBASE):
logger.warning(msg)
@staticmethod
def _unprotect_file(path, allow_copy=True):
def _unprotect_file(path):
if System.is_symlink(path) or System.is_hardlink(path):
if allow_copy:
logger.debug("Unprotecting '{}'".format(path))
tmp = os.path.join(os.path.dirname(path), "." + str(uuid()))
logger.debug("Unprotecting '{}'".format(path))
tmp = os.path.join(os.path.dirname(path), "." + str(uuid()))
# The operations order is important here - if some application
# would access the file during the process of copyfile then it
# would get only the part of file. So, at first, the file
# should be copied with the temporary name, and then
# original file should be replaced by new.
copyfile(
path, tmp, name="Unprotecting '{}'".format(relpath(path))
)
remove(path)
os.rename(tmp, path)
# The operations order is important here - if some application
# would access the file during the process of copyfile then it
# would get only the part of file. So, at first, the file should be
# copied with the temporary name, and then original file should be
# replaced by new.
copyfile(path, tmp, name="Unprotecting '{}'".format(relpath(path)))
remove(path)
os.rename(tmp, path)
else:
logger.debug(
@@ -433,11 +421,11 @@ class RemoteLOCAL(RemoteBASE):
os.chmod(path, os.stat(path).st_mode | stat.S_IWRITE)
def _unprotect_dir(self, path, allow_copy=True):
def _unprotect_dir(self, path):
for fname in walk_files(path, self.repo.dvcignore):
self._unprotect_file(fname, allow_copy)
RemoteLOCAL._unprotect_file(fname)
def unprotect(self, path_info, allow_copy=True):
def unprotect(self, path_info):
path = path_info.fspath
if not os.path.exists(path):
raise DvcException(
@@ -445,9 +433,9 @@ class RemoteLOCAL(RemoteBASE):
)
if os.path.isdir(path):
self._unprotect_dir(path, allow_copy)
self._unprotect_dir(path)
else:
self._unprotect_file(path, allow_copy)
RemoteLOCAL._unprotect_file(path)
@staticmethod
def protect(path_info):
@@ -521,36 +509,3 @@ class RemoteLOCAL(RemoteBASE):
if self.is_dir_checksum(c):
unpacked.add(c + self.UNPACKED_DIR_SUFFIX)
return unpacked
def _get_cache_type(self, path_info):
if self.cache_type_confirmed:
return self.cache_types[0]
workspace_file = path_info.with_name("." + uuid())
test_cache_file = self.path_info / ".cache_type_test_file"
if not self.exists(test_cache_file):
with open(fspath_py35(test_cache_file), "wb") as fobj:
fobj.write(bytes(1))
try:
self.link(test_cache_file, workspace_file)
finally:
self.remove(workspace_file)
self.remove(test_cache_file)
self.cache_type_confirmed = True
return self.cache_types[0]
def _link_matches(self, path_info):
is_hardlink = System.is_hardlink(path_info)
is_symlink = System.is_symlink(path_info)
is_copy_or_reflink = not is_hardlink and not is_symlink
cache_type = self._get_cache_type(path_info)
if cache_type == "symlink":
return is_symlink
if cache_type == "hardlink":
return is_hardlink
return is_copy_or_reflink
+25 -24
View File
@@ -1,15 +1,17 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import os
import logging
import os
import threading
from dvc.scheme import Schemes
from funcy import cached_property, wrap_prop
from dvc.config import Config
from dvc.remote.base import RemoteBASE
from dvc.progress import Tqdm
from dvc.path_info import CloudURLInfo
from dvc.progress import Tqdm
from dvc.remote.base import RemoteBASE
from dvc.scheme import Schemes
logger = logging.getLogger(__name__)
@@ -62,30 +64,29 @@ class RemoteOSS(RemoteBASE):
or "defaultSecret"
)
self._bucket = None
@property
@wrap_prop(threading.Lock())
@cached_property
def oss_service(self):
import oss2
if self._bucket is None:
logger.debug("URL {}".format(self.path_info))
logger.debug("key id {}".format(self.key_id))
logger.debug("key secret {}".format(self.key_secret))
auth = oss2.Auth(self.key_id, self.key_secret)
self._bucket = oss2.Bucket(
auth, self.endpoint, self.path_info.bucket
logger.debug("URL {}".format(self.path_info))
logger.debug("key id {}".format(self.key_id))
logger.debug("key secret {}".format(self.key_secret))
auth = oss2.Auth(self.key_id, self.key_secret)
bucket = oss2.Bucket(auth, self.endpoint, self.path_info.bucket)
# Ensure bucket exists
try:
bucket.get_bucket_info()
except oss2.exceptions.NoSuchBucket:
bucket.create_bucket(
oss2.BUCKET_ACL_PUBLIC_READ,
oss2.models.BucketCreateConfig(
oss2.BUCKET_STORAGE_CLASS_STANDARD
),
)
try: # verify that bucket exists
self._bucket.get_bucket_info()
except oss2.exceptions.NoSuchBucket:
self._bucket.create_bucket(
oss2.BUCKET_ACL_PUBLIC_READ,
oss2.models.BucketCreateConfig(
oss2.BUCKET_STORAGE_CLASS_STANDARD
),
)
return self._bucket
return bucket
def remove(self, path_info):
if path_info.scheme != self.scheme:
+4 -1
View File
@@ -1,6 +1,8 @@
from collections import deque
from contextlib import contextmanager
from funcy import memoize
import threading
from funcy import memoize, wrap_with
@contextmanager
@@ -16,6 +18,7 @@ def get_connection(conn_func, *args, **kwargs):
pool.release(conn)
@wrap_with(threading.Lock())
@memoize
def get_pool(conn_func, *args, **kwargs):
return Pool(conn_func, *args, **kwargs)
+46 -18
View File
@@ -1,17 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
import logging
import posixpath
from funcy import cached_property
import os
import threading
from funcy import cached_property, wrap_prop
from dvc.progress import Tqdm
from dvc.config import Config
from dvc.remote.base import RemoteBASE
from dvc.exceptions import DvcException, ETagMismatchError
from dvc.exceptions import DvcException
from dvc.exceptions import ETagMismatchError
from dvc.path_info import CloudURLInfo
from dvc.progress import Tqdm
from dvc.remote.base import RemoteBASE
from dvc.scheme import Schemes
logger = logging.getLogger(__name__)
@@ -26,11 +27,7 @@ class RemoteS3(RemoteBASE):
def __init__(self, repo, config):
super(RemoteS3, self).__init__(repo, config)
storagepath = "s3://{}".format(
config.get(Config.SECTION_AWS_STORAGEPATH, "").lstrip("/")
)
url = config.get(Config.SECTION_REMOTE_URL, storagepath)
url = config.get(Config.SECTION_REMOTE_URL, "s3://")
self.path_info = self.path_cls(url)
self.region = config.get(Config.SECTION_AWS_REGION)
@@ -60,6 +57,7 @@ class RemoteS3(RemoteBASE):
if shared_creds:
os.environ.setdefault("AWS_SHARED_CREDENTIALS_FILE", shared_creds)
@wrap_prop(threading.Lock())
@cached_property
def s3(self):
import boto3
@@ -145,13 +143,13 @@ class RemoteS3(RemoteBASE):
# multipart.
#
# If an object's etag looks like '8978c98bb5a48c2fb5f2c4c905768afa',
# then it was transfered as a single part, which means that the chunk
# then it was transferred as a single part, which means that the chunk
# size used to transfer it was greater or equal to the ContentLength
# of that object. So to preserve that tag over the next transfer, we
# could use any value >= ContentLength.
#
# If an object's etag looks like '50d67013a5e1a4070bef1fc8eea4d5f9-13',
# then it was transfered as a multipart, which means that the chunk
# then it was transferred as a multipart, which means that the chunk
# size used to transfer it was less than ContentLength of that object.
# Unfortunately, in general, it doesn't mean that the chunk size was
# the same throughout the transfer, so it means that in order to
@@ -209,10 +207,37 @@ class RemoteS3(RemoteBASE):
def list_cache_paths(self):
return self._list_paths(self.path_info)
def isfile(self, path_info):
from botocore.exceptions import ClientError
if path_info.path.endswith("/"):
return False
try:
self.s3.head_object(Bucket=path_info.bucket, Key=path_info.path)
except ClientError as exc:
if exc.response["Error"]["Code"] != "404":
raise
return False
return True
def exists(self, path_info):
"""Check if the blob exists. If it does not exist,
it could be a part of a directory path.
eg: if `data/file.txt` exists, check for `data` should return True
"""
return self.isfile(path_info) or self.isdir(path_info)
def makedirs(self, path_info):
# We need to support creating empty directories, which means
# creating an object with an empty body and a trailing slash `/`.
#
# We are not creating directory objects for every parent prefix,
# as it is not required.
dir_path = path_info / ""
fname = next(self._list_paths(path_info, max_items=1), "")
return path_info.path == fname or fname.startswith(dir_path.path)
self.s3.put_object(Bucket=path_info.bucket, Key=dir_path.path, Body="")
def isdir(self, path_info):
# S3 doesn't have a concept for directories.
@@ -272,5 +297,8 @@ class RemoteS3(RemoteBASE):
)
def walk_files(self, path_info, max_items=None):
for fname in self._list_paths(path_info, max_items):
yield path_info / posixpath.relpath(fname, path_info.path)
for fname in self._list_paths(path_info / "", max_items):
if fname.endswith("/"):
continue
yield path_info.replace(path=fname)
+2 -1
View File
@@ -1,9 +1,10 @@
import logging
import time
import sys
import time
from functools import wraps
import colorama
from dvc.config import Config
+28 -27
View File
@@ -1,30 +1,39 @@
from __future__ import unicode_literals
import errno
import itertools
import io
import os
import posixpath
import getpass
import io
import itertools
import logging
import os
import threading
from concurrent.futures import ThreadPoolExecutor
from contextlib import contextmanager, closing
from contextlib import closing, contextmanager
from funcy import memoize, wrap_with
import dvc.prompt as prompt
from dvc.config import Config
from dvc.utils import to_chunks
from dvc.utils.compat import urlparse, StringIO
from dvc.remote.base import RemoteBASE
from dvc.scheme import Schemes
from dvc.remote.pool import get_connection
from dvc.progress import Tqdm
from dvc.remote.base import RemoteBASE
from dvc.remote.pool import get_connection
from dvc.scheme import Schemes
from dvc.utils import to_chunks
from dvc.utils.compat import StringIO
from dvc.utils.compat import urlparse
logger = logging.getLogger(__name__)
saved_passwords = {}
saved_passwords_lock = threading.Lock()
@wrap_with(threading.Lock())
@memoize
def ask_password(host, user, port):
return prompt.password(
"Enter a private key passphrase or a password for "
"host '{host}' port '{port}' user '{user}'".format(
host=host, port=port, user=user
)
)
class RemoteSSH(RemoteBASE):
@@ -119,21 +128,11 @@ class RemoteSSH(RemoteBASE):
def ensure_credentials(self, path_info=None):
if path_info is None:
path_info = self.path_info
host, user, port = path_info.host, path_info.user, path_info.port
# NOTE: we use the same password regardless of the server :(
if self.ask_password and self.password is None:
with saved_passwords_lock:
server_key = (host, user, port)
password = saved_passwords.get(server_key)
if password is None:
saved_passwords[server_key] = password = prompt.password(
"Enter a private key passphrase or a password for "
"host '{host}' port '{port}' user '{user}'".format(
host=host, port=port, user=user
)
)
self.password = password
host, user, port = path_info.host, path_info.user, path_info.port
self.password = ask_password(host, user, port)
def ssh(self, path_info):
self.ensure_credentials(path_info)
@@ -263,12 +262,14 @@ class RemoteSSH(RemoteBASE):
def list_cache_paths(self):
with self.ssh(self.path_info) as ssh:
return list(ssh.walk_files(self.path_info.path))
# If we simply return an iterator then with above closes instantly
for path in ssh.walk_files(self.path_info.path):
yield path
def walk_files(self, path_info):
with self.ssh(path_info) as ssh:
for fname in ssh.walk_files(path_info.path):
yield path_info / posixpath.relpath(fname, path_info.path)
yield path_info.replace(path=fname)
def makedirs(self, path_info):
with self.ssh(path_info) as ssh:
+22 -3
View File
@@ -1,8 +1,9 @@
import errno
import logging
import os
import posixpath
import logging
import errno
import stat
from funcy import cached_property
try:
@@ -185,8 +186,26 @@ class SSHConnection:
self.sftp.get(src, dest, callback=pbar.update_to)
def move(self, src, dst):
"""Rename src to dst, if it is not possible (in case src and dst are
on different filesystems) and actual physical copying of data is
happening.
"""
self.makedirs(posixpath.dirname(dst))
self.sftp.rename(src, dst)
try:
self.sftp.rename(src, dst)
except OSError:
self._atomic_copy(src, dst)
self.remove(src)
def _atomic_copy(self, src, dst):
tmp = tmp_fname(dst)
try:
self.copy(src, tmp)
self.sftp.rename(tmp, dst)
finally:
self.remove(tmp)
def upload(self, src, dest, no_progress_bar=False, progress_title=None):
self.makedirs(posixpath.dirname(dest))
+22 -16
View File
@@ -1,26 +1,29 @@
from __future__ import unicode_literals
import os
import logging
import os
from contextlib import contextmanager
from functools import wraps
from itertools import chain
from functools import wraps
from funcy import cached_property
from .graph import check_acyclic
from .graph import get_pipeline
from .graph import get_pipelines
from .graph import get_stages
from dvc.config import Config
from dvc.exceptions import (
NotDvcRepoError,
OutputNotFoundError,
FileMissingError,
)
from dvc.exceptions import FileMissingError
from dvc.exceptions import NotDvcRepoError
from dvc.exceptions import OutputNotFoundError
from dvc.ignore import DvcIgnoreFilter
from dvc.path_info import PathInfo
from dvc.remote.base import RemoteActionNotImplemented
from dvc.utils.compat import open as _open, fspath_py35, FileNotFoundError
from dvc.utils import relpath
from .graph import check_acyclic, get_pipeline, get_pipelines, get_stages
from dvc.utils.fs import path_isin
from dvc.utils.compat import FileNotFoundError
from dvc.utils.compat import fspath_py35
from dvc.utils.compat import open as _open
logger = logging.getLogger(__name__)
@@ -66,7 +69,7 @@ class Repo(object):
def __init__(self, root_dir=None):
from dvc.state import State
from dvc.lock import Lock
from dvc.lock import make_lock
from dvc.scm import SCM
from dvc.cache import Cache
from dvc.data_cloud import DataCloud
@@ -85,9 +88,12 @@ class Repo(object):
self.tree = WorkingTree(self.root_dir)
self.lock = Lock(
hardlink_lock = self.config.config["core"].get("hardlink_lock", False)
self.lock = make_lock(
os.path.join(self.dvc_dir, "lock"),
tmp_dir=os.path.join(self.dvc_dir, "tmp"),
hardlink_lock=hardlink_lock,
friendly=True,
)
# NOTE: storing state and link_state in the repository itself to avoid
# any possible state corruption in 'shared cache dir' scenario.
@@ -164,7 +170,7 @@ class Repo(object):
+ updater.lock.files
)
if self.cache.local.cache_dir.startswith(self.root_dir + os.sep):
if path_isin(self.cache.local.cache_dir, self.root_dir):
flist += [self.cache.local.cache_dir]
self.scm.ignore_list(flist)
@@ -191,7 +197,7 @@ class Repo(object):
ret = []
for node in nodes:
stage = attrs[node]
if stage.path.startswith(target + os.sep):
if path_isin(stage.path, target):
ret.append(stage)
return ret
@@ -433,7 +439,7 @@ class Repo(object):
matched = list(filter(func, outs))
if not matched:
raise OutputNotFoundError(path)
raise OutputNotFoundError(path, self)
return matched
@@ -494,7 +500,7 @@ class Repo(object):
@cached_property
def dvcignore(self):
return DvcIgnoreFilter(self.root_dir)
return DvcIgnoreFilter(self.root_dir, self.tree)
def close(self):
self.scm.close()
+7 -5
View File
@@ -1,16 +1,18 @@
from __future__ import unicode_literals
import os
import logging
import os
import colorama
from six import string_types
from dvc.repo.scm_context import scm_context
from dvc.stage import Stage
from dvc.utils import walk_files, LARGE_DIR_SIZE
from . import locked
from dvc.exceptions import RecursiveAddingWhileUsingFilename
from dvc.progress import Tqdm
from . import locked
from dvc.repo.scm_context import scm_context
from dvc.stage import Stage
from dvc.utils import LARGE_DIR_SIZE
from dvc.utils import walk_files
logger = logging.getLogger(__name__)
+2 -1
View File
@@ -1,4 +1,5 @@
from funcy import group_by
from dvc.scm.tree import WorkingTree
@@ -22,7 +23,7 @@ def brancher( # noqa: E302
str: the display name for the currently selected tree, it could be:
- a git revision identifier
- empty string it there is no branches to iterate over
- "Working Tree" if there are uncommited changes in the SCM repo
- "Working Tree" if there are uncommitted changes in the SCM repo
"""
if not any([branches, all_branches, tags, all_tags, all_commits]):
yield ""
+10 -12
View File
@@ -2,21 +2,22 @@ from __future__ import unicode_literals
import logging
from dvc.exceptions import CheckoutErrorSuggestGit, CheckoutError
from dvc.exceptions import CheckoutError
from dvc.exceptions import CheckoutErrorSuggestGit
from dvc.progress import Tqdm
logger = logging.getLogger(__name__)
def _cleanup_unused_links(self, all_stages):
def _cleanup_unused_links(repo):
used = [
out.fspath
for stage in all_stages
for stage in repo.stages
for out in stage.outs
if out.scheme == "local"
]
self.state.remove_unused_links(used)
repo.state.remove_unused_links(used)
def get_all_files_numbers(stages):
@@ -29,7 +30,11 @@ def _checkout(
from dvc.stage import StageFileDoesNotExistError, StageFileBadNameError
stages = set()
targets = targets or [None]
if not targets:
targets = [None]
_cleanup_unused_links(self)
for target in targets:
try:
new = self.collect(
@@ -41,7 +46,6 @@ def _checkout(
raise
raise CheckoutErrorSuggestGit(target, exc)
_cleanup_unused_links(self, self.stages)
total = get_all_files_numbers(stages)
if total == 0:
logger.info("Nothing to do")
@@ -50,12 +54,6 @@ def _checkout(
total=total, unit="file", desc="Checkout", disable=total == 0
) as pbar:
for stage in stages:
if stage.locked:
logger.warning(
"DVC-file '{path}' is locked. Its dependencies are"
" not going to be checked out.".format(path=stage.relpath)
)
failed.extend(
stage.checkout(force=force, progress_callback=pbar.update_desc)
)
+1 -1
View File
@@ -1,4 +1,4 @@
from dvc.utils import remove
from dvc.utils.fs import remove
def destroy(self):
+7 -11
View File
@@ -3,18 +3,14 @@ from __future__ import unicode_literals
import os
from errno import ENOENT
import dvc.logger as logger
from dvc.scm.base import FileNotInCommitError
from dvc.scm.git import (
DIFF_A_TREE,
DIFF_B_TREE,
DIFF_A_REF,
DIFF_B_REF,
DIFF_EQUAL,
)
from . import locked
from dvc.scm.base import FileNotInCommitError
from dvc.scm.git import DIFF_A_REF
from dvc.scm.git import DIFF_A_TREE
from dvc.scm.git import DIFF_B_REF
from dvc.scm.git import DIFF_B_TREE
from dvc.scm.git import DIFF_EQUAL
DIFF_TARGET = "target"
@@ -224,7 +220,7 @@ def _diff_royal(self, target, diff_dct):
@locked
def diff(self, a_ref, target=None, b_ref=None):
"""Gerenates diff message string output
"""Generates diff message string output
Args:
target(str) - file/directory to check diff of
+6 -5
View File
@@ -2,11 +2,12 @@ from __future__ import unicode_literals
import logging
from dvc.config import NoRemoteError
from dvc.exceptions import DownloadError, OutputNotFoundError
from dvc.scm.base import CloneError
from dvc.external_repo import external_repo
from dvc.cache import NamedCache
from dvc.config import NoRemoteError
from dvc.exceptions import DownloadError
from dvc.exceptions import OutputNotFoundError
from dvc.external_repo import external_repo
from dvc.scm.base import CloneError
logger = logging.getLogger(__name__)
@@ -26,7 +27,7 @@ def _fetch(
"""Download data items from a cloud and imported repositories
Returns:
int: number of succesfully downloaded files
int: number of successfully downloaded files
Raises:
DownloadError: thrown when there are failed downloads, either

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