mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-02 23:09:28 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1834f34e7 | ||
|
|
536273ab1e | ||
|
|
5cff4891fd | ||
|
|
e29da6eae1 | ||
|
|
046ebb05dd | ||
|
|
36ec25ffd7 | ||
|
|
6e6d9253d6 | ||
|
|
910c232077 | ||
|
|
c90947f33c | ||
|
|
4ee4f137f2 | ||
|
|
a750a75016 | ||
|
|
cf976f17ae | ||
|
|
7041fa5f42 | ||
|
|
d898877175 | ||
|
|
1ca1d78b38 | ||
|
|
da6ebdbf8c | ||
|
|
e995492095 | ||
|
|
276d499c9b | ||
|
|
fd4696cc50 | ||
|
|
90b9193250 | ||
|
|
77e35145a9 | ||
|
|
49c7c55ba3 | ||
|
|
f28d5ee9e0 | ||
|
|
621fe07a3b | ||
|
|
8323923ce9 | ||
|
|
04b50c5cd0 | ||
|
|
b20a59d748 | ||
|
|
bf2f08da09 | ||
|
|
1835c8c6e9 | ||
|
|
7e1bb07f10 | ||
|
|
92dc1f31f5 | ||
|
|
e16c0c8b2f | ||
|
|
33e7645603 | ||
|
|
c827654fe7 | ||
|
|
b8c666b1b7 | ||
|
|
fd73b389c6 | ||
|
|
0433565f1f | ||
|
|
b5449503f0 | ||
|
|
fa14507bc8 | ||
|
|
21458796ed | ||
|
|
a6e49a72de | ||
|
|
97348cfba7 | ||
|
|
c5e3d42899 | ||
|
|
c6c2ce130a | ||
|
|
be975ad9eb | ||
|
|
d0aa1ce776 | ||
|
|
5ec4fa473c | ||
|
|
5fd54679c7 | ||
|
|
5c1487e2ec | ||
|
|
30517f6676 | ||
|
|
bbb77360e9 | ||
|
|
1af150a5e9 | ||
|
|
cf59d801ce | ||
|
|
b733dea4c2 | ||
|
|
2ed677057d | ||
|
|
0597311439 | ||
|
|
fc8ec6aa95 | ||
|
|
3dca69b678 | ||
|
|
4c0bb8d316 | ||
|
|
1cb371d4d7 | ||
|
|
c96795dcfb | ||
|
|
8f768addf9 | ||
|
|
af1fb722c8 | ||
|
|
8b1c1a31f3 | ||
|
|
262419ef35 | ||
|
|
265fc581c0 | ||
|
|
80f414c19d | ||
|
|
2cd33e10ac | ||
|
|
603f5fdba1 | ||
|
|
528eab0418 | ||
|
|
ed664143b4 | ||
|
|
21a2504bcb | ||
|
|
d0291e8039 | ||
|
|
358c5e0c39 | ||
|
|
9d7fba5c61 | ||
|
|
a1e339b4fa | ||
|
|
6c58d438b3 | ||
|
|
0c709b6ec1 | ||
|
|
0aeb7a8662 | ||
|
|
46f3157f43 | ||
|
|
36fd69be21 | ||
|
|
3759535d43 | ||
|
|
2c4365513e | ||
|
|
26152292aa | ||
|
|
3e0f2bafbb | ||
|
|
78637713cf | ||
|
|
f9104275f3 | ||
|
|
bce3b5d2a1 | ||
|
|
6ace5ed380 | ||
|
|
6543580100 | ||
|
|
a1b891ce01 | ||
|
|
3a85326df3 | ||
|
|
355ebc72eb | ||
|
|
5a6f834088 | ||
|
|
e98bf38d4c | ||
|
|
192c9cc577 | ||
|
|
0e38525d8d | ||
|
|
8acd943d52 | ||
|
|
79c88008f8 | ||
|
|
9c9ca39fb4 | ||
|
|
89109fafcf | ||
|
|
aa19e24670 | ||
|
|
629223cbc2 | ||
|
|
96449f157c | ||
|
|
8cb2646023 | ||
|
|
25313f5d82 | ||
|
|
182728d674 | ||
|
|
f245567613 | ||
|
|
4163c8f57d | ||
|
|
6532258e4a | ||
|
|
eb45cda5d0 | ||
|
|
abd4f4a2a8 | ||
|
|
f41e535857 | ||
|
|
1989c0fe98 | ||
|
|
3ddd4b87fc | ||
|
|
e089ee0365 | ||
|
|
02f7fcae73 | ||
|
|
cdba65f27e | ||
|
|
3959c51373 | ||
|
|
90aaf5a296 | ||
|
|
45c5c83699 | ||
|
|
ecb71c8263 | ||
|
|
e6e3912da0 | ||
|
|
7f6890ff25 | ||
|
|
99daf5d113 | ||
|
|
83ffb8803e | ||
|
|
ace8464acb | ||
|
|
0dbdfd2b9b | ||
|
|
6d4251798a | ||
|
|
11d031935d | ||
|
|
64beb1578a | ||
|
|
f888544554 | ||
|
|
1e538ed87b | ||
|
|
e69deaa132 | ||
|
|
be0a1a4a12 | ||
|
|
004f49744e | ||
|
|
45181504ea | ||
|
|
915dd5062e | ||
|
|
d08c85f7d1 | ||
|
|
2342099bd8 | ||
|
|
06d8e3c639 | ||
|
|
195e67c0c1 | ||
|
|
c75a5583b3 | ||
|
|
981baa8c30 | ||
|
|
aa517bc90e | ||
|
|
c74dc20b97 | ||
|
|
1ba3944944 | ||
|
|
8620b6e5af | ||
|
|
6b6084a848 | ||
|
|
967dd5ea2b | ||
|
|
ad39bf14ed | ||
|
|
01416df5a4 | ||
|
|
eb592e7e33 | ||
|
|
2db07b047e | ||
|
|
8ecc66ae9a | ||
|
|
dfa5f4d9a0 | ||
|
|
75952c4e36 | ||
|
|
31b0b8de73 | ||
|
|
fae47e6122 | ||
|
|
ac3eef439f | ||
|
|
b044bcce5e | ||
|
|
a251cffdb5 | ||
|
|
37858be02f | ||
|
|
9b7240e9e0 | ||
|
|
e362438f03 | ||
|
|
6c79f6bbfc | ||
|
|
58cd1cbc09 | ||
|
|
96e3b48afd | ||
|
|
1f03c40dbe | ||
|
|
e93e8f05a3 | ||
|
|
0f1326e547 | ||
|
|
2a04263f02 | ||
|
|
348446050e | ||
|
|
7e1036b30d | ||
|
|
a15dbc8e03 | ||
|
|
4e02197245 | ||
|
|
4ab7ef06e5 | ||
|
|
dae2faf36d | ||
|
|
58f10a4661 | ||
|
|
64e787fa02 | ||
|
|
c7e2ec9707 | ||
|
|
a4cc198ac7 | ||
|
|
d203bf9024 | ||
|
|
baeec8ec2d | ||
|
|
9c689a3b51 | ||
|
|
e1bf418e72 | ||
|
|
b6d8c07d4d | ||
|
|
b814531c84 | ||
|
|
3b3cd0a0b2 | ||
|
|
b2101a85ad | ||
|
|
ba760c56e5 | ||
|
|
787c72ea18 | ||
|
|
a2b7a59322 | ||
|
|
ec090c5680 | ||
|
|
58f74ae543 | ||
|
|
2e8bcbf719 | ||
|
|
10f523430b | ||
|
|
5563a595d5 | ||
|
|
dd3deb6cbd | ||
|
|
7841020b2b | ||
|
|
1c394986db | ||
|
|
870935ebdc | ||
|
|
1be82fa56d | ||
|
|
ce0f6c187e | ||
|
|
559ec392f6 | ||
|
|
c71a4082e6 | ||
|
|
ce269ffa58 | ||
|
|
61d0893d7c | ||
|
|
eef5fb9b37 | ||
|
|
f03d555747 | ||
|
|
a91993616f | ||
|
|
3c570a2177 | ||
|
|
3312a90949 | ||
|
|
32bebc0246 | ||
|
|
35648134f7 | ||
|
|
df09e522ec | ||
|
|
40f7f34889 | ||
|
|
cf52efb8c3 | ||
|
|
8be71be540 | ||
|
|
071a5b7ba4 | ||
|
|
ed6cf093a8 | ||
|
|
7c3c47b4a2 | ||
|
|
3a2259821b | ||
|
|
649ee67e4b | ||
|
|
7cdb3b0ae2 | ||
|
|
ecdc44e010 | ||
|
|
24e10e0278 | ||
|
|
7c8e6e3156 | ||
|
|
d2812fa48f | ||
|
|
086d51d9de | ||
|
|
1d8b2e5fa6 | ||
|
|
894f530d22 | ||
|
|
1b9d40960f | ||
|
|
696fabc745 | ||
|
|
780dc72301 | ||
|
|
1ca3ef14a3 | ||
|
|
a6c89cac8d | ||
|
|
593f1064cf | ||
|
|
35bb261b89 | ||
|
|
fe6d543c1f | ||
|
|
4351d32de4 | ||
|
|
efc2d7ad3d | ||
|
|
7c65739e7e | ||
|
|
cc4816f652 | ||
|
|
faa1bdfbce | ||
|
|
006b6f3513 | ||
|
|
1bc4c1dceb | ||
|
|
531b2110cf | ||
|
|
c1917ed751 | ||
|
|
43195a806d | ||
|
|
d41512c85c | ||
|
|
e9fb0b92aa | ||
|
|
5485275b92 | ||
|
|
1f80f2f2ed | ||
|
|
6b6dc01ac4 | ||
|
|
91d3ff0f69 | ||
|
|
701f48197e | ||
|
|
da3bbccf66 | ||
|
|
40484e8d57 | ||
|
|
48b56c83dc | ||
|
|
f8bbf20b77 | ||
|
|
cd2c4d0caf | ||
|
|
9b329d01bf | ||
|
|
5c442cb867 | ||
|
|
2d5c15bee1 | ||
|
|
7868c1301c | ||
|
|
dfe76c54d7 | ||
|
|
392c15e1d4 | ||
|
|
121d56b739 | ||
|
|
66406e6c99 | ||
|
|
8399bf612a | ||
|
|
6c1b786564 | ||
|
|
d1c5fcc1b8 | ||
|
|
4ee3271b0c | ||
|
|
dae8515933 | ||
|
|
f02dd83e53 | ||
|
|
a2066d75f9 | ||
|
|
283d8245e1 | ||
|
|
c52b12ac2a | ||
|
|
440cb9d2bc | ||
|
|
2d820cccad | ||
|
|
30c3e3e4d4 | ||
|
|
58029e9719 | ||
|
|
990e834f1d | ||
|
|
33a2449c91 | ||
|
|
e2ce95e17e | ||
|
|
53f9953cd1 | ||
|
|
e6419b939d | ||
|
|
37ccc5e8d8 | ||
|
|
8312eb9b42 | ||
|
|
c8745dd580 | ||
|
|
1cf8b5e582 | ||
|
|
12955da3c3 | ||
|
|
e73cc9b40a | ||
|
|
b56f6b69dd | ||
|
|
54a6709d99 | ||
|
|
e4a5a2aec5 | ||
|
|
16ecd53b71 | ||
|
|
612f3039e0 | ||
|
|
17f523bf86 | ||
|
|
a0bf2d997a | ||
|
|
8ed9b911db | ||
|
|
ef22b1d77e | ||
|
|
aaa825a9a8 | ||
|
|
2a40a55bb4 | ||
|
|
3aa9f51e36 | ||
|
|
1cfdc22549 | ||
|
|
2b889baa5e | ||
|
|
8589aa4001 | ||
|
|
8cbc4c43f1 | ||
|
|
417c327143 | ||
|
|
204eebaf15 | ||
|
|
3a6fd75d98 | ||
|
|
351b952d5f | ||
|
|
fe7db32311 | ||
|
|
0bd34c8977 | ||
|
|
48a63ca588 | ||
|
|
4459d6ac57 | ||
|
|
08a50ee36e | ||
|
|
f1ea64c4ef | ||
|
|
7ede45af2d | ||
|
|
51db1765ee | ||
|
|
9101fce761 | ||
|
|
3f842a205f | ||
|
|
77e900fed2 | ||
|
|
dde456c923 | ||
|
|
cb9d3529af | ||
|
|
876c10fcf5 | ||
|
|
93eea0cd42 | ||
|
|
25fd274090 | ||
|
|
ddfa607437 | ||
|
|
c5a3566e49 | ||
|
|
e7d4f5b24e | ||
|
|
1d2892efac | ||
|
|
83835afa69 | ||
|
|
4c5d052d8c | ||
|
|
e4d514c990 | ||
|
|
baeecd1972 | ||
|
|
dbd098506c | ||
|
|
4779c248f8 | ||
|
|
2cf3a12e46 | ||
|
|
78fb5275e5 | ||
|
|
ccbe2de89e | ||
|
|
54e39e4abd | ||
|
|
59a4c4f67c | ||
|
|
1a7ae16f69 | ||
|
|
286f4fda3e | ||
|
|
d2c63bffcb | ||
|
|
2b41305c51 | ||
|
|
546becddaf | ||
|
|
c6015a05a3 | ||
|
|
c0754ee5b3 | ||
|
|
4db2e89938 | ||
|
|
4a6f3ed64a | ||
|
|
9f0c4350bd | ||
|
|
a91528c3c9 | ||
|
|
b80d84e689 | ||
|
|
2a2c02c184 | ||
|
|
35b648bbcf | ||
|
|
96ac321dd5 | ||
|
|
50fd8ca15d | ||
|
|
184cc35cf0 | ||
|
|
81da91a973 | ||
|
|
ad5b91e51a | ||
|
|
391cf80e7b | ||
|
|
8d13486048 | ||
|
|
37761a2627 | ||
|
|
968a8fde99 | ||
|
|
3043b6588f | ||
|
|
47208533a2 | ||
|
|
09ccd9c7e4 | ||
|
|
cd9dd0a312 | ||
|
|
6fbbbeccc5 | ||
|
|
c34cbf63af | ||
|
|
b76aad8d56 | ||
|
|
68bf095eae | ||
|
|
ea6e01d0e0 | ||
|
|
19038b32bd | ||
|
|
648edd71eb | ||
|
|
0c95cde712 | ||
|
|
e218431f64 | ||
|
|
d3182e0dd9 | ||
|
|
e040e875dc | ||
|
|
303da84b18 | ||
|
|
03bfdf2663 | ||
|
|
8edaef0103 | ||
|
|
fd8a8c2729 | ||
|
|
30337bf0ba | ||
|
|
5426ca6f79 | ||
|
|
a89c25700f | ||
|
|
0e0bb8ad76 | ||
|
|
1de37556d5 | ||
|
|
098685b8f3 | ||
|
|
8f549c8a8d | ||
|
|
e56d97bb28 | ||
|
|
07b72c20e9 | ||
|
|
592f251481 | ||
|
|
890c0020ed | ||
|
|
7ce0b1cba2 | ||
|
|
b8a9c37af5 | ||
|
|
1af63acae5 | ||
|
|
d68599a5f7 | ||
|
|
7ccf35b47e | ||
|
|
4f49023a08 | ||
|
|
d7ea7c358d | ||
|
|
3f0da8f6cc | ||
|
|
b7b7eff4a3 | ||
|
|
ddeb32ed33 | ||
|
|
c7be921f44 | ||
|
|
c95c3f0f7a | ||
|
|
be824ede0d | ||
|
|
cbe46cccd8 | ||
|
|
49b5157abe | ||
|
|
6f87c14bf9 | ||
|
|
0f80d16ba7 | ||
|
|
4b23b4f8ae | ||
|
|
446871d84f | ||
|
|
99bbe15980 | ||
|
|
320771551b | ||
|
|
1676d1f344 | ||
|
|
c8ae2895f0 | ||
|
|
ed1ab7fa75 | ||
|
|
73497dae9e | ||
|
|
eff775c760 | ||
|
|
e73d662686 | ||
|
|
86a6fb104a | ||
|
|
3b097dcccd | ||
|
|
e910dac0dc | ||
|
|
17a71d6011 | ||
|
|
5a6ccc8027 | ||
|
|
6dccfcb3f9 | ||
|
|
2b0c365f35 | ||
|
|
611e2f4311 | ||
|
|
561a375b48 | ||
|
|
e51345e276 | ||
|
|
00e61e18ae | ||
|
|
5490fc00d1 | ||
|
|
f1043676da | ||
|
|
f7ebcbb974 | ||
|
|
c637b963e0 | ||
|
|
9156bb80db | ||
|
|
58295ca0ab | ||
|
|
4176007493 | ||
|
|
9d7883f529 | ||
|
|
93c556acdd | ||
|
|
676b1e47e9 | ||
|
|
0b141ac08a | ||
|
|
b4db1e06a1 | ||
|
|
0d0fff6ddc | ||
|
|
6c8c26f741 | ||
|
|
f68a12558a | ||
|
|
a8a5b29fb7 | ||
|
|
037e2e54f0 | ||
|
|
ae80889477 | ||
|
|
e1acab5113 | ||
|
|
d84196db62 | ||
|
|
80c2815e67 | ||
|
|
e42a01a3dd | ||
|
|
8e1b75feb5 | ||
|
|
4ec6d64629 | ||
|
|
a86470ccbc | ||
|
|
5d2701341b | ||
|
|
97589a1358 | ||
|
|
9e72ce53cd | ||
|
|
e34a6d3b30 | ||
|
|
25dcc33580 | ||
|
|
18fa1a0272 | ||
|
|
49a27f4140 | ||
|
|
9b2aa79c18 | ||
|
|
24023c6977 | ||
|
|
5310bcdcec | ||
|
|
ae92154cec | ||
|
|
f661cee8f6 | ||
|
|
e07c85a993 | ||
|
|
58d9e379fe | ||
|
|
524162dc3f | ||
|
|
aa15b21dce | ||
|
|
0404967591 | ||
|
|
465d886c29 | ||
|
|
459adfb40f | ||
|
|
fe9f8654a3 | ||
|
|
8208846003 | ||
|
|
73aea2d972 | ||
|
|
10184e9370 | ||
|
|
2690f05cee | ||
|
|
34c2c95115 | ||
|
|
5c6c1dc719 | ||
|
|
98bcfad8e7 | ||
|
|
7ab4c59d70 | ||
|
|
a137b2a6bc | ||
|
|
6f0e8a8db5 | ||
|
|
d294945499 | ||
|
|
db70d1cc5c | ||
|
|
ed6e11e856 | ||
|
|
c1cd13c34f | ||
|
|
12dd746165 | ||
|
|
5165fb67a1 | ||
|
|
b2c1c4dccd | ||
|
|
b060cadbf1 | ||
|
|
7c55a9c7cd | ||
|
|
037b5b2e0a | ||
|
|
99acb8e335 | ||
|
|
b46cfbda5d | ||
|
|
a8d193bf2a | ||
|
|
0b1cf6c771 | ||
|
|
b3aa698cd4 | ||
|
|
bb9a3a0403 | ||
|
|
3427e115eb | ||
|
|
3d5f4dc66c | ||
|
|
80633f4fad | ||
|
|
8bda3650c5 | ||
|
|
ea2e5dc3e5 | ||
|
|
61afdca3a6 | ||
|
|
6a6bc7035a | ||
|
|
ccc97622fa | ||
|
|
916b5d169f | ||
|
|
38e9b2ba95 | ||
|
|
18f039d7c7 | ||
|
|
f1c9e9365b | ||
|
|
b50d1ee674 | ||
|
|
1c33024b2c | ||
|
|
dd2d2dce19 | ||
|
|
5f558d003a | ||
|
|
3676f35b7c | ||
|
|
9af6fcad4a | ||
|
|
6d80b72434 | ||
|
|
46d9290fc2 | ||
|
|
c47c2123fb | ||
|
|
d25d26ff74 | ||
|
|
c2e4f51479 | ||
|
|
977a1f9f43 | ||
|
|
608269dc88 | ||
|
|
e48d0d1d9c | ||
|
|
bf08bd3f12 | ||
|
|
db5dfdd6bc | ||
|
|
3b10f40ca3 | ||
|
|
4660443d0e | ||
|
|
a92ace0054 | ||
|
|
309ae4d995 | ||
|
|
e1254c9ece | ||
|
|
7a65ac06b2 | ||
|
|
b0052aa666 | ||
|
|
da27174e02 | ||
|
|
aabdddc2b7 | ||
|
|
fc1a192c90 | ||
|
|
178244f666 | ||
|
|
dba7d3c320 | ||
|
|
8b075eb754 | ||
|
|
f507e4d9c0 | ||
|
|
5169205229 | ||
|
|
3bcd683677 | ||
|
|
30ec1cf5e9 | ||
|
|
a814f04ee0 | ||
|
|
90a27179eb | ||
|
|
175c578579 | ||
|
|
5441964fdb | ||
|
|
d4bfe38a54 | ||
|
|
a060049186 | ||
|
|
c02285ab49 | ||
|
|
bccbbc1d94 | ||
|
|
d736b31d8b | ||
|
|
68c1cc095c | ||
|
|
c94d28594d | ||
|
|
d3d429ef7f | ||
|
|
6ac88104da | ||
|
|
d87cdba6d8 | ||
|
|
b25ef5cdfb | ||
|
|
435407185f | ||
|
|
0b192af6f4 | ||
|
|
c6b602a167 | ||
|
|
1d5de9c5ba | ||
|
|
0e5674ffea | ||
|
|
684fc32e36 | ||
|
|
81f37be9d6 | ||
|
|
3dfdd577e7 | ||
|
|
bfb6a32b51 | ||
|
|
33de761560 | ||
|
|
ff38aed927 | ||
|
|
b62f17ffaf | ||
|
|
a0c781a676 | ||
|
|
6ac7dfc008 | ||
|
|
b0372918ea | ||
|
|
4b0fa4d559 | ||
|
|
a24c389674 | ||
|
|
f386f7c082 | ||
|
|
eadd0da394 | ||
|
|
06673dd212 | ||
|
|
5f0b1edb40 | ||
|
|
05701e26db | ||
|
|
109288e1cd | ||
|
|
9e6f44bc1c | ||
|
|
68b24307f9 | ||
|
|
ea8a4a3db4 | ||
|
|
08ae68caf4 | ||
|
|
4400fb4090 | ||
|
|
28848c5407 | ||
|
|
b13184fcec | ||
|
|
ee5fbc72ab | ||
|
|
d7fd68a1ae | ||
|
|
59d6d5ea98 | ||
|
|
6fc0a6ff98 | ||
|
|
b4b5db4b4e | ||
|
|
23e4d376c9 | ||
|
|
089a90c969 | ||
|
|
198daec421 | ||
|
|
04eb6a9f00 | ||
|
|
0467f6a036 | ||
|
|
dc3d9c9677 | ||
|
|
fcf92b2755 | ||
|
|
c806275167 | ||
|
|
bd1add0b95 | ||
|
|
e36f0e9d85 | ||
|
|
bc1b8f3494 | ||
|
|
9169d273e3 | ||
|
|
bb7512687c | ||
|
|
334f297ad3 | ||
|
|
ace3ba0ef9 | ||
|
|
959d149381 | ||
|
|
7a65d67e7f | ||
|
|
e9be6779ea | ||
|
|
876d123f04 | ||
|
|
2bfe403a28 | ||
|
|
9bef123182 | ||
|
|
e98ca36f39 | ||
|
|
b9bf73f043 | ||
|
|
7628fe9d44 | ||
|
|
d415a05460 | ||
|
|
f5699ce16b | ||
|
|
baeedead3a | ||
|
|
5630db55ad | ||
|
|
f1bd47ce0e | ||
|
|
74a13a4d9a | ||
|
|
b7732a591d | ||
|
|
d57bb23c93 | ||
|
|
28402dedf1 | ||
|
|
df60f5d9bf | ||
|
|
7ea73b2596 | ||
|
|
aa2e830511 | ||
|
|
6fdd6673f3 | ||
|
|
fac5f18dea | ||
|
|
d8068430e7 | ||
|
|
25b1ef9971 | ||
|
|
df563e43c1 | ||
|
|
3ba6b1f0bf | ||
|
|
6a9fbaf3d2 | ||
|
|
a036eaf817 | ||
|
|
665185b9e7 | ||
|
|
43e281521e | ||
|
|
73b46a5789 | ||
|
|
8124396218 | ||
|
|
e95ae5613d | ||
|
|
ca4dd08e37 | ||
|
|
0ebc253a43 | ||
|
|
6bfce5a5db | ||
|
|
5fa33a06bc | ||
|
|
6a75f3c327 | ||
|
|
d7587556c9 | ||
|
|
bb80eab77a | ||
|
|
ab3f8fb1a4 | ||
|
|
6edca79b79 | ||
|
|
2a888970e8 | ||
|
|
73fdfc8059 | ||
|
|
8dc11417d2 | ||
|
|
b02855fb93 | ||
|
|
af255a46b4 | ||
|
|
2748492f6b | ||
|
|
c69d3188a6 | ||
|
|
9b9c04e633 | ||
|
|
350e0fb1f7 | ||
|
|
f14358bc72 | ||
|
|
705bc6ec82 | ||
|
|
61a02e6f56 | ||
|
|
8e5226ece0 | ||
|
|
2a1af0b880 | ||
|
|
014d149512 | ||
|
|
77ae4aa92e | ||
|
|
588471a811 | ||
|
|
e59c19e752 | ||
|
|
b0a92414fa | ||
|
|
dae500d7c7 | ||
|
|
a01bc2918e | ||
|
|
a33bbc97d5 | ||
|
|
ba2e8bdac7 | ||
|
|
4d6e045989 | ||
|
|
00eea4560e | ||
|
|
6e02dddabd | ||
|
|
b550c5e625 | ||
|
|
b36861ada9 | ||
|
|
d482e26c99 | ||
|
|
a8ad59ae4e | ||
|
|
76cc649f5e | ||
|
|
d52e1aa17b | ||
|
|
65f61acffa |
+1
-10
@@ -1,12 +1,3 @@
|
||||
/state
|
||||
/lock
|
||||
/config.local
|
||||
/updater
|
||||
/updater.lock
|
||||
/state-journal
|
||||
/state-wal
|
||||
/cache
|
||||
/pkg
|
||||
/repos
|
||||
/tmp
|
||||
/experiments
|
||||
/cache
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
['remote "dvc"']
|
||||
url = https://remote.dvc.org/dvc
|
||||
[core]
|
||||
remote = dvc
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
[flake8]
|
||||
ignore=
|
||||
E203, # Whitespace before ':'
|
||||
E266, # Too many leading '#' for block comment
|
||||
W503, # Line break occurred before a binary operator
|
||||
P1, # unindexed parameters in the str.format, see:
|
||||
# Whitespace before ':'
|
||||
E203
|
||||
# Too many leading '#' for block comment
|
||||
E266
|
||||
# Line break occurred before a binary operator
|
||||
W503
|
||||
# unindexed parameters in the str.format, see:
|
||||
# https://pypi.org/project/flake8-string-format/
|
||||
max_line_length = 79
|
||||
max-complexity = 15
|
||||
select = B,C,E,F,W,T4,B902,T,P
|
||||
P1
|
||||
# use `!r` instead of surrounding by quotes
|
||||
B028
|
||||
max_line_length = 88
|
||||
max-complexity = 10
|
||||
select = B,C,E,F,W,T4,B902,T,P,TYP
|
||||
show_source = true
|
||||
count = true
|
||||
min_python_version = 3.8.0
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# The following commits will be ignored by the GitHub blame view.
|
||||
# You can configure `git blame` to use this file as the default ignore file:
|
||||
# git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||
# or, you can also manually ignore commits from this file by doing:
|
||||
# git blame --ignore-revs-file .git-blame-ignore-revs <filepath>
|
||||
# see the `blame.markIgnoredLines` and `blame.markUnblamableLines` options as well.
|
||||
# Refer to: https://git-scm.com/docs/git-blame#Documentation/git-blame.txt---ignore-revs-fileltfilegt
|
||||
|
||||
# update black to 2023 stable style and change line-length to 88
|
||||
a86470ccbc693d6f0a4f8066cbd1357e5191c4ff
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
name: Epic/Story Issue Template
|
||||
about: Template for "top" level issues - Epics (>2 weeks) / Stories (<2 weeks)
|
||||
title: 'Epic: New Feature'
|
||||
labels: epic
|
||||
assignees:
|
||||
|
||||
---
|
||||
|
||||
## Summary / Background
|
||||
What do you want to achieve, why? business context
|
||||
...
|
||||
|
||||
## Scope
|
||||
|
||||
What will be impacted and what won't be?
|
||||
What needs implementation and what is invariant?
|
||||
e.g.
|
||||
- user should be able to run workflow X from UI
|
||||
- enable workflow Y, Z from CLI
|
||||
|
||||
## Assumptions
|
||||
Product / UX assumptions as well as technical assumptions / limitations
|
||||
e.g.
|
||||
* Support only Python Runtime
|
||||
* Focus on DVC experiments only
|
||||
* Deployment environments don't change often and can be picked up from shared configuration
|
||||
|
||||
## Open Questions
|
||||
e.g.
|
||||
- How should access control work for shared artifacts (workflow X)
|
||||
- Python runtime assumption - is it really valid? in light of <...>
|
||||
|
||||
## Blockers / Dependencies
|
||||
List issues or other conditions / blockers
|
||||
|
||||
## General Approach
|
||||
Invocation example:
|
||||
```shell
|
||||
$ mapper-run task.tar.gz --ray-cluster <ip>:<port>
|
||||
```
|
||||
|
||||
## Steps
|
||||
|
||||
### Must have (p1)
|
||||
- [ ] subissue2
|
||||
- [ ] step 2
|
||||
- info
|
||||
- info
|
||||
|
||||
### Optional / followup (p2)
|
||||
- [ ] ⌛ step 3 wip
|
||||
- [ ] step 4
|
||||
|
||||
## Timelines
|
||||
|
||||
Put your estimations here. Update once certainty changes
|
||||
- end of week (Feb 3) for prototype with workflows X, Y
|
||||
- Feb 15 - MVP in production
|
||||
- Low priority followups can be done later
|
||||
@@ -0,0 +1,10 @@
|
||||
pull_request_rules:
|
||||
- name: backport patches to 2.x
|
||||
conditions:
|
||||
- base=main
|
||||
- label=backport
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- "2.x"
|
||||
title: ({{ destination_branch }}) {{ title }}
|
||||
@@ -2,7 +2,7 @@ name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
branches: ["main", "2.x"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
schedule:
|
||||
|
||||
@@ -11,68 +11,10 @@ permissions: # added using https://github.com/step-security/secure-workflows
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
binary:
|
||||
permissions:
|
||||
contents: write # for actions/upload-release-asset to upload release asset
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- {pkg: "deb", os: "ubuntu-18.04", asset: "dvc_${{ github.event.release.tag_name }}_amd64.deb"}
|
||||
- {pkg: "rpm", os: "ubuntu-18.04", asset: "dvc-${{ github.event.release.tag_name }}-1.x86_64.rpm"}
|
||||
- {pkg: "osxpkg", os: "macos-10.15", asset: "dvc-${{ github.event.release.tag_name }}.pkg"}
|
||||
- {pkg: "exe", os: "windows-2019", asset: "dvc-${{ github.event.release.tag_name }}.exe"}
|
||||
|
||||
name: ${{ matrix.pkg }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
cache: pip
|
||||
cache-dependency-path: |
|
||||
pyproject.toml
|
||||
scripts/build-requirements.txt
|
||||
|
||||
- name: Set up Ruby 2.6
|
||||
uses: ruby/setup-ruby@v1
|
||||
if: matrix.pkg != 'exe'
|
||||
with:
|
||||
ruby-version: '2.6'
|
||||
|
||||
- name: Install fpm
|
||||
if: matrix.pkg != 'exe'
|
||||
run: gem install --no-document fpm
|
||||
|
||||
- name: Install deps
|
||||
run: |
|
||||
pip install --upgrade pip wheel setuptools
|
||||
pip install .[all]
|
||||
pip install -r scripts/build-requirements.txt
|
||||
|
||||
- name: Pull images
|
||||
run: dvc pull
|
||||
|
||||
- name: Build ${{ matrix.pkg }}
|
||||
run: python scripts/build.py ${{ matrix.pkg }}
|
||||
|
||||
- name: Publish ${{ matrix.pkg }}
|
||||
if: github.event_name == 'release'
|
||||
uses: actions/[email protected]
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: scripts/${{ matrix.pkg == 'exe' && 'innosetup' || 'fpm' }}/${{ matrix.asset }}
|
||||
asset_name: ${{ matrix.asset }}
|
||||
asset_content_type: binary/octet-stream
|
||||
|
||||
pip:
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -100,13 +42,10 @@ jobs:
|
||||
- name: Publish packages to PyPI
|
||||
if: github.event_name == 'release'
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
password: ${{ secrets.PYPI_TOKEN }}
|
||||
|
||||
- name: Publish to Test PyPI
|
||||
if: ${{ github.event_name == 'release' || (github.event_name == 'push' && github.ref == 'refs/heads/main') }}
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
password: ${{ secrets.TEST_PYPI_TOKEN }}
|
||||
repository_url: https://test.pypi.org/legacy/
|
||||
skip_existing: true
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
name: Remote Plugin Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
timeout-minutes: 45
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04]
|
||||
pyv: ["3.10"]
|
||||
plugin: ["dvc-s3"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
path: dvc
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: iterative/${{ matrix.plugin }}
|
||||
ref: main
|
||||
path: ${{ matrix.plugin }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.pyv }}
|
||||
cache: pip
|
||||
cache-dependency-path: |
|
||||
dvc/pyproject.toml
|
||||
${{ matrix.plugin }}/setup.cfg
|
||||
|
||||
- name: Install plugin + DVC@PR
|
||||
run: |
|
||||
pip install --upgrade pip wheel
|
||||
pip install "./dvc[testing]"
|
||||
pip install -e "./${{ matrix.plugin }}[tests]"
|
||||
|
||||
- name: Run plugin tests
|
||||
timeout-minutes: 15
|
||||
working-directory: ${{ matrix.plugin }}
|
||||
run: pytest -v -n=auto
|
||||
@@ -2,59 +2,28 @@ name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
branches: [main, 2.x]
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '5 1 * * *' # every day at 01:05
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
DVC_TEST: "true"
|
||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
||||
SHELL: /bin/bash
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: # added using https://github.com/step-security/secure-workflows
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.8
|
||||
cache: pip
|
||||
cache-dependency-path: pyproject.toml
|
||||
- name: Install requirements
|
||||
run: |
|
||||
pip install --upgrade pip wheel
|
||||
pip install ".[dev]"
|
||||
- uses: pre-commit/[email protected]
|
||||
tests:
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 50
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, windows-latest]
|
||||
os: [ubuntu-20.04, windows-latest, macos-latest]
|
||||
pyv: ["3.8", "3.9", "3.10", "3.11"]
|
||||
pytest-filter:
|
||||
- "import or plot or live or experiment"
|
||||
- "not (import or plot or live or experiment)"
|
||||
include:
|
||||
- {os: macos-latest, pyv: "3.8"}
|
||||
- {os: macos-latest, pyv: "3.9"}
|
||||
- {os: macos-latest, pyv: "3.10"}
|
||||
- {os: macos-latest, pyv: "3.11"}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -70,9 +39,14 @@ jobs:
|
||||
run: |
|
||||
pip install --upgrade pip wheel
|
||||
pip install -e ".[dev]"
|
||||
- uses: pre-commit/[email protected]
|
||||
- name: run tests
|
||||
timeout-minutes: 20
|
||||
run: pytest -nauto --durations 100 --cov --cov-report=xml --cov-report=term -k "${{ matrix.pytest-filter }}"
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
PYTHONUTF8: 1
|
||||
run: >
|
||||
pytest -n=logical --timeout=300 --durations=100
|
||||
--cov --cov-report=xml --cov-report=term
|
||||
- name: upload coverage report
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
@@ -80,7 +54,7 @@ jobs:
|
||||
fail_ci_if_error: false
|
||||
check:
|
||||
if: always()
|
||||
needs: [lint, tests]
|
||||
needs: [tests]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: re-actors/alls-green@release/v1
|
||||
@@ -88,7 +62,7 @@ jobs:
|
||||
jobs: ${{ toJSON(needs) }}
|
||||
notify:
|
||||
if: github.ref == 'refs/heads/main' && failure()
|
||||
needs: [lint, tests]
|
||||
needs: [tests]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Slack Notification
|
||||
|
||||
+21
-7
@@ -6,7 +6,7 @@ ci:
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.3.0
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-case-conflict
|
||||
@@ -29,34 +29,45 @@ repos:
|
||||
- id: beautysh
|
||||
args: ["-i", "2"]
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.2.0
|
||||
rev: v3.3.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args:
|
||||
- --py38-plus
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.10.0
|
||||
rev: 23.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.10.1
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: 'v0.0.261'
|
||||
hooks:
|
||||
- id: ruff
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.2
|
||||
rev: v2.2.4
|
||||
hooks:
|
||||
- id: codespell
|
||||
additional_dependencies: ["tomli"]
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 5.0.4
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
- flake8-broken-line
|
||||
- flake8-bugbear
|
||||
- flake8-comprehensions
|
||||
- flake8-debugger
|
||||
- flake8-string-format
|
||||
- flake8-typing-imports
|
||||
- repo: https://github.com/PyCQA/bandit
|
||||
rev: '1.7.5'
|
||||
hooks:
|
||||
- id: bandit
|
||||
args: ["-c", "pyproject.toml", "--quiet", "--format=custom"]
|
||||
files: ^dvc/
|
||||
additional_dependencies: [".[toml]"]
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: mypy
|
||||
@@ -65,11 +76,14 @@ repos:
|
||||
files: ^dvc/
|
||||
language: system
|
||||
types: [python]
|
||||
require_serial: true
|
||||
- id: pylint
|
||||
name: pylint
|
||||
entry: pylint
|
||||
files: ^dvc/
|
||||
language: system
|
||||
types: [python]
|
||||
require_serial: true
|
||||
- hooks:
|
||||
- args:
|
||||
- git-hook
|
||||
|
||||
@@ -4,6 +4,7 @@ DVC
|
||||
Make your data science projects reproducible and shareable.
|
||||
"""
|
||||
import dvc.logger
|
||||
from dvc.build import PKG # noqa: F401
|
||||
from dvc.version import __version__, version_tuple # noqa: F401
|
||||
|
||||
dvc.logger.setup()
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import os
|
||||
|
||||
|
||||
def get_hook_dirs():
|
||||
return [os.path.dirname(__file__)]
|
||||
@@ -0,0 +1,2 @@
|
||||
# ruff: noqa: N999
|
||||
hiddenimports = ["win32timezone"]
|
||||
@@ -1,3 +1,4 @@
|
||||
# ruff: noqa: N999
|
||||
# pylint: disable=import-error
|
||||
from PyInstaller.utils.hooks import collect_submodules, is_module_or_submodule
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from PyInstaller.utils.hooks import ( # pylint:disable=import-error
|
||||
copy_metadata,
|
||||
)
|
||||
# ruff: noqa: N999
|
||||
from PyInstaller.utils.hooks import copy_metadata # pylint:disable=import-error
|
||||
|
||||
# needed for `dvc doctor` to show dep versions
|
||||
datas = copy_metadata("adlfs", recursive=True)
|
||||
@@ -1,3 +1,4 @@
|
||||
# ruff: noqa: N999
|
||||
import os
|
||||
|
||||
if os.name == "nt":
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
from PyInstaller.utils.hooks import ( # pylint:disable=import-error
|
||||
copy_metadata,
|
||||
)
|
||||
# ruff: noqa: N999
|
||||
from PyInstaller.utils.hooks import copy_metadata # pylint:disable=import-error
|
||||
|
||||
datas = copy_metadata("google-cloud-storage")
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# ruff: noqa: N999
|
||||
from PyInstaller.utils.hooks import copy_metadata # pylint:disable=import-error
|
||||
|
||||
datas = copy_metadata("flatten-dict")
|
||||
@@ -1,3 +1,4 @@
|
||||
# ruff: noqa: N999
|
||||
# pylint: disable=import-error
|
||||
from PyInstaller.utils.hooks import collect_submodules
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
# ruff: noqa: N999
|
||||
hiddenimports = ["fsspec.implementations.memory"]
|
||||
@@ -0,0 +1,5 @@
|
||||
# ruff: noqa: N999
|
||||
from PyInstaller.utils.hooks import copy_metadata # pylint:disable=import-error
|
||||
|
||||
datas = copy_metadata("google-compute-engine")
|
||||
hiddenimports = ["google_cloud_engine"]
|
||||
@@ -0,0 +1,4 @@
|
||||
# ruff: noqa: N999
|
||||
from PyInstaller.utils.hooks import copy_metadata # pylint:disable=import-error
|
||||
|
||||
datas = copy_metadata("pydrive2")
|
||||
+34
-25
@@ -8,15 +8,19 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
@contextmanager
|
||||
def viztracer_profile(path: Union[Callable[[], str], str], depth: int = -1):
|
||||
def viztracer_profile(
|
||||
path: Union[Callable[[], str], str],
|
||||
depth: int = -1,
|
||||
log_async: bool = False,
|
||||
):
|
||||
try:
|
||||
import viztracer # pylint: disable=import-error
|
||||
except ImportError:
|
||||
print("Failed to run profiler, viztracer is not installed")
|
||||
print("Failed to run profiler, viztracer is not installed") # noqa: T201
|
||||
yield
|
||||
return
|
||||
|
||||
tracer = viztracer.VizTracer(max_stack_depth=depth)
|
||||
tracer = viztracer.VizTracer(max_stack_depth=depth, log_async=log_async)
|
||||
|
||||
tracer.start()
|
||||
yield
|
||||
@@ -27,14 +31,14 @@ def viztracer_profile(path: Union[Callable[[], str], str], depth: int = -1):
|
||||
|
||||
@contextmanager
|
||||
def yappi_profile(
|
||||
path: Union[Callable[[], str], str] = None,
|
||||
path: Optional[Union[Callable[[], str], str]] = None,
|
||||
wall_clock: Optional[bool] = True,
|
||||
separate_threads: Optional[bool] = False,
|
||||
):
|
||||
try:
|
||||
import yappi # pylint: disable=import-error
|
||||
except ImportError:
|
||||
print("Failed to run profiler, yappi is not installed")
|
||||
print("Failed to run profiler, yappi is not installed") # noqa: T201
|
||||
yield
|
||||
return
|
||||
|
||||
@@ -60,7 +64,7 @@ def yappi_profile(
|
||||
st.save(out, type="callgrind")
|
||||
else:
|
||||
if ctx_id is not None:
|
||||
print(f"\nThread {ctx_id}")
|
||||
print(f"\nThread {ctx_id}") # noqa: T201
|
||||
st.print_all() # pylint:disable=no-member
|
||||
if ctx_id is None:
|
||||
threads.print_all() # pylint:disable=no-member
|
||||
@@ -74,7 +78,7 @@ def instrument(html_output=False):
|
||||
try:
|
||||
from pyinstrument import Profiler # pylint: disable=import-error
|
||||
except ImportError:
|
||||
print("Failed to run profiler, pyinstrument is not installed")
|
||||
print("Failed to run profiler, pyinstrument is not installed") # noqa: T201
|
||||
yield
|
||||
return
|
||||
|
||||
@@ -87,11 +91,11 @@ def instrument(html_output=False):
|
||||
if html_output:
|
||||
profiler.open_in_browser()
|
||||
return
|
||||
print(profiler.output_text(unicode=True, color=True))
|
||||
print(profiler.output_text(unicode=True, color=True)) # noqa: T201
|
||||
|
||||
|
||||
@contextmanager
|
||||
def profile(dump_path: str = None):
|
||||
def profile(dump_path: Optional[str] = None):
|
||||
"""Run a cprofile"""
|
||||
import cProfile
|
||||
|
||||
@@ -115,28 +119,31 @@ def debug():
|
||||
try:
|
||||
import ipdb as pdb # noqa: T100, pylint: disable=import-error
|
||||
except ImportError:
|
||||
import pdb # noqa: T100
|
||||
import pdb # type: ignore[no-redef] # noqa: T100
|
||||
pdb.post_mortem()
|
||||
|
||||
raise # prevent from jumping ahead
|
||||
|
||||
|
||||
def _sigshow(_, frame: Optional["FrameType"]) -> None:
|
||||
import sys
|
||||
from shutil import get_terminal_size
|
||||
from traceback import format_stack
|
||||
|
||||
from dvc.ui import ui
|
||||
|
||||
lines = "\u2015" * get_terminal_size().columns
|
||||
ui.error_write(lines, "\n", *format_stack(frame), lines, sep="")
|
||||
stack = format_stack(frame)
|
||||
print(lines, "\n", *stack, lines, sep="", file=sys.stderr) # noqa: T201
|
||||
|
||||
|
||||
@contextmanager
|
||||
def show_stack():
|
||||
r"""Show stack trace on SIGQUIT (Ctrl-\) or SIGINFO (Ctrl-T on macOS)."""
|
||||
import signal
|
||||
import sys
|
||||
|
||||
if sys.platform != "win32":
|
||||
signal.signal(signal.SIGQUIT, _sigshow)
|
||||
|
||||
signal.signal(signal.SIGQUIT, _sigshow)
|
||||
try:
|
||||
signal.signal(signal.SIGINFO, _sigshow) # only available on macOS
|
||||
except AttributeError:
|
||||
@@ -154,7 +161,7 @@ def _get_path_func(tool: str, ext: str):
|
||||
|
||||
|
||||
@contextmanager
|
||||
def debugtools(args: "Namespace" = None, **kwargs):
|
||||
def debugtools(args: Optional["Namespace"] = None, **kwargs):
|
||||
kw = vars(args) if args else {}
|
||||
kw.update(kwargs)
|
||||
|
||||
@@ -175,10 +182,15 @@ def debugtools(args: "Namespace" = None, **kwargs):
|
||||
separate_threads=kw.get("yappi_separate_threads"),
|
||||
)
|
||||
)
|
||||
if kw.get("viztracer") or kw.get("viztracer_depth"):
|
||||
if (
|
||||
kw.get("viztracer")
|
||||
or kw.get("viztracer_depth")
|
||||
or kw.get("viztracer_async")
|
||||
):
|
||||
path_func = _get_path_func("viztracer", "json")
|
||||
depth = kw.get("viztracer_depth") or -1
|
||||
prof = viztracer_profile(path=path_func, depth=depth)
|
||||
log_async = kw.get("viztracer_async") or False
|
||||
prof = viztracer_profile(path=path_func, depth=depth, log_async=log_async)
|
||||
stack.enter_context(prof)
|
||||
yield
|
||||
|
||||
@@ -186,12 +198,8 @@ def debugtools(args: "Namespace" = None, **kwargs):
|
||||
def add_debugging_flags(parser):
|
||||
from argparse import SUPPRESS
|
||||
|
||||
parser.add_argument(
|
||||
"--cprofile", action="store_true", default=False, help=SUPPRESS
|
||||
)
|
||||
parser.add_argument(
|
||||
"--yappi", action="store_true", default=False, help=SUPPRESS
|
||||
)
|
||||
parser.add_argument("--cprofile", action="store_true", default=False, help=SUPPRESS)
|
||||
parser.add_argument("--yappi", action="store_true", default=False, help=SUPPRESS)
|
||||
parser.add_argument(
|
||||
"--yappi-separate-threads",
|
||||
action="store_true",
|
||||
@@ -202,10 +210,11 @@ def add_debugging_flags(parser):
|
||||
"--viztracer", action="store_true", default=False, help=SUPPRESS
|
||||
)
|
||||
parser.add_argument("--viztracer-depth", type=int, help=SUPPRESS)
|
||||
parser.add_argument("--cprofile-dump", help=SUPPRESS)
|
||||
parser.add_argument(
|
||||
"--pdb", action="store_true", default=False, help=SUPPRESS
|
||||
"--viztracer-async", action="store_true", default=False, help=SUPPRESS
|
||||
)
|
||||
parser.add_argument("--cprofile-dump", help=SUPPRESS)
|
||||
parser.add_argument("--pdb", action="store_true", default=False, help=SUPPRESS)
|
||||
parser.add_argument(
|
||||
"--instrument", action="store_true", default=False, help=SUPPRESS
|
||||
)
|
||||
|
||||
+12
-6
@@ -47,7 +47,7 @@ def is_enabled():
|
||||
enabled = not os.getenv(DVC_NO_ANALYTICS)
|
||||
if enabled:
|
||||
enabled = to_bool(
|
||||
Config(validate=False).get("core", {}).get("analytics", "true")
|
||||
Config.from_cwd(validate=False).get("core", {}).get("analytics", "true")
|
||||
)
|
||||
|
||||
logger.debug("Analytics is %sabled.", "en" if enabled else "dis")
|
||||
@@ -82,10 +82,9 @@ def send(path):
|
||||
|
||||
|
||||
def _scm_in_use():
|
||||
from scmrepo.noscm import NoSCM
|
||||
|
||||
from dvc.exceptions import NotDvcRepoError
|
||||
from dvc.repo import Repo
|
||||
from dvc.scm import NoSCM
|
||||
|
||||
from .scm import SCM, SCMError
|
||||
|
||||
@@ -102,17 +101,24 @@ def _runtime_info():
|
||||
"""
|
||||
Gather information from the environment where DVC runs to fill a report.
|
||||
"""
|
||||
from iterative_telemetry import find_or_create_user_id
|
||||
from iterative_telemetry import _generate_ci_id, find_or_create_user_id
|
||||
|
||||
from dvc import __version__
|
||||
from dvc.utils import is_binary
|
||||
|
||||
ci_id = _generate_ci_id()
|
||||
if ci_id:
|
||||
group_id, user_id = ci_id
|
||||
else:
|
||||
group_id, user_id = None, find_or_create_user_id()
|
||||
|
||||
return {
|
||||
"dvc_version": __version__,
|
||||
"is_binary": is_binary(),
|
||||
"scm_class": _scm_in_use(),
|
||||
"system_info": _system_info(),
|
||||
"user_id": find_or_create_user_id(),
|
||||
"user_id": user_id,
|
||||
"group_id": group_id,
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +131,7 @@ def _system_info():
|
||||
system = platform.system()
|
||||
|
||||
if system == "Windows":
|
||||
version = sys.getwindowsversion()
|
||||
version = sys.getwindowsversion() # type: ignore[attr-defined]
|
||||
|
||||
return {
|
||||
"os": "windows",
|
||||
|
||||
+23
-6
@@ -2,6 +2,7 @@ from dataclasses import asdict, dataclass, field, fields
|
||||
from typing import Any, ClassVar, Dict, List, Optional
|
||||
|
||||
from funcy import compact
|
||||
from voluptuous import Required
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -12,15 +13,27 @@ class Annotation:
|
||||
PARAM_META: ClassVar[str] = "meta"
|
||||
|
||||
desc: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
type: Optional[str] = None # noqa: A003
|
||||
labels: List[str] = field(default_factory=list)
|
||||
meta: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def update(self, **kwargs) -> "Annotation":
|
||||
for attr, value in kwargs.items():
|
||||
if value and hasattr(self, attr):
|
||||
setattr(self, attr, value)
|
||||
return self
|
||||
def to_dict(self) -> Dict[str, str]:
|
||||
return compact(asdict(self))
|
||||
|
||||
|
||||
@dataclass
|
||||
class Artifact:
|
||||
PARAM_PATH: ClassVar[str] = "path"
|
||||
PARAM_DESC: ClassVar[str] = "desc"
|
||||
PARAM_TYPE: ClassVar[str] = "type"
|
||||
PARAM_LABELS: ClassVar[str] = "labels"
|
||||
PARAM_META: ClassVar[str] = "meta"
|
||||
|
||||
path: str
|
||||
desc: Optional[str] = None
|
||||
type: Optional[str] = None # noqa: A003
|
||||
labels: List[str] = field(default_factory=list)
|
||||
meta: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> Dict[str, str]:
|
||||
return compact(asdict(self))
|
||||
@@ -33,3 +46,7 @@ ANNOTATION_SCHEMA = {
|
||||
Annotation.PARAM_LABELS: [str],
|
||||
Annotation.PARAM_META: object,
|
||||
}
|
||||
ARTIFACT_SCHEMA = {
|
||||
Required(Artifact.PARAM_PATH): str,
|
||||
**ANNOTATION_SCHEMA, # type: ignore[arg-type]
|
||||
}
|
||||
|
||||
+9
-3
@@ -2,14 +2,20 @@ from dvc.fs.dvc import _DVCFileSystem as DVCFileSystem
|
||||
|
||||
from .data import open # pylint: disable=redefined-builtin
|
||||
from .data import get_url, read
|
||||
from .experiments import make_checkpoint
|
||||
from .params import params_show
|
||||
from .experiments import exp_save, exp_show
|
||||
from .scm import all_branches, all_commits, all_tags
|
||||
from .show import metrics_show, params_show
|
||||
|
||||
__all__ = [
|
||||
"all_branches",
|
||||
"all_commits",
|
||||
"all_tags",
|
||||
"exp_save",
|
||||
"exp_show",
|
||||
"get_url",
|
||||
"make_checkpoint",
|
||||
"open",
|
||||
"params_show",
|
||||
"metrics_show",
|
||||
"read",
|
||||
"DVCFileSystem",
|
||||
]
|
||||
|
||||
+95
-26
@@ -1,12 +1,30 @@
|
||||
from contextlib import _GeneratorContextManager as GCM
|
||||
from typing import Optional
|
||||
from contextlib import contextmanager
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from funcy import reraise
|
||||
|
||||
from dvc.exceptions import OutputNotFoundError, PathMissingError
|
||||
from dvc.exceptions import FileMissingError, OutputNotFoundError, PathMissingError
|
||||
from dvc.repo import Repo
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _wrap_exceptions(repo, url):
|
||||
from dvc.config import NoRemoteError
|
||||
from dvc.exceptions import NoOutputInExternalRepoError, NoRemoteInExternalRepoError
|
||||
|
||||
try:
|
||||
yield
|
||||
except NoRemoteError as exc:
|
||||
raise NoRemoteInExternalRepoError(url) from exc
|
||||
except OutputNotFoundError as exc:
|
||||
if exc.repo is repo:
|
||||
raise NoOutputInExternalRepoError(exc.output, repo.root_dir, url) from exc
|
||||
raise
|
||||
except FileMissingError as exc:
|
||||
raise PathMissingError(exc.path, url) from exc
|
||||
|
||||
|
||||
def get_url(path, repo=None, rev=None, remote=None):
|
||||
"""
|
||||
Returns the URL to the storage location of a data file or directory tracked
|
||||
@@ -19,31 +37,33 @@ def get_url(path, repo=None, rev=None, remote=None):
|
||||
directory in the remote storage.
|
||||
"""
|
||||
with Repo.open(repo, rev=rev, subrepos=True, uninitialized=True) as _repo:
|
||||
fs_path = _repo.dvcfs.from_os_path(path)
|
||||
with reraise(FileNotFoundError, PathMissingError(path, repo)):
|
||||
info = _repo.dvcfs.info(fs_path)
|
||||
with _wrap_exceptions(_repo, path):
|
||||
fs_path = _repo.dvcfs.from_os_path(path)
|
||||
|
||||
dvc_info = info.get("dvc_info")
|
||||
if not dvc_info:
|
||||
raise OutputNotFoundError(path, repo)
|
||||
with reraise(FileNotFoundError, PathMissingError(path, repo)):
|
||||
info = _repo.dvcfs.info(fs_path)
|
||||
|
||||
dvc_repo = info["repo"]
|
||||
md5 = dvc_info["md5"]
|
||||
dvc_info = info.get("dvc_info")
|
||||
if not dvc_info:
|
||||
raise OutputNotFoundError(path, repo)
|
||||
|
||||
return dvc_repo.cloud.get_url_for(remote, checksum=md5)
|
||||
dvc_repo = info["repo"] # pylint: disable=unsubscriptable-object
|
||||
md5 = dvc_info["md5"]
|
||||
|
||||
return dvc_repo.cloud.get_url_for(remote, checksum=md5)
|
||||
|
||||
|
||||
class _OpenContextManager(GCM):
|
||||
def __init__(
|
||||
self, func, args, kwds
|
||||
): # pylint: disable=super-init-not-called
|
||||
def __init__(self, func, args, kwds): # pylint: disable=super-init-not-called
|
||||
self.gen = func(*args, **kwds)
|
||||
self.func, self.args, self.kwds = func, args, kwds
|
||||
self.func, self.args, self.kwds = ( # type: ignore[assignment]
|
||||
func,
|
||||
args,
|
||||
kwds,
|
||||
)
|
||||
|
||||
def __getattr__(self, name):
|
||||
raise AttributeError(
|
||||
"dvc.api.open() should be used in a with statement."
|
||||
)
|
||||
raise AttributeError("dvc.api.open() should be used in a with statement.")
|
||||
|
||||
|
||||
def open( # noqa, pylint: disable=redefined-builtin
|
||||
@@ -53,6 +73,7 @@ def open( # noqa, pylint: disable=redefined-builtin
|
||||
remote: Optional[str] = None,
|
||||
mode: str = "r",
|
||||
encoding: Optional[str] = None,
|
||||
config: Optional[Dict[str, Any]] = None,
|
||||
):
|
||||
"""
|
||||
Opens a file tracked in a DVC project.
|
||||
@@ -94,6 +115,8 @@ def open( # noqa, pylint: disable=redefined-builtin
|
||||
Defaults to None.
|
||||
This should only be used in text mode.
|
||||
Mirrors the namesake parameter in builtin `open()`_.
|
||||
config(dict, optional): config to be passed to the DVC repository.
|
||||
Defaults to None.
|
||||
|
||||
Returns:
|
||||
_OpenContextManager: A context manager that generatse a corresponding
|
||||
@@ -189,25 +212,71 @@ def open( # noqa, pylint: disable=redefined-builtin
|
||||
"rev": rev,
|
||||
"mode": mode,
|
||||
"encoding": encoding,
|
||||
"config": config,
|
||||
}
|
||||
return _OpenContextManager(_open, args, kwargs)
|
||||
|
||||
|
||||
def _open(path, repo=None, rev=None, remote=None, mode="r", encoding=None):
|
||||
with Repo.open(repo, rev=rev, subrepos=True, uninitialized=True) as _repo:
|
||||
with _repo.open_by_relpath(
|
||||
path, remote=remote, mode=mode, encoding=encoding
|
||||
) as fd:
|
||||
yield fd
|
||||
def _open(path, repo=None, rev=None, remote=None, mode="r", encoding=None, config=None):
|
||||
if remote:
|
||||
if config is not None:
|
||||
raise ValueError(
|
||||
"can't specify both `remote` and `config` at the same time"
|
||||
)
|
||||
config = {"core": {"remote": remote}}
|
||||
|
||||
repo_kwargs: Dict[str, Any] = {
|
||||
"subrepos": True,
|
||||
"uninitialized": True,
|
||||
"config": config,
|
||||
}
|
||||
|
||||
with Repo.open(repo, rev=rev, **repo_kwargs) as _repo:
|
||||
with _wrap_exceptions(_repo, path):
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
from dvc.exceptions import IsADirectoryError as DvcIsADirectoryError
|
||||
from dvc.fs.data import DataFileSystem
|
||||
from dvc.fs.dvc import DVCFileSystem
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dvc.fs import FileSystem
|
||||
|
||||
fs: Union["FileSystem", DataFileSystem, DVCFileSystem]
|
||||
if os.path.isabs(path):
|
||||
fs = DataFileSystem(index=_repo.index.data["local"])
|
||||
fs_path = path
|
||||
else:
|
||||
fs = DVCFileSystem(repo=_repo, subrepos=True)
|
||||
fs_path = fs.from_os_path(path)
|
||||
|
||||
try:
|
||||
with fs.open(
|
||||
fs_path,
|
||||
mode=mode,
|
||||
encoding=encoding,
|
||||
) as fobj:
|
||||
yield fobj
|
||||
except FileNotFoundError as exc:
|
||||
raise FileMissingError(path) from exc
|
||||
except IsADirectoryError as exc:
|
||||
raise DvcIsADirectoryError(f"'{path}' is a directory") from exc
|
||||
|
||||
|
||||
def read(path, repo=None, rev=None, remote=None, mode="r", encoding=None):
|
||||
def read(path, repo=None, rev=None, remote=None, mode="r", encoding=None, config=None):
|
||||
"""
|
||||
Returns the contents of a tracked file (by DVC or Git). For Git repos, HEAD
|
||||
is used unless a rev argument is supplied. The default remote is tried
|
||||
unless a remote argument is supplied.
|
||||
"""
|
||||
with open(
|
||||
path, repo=repo, rev=rev, remote=remote, mode=mode, encoding=encoding
|
||||
path,
|
||||
repo=repo,
|
||||
rev=rev,
|
||||
remote=remote,
|
||||
mode=mode,
|
||||
encoding=encoding,
|
||||
config=config,
|
||||
) as fd:
|
||||
return fd.read()
|
||||
|
||||
+111
-24
@@ -1,33 +1,120 @@
|
||||
import builtins
|
||||
import os
|
||||
from time import sleep
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
from rich.text import Text
|
||||
|
||||
from dvc.env import DVC_CHECKPOINT, DVC_ROOT
|
||||
from dvc.repo import Repo
|
||||
from dvc.stage.monitor import CheckpointTask
|
||||
from dvc.repo.experiments.show import tabulate
|
||||
|
||||
|
||||
def make_checkpoint():
|
||||
def exp_save(
|
||||
name: Optional[str] = None,
|
||||
force: bool = False,
|
||||
include_untracked: Optional[List[str]] = None,
|
||||
):
|
||||
"""
|
||||
Signal DVC to create a checkpoint experiment.
|
||||
Create a new DVC experiment using `exp save`.
|
||||
|
||||
If the current process is being run from DVC, this function will block
|
||||
until DVC has finished creating the checkpoint. Otherwise, this function
|
||||
will return immediately.
|
||||
See https://dvc.org/doc/command-reference/exp/save.
|
||||
|
||||
Args:
|
||||
name (str, optional): specify a name for this experiment.
|
||||
If `None`, a default one will be generated, such as `urban-sign`.
|
||||
Defaults to `None`.
|
||||
force (bool): overwrite the experiment if an experiment with the same
|
||||
name already exists.
|
||||
Defaults to `False`.
|
||||
include_untracked (List[str], optional): specify untracked file(s) to
|
||||
be included in the saved experiment.
|
||||
Defaults to `None`.
|
||||
|
||||
Returns:
|
||||
str: The `Git revision`_ of the created experiment.
|
||||
|
||||
Raises:
|
||||
ExperimentExistsError: If an experiment with `name` already exists and
|
||||
`force=False`.
|
||||
|
||||
.. _Git revision:
|
||||
https://git-scm.com/docs/revisions
|
||||
"""
|
||||
if os.getenv(DVC_CHECKPOINT) is None:
|
||||
return
|
||||
with Repo() as repo:
|
||||
return repo.experiments.save(
|
||||
name=name, force=force, include_untracked=include_untracked
|
||||
)
|
||||
|
||||
root_dir = os.getenv(DVC_ROOT, Repo.find_root())
|
||||
signal_file = os.path.join(
|
||||
root_dir, Repo.DVC_DIR, "tmp", CheckpointTask.SIGNAL_FILE
|
||||
)
|
||||
|
||||
with builtins.open(signal_file, "w", encoding="utf-8") as fobj:
|
||||
# NOTE: force flushing/writing empty file to disk, otherwise when
|
||||
# run in certain contexts (pytest) file may not actually be written
|
||||
fobj.write("")
|
||||
fobj.flush()
|
||||
os.fsync(fobj.fileno())
|
||||
while os.path.exists(signal_file):
|
||||
sleep(0.1)
|
||||
def _postprocess(exp_rows):
|
||||
for exp_row in exp_rows:
|
||||
for k, v in exp_row.items():
|
||||
if isinstance(v, Text):
|
||||
v_str = str(v)
|
||||
try:
|
||||
exp_row[k] = float(v_str)
|
||||
except ValueError:
|
||||
exp_row[k] = v_str
|
||||
|
||||
if not exp_row[k]:
|
||||
exp_row[k] = None
|
||||
|
||||
return exp_rows
|
||||
|
||||
|
||||
def exp_show(
|
||||
repo: Optional[str] = None,
|
||||
revs: Optional[Union[str, List[str]]] = None,
|
||||
num: int = 1,
|
||||
param_deps: bool = False,
|
||||
force: bool = False,
|
||||
config: Optional[Dict] = None,
|
||||
) -> List[Dict]:
|
||||
"""Get DVC experiments tracked in `repo`.
|
||||
|
||||
Without arguments, this function will retrieve all experiments derived from
|
||||
the Git `HEAD`.
|
||||
|
||||
See the options below to customize the experiments retrieved.
|
||||
|
||||
Args:
|
||||
repo (str, optional): location of the DVC repository.
|
||||
Defaults to the current project (found by walking up from the
|
||||
current working directory tree).
|
||||
It can be a URL or a file system path.
|
||||
Both HTTP and SSH protocols are supported for online Git repos
|
||||
(e.g. [user@]server:project.git).
|
||||
revs (Union[str, List[str]], optional): Git revision(s) (e.g. branch,
|
||||
tag, SHA commit) to use as a reference point to start listing
|
||||
experiments.
|
||||
Defaults to `None`, which will use `HEAD` as starting point.
|
||||
num (int, optional): show experiments from the last `num` commits
|
||||
(first parents) starting from the `revs` baseline.
|
||||
Give a negative value to include all first-parent commits (similar
|
||||
to `git log -n`).
|
||||
Defaults to 1.
|
||||
param_deps (bool, optional): include only parameters that are stage
|
||||
dependencies.
|
||||
Defaults to `False`.
|
||||
force (bool, optional): force re-collection of experiments instead of
|
||||
loading from internal experiments cache.
|
||||
DVC caches `exp_show` data for completed experiments to improve
|
||||
performance of subsequent calls.
|
||||
When `force` is specified, DVC will reload all experiment data and
|
||||
ignore any previously cached results.
|
||||
Defaults to `False`.
|
||||
config (dict, optional): config to be passed through to DVC project.
|
||||
Defaults to `None`.
|
||||
|
||||
Returns:
|
||||
List[Dict]: Each item in the list will contain a dictionary with
|
||||
the info for an individual experiment.
|
||||
See Examples below.
|
||||
"""
|
||||
with Repo.open(repo, config=config) as _repo:
|
||||
experiments = _repo.experiments.show(
|
||||
revs=revs,
|
||||
num=num,
|
||||
param_deps=param_deps,
|
||||
force=force,
|
||||
)
|
||||
td, _ = tabulate(experiments, fill_value=None)
|
||||
|
||||
return _postprocess(td.as_dict())
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from dvc.repo import Repo
|
||||
|
||||
|
||||
def all_branches(repo: Optional[str] = None) -> List[str]:
|
||||
"""Get all Git branches in a DVC repository.
|
||||
|
||||
Args:
|
||||
repo (str, optional): location of the DVC repository.
|
||||
Defaults to the current project (found by walking up from the
|
||||
current working directory tree).
|
||||
It can be a URL or a file system path.
|
||||
Both HTTP and SSH protocols are supported for online Git repos
|
||||
(e.g. [user@]server:project.git).
|
||||
Returns:
|
||||
List[str]: Names of the Git branches.
|
||||
"""
|
||||
with Repo.open(repo) as _repo:
|
||||
return _repo.scm.list_branches()
|
||||
|
||||
|
||||
def all_commits(repo: Optional[str] = None) -> List[str]:
|
||||
"""Get all Git commits in a DVC repository.
|
||||
|
||||
Args:
|
||||
repo (str, optional): location of the DVC repository.
|
||||
Defaults to the current project (found by walking up from the
|
||||
current working directory tree).
|
||||
It can be a URL or a file system path.
|
||||
Both HTTP and SSH protocols are supported for online Git repos
|
||||
(e.g. [user@]server:project.git).
|
||||
Returns:
|
||||
List[str]: SHAs of the Git commits.
|
||||
"""
|
||||
with Repo.open(repo) as _repo:
|
||||
return _repo.scm.list_all_commits()
|
||||
|
||||
|
||||
def all_tags(repo: Optional[str] = None) -> List[str]:
|
||||
"""Get all Git tags in a DVC repository.
|
||||
|
||||
Args:
|
||||
repo (str, optional): location of the DVC repository.
|
||||
Defaults to the current project (found by walking up from the
|
||||
current working directory tree).
|
||||
It can be a URL or a file system path.
|
||||
Both HTTP and SSH protocols are supported for online Git repos
|
||||
(e.g. [user@]server:project.git).
|
||||
Returns:
|
||||
List[str]: Names of the Git tags.
|
||||
"""
|
||||
with Repo.open(repo) as _repo:
|
||||
return _repo.scm.list_tags()
|
||||
@@ -1,18 +1,166 @@
|
||||
import typing
|
||||
from collections import Counter
|
||||
from typing import Dict, Iterable, Optional, Union
|
||||
|
||||
from funcy import first
|
||||
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.repo import Repo
|
||||
|
||||
|
||||
def _onerror_raise(exception: Exception, *args, **kwargs):
|
||||
raise exception
|
||||
|
||||
|
||||
def _postprocess(results):
|
||||
processed: Dict[str, Dict] = {}
|
||||
for rev, rev_data in results.items():
|
||||
if not rev_data:
|
||||
continue
|
||||
|
||||
processed[rev] = {}
|
||||
|
||||
counts: typing.Counter[str] = Counter()
|
||||
for file_data in rev_data["data"].values():
|
||||
for k in file_data["data"]:
|
||||
counts[k] += 1
|
||||
|
||||
for file_name, file_data in rev_data["data"].items():
|
||||
to_merge = {
|
||||
(k if counts[k] == 1 else f"{file_name}:{k}"): v
|
||||
for k, v in file_data["data"].items()
|
||||
}
|
||||
processed[rev] = {**processed[rev], **to_merge}
|
||||
|
||||
if "workspace" in processed:
|
||||
del processed["workspace"]
|
||||
|
||||
return processed
|
||||
|
||||
|
||||
def metrics_show(
|
||||
*targets: str,
|
||||
repo: Optional[str] = None,
|
||||
rev: Optional[str] = None,
|
||||
config: Optional[Dict] = None,
|
||||
) -> Dict:
|
||||
"""Get metrics tracked in `repo`.
|
||||
|
||||
Without arguments, this function will retrieve all metrics from all tracked
|
||||
metric files, for the current working tree.
|
||||
|
||||
See the options below to restrict the metrics retrieved.
|
||||
|
||||
Args:
|
||||
*targets (str, optional): Names of the metric files to retrieve
|
||||
metrics from. For example, "classifier_eval.json,
|
||||
clustering_eval.json".
|
||||
If no `targets` are provided, all metric files tracked in `dvc.yaml`
|
||||
will be used.
|
||||
Note that targets don't necessarily have to be defined in `dvc.yaml`.
|
||||
repo (str, optional): Location of the DVC repository.
|
||||
Defaults to the current project (found by walking up from the
|
||||
current working directory tree).
|
||||
It can be a URL or a file system path.
|
||||
Both HTTP and SSH protocols are supported for online Git repos
|
||||
(e.g. [user@]server:project.git).
|
||||
rev (str, optional): Name of the `Git revision`_ to retrieve metrics
|
||||
from.
|
||||
Defaults to `None`.
|
||||
An example of git revision can be a branch or tag name, a commit
|
||||
hash or a dvc experiment name.
|
||||
If `repo` is not a Git repo, this option is ignored.
|
||||
If `None`, the current working tree will be used.
|
||||
config (dict, optional): config to be passed through to DVC project.
|
||||
Defaults to `None`.
|
||||
|
||||
Returns:
|
||||
Dict: See Examples below.
|
||||
|
||||
Examples:
|
||||
|
||||
- No arguments.
|
||||
|
||||
Working on https://github.com/iterative/example-get-started
|
||||
|
||||
>>> import dvc.api
|
||||
>>> metrics = dvc.api.metrics_show()
|
||||
>>> print(json.dumps(metrics, indent=4))
|
||||
{
|
||||
"avg_prec": 0.9249974999612706,
|
||||
"roc_auc": 0.9460213440787918
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
- Using `rev`.
|
||||
|
||||
Working on https://github.com/iterative/example-get-started
|
||||
|
||||
>>> import json
|
||||
>>> import dvc.api
|
||||
>>> metrics = dvc.api.metrics_show(rev="tune-hyperparams")
|
||||
>>> print(json.dumps(metrics, indent=4))
|
||||
{
|
||||
"avg_prec": 0.9268792615819422,
|
||||
"roc_auc": 0.945093365854111
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
- Using `targets`.
|
||||
|
||||
Working on https://github.com/iterative/example-get-started
|
||||
|
||||
>>> import json
|
||||
>>> import dvc.api
|
||||
>>> metrics = dvc.api.metrics_show("evaluation.json")
|
||||
>>> print(json.dumps(metrics, indent=4))
|
||||
{
|
||||
"avg_prec": 0.9249974999612706,
|
||||
"roc_auc": 0.9460213440787918
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
- Git URL as `repo`.
|
||||
|
||||
>>> import json
|
||||
>>> import dvc.api
|
||||
>>> metrics = dvc.api.metrics_show(
|
||||
... repo="https://github.com/iterative/demo-fashion-mnist")
|
||||
>>> print(json.dumps(metrics, indent=4))
|
||||
{
|
||||
"loss": 0.25284987688064575,
|
||||
"accuracy": 0.9071000218391418
|
||||
}
|
||||
|
||||
|
||||
.. _Git revision:
|
||||
https://git-scm.com/docs/revisions
|
||||
"""
|
||||
|
||||
with Repo.open(repo, config=config) as _repo:
|
||||
metrics = _repo.metrics.show(
|
||||
targets=targets,
|
||||
revs=rev if rev is None else [rev],
|
||||
onerror=_onerror_raise,
|
||||
)
|
||||
|
||||
metrics = _postprocess(metrics)
|
||||
|
||||
if not metrics:
|
||||
return {}
|
||||
|
||||
return metrics[first(metrics)]
|
||||
|
||||
|
||||
def params_show(
|
||||
*targets: str,
|
||||
repo: Optional[str] = None,
|
||||
stages: Optional[Union[str, Iterable[str]]] = None,
|
||||
rev: Optional[str] = None,
|
||||
deps: bool = False,
|
||||
config: Optional[Dict] = None,
|
||||
) -> Dict:
|
||||
"""Get parameters tracked in `repo`.
|
||||
|
||||
@@ -51,13 +199,12 @@ def params_show(
|
||||
deps (bool, optional): Whether to retrieve only parameters that are
|
||||
stage dependencies or not.
|
||||
Defaults to `False`.
|
||||
config (dict, optional): config to be passed through to DVC project.
|
||||
Defaults to `None`.
|
||||
|
||||
Returns:
|
||||
Dict: See Examples below.
|
||||
|
||||
Raises:
|
||||
DvcException: If no params are found in `repo`.
|
||||
|
||||
Examples:
|
||||
|
||||
- No arguments.
|
||||
@@ -238,35 +385,7 @@ def params_show(
|
||||
if isinstance(stages, str):
|
||||
stages = [stages]
|
||||
|
||||
def _onerror_raise(result: Dict, exception: Exception, *args, **kwargs):
|
||||
raise exception
|
||||
|
||||
def _postprocess(params):
|
||||
processed = {}
|
||||
for rev, rev_data in params.items():
|
||||
processed[rev] = {}
|
||||
|
||||
counts = Counter()
|
||||
for file_data in rev_data["data"].values():
|
||||
for k in file_data["data"]:
|
||||
counts[k] += 1
|
||||
|
||||
for file_name, file_data in rev_data["data"].items():
|
||||
to_merge = {
|
||||
(k if counts[k] == 1 else f"{file_name}:{k}"): v
|
||||
for k, v in file_data["data"].items()
|
||||
}
|
||||
processed[rev] = {**processed[rev], **to_merge}
|
||||
|
||||
if "workspace" in processed:
|
||||
del processed["workspace"]
|
||||
|
||||
if not processed:
|
||||
raise DvcException("No params found")
|
||||
|
||||
return processed[first(processed)]
|
||||
|
||||
with Repo.open(repo) as _repo:
|
||||
with Repo.open(repo, config=config) as _repo:
|
||||
params = _repo.params.show(
|
||||
revs=rev if rev is None else [rev],
|
||||
targets=targets,
|
||||
@@ -275,4 +394,9 @@ def params_show(
|
||||
stages=stages,
|
||||
)
|
||||
|
||||
return _postprocess(params)
|
||||
params = _postprocess(params)
|
||||
|
||||
if not params:
|
||||
return {}
|
||||
|
||||
return params[first(params)]
|
||||
@@ -0,0 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
try:
|
||||
# file is created during dvc build
|
||||
# pylint:disable=unused-import
|
||||
from . import _build # type: ignore[attr-defined, import]
|
||||
|
||||
PKG: Optional[str] = _build.PKG # type: ignore[assignment]
|
||||
except ImportError:
|
||||
PKG = None # type: ignore[assignment]
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Optional, Tuple
|
||||
|
||||
from dvc.fs import GitFileSystem, Schemes
|
||||
from dvc_data.hashfile.db import get_odb
|
||||
from dvc_data.hashfile.hash import DEFAULT_ALGORITHM
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dvc.repo import Repo
|
||||
|
||||
LEGACY_HASH_NAMES = {"md5-dos2unix", "params"}
|
||||
|
||||
|
||||
def _get_odb(
|
||||
repo,
|
||||
settings,
|
||||
fs=None,
|
||||
prefix: Optional[Tuple[str, ...]] = None,
|
||||
hash_name: Optional[str] = None,
|
||||
**kwargs,
|
||||
):
|
||||
from dvc.fs import get_cloud_fs
|
||||
|
||||
if not settings:
|
||||
return None
|
||||
|
||||
cls, config, fs_path = get_cloud_fs(repo, **settings)
|
||||
fs = fs or cls(**config)
|
||||
if prefix:
|
||||
fs_path = fs.path.join(fs_path, *prefix)
|
||||
if hash_name:
|
||||
config["hash_name"] = hash_name
|
||||
return get_odb(fs, fs_path, state=repo.state, **config)
|
||||
|
||||
|
||||
class CacheManager:
|
||||
CACHE_DIR = "cache"
|
||||
FILES_DIR = "files"
|
||||
|
||||
def __init__(self, repo):
|
||||
self._repo = repo
|
||||
self.config = config = repo.config["cache"]
|
||||
self._odb = {}
|
||||
|
||||
default = None
|
||||
if repo and repo.local_dvc_dir:
|
||||
default = os.path.join(repo.local_dvc_dir, self.CACHE_DIR)
|
||||
|
||||
local = config.get("local")
|
||||
|
||||
if local:
|
||||
settings = {"name": local}
|
||||
elif "dir" not in config and not default:
|
||||
settings = None
|
||||
else:
|
||||
from dvc.config_schema import LOCAL_COMMON
|
||||
|
||||
url = config.get("dir") or default
|
||||
settings = {"url": url}
|
||||
for opt in LOCAL_COMMON:
|
||||
if opt in config:
|
||||
settings[str(opt)] = config.get(opt)
|
||||
|
||||
kwargs = {}
|
||||
if not isinstance(repo.fs, GitFileSystem):
|
||||
kwargs["fs"] = repo.fs
|
||||
|
||||
odb = _get_odb(
|
||||
repo,
|
||||
settings,
|
||||
prefix=(self.FILES_DIR, DEFAULT_ALGORITHM),
|
||||
**kwargs,
|
||||
)
|
||||
self._odb["repo"] = odb
|
||||
self._odb[Schemes.LOCAL] = odb
|
||||
legacy_odb = _get_odb(repo, settings, hash_name="md5-dos2unix", **kwargs)
|
||||
self._odb["legacy"] = legacy_odb
|
||||
|
||||
def _init_odb(self, schemes):
|
||||
for scheme in schemes:
|
||||
remote = self.config.get(scheme)
|
||||
settings = {"name": remote} if remote else None
|
||||
self._odb[scheme] = _get_odb(
|
||||
self._repo,
|
||||
settings,
|
||||
prefix=(self.FILES_DIR, DEFAULT_ALGORITHM),
|
||||
)
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return self._odb[name]
|
||||
except KeyError as exc:
|
||||
raise AttributeError from exc
|
||||
|
||||
def by_scheme(self):
|
||||
yield from self._odb.items()
|
||||
|
||||
@property
|
||||
def local_cache_dir(self) -> str:
|
||||
"""Return base local cache directory without any prefixes.
|
||||
|
||||
(i.e. `dvc cache dir`).
|
||||
"""
|
||||
return self.legacy.path
|
||||
|
||||
|
||||
def migrate_2_to_3(repo: "Repo", dry: bool = False):
|
||||
"""Migrate legacy 2.x objects to 3.x cache.
|
||||
|
||||
Legacy 'md5-dos2unix' objects will be re-hashed with 'md5', added to 3.x cache,
|
||||
and then a link from the legacy 2.x location to the 3.x location will be created.
|
||||
"""
|
||||
from dvc.fs.callbacks import TqdmCallback
|
||||
from dvc.ui import ui
|
||||
from dvc_data.hashfile.db.migrate import migrate, prepare
|
||||
|
||||
src = repo.cache.legacy
|
||||
dest = repo.cache.local
|
||||
if dry:
|
||||
oids = list(src._list_oids()) # pylint: disable=protected-access
|
||||
ui.write(
|
||||
f"{len(oids)} files will be re-hashed and migrated to the DVC 3.0 cache "
|
||||
"location."
|
||||
)
|
||||
return
|
||||
|
||||
with TqdmCallback(
|
||||
desc="Computing DVC 3.0 hashes",
|
||||
unit="files",
|
||||
) as cb:
|
||||
migration = prepare(src, dest, callback=cb)
|
||||
|
||||
with TqdmCallback(
|
||||
desc="Migrating to DVC 3.0 cache",
|
||||
unit="files",
|
||||
) as cb:
|
||||
count = migrate(migration, callback=cb)
|
||||
ui.write(f"Migrated {count} files to DVC 3.0 cache location.")
|
||||
+48
-24
@@ -39,13 +39,17 @@ def parse_args(argv=None):
|
||||
|
||||
def _log_unknown_exceptions() -> None:
|
||||
from dvc.info import get_dvc_info
|
||||
from dvc.logger import FOOTER
|
||||
from dvc.ui import ui
|
||||
from dvc.utils import colorize
|
||||
|
||||
logger.exception("unexpected error")
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
logger.debug("Version info for developers:\n%s", get_dvc_info())
|
||||
logger.info(FOOTER)
|
||||
return None
|
||||
|
||||
q = colorize("Having any troubles?", "yellow")
|
||||
link = colorize("https://dvc.org/support", "blue")
|
||||
footer = f"\n{q} Hit us up at {link}, we are always happy to help!"
|
||||
ui.error_write(footer)
|
||||
|
||||
|
||||
def _log_exceptions(exc: Exception) -> Optional[int]:
|
||||
@@ -57,9 +61,11 @@ def _log_exceptions(exc: Exception) -> Optional[int]:
|
||||
|
||||
if exc.errno == errno.EMFILE:
|
||||
logger.exception(
|
||||
"too many open files, please visit "
|
||||
"{} to see how to handle this "
|
||||
"problem".format(error_link("many-files")),
|
||||
(
|
||||
"too many open files, please visit "
|
||||
"%s to see how to handle this problem"
|
||||
),
|
||||
error_link("many-files"),
|
||||
extra={"tb_only": True},
|
||||
)
|
||||
else:
|
||||
@@ -69,7 +75,7 @@ def _log_exceptions(exc: Exception) -> Optional[int]:
|
||||
from dvc.fs import AuthError, ConfigError, RemoteMissingDepsError
|
||||
|
||||
if isinstance(exc, RemoteMissingDepsError):
|
||||
from dvc.utils.pkg import PKG
|
||||
from dvc import PKG
|
||||
|
||||
proto = exc.protocol
|
||||
by_pkg = {
|
||||
@@ -77,11 +83,11 @@ def _log_exceptions(exc: Exception) -> Optional[int]:
|
||||
"conda": f"conda install -c conda-forge dvc-{proto}",
|
||||
}
|
||||
|
||||
cmd = by_pkg.get(PKG)
|
||||
if cmd:
|
||||
if PKG in by_pkg:
|
||||
link = format_link("https://dvc.org/doc/install")
|
||||
cmd = by_pkg.get(PKG)
|
||||
hint = (
|
||||
f"To install dvc with those dependencies, run:\n"
|
||||
"To install dvc with those dependencies, run:\n"
|
||||
"\n"
|
||||
f"\t{cmd}\n"
|
||||
"\n"
|
||||
@@ -92,8 +98,10 @@ def _log_exceptions(exc: Exception) -> Optional[int]:
|
||||
hint = f"\nPlease report this bug to {link}. Thank you!"
|
||||
|
||||
logger.exception(
|
||||
f"URL '{exc.url}' is supported but requires these missing "
|
||||
f"dependencies: {exc.missing_deps}. {hint}",
|
||||
"URL '%s' is supported but requires these missing dependencies: %s. %s",
|
||||
exc.url,
|
||||
exc.missing_deps,
|
||||
hint,
|
||||
extra={"tb_only": True},
|
||||
)
|
||||
return None
|
||||
@@ -102,7 +110,9 @@ def _log_exceptions(exc: Exception) -> Optional[int]:
|
||||
link = format_link("https://man.dvc.org/remote/modify")
|
||||
logger.exception("configuration error")
|
||||
logger.exception(
|
||||
f"{exc!s}\nLearn more about configuration settings at {link}.",
|
||||
"%s\nLearn more about configuration settings at %s.",
|
||||
exc,
|
||||
link,
|
||||
extra={"tb_only": True},
|
||||
)
|
||||
return 251
|
||||
@@ -114,9 +124,14 @@ def _log_exceptions(exc: Exception) -> Optional[int]:
|
||||
|
||||
directory = relpath(exc.directory)
|
||||
logger.exception(
|
||||
f"Could not open pickled '{exc.type}' cache.\n"
|
||||
f"Remove the '{directory}' directory and then retry this command."
|
||||
f"\nSee {error_link('pickle')} for more information.",
|
||||
(
|
||||
"Could not open pickled '%s' cache.\n"
|
||||
"Remove the '%s' directory and then retry this command."
|
||||
"\nSee %s for more information."
|
||||
),
|
||||
exc.type,
|
||||
directory,
|
||||
error_link("pickle"),
|
||||
extra={"tb_only": True},
|
||||
)
|
||||
return None
|
||||
@@ -131,7 +146,7 @@ def _log_exceptions(exc: Exception) -> Optional[int]:
|
||||
return None
|
||||
|
||||
|
||||
def main(argv=None): # noqa: C901
|
||||
def main(argv=None): # noqa: C901, PLR0912, PLR0915
|
||||
"""Main entry point for dvc CLI.
|
||||
|
||||
Args:
|
||||
@@ -143,7 +158,7 @@ def main(argv=None): # noqa: C901
|
||||
from dvc._debug import debugtools
|
||||
from dvc.config import ConfigError
|
||||
from dvc.exceptions import DvcException, NotDvcRepoError
|
||||
from dvc.logger import disable_other_loggers, set_loggers_level
|
||||
from dvc.logger import set_loggers_level
|
||||
|
||||
# NOTE: stderr/stdout may be closed if we are running from dvc.daemon.
|
||||
# On Linux we directly call cli.main after double forking and closing
|
||||
@@ -156,9 +171,8 @@ def main(argv=None): # noqa: C901
|
||||
logging.disable(logging.INFO)
|
||||
|
||||
args = None
|
||||
disable_other_loggers()
|
||||
|
||||
outerLogLevel = logger.level
|
||||
outer_log_level = logger.level
|
||||
try:
|
||||
args = parse_args(argv)
|
||||
|
||||
@@ -168,12 +182,22 @@ def main(argv=None): # noqa: C901
|
||||
elif args.verbose == 1:
|
||||
level = logging.DEBUG
|
||||
elif args.verbose > 1:
|
||||
level = logging.TRACE
|
||||
level = logging.TRACE # type: ignore[attr-defined]
|
||||
|
||||
if level is not None:
|
||||
set_loggers_level(level)
|
||||
|
||||
logger.trace(args)
|
||||
if level and level <= logging.DEBUG:
|
||||
from platform import platform, python_implementation, python_version
|
||||
|
||||
from dvc import PKG, __version__
|
||||
|
||||
pyv = " ".join([python_implementation(), python_version()])
|
||||
pkg = f" ({PKG})" if PKG else ""
|
||||
logger.debug("v%s%s, %s on %s", __version__, pkg, pyv, platform())
|
||||
logger.debug("command: %s", " ".join(argv or sys.argv))
|
||||
|
||||
logger.trace(args) # type: ignore[attr-defined]
|
||||
|
||||
if not sys.stdout.closed and not args.quiet:
|
||||
from dvc.ui import ui
|
||||
@@ -218,9 +242,9 @@ def main(argv=None): # noqa: C901
|
||||
|
||||
return ret
|
||||
finally:
|
||||
logger.setLevel(outerLogLevel)
|
||||
logger.setLevel(outer_log_level)
|
||||
|
||||
from dvc.external_repo import clean_repos
|
||||
from dvc.repo.open_repo import clean_repos
|
||||
|
||||
# Remove cached repos in the end of the call, these are anonymous
|
||||
# so won't be reused by any other subsequent run anyway.
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
from argparse import _AppendAction
|
||||
|
||||
|
||||
class CommaSeparatedArgs(_AppendAction): # pylint: disable=protected-access
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
from funcy import ldistinct
|
||||
|
||||
items = getattr(namespace, self.dest) or []
|
||||
items.extend(map(str.strip, values.split(",")))
|
||||
setattr(namespace, self.dest, ldistinct(items))
|
||||
|
||||
|
||||
class KeyValueArgs(_AppendAction):
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
items = getattr(namespace, self.dest) or {}
|
||||
for value in filter(bool, values):
|
||||
key, _, value = value.partition("=")
|
||||
items[key.strip()] = value
|
||||
|
||||
setattr(namespace, self.dest, items)
|
||||
+6
-2
@@ -1,6 +1,10 @@
|
||||
import logging
|
||||
import os
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dvc.config import Config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -8,13 +12,13 @@ logger = logging.getLogger(__name__)
|
||||
class CmdBase(ABC):
|
||||
UNINITIALIZED = False
|
||||
|
||||
def __init__(self, args):
|
||||
def __init__(self, args: Any):
|
||||
from dvc.repo import Repo
|
||||
|
||||
os.chdir(args.cd)
|
||||
|
||||
self.repo: "Repo" = Repo(uninitialized=self.UNINITIALIZED)
|
||||
self.config = self.repo.config
|
||||
self.config: "Config" = self.repo.config
|
||||
self.args = args
|
||||
|
||||
def do_run(self):
|
||||
|
||||
+5
-18
@@ -2,8 +2,9 @@
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
|
||||
from dvc import __version__
|
||||
from dvc.commands import (
|
||||
add,
|
||||
cache,
|
||||
@@ -40,7 +41,6 @@ from dvc.commands import (
|
||||
remove,
|
||||
repro,
|
||||
root,
|
||||
run,
|
||||
stage,
|
||||
unprotect,
|
||||
update,
|
||||
@@ -61,7 +61,6 @@ COMMANDS = [
|
||||
remove,
|
||||
move,
|
||||
unprotect,
|
||||
run,
|
||||
repro,
|
||||
data_sync,
|
||||
gc,
|
||||
@@ -128,17 +127,6 @@ class DvcParser(argparse.ArgumentParser):
|
||||
return args
|
||||
|
||||
|
||||
class VersionAction(argparse.Action): # pragma: no cover
|
||||
# pylint: disable=too-few-public-methods
|
||||
"""Shows DVC version and exits."""
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
from dvc import __version__
|
||||
|
||||
print(__version__)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def get_parent_parser():
|
||||
"""Create instances of a parser containing common arguments shared among
|
||||
all the commands.
|
||||
@@ -161,6 +149,7 @@ def get_parent_parser():
|
||||
return parent_parser
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def get_main_parser():
|
||||
parent_parser = get_parent_parser()
|
||||
|
||||
@@ -186,13 +175,11 @@ def get_main_parser():
|
||||
help="Show this help message and exit.",
|
||||
)
|
||||
|
||||
# NOTE: On some python versions action='version' prints to stderr
|
||||
# instead of stdout https://bugs.python.org/issue18920
|
||||
parser.add_argument(
|
||||
"-V",
|
||||
"--version",
|
||||
action=VersionAction,
|
||||
nargs=0,
|
||||
action="version",
|
||||
version=__version__,
|
||||
help="Show program's version.",
|
||||
)
|
||||
|
||||
|
||||
+1
-3
@@ -28,7 +28,5 @@ def hide_subparsers_from_help(subparsers):
|
||||
# Need to set `add_help=False`, but avoid setting `help`
|
||||
# (not even to `argparse.SUPPPRESS`).
|
||||
# NOTE: The argument is the parent subparser, not the subcommand parser.
|
||||
cmds = [
|
||||
cmd for cmd, parser in subparsers.choices.items() if parser.add_help
|
||||
]
|
||||
cmds = [cmd for cmd, parser in subparsers.choices.items() if parser.add_help]
|
||||
subparsers.metavar = "{{{}}}".format(",".join(cmds))
|
||||
|
||||
+44
-68
@@ -2,71 +2,61 @@ import argparse
|
||||
import logging
|
||||
|
||||
from dvc.cli import completion
|
||||
from dvc.cli.actions import KeyValueArgs
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.cli.utils import append_doc_link
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _add_annotating_args(parser: argparse.ArgumentParser) -> None:
|
||||
parser.add_argument(
|
||||
"--desc",
|
||||
type=str,
|
||||
metavar="<text>",
|
||||
help="User description of the data.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--meta",
|
||||
metavar="key=value",
|
||||
nargs=1,
|
||||
action=KeyValueArgs,
|
||||
help="Custom metadata to add to the data.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--label",
|
||||
dest="labels",
|
||||
type=str,
|
||||
action="append",
|
||||
metavar="<str>",
|
||||
help="Label for the data.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--type",
|
||||
type=str,
|
||||
metavar="<str>",
|
||||
help="Type of the data.",
|
||||
)
|
||||
|
||||
|
||||
class CmdAdd(CmdBase):
|
||||
def validate_args(self) -> None:
|
||||
from dvc.exceptions import InvalidArgumentError
|
||||
|
||||
args = self.args
|
||||
invalid_opt = None
|
||||
|
||||
if args.to_remote or args.out:
|
||||
message = "{option} can't be used with "
|
||||
message += "--to-remote" if args.to_remote else "--out"
|
||||
if len(args.targets) != 1:
|
||||
invalid_opt = "multiple targets"
|
||||
elif args.glob:
|
||||
invalid_opt = "--glob option"
|
||||
elif args.no_commit:
|
||||
invalid_opt = "--no-commit option"
|
||||
else:
|
||||
message = "{option} can't be used without --to-remote"
|
||||
if args.remote:
|
||||
invalid_opt = "--remote"
|
||||
elif args.remote_jobs:
|
||||
invalid_opt = "--remote-jobs"
|
||||
|
||||
if invalid_opt is not None:
|
||||
raise InvalidArgumentError(message.format(option=invalid_opt))
|
||||
|
||||
def run(self):
|
||||
from dvc.exceptions import (
|
||||
DvcException,
|
||||
RecursiveAddingWhileUsingFilename,
|
||||
)
|
||||
from dvc.exceptions import DvcException, InvalidArgumentError
|
||||
|
||||
try:
|
||||
if len(self.args.targets) > 1 and self.args.file:
|
||||
raise RecursiveAddingWhileUsingFilename()
|
||||
self.validate_args()
|
||||
except InvalidArgumentError:
|
||||
logger.exception("")
|
||||
return 1
|
||||
|
||||
try:
|
||||
self.repo.add(
|
||||
self.args.targets,
|
||||
recursive=self.args.recursive,
|
||||
no_commit=self.args.no_commit,
|
||||
fname=self.args.file,
|
||||
external=self.args.external,
|
||||
glob=self.args.glob,
|
||||
desc=self.args.desc,
|
||||
out=self.args.out,
|
||||
type=self.args.type,
|
||||
labels=self.args.labels,
|
||||
meta=self.args.meta,
|
||||
remote=self.args.remote,
|
||||
to_remote=self.args.to_remote,
|
||||
jobs=self.args.jobs,
|
||||
remote_jobs=self.args.remote_jobs,
|
||||
force=self.args.force,
|
||||
)
|
||||
|
||||
except FileNotFoundError:
|
||||
logger.exception("")
|
||||
return 1
|
||||
except DvcException:
|
||||
logger.exception("")
|
||||
return 1
|
||||
@@ -83,36 +73,18 @@ def add_parser(subparsers, parent_parser):
|
||||
help=ADD_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-R",
|
||||
"--recursive",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Recursively add files under directory targets.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-commit",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Don't put files/directories into cache.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--external",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Allow targets that are outside of the DVC repository.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--glob",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Allows targets containing shell-style wildcards.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--file",
|
||||
help="Specify name of the .dvc file this command will generate.",
|
||||
metavar="<filename>",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--out",
|
||||
@@ -132,8 +104,7 @@ def add_parser(subparsers, parent_parser):
|
||||
metavar="<name>",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-j",
|
||||
"--jobs",
|
||||
"--remote-jobs",
|
||||
type=int,
|
||||
help=(
|
||||
"Only used along with '--to-remote'. "
|
||||
@@ -143,8 +114,13 @@ def add_parser(subparsers, parent_parser):
|
||||
),
|
||||
metavar="<number>",
|
||||
)
|
||||
|
||||
_add_annotating_args(parser)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Override local file or folder if exists.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"targets", nargs="+", help="Input files/directories to add."
|
||||
).complete = completion.FILE
|
||||
|
||||
+47
-9
@@ -1,6 +1,8 @@
|
||||
import argparse
|
||||
import os
|
||||
|
||||
from dvc.cli import completion
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.cli.utils import append_doc_link, fix_subparsers
|
||||
from dvc.commands.config import CmdConfig
|
||||
from dvc.ui import ui
|
||||
@@ -9,13 +11,21 @@ from dvc.ui import ui
|
||||
class CmdCacheDir(CmdConfig):
|
||||
def run(self):
|
||||
if self.args.value is None and not self.args.unset:
|
||||
from dvc.config import ConfigError
|
||||
|
||||
if self.args.level:
|
||||
conf = self.config.read(level=self.args.level)
|
||||
else:
|
||||
# Use merged config with default values
|
||||
conf = self.config
|
||||
self._check(conf, False, "cache", "dir")
|
||||
ui.write(conf["cache"]["dir"])
|
||||
try:
|
||||
self._check(conf, False, "cache", "dir")
|
||||
path = conf["cache"]["dir"]
|
||||
except ConfigError:
|
||||
if not self.config.dvc_dir or self.args.level:
|
||||
raise
|
||||
path = os.path.join(self.config.dvc_dir, "cache")
|
||||
ui.write(path)
|
||||
return 0
|
||||
with self.config.edit(level=self.args.level) as conf:
|
||||
if self.args.unset:
|
||||
@@ -27,6 +37,14 @@ class CmdCacheDir(CmdConfig):
|
||||
return 0
|
||||
|
||||
|
||||
class CmdCacheMigrate(CmdBase):
|
||||
def run(self):
|
||||
from dvc.cachemgr import migrate_2_to_3
|
||||
|
||||
migrate_2_to_3(self.repo, dry=self.args.dry)
|
||||
return 0
|
||||
|
||||
|
||||
def add_parser(subparsers, parent_parser):
|
||||
from dvc.commands.config import parent_config_parser
|
||||
|
||||
@@ -42,11 +60,9 @@ def add_parser(subparsers, parent_parser):
|
||||
|
||||
cache_subparsers = cache_parser.add_subparsers(
|
||||
dest="cmd",
|
||||
help="Use `dvc cache CMD --help` for command-specific " "help.",
|
||||
help="Use `dvc cache CMD --help` for command-specific help.",
|
||||
)
|
||||
|
||||
fix_subparsers(cache_subparsers)
|
||||
|
||||
parent_cache_config_parser = argparse.ArgumentParser(
|
||||
add_help=False, parents=[parent_config_parser]
|
||||
)
|
||||
@@ -68,10 +84,32 @@ def add_parser(subparsers, parent_parser):
|
||||
)
|
||||
cache_dir_parser.add_argument(
|
||||
"value",
|
||||
help="Path to cache directory. Relative paths are resolved relative "
|
||||
"to the current directory and saved to config relative to the "
|
||||
"config file location. If no path is provided, it returns the "
|
||||
"current cache directory.",
|
||||
help=(
|
||||
"Path to cache directory. Relative paths are resolved relative "
|
||||
"to the current directory and saved to config relative to the "
|
||||
"config file location. If no path is provided, it returns the "
|
||||
"current cache directory."
|
||||
),
|
||||
nargs="?",
|
||||
).complete = completion.DIR
|
||||
cache_dir_parser.set_defaults(func=CmdCacheDir)
|
||||
|
||||
CACHE_MIGRATE_HELP = "Migrate cached files to the DVC 3.0 cache location."
|
||||
cache_migrate_parser = cache_subparsers.add_parser(
|
||||
"migrate",
|
||||
parents=[parent_parser],
|
||||
description=append_doc_link(CACHE_HELP, "cache/migrate"),
|
||||
help=CACHE_MIGRATE_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
cache_migrate_parser.add_argument(
|
||||
"--dry",
|
||||
help=(
|
||||
"Only print actions which would be taken without actually migrating "
|
||||
"any data."
|
||||
),
|
||||
action="store_true",
|
||||
)
|
||||
cache_migrate_parser.set_defaults(func=CmdCacheMigrate)
|
||||
|
||||
fix_subparsers(cache_subparsers)
|
||||
|
||||
@@ -39,7 +39,7 @@ class CmdCheckIgnore(CmdBase):
|
||||
target = input()
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
break
|
||||
if target == "":
|
||||
if not target:
|
||||
break
|
||||
if not self._check_one_file(target):
|
||||
ret = 0
|
||||
@@ -62,9 +62,7 @@ class CmdCheckIgnore(CmdBase):
|
||||
raise DvcException("cannot have both `targets` and `--stdin`")
|
||||
|
||||
if self.args.non_matching and not self.args.details:
|
||||
raise DvcException(
|
||||
"`--non-matching` is only valid with `--details`"
|
||||
)
|
||||
raise DvcException("`--non-matching` is only valid with `--details`")
|
||||
|
||||
if self.args.all and not self.args.details:
|
||||
raise DvcException("`--all` is only valid with `--details`")
|
||||
@@ -80,9 +78,7 @@ class CmdCheckIgnore(CmdBase):
|
||||
|
||||
|
||||
def add_parser(subparsers, parent_parser):
|
||||
ADD_HELP = (
|
||||
"Check whether files or directories are excluded due to `.dvcignore`."
|
||||
)
|
||||
ADD_HELP = "Check whether files or directories are excluded due to `.dvcignore`."
|
||||
|
||||
parser = subparsers.add_parser(
|
||||
"check-ignore",
|
||||
@@ -103,16 +99,20 @@ def add_parser(subparsers, parent_parser):
|
||||
"--all",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Include the target paths which don’t match any pattern "
|
||||
"in the `--details` list.",
|
||||
help=(
|
||||
"Include the target paths which don't match any pattern "
|
||||
"in the `--details` list."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"-n",
|
||||
"--non-matching",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Include the target paths which don’t match any pattern "
|
||||
"in the `--details` list.",
|
||||
help=(
|
||||
"Include the target paths which don't match any pattern "
|
||||
"in the `--details` list."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stdin",
|
||||
|
||||
@@ -22,9 +22,7 @@ def log_changes(stats):
|
||||
continue
|
||||
|
||||
for entry in entries:
|
||||
ui.write(
|
||||
f"[{color}]{state[0].upper()}", entry, styled=True, sep="\t"
|
||||
)
|
||||
ui.write(f"[{color}]{state[0].upper()}", entry, styled=True, sep="\t")
|
||||
|
||||
|
||||
class CmdCheckout(CmdBase):
|
||||
@@ -46,9 +44,7 @@ class CmdCheckout(CmdBase):
|
||||
|
||||
if self.args.summary:
|
||||
default_message = "No changes."
|
||||
msg = get_summary(
|
||||
sorted(stats.items(), key=operator.itemgetter(0))
|
||||
)
|
||||
msg = get_summary(sorted(stats.items(), key=operator.itemgetter(0)))
|
||||
ui.write(msg or default_message)
|
||||
else:
|
||||
log_changes(stats)
|
||||
|
||||
@@ -24,11 +24,7 @@ class CmdCommit(CmdBase):
|
||||
force=self.args.force,
|
||||
)
|
||||
except DvcException:
|
||||
logger.exception(
|
||||
"failed to commit{}".format(
|
||||
(" " + target) if target else ""
|
||||
)
|
||||
)
|
||||
logger.exception("failed to commit%s", (" " + target) if target else "")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class CmdCompletion(CmdBaseNoRepo):
|
||||
|
||||
shell = self.args.shell
|
||||
parser = self.args.parser
|
||||
script = shtab.complete(parser, shell=shell, preamble=PREAMBLE)
|
||||
script = shtab.complete(parser, shell=shell, preamble=PREAMBLE) # nosec B604
|
||||
ui.write(script, force=True)
|
||||
return 0
|
||||
|
||||
|
||||
+10
-17
@@ -17,9 +17,7 @@ def _name_type(value):
|
||||
match = re.match(NAME_REGEX, value)
|
||||
if not match:
|
||||
raise argparse.ArgumentTypeError(
|
||||
"name argument should look like "
|
||||
"remote.name.option or "
|
||||
"section.option"
|
||||
"name argument should look like remote.name.option or section.option"
|
||||
)
|
||||
return (
|
||||
bool(match.group("remote")),
|
||||
@@ -34,16 +32,15 @@ class CmdConfig(CmdBaseNoRepo):
|
||||
|
||||
super().__init__(args)
|
||||
|
||||
self.config = Config(validate=False)
|
||||
self.config = Config.from_cwd(validate=False)
|
||||
|
||||
def run(self):
|
||||
if self.args.show_origin:
|
||||
if any((self.args.value, self.args.unset)):
|
||||
logger.error(
|
||||
"--show-origin can't be used together with any of these "
|
||||
"options: -u/--unset, value"
|
||||
)
|
||||
return 1
|
||||
if self.args.show_origin and (self.args.value or self.args.unset):
|
||||
logger.error(
|
||||
"--show-origin can't be used together with any of these "
|
||||
"options: -u/--unset, value"
|
||||
)
|
||||
return 1
|
||||
|
||||
if self.args.list:
|
||||
return self._list()
|
||||
@@ -71,9 +68,7 @@ class CmdConfig(CmdBaseNoRepo):
|
||||
|
||||
for level in levels:
|
||||
conf = self.config.read(level)
|
||||
prefix = self._config_file_prefix(
|
||||
self.args.show_origin, self.config, level
|
||||
)
|
||||
prefix = self._config_file_prefix(self.args.show_origin, self.config, level)
|
||||
configs = list(self._format_config(conf, prefix))
|
||||
if configs:
|
||||
ui.write("\n".join(configs))
|
||||
@@ -132,9 +127,7 @@ class CmdConfig(CmdBaseNoRepo):
|
||||
raise ConfigError(f"{name} '{section}' doesn't exist")
|
||||
|
||||
if opt and opt not in conf[section]:
|
||||
raise ConfigError(
|
||||
f"option '{opt}' doesn't exist in {name} '{section}'"
|
||||
)
|
||||
raise ConfigError(f"option '{opt}' doesn't exist in {name} '{section}'")
|
||||
|
||||
def _get_appropriate_levels(self, levels):
|
||||
if levels:
|
||||
|
||||
@@ -46,7 +46,7 @@ def add_parser(subparsers, parent_parser):
|
||||
|
||||
daemon_subparsers = daemon_parser.add_subparsers(
|
||||
dest="cmd",
|
||||
help="Use `dvc daemon CMD --help` for command-specific " "help.",
|
||||
help="Use `dvc daemon CMD --help` for command-specific help.",
|
||||
)
|
||||
|
||||
fix_subparsers(daemon_subparsers)
|
||||
|
||||
+28
-30
@@ -9,11 +9,11 @@ if TYPE_CHECKING:
|
||||
from networkx import DiGraph
|
||||
|
||||
|
||||
def _show_ascii(G: "DiGraph"):
|
||||
def _show_ascii(graph: "DiGraph"):
|
||||
from dvc.dagascii import draw
|
||||
from dvc.repo.graph import get_pipelines
|
||||
|
||||
pipelines = get_pipelines(G)
|
||||
pipelines = get_pipelines(graph)
|
||||
|
||||
ret = []
|
||||
for pipeline in pipelines:
|
||||
@@ -31,7 +31,7 @@ def _quote_label(node):
|
||||
return label
|
||||
|
||||
|
||||
def _show_dot(G: "DiGraph"):
|
||||
def _show_dot(graph: "DiGraph"):
|
||||
import io
|
||||
|
||||
import networkx as nx
|
||||
@@ -39,15 +39,15 @@ def _show_dot(G: "DiGraph"):
|
||||
|
||||
dot_file = io.StringIO()
|
||||
|
||||
nx.relabel_nodes(G, _quote_label, copy=False)
|
||||
write_dot(G.reverse(), dot_file)
|
||||
nx.relabel_nodes(graph, _quote_label, copy=False)
|
||||
write_dot(graph.reverse(), dot_file)
|
||||
return dot_file.getvalue()
|
||||
|
||||
|
||||
def _show_mermaid(G, markdown: bool = False):
|
||||
def _show_mermaid(graph, markdown: bool = False):
|
||||
from dvc.repo.graph import get_pipelines
|
||||
|
||||
pipelines = get_pipelines(G)
|
||||
pipelines = get_pipelines(graph)
|
||||
|
||||
graph = "flowchart TD"
|
||||
|
||||
@@ -86,9 +86,7 @@ def _collect_targets(repo, target, outs):
|
||||
targets.extend([str(out) for out in stage.outs])
|
||||
continue
|
||||
|
||||
for out in outs_trie.itervalues( # noqa: B301
|
||||
prefix=repo.fs.path.parts(path)
|
||||
):
|
||||
for out in outs_trie.itervalues(prefix=repo.fs.path.parts(path)): # noqa: B301
|
||||
targets.extend(str(out))
|
||||
|
||||
return targets
|
||||
@@ -102,43 +100,42 @@ def _transform(index, outs):
|
||||
def _relabel(node) -> str:
|
||||
return node.addressing if isinstance(node, Stage) else str(node)
|
||||
|
||||
G = index.outs_graph if outs else index.graph
|
||||
return nx.relabel_nodes(G, _relabel, copy=True)
|
||||
graph = index.outs_graph if outs else index.graph
|
||||
return nx.relabel_nodes(graph, _relabel, copy=True)
|
||||
|
||||
|
||||
def _filter(G, targets, full):
|
||||
def _filter(graph, targets, full):
|
||||
import networkx as nx
|
||||
|
||||
if not targets:
|
||||
return G
|
||||
return graph
|
||||
|
||||
H = G.copy()
|
||||
new_graph = graph.copy()
|
||||
if not full:
|
||||
descendants = set()
|
||||
for target in targets:
|
||||
descendants.update(nx.descendants(G, target))
|
||||
descendants.update(nx.descendants(graph, target))
|
||||
descendants.add(target)
|
||||
H.remove_nodes_from(set(G.nodes()) - descendants)
|
||||
new_graph.remove_nodes_from(set(graph.nodes()) - descendants)
|
||||
|
||||
undirected = H.to_undirected()
|
||||
undirected = new_graph.to_undirected()
|
||||
connected = set()
|
||||
for target in targets:
|
||||
connected.update(nx.node_connected_component(undirected, target))
|
||||
|
||||
H.remove_nodes_from(set(H.nodes()) - connected)
|
||||
|
||||
return H
|
||||
new_graph.remove_nodes_from(set(new_graph.nodes()) - connected)
|
||||
return new_graph
|
||||
|
||||
|
||||
def _build(repo, target=None, full=False, outs=False):
|
||||
targets = _collect_targets(repo, target, outs)
|
||||
G = _transform(repo.index, outs)
|
||||
return _filter(G, targets, full)
|
||||
graph = _transform(repo.index, outs)
|
||||
return _filter(graph, targets, full)
|
||||
|
||||
|
||||
class CmdDAG(CmdBase):
|
||||
def run(self):
|
||||
G = _build(
|
||||
graph = _build(
|
||||
self.repo,
|
||||
target=self.args.target,
|
||||
full=self.args.full,
|
||||
@@ -146,12 +143,12 @@ class CmdDAG(CmdBase):
|
||||
)
|
||||
|
||||
if self.args.dot:
|
||||
ui.write(_show_dot(G))
|
||||
ui.write(_show_dot(graph))
|
||||
elif self.args.mermaid or self.args.markdown:
|
||||
ui.write(_show_mermaid(G, self.args.markdown))
|
||||
ui.write(_show_mermaid(graph, self.args.markdown))
|
||||
else:
|
||||
with ui.pager():
|
||||
ui.write(_show_ascii(G))
|
||||
ui.write(_show_ascii(graph))
|
||||
|
||||
return 0
|
||||
|
||||
@@ -179,7 +176,6 @@ def add_parser(subparsers, parent_parser):
|
||||
)
|
||||
dag_parser.add_argument(
|
||||
"--md",
|
||||
"--show-md",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest="markdown",
|
||||
@@ -204,7 +200,9 @@ def add_parser(subparsers, parent_parser):
|
||||
dag_parser.add_argument(
|
||||
"target",
|
||||
nargs="?",
|
||||
help="Stage name or output to show pipeline for. "
|
||||
"Finds all stages in the workspace by default.",
|
||||
help=(
|
||||
"Stage name or output to show pipeline for. "
|
||||
"Finds all stages in the workspace by default."
|
||||
),
|
||||
)
|
||||
dag_parser.set_defaults(func=CmdDAG)
|
||||
|
||||
+31
-136
@@ -1,18 +1,11 @@
|
||||
import argparse
|
||||
import logging
|
||||
from operator import itemgetter
|
||||
from typing import TYPE_CHECKING, Any, Dict, Iterable, Set
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from funcy import chunks, compact, log_durations
|
||||
|
||||
from dvc.cli import completion
|
||||
from dvc.cli.actions import CommaSeparatedArgs
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.cli.utils import (
|
||||
append_doc_link,
|
||||
fix_subparsers,
|
||||
hide_subparsers_from_help,
|
||||
)
|
||||
from dvc.cli.utils import append_doc_link, fix_subparsers
|
||||
from dvc.ui import ui
|
||||
from dvc.utils import colorize
|
||||
|
||||
@@ -25,12 +18,14 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class CmdDataStatus(CmdBase):
|
||||
COLORS = {
|
||||
"not_in_remote": "red",
|
||||
"not_in_cache": "red",
|
||||
"committed": "green",
|
||||
"uncommitted": "yellow",
|
||||
"untracked": "cyan",
|
||||
}
|
||||
LABELS = {
|
||||
"not_in_remote": "Not in remote",
|
||||
"not_in_cache": "Not in cache",
|
||||
"committed": "DVC committed changes",
|
||||
"uncommitted": "DVC uncommitted changes",
|
||||
@@ -38,21 +33,21 @@ class CmdDataStatus(CmdBase):
|
||||
"unchanged": "DVC unchanged files",
|
||||
}
|
||||
HINTS = {
|
||||
"not_in_remote": ('use "dvc push <file>..." to upload files',),
|
||||
"not_in_cache": ('use "dvc fetch <file>..." to download files',),
|
||||
"committed": (
|
||||
"git commit the corresponding dvc files to update the repo",
|
||||
),
|
||||
"committed": ("git commit the corresponding dvc files to update the repo",),
|
||||
"uncommitted": (
|
||||
'use "dvc commit <file>..." to track changes',
|
||||
'use "dvc checkout <file>..." to discard changes',
|
||||
),
|
||||
"untracked": (
|
||||
'use "git add <file> ..." or '
|
||||
'"dvc add <file>..." to commit to git or to dvc',
|
||||
(
|
||||
'use "git add <file> ..." or '
|
||||
'"dvc add <file>..." to commit to git or to dvc'
|
||||
),
|
||||
),
|
||||
"git_dirty": (
|
||||
"there are {}changes not tracked by dvc, "
|
||||
'use "git status" to see',
|
||||
'there are {}changes not tracked by dvc, use "git status" to see',
|
||||
),
|
||||
}
|
||||
|
||||
@@ -72,7 +67,7 @@ class CmdDataStatus(CmdBase):
|
||||
yield stage, items
|
||||
|
||||
@classmethod
|
||||
def _show_status(cls, status: "DataStatus") -> int:
|
||||
def _show_status(cls, status: "DataStatus") -> int: # noqa: C901
|
||||
git_info = status.pop("git") # type: ignore[misc]
|
||||
result = dict(cls._process_status(status))
|
||||
if not result:
|
||||
@@ -96,8 +91,7 @@ class CmdDataStatus(CmdBase):
|
||||
|
||||
if isinstance(stage_status, dict):
|
||||
items = [
|
||||
": ".join([state, file])
|
||||
for file, state in stage_status.items()
|
||||
": ".join([state, file]) for file, state in stage_status.items()
|
||||
]
|
||||
else:
|
||||
items = stage_status
|
||||
@@ -114,10 +108,14 @@ class CmdDataStatus(CmdBase):
|
||||
return 0
|
||||
|
||||
def run(self) -> int:
|
||||
with log_durations(logger.trace, "in data_status"): # type: ignore
|
||||
with log_durations(
|
||||
logger.trace, "in data_status" # type: ignore[attr-defined]
|
||||
):
|
||||
status = self.repo.data_status(
|
||||
granular=self.args.granular,
|
||||
untracked_files=self.args.untracked_files,
|
||||
not_in_remote=self.args.not_in_remote,
|
||||
remote_refresh=self.args.remote_refresh,
|
||||
)
|
||||
|
||||
if not self.args.unchanged:
|
||||
@@ -131,65 +129,6 @@ class CmdDataStatus(CmdBase):
|
||||
return self._show_status(status)
|
||||
|
||||
|
||||
class CmdDataLs(CmdBase):
|
||||
@staticmethod
|
||||
def _show_table(
|
||||
d: Iterable[Dict[str, Any]],
|
||||
filter_types: Set[str],
|
||||
filter_labels: Set[str],
|
||||
markdown: bool = False,
|
||||
) -> None:
|
||||
from rich.style import Style
|
||||
|
||||
from dvc.compare import TabularData
|
||||
|
||||
td = TabularData(
|
||||
columns=["Path", "Type", "Labels", "Description"], fill_value="-"
|
||||
)
|
||||
for entry in sorted(d, key=itemgetter("path")):
|
||||
typ = entry.get("type", "")
|
||||
desc = entry.get("desc", "-")
|
||||
labels = entry.get("labels", [])
|
||||
|
||||
if filter_types and typ not in filter_types:
|
||||
continue
|
||||
if filter_labels and filter_labels.isdisjoint(labels):
|
||||
continue
|
||||
|
||||
rich_label = ui.rich_text()
|
||||
for index, label in enumerate(labels):
|
||||
if index:
|
||||
rich_label.append(",")
|
||||
style = Style(bold=label in filter_labels, color="green")
|
||||
rich_label.append(label, style=style)
|
||||
|
||||
if markdown and desc:
|
||||
desc = desc.partition("\n")[0]
|
||||
|
||||
path = ui.rich_text(entry["path"], style="cyan")
|
||||
type_style = Style(bold=typ in filter_types, color="yellow")
|
||||
typ = ui.rich_text(entry.get("type", ""), style=type_style)
|
||||
td.append([path, typ or "-", rich_label or "-", desc])
|
||||
|
||||
td.render(markdown=markdown, rich_table=True)
|
||||
|
||||
def run(self):
|
||||
from dvc.repo.data import ls
|
||||
|
||||
filter_labels = set(self.args.labels)
|
||||
filter_types = set(self.args.type)
|
||||
d = ls(
|
||||
self.repo, targets=self.args.targets, recursive=self.args.recursive
|
||||
)
|
||||
self._show_table(
|
||||
d,
|
||||
filter_labels=filter_labels,
|
||||
filter_types=filter_types,
|
||||
markdown=self.args.markdown,
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def add_parser(subparsers, parent_parser):
|
||||
data_parser = subparsers.add_parser(
|
||||
"data",
|
||||
@@ -203,8 +142,7 @@ def add_parser(subparsers, parent_parser):
|
||||
fix_subparsers(data_subparsers)
|
||||
|
||||
DATA_STATUS_HELP = (
|
||||
"Show changes between the last git commit, "
|
||||
"the dvcfiles and the workspace."
|
||||
"Show changes between the last git commit, the dvcfiles and the workspace."
|
||||
)
|
||||
data_status_parser = data_subparsers.add_parser(
|
||||
"status",
|
||||
@@ -219,13 +157,6 @@ def add_parser(subparsers, parent_parser):
|
||||
default=False,
|
||||
help="Show output in JSON format.",
|
||||
)
|
||||
data_status_parser.add_argument(
|
||||
"--show-json",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest="json",
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
data_status_parser.add_argument(
|
||||
"--granular",
|
||||
action="store_true",
|
||||
@@ -246,52 +177,16 @@ def add_parser(subparsers, parent_parser):
|
||||
nargs="?",
|
||||
help="Show untracked files.",
|
||||
)
|
||||
data_status_parser.add_argument(
|
||||
"--not-in-remote",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Show files not in remote.",
|
||||
)
|
||||
data_status_parser.add_argument(
|
||||
"--no-remote-refresh",
|
||||
dest="remote_refresh",
|
||||
action="store_false",
|
||||
help="Use cached remote index (don't check remote).",
|
||||
)
|
||||
data_status_parser.set_defaults(func=CmdDataStatus)
|
||||
|
||||
DATA_LS_HELP = "List data tracked by DVC with its metadata."
|
||||
data_ls_parser = data_subparsers.add_parser(
|
||||
"ls",
|
||||
aliases=["list"],
|
||||
parents=[parent_parser],
|
||||
description=append_doc_link(DATA_LS_HELP, "data/ls"),
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
add_help=False,
|
||||
)
|
||||
data_ls_parser.add_argument(
|
||||
"--md",
|
||||
"--show-md",
|
||||
dest="markdown",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Show tabulated output in the Markdown format (GFM).",
|
||||
)
|
||||
data_ls_parser.add_argument(
|
||||
"--type",
|
||||
action=CommaSeparatedArgs,
|
||||
default=[],
|
||||
help="Comma-separated list of type to filter.",
|
||||
)
|
||||
data_ls_parser.add_argument(
|
||||
"--labels",
|
||||
action=CommaSeparatedArgs,
|
||||
default=[],
|
||||
help="Comma-separated list of labels to filter.",
|
||||
)
|
||||
data_ls_parser.add_argument(
|
||||
"-R",
|
||||
"--recursive",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Recursively list from the specified directories.",
|
||||
)
|
||||
data_ls_parser.add_argument(
|
||||
"targets",
|
||||
default=None,
|
||||
nargs="*",
|
||||
help=(
|
||||
"Limit command scope to these tracked files/directories, "
|
||||
".dvc files, or stage names."
|
||||
),
|
||||
).complete = completion.DVCFILES_AND_STAGE
|
||||
data_ls_parser.set_defaults(func=CmdDataLs)
|
||||
hide_subparsers_from_help(data_subparsers)
|
||||
|
||||
+22
-15
@@ -40,6 +40,7 @@ class CmdDataPull(CmdDataBase):
|
||||
recursive=self.args.recursive,
|
||||
run_cache=self.args.run_cache,
|
||||
glob=self.args.glob,
|
||||
allow_missing=self.args.allow_missing,
|
||||
)
|
||||
self.log_summary(stats)
|
||||
except (CheckoutError, DvcException) as exc:
|
||||
@@ -122,7 +123,7 @@ def shared_parent_parser():
|
||||
"Limit command scope to these tracked files/directories, "
|
||||
".dvc files and stage names."
|
||||
),
|
||||
).complete = completion.DVC_FILE
|
||||
).complete = completion.DVC_FILE # type: ignore[attr-defined]
|
||||
|
||||
return parent_parser
|
||||
|
||||
@@ -197,6 +198,12 @@ def add_parser(subparsers, _parent_parser):
|
||||
default=False,
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
pull_parser.add_argument(
|
||||
"--allow-missing",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Ignore errors if some of the files or directories are missing.",
|
||||
)
|
||||
pull_parser.set_defaults(func=CmdDataPull)
|
||||
|
||||
# Push
|
||||
@@ -262,9 +269,7 @@ def add_parser(subparsers, _parent_parser):
|
||||
push_parser.set_defaults(func=CmdDataPush)
|
||||
|
||||
# Fetch
|
||||
FETCH_HELP = (
|
||||
"Download files or directories from remote storage to the cache."
|
||||
)
|
||||
FETCH_HELP = "Download files or directories from remote storage to the cache."
|
||||
|
||||
fetch_parser = subparsers.add_parser(
|
||||
"fetch",
|
||||
@@ -302,7 +307,7 @@ def add_parser(subparsers, _parent_parser):
|
||||
"--with-deps",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Fetch cache for all dependencies of the " "specified target.",
|
||||
help="Fetch cache for all dependencies of the specified target.",
|
||||
)
|
||||
fetch_parser.add_argument(
|
||||
"-R",
|
||||
@@ -320,9 +325,7 @@ def add_parser(subparsers, _parent_parser):
|
||||
fetch_parser.set_defaults(func=CmdDataFetch)
|
||||
|
||||
# Status
|
||||
STATUS_HELP = (
|
||||
"Show changed stages, compare local cache and a remote storage."
|
||||
)
|
||||
STATUS_HELP = "Show changed stages, compare local cache and a remote storage."
|
||||
|
||||
status_parser = subparsers.add_parser(
|
||||
"status",
|
||||
@@ -360,24 +363,29 @@ def add_parser(subparsers, _parent_parser):
|
||||
"--all-branches",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Show status of a local cache compared to a remote repository "
|
||||
"for all branches.",
|
||||
help=(
|
||||
"Show status of a local cache compared to a remote repository "
|
||||
"for all branches."
|
||||
),
|
||||
)
|
||||
status_parser.add_argument(
|
||||
"-T",
|
||||
"--all-tags",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Show status of a local cache compared to a remote repository "
|
||||
"for all tags.",
|
||||
help=(
|
||||
"Show status of a local cache compared to a remote repository for all tags."
|
||||
),
|
||||
)
|
||||
status_parser.add_argument(
|
||||
"-A",
|
||||
"--all-commits",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Show status of a local cache compared to a remote repository "
|
||||
"for all commits.",
|
||||
help=(
|
||||
"Show status of a local cache compared to a remote repository "
|
||||
"for all commits."
|
||||
),
|
||||
)
|
||||
status_parser.add_argument(
|
||||
"-d",
|
||||
@@ -395,7 +403,6 @@ def add_parser(subparsers, _parent_parser):
|
||||
)
|
||||
status_parser.add_argument(
|
||||
"--json",
|
||||
"--show-json",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Show status in JSON format.",
|
||||
|
||||
+10
-17
@@ -109,12 +109,10 @@ class CmdDiff(CmdBase):
|
||||
ui.write()
|
||||
|
||||
if not sum(summary.values()):
|
||||
return None
|
||||
return
|
||||
|
||||
states_summary = ", ".join(
|
||||
f"{summary[state]} {state}"
|
||||
for state in states
|
||||
if summary[state] > 0
|
||||
f"{summary[state]} {state}" for state in states if summary[state] > 0
|
||||
)
|
||||
ui.write("files summary:", states_summary)
|
||||
|
||||
@@ -122,9 +120,7 @@ class CmdDiff(CmdBase):
|
||||
from dvc.exceptions import DvcException
|
||||
|
||||
try:
|
||||
diff = self.repo.diff(
|
||||
self.args.a_rev, self.args.b_rev, self.args.targets
|
||||
)
|
||||
diff = self.repo.diff(self.args.a_rev, self.args.b_rev, self.args.targets)
|
||||
show_hash = self.args.show_hash
|
||||
hide_missing = self.args.b_rev or self.args.hide_missing
|
||||
if hide_missing:
|
||||
@@ -133,9 +129,11 @@ class CmdDiff(CmdBase):
|
||||
for key, entries in diff.items():
|
||||
entries = sorted(
|
||||
entries,
|
||||
key=lambda entry: entry["path"]["old"]
|
||||
if isinstance(entry["path"], dict)
|
||||
else entry["path"],
|
||||
key=lambda entry: (
|
||||
entry["path"]["old"]
|
||||
if isinstance(entry["path"], dict)
|
||||
else entry["path"]
|
||||
),
|
||||
)
|
||||
if not show_hash:
|
||||
for entry in entries:
|
||||
@@ -170,10 +168,7 @@ def add_parser(subparsers, parent_parser):
|
||||
diff_parser.add_argument(
|
||||
"--targets",
|
||||
nargs="*",
|
||||
help=(
|
||||
"Specific DVC-tracked files to compare. "
|
||||
"Accepts one or more file paths."
|
||||
),
|
||||
help="Specific DVC-tracked files to compare. Accepts one or more file paths.",
|
||||
metavar="<paths>",
|
||||
).complete = completion.FILE
|
||||
diff_parser.add_argument(
|
||||
@@ -184,12 +179,11 @@ def add_parser(subparsers, parent_parser):
|
||||
)
|
||||
diff_parser.add_argument(
|
||||
"b_rev",
|
||||
help=("New Git commit to compare (defaults to the current workspace)"),
|
||||
help="New Git commit to compare (defaults to the current workspace)",
|
||||
nargs="?",
|
||||
)
|
||||
diff_parser.add_argument(
|
||||
"--json",
|
||||
"--show-json",
|
||||
help="Format the output into a JSON",
|
||||
action="store_true",
|
||||
default=False,
|
||||
@@ -202,7 +196,6 @@ def add_parser(subparsers, parent_parser):
|
||||
)
|
||||
diff_parser.add_argument(
|
||||
"--md",
|
||||
"--show-md",
|
||||
help="Show tabulated output in the Markdown format (GFM).",
|
||||
action="store_true",
|
||||
dest="markdown",
|
||||
|
||||
@@ -1,39 +1,35 @@
|
||||
import argparse
|
||||
|
||||
from dvc.cli.utils import (
|
||||
append_doc_link,
|
||||
fix_subparsers,
|
||||
hide_subparsers_from_help,
|
||||
)
|
||||
from dvc.cli.utils import append_doc_link, fix_subparsers, hide_subparsers_from_help
|
||||
from dvc.commands.experiments import (
|
||||
apply,
|
||||
branch,
|
||||
clean,
|
||||
diff,
|
||||
exec_run,
|
||||
gc,
|
||||
init,
|
||||
ls,
|
||||
pull,
|
||||
push,
|
||||
queue_worker,
|
||||
remove,
|
||||
run,
|
||||
save,
|
||||
show,
|
||||
)
|
||||
|
||||
SUB_COMMANDS = [
|
||||
apply,
|
||||
branch,
|
||||
clean,
|
||||
diff,
|
||||
exec_run,
|
||||
gc,
|
||||
init,
|
||||
ls,
|
||||
pull,
|
||||
push,
|
||||
queue_worker,
|
||||
remove,
|
||||
run,
|
||||
save,
|
||||
show,
|
||||
]
|
||||
|
||||
@@ -52,8 +48,7 @@ def add_parser(subparsers, parent_parser):
|
||||
|
||||
experiments_subparsers = experiments_parser.add_subparsers(
|
||||
dest="cmd",
|
||||
help="Use `dvc experiments CMD --help` to display "
|
||||
"command-specific help.",
|
||||
help="Use `dvc experiments CMD --help` to display command-specific help.",
|
||||
)
|
||||
|
||||
fix_subparsers(experiments_subparsers)
|
||||
@@ -83,6 +78,7 @@ def add_rev_selection_flags(
|
||||
experiments_subcmd_parser.add_argument(
|
||||
"--rev",
|
||||
type=str,
|
||||
action="append",
|
||||
default=None,
|
||||
help=msg,
|
||||
metavar="<commit>",
|
||||
|
||||
@@ -4,25 +4,27 @@ import logging
|
||||
from dvc.cli import completion
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.ui import ui
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CmdExperimentsApply(CmdBase):
|
||||
def run(self):
|
||||
|
||||
self.repo.experiments.apply(
|
||||
self.args.experiment, force=self.args.force
|
||||
)
|
||||
if not self.args.force:
|
||||
ui.write(
|
||||
"The --no-force option is deprecated and will be removed in a future"
|
||||
" DVC release. To revert the result of 'exp apply', run:\n"
|
||||
"\n\tgit reset --hard\n"
|
||||
"\tgit stash apply refs/exps/apply/stash\n"
|
||||
)
|
||||
self.repo.experiments.apply(self.args.experiment)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def add_parser(experiments_subparsers, parent_parser):
|
||||
|
||||
EXPERIMENTS_APPLY_HELP = (
|
||||
"Apply the changes from an experiment to your workspace."
|
||||
)
|
||||
EXPERIMENTS_APPLY_HELP = "Apply the changes from an experiment to your workspace."
|
||||
experiments_apply_parser = experiments_subparsers.add_parser(
|
||||
"apply",
|
||||
parents=[parent_parser],
|
||||
|
||||
@@ -9,14 +9,12 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class CmdExperimentsBranch(CmdBase):
|
||||
def run(self):
|
||||
|
||||
self.repo.experiments.branch(self.args.experiment, self.args.branch)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def add_parser(experiments_subparsers, parent_parser):
|
||||
|
||||
EXPERIMENTS_BRANCH_HELP = "Promote an experiment to a Git branch."
|
||||
experiments_branch_parser = experiments_subparsers.add_parser(
|
||||
"branch",
|
||||
@@ -29,6 +27,12 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
"experiment", help="Experiment to be promoted."
|
||||
)
|
||||
experiments_branch_parser.add_argument(
|
||||
"branch", help="Name for the new Git branch"
|
||||
"branch",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help=(
|
||||
"Optional name for the new Git branch. "
|
||||
"Defaults to '{experiment-name}-branch'."
|
||||
),
|
||||
)
|
||||
experiments_branch_parser.set_defaults(func=CmdExperimentsBranch)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.cli.utils import append_doc_link
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CmdExperimentsClean(CmdBase):
|
||||
def run(self):
|
||||
self.repo.experiments.clean()
|
||||
return 0
|
||||
|
||||
|
||||
def add_parser(experiments_subparsers, parent_parser):
|
||||
EXPERIMENTS_CLEAN_HELP = "Cleanup experiments temporary internal files."
|
||||
experiments_clean_parser = experiments_subparsers.add_parser(
|
||||
"clean",
|
||||
parents=[parent_parser],
|
||||
description=append_doc_link(EXPERIMENTS_CLEAN_HELP, "exp/clean"),
|
||||
help=EXPERIMENTS_CLEAN_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
experiments_clean_parser.set_defaults(func=CmdExperimentsClean)
|
||||
@@ -13,7 +13,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class CmdExperimentsDiff(CmdBase):
|
||||
def run(self):
|
||||
|
||||
try:
|
||||
diff = self.repo.experiments.diff(
|
||||
a_rev=self.args.a_rev,
|
||||
@@ -53,10 +52,7 @@ class CmdExperimentsDiff(CmdBase):
|
||||
|
||||
|
||||
def add_parser(experiments_subparsers, parent_parser):
|
||||
|
||||
EXPERIMENTS_DIFF_HELP = (
|
||||
"Show changes between experiments in the DVC repository."
|
||||
)
|
||||
EXPERIMENTS_DIFF_HELP = "Show changes between experiments."
|
||||
|
||||
experiments_diff_parser = experiments_subparsers.add_parser(
|
||||
"diff",
|
||||
@@ -87,14 +83,12 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
)
|
||||
experiments_diff_parser.add_argument(
|
||||
"--json",
|
||||
"--show-json",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Show output in JSON format.",
|
||||
)
|
||||
experiments_diff_parser.add_argument(
|
||||
"--md",
|
||||
"--show-md",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest="markdown",
|
||||
|
||||
@@ -9,10 +9,7 @@ class CmdExecutorRun(CmdBaseNoRepo):
|
||||
"""Run an experiment executor."""
|
||||
|
||||
def run(self):
|
||||
from dvc.repo.experiments.executor.base import (
|
||||
BaseExecutor,
|
||||
ExecutorInfo,
|
||||
)
|
||||
from dvc.repo.experiments.executor.base import BaseExecutor, ExecutorInfo
|
||||
from dvc.utils.serialize import load_json
|
||||
|
||||
info = ExecutorInfo.from_dict(load_json(self.args.infofile))
|
||||
@@ -22,6 +19,8 @@ class CmdExecutorRun(CmdBaseNoRepo):
|
||||
queue=None,
|
||||
log_level=logger.getEffectiveLevel(),
|
||||
infofile=self.args.infofile,
|
||||
copy_paths=self.args.copy_paths,
|
||||
message=self.args.message,
|
||||
)
|
||||
return 0
|
||||
|
||||
@@ -39,4 +38,21 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
help="Path to executor info file",
|
||||
default=None,
|
||||
)
|
||||
exec_run_parser.add_argument(
|
||||
"-C",
|
||||
"--copy-paths",
|
||||
action="append",
|
||||
default=[],
|
||||
help=(
|
||||
"List of ignored or untracked paths to copy into the temp directory."
|
||||
" Only used if `--temp` or `--queue` is specified."
|
||||
),
|
||||
)
|
||||
exec_run_parser.add_argument(
|
||||
"-M",
|
||||
"--message",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Custom commit message to use when committing the experiment.",
|
||||
)
|
||||
exec_run_parser.set_defaults(func=CmdExecutorRun)
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.exceptions import InvalidArgumentError
|
||||
from dvc.ui import ui
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _raise_error_if_all_disabled(**kwargs):
|
||||
if not any(kwargs.values()):
|
||||
raise InvalidArgumentError(
|
||||
"Either of `-w|--workspace`, `-a|--all-branches`, `-T|--all-tags`,"
|
||||
" `--all-commits` or `--date` needs to be set."
|
||||
)
|
||||
|
||||
|
||||
class CmdExperimentsGC(CmdBase):
|
||||
def run(self):
|
||||
_raise_error_if_all_disabled(
|
||||
all_branches=self.args.all_branches,
|
||||
all_tags=self.args.all_tags,
|
||||
all_commits=self.args.all_commits,
|
||||
commit_date=self.args.commit_date,
|
||||
workspace=self.args.workspace,
|
||||
)
|
||||
|
||||
msg = "This will remove all experiments except those derived from "
|
||||
|
||||
msg += "the workspace"
|
||||
if self.args.all_commits:
|
||||
msg += " and all git commits"
|
||||
else:
|
||||
if self.args.all_branches and self.args.all_tags:
|
||||
msg += " and all git branches and tags"
|
||||
elif self.args.all_branches:
|
||||
msg += " and all git branches"
|
||||
elif self.args.all_tags:
|
||||
msg += " and all git tags"
|
||||
|
||||
if self.args.commit_date:
|
||||
msg += (
|
||||
" and all git commits before date "
|
||||
f"{self.args.commit_date}"
|
||||
)
|
||||
msg += " of the current repo."
|
||||
|
||||
if self.args.queued:
|
||||
msg += " Run queued experiments will be preserved."
|
||||
else:
|
||||
msg += " Run queued experiments will be removed."
|
||||
|
||||
logger.warning(msg)
|
||||
|
||||
msg = "Are you sure you want to proceed?"
|
||||
if not self.args.force and not ui.confirm(msg):
|
||||
return 1
|
||||
|
||||
removed = self.repo.experiments.gc(
|
||||
all_branches=self.args.all_branches,
|
||||
all_tags=self.args.all_tags,
|
||||
all_commits=self.args.all_commits,
|
||||
workspace=self.args.workspace,
|
||||
commit_date=self.args.commit_date,
|
||||
queued=self.args.queued,
|
||||
)
|
||||
|
||||
if removed:
|
||||
ui.write(
|
||||
f"Removed {removed} experiments.",
|
||||
"To remove unused cache files",
|
||||
"use 'dvc gc'.",
|
||||
)
|
||||
else:
|
||||
ui.write("No experiments to remove.")
|
||||
return 0
|
||||
|
||||
|
||||
def add_parser(experiments_subparsers, parent_parser):
|
||||
|
||||
EXPERIMENTS_GC_HELP = "Garbage collect unneeded experiments."
|
||||
EXPERIMENTS_GC_DESCRIPTION = (
|
||||
"Removes all experiments which are not derived from the specified"
|
||||
"Git revisions."
|
||||
)
|
||||
experiments_gc_parser = experiments_subparsers.add_parser(
|
||||
"gc",
|
||||
parents=[parent_parser],
|
||||
description=append_doc_link(EXPERIMENTS_GC_DESCRIPTION, "exp/gc"),
|
||||
help=EXPERIMENTS_GC_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
experiments_gc_parser.add_argument(
|
||||
"-w",
|
||||
"--workspace",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Keep experiments derived from the current workspace.",
|
||||
)
|
||||
experiments_gc_parser.add_argument(
|
||||
"-a",
|
||||
"--all-branches",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Keep experiments derived from the tips of all Git branches.",
|
||||
)
|
||||
experiments_gc_parser.add_argument(
|
||||
"-T",
|
||||
"--all-tags",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Keep experiments derived from all Git tags.",
|
||||
)
|
||||
experiments_gc_parser.add_argument(
|
||||
"-A",
|
||||
"--all-commits",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Keep experiments derived from all Git commits.",
|
||||
)
|
||||
experiments_gc_parser.add_argument(
|
||||
"--date",
|
||||
type=str,
|
||||
dest="commit_date",
|
||||
metavar="<YYYY-MM-DD>",
|
||||
default=None,
|
||||
help=(
|
||||
"Keep experiments from the commits after (inclusive) a certain "
|
||||
"date. Date must match the extended ISO 8601 format (YYYY-MM-DD)."
|
||||
),
|
||||
)
|
||||
experiments_gc_parser.add_argument(
|
||||
"--queued",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=(
|
||||
"Keep queued experiments (experiments run queue will be cleared "
|
||||
"by default)."
|
||||
),
|
||||
)
|
||||
experiments_gc_parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Force garbage collection - automatically agree to all prompts.",
|
||||
)
|
||||
experiments_gc_parser.set_defaults(func=CmdExperimentsGC)
|
||||
@@ -1,210 +0,0 @@
|
||||
import argparse
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
from funcy import compact
|
||||
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.exceptions import InvalidArgumentError
|
||||
from dvc.ui import ui
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dvc.dependency import Dependency
|
||||
from dvc.stage import PipelineStage
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CmdExperimentsInit(CmdBase):
|
||||
DEFAULT_NAME = "train"
|
||||
CODE = "src"
|
||||
DATA = "data"
|
||||
MODELS = "models"
|
||||
DEFAULT_METRICS = "metrics.json"
|
||||
DEFAULT_PARAMS = "params.yaml"
|
||||
PLOTS = "plots"
|
||||
DEFAULTS = {
|
||||
"code": CODE,
|
||||
"data": DATA,
|
||||
"models": MODELS,
|
||||
"metrics": DEFAULT_METRICS,
|
||||
"params": DEFAULT_PARAMS,
|
||||
"plots": PLOTS,
|
||||
}
|
||||
|
||||
def run(self):
|
||||
from dvc.commands.stage import parse_cmd
|
||||
|
||||
cmd = parse_cmd(self.args.command)
|
||||
if not self.args.interactive and not cmd:
|
||||
raise InvalidArgumentError("command is not specified")
|
||||
|
||||
from dvc.repo.experiments.init import init
|
||||
|
||||
defaults = {}
|
||||
if not self.args.explicit:
|
||||
config = self.repo.config["exp"]
|
||||
defaults.update({**self.DEFAULTS, **config})
|
||||
|
||||
cli_args = compact(
|
||||
{
|
||||
"cmd": cmd,
|
||||
"code": self.args.code,
|
||||
"data": self.args.data,
|
||||
"models": self.args.models,
|
||||
"metrics": self.args.metrics,
|
||||
"params": self.args.params,
|
||||
"plots": self.args.plots,
|
||||
"live": self.args.live,
|
||||
}
|
||||
)
|
||||
|
||||
initialized_stage, initialized_deps, initialized_out_dirs = init(
|
||||
self.repo,
|
||||
name=self.args.name,
|
||||
type=self.args.type,
|
||||
defaults=defaults,
|
||||
overrides=cli_args,
|
||||
interactive=self.args.interactive,
|
||||
force=self.args.force,
|
||||
)
|
||||
self._post_init_display(
|
||||
initialized_stage, initialized_deps, initialized_out_dirs
|
||||
)
|
||||
if self.args.run:
|
||||
self.repo.experiments.run(targets=[initialized_stage.addressing])
|
||||
return 0
|
||||
|
||||
def _post_init_display(
|
||||
self,
|
||||
stage: "PipelineStage",
|
||||
new_deps: List["Dependency"],
|
||||
new_out_dirs: List[str],
|
||||
) -> None:
|
||||
from dvc.utils import humanize
|
||||
|
||||
path_fmt = "[green]{}[/green]".format
|
||||
if new_deps:
|
||||
deps_paths = humanize.join(map(path_fmt, new_deps))
|
||||
ui.write(f"Creating dependencies: {deps_paths}", styled=True)
|
||||
|
||||
if new_out_dirs:
|
||||
out_dirs_paths = humanize.join(map(path_fmt, new_out_dirs))
|
||||
ui.write(
|
||||
f"Creating output directories: {out_dirs_paths}", styled=True
|
||||
)
|
||||
|
||||
ui.write(
|
||||
f"Creating [b]{self.args.name}[/b] stage in [green]dvc.yaml[/]",
|
||||
styled=True,
|
||||
)
|
||||
if stage.outs or not self.args.run:
|
||||
# separate the above status-like messages with help/tips section
|
||||
ui.write(styled=True)
|
||||
|
||||
if stage.outs:
|
||||
outs_paths = humanize.join(map(path_fmt, stage.outs))
|
||||
tips = f"Ensure your experiment command creates {outs_paths}."
|
||||
ui.write(tips, styled=True)
|
||||
|
||||
if not self.args.run:
|
||||
ui.write(
|
||||
'You can now run your experiment using [b]"dvc exp run"[/].',
|
||||
styled=True,
|
||||
)
|
||||
else:
|
||||
# separate between `exp.run` output and `dvc exp init` output
|
||||
ui.write(styled=True)
|
||||
|
||||
|
||||
def add_parser(experiments_subparsers, parent_parser):
|
||||
|
||||
EXPERIMENTS_INIT_HELP = "Quickly setup any project to use experiments."
|
||||
experiments_init_parser = experiments_subparsers.add_parser(
|
||||
"init",
|
||||
parents=[parent_parser],
|
||||
description=append_doc_link(EXPERIMENTS_INIT_HELP, "exp/init"),
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help=EXPERIMENTS_INIT_HELP,
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"command",
|
||||
nargs=argparse.REMAINDER,
|
||||
help="Command to execute.",
|
||||
metavar="command",
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--run",
|
||||
action="store_true",
|
||||
help="Run the experiment after initializing it",
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--interactive",
|
||||
"-i",
|
||||
action="store_true",
|
||||
help="Prompt for values that are not provided",
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Overwrite existing stage",
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--explicit",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Only use the path values explicitly provided",
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--name",
|
||||
"-n",
|
||||
help="Name of the stage to create (default: %(default)s)",
|
||||
default=CmdExperimentsInit.DEFAULT_NAME,
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--code",
|
||||
help="Path to the source file or directory "
|
||||
"which your experiments depend"
|
||||
f" (default: {CmdExperimentsInit.CODE})",
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--data",
|
||||
help="Path to the data file or directory "
|
||||
"which your experiments depend"
|
||||
f" (default: {CmdExperimentsInit.DATA})",
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--models",
|
||||
help="Path to the model file or directory for your experiments"
|
||||
f" (default: {CmdExperimentsInit.MODELS})",
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--params",
|
||||
help="Path to the parameters file for your experiments"
|
||||
f" (default: {CmdExperimentsInit.DEFAULT_PARAMS})",
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--metrics",
|
||||
help="Path to the metrics file for your experiments"
|
||||
f" (default: {CmdExperimentsInit.DEFAULT_METRICS})",
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--plots",
|
||||
help="Path to the plots file or directory for your experiments"
|
||||
f" (default: {CmdExperimentsInit.PLOTS})",
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--live",
|
||||
help="Path to log dvclive outputs for your experiments",
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--type",
|
||||
choices=["default", "checkpoint"],
|
||||
default="default",
|
||||
help="Select type of stage to create (default: %(default)s)",
|
||||
)
|
||||
experiments_init_parser.set_defaults(func=CmdExperimentsInit)
|
||||
@@ -3,6 +3,8 @@ import logging
|
||||
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.exceptions import InvalidArgumentError
|
||||
from dvc.ui import ui
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -10,28 +12,36 @@ logger = logging.getLogger(__name__)
|
||||
class CmdExperimentsList(CmdBase):
|
||||
def run(self):
|
||||
name_only = self.args.name_only
|
||||
sha_only = self.args.sha_only
|
||||
git_remote = self.args.git_remote
|
||||
if sha_only and git_remote:
|
||||
raise InvalidArgumentError("--sha-only not supported with git_remote.")
|
||||
exps = self.repo.experiments.ls(
|
||||
all_commits=self.args.all_commits,
|
||||
rev=self.args.rev,
|
||||
num=self.args.num,
|
||||
git_remote=self.args.git_remote,
|
||||
git_remote=git_remote,
|
||||
)
|
||||
tags = self.repo.scm.describe(exps)
|
||||
remained = {baseline for baseline, tag in tags.items() if tag is None}
|
||||
base = "refs/heads/"
|
||||
ref_heads = self.repo.scm.describe(remained, base=base)
|
||||
|
||||
for baseline in exps:
|
||||
name = (
|
||||
tags[baseline]
|
||||
or ref_heads[baseline][len(base) :]
|
||||
or baseline[:7]
|
||||
)
|
||||
if not name_only:
|
||||
print(f"{name}:")
|
||||
for exp_name in exps[baseline]:
|
||||
indent = "" if name_only else "\t"
|
||||
print(f"{indent}{exp_name}")
|
||||
if not (name_only or sha_only):
|
||||
tag_base = "refs/tags/"
|
||||
branch_base = "refs/heads/"
|
||||
name = baseline[:7]
|
||||
if baseline.startswith(tag_base):
|
||||
name = baseline[len(tag_base) :]
|
||||
elif baseline.startswith(branch_base):
|
||||
name = baseline[len(branch_base) :]
|
||||
ui.write(f"{name}:")
|
||||
for exp_name, rev in exps[baseline]:
|
||||
if name_only:
|
||||
ui.write(exp_name)
|
||||
elif sha_only:
|
||||
ui.write(rev)
|
||||
elif rev:
|
||||
ui.write(f"\t{rev[:7]} [{exp_name}]")
|
||||
else:
|
||||
ui.write(f"\t{exp_name}")
|
||||
|
||||
return 0
|
||||
|
||||
@@ -48,11 +58,18 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
add_rev_selection_flags(experiments_list_parser, "List")
|
||||
experiments_list_parser.add_argument(
|
||||
display_group = experiments_list_parser.add_mutually_exclusive_group()
|
||||
display_group.add_argument(
|
||||
"--name-only",
|
||||
"--names-only",
|
||||
action="store_true",
|
||||
help="Only output experiment names (without parent commits).",
|
||||
help="Only output experiment names (without SHAs or parent commits).",
|
||||
)
|
||||
display_group.add_argument(
|
||||
"--sha-only",
|
||||
"--shas-only",
|
||||
action="store_true",
|
||||
help="Only output experiment commit SHAs (without names or parent commits).",
|
||||
)
|
||||
experiments_list_parser.add_argument(
|
||||
"git_remote",
|
||||
|
||||
@@ -3,25 +3,13 @@ import logging
|
||||
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.exceptions import InvalidArgumentError
|
||||
from dvc.ui import ui
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CmdExperimentsPull(CmdBase):
|
||||
def raise_error_if_all_disabled(self):
|
||||
if not any(
|
||||
[self.args.experiment, self.args.all_commits, self.args.rev]
|
||||
):
|
||||
raise InvalidArgumentError(
|
||||
"Either provide an `experiment` argument, or use the "
|
||||
"`--rev` or `--all-commits` flag."
|
||||
)
|
||||
|
||||
def run(self):
|
||||
self.raise_error_if_all_disabled()
|
||||
|
||||
pulled_exps = self.repo.experiments.pull(
|
||||
self.args.git_remote,
|
||||
self.args.experiment,
|
||||
@@ -63,21 +51,18 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
help=EXPERIMENTS_PULL_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
add_rev_selection_flags(experiments_pull_parser, "Pull", False)
|
||||
add_rev_selection_flags(experiments_pull_parser, "Pull", True)
|
||||
experiments_pull_parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
action="store_true",
|
||||
help="Replace local experiment already exists.",
|
||||
help="Replace local experiment if it already exists.",
|
||||
)
|
||||
experiments_pull_parser.add_argument(
|
||||
"--no-cache",
|
||||
action="store_false",
|
||||
dest="pull_cache",
|
||||
help=(
|
||||
"Do not pull cached outputs for this experiment from DVC remote "
|
||||
"storage."
|
||||
),
|
||||
help="Do not pull cached outputs for this experiment from DVC remote storage.",
|
||||
)
|
||||
experiments_pull_parser.add_argument(
|
||||
"-r",
|
||||
@@ -92,8 +77,7 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
type=int,
|
||||
metavar="<number>",
|
||||
help=(
|
||||
"Number of jobs to run simultaneously when pulling from DVC "
|
||||
"remote storage."
|
||||
"Number of jobs to run simultaneously when pulling from DVC remote storage."
|
||||
),
|
||||
)
|
||||
experiments_pull_parser.add_argument(
|
||||
|
||||
@@ -1,54 +1,81 @@
|
||||
import argparse
|
||||
import logging
|
||||
from typing import Any, Dict
|
||||
|
||||
from dvc.cli import completion
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.exceptions import InvalidArgumentError
|
||||
from dvc.ui import ui
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CmdExperimentsPush(CmdBase):
|
||||
def raise_error_if_all_disabled(self):
|
||||
if not any(
|
||||
[self.args.experiment, self.args.all_commits, self.args.rev]
|
||||
):
|
||||
raise InvalidArgumentError(
|
||||
"Either provide an `experiment` argument, or use the "
|
||||
"`--rev` or `--all-commits` flag."
|
||||
@staticmethod
|
||||
def log_result(result: Dict[str, Any], remote: str):
|
||||
from dvc.utils import humanize
|
||||
|
||||
def join_exps(exps):
|
||||
return humanize.join([f"[bold]{e}[/]" for e in exps])
|
||||
|
||||
if diverged_exps := result.get("diverged"):
|
||||
exps = join_exps(diverged_exps)
|
||||
ui.error_write(
|
||||
f"[yellow]Local experiment {exps} has diverged "
|
||||
"from remote experiment with the same name.\n"
|
||||
"To override the remote experiment re-run with '--force'.",
|
||||
styled=True,
|
||||
)
|
||||
if uptodate_exps := result.get("up_to_date"):
|
||||
exps = join_exps(uptodate_exps)
|
||||
verb = "are" if len(uptodate_exps) > 1 else "is"
|
||||
ui.write(
|
||||
f"Experiment {exps} {verb} up to date on Git remote {remote!r}.",
|
||||
styled=True,
|
||||
)
|
||||
if pushed_exps := result.get("success"):
|
||||
exps = join_exps(pushed_exps)
|
||||
ui.write(f"Pushed experiment {exps} to Git remote {remote!r}.", styled=True)
|
||||
if not uptodate_exps and not pushed_exps:
|
||||
ui.write("No experiments to push.")
|
||||
|
||||
if uploaded := result.get("uploaded"):
|
||||
stats = {"uploaded": uploaded}
|
||||
ui.write(humanize.get_summary(stats.items()))
|
||||
|
||||
if project_url := result.get("url"):
|
||||
ui.rich_print(
|
||||
"View your experiments at", project_url, style="yellow", soft_wrap=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
from dvc.repo.experiments.push import UploadError
|
||||
|
||||
self.raise_error_if_all_disabled()
|
||||
|
||||
pushed_exps = self.repo.experiments.push(
|
||||
self.args.git_remote,
|
||||
self.args.experiment,
|
||||
all_commits=self.args.all_commits,
|
||||
rev=self.args.rev,
|
||||
num=self.args.num,
|
||||
force=self.args.force,
|
||||
push_cache=self.args.push_cache,
|
||||
dvc_remote=self.args.dvc_remote,
|
||||
jobs=self.args.jobs,
|
||||
run_cache=self.args.run_cache,
|
||||
)
|
||||
|
||||
if pushed_exps:
|
||||
ui.write(
|
||||
f"Pushed experiment '{pushed_exps}'"
|
||||
f"to Git remote '{self.args.git_remote}'."
|
||||
try:
|
||||
result = self.repo.experiments.push(
|
||||
self.args.git_remote,
|
||||
self.args.experiment,
|
||||
all_commits=self.args.all_commits,
|
||||
rev=self.args.rev,
|
||||
num=self.args.num,
|
||||
force=self.args.force,
|
||||
push_cache=self.args.push_cache,
|
||||
dvc_remote=self.args.dvc_remote,
|
||||
jobs=self.args.jobs,
|
||||
run_cache=self.args.run_cache,
|
||||
)
|
||||
else:
|
||||
ui.write("No experiments to push.")
|
||||
except UploadError as e:
|
||||
self.log_result(e.result, self.args.git_remote)
|
||||
raise
|
||||
|
||||
self.log_result(result, self.args.git_remote)
|
||||
if not self.args.push_cache:
|
||||
ui.write(
|
||||
"To push cached outputs",
|
||||
"for this experiment to DVC remote storage,"
|
||||
"re-run this command without '--no-cache'.",
|
||||
(
|
||||
"for this experiment to DVC remote storage,"
|
||||
"re-run this command without '--no-cache'."
|
||||
),
|
||||
)
|
||||
|
||||
return 0
|
||||
@@ -65,7 +92,7 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
help=EXPERIMENTS_PUSH_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
add_rev_selection_flags(experiments_push_parser, "Push", False)
|
||||
add_rev_selection_flags(experiments_push_parser, "Push", True)
|
||||
experiments_push_parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
@@ -76,10 +103,7 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
"--no-cache",
|
||||
action="store_false",
|
||||
dest="push_cache",
|
||||
help=(
|
||||
"Do not push cached outputs for this experiment to DVC remote "
|
||||
"storage."
|
||||
),
|
||||
help="Do not push cached outputs for this experiment to DVC remote storage.",
|
||||
)
|
||||
experiments_push_parser.add_argument(
|
||||
"-r",
|
||||
@@ -93,10 +117,7 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
"--jobs",
|
||||
type=int,
|
||||
metavar="<number>",
|
||||
help=(
|
||||
"Number of jobs to run simultaneously when pushing to DVC remote "
|
||||
"storage."
|
||||
),
|
||||
help="Number of jobs to run simultaneously when pushing to DVC remote storage.",
|
||||
)
|
||||
experiments_push_parser.add_argument(
|
||||
"--run-cache",
|
||||
|
||||
@@ -9,7 +9,9 @@ class CmdQueueWorker(CmdBase):
|
||||
"""Run the exp queue worker."""
|
||||
|
||||
def run(self):
|
||||
self.repo.experiments.celery_queue.worker.start(self.args.name)
|
||||
self.repo.experiments.celery_queue.worker.start(
|
||||
self.args.name, fsapp_clean=self.args.clean
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
@@ -22,4 +24,9 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
add_help=False,
|
||||
)
|
||||
parser.add_argument("name", help="Celery worker name.")
|
||||
parser.add_argument(
|
||||
"--clean",
|
||||
action="store_true",
|
||||
help="Automatically cleanup celery broker on shutdown.",
|
||||
)
|
||||
parser.set_defaults(func=CmdQueueWorker)
|
||||
|
||||
@@ -24,10 +24,11 @@ class CmdExperimentsRemove(CmdBase):
|
||||
)
|
||||
|
||||
def run(self):
|
||||
from dvc.utils import humanize
|
||||
|
||||
self.check_arguments()
|
||||
|
||||
removed_list = self.repo.experiments.remove(
|
||||
removed = self.repo.experiments.remove(
|
||||
exp_names=self.args.experiment,
|
||||
all_commits=self.args.all_commits,
|
||||
rev=self.args.rev,
|
||||
@@ -35,8 +36,10 @@ class CmdExperimentsRemove(CmdBase):
|
||||
queue=self.args.queue,
|
||||
git_remote=self.args.git_remote,
|
||||
)
|
||||
removed = ",".join(removed_list)
|
||||
ui.write(f"Removed experiments: {removed}")
|
||||
if removed:
|
||||
ui.write(f"Removed experiments: {humanize.join(map(repr, removed))}")
|
||||
else:
|
||||
ui.write("No experiments to remove.")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
@@ -1,57 +1,32 @@
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from dvc.cli import completion
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.commands.repro import CmdRepro
|
||||
from dvc.commands.repro import add_arguments as add_repro_arguments
|
||||
from dvc.exceptions import InvalidArgumentError
|
||||
from dvc.ui import ui
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CmdExperimentsRun(CmdRepro):
|
||||
def run(self):
|
||||
from dvc.compare import show_metrics
|
||||
|
||||
if self.args.checkpoint_resume:
|
||||
if self.args.reset:
|
||||
raise InvalidArgumentError(
|
||||
"--reset and --rev are mutually exclusive."
|
||||
)
|
||||
if not (self.args.queue or self.args.tmp_dir):
|
||||
raise InvalidArgumentError(
|
||||
"--rev can only be used in conjunction with "
|
||||
"--queue or --temp."
|
||||
)
|
||||
|
||||
if self.args.reset:
|
||||
ui.write("Any existing checkpoints will be reset and re-run.")
|
||||
|
||||
results = self.repo.experiments.run(
|
||||
self.repo.experiments.run(
|
||||
name=self.args.name,
|
||||
queue=self.args.queue,
|
||||
run_all=self.args.run_all,
|
||||
jobs=self.args.jobs,
|
||||
params=self.args.set_param,
|
||||
checkpoint_resume=self.args.checkpoint_resume,
|
||||
reset=self.args.reset,
|
||||
tmp_dir=self.args.tmp_dir,
|
||||
machine=self.args.machine,
|
||||
copy_paths=self.args.copy_paths,
|
||||
message=self.args.message,
|
||||
**self._common_kwargs,
|
||||
)
|
||||
|
||||
if self.args.metrics and results:
|
||||
metrics = self.repo.metrics.show(revs=list(results))
|
||||
metrics.pop("workspace", None)
|
||||
show_metrics(metrics)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def add_parser(experiments_subparsers, parent_parser):
|
||||
|
||||
EXPERIMENTS_RUN_HELP = "Run or resume an experiment."
|
||||
experiments_run_parser = experiments_subparsers.add_parser(
|
||||
"run",
|
||||
@@ -61,22 +36,6 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
_add_run_common(experiments_run_parser)
|
||||
experiments_run_parser.add_argument(
|
||||
"-r",
|
||||
"--rev",
|
||||
type=str,
|
||||
dest="checkpoint_resume",
|
||||
help=(
|
||||
"Continue the specified checkpoint experiment. Can only be used "
|
||||
"in conjunction with --queue or --temp."
|
||||
),
|
||||
metavar="<experiment_rev>",
|
||||
).complete = completion.EXPERIMENT
|
||||
experiments_run_parser.add_argument(
|
||||
"--reset",
|
||||
action="store_true",
|
||||
help="Reset existing checkpoints and restart the experiment.",
|
||||
)
|
||||
experiments_run_parser.set_defaults(func=CmdExperimentsRun)
|
||||
|
||||
|
||||
@@ -140,3 +99,20 @@ def _add_run_common(parser):
|
||||
# )
|
||||
# metavar="<name>",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-C",
|
||||
"--copy-paths",
|
||||
action="append",
|
||||
default=[],
|
||||
help=(
|
||||
"List of ignored or untracked paths to copy into the temp directory."
|
||||
" Only used if `--temp` or `--queue` is specified."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"-m",
|
||||
"--message",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Custom commit message to use when committing the experiment.",
|
||||
)
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.ui import ui
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CmdExperimentsSave(CmdBase):
|
||||
def run(self):
|
||||
try:
|
||||
ref = self.repo.experiments.save(
|
||||
name=self.args.name,
|
||||
force=self.args.force,
|
||||
include_untracked=self.args.include_untracked,
|
||||
message=self.args.message,
|
||||
)
|
||||
except DvcException:
|
||||
logger.exception("failed to save experiment")
|
||||
return 1
|
||||
|
||||
if self.args.json:
|
||||
ui.write_json({"ref": ref})
|
||||
else:
|
||||
name = self.repo.experiments.get_exact_name([ref])[ref]
|
||||
ui.write(f"Experiment has been saved as: {name}")
|
||||
ui.write(
|
||||
"\nTo promote an experiment to a Git branch run:\n\n"
|
||||
"\tdvc exp branch <exp> <branch>\n"
|
||||
)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def add_parser(experiments_subparsers, parent_parser):
|
||||
EXPERIMENTS_SAVE_HELP = "Save current workspace as an experiment."
|
||||
save_parser = experiments_subparsers.add_parser(
|
||||
"save",
|
||||
parents=[parent_parser],
|
||||
description=append_doc_link(EXPERIMENTS_SAVE_HELP, "exp/save"),
|
||||
help=EXPERIMENTS_SAVE_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
save_parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Replace experiment if it already exists.",
|
||||
)
|
||||
save_parser.add_argument(
|
||||
"--json",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Show output in JSON format.",
|
||||
)
|
||||
save_parser.add_argument(
|
||||
"-n",
|
||||
"--name",
|
||||
default=None,
|
||||
help=(
|
||||
"Human-readable experiment name. If not specified, a name will "
|
||||
"be auto-generated."
|
||||
),
|
||||
metavar="<name>",
|
||||
)
|
||||
save_parser.add_argument(
|
||||
"-I",
|
||||
"--include-untracked",
|
||||
action="append",
|
||||
default=[],
|
||||
help="List of untracked paths to include in the experiment.",
|
||||
metavar="<path>",
|
||||
)
|
||||
save_parser.add_argument(
|
||||
"-M",
|
||||
"--message",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Custom commit message to use when committing the experiment.",
|
||||
)
|
||||
save_parser.set_defaults(func=CmdExperimentsSave)
|
||||
@@ -1,20 +1,16 @@
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from collections import Counter, OrderedDict, defaultdict
|
||||
from datetime import date, datetime
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Dict, Iterable
|
||||
|
||||
from funcy import lmap
|
||||
|
||||
from dvc.cli import completion
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.commands.metrics import DEFAULT_PRECISION
|
||||
from dvc.exceptions import DvcException, InvalidArgumentError
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.ui import ui
|
||||
from dvc.utils.flatten import flatten
|
||||
from dvc.utils.serialize import encode_exception
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -28,269 +24,13 @@ FILL_VALUE_ERRORED = "!"
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _update_names(names, items):
|
||||
for name, item in items:
|
||||
item = item.get("data", {})
|
||||
if isinstance(item, dict):
|
||||
item = flatten(item)
|
||||
names[name].update({key: None for key in item})
|
||||
|
||||
|
||||
def _collect_names(all_experiments, **kwargs):
|
||||
metric_names = defaultdict(dict)
|
||||
param_names = defaultdict(dict)
|
||||
deps_names = set()
|
||||
|
||||
for _, experiments in all_experiments.items():
|
||||
for exp_data in experiments.values():
|
||||
exp = exp_data.get("data", {})
|
||||
_update_names(metric_names, exp.get("metrics", {}).items())
|
||||
_update_names(param_names, exp.get("params", {}).items())
|
||||
for dep_name in exp.get("deps", {}):
|
||||
deps_names.add(dep_name)
|
||||
|
||||
return metric_names, param_names, sorted(deps_names)
|
||||
|
||||
|
||||
experiment_types = {
|
||||
"checkpoint_tip": "│ ╓",
|
||||
"checkpoint_commit": "│ ╟",
|
||||
"checkpoint_base": "├─╨",
|
||||
"branch_commit": "├──",
|
||||
"branch_base": "└──",
|
||||
"baseline": "",
|
||||
}
|
||||
|
||||
|
||||
def _collect_rows(
|
||||
base_rev,
|
||||
experiments,
|
||||
all_headers,
|
||||
metric_headers,
|
||||
param_headers,
|
||||
metric_names,
|
||||
param_names,
|
||||
deps_names,
|
||||
precision=DEFAULT_PRECISION,
|
||||
sort_by=None,
|
||||
sort_order=None,
|
||||
fill_value=FILL_VALUE,
|
||||
iso=False,
|
||||
):
|
||||
from scmrepo.git import Git
|
||||
|
||||
if sort_by:
|
||||
sort_path, sort_name, sort_type = _sort_column(
|
||||
sort_by, metric_names, param_names
|
||||
)
|
||||
reverse = sort_order == "desc"
|
||||
experiments = _sort_exp(
|
||||
experiments, sort_path, sort_name, sort_type, reverse
|
||||
)
|
||||
|
||||
new_checkpoint = True
|
||||
for i, (rev, results) in enumerate(experiments.items()):
|
||||
fill_value = FILL_VALUE_ERRORED if results.get("error") else fill_value
|
||||
row_dict = {k: fill_value for k in all_headers}
|
||||
|
||||
exp = results.get("data", {})
|
||||
|
||||
state = exp.get("status")
|
||||
if state == "Success":
|
||||
state = fill_value
|
||||
|
||||
is_baseline = rev == "baseline"
|
||||
|
||||
if is_baseline:
|
||||
name_rev = base_rev[:7] if Git.is_sha(base_rev) else base_rev
|
||||
else:
|
||||
name_rev = rev[:7]
|
||||
|
||||
tip = exp.get("checkpoint_tip")
|
||||
parent_rev = exp.get("checkpoint_parent", "")
|
||||
parent_exp = experiments.get(parent_rev, {}).get("data", {})
|
||||
parent_tip = parent_exp.get("checkpoint_tip")
|
||||
|
||||
parent = ""
|
||||
if is_baseline:
|
||||
typ = "baseline"
|
||||
elif tip:
|
||||
if tip == parent_tip:
|
||||
typ = (
|
||||
"checkpoint_tip" if new_checkpoint else "checkpoint_commit"
|
||||
)
|
||||
elif parent_rev == base_rev:
|
||||
typ = "checkpoint_base"
|
||||
else:
|
||||
typ = "checkpoint_commit"
|
||||
parent = parent_rev[:7]
|
||||
elif i < len(experiments) - 1:
|
||||
typ = "branch_commit"
|
||||
else:
|
||||
typ = "branch_base"
|
||||
|
||||
if not is_baseline:
|
||||
new_checkpoint = not (tip and tip == parent_tip)
|
||||
|
||||
row_dict["Experiment"] = exp.get("name", "")
|
||||
row_dict["rev"] = name_rev
|
||||
row_dict["typ"] = typ
|
||||
row_dict["Created"] = format_time(
|
||||
exp.get("timestamp"), fill_value, iso
|
||||
)
|
||||
row_dict["parent"] = parent
|
||||
row_dict["State"] = state
|
||||
row_dict["Executor"] = exp.get("executor", fill_value)
|
||||
|
||||
_extend_row(
|
||||
row_dict,
|
||||
metric_names,
|
||||
metric_headers,
|
||||
exp.get("metrics", {}).items(),
|
||||
precision,
|
||||
fill_value=fill_value,
|
||||
)
|
||||
_extend_row(
|
||||
row_dict,
|
||||
param_names,
|
||||
param_headers,
|
||||
exp.get("params", {}).items(),
|
||||
precision,
|
||||
fill_value=fill_value,
|
||||
)
|
||||
for dep in deps_names:
|
||||
hash_info = exp.get("deps", {}).get(dep, {}).get("hash")
|
||||
if hash_info is not None:
|
||||
hash_info = hash_info[:7]
|
||||
row_dict[dep] = hash_info
|
||||
yield list(row_dict.values())
|
||||
|
||||
|
||||
def _sort_column(sort_by, metric_names, param_names):
|
||||
path, _, sort_name = sort_by.rpartition(":")
|
||||
matches = set()
|
||||
|
||||
if path:
|
||||
if path in metric_names and sort_name in metric_names[path]:
|
||||
matches.add((path, sort_name, "metrics"))
|
||||
if path in param_names and sort_name in param_names[path]:
|
||||
matches.add((path, sort_name, "params"))
|
||||
else:
|
||||
for path in metric_names:
|
||||
if sort_name in metric_names[path]:
|
||||
matches.add((path, sort_name, "metrics"))
|
||||
for path in param_names:
|
||||
if sort_name in param_names[path]:
|
||||
matches.add((path, sort_name, "params"))
|
||||
|
||||
if len(matches) == 1:
|
||||
return matches.pop()
|
||||
if len(matches) > 1:
|
||||
raise InvalidArgumentError(
|
||||
"Ambiguous sort column '{}' matched '{}'".format(
|
||||
sort_by,
|
||||
", ".join([f"{path}:{name}" for path, name, _ in matches]),
|
||||
)
|
||||
)
|
||||
raise InvalidArgumentError(f"Unknown sort column '{sort_by}'")
|
||||
|
||||
|
||||
def _sort_exp(experiments, sort_path, sort_name, typ, reverse):
|
||||
def _sort(item):
|
||||
rev, exp = item
|
||||
exp_data = exp.get("data", {})
|
||||
tip = exp_data.get("checkpoint_tip")
|
||||
if tip and tip != rev:
|
||||
# Sort checkpoint experiments by tip commit
|
||||
return _sort((tip, experiments[tip]))
|
||||
data = exp_data.get(typ, {}).get(sort_path, {}).get("data", {})
|
||||
val = flatten(data).get(sort_name)
|
||||
return val is None, val
|
||||
|
||||
ret = OrderedDict()
|
||||
if "baseline" in experiments:
|
||||
ret["baseline"] = experiments.pop("baseline")
|
||||
|
||||
ret.update(sorted(experiments.items(), key=_sort, reverse=reverse))
|
||||
return ret
|
||||
|
||||
|
||||
def format_time(datetime_obj, fill_value=FILL_VALUE, iso=False):
|
||||
if datetime_obj is None:
|
||||
return fill_value
|
||||
|
||||
if iso:
|
||||
return datetime_obj.isoformat()
|
||||
|
||||
if datetime_obj.date() == date.today():
|
||||
fmt = "%I:%M %p"
|
||||
else:
|
||||
fmt = "%b %d, %Y"
|
||||
return datetime_obj.strftime(fmt)
|
||||
|
||||
|
||||
def _extend_row(row, names, headers, items, precision, fill_value=FILL_VALUE):
|
||||
from dvc.compare import _format_field, with_value
|
||||
|
||||
for fname, data in items:
|
||||
item = data.get("data", {})
|
||||
item = flatten(item) if isinstance(item, dict) else {fname: item}
|
||||
for name in names[fname]:
|
||||
value = with_value(
|
||||
item.get(name),
|
||||
FILL_VALUE_ERRORED if data.get("error", None) else fill_value,
|
||||
)
|
||||
# wrap field data in ui.rich_text, otherwise rich may
|
||||
# interpret unescaped braces from list/dict types as rich
|
||||
# markup tags
|
||||
value = ui.rich_text(str(_format_field(value, precision)))
|
||||
if name in headers:
|
||||
row[name] = value
|
||||
else:
|
||||
row[f"{fname}:{name}"] = value
|
||||
|
||||
|
||||
def experiments_table(
|
||||
all_experiments,
|
||||
headers,
|
||||
metric_headers,
|
||||
metric_names,
|
||||
param_headers,
|
||||
param_names,
|
||||
deps_names,
|
||||
sort_by=None,
|
||||
sort_order=None,
|
||||
precision=DEFAULT_PRECISION,
|
||||
fill_value=FILL_VALUE,
|
||||
iso=False,
|
||||
) -> "TabularData":
|
||||
from funcy import lconcat
|
||||
|
||||
from dvc.compare import TabularData
|
||||
|
||||
all_headers = lconcat(headers, metric_headers, param_headers, deps_names)
|
||||
td = TabularData(all_headers, fill_value=fill_value)
|
||||
for base_rev, experiments in all_experiments.items():
|
||||
rows = _collect_rows(
|
||||
base_rev,
|
||||
experiments,
|
||||
all_headers,
|
||||
metric_headers,
|
||||
param_headers,
|
||||
metric_names,
|
||||
param_names,
|
||||
deps_names,
|
||||
sort_by=sort_by,
|
||||
sort_order=sort_order,
|
||||
precision=precision,
|
||||
fill_value=fill_value,
|
||||
iso=iso,
|
||||
)
|
||||
td.extend(rows)
|
||||
|
||||
return td
|
||||
|
||||
|
||||
def prepare_exp_id(kwargs) -> "RichText":
|
||||
exp_name = kwargs["Experiment"]
|
||||
rev = kwargs["rev"]
|
||||
@@ -314,52 +54,18 @@ def baseline_styler(typ):
|
||||
return {"style": "bold"} if typ == "baseline" else {}
|
||||
|
||||
|
||||
def show_experiments(
|
||||
all_experiments,
|
||||
def show_experiments( # noqa: C901
|
||||
td: "TabularData",
|
||||
headers: Dict[str, Iterable[str]],
|
||||
keep=None,
|
||||
drop=None,
|
||||
pager=True,
|
||||
csv=False,
|
||||
markdown=False,
|
||||
pcp=False,
|
||||
**kwargs,
|
||||
):
|
||||
from funcy.seqs import flatten as flatten_list
|
||||
|
||||
metric_names, param_names, deps_names = _collect_names(all_experiments)
|
||||
|
||||
headers = [
|
||||
"Experiment",
|
||||
"rev",
|
||||
"typ",
|
||||
"Created",
|
||||
"parent",
|
||||
"State",
|
||||
"Executor",
|
||||
]
|
||||
|
||||
names = {**metric_names, **param_names}
|
||||
counter = Counter(flatten_list([list(a.keys()) for a in names.values()]))
|
||||
counter.update(headers)
|
||||
metric_headers = _normalize_headers(metric_names, counter)
|
||||
param_headers = _normalize_headers(param_names, counter)
|
||||
|
||||
td = experiments_table(
|
||||
all_experiments,
|
||||
headers,
|
||||
metric_headers,
|
||||
metric_names,
|
||||
param_headers,
|
||||
param_names,
|
||||
deps_names,
|
||||
kwargs.get("sort_by"),
|
||||
kwargs.get("sort_order"),
|
||||
kwargs.get("precision"),
|
||||
kwargs.get("fill_value"),
|
||||
kwargs.get("iso"),
|
||||
)
|
||||
if keep:
|
||||
for col in td.keys():
|
||||
for col in td.keys(): # noqa: SIM118
|
||||
if re.match(keep, col):
|
||||
td.protect(col)
|
||||
|
||||
@@ -371,16 +77,9 @@ def show_experiments(
|
||||
|
||||
if not csv:
|
||||
merge_headers = ["Experiment", "rev", "typ", "parent"]
|
||||
td.column("Experiment")[:] = map(
|
||||
prepare_exp_id, td.as_dict(merge_headers)
|
||||
)
|
||||
td.column("Experiment")[:] = map(prepare_exp_id, td.as_dict(merge_headers))
|
||||
td.drop(*merge_headers[1:])
|
||||
|
||||
headers = {
|
||||
"metrics": metric_headers,
|
||||
"params": param_headers,
|
||||
"deps": deps_names,
|
||||
}
|
||||
styles = {
|
||||
"Experiment": {"no_wrap": True, "header_style": "black on grey93"},
|
||||
"Created": {"header_style": "black on grey93"},
|
||||
@@ -405,51 +104,23 @@ def show_experiments(
|
||||
}
|
||||
)
|
||||
|
||||
if kwargs.get("only_changed", False) or pcp:
|
||||
if kwargs.get("only_changed", False):
|
||||
td.drop_duplicates("cols", ignore_empty=False)
|
||||
|
||||
cols_to_drop = set()
|
||||
if drop is not None:
|
||||
cols_to_drop = {col for col in td.keys() if re.match(drop, col)}
|
||||
if pcp:
|
||||
cols_to_drop.add("Created")
|
||||
cols_to_drop = {col for col in td.keys() if re.match(drop, col)} # noqa: SIM118
|
||||
td.drop(*cols_to_drop)
|
||||
|
||||
if pcp:
|
||||
subset = {x for x in td.keys() if x != "Experiment"}
|
||||
td.dropna(
|
||||
"rows",
|
||||
how="all",
|
||||
subset=subset,
|
||||
)
|
||||
td.drop_duplicates("rows", subset=subset)
|
||||
if "Experiment" in td:
|
||||
td.column("Experiment")[:] = [
|
||||
# remove tree characters
|
||||
str(x).encode("ascii", "ignore").strip().decode()
|
||||
for x in td.column("Experiment")
|
||||
]
|
||||
out = kwargs.get("out") or "dvc_plots"
|
||||
output_file = os.path.join(out, "index.html")
|
||||
ui.write(
|
||||
td.to_parallel_coordinates(
|
||||
output_path=os.path.abspath(output_file),
|
||||
color_by=kwargs.get("sort_by") or "Experiment",
|
||||
)
|
||||
)
|
||||
if kwargs.get("open"):
|
||||
return ui.open_browser(output_file)
|
||||
|
||||
else:
|
||||
td.render(
|
||||
pager=pager,
|
||||
borders="horizontals",
|
||||
rich_table=True,
|
||||
header_styles=styles,
|
||||
row_styles=row_styles,
|
||||
csv=csv,
|
||||
markdown=markdown,
|
||||
)
|
||||
td.render(
|
||||
pager=pager,
|
||||
borders="horizontals",
|
||||
rich_table=True,
|
||||
header_styles=styles,
|
||||
row_styles=row_styles,
|
||||
csv=csv,
|
||||
markdown=markdown,
|
||||
)
|
||||
|
||||
|
||||
def _normalize_headers(names, count):
|
||||
@@ -468,8 +139,10 @@ def _format_json(item):
|
||||
|
||||
class CmdExperimentsShow(CmdBase):
|
||||
def run(self):
|
||||
from dvc.repo.experiments.show import tabulate
|
||||
|
||||
try:
|
||||
all_experiments = self.repo.experiments.show(
|
||||
exps = self.repo.experiments.show(
|
||||
all_branches=self.args.all_branches,
|
||||
all_tags=self.args.all_tags,
|
||||
all_commits=self.args.all_commits,
|
||||
@@ -480,38 +153,40 @@ class CmdExperimentsShow(CmdBase):
|
||||
sha_only=self.args.sha,
|
||||
param_deps=self.args.param_deps,
|
||||
fetch_running=self.args.fetch_running,
|
||||
force=self.args.force,
|
||||
)
|
||||
except DvcException:
|
||||
logger.exception("failed to show experiments")
|
||||
return 1
|
||||
|
||||
if self.args.json:
|
||||
ui.write_json(all_experiments, default=_format_json)
|
||||
ui.write_json([exp.dumpd() for exp in exps], default=_format_json)
|
||||
else:
|
||||
precision = (
|
||||
self.args.precision or None
|
||||
if self.args.csv
|
||||
else DEFAULT_PRECISION
|
||||
self.args.precision or None if self.args.csv else DEFAULT_PRECISION
|
||||
)
|
||||
fill_value = "" if self.args.csv else FILL_VALUE
|
||||
iso = True if self.args.csv else False
|
||||
iso = self.args.csv
|
||||
td, headers = tabulate(
|
||||
exps,
|
||||
precision=precision,
|
||||
fill_value=fill_value,
|
||||
iso=iso,
|
||||
sort_by=self.args.sort_by,
|
||||
sort_order=self.args.sort_order,
|
||||
)
|
||||
|
||||
show_experiments(
|
||||
all_experiments,
|
||||
td,
|
||||
headers,
|
||||
keep=self.args.keep,
|
||||
drop=self.args.drop,
|
||||
sort_by=self.args.sort_by,
|
||||
sort_order=self.args.sort_order,
|
||||
precision=precision,
|
||||
fill_value=fill_value,
|
||||
iso=iso,
|
||||
pager=not self.args.no_pager,
|
||||
csv=self.args.csv,
|
||||
markdown=self.args.markdown,
|
||||
only_changed=self.args.only_changed,
|
||||
pcp=self.args.pcp,
|
||||
out=self.args.out,
|
||||
open=self.args.open,
|
||||
)
|
||||
return 0
|
||||
|
||||
@@ -580,10 +255,7 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
)
|
||||
experiments_show_parser.add_argument(
|
||||
"--sort-order",
|
||||
help=(
|
||||
"Sort order to use with --sort-by."
|
||||
" Defaults to ascending ('asc')."
|
||||
),
|
||||
help="Sort order to use with --sort-by. Defaults to ascending ('asc').",
|
||||
choices=("asc", "desc"),
|
||||
default="asc",
|
||||
)
|
||||
@@ -607,21 +279,18 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
)
|
||||
experiments_show_parser.add_argument(
|
||||
"--json",
|
||||
"--show-json",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Print output in JSON format instead of a human-readable table.",
|
||||
)
|
||||
experiments_show_parser.add_argument(
|
||||
"--csv",
|
||||
"--show-csv",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Print output in csv format instead of a human-readable table.",
|
||||
)
|
||||
experiments_show_parser.add_argument(
|
||||
"--md",
|
||||
"--show-md",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest="markdown",
|
||||
@@ -636,31 +305,16 @@ def add_parser(experiments_subparsers, parent_parser):
|
||||
),
|
||||
metavar="<n>",
|
||||
)
|
||||
experiments_show_parser.add_argument(
|
||||
"--parallel-coordinates-plot",
|
||||
"--pcp",
|
||||
dest="pcp",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Generate a Parallel Coordinates Plot from the tabulated output.",
|
||||
)
|
||||
experiments_show_parser.add_argument(
|
||||
"-o",
|
||||
"--out",
|
||||
default=None,
|
||||
help="Destination folder to save the Parallel Coordinates Plot to",
|
||||
metavar="<path>",
|
||||
).complete = completion.DIR
|
||||
experiments_show_parser.add_argument(
|
||||
"--open",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Open the Parallel Coordinates Plot directly in the browser.",
|
||||
)
|
||||
experiments_show_parser.add_argument(
|
||||
"--no-fetch",
|
||||
dest="fetch_running",
|
||||
action="store_false",
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
experiments_show_parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
action="store_true",
|
||||
help="Force re-collection of experiments instead of loading from exp cache.",
|
||||
)
|
||||
experiments_show_parser.set_defaults(func=CmdExperimentsShow)
|
||||
|
||||
+22
-12
@@ -10,7 +10,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CmdGC(CmdBase):
|
||||
def run(self):
|
||||
def run(self): # noqa: C901, PLR0912
|
||||
from dvc.repo.gc import _validate_args
|
||||
|
||||
_validate_args(
|
||||
@@ -22,6 +22,8 @@ class CmdGC(CmdBase):
|
||||
workspace=self.args.workspace,
|
||||
rev=self.args.rev,
|
||||
num=self.args.num,
|
||||
cloud=self.args.cloud,
|
||||
not_in_remote=self.args.not_in_remote,
|
||||
)
|
||||
|
||||
if self.args.rev:
|
||||
@@ -40,17 +42,16 @@ class CmdGC(CmdBase):
|
||||
elif self.args.all_tags:
|
||||
msg += " and all git tags"
|
||||
if self.args.commit_date:
|
||||
msg += (
|
||||
" and all git commits before date "
|
||||
f"{self.args.commit_date}"
|
||||
)
|
||||
msg += f" and all git commits before date {self.args.commit_date}"
|
||||
if self.args.rev:
|
||||
msg += (
|
||||
f" and last {self.args.num} commits from {self.args.rev}"
|
||||
)
|
||||
msg += f" and last {self.args.num} commits from {self.args.rev}"
|
||||
|
||||
if self.args.all_experiments:
|
||||
msg += " and all experiments"
|
||||
|
||||
if self.args.not_in_remote:
|
||||
msg += " that are present in the DVC remote"
|
||||
|
||||
if self.args.repos:
|
||||
msg += " of the current and the following repos:"
|
||||
|
||||
@@ -79,6 +80,7 @@ class CmdGC(CmdBase):
|
||||
workspace=self.args.workspace,
|
||||
rev=self.args.rev,
|
||||
num=self.args.num,
|
||||
not_in_remote=self.args.not_in_remote,
|
||||
)
|
||||
return 0
|
||||
|
||||
@@ -162,12 +164,18 @@ def add_parser(subparsers, parent_parser):
|
||||
default=False,
|
||||
help="Keep data files for all experiments.",
|
||||
)
|
||||
gc_parser.add_argument(
|
||||
"--not-in-remote",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Keep data files that are not present in the remote.",
|
||||
)
|
||||
gc_parser.add_argument(
|
||||
"-c",
|
||||
"--cloud",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Collect garbage in remote repository.",
|
||||
help="Collect garbage in remote storage in addition to local cache.",
|
||||
)
|
||||
gc_parser.add_argument(
|
||||
"-r",
|
||||
@@ -198,9 +206,11 @@ def add_parser(subparsers, parent_parser):
|
||||
dest="repos",
|
||||
type=str,
|
||||
nargs="*",
|
||||
help="Keep data files required by these projects "
|
||||
"in addition to the current one. "
|
||||
"Useful if you share a single cache across repos.",
|
||||
help=(
|
||||
"Keep data files required by these projects "
|
||||
"in addition to the current one. "
|
||||
"Useful if you share a single cache across repos."
|
||||
),
|
||||
metavar="<paths>",
|
||||
)
|
||||
gc_parser.set_defaults(func=CmdGC)
|
||||
|
||||
+20
-15
@@ -3,10 +3,9 @@ import logging
|
||||
|
||||
from dvc.cli import completion
|
||||
from dvc.cli.command import CmdBaseNoRepo
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.exceptions import DvcException
|
||||
|
||||
from ..cli.utils import append_doc_link
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -15,14 +14,8 @@ class CmdGet(CmdBaseNoRepo):
|
||||
from dvc.api import get_url
|
||||
from dvc.ui import ui
|
||||
|
||||
try:
|
||||
url = get_url(
|
||||
self.args.path, repo=self.args.url, rev=self.args.rev
|
||||
)
|
||||
ui.write(url, force=True)
|
||||
except DvcException:
|
||||
logger.exception("failed to show URL")
|
||||
return 1
|
||||
url = get_url(self.args.path, repo=self.args.url, rev=self.args.rev)
|
||||
ui.write(url, force=True)
|
||||
|
||||
return 0
|
||||
|
||||
@@ -34,6 +27,7 @@ class CmdGet(CmdBaseNoRepo):
|
||||
|
||||
def _get_file_from_repo(self):
|
||||
from dvc.repo import Repo
|
||||
from dvc.scm import CloneError
|
||||
|
||||
try:
|
||||
Repo.get(
|
||||
@@ -42,13 +36,15 @@ class CmdGet(CmdBaseNoRepo):
|
||||
out=self.args.out,
|
||||
rev=self.args.rev,
|
||||
jobs=self.args.jobs,
|
||||
force=self.args.force,
|
||||
)
|
||||
return 0
|
||||
except CloneError:
|
||||
logger.exception("failed to get '%s'", self.args.path)
|
||||
return 1
|
||||
except DvcException:
|
||||
logger.exception(
|
||||
"failed to get '{}' from '{}'".format(
|
||||
self.args.path, self.args.url
|
||||
)
|
||||
"failed to get '%s' from '%s'", self.args.path, self.args.url
|
||||
)
|
||||
return 1
|
||||
|
||||
@@ -84,8 +80,10 @@ def add_parser(subparsers, parent_parser):
|
||||
get_parser.add_argument(
|
||||
"--show-url",
|
||||
action="store_true",
|
||||
help="Print the storage location (URL) the target data would be "
|
||||
"downloaded from, and exit.",
|
||||
help=(
|
||||
"Print the storage location (URL) the target data would be "
|
||||
"downloaded from, and exit."
|
||||
),
|
||||
)
|
||||
get_parser.add_argument(
|
||||
"-j",
|
||||
@@ -97,4 +95,11 @@ def add_parser(subparsers, parent_parser):
|
||||
),
|
||||
metavar="<number>",
|
||||
)
|
||||
get_parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Override local file or folder if exists.",
|
||||
)
|
||||
get_parser.set_defaults(func=CmdGet)
|
||||
|
||||
+14
-3
@@ -3,10 +3,9 @@ import logging
|
||||
|
||||
from dvc.cli import completion
|
||||
from dvc.cli.command import CmdBaseNoRepo
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.exceptions import DvcException
|
||||
|
||||
from ..cli.utils import append_doc_link
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -15,7 +14,12 @@ class CmdGetUrl(CmdBaseNoRepo):
|
||||
from dvc.repo import Repo
|
||||
|
||||
try:
|
||||
Repo.get_url(self.args.url, out=self.args.out, jobs=self.args.jobs)
|
||||
Repo.get_url(
|
||||
self.args.url,
|
||||
out=self.args.out,
|
||||
jobs=self.args.jobs,
|
||||
force=self.args.force,
|
||||
)
|
||||
return 0
|
||||
except DvcException:
|
||||
logger.exception("failed to get '%s'", self.args.url)
|
||||
@@ -47,4 +51,11 @@ def add_parser(subparsers, parent_parser):
|
||||
),
|
||||
metavar="<number>",
|
||||
)
|
||||
get_parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Override local file or folder if exists.",
|
||||
)
|
||||
get_parser.set_defaults(func=CmdGetUrl)
|
||||
|
||||
@@ -64,15 +64,15 @@ class CmdPrePush(CmdHookBase):
|
||||
|
||||
class CmdMergeDriver(CmdHookBase):
|
||||
def _run(self):
|
||||
from dvc.dvcfile import Dvcfile
|
||||
from dvc.dvcfile import load_file
|
||||
from dvc.repo import Repo
|
||||
|
||||
dvc = Repo()
|
||||
|
||||
try:
|
||||
ancestor = Dvcfile(dvc, self.args.ancestor, verify=False)
|
||||
our = Dvcfile(dvc, self.args.our, verify=False)
|
||||
their = Dvcfile(dvc, self.args.their, verify=False)
|
||||
ancestor = load_file(dvc, self.args.ancestor, verify=False)
|
||||
our = load_file(dvc, self.args.our, verify=False)
|
||||
their = load_file(dvc, self.args.their, verify=False)
|
||||
|
||||
our.merge(ancestor, their, allowed=["add", "remove", "change"])
|
||||
|
||||
|
||||
+12
-19
@@ -11,34 +11,32 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class CmdImport(CmdBase):
|
||||
def run(self):
|
||||
from dvc.scm import CloneError
|
||||
|
||||
try:
|
||||
self.repo.imp(
|
||||
self.args.url,
|
||||
self.args.path,
|
||||
out=self.args.out,
|
||||
fname=self.args.file,
|
||||
rev=self.args.rev,
|
||||
no_exec=self.args.no_exec,
|
||||
no_download=self.args.no_download,
|
||||
desc=self.args.desc,
|
||||
type=self.args.type,
|
||||
labels=self.args.labels,
|
||||
meta=self.args.meta,
|
||||
jobs=self.args.jobs,
|
||||
)
|
||||
except CloneError:
|
||||
logger.exception("failed to import '%s'", self.args.path)
|
||||
return 1
|
||||
except DvcException:
|
||||
logger.exception(
|
||||
"failed to import '{}' from '{}'.".format(
|
||||
self.args.path, self.args.url
|
||||
)
|
||||
"failed to import '%s' from '%s'.",
|
||||
self.args.path,
|
||||
self.args.url,
|
||||
)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def add_parser(subparsers, parent_parser):
|
||||
from .add import _add_annotating_args
|
||||
|
||||
IMPORT_HELP = (
|
||||
"Download file or directory tracked by DVC or by Git "
|
||||
"into the workspace, and track it."
|
||||
@@ -70,11 +68,6 @@ def add_parser(subparsers, parent_parser):
|
||||
help="Git revision (e.g. SHA, branch, tag)",
|
||||
metavar="<commit>",
|
||||
)
|
||||
import_parser.add_argument(
|
||||
"--file",
|
||||
help="Specify name of the .dvc file this command will generate.",
|
||||
metavar="<filename>",
|
||||
)
|
||||
no_download_exec_group = import_parser.add_mutually_exclusive_group()
|
||||
no_download_exec_group.add_argument(
|
||||
"--no-exec",
|
||||
@@ -86,8 +79,10 @@ def add_parser(subparsers, parent_parser):
|
||||
"--no-download",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Create .dvc file including target data hash value(s)"
|
||||
" but do not actually download the file(s).",
|
||||
help=(
|
||||
"Create .dvc file including target data hash value(s)"
|
||||
" but do not actually download the file(s)."
|
||||
),
|
||||
)
|
||||
import_parser.add_argument(
|
||||
"-j",
|
||||
@@ -99,6 +94,4 @@ def add_parser(subparsers, parent_parser):
|
||||
),
|
||||
metavar="<number>",
|
||||
)
|
||||
|
||||
_add_annotating_args(import_parser)
|
||||
import_parser.set_defaults(func=CmdImport)
|
||||
|
||||
+43
-48
@@ -15,35 +15,28 @@ class CmdImportUrl(CmdBase):
|
||||
self.repo.imp_url(
|
||||
self.args.url,
|
||||
out=self.args.out,
|
||||
fname=self.args.file,
|
||||
no_exec=self.args.no_exec,
|
||||
no_download=self.args.no_download,
|
||||
remote=self.args.remote,
|
||||
to_remote=self.args.to_remote,
|
||||
desc=self.args.desc,
|
||||
type=self.args.type,
|
||||
labels=self.args.labels,
|
||||
meta=self.args.meta,
|
||||
jobs=self.args.jobs,
|
||||
force=self.args.force,
|
||||
version_aware=self.args.version_aware,
|
||||
)
|
||||
except DvcException:
|
||||
logger.exception(
|
||||
"failed to import {}. You could also try downloading "
|
||||
"it manually, and adding it with `dvc add`.".format(
|
||||
self.args.url
|
||||
)
|
||||
(
|
||||
"failed to import %s. You could also try downloading "
|
||||
"it manually, and adding it with `dvc add`."
|
||||
),
|
||||
self.args.url,
|
||||
)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def add_parser(subparsers, parent_parser):
|
||||
from .add import _add_annotating_args
|
||||
|
||||
IMPORT_HELP = (
|
||||
"Download or copy file from URL and take it under DVC control."
|
||||
)
|
||||
IMPORT_HELP = "Download or copy file from URL and take it under DVC control."
|
||||
|
||||
import_parser = subparsers.add_parser(
|
||||
"import-url",
|
||||
@@ -54,39 +47,22 @@ def add_parser(subparsers, parent_parser):
|
||||
)
|
||||
import_parser.add_argument(
|
||||
"url",
|
||||
help="Location of the data to download. Supported URLs:\n"
|
||||
"/absolute/path/to/file/or/dir\n"
|
||||
"relative/path/to/file/or/dir\n"
|
||||
"C:\\\\path\\to\\file\\or\\dir\n"
|
||||
"https://example.com/path/to/file\n"
|
||||
"s3://bucket/key/path\n"
|
||||
"gs://bucket/path/to/file/or/dir\n"
|
||||
"hdfs://example.com/path/to/file\n"
|
||||
"ssh://example.com/absolute/path/to/file/or/dir\n"
|
||||
"remote://remote_name/path/to/file/or/dir (see `dvc remote`)",
|
||||
help=(
|
||||
"Location of the data to download. Supported URLs:\n"
|
||||
"/absolute/path/to/file/or/dir\n"
|
||||
"relative/path/to/file/or/dir\n"
|
||||
"C:\\\\path\\to\\file\\or\\dir\n"
|
||||
"https://example.com/path/to/file\n"
|
||||
"s3://bucket/key/path\n"
|
||||
"gs://bucket/path/to/file/or/dir\n"
|
||||
"hdfs://example.com/path/to/file\n"
|
||||
"ssh://example.com/absolute/path/to/file/or/dir\n"
|
||||
"remote://remote_name/path/to/file/or/dir (see `dvc remote`)"
|
||||
),
|
||||
)
|
||||
import_parser.add_argument(
|
||||
"out", nargs="?", help="Destination path to put files to."
|
||||
).complete = completion.DIR
|
||||
import_parser.add_argument(
|
||||
"--file",
|
||||
help="Specify name of the .dvc file this command will generate.",
|
||||
metavar="<filename>",
|
||||
).complete = completion.DIR
|
||||
no_download_exec_group = import_parser.add_mutually_exclusive_group()
|
||||
no_download_exec_group.add_argument(
|
||||
"--no-exec",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Only create .dvc file without actually importing target data.",
|
||||
)
|
||||
no_download_exec_group.add_argument(
|
||||
"--no-download",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Create .dvc file including target data hash value(s)"
|
||||
" but do not actually download the file(s).",
|
||||
)
|
||||
import_parser.add_argument(
|
||||
"--to-remote",
|
||||
action="store_true",
|
||||
@@ -99,6 +75,22 @@ def add_parser(subparsers, parent_parser):
|
||||
help="Remote storage to download to",
|
||||
metavar="<name>",
|
||||
)
|
||||
no_download_exec_group = import_parser.add_mutually_exclusive_group()
|
||||
no_download_exec_group.add_argument(
|
||||
"--no-exec",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Only create .dvc file without actually importing target data.",
|
||||
)
|
||||
no_download_exec_group.add_argument(
|
||||
"--no-download",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=(
|
||||
"Create .dvc file including target data hash value(s)"
|
||||
" but do not actually download the file(s)."
|
||||
),
|
||||
)
|
||||
import_parser.add_argument(
|
||||
"-j",
|
||||
"--jobs",
|
||||
@@ -109,14 +101,17 @@ def add_parser(subparsers, parent_parser):
|
||||
),
|
||||
metavar="<number>",
|
||||
)
|
||||
import_parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Override local file or folder if exists.",
|
||||
)
|
||||
import_parser.add_argument(
|
||||
"--version-aware",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=(
|
||||
"Import using cloud versioning. Implied if the URL contains a "
|
||||
"version ID."
|
||||
),
|
||||
help="Import using cloud versioning. Implied if the URL contains a version ID.",
|
||||
)
|
||||
_add_annotating_args(import_parser)
|
||||
import_parser.set_defaults(func=CmdImportUrl)
|
||||
|
||||
@@ -75,8 +75,9 @@ def add_parser(subparsers, parent_parser):
|
||||
"--no-scm",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Initiate DVC in directory that is "
|
||||
"not tracked by any SCM tool (e.g. Git).",
|
||||
help=(
|
||||
"Initiate DVC in directory that is not tracked by any SCM tool (e.g. Git)."
|
||||
),
|
||||
)
|
||||
init_parser.add_argument(
|
||||
"-f",
|
||||
@@ -84,8 +85,7 @@ def add_parser(subparsers, parent_parser):
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=(
|
||||
"Overwrite existing '.dvc/' directory. "
|
||||
"This operation removes local cache."
|
||||
"Overwrite existing '.dvc/' directory. This operation removes local cache."
|
||||
),
|
||||
)
|
||||
init_parser.add_argument(
|
||||
|
||||
@@ -31,7 +31,9 @@ def add_parser(subparsers, parent_parser):
|
||||
"--use-pre-commit-tool",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Install DVC hooks using pre-commit "
|
||||
"(https://pre-commit.com) if it is installed.",
|
||||
help=(
|
||||
"Install DVC hooks using pre-commit "
|
||||
"(https://pre-commit.com) if it is installed."
|
||||
),
|
||||
)
|
||||
install_parser.set_defaults(func=CmdInstall)
|
||||
|
||||
@@ -71,7 +71,6 @@ def add_parser(subparsers, parent_parser):
|
||||
)
|
||||
list_parser.add_argument(
|
||||
"--json",
|
||||
"--show-json",
|
||||
action="store_true",
|
||||
help="Show output in JSON format.",
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@ class LsColors:
|
||||
else:
|
||||
self._codes[code] = color
|
||||
|
||||
def format(self, entry):
|
||||
def format(self, entry): # noqa: A003
|
||||
text = entry["path"]
|
||||
|
||||
if entry.get("isout", False) and "out" in self._codes:
|
||||
|
||||
+14
-32
@@ -1,4 +1,5 @@
|
||||
import argparse
|
||||
from typing import Dict, List
|
||||
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.cli.utils import append_doc_link, fix_subparsers
|
||||
@@ -6,7 +7,6 @@ from dvc.commands.config import CmdConfig
|
||||
from dvc.compare import TabularData
|
||||
from dvc.config import ConfigError
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.types import Dict, List
|
||||
from dvc.ui import ui
|
||||
from dvc.utils import format_link
|
||||
|
||||
@@ -18,7 +18,6 @@ class MachineDisabledError(ConfigError):
|
||||
|
||||
class CmdMachineConfig(CmdConfig):
|
||||
def __init__(self, args):
|
||||
|
||||
super().__init__(args)
|
||||
if not self.config["feature"].get("machine", False):
|
||||
raise MachineDisabledError
|
||||
@@ -163,20 +162,18 @@ class CmdMachineRename(CmdBase):
|
||||
all_config = self.config.load_config_to_level(None)
|
||||
if self.args.new in all_config.get("machine", {}):
|
||||
raise ConfigError(
|
||||
"Rename failed. Machine '{}' already exists.".format(
|
||||
self.args.new
|
||||
)
|
||||
f"Rename failed. Machine '{self.args.new}' already exists."
|
||||
)
|
||||
ui.write(f"Rename machine '{self.args.name}' to '{self.args.new}'.")
|
||||
|
||||
def run(self):
|
||||
|
||||
self._check_before_rename()
|
||||
|
||||
with self.config.edit(self.args.level) as conf:
|
||||
self._check_exists(conf)
|
||||
conf["machine"][self.args.new] = conf["machine"][self.args.name]
|
||||
try:
|
||||
assert self.repo.machine
|
||||
self.repo.machine.rename(self.args.name, self.args.new)
|
||||
except DvcException as error:
|
||||
del conf["machine"][self.args.new]
|
||||
@@ -199,7 +196,7 @@ class CmdMachineDefault(CmdMachineConfig):
|
||||
if self.args.name is None and not self.args.unset:
|
||||
conf = self.config.read(self.args.level)
|
||||
try:
|
||||
print(conf["core"]["machine"])
|
||||
ui.write(conf["core"]["machine"])
|
||||
except KeyError:
|
||||
ui.write("No default machine set")
|
||||
return 1
|
||||
@@ -208,9 +205,7 @@ class CmdMachineDefault(CmdMachineConfig):
|
||||
if self.args.unset:
|
||||
conf["core"].pop("machine", None)
|
||||
else:
|
||||
merged_conf = self.config.load_config_to_level(
|
||||
self.args.level
|
||||
)
|
||||
merged_conf = self.config.load_config_to_level(self.args.level)
|
||||
if (
|
||||
self.args.name in conf["machine"]
|
||||
or self.args.name in merged_conf["machine"]
|
||||
@@ -218,8 +213,7 @@ class CmdMachineDefault(CmdMachineConfig):
|
||||
conf["core"]["machine"] = self.args.name
|
||||
else:
|
||||
raise ConfigError(
|
||||
"default machine must be present in machine "
|
||||
"list."
|
||||
"default machine must be present in machine list."
|
||||
)
|
||||
return 0
|
||||
|
||||
@@ -250,7 +244,6 @@ class CmdMachineStatus(CmdBase):
|
||||
all_status: List[Dict],
|
||||
td: TabularData,
|
||||
):
|
||||
|
||||
if not all_status:
|
||||
row = [
|
||||
name,
|
||||
@@ -309,7 +302,7 @@ class CmdMachineSsh(CmdBase):
|
||||
return 0
|
||||
|
||||
|
||||
def add_parser(subparsers, parent_parser):
|
||||
def add_parser(subparsers, parent_parser): # noqa: PLR0915
|
||||
from dvc.commands.config import parent_config_parser
|
||||
|
||||
machine_HELP = "Set up and manage cloud machines."
|
||||
@@ -324,7 +317,7 @@ def add_parser(subparsers, parent_parser):
|
||||
|
||||
machine_subparsers = machine_parser.add_subparsers(
|
||||
dest="cmd",
|
||||
help="Use `dvc machine CMD --help` for " "command-specific help.",
|
||||
help="Use `dvc machine CMD --help` for command-specific help.",
|
||||
)
|
||||
|
||||
fix_subparsers(machine_subparsers)
|
||||
@@ -342,8 +335,7 @@ def add_parser(subparsers, parent_parser):
|
||||
"cloud",
|
||||
help="Machine cloud. See full list of supported clouds at {}".format(
|
||||
format_link(
|
||||
"https://github.com/iterative/"
|
||||
"terraform-provider-iterative#machine"
|
||||
"https://github.com/iterative/terraform-provider-iterative#machine"
|
||||
)
|
||||
),
|
||||
)
|
||||
@@ -371,9 +363,7 @@ def add_parser(subparsers, parent_parser):
|
||||
help=machine_DEFAULT_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
machine_default_parser.add_argument(
|
||||
"name", nargs="?", help="Name of the machine"
|
||||
)
|
||||
machine_default_parser.add_argument("name", nargs="?", help="Name of the machine")
|
||||
machine_default_parser.add_argument(
|
||||
"-u",
|
||||
"--unset",
|
||||
@@ -413,9 +403,7 @@ def add_parser(subparsers, parent_parser):
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
machine_modify_parser.add_argument("name", help="Name of the machine")
|
||||
machine_modify_parser.add_argument(
|
||||
"option", help="Name of the option to modify."
|
||||
)
|
||||
machine_modify_parser.add_argument("option", help="Name of the option to modify.")
|
||||
machine_modify_parser.add_argument(
|
||||
"value", nargs="?", help="(optional) Value of the option."
|
||||
)
|
||||
@@ -448,9 +436,7 @@ def add_parser(subparsers, parent_parser):
|
||||
help=machine_REMOVE_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
machine_remove_parser.add_argument(
|
||||
"name", help="Name of the machine to remove."
|
||||
)
|
||||
machine_remove_parser.add_argument("name", help="Name of the machine to remove.")
|
||||
machine_remove_parser.set_defaults(func=CmdMachineRemove)
|
||||
|
||||
machine_CREATE_HELP = "Create and start a machine instance."
|
||||
@@ -461,14 +447,10 @@ def add_parser(subparsers, parent_parser):
|
||||
help=machine_CREATE_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
machine_create_parser.add_argument(
|
||||
"name", help="Name of the machine to create."
|
||||
)
|
||||
machine_create_parser.add_argument("name", help="Name of the machine to create.")
|
||||
machine_create_parser.set_defaults(func=CmdMachineCreate)
|
||||
|
||||
machine_STATUS_HELP = (
|
||||
"List the status of running instances for one/all machines."
|
||||
)
|
||||
machine_STATUS_HELP = "List the status of running instances for one/all machines."
|
||||
machine_status_parser = machine_subparsers.add_parser(
|
||||
"status",
|
||||
parents=[parent_parser],
|
||||
|
||||
@@ -140,14 +140,12 @@ def add_parser(subparsers, parent_parser):
|
||||
)
|
||||
metrics_show_parser.add_argument(
|
||||
"--json",
|
||||
"--show-json",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Show output in JSON format.",
|
||||
)
|
||||
metrics_show_parser.add_argument(
|
||||
"--md",
|
||||
"--show-md",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest="markdown",
|
||||
@@ -223,14 +221,12 @@ def add_parser(subparsers, parent_parser):
|
||||
)
|
||||
metrics_diff_parser.add_argument(
|
||||
"--json",
|
||||
"--show-json",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Show output in JSON format.",
|
||||
)
|
||||
metrics_diff_parser.add_argument(
|
||||
"--md",
|
||||
"--show-md",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest="markdown",
|
||||
|
||||
@@ -14,9 +14,7 @@ class CmdMove(CmdBase):
|
||||
try:
|
||||
self.repo.move(self.args.src, self.args.dst)
|
||||
except DvcException:
|
||||
msg = "failed to move '{}' -> '{}'".format(
|
||||
self.args.src, self.args.dst
|
||||
)
|
||||
msg = f"failed to move '{self.args.src}' -> '{self.args.dst}'"
|
||||
logger.exception(msg)
|
||||
return 1
|
||||
return 0
|
||||
@@ -40,7 +38,5 @@ def add_parser(subparsers, parent_parser):
|
||||
move_parser.add_argument(
|
||||
"src", help="Source path to a data file or directory."
|
||||
).complete = completion.FILE
|
||||
move_parser.add_argument(
|
||||
"dst", help="Destination path."
|
||||
).complete = completion.FILE
|
||||
move_parser.add_argument("dst", help="Destination path.").complete = completion.FILE
|
||||
move_parser.set_defaults(func=CmdMove)
|
||||
|
||||
@@ -79,7 +79,7 @@ def add_parser(subparsers, parent_parser):
|
||||
params_diff_parser.add_argument(
|
||||
"b_rev",
|
||||
nargs="?",
|
||||
help=("New Git commit to compare (defaults to the current workspace)"),
|
||||
help="New Git commit to compare (defaults to the current workspace)",
|
||||
)
|
||||
params_diff_parser.add_argument(
|
||||
"--targets",
|
||||
@@ -105,14 +105,12 @@ def add_parser(subparsers, parent_parser):
|
||||
)
|
||||
params_diff_parser.add_argument(
|
||||
"--json",
|
||||
"--show-json",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Show output in JSON format.",
|
||||
)
|
||||
params_diff_parser.add_argument(
|
||||
"--md",
|
||||
"--show-md",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest="markdown",
|
||||
|
||||
+69
-59
@@ -1,9 +1,9 @@
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional
|
||||
|
||||
from funcy import first
|
||||
from funcy import compact, first, get_in
|
||||
|
||||
from dvc.cli import completion
|
||||
from dvc.cli.command import CmdBase
|
||||
@@ -12,16 +12,51 @@ from dvc.exceptions import DvcException
|
||||
from dvc.ui import ui
|
||||
from dvc.utils import format_link
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dvc.render.match import RendererWithErrors
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _show_json(renderers, split=False):
|
||||
def _show_json(
|
||||
renderers_with_errors: List["RendererWithErrors"],
|
||||
split=False,
|
||||
errors: Optional[Dict[str, Exception]] = None,
|
||||
):
|
||||
from dvc.render.convert import to_json
|
||||
from dvc.utils.serialize import encode_exception
|
||||
|
||||
result = {
|
||||
renderer.name: to_json(renderer, split) for renderer in renderers
|
||||
}
|
||||
ui.write_json(result)
|
||||
all_errors: List[Dict] = []
|
||||
data = {}
|
||||
|
||||
for renderer, src_errors, def_errors in renderers_with_errors:
|
||||
name = renderer.name
|
||||
data[name] = to_json(renderer, split)
|
||||
all_errors.extend(
|
||||
{
|
||||
"name": name,
|
||||
"rev": rev,
|
||||
"source": source,
|
||||
**encode_exception(e),
|
||||
}
|
||||
for rev, per_rev_src_errors in src_errors.items()
|
||||
for source, e in per_rev_src_errors.items()
|
||||
)
|
||||
all_errors.extend(
|
||||
{
|
||||
"name": name,
|
||||
"rev": rev,
|
||||
**encode_exception(e),
|
||||
}
|
||||
for rev, e in def_errors.items()
|
||||
)
|
||||
|
||||
# these errors are not tied to any renderers
|
||||
errors = errors or {}
|
||||
all_errors.extend({"rev": rev, **encode_exception(e)} for rev, e in errors.items())
|
||||
|
||||
ui.write_json(compact({"errors": all_errors, "data": data}), highlight=False)
|
||||
|
||||
|
||||
def _adjust_vega_renderers(renderers):
|
||||
@@ -78,12 +113,6 @@ class CmdPlots(CmdBase):
|
||||
props = {p: getattr(self.args, p) for p in PLOT_PROPS}
|
||||
return {k: v for k, v in props.items() if v is not None}
|
||||
|
||||
def _config_files(self):
|
||||
config_files = None
|
||||
if self.args.from_config:
|
||||
config_files = {self.args.from_config}
|
||||
return config_files
|
||||
|
||||
def _html_template_path(self):
|
||||
html_template_path = self.args.html_template
|
||||
if not html_template_path:
|
||||
@@ -91,12 +120,11 @@ class CmdPlots(CmdBase):
|
||||
"html_template", None
|
||||
)
|
||||
if html_template_path and not os.path.isabs(html_template_path):
|
||||
html_template_path = os.path.join(
|
||||
self.repo.dvc_dir, html_template_path
|
||||
)
|
||||
assert self.repo.dvc_dir
|
||||
html_template_path = os.path.join(self.repo.dvc_dir, html_template_path)
|
||||
return html_template_path
|
||||
|
||||
def run(self):
|
||||
def run(self) -> int: # noqa: C901, PLR0911, PLR0912
|
||||
from pathlib import Path
|
||||
|
||||
from dvc.render.match import match_defs_renderers
|
||||
@@ -107,14 +135,11 @@ class CmdPlots(CmdBase):
|
||||
logger.error("please specify a target for `--show-vega`")
|
||||
return 1
|
||||
if len(self.args.targets) > 1:
|
||||
logger.error(
|
||||
"you can only specify one target for `--show-vega`"
|
||||
)
|
||||
logger.error("you can only specify one target for `--show-vega`")
|
||||
return 1
|
||||
if self.args.json:
|
||||
logger.error(
|
||||
"'--show-vega' and '--json' are mutually exclusive "
|
||||
"options."
|
||||
"'--show-vega' and '--json' are mutually exclusive options."
|
||||
)
|
||||
return 1
|
||||
|
||||
@@ -122,37 +147,39 @@ class CmdPlots(CmdBase):
|
||||
plots_data = self._func(
|
||||
targets=self.args.targets,
|
||||
props=self._props(),
|
||||
config_files=self._config_files(),
|
||||
)
|
||||
|
||||
if not plots_data:
|
||||
if not plots_data and not self.args.json:
|
||||
ui.error_write(
|
||||
"No plots were loaded, "
|
||||
"visualization file will not be created."
|
||||
"No plots were loaded, visualization file will not be created."
|
||||
)
|
||||
|
||||
out: str = self.args.out or self.repo.config.get("plots", {}).get(
|
||||
"out_dir", "dvc_plots"
|
||||
)
|
||||
|
||||
renderers_out = (
|
||||
out if self.args.json else os.path.join(out, "static")
|
||||
)
|
||||
|
||||
renderers = match_defs_renderers(
|
||||
renderers_out = out if self.args.json else os.path.join(out, "static")
|
||||
renderers_with_errors = match_defs_renderers(
|
||||
data=plots_data,
|
||||
out=renderers_out,
|
||||
templates_dir=self.repo.plots.templates_dir,
|
||||
)
|
||||
if self.args.json:
|
||||
_show_json(renderers, self.args.split)
|
||||
errors = compact(
|
||||
{
|
||||
rev: get_in(data, ["definitions", "error"])
|
||||
for rev, data in plots_data.items()
|
||||
}
|
||||
)
|
||||
_show_json(renderers_with_errors, self.args.split, errors=errors)
|
||||
return 0
|
||||
|
||||
renderers = [r.renderer for r in renderers_with_errors]
|
||||
_adjust_vega_renderers(renderers)
|
||||
if self.args.show_vega:
|
||||
renderer = first(filter(lambda r: r.TYPE == "vega", renderers))
|
||||
if renderer:
|
||||
ui.write_json(json.loads(renderer.get_filled_template()))
|
||||
ui.write_json(renderer.get_filled_template(as_string=False))
|
||||
return 0
|
||||
|
||||
output_file: Path = (Path.cwd() / out).resolve() / "index.html"
|
||||
@@ -161,7 +188,7 @@ class CmdPlots(CmdBase):
|
||||
render_html(
|
||||
renderers=renderers,
|
||||
output_file=output_file,
|
||||
template_path=self._html_template_path(),
|
||||
html_template=self._html_template_path(),
|
||||
)
|
||||
|
||||
ui.write(output_file.as_uri())
|
||||
@@ -223,9 +250,8 @@ class CmdPlotsTemplates(CmdBase):
|
||||
return 0
|
||||
raise InvalidArgumentError(f"Unexpected template: {target}.")
|
||||
|
||||
else:
|
||||
for template in TEMPLATES:
|
||||
ui.write(template.DEFAULT_NAME)
|
||||
for template in TEMPLATES:
|
||||
ui.write(template.DEFAULT_NAME)
|
||||
|
||||
return 0
|
||||
except DvcException:
|
||||
@@ -251,8 +277,7 @@ def add_parser(subparsers, parent_parser):
|
||||
fix_subparsers(plots_subparsers)
|
||||
|
||||
SHOW_HELP = (
|
||||
"Generate plots from target files or from `plots`"
|
||||
" definitions in `dvc.yaml`."
|
||||
"Generate plots from target files or from `plots` definitions in `dvc.yaml`."
|
||||
)
|
||||
plots_show_parser = plots_subparsers.add_parser(
|
||||
"show",
|
||||
@@ -275,8 +300,7 @@ def add_parser(subparsers, parent_parser):
|
||||
plots_show_parser.set_defaults(func=CmdPlotsShow)
|
||||
|
||||
PLOTS_DIFF_HELP = (
|
||||
"Show multiple versions of a plot by overlaying them "
|
||||
"in a single image."
|
||||
"Show multiple versions of a plot by overlaying them in a single image."
|
||||
)
|
||||
plots_diff_parser = plots_subparsers.add_parser(
|
||||
"diff",
|
||||
@@ -334,9 +358,7 @@ def add_parser(subparsers, parent_parser):
|
||||
)
|
||||
plots_modify_parser.set_defaults(func=CmdPlotsModify)
|
||||
|
||||
TEMPLATES_HELP = (
|
||||
"List built-in plots templates or show JSON specification for one."
|
||||
)
|
||||
TEMPLATES_HELP = "List built-in plots templates or show JSON specification for one."
|
||||
plots_templates_parser = plots_subparsers.add_parser(
|
||||
"templates",
|
||||
parents=[parent_parser],
|
||||
@@ -362,11 +384,8 @@ def _add_props_arguments(parser):
|
||||
"--template",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help=(
|
||||
"Special JSON or HTML schema file to inject with the data. "
|
||||
"See {}".format(
|
||||
format_link("https://man.dvc.org/plots#plot-templates")
|
||||
)
|
||||
help="Special JSON or HTML schema file to inject with the data. See {}".format(
|
||||
format_link("https://man.dvc.org/plots#plot-templates")
|
||||
),
|
||||
metavar="<path>",
|
||||
).complete = completion.FILE
|
||||
@@ -383,9 +402,7 @@ def _add_props_arguments(parser):
|
||||
default=None, # Use default None to distinguish when it's not used
|
||||
help="Provided CSV or TSV datafile does not have a header.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--title", default=None, metavar="<text>", help="Plot title."
|
||||
)
|
||||
parser.add_argument("--title", default=None, metavar="<text>", help="Plot title.")
|
||||
parser.add_argument(
|
||||
"--x-label", default=None, help="X axis label", metavar="<text>"
|
||||
)
|
||||
@@ -413,7 +430,6 @@ def _add_ui_arguments(parser):
|
||||
)
|
||||
parser.add_argument(
|
||||
"--json",
|
||||
"--show-json",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=argparse.SUPPRESS,
|
||||
@@ -433,9 +449,3 @@ def _add_ui_arguments(parser):
|
||||
help="Custom HTML template for VEGA visualization.",
|
||||
metavar="<path>",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--from-config",
|
||||
default=None,
|
||||
metavar="<path>",
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@ def add_parser(subparsers, parent_parser):
|
||||
|
||||
queue_subparsers = queue_parser.add_subparsers(
|
||||
dest="cmd",
|
||||
help="Use `dvc queue CMD --help` to display " "command-specific help.",
|
||||
help="Use `dvc queue CMD --help` to display command-specific help.",
|
||||
)
|
||||
|
||||
fix_subparsers(queue_subparsers)
|
||||
|
||||
@@ -11,13 +11,17 @@ class CmdQueueKill(CmdBase):
|
||||
"""Kill exp task in queue."""
|
||||
|
||||
def run(self):
|
||||
self.repo.experiments.celery_queue.kill(revs=self.args.task)
|
||||
self.repo.experiments.celery_queue.kill(
|
||||
revs=self.args.task, force=self.args.force
|
||||
)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def add_parser(queue_subparsers, parent_parser):
|
||||
QUEUE_KILL_HELP = "Kill actively running experiment queue tasks."
|
||||
QUEUE_KILL_HELP = (
|
||||
"Gracefully interrupt running experiment queue tasks (equivalent to Ctrl-C)"
|
||||
)
|
||||
queue_kill_parser = queue_subparsers.add_parser(
|
||||
"kill",
|
||||
parents=[parent_parser],
|
||||
@@ -25,6 +29,13 @@ def add_parser(queue_subparsers, parent_parser):
|
||||
help=QUEUE_KILL_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
queue_kill_parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Forcefully and immediately kill running experiment queue tasks",
|
||||
)
|
||||
queue_kill_parser.add_argument(
|
||||
"task",
|
||||
nargs="*",
|
||||
|
||||
@@ -34,9 +34,7 @@ def add_parser(queue_subparsers, parent_parser):
|
||||
queue_logs_parser.add_argument(
|
||||
"-e",
|
||||
"--encoding",
|
||||
help=(
|
||||
"Text encoding for log output. Defaults to system locale encoding."
|
||||
),
|
||||
help="Text encoding for log output. Defaults to system locale encoding.",
|
||||
metavar="<encoding>",
|
||||
)
|
||||
queue_logs_parser.add_argument(
|
||||
|
||||
@@ -28,7 +28,6 @@ class CmdQueueRemove(CmdBase):
|
||||
)
|
||||
|
||||
def run(self):
|
||||
|
||||
self.check_arguments()
|
||||
|
||||
if self.args.all:
|
||||
@@ -57,7 +56,6 @@ class CmdQueueRemove(CmdBase):
|
||||
|
||||
|
||||
def add_parser(queue_subparsers, parent_parser):
|
||||
|
||||
QUEUE_REMOVE_HELP = "Remove queued and completed tasks from the queue."
|
||||
queue_remove_parser = queue_subparsers.add_parser(
|
||||
"remove",
|
||||
|
||||
@@ -12,20 +12,15 @@ class CmdQueueStart(CmdBase):
|
||||
"""Start exp queue workers."""
|
||||
|
||||
def run(self):
|
||||
started = self.repo.experiments.celery_queue.start_workers(
|
||||
self.args.jobs
|
||||
)
|
||||
started = self.repo.experiments.celery_queue.start_workers(self.args.jobs)
|
||||
|
||||
suffix = "s" if started > 1 else ""
|
||||
ui.write(
|
||||
f"Started '{started}' new experiments task queue worker{suffix}."
|
||||
)
|
||||
ui.write(f"Started '{started}' new experiments task queue worker{suffix}.")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def add_parser(queue_subparsers, parent_parser):
|
||||
|
||||
QUEUE_START_HELP = "Start the experiments task queue worker."
|
||||
queue_start_parser = queue_subparsers.add_parser(
|
||||
"start",
|
||||
@@ -39,10 +34,7 @@ def add_parser(queue_subparsers, parent_parser):
|
||||
"--jobs",
|
||||
type=int,
|
||||
default=1,
|
||||
help=(
|
||||
"Maximum number of concurrent queue workers to start. "
|
||||
"Defaults to 1."
|
||||
),
|
||||
help="Maximum number of concurrent queue workers to start. Defaults to 1.",
|
||||
metavar="<number>",
|
||||
)
|
||||
queue_start_parser.set_defaults(func=CmdQueueStart)
|
||||
|
||||
@@ -1,33 +1,32 @@
|
||||
import argparse
|
||||
import logging
|
||||
from typing import List, Mapping, Optional
|
||||
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.compare import TabularData
|
||||
from dvc.ui import ui
|
||||
|
||||
from ..experiments.show import format_time
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CmdQueueStatus(CmdBase):
|
||||
"""Show queue task and worker status."""
|
||||
|
||||
def run(self):
|
||||
result: List[
|
||||
Mapping[str, Optional[str]]
|
||||
] = self.repo.experiments.celery_queue.status()
|
||||
def run(self) -> int:
|
||||
from dvc.repo.experiments.show import format_time
|
||||
|
||||
result = self.repo.experiments.celery_queue.status()
|
||||
if result:
|
||||
all_headers = ["Task", "Name", "Created", "Status"]
|
||||
td = TabularData(all_headers)
|
||||
for exp in result:
|
||||
created = format_time(exp.get("timestamp"))
|
||||
assert exp["rev"]
|
||||
assert exp["status"]
|
||||
td.append(
|
||||
[
|
||||
exp["rev"][:7],
|
||||
exp.get("name", ""),
|
||||
exp.get("name") or "",
|
||||
created,
|
||||
exp["status"],
|
||||
]
|
||||
@@ -38,9 +37,7 @@ class CmdQueueStatus(CmdBase):
|
||||
ui.write()
|
||||
|
||||
worker_status = self.repo.experiments.celery_queue.worker_status()
|
||||
active_count = len(
|
||||
[name for name, task in worker_status.items() if task]
|
||||
)
|
||||
active_count = len([name for name, task in worker_status.items() if task])
|
||||
idle_count = len(worker_status) - active_count
|
||||
|
||||
ui.write(f"Worker status: {active_count} active, {idle_count} idle")
|
||||
@@ -49,9 +46,7 @@ class CmdQueueStatus(CmdBase):
|
||||
|
||||
|
||||
def add_parser(queue_subparsers, parent_parser):
|
||||
QUEUE_STATUS_HELP = (
|
||||
"Show the status of experiments queue tasks and workers."
|
||||
)
|
||||
QUEUE_STATUS_HELP = "Show the status of experiments queue tasks and workers."
|
||||
queue_status_parser = queue_subparsers.add_parser(
|
||||
"status",
|
||||
parents=[parent_parser],
|
||||
|
||||
@@ -26,7 +26,6 @@ class CmdQueueStop(CmdBase):
|
||||
|
||||
|
||||
def add_parser(queue_subparsers, parent_parser):
|
||||
|
||||
QUEUE_STOP_HELP = "Stop all experiments task queue workers."
|
||||
queue_stop_parser = queue_subparsers.add_parser(
|
||||
"stop",
|
||||
|
||||
+5
-13
@@ -95,9 +95,7 @@ class CmdRemoteDefault(CmdRemote):
|
||||
if self.args.unset:
|
||||
conf["core"].pop("remote", None)
|
||||
else:
|
||||
merged_conf = self.config.load_config_to_level(
|
||||
self.args.level
|
||||
)
|
||||
merged_conf = self.config.load_config_to_level(self.args.level)
|
||||
if (
|
||||
self.args.name in conf["remote"]
|
||||
or self.args.name in merged_conf["remote"]
|
||||
@@ -164,7 +162,7 @@ def add_parser(subparsers, parent_parser):
|
||||
|
||||
remote_subparsers = remote_parser.add_subparsers(
|
||||
dest="cmd",
|
||||
help="Use `dvc remote CMD --help` for " "command-specific help.",
|
||||
help="Use `dvc remote CMD --help` for command-specific help.",
|
||||
)
|
||||
|
||||
fix_subparsers(remote_subparsers)
|
||||
@@ -208,9 +206,7 @@ def add_parser(subparsers, parent_parser):
|
||||
help=REMOTE_DEFAULT_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
remote_default_parser.add_argument(
|
||||
"name", nargs="?", help="Name of the remote"
|
||||
)
|
||||
remote_default_parser.add_argument("name", nargs="?", help="Name of the remote")
|
||||
remote_default_parser.add_argument(
|
||||
"-u",
|
||||
"--unset",
|
||||
@@ -229,9 +225,7 @@ def add_parser(subparsers, parent_parser):
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
remote_modify_parser.add_argument("name", help="Name of the remote")
|
||||
remote_modify_parser.add_argument(
|
||||
"option", help="Name of the option to modify."
|
||||
)
|
||||
remote_modify_parser.add_argument("option", help="Name of the option to modify.")
|
||||
remote_modify_parser.add_argument(
|
||||
"value", nargs="?", help="(optional) Value of the option."
|
||||
)
|
||||
@@ -262,9 +256,7 @@ def add_parser(subparsers, parent_parser):
|
||||
help=REMOTE_REMOVE_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
remote_remove_parser.add_argument(
|
||||
"name", help="Name of the remote to remove."
|
||||
)
|
||||
remote_remove_parser.add_argument("name", help="Name of the remote to remove.")
|
||||
remote_remove_parser.set_defaults(func=CmdRemoteRemove)
|
||||
REMOTE_RENAME_HELP = "Rename a DVC remote"
|
||||
remote_rename_parser = remote_subparsers.add_parser(
|
||||
|
||||
@@ -22,8 +22,7 @@ class CmdRemove(CmdBase):
|
||||
|
||||
def add_parser(subparsers, parent_parser):
|
||||
REMOVE_HELP = (
|
||||
"Remove stages from dvc.yaml and/or"
|
||||
" stop tracking files or directories."
|
||||
"Remove stages from dvc.yaml and/or stop tracking files or directories."
|
||||
)
|
||||
remove_parser = subparsers.add_parser(
|
||||
"remove",
|
||||
|
||||
+20
-31
@@ -10,20 +10,12 @@ class CmdRepro(CmdBase):
|
||||
def run(self):
|
||||
from dvc.ui import ui
|
||||
|
||||
stages = self.repo.reproduce(
|
||||
**self._common_kwargs, **self._repro_kwargs
|
||||
)
|
||||
stages = self.repo.reproduce(**self._common_kwargs, **self._repro_kwargs)
|
||||
if len(stages) == 0:
|
||||
ui.write(CmdDataStatus.UP_TO_DATE_MSG)
|
||||
else:
|
||||
ui.write("Use `dvc push` to send your updates to remote storage.")
|
||||
|
||||
if self.args.metrics:
|
||||
from dvc.compare import show_metrics
|
||||
|
||||
metrics = self.repo.metrics.show()
|
||||
show_metrics(metrics)
|
||||
|
||||
return 0
|
||||
|
||||
@property
|
||||
@@ -40,6 +32,7 @@ class CmdRepro(CmdBase):
|
||||
"recursive": self.args.recursive,
|
||||
"force_downstream": self.args.force_downstream,
|
||||
"pull": self.args.pull,
|
||||
"allow_missing": self.args.allow_missing,
|
||||
}
|
||||
|
||||
@property
|
||||
@@ -55,16 +48,14 @@ def add_arguments(repro_parser):
|
||||
repro_parser.add_argument(
|
||||
"targets",
|
||||
nargs="*",
|
||||
help=(
|
||||
"""\
|
||||
help="""\
|
||||
Stages to reproduce. 'dvc.yaml' by default.
|
||||
The targets can be path to a dvc.yaml file or `.dvc` file,
|
||||
or a stage name from dvc.yaml file from
|
||||
current working directory. To run a stage from dvc.yaml
|
||||
from other directories, the target must be a path followed by colon `:`
|
||||
and then the stage name name.
|
||||
"""
|
||||
),
|
||||
""",
|
||||
).complete = completion.DVCFILES_AND_STAGE
|
||||
repro_parser.add_argument(
|
||||
"-f",
|
||||
@@ -85,16 +76,14 @@ and then the stage name name.
|
||||
"--single-item",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Reproduce only single data item without recursive dependencies "
|
||||
"check.",
|
||||
help="Reproduce only single data item without recursive dependencies check.",
|
||||
)
|
||||
repro_parser.add_argument(
|
||||
"-p",
|
||||
"--pipeline",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Reproduce the whole pipeline that the specified targets "
|
||||
"belong to.",
|
||||
help="Reproduce the whole pipeline that the specified targets belong to.",
|
||||
)
|
||||
repro_parser.add_argument(
|
||||
"-P",
|
||||
@@ -110,13 +99,6 @@ and then the stage name name.
|
||||
default=False,
|
||||
help="Reproduce all stages in the specified directory.",
|
||||
)
|
||||
repro_parser.add_argument(
|
||||
"-m",
|
||||
"--metrics",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Show metrics after reproduction.",
|
||||
)
|
||||
repro_parser.add_argument(
|
||||
"--downstream",
|
||||
action="store_true",
|
||||
@@ -127,8 +109,10 @@ and then the stage name name.
|
||||
"--force-downstream",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Reproduce all descendants of a changed stage even if their "
|
||||
"direct dependencies didn't change.",
|
||||
help=(
|
||||
"Reproduce all descendants of a changed stage even if their "
|
||||
"direct dependencies didn't change."
|
||||
),
|
||||
)
|
||||
repro_parser.add_argument(
|
||||
"--pull",
|
||||
@@ -139,19 +123,24 @@ and then the stage name name.
|
||||
"from the run-cache."
|
||||
),
|
||||
)
|
||||
repro_parser.add_argument(
|
||||
"--allow-missing",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=("Skip stages with missing data but no other changes."),
|
||||
)
|
||||
repro_parser.add_argument(
|
||||
"--dry",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Only print the commands that would be executed without "
|
||||
"actually executing.",
|
||||
help=(
|
||||
"Only print the commands that would be executed without actually executing."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def add_parser(subparsers, parent_parser):
|
||||
REPRO_HELP = (
|
||||
"Reproduce complete or partial pipelines by executing their stages."
|
||||
)
|
||||
REPRO_HELP = "Reproduce complete or partial pipelines by executing their stages."
|
||||
repro_parser = subparsers.add_parser(
|
||||
"repro",
|
||||
parents=[parent_parser],
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.cli.utils import append_doc_link
|
||||
from dvc.commands.stage import parse_cmd
|
||||
from dvc.exceptions import DvcException
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CmdRun(CmdBase):
|
||||
def run(self):
|
||||
if not any(
|
||||
[
|
||||
self.args.deps,
|
||||
self.args.outs,
|
||||
self.args.outs_no_cache,
|
||||
self.args.metrics,
|
||||
self.args.metrics_no_cache,
|
||||
self.args.plots,
|
||||
self.args.plots_no_cache,
|
||||
self.args.outs_persist,
|
||||
self.args.outs_persist_no_cache,
|
||||
self.args.checkpoints,
|
||||
self.args.params,
|
||||
self.args.command,
|
||||
]
|
||||
): # pragma: no cover
|
||||
logger.error(
|
||||
"too few arguments. Specify at least one: `-d`, `-o`, `-O`, "
|
||||
"`-m`, `-M`, `-p`, `--plots`, `--plots-no-cache`, "
|
||||
"`--outs-persist`, `--outs-persist-no-cache`, `command`."
|
||||
)
|
||||
return 1
|
||||
|
||||
kwargs = vars(self.args)
|
||||
kwargs.update(
|
||||
{
|
||||
"cmd": parse_cmd(self.args.command),
|
||||
"fname": kwargs.pop("file"),
|
||||
"no_exec": (self.args.no_exec or bool(self.args.checkpoints)),
|
||||
"run_cache": not kwargs.pop("no_run_cache"),
|
||||
}
|
||||
)
|
||||
try:
|
||||
self.repo.run(**kwargs)
|
||||
except DvcException:
|
||||
logger.exception("")
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def add_parser(subparsers, parent_parser):
|
||||
from dvc.commands.stage import _add_common_args
|
||||
|
||||
RUN_HELP = (
|
||||
"Generate a dvc.yaml file from a command and execute the command."
|
||||
)
|
||||
run_parser = subparsers.add_parser(
|
||||
"run",
|
||||
parents=[parent_parser],
|
||||
description=append_doc_link(RUN_HELP, "run"),
|
||||
help=RUN_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
run_parser.add_argument("-n", "--name", help="Stage name.")
|
||||
run_parser.add_argument(
|
||||
"--file", metavar="<filename>", help=argparse.SUPPRESS
|
||||
)
|
||||
run_parser.add_argument(
|
||||
"--single-stage",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
_add_common_args(run_parser)
|
||||
run_parser.add_argument(
|
||||
"--no-exec",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Only create dvc.yaml without actually running it.",
|
||||
)
|
||||
run_parser.add_argument(
|
||||
"--no-commit",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Don't put files/directories into cache.",
|
||||
)
|
||||
run_parser.add_argument(
|
||||
"--no-run-cache",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=(
|
||||
"Execute the command even if this stage has already been run "
|
||||
"with the same command/dependencies/outputs/etc before."
|
||||
),
|
||||
)
|
||||
run_parser.set_defaults(func=CmdRun)
|
||||
+43
-27
@@ -1,5 +1,6 @@
|
||||
import argparse
|
||||
import logging
|
||||
from contextlib import contextmanager
|
||||
from itertools import chain, filterfalse
|
||||
from typing import TYPE_CHECKING, Dict, Iterable, List
|
||||
|
||||
@@ -44,9 +45,7 @@ def generate_description(stage: "Stage") -> str:
|
||||
return "; ".join(desc)
|
||||
|
||||
|
||||
def prepare_description(
|
||||
stage: "Stage", max_length: int = MAX_TEXT_LENGTH
|
||||
) -> str:
|
||||
def prepare_description(stage: "Stage", max_length: int = MAX_TEXT_LENGTH) -> str:
|
||||
desc = stage.short_description() or generate_description(stage)
|
||||
return truncate_text(desc, max_length)
|
||||
|
||||
@@ -57,9 +56,9 @@ def prepare_stages_data(
|
||||
max_length: int = MAX_TEXT_LENGTH,
|
||||
) -> Dict[str, str]:
|
||||
return {
|
||||
stage.addressing: prepare_description(stage, max_length=max_length)
|
||||
if description
|
||||
else ""
|
||||
stage.addressing: (
|
||||
prepare_description(stage, max_length=max_length) if description else ""
|
||||
)
|
||||
for stage in stages
|
||||
}
|
||||
|
||||
@@ -67,7 +66,7 @@ def prepare_stages_data(
|
||||
class CmdStageList(CmdBase):
|
||||
def _get_stages(self) -> Iterable["Stage"]:
|
||||
if self.args.all:
|
||||
stages: List["Stage"] = self.repo.index.stages # type: ignore
|
||||
stages: List["Stage"] = self.repo.index.stages
|
||||
logger.trace( # type: ignore[attr-defined]
|
||||
"%d no. of stages found", len(stages)
|
||||
)
|
||||
@@ -75,9 +74,7 @@ class CmdStageList(CmdBase):
|
||||
|
||||
# removing duplicates while maintaining order
|
||||
collected = chain.from_iterable(
|
||||
self.repo.stage.collect(
|
||||
target=target, recursive=self.args.recursive
|
||||
)
|
||||
self.repo.stage.collect(target=target, recursive=self.args.recursive)
|
||||
for target in self.args.targets
|
||||
)
|
||||
return dict.fromkeys(collected).keys()
|
||||
@@ -95,7 +92,7 @@ class CmdStageList(CmdBase):
|
||||
|
||||
stages = self._get_stages()
|
||||
data = prepare_stages_data(stages, description=not self.args.name_only)
|
||||
ui.table(data.items())
|
||||
ui.table(list(data.items()))
|
||||
|
||||
return 0
|
||||
|
||||
@@ -123,8 +120,22 @@ def parse_cmd(commands: List[str]) -> str:
|
||||
return " ".join(map(quote_argument, commands))
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _disable_logging(highest_level=logging.CRITICAL):
|
||||
previous_level = logging.root.manager.disable
|
||||
|
||||
logging.disable(highest_level)
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
logging.disable(previous_level)
|
||||
|
||||
|
||||
class CmdStageAdd(CmdBase):
|
||||
def run(self):
|
||||
from dvc.repo import lock_repo
|
||||
|
||||
kwargs = vars(self.args)
|
||||
kwargs.update(
|
||||
{
|
||||
@@ -132,7 +143,15 @@ class CmdStageAdd(CmdBase):
|
||||
"params": parse_params(self.args.params),
|
||||
}
|
||||
)
|
||||
self.repo.stage.add(**kwargs)
|
||||
|
||||
with self.repo.scm_context, lock_repo(self.repo):
|
||||
with _disable_logging(logging.INFO):
|
||||
stage = self.repo.stage.add(**kwargs)
|
||||
logger.info("Added stage %r in %r", stage.addressing, stage.relpath)
|
||||
if self.args.run:
|
||||
stage.run()
|
||||
stage.dump(update_pipeline=False)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
@@ -173,17 +192,7 @@ def _add_common_args(parser):
|
||||
"--outs-no-cache",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Declare output file or directory "
|
||||
"(do not put into DVC cache).",
|
||||
metavar="<filename>",
|
||||
).complete = completion.FILE
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--checkpoints",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Declare checkpoint output file or directory for 'dvc exp run'. "
|
||||
"Not compatible with 'dvc repro'.",
|
||||
help="Declare output file or directory (do not put into DVC cache).",
|
||||
metavar="<filename>",
|
||||
).complete = completion.FILE
|
||||
parser.add_argument(
|
||||
@@ -196,16 +205,17 @@ def _add_common_args(parser):
|
||||
"--outs-persist",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Declare output file or directory that will not be "
|
||||
"removed upon repro.",
|
||||
help="Declare output file or directory that will not be removed upon repro.",
|
||||
metavar="<filename>",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--outs-persist-no-cache",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Declare output file or directory that will not be "
|
||||
"removed upon repro (do not put into DVC cache).",
|
||||
help=(
|
||||
"Declare output file or directory that will not be "
|
||||
"removed upon repro (do not put into DVC cache)."
|
||||
),
|
||||
metavar="<filename>",
|
||||
)
|
||||
parser.add_argument(
|
||||
@@ -259,6 +269,12 @@ def _add_common_args(parser):
|
||||
"This doesn't affect any DVC operations."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--run",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Execute the stage after generating it.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"command",
|
||||
nargs=argparse.REMAINDER,
|
||||
|
||||
@@ -81,9 +81,7 @@ class CmdDataStatus(CmdDataBase):
|
||||
if not self.repo.index.stages:
|
||||
ui.write(self.EMPTY_PROJECT_MSG)
|
||||
elif self.args.cloud or self.args.remote:
|
||||
remote = self.args.remote or self.repo.config["core"].get(
|
||||
"remote"
|
||||
)
|
||||
remote = self.args.remote or self.repo.config["core"].get("remote")
|
||||
ui.write(self.IN_SYNC_MSG.format(remote=remote))
|
||||
else:
|
||||
ui.write(self.UP_TO_DATE_MSG)
|
||||
|
||||
@@ -60,8 +60,10 @@ def add_parser(subparsers, parent_parser):
|
||||
"--no-download",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Update .dvc file git revision/hash value(s)"
|
||||
" but do not download the file(s).",
|
||||
help=(
|
||||
"Update .dvc file git revision/hash value(s)"
|
||||
" but do not download the file(s)."
|
||||
),
|
||||
)
|
||||
update_parser.add_argument(
|
||||
"--to-remote",
|
||||
|
||||
@@ -21,9 +21,7 @@ class CmdVersion(CmdBaseNoRepo):
|
||||
|
||||
|
||||
def add_parser(subparsers, parent_parser):
|
||||
VERSION_HELP = (
|
||||
"Display the DVC version and system/environment information."
|
||||
)
|
||||
VERSION_HELP = "Display the DVC version and system/environment information."
|
||||
version_parser = subparsers.add_parser(
|
||||
"version",
|
||||
parents=[parent_parser],
|
||||
|
||||
+34
-69
@@ -22,7 +22,6 @@ from typing import (
|
||||
from funcy import reraise
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dvc.types import StrPath
|
||||
from dvc.ui.table import CellT
|
||||
|
||||
|
||||
@@ -35,7 +34,7 @@ def with_value(value, default):
|
||||
|
||||
|
||||
class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
def __init__(self, columns: Sequence[str], fill_value: str = ""):
|
||||
def __init__(self, columns: Sequence[str], fill_value: Optional[str] = ""):
|
||||
self._columns: Dict[str, Column] = {name: Column() for name in columns}
|
||||
self._keys: List[str] = list(columns)
|
||||
self._fill_value = fill_value
|
||||
@@ -58,15 +57,13 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
return self._columns[name]
|
||||
|
||||
def items(self) -> ItemsView[str, Column]:
|
||||
projection = {k: self.column(k) for k in self.keys()}
|
||||
projection = {k: self.column(k) for k in self.keys()} # noqa: SIM118
|
||||
return projection.items()
|
||||
|
||||
def keys(self) -> List[str]:
|
||||
return self._keys
|
||||
|
||||
def _iter_col_row(
|
||||
self, row: Sequence["CellT"]
|
||||
) -> Iterator[Tuple["CellT", Column]]:
|
||||
def _iter_col_row(self, row: Sequence["CellT"]) -> Iterator[Tuple["CellT", Column]]:
|
||||
for val, col in zip_longest(row, self.columns):
|
||||
if col is None:
|
||||
break
|
||||
@@ -103,9 +100,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __setitem__(
|
||||
self, item: slice, value: Iterable[Sequence["CellT"]]
|
||||
) -> None:
|
||||
def __setitem__(self, item: slice, value: Iterable[Sequence["CellT"]]) -> None:
|
||||
...
|
||||
|
||||
def __setitem__(self, item, value) -> None:
|
||||
@@ -113,8 +108,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
if isinstance(item, slice):
|
||||
n = len(self.columns)
|
||||
normalized_rows = (
|
||||
chain(val, repeat(self._fill_value, n - len(val)))
|
||||
for val in value
|
||||
chain(val, repeat(self._fill_value, n - len(val))) for val in value
|
||||
)
|
||||
# we need to transpose those rows into columnar format
|
||||
# as we work in terms of column-based arrays
|
||||
@@ -166,24 +160,6 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
writer.writerow(row)
|
||||
return buff.getvalue()
|
||||
|
||||
def to_parallel_coordinates(
|
||||
self, output_path: "StrPath", color_by: str = None
|
||||
) -> "StrPath":
|
||||
from dvc_render.html import render_html
|
||||
from dvc_render.plotly import ParallelCoordinatesRenderer
|
||||
|
||||
render_html(
|
||||
renderers=[
|
||||
ParallelCoordinatesRenderer(
|
||||
self.as_dict(),
|
||||
color_by=color_by,
|
||||
fill_value=self._fill_value,
|
||||
)
|
||||
],
|
||||
output_file=output_path,
|
||||
)
|
||||
return output_path
|
||||
|
||||
def add_column(self, name: str) -> None:
|
||||
self._columns[name] = Column([self._fill_value] * len(self))
|
||||
self._keys.append(name)
|
||||
@@ -195,7 +171,8 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
self.add_column(key)
|
||||
|
||||
row: List["CellT"] = [
|
||||
with_value(d.get(key), self._fill_value) for key in self.keys()
|
||||
with_value(d.get(key), self._fill_value)
|
||||
for key in self.keys() # noqa: SIM118
|
||||
]
|
||||
self.append(row)
|
||||
|
||||
@@ -208,14 +185,12 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
ui.table(self, headers=self.keys(), **kwargs)
|
||||
|
||||
def as_dict(
|
||||
self, cols: Iterable[str] = None
|
||||
self, cols: Optional[Iterable[str]] = None
|
||||
) -> Iterable[Dict[str, "CellT"]]:
|
||||
keys = self.keys() if cols is None else set(cols)
|
||||
return [
|
||||
{k: self._columns[k][i] for k in keys} for i in range(len(self))
|
||||
]
|
||||
return [{k: self._columns[k][i] for k in keys} for i in range(len(self))]
|
||||
|
||||
def dropna(
|
||||
def dropna( # noqa: C901, PLR0912
|
||||
self,
|
||||
axis: str = "rows",
|
||||
how="any",
|
||||
@@ -223,13 +198,10 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
):
|
||||
if axis not in ["rows", "cols"]:
|
||||
raise ValueError(
|
||||
f"Invalid 'axis' value {axis}."
|
||||
"Choose one of ['rows', 'cols']"
|
||||
f"Invalid 'axis' value {axis}.Choose one of ['rows', 'cols']"
|
||||
)
|
||||
if how not in ["any", "all"]:
|
||||
raise ValueError(
|
||||
f"Invalid 'how' value {how}." "Choose one of ['any', 'all']"
|
||||
)
|
||||
raise ValueError(f"Invalid 'how' value {how}. Choose one of ['any', 'all']")
|
||||
|
||||
match_line: Set = set()
|
||||
match_any = True
|
||||
@@ -244,8 +216,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
if axis == "rows":
|
||||
match_line.add(n_row)
|
||||
break
|
||||
else:
|
||||
match_line.add(self.keys()[n_col])
|
||||
match_line.add(self.keys()[n_col])
|
||||
|
||||
to_drop = match_line
|
||||
if how == "all":
|
||||
@@ -256,18 +227,14 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
to_drop -= match_line
|
||||
|
||||
if axis == "rows":
|
||||
for name in self.keys():
|
||||
for name in self.keys(): # noqa: SIM118
|
||||
self._columns[name] = Column(
|
||||
[
|
||||
x
|
||||
for n, x in enumerate(self._columns[name])
|
||||
if n not in to_drop
|
||||
]
|
||||
[x for n, x in enumerate(self._columns[name]) if n not in to_drop]
|
||||
)
|
||||
else:
|
||||
self.drop(*to_drop)
|
||||
|
||||
def drop_duplicates(
|
||||
def drop_duplicates( # noqa: C901
|
||||
self,
|
||||
axis: str = "rows",
|
||||
subset: Optional[Iterable[str]] = None,
|
||||
@@ -275,8 +242,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
):
|
||||
if axis not in ["rows", "cols"]:
|
||||
raise ValueError(
|
||||
f"Invalid 'axis' value {axis}."
|
||||
"Choose one of ['rows', 'cols']"
|
||||
f"Invalid 'axis' value {axis}.Choose one of ['rows', 'cols']"
|
||||
)
|
||||
|
||||
if axis == "cols":
|
||||
@@ -309,7 +275,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
|
||||
else:
|
||||
unique_rows.append(tuple_row)
|
||||
|
||||
for name in self.keys():
|
||||
for name in self.keys(): # noqa: SIM118
|
||||
self._columns[name] = Column(
|
||||
[
|
||||
x
|
||||
@@ -324,12 +290,13 @@ def _normalize_float(val: float, precision: int):
|
||||
|
||||
|
||||
def _format_field(
|
||||
val: Any, precision: int = None, round_digits: bool = False
|
||||
val: Any, precision: Optional[int] = None, round_digits: bool = False
|
||||
) -> str:
|
||||
def _format(_val):
|
||||
if isinstance(_val, float) and precision:
|
||||
func = round if round_digits else _normalize_float
|
||||
return func(_val, precision)
|
||||
if round_digits:
|
||||
return round(_val, precision)
|
||||
return _normalize_float(_val, precision)
|
||||
if isinstance(_val, abc.Mapping):
|
||||
return {k: _format(v) for k, v in _val.items()}
|
||||
if isinstance(_val, list):
|
||||
@@ -345,11 +312,11 @@ def diff_table(
|
||||
old: bool = True,
|
||||
no_path: bool = False,
|
||||
show_changes: bool = True,
|
||||
precision: int = None,
|
||||
precision: Optional[int] = None,
|
||||
round_digits: bool = False,
|
||||
on_empty_diff: str = None,
|
||||
a_rev: str = None,
|
||||
b_rev: str = None,
|
||||
on_empty_diff: Optional[str] = None,
|
||||
a_rev: Optional[str] = None,
|
||||
b_rev: Optional[str] = None,
|
||||
) -> TabularData:
|
||||
a_rev = a_rev or "HEAD"
|
||||
b_rev = b_rev or "workspace"
|
||||
@@ -361,9 +328,7 @@ def diff_table(
|
||||
for item, change in sorted(diff_in_file.items()):
|
||||
old_value = with_value(change.get("old"), fill_value)
|
||||
new_value = with_value(change.get("new"), fill_value)
|
||||
diff_value = with_value(
|
||||
change.get("diff", on_empty_diff), fill_value
|
||||
)
|
||||
diff_value = with_value(change.get("diff", on_empty_diff), fill_value)
|
||||
td.append(
|
||||
[
|
||||
fname,
|
||||
@@ -387,18 +352,18 @@ def diff_table(
|
||||
return td
|
||||
|
||||
|
||||
def show_diff(
|
||||
def show_diff( # noqa: PLR0913
|
||||
diff,
|
||||
title: str,
|
||||
old: bool = True,
|
||||
no_path: bool = False,
|
||||
show_changes: bool = True,
|
||||
precision: int = None,
|
||||
precision: Optional[int] = None,
|
||||
round_digits: bool = False,
|
||||
on_empty_diff: str = None,
|
||||
on_empty_diff: Optional[str] = None,
|
||||
markdown: bool = False,
|
||||
a_rev: str = None,
|
||||
b_rev: str = None,
|
||||
a_rev: Optional[str] = None,
|
||||
b_rev: Optional[str] = None,
|
||||
) -> None:
|
||||
td = diff_table(
|
||||
diff,
|
||||
@@ -420,7 +385,7 @@ def metrics_table(
|
||||
all_branches: bool = False,
|
||||
all_tags: bool = False,
|
||||
all_commits: bool = False,
|
||||
precision: int = None,
|
||||
precision: Optional[int] = None,
|
||||
round_digits: bool = False,
|
||||
):
|
||||
from dvc.utils.diff import format_dict
|
||||
@@ -460,7 +425,7 @@ def show_metrics(
|
||||
all_branches: bool = False,
|
||||
all_tags: bool = False,
|
||||
all_commits: bool = False,
|
||||
precision: int = None,
|
||||
precision: Optional[int] = None,
|
||||
round_digits: bool = False,
|
||||
) -> None:
|
||||
td = metrics_table(
|
||||
|
||||
+38
-29
@@ -4,11 +4,18 @@ import os
|
||||
import re
|
||||
from contextlib import contextmanager
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, Dict, Optional
|
||||
|
||||
from funcy import cached_property, compact, memoize, re_find
|
||||
from funcy import compact, memoize, re_find
|
||||
|
||||
from dvc.exceptions import DvcException, NotDvcRepoError
|
||||
|
||||
from .utils.objects import cached_property
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dvc.fs import FileSystem
|
||||
from dvc.types import DictStrAny, StrPath
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -72,9 +79,6 @@ class Config(dict):
|
||||
ConfigError: thrown if config has an invalid format.
|
||||
"""
|
||||
|
||||
APPNAME = "dvc"
|
||||
APPAUTHOR = "iterative"
|
||||
|
||||
SYSTEM_LEVELS = ("system", "global")
|
||||
REPO_LEVELS = ("repo", "local")
|
||||
# In the order they shadow each other
|
||||
@@ -84,7 +88,11 @@ class Config(dict):
|
||||
CONFIG_LOCAL = "config.local"
|
||||
|
||||
def __init__(
|
||||
self, dvc_dir=None, validate=True, fs=None, config=None
|
||||
self,
|
||||
dvc_dir: Optional["StrPath"] = None,
|
||||
validate: bool = True,
|
||||
fs: Optional["FileSystem"] = None,
|
||||
config: Optional["DictStrAny"] = None,
|
||||
): # pylint: disable=super-init-not-called
|
||||
from dvc.fs import LocalFileSystem
|
||||
|
||||
@@ -92,31 +100,35 @@ class Config(dict):
|
||||
self.wfs = LocalFileSystem()
|
||||
self.fs = fs or self.wfs
|
||||
|
||||
if not dvc_dir:
|
||||
try:
|
||||
from dvc.repo import Repo
|
||||
|
||||
self.dvc_dir = Repo.find_dvc_dir()
|
||||
except NotDvcRepoError:
|
||||
self.dvc_dir = None
|
||||
else:
|
||||
if dvc_dir:
|
||||
self.dvc_dir = self.fs.path.abspath(self.fs.path.realpath(dvc_dir))
|
||||
|
||||
self.load(validate=validate, config=config)
|
||||
|
||||
@classmethod
|
||||
def from_cwd(cls, fs: Optional["FileSystem"] = None, **kwargs):
|
||||
from dvc.repo import Repo
|
||||
|
||||
try:
|
||||
dvc_dir = Repo.find_dvc_dir(fs=fs)
|
||||
except NotDvcRepoError:
|
||||
dvc_dir = None
|
||||
|
||||
return cls(dvc_dir=dvc_dir, fs=fs, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def get_dir(cls, level):
|
||||
from appdirs import site_config_dir, user_config_dir
|
||||
from dvc.dirs import global_config_dir, system_config_dir
|
||||
|
||||
assert level in ("global", "system")
|
||||
|
||||
if level == "global":
|
||||
return user_config_dir(cls.APPNAME, cls.APPAUTHOR)
|
||||
return global_config_dir()
|
||||
if level == "system":
|
||||
return site_config_dir(cls.APPNAME, cls.APPAUTHOR)
|
||||
return system_config_dir()
|
||||
|
||||
@cached_property
|
||||
def files(self):
|
||||
def files(self) -> Dict[str, str]:
|
||||
files = {
|
||||
level: os.path.join(self.get_dir(level), self.CONFIG)
|
||||
for level in ("system", "global")
|
||||
@@ -139,10 +151,10 @@ class Config(dict):
|
||||
dvc.config.Config: config object.
|
||||
"""
|
||||
config_file = os.path.join(dvc_dir, Config.CONFIG)
|
||||
open(config_file, "w+", encoding="utf-8").close()
|
||||
return Config(dvc_dir)
|
||||
with open(config_file, "w+", encoding="utf-8"):
|
||||
return Config(dvc_dir)
|
||||
|
||||
def load(self, validate=True, config=None):
|
||||
def load(self, validate: bool = True, config: Optional["DictStrAny"] = None):
|
||||
"""Loads config from all the config files.
|
||||
|
||||
Raises:
|
||||
@@ -159,17 +171,13 @@ class Config(dict):
|
||||
self.clear()
|
||||
self.update(conf)
|
||||
|
||||
# Add resolved default cache.dir
|
||||
if not self["cache"].get("dir") and self.dvc_dir:
|
||||
self["cache"]["dir"] = os.path.join(self.dvc_dir, "cache")
|
||||
|
||||
def _get_fs(self, level):
|
||||
# NOTE: this might be a Gitfs, which doesn't see things outside of
|
||||
# the repo.
|
||||
return self.fs if level == "repo" else self.wfs
|
||||
|
||||
def _load_config(self, level):
|
||||
from configobj import ConfigObj
|
||||
from configobj import ConfigObj, ConfigObjError
|
||||
|
||||
filename = self.files[level]
|
||||
fs = self._get_fs(level)
|
||||
@@ -180,6 +188,8 @@ class Config(dict):
|
||||
conf_obj = ConfigObj(fobj)
|
||||
except UnicodeDecodeError as exc:
|
||||
raise ConfigError(str(exc)) from exc
|
||||
except ConfigObjError as exc:
|
||||
raise ConfigError(str(exc)) from exc
|
||||
else:
|
||||
conf_obj = ConfigObj()
|
||||
return _parse_named(_lower_keys(conf_obj.dict()))
|
||||
@@ -278,7 +288,7 @@ class Config(dict):
|
||||
return Schema(dirs_schema, extra=ALLOW_EXTRA)(conf)
|
||||
|
||||
def load_config_to_level(self, level=None):
|
||||
merged_conf = {}
|
||||
merged_conf: Dict = {}
|
||||
for merge_level in self.LEVELS:
|
||||
if merge_level == level:
|
||||
break
|
||||
@@ -324,7 +334,7 @@ class Config(dict):
|
||||
|
||||
|
||||
def _parse_named(conf):
|
||||
result = {"remote": {}, "machine": {}}
|
||||
result: Dict[str, Dict] = {"remote": {}, "machine": {}}
|
||||
|
||||
for section, val in conf.items():
|
||||
match = re_find(r'^\s*(remote|machine)\s*"(.*)"\s*$', section)
|
||||
@@ -361,6 +371,5 @@ def merge(into, update):
|
||||
|
||||
def _lower_keys(data):
|
||||
return {
|
||||
k.lower(): _lower_keys(v) if isinstance(v, dict) else v
|
||||
for k, v in data.items()
|
||||
k.lower(): _lower_keys(v) if isinstance(v, dict) else v for k, v in data.items()
|
||||
}
|
||||
|
||||
+59
-24
@@ -1,8 +1,10 @@
|
||||
import logging
|
||||
import os
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from funcy import walk_values
|
||||
from funcy import once, walk_values
|
||||
from voluptuous import (
|
||||
REMOVE_EXTRA,
|
||||
All,
|
||||
Any,
|
||||
Coerce,
|
||||
@@ -13,6 +15,8 @@ from voluptuous import (
|
||||
Schema,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
Bool = All(
|
||||
Lower,
|
||||
Any("true", "false"),
|
||||
@@ -34,14 +38,12 @@ def supported_cache_type(types):
|
||||
|
||||
unsupported = set(types) - {"reflink", "hardlink", "symlink", "copy"}
|
||||
if unsupported:
|
||||
raise Invalid(
|
||||
"Unsupported cache type(s): {}".format(", ".join(unsupported))
|
||||
)
|
||||
raise Invalid("Unsupported cache type(s): {}".format(", ".join(unsupported)))
|
||||
|
||||
return types
|
||||
|
||||
|
||||
def Choices(*choices):
|
||||
def Choices(*choices): # noqa: N802
|
||||
"""Checks that value belongs to the specified set of values
|
||||
|
||||
Args:
|
||||
@@ -51,7 +53,7 @@ def Choices(*choices):
|
||||
return Any(*choices, msg="expected one of {}".format(", ".join(choices)))
|
||||
|
||||
|
||||
def ByUrl(mapping):
|
||||
def ByUrl(mapping): # noqa: N802
|
||||
schemas = walk_values(Schema, mapping)
|
||||
|
||||
def validate(data):
|
||||
@@ -60,7 +62,9 @@ def ByUrl(mapping):
|
||||
|
||||
parsed = urlparse(data["url"])
|
||||
# Windows absolute paths should really have scheme == "" (local)
|
||||
if os.name == "nt" and len(parsed.scheme) == 1 and parsed.netloc == "":
|
||||
if os.name == "nt" and len(parsed.scheme) == 1 and not parsed.netloc:
|
||||
return schemas[""](data)
|
||||
if not parsed.netloc:
|
||||
return schemas[""](data)
|
||||
if parsed.scheme not in schemas:
|
||||
raise Invalid(f"Unsupported URL type {parsed.scheme}://")
|
||||
@@ -74,6 +78,27 @@ class RelPath(str):
|
||||
pass
|
||||
|
||||
|
||||
class FeatureSchema(Schema):
|
||||
def __init__(self, schema, required=False):
|
||||
super().__init__(schema, required=required, extra=REMOVE_EXTRA)
|
||||
|
||||
@staticmethod
|
||||
@once
|
||||
def _log_deprecated(keys):
|
||||
# only run this once per session
|
||||
message = "%s config option%s unsupported"
|
||||
paths = ", ".join(f"'feature.{key}'" for key in keys)
|
||||
pluralize = " is" if len(keys) == 1 else "s are"
|
||||
logger.warning(message, paths, pluralize)
|
||||
|
||||
def __call__(self, data):
|
||||
ret = super().__call__(data)
|
||||
extra_keys = data.keys() - ret.keys()
|
||||
if extra_keys:
|
||||
self._log_deprecated(sorted(extra_keys))
|
||||
return ret
|
||||
|
||||
|
||||
REMOTE_COMMON = {
|
||||
"url": str,
|
||||
"checksum_jobs": All(Coerce(int), Range(1)),
|
||||
@@ -97,6 +122,8 @@ HTTP_COMMON = {
|
||||
"ask_password": Bool,
|
||||
"ssl_verify": Any(Bool, str),
|
||||
"method": str,
|
||||
"connect_timeout": All(Coerce(float), Range(0, min_included=True)),
|
||||
"read_timeout": All(Coerce(float), Range(0, min_included=True)),
|
||||
Optional("verify", default=False): Bool,
|
||||
}
|
||||
WEBDAV_COMMON = {
|
||||
@@ -104,6 +131,7 @@ WEBDAV_COMMON = {
|
||||
"password": str,
|
||||
"ask_password": Bool,
|
||||
"token": str,
|
||||
"custom_auth_header": str,
|
||||
"cert_path": str,
|
||||
"key_path": str,
|
||||
"timeout": Coerce(int),
|
||||
@@ -122,16 +150,17 @@ SCHEMA = {
|
||||
Optional("autostage", default=False): Bool,
|
||||
Optional("experiments"): Bool, # obsoleted
|
||||
Optional("check_update", default=True): Bool,
|
||||
"site_cache_dir": str,
|
||||
"machine": Lower,
|
||||
},
|
||||
"cache": {
|
||||
"local": str,
|
||||
"s3": str,
|
||||
"gs": str,
|
||||
"hdfs": str,
|
||||
"webhdfs": str,
|
||||
"ssh": str,
|
||||
"azure": str,
|
||||
"local": str, # obsoleted
|
||||
"s3": str, # obsoleted
|
||||
"gs": str, # obsoleted
|
||||
"hdfs": str, # obsoleted
|
||||
"webhdfs": str, # obsoleted
|
||||
"ssh": str, # obsoleted
|
||||
"azure": str, # obsoleted
|
||||
# This is for default local cache
|
||||
"dir": str,
|
||||
**LOCAL_COMMON,
|
||||
@@ -186,6 +215,7 @@ SCHEMA = {
|
||||
"timeout": Coerce(int),
|
||||
"gss_auth": Bool,
|
||||
"allow_agent": Bool,
|
||||
"max_sessions": Coerce(int),
|
||||
Optional("verify", default=False): Bool,
|
||||
**REMOTE_COMMON,
|
||||
},
|
||||
@@ -245,19 +275,17 @@ SCHEMA = {
|
||||
)
|
||||
},
|
||||
"state": {
|
||||
"dir": str,
|
||||
"dir": str, # obsoleted
|
||||
"row_limit": All(Coerce(int), Range(1)), # obsoleted
|
||||
"row_cleanup_quota": All(Coerce(int), Range(0, 100)), # obsoleted
|
||||
},
|
||||
"index": {
|
||||
"dir": str,
|
||||
"dir": str, # obsoleted
|
||||
},
|
||||
"machine": {
|
||||
str: {
|
||||
"cloud": All(Lower, Choices("aws", "azure")),
|
||||
"region": All(
|
||||
Lower, Choices("us-west", "us-east", "eu-west", "eu-north")
|
||||
),
|
||||
"region": All(Lower, Choices("us-west", "us-east", "eu-west", "eu-north")),
|
||||
"image": str,
|
||||
"spot": Bool,
|
||||
"spot_price": Coerce(float),
|
||||
@@ -270,11 +298,12 @@ SCHEMA = {
|
||||
},
|
||||
},
|
||||
# section for experimental features
|
||||
"feature": {
|
||||
Optional("machine", default=False): Bool,
|
||||
# enabled by default. It's of no use, kept for backward compatibility.
|
||||
Optional("parametrization", default=True): Bool,
|
||||
},
|
||||
# only specified keys are validated, others get logged and then ignored/removed
|
||||
"feature": FeatureSchema(
|
||||
{
|
||||
Optional("machine", default=False): Bool,
|
||||
},
|
||||
),
|
||||
"plots": {
|
||||
"html_template": str,
|
||||
Optional("auto_open", default=False): Bool,
|
||||
@@ -298,4 +327,10 @@ SCHEMA = {
|
||||
"config_dir": str,
|
||||
"config_name": str,
|
||||
},
|
||||
"studio": {
|
||||
"token": str,
|
||||
"url": str,
|
||||
"repo_url": str,
|
||||
Optional("offline", default=False): Bool,
|
||||
},
|
||||
}
|
||||
|
||||
+40
-19
@@ -5,7 +5,7 @@ import logging
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
from subprocess import Popen
|
||||
from subprocess import Popen # nosec B404
|
||||
from typing import List
|
||||
|
||||
from dvc.env import DVC_DAEMON
|
||||
@@ -14,52 +14,72 @@ from dvc.utils import fix_env, is_binary
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _popen(cmd, **kwargs):
|
||||
def _suppress_resource_warning(popen: Popen):
|
||||
"""Sets the returncode to avoid ResourceWarning when popen is garbage collected."""
|
||||
# only use for daemon processes.
|
||||
# See https://bugs.python.org/issue38890.
|
||||
popen.returncode = 0
|
||||
|
||||
|
||||
def _popen(cmd, **kwargs) -> Popen:
|
||||
prefix = [sys.executable]
|
||||
if not is_binary():
|
||||
main_entrypoint = os.path.join(
|
||||
os.path.abspath(os.path.dirname(__file__)), "__main__.py"
|
||||
)
|
||||
prefix += [main_entrypoint]
|
||||
return Popen(prefix + cmd, close_fds=True, shell=False, **kwargs)
|
||||
return Popen(prefix + cmd, close_fds=True, shell=False, **kwargs) # nosec B603
|
||||
|
||||
|
||||
def _spawn_windows(cmd, env):
|
||||
from subprocess import (
|
||||
CREATE_NEW_PROCESS_GROUP,
|
||||
CREATE_NO_WINDOW,
|
||||
STARTF_USESHOWWINDOW,
|
||||
STARTUPINFO,
|
||||
)
|
||||
if sys.platform == "win32":
|
||||
from subprocess import ( # nosec B404
|
||||
CREATE_NEW_PROCESS_GROUP,
|
||||
CREATE_NO_WINDOW,
|
||||
STARTF_USESHOWWINDOW,
|
||||
STARTUPINFO,
|
||||
)
|
||||
|
||||
# https://stackoverflow.com/a/7006424
|
||||
# https://bugs.python.org/issue41619
|
||||
creationflags = CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW
|
||||
# https://stackoverflow.com/a/7006424
|
||||
# https://bugs.python.org/issue41619
|
||||
creationflags = CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW
|
||||
|
||||
startupinfo = STARTUPINFO()
|
||||
startupinfo.dwFlags |= STARTF_USESHOWWINDOW
|
||||
startupinfo = STARTUPINFO()
|
||||
startupinfo.dwFlags |= STARTF_USESHOWWINDOW
|
||||
|
||||
_popen(cmd, env=env, creationflags=creationflags, startupinfo=startupinfo)
|
||||
popen = _popen(
|
||||
cmd, env=env, creationflags=creationflags, startupinfo=startupinfo
|
||||
)
|
||||
_suppress_resource_warning(popen)
|
||||
|
||||
|
||||
def _spawn_posix(cmd, env):
|
||||
from dvc.cli import main
|
||||
|
||||
# `fork` will copy buffers, so we need to flush them before forking.
|
||||
# Otherwise, we will get duplicated outputs.
|
||||
if sys.stdout and not sys.stdout.closed:
|
||||
sys.stdout.flush()
|
||||
if sys.stderr and not sys.stderr.closed:
|
||||
sys.stderr.flush()
|
||||
|
||||
# NOTE: using os._exit instead of sys.exit, because dvc built
|
||||
# with PyInstaller has trouble with SystemExit exception and throws
|
||||
# errors such as "[26338] Failed to execute script __main__"
|
||||
try:
|
||||
pid = os.fork() # pylint: disable=no-member
|
||||
# pylint: disable-next=no-member
|
||||
pid = os.fork() # type: ignore[attr-defined]
|
||||
if pid > 0:
|
||||
return
|
||||
except OSError:
|
||||
logger.exception("failed at first fork")
|
||||
os._exit(1) # pylint: disable=protected-access
|
||||
|
||||
os.setsid() # pylint: disable=no-member
|
||||
os.setsid() # type: ignore[attr-defined] # pylint: disable=no-member
|
||||
|
||||
try:
|
||||
pid = os.fork() # pylint: disable=no-member
|
||||
# pylint: disable-next=no-member
|
||||
pid = os.fork() # type: ignore[attr-defined]
|
||||
if pid > 0:
|
||||
os._exit(0) # pylint: disable=protected-access
|
||||
except OSError:
|
||||
@@ -69,6 +89,7 @@ def _spawn_posix(cmd, env):
|
||||
sys.stdin.close()
|
||||
sys.stdout.close()
|
||||
sys.stderr.close()
|
||||
os.closerange(0, 3)
|
||||
|
||||
if platform.system() == "Darwin":
|
||||
# workaround for MacOS bug
|
||||
@@ -100,7 +121,7 @@ def daemon(args):
|
||||
Args:
|
||||
args (list): list of arguments to append to `dvc daemon` command.
|
||||
"""
|
||||
daemonize(["daemon", "-q"] + args)
|
||||
daemonize(["daemon", "-q", *args])
|
||||
|
||||
|
||||
def daemonize(cmd: List[str]):
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user