Compare commits

...
530 Commits
Author SHA1 Message Date
frederik-sandfort 18fa1a0272 Custom header for dvc webdav (#8933)Fix https://github.com/iterative/dvc/issues/8931
* include custom auth header for webdav

* Revert "include custom auth header for webdav"

This reverts commit d2f2b2889b6bcf0429e4a28076f709a04642ae3a.

* include custom auth header for dvc webdav, ran unit tests

* update dvc-webdav version
2023-02-02 16:36:11 +05:45
dependabot[bot] 49a27f4140 build(deps-dev): bump dvc-render from 0.0.17 to 0.1.0 (#8935)
Bumps [dvc-render](https://github.com/iterative/dvc-render) from 0.0.17 to 0.1.0.
- [Release notes](https://github.com/iterative/dvc-render/releases)
- [Commits](https://github.com/iterative/dvc-render/compare/0.0.17...0.1.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-02 14:13:27 +05:45
dependabot[bot] 9b2aa79c18 build(deps-dev): bump pylint from 2.15.10 to 2.16.0 (#8936)
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.15.10 to 2.16.0.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.15.10...v2.16.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-02 14:12:53 +05:45
Ruslan Kuprieiev 24023c6977 index: introduce data_keys (#8938)
`data_keys` are similar to `outs`, but they provide more granularity, so we
can use them in `build_data_index` to rebuild index views with filtered
outputs. E.g. when running `dvc diff dir/file`, workspace index should only
have `dir/file` in it.

This also could be used for standalone (non-output) data in the future.

Pre-requisite for #8930
2023-02-02 05:50:16 +02:00
Ruslan Kuprieiev 5310bcdcec deps: bump dvc-data to 0.37.3 (#8923) 2023-02-02 04:18:18 +02:00
dependabot[bot] ae92154cec build(deps-dev): bump beautifulsoup4 from 4.11.1 to 4.11.2 (#8928)
Bumps [beautifulsoup4](https://www.crummy.com/software/BeautifulSoup/bs4/) from 4.11.1 to 4.11.2.

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-01 09:10:50 +05:45
Ruslan Kuprieiev f661cee8f6 stage: use repo.fs directly without copying (#8929)
Otherwise when using brancher we end up in a strange situation where even
though we are collecting targets from `gitfs`, we are still using `localfs`
as `self.fs`, which make operations like `self.fs.abspath` in
`collect_granular` return invalid paths.
2023-02-01 05:21:53 +02:00
Ruslan Kuprieiev e07c85a993 index: collect_targets: introduce onerror (#8925) 2023-02-01 03:49:27 +02:00
Ruslan Kuprieiev 58d9e379fe tests: gc: use timedelta again (#8926)
Leftover from #8912
2023-02-01 03:14:22 +02:00
skshetry 524162dc3f version/info: match dashes' length below DVC version (#8921) 2023-01-31 18:32:04 +05:45
skshetry aa15b21dce logging: log pkg version/type, platform info and cli arguments (#8918)
logging: log pkg version and type, platform info and cli arguments
2023-01-31 15:46:14 +05:45
Peter Rowlands 0404967591 deps: bump dvc-gs to 2.21.1 2023-01-31 17:26:44 +09:00
Peter Rowlands 465d886c29 testing: add tests for version-aware import-url 2023-01-31 15:55:44 +09:00
Peter Rowlands 459adfb40f import-url: disable push on version aware imports before saving 2023-01-31 15:55:44 +09:00
Peter Rowlands fe9f8654a3 testing: add test for worktree update 2023-01-31 15:55:44 +09:00
skshetry 8208846003 bump ruff to 0.0.238 (#8915) 2023-01-31 11:24:03 +05:45
Peter Rowlands 73aea2d972 ci: run dvc-s3 tests on DVC pull requests 2023-01-31 14:13:55 +09:00
Ruslan Kuprieiev 10184e9370 tests: gc: use timedelta (#8912)
Bug from https://github.com/iterative/dvc/pull/8880
2023-01-31 05:18:09 +02:00
Ruslan Kuprieiev 2690f05cee pre-commit: bump isort to 5.12.0
https://github.com/PyCQA/isort/issues/2077
2023-01-31 04:02:24 +02:00
Ruslan Kuprieiev 34c2c95115 index: expose rev property (#8909)
index: add rev property

This is very useful to identify indexes that are built from git revisions
and so are describing an immutable state and can be used for caching.

We were already using this for `__repr__`.
2023-01-30 20:18:54 +02:00
skshetry 5c6c1dc719 mypy: unignore module override (#8902) 2023-01-28 15:08:52 +05:45
98bcfad8e7 build(deps-dev): bump scmrepo from 0.1.6 to 0.1.7 (#8821)
* build(deps-dev): bump scmrepo from 0.1.6 to 0.1.7

Bumps [scmrepo](https://github.com/iterative/scmrepo) from 0.1.6 to 0.1.7.
- [Release notes](https://github.com/iterative/scmrepo/releases)
- [Commits](https://github.com/iterative/scmrepo/compare/0.1.6...0.1.7)

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

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

* mypy: remove scmrepo from ignored modules

* Fix mypy issues

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: skshetry <[email protected]>
Co-authored-by: Saugat Pachhai (सौगात) <[email protected]>
2023-01-28 14:26:00 +05:45
skshetry 7ab4c59d70 test: run pytest-style checks (#8901) 2023-01-28 14:10:53 +05:45
skshetry a137b2a6bc update ruff and enable flake8-logging-format and flake8-type-checking (#8894)
* update ruff and enable flake8-logging-format and flake8-type-checking check

* Update dvc/external_repo.py

* Update dvc/fs/dvc.py

* Update dvc/repo/experiments/__init__.py

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

* Update pyproject.toml
2023-01-27 19:14:19 +05:45
Daniele Trifirò 6f0e8a8db5 tests: fix test_iter_revs mock 2023-01-27 12:52:46 +01:00
Daniele Trifirò d294945499 gc: fix usage with --date 2023-01-27 12:52:46 +01:00
Dave Berenbaum db70d1cc5c analytics: differentiate ci users (#8838) 2023-01-26 21:50:14 +05:45
Andrii Korzhuk ed6e11e856 Fixed typo in _format_field function 2023-01-26 13:50:40 +01:00
skshetry c1cd13c34f serialize: fix loading of cloud metadata from .dvc/dvc.lock files (#8889) 2023-01-26 16:18:03 +05:45
David de la Iglesia Castro 12dd746165 output: Pass version_aware=True to fs if files is present.
This will be ignored by filesystems that don't support `version_aware`.

Closes #8853
2023-01-26 10:48:59 +01:00
5165fb67a1 [pre-commit.ci] pre-commit autoupdate (#8869)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/charliermarsh/ruff-pre-commit: v0.0.223 → v0.0.230](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.223...v0.0.230)

* Update .pre-commit-config.yaml

* fix lint issues

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: skshetry <[email protected]>
Co-authored-by: Saugat Pachhai (सौगात) <[email protected]>
2023-01-26 14:24:09 +05:45
Peter Rowlands b2c1c4dccd data_cloud: move version_aware config check to get_remote 2023-01-26 17:21:47 +09:00
Peter Rowlands b060cadbf1 deps: bump dvc-data to 0.36.2 2023-01-26 17:21:47 +09:00
Peter Rowlands 7c55a9c7cd testing: add initial versioned remote tests 2023-01-26 17:03:50 +09:00
Peter Rowlands 037b5b2e0a testing: add cloud versioning remote fixtures 2023-01-26 17:03:50 +09:00
dependabot[bot] 99acb8e335 build(deps-dev): bump dvc-data from 0.36.0 to 0.36.1 (#8887)
Bumps [dvc-data](https://github.com/iterative/dvc-data) from 0.36.0 to 0.36.1.
- [Release notes](https://github.com/iterative/dvc-data/releases)
- [Commits](https://github.com/iterative/dvc-data/compare/0.36.0...0.36.1)

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

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-26 08:32:02 +05:45
Ruslan Kuprieiev b46cfbda5d deps: bump dvc-data to 0.36.0 (#8885) 2023-01-26 00:22:12 +02:00
Dave Berenbaum a8d193bf2a queue: update kill ui messages (#8798)
* queue: update kill ui messages

* queue kill: update ui, kill all valid tasks

* queue kill: update wording of ui
2023-01-25 20:08:27 +05:45
dependabot[bot] 0b1cf6c771 build(deps-dev): bump dvc-gs from 2.20.0 to 2.21.0 (#8841)
Bumps [dvc-gs](https://github.com/iterative/dvc-gs) from 2.20.0 to 2.21.0.
- [Release notes](https://github.com/iterative/dvc-gs/releases)
- [Commits](https://github.com/iterative/dvc-gs/compare/2.20.0...2.21.0)

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

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 19:29:21 +05:45
skshetry b3aa698cd4 dumpd: serialize files at the end in .dvc files (#8879) 2023-01-25 18:52:15 +05:45
skshetry bb9a3a0403 import/import-url: try not to delete dvcfile before import/import-url completes (#8876)
* import/import-url: try not to delete dvcfile before import/import-url completes

* use repo.stage.create instead of internal create_stage utility
2023-01-25 18:16:45 +05:45
skshetry 3427e115eb data status: check if change.old.hash_info is None or not (#8878)
data:status: check if change.old.hash_info is None or not
2023-01-25 14:24:38 +02:00
skshetry 3d5f4dc66c add support for output-specific remotes (#8862)
Also splits metadata in `files` to cloud vs local. But this happens
during dump/load, so actual behaviour is changed. During runtime,
the metadata is merged and loaded. During dump, the metadata
are split if there is a `remote` set.
2023-01-25 18:06:12 +05:45
David de la Iglesia Castro 80633f4fad stage.save: Remove merge_versioned option.
If we want `add`, `commit` and `move` to work for cloud versioning, option must be always True so it is the same as not having it.

Closes #8828
(We were not passing `merge_versioned=True` in `dvc commit`)
2023-01-25 10:35:37 +01:00
Ruslan Kuprieiev 8bda3650c5 deps: bump dvc-data to 0.35.3 2023-01-24 23:06:34 +02:00
David de la Iglesia Castro ea2e5dc3e5 output: Don't serialize hash_info or meta in cloud versioning.
Closes #8357
2023-01-24 18:40:39 +01:00
dependabot[bot] 61afdca3a6 build(deps-dev): bump dvc-data from 0.35.1 to 0.35.2 (#8871)
Bumps [dvc-data](https://github.com/iterative/dvc-data) from 0.35.1 to 0.35.2.
- [Release notes](https://github.com/iterative/dvc-data/releases)
- [Commits](https://github.com/iterative/dvc-data/compare/0.35.1...0.35.2)

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

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 20:47:16 +05:45
Ruslan Kuprieiev 6a6bc7035a data: status: use data index (#8846)
Pre-requisite for #8761
2023-01-23 21:29:32 +02:00
Ruslan Kuprieiev ccc97622fa deps: bump dvc-ssh to 2.21.0 (#8865)
* deps: bump dvc-ssh to 2.21.0

* Update pyproject.toml
2023-01-23 01:36:52 +02:00
Ruslan Kuprieiev 916b5d169f deps: bump dvc-azure to 2.21.0 2023-01-23 00:46:58 +02:00
Peter Rowlands 38e9b2ba95 deps: bump dvc-data to 0.35.1 2023-01-21 03:44:02 +09:00
Dave Berenbaumandskshetry 18f039d7c7 add viztracer async option (#8856)
* add viztracer async option

* Update dvc/_debug.py

Co-authored-by: skshetry <[email protected]>
2023-01-20 22:29:11 +05:45
skshetry f1c9e9365b remove setup.py file (#8851) 2023-01-20 20:26:13 +05:45
Ruslan Kuprieiev b50d1ee674 deps: bump dvc-data to 0.35.0 2023-01-20 04:16:12 +02:00
Peter Rowlands 1c33024b2c deps: bump dvc-data to 0.34.0 2023-01-19 19:09:45 +09:00
David de la Iglesia CastroandPeter Rowlands dd2d2dce19 param: Fix status check for Tuples.
Closes #8803

Update dvc/dependency/param.py

Co-authored-by: Peter Rowlands (변기호) <[email protected]>
2023-01-19 08:19:16 +01:00
David de la Iglesia Castro 5f558d003a deps: bump dvc-data to 0.33.0
- Update `test_gc`
2023-01-18 15:17:07 +02:00
David de la Iglesia Castro 3676f35b7c output.remove: Pass explicit recursive flag.
Per changes in https://github.com/iterative/dvc-objects/pull/176
2023-01-18 15:17:07 +02:00
David de la Iglesia Castro 9af6fcad4a exp run: Fix --dry option.
Closes #8831
2023-01-17 13:32:20 +01:00
skshetry 6d80b72434 lint: run lint checks on all platforms (#8832) 2023-01-17 10:51:57 +05:45
pre-commit-ci[bot] 46d9290fc2 [pre-commit.ci] pre-commit autoupdate (#8830)
updates:
- [github.com/charliermarsh/ruff-pre-commit: v0.0.222 → v0.0.223](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.222...v0.0.223)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-01-17 08:55:33 +05:45
skshetry c47c2123fb cleanup linters: get rid of pylint-pytest, run pylint only on dvc/ directory, etc (#8822)
* cleanup linters: get rid of pylint-pytest, run pylint only on dvc/ directory, etc.

* remove pylint direcitves

* Apply suggestions from code review

* Update pyproject.toml
2023-01-17 08:35:19 +05:45
Dave Berenbaum d25d26ff74 exp: drop some words from exp names (#8811) 2023-01-16 17:11:04 +01:00
Peter Rowlands c2e4f51479 serialize: fix mypy error 2023-01-16 15:58:21 +09:00
Peter Rowlands 977a1f9f43 tests: update for dvc-objects batched transfer 2023-01-16 15:58:21 +09:00
Peter Rowlands 608269dc88 status: remove deprecated/unused dvc-data.hashfile.status flag 2023-01-16 15:58:21 +09:00
Peter Rowlands e48d0d1d9c tests: remove duplicated dvc-objects/dvc-data tests 2023-01-16 15:58:21 +09:00
Peter Rowlands bf08bd3f12 deps: bump dvc-data to 0.32.0 2023-01-16 15:58:21 +09:00
dependabot[bot] db5dfdd6bc build(deps-dev): bump pytest from 7.2.0 to 7.2.1 (#8819)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.2.0 to 7.2.1.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.2.0...7.2.1)

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

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-16 08:37:15 +05:45
dependabot[bot] 3b10f40ca3 build(deps-dev): bump dvc-task from 0.1.10 to 0.1.11 (#8820)
Bumps [dvc-task](https://github.com/iterative/dvc-task) from 0.1.10 to 0.1.11.
- [Release notes](https://github.com/iterative/dvc-task/releases)
- [Commits](https://github.com/iterative/dvc-task/compare/0.1.10...0.1.11)

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

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-16 08:36:39 +05:45
Ruslan Kuprieiev 4660443d0e deps: bump dvc-data to 0.31.4 2023-01-16 04:09:00 +02:00
Ruslan Kuprieiev a92ace0054 deps: bump dvc-data to 0.31.3 2023-01-16 01:16:35 +02:00
skshetry 309ae4d995 mypy: enable check-untyped-defs (#8815) 2023-01-15 21:40:40 +05:45
daniele e1254c9ece Add misc type hints (#8430) 2023-01-15 17:58:34 +05:45
skshetry 7a65ac06b2 mypy: don't ignore all imports, also disallow implicit optional (#8814)
* mypy: don't ignore all imports

* mypy: disallow implicit_optional
2023-01-15 16:25:03 +05:45
skshetry b0052aa666 pre-commit update: flake8/ruff/pyupgrade/black/isort (#8813) 2023-01-15 14:56:54 +05:45
skshetry da27174e02 dvcfile: rename PipelineFile to ProjectFile, get rid of Dvcfile class, cleanups, etc. (#8812)
dvcfile: rename PipelineFile to ProjectFile, get rid of Dvcfile class, cleanups etc
2023-01-15 12:45:10 +05:45
Ruslan Kuprieiev aabdddc2b7 index: introduce build_data_index
This is a generally useful function that builds a data index from repo index.

This is currently only used in cloud versioning, but it is going to be used in
`status`, `diff` and other data-centric operations
2023-01-15 01:45:09 +02:00
karajan1001 fc1a192c90 Bump pathspec to 0.10.3
fix: #8217
1. Replace group name to avoid conflict.
2. Disable failed tests.
3. Bump pathspec to 0.10.3
2023-01-13 22:47:36 +08:00
dependabot[bot]andSaugat Pachhai 178244f666 build(deps-dev): bump dvc-task from 0.1.9 to 0.1.10 (#8807)
* build(deps-dev): bump dvc-task from 0.1.9 to 0.1.10

Bumps [dvc-task](https://github.com/iterative/dvc-task) from 0.1.9 to 0.1.10.
- [Release notes](https://github.com/iterative/dvc-task/releases)
- [Commits](https://github.com/iterative/dvc-task/compare/0.1.9...0.1.10)

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

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

* remove xfail on json.JSONDecodeError/PermissionError

JSONDecodeError should be fixed by the new release. Next we need to investigate
PermissionError.

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Saugat Pachhai (सौगात) <[email protected]>
2023-01-13 17:25:36 +05:45
skshetry dba7d3c320 mypy: use typesafe cached_property (#8799) 2023-01-13 09:08:59 +05:45
David de la Iglesia Castro 8b075eb754 stage: Skip run-cache if any out has cache: false.
Closes https://github.com/iterative/dvc/issues/4428
2023-01-12 19:10:01 +01:00
David de la Iglesia Castro f507e4d9c0 config: Reraise ConfigError from ConfigObjError.
Closes #8801
2023-01-12 18:11:54 +01:00
skshetry 5169205229 deps: bump minimum version req for grandalf to 0.7. 2023-01-12 18:38:45 +05:45
skshetry 3bcd683677 repo.check_graph: rename method (#8800) 2023-01-12 16:03:51 +05:45
Saugat Pachhai (सौगात) 30ec1cf5e9 index/stage: move repo collection logic to index 2023-01-11 19:38:43 +02:00
skshetry a814f04ee0 tests: xfail test_apply_failed on JSONDecodeError/PermissionError (#8796) 2023-01-11 18:04:23 +05:45
skshetry 90a27179eb ruff: ignore complex functions and remove dvc.utils.fs.makedirs (#8795)
We may want to prevent any new functions from getting complex.
2023-01-11 17:58:51 +05:45
skshetry 175c578579 ruff: ban relative parent/grandparent imports (#8794) 2023-01-11 16:48:46 +05:45
skshetry 5441964fdb add ruff (#8793)
* add ruff linter

* enable few more checks
2023-01-11 15:20:38 +05:45
Ivan Shcheklein d4bfe38a54 perf: remove fs exists check in plots, parallel data collect 2023-01-10 20:27:02 -08:00
dependabot[bot] a060049186 build(deps-dev): unpin grandalf
See https://github.com/bdcht/grandalf/issues/26#issuecomment-1378175273.
2023-01-11 08:44:49 +05:45
Ruslan Kuprieiev c02285ab49 deps: bump dvc-data to 0.29.0 2023-01-11 03:03:38 +02:00
Jorge Orpinel bccbbc1d94 Update dvc/commands/imp_url.py 2023-01-10 19:05:18 +02:00
Jorge Orpinel d736b31d8b import-url: Update options order
Per https://github.com/iterative/dvc.org/pull/3934
2023-01-10 19:05:18 +02:00
Peter Rowlands 68c1cc095c deps: bump dvc-data to 0.28.6 2023-01-11 00:20:22 +09:00
Peter Rowlands c94d28594d worktree push: remove existing version id optimization for file outs 2023-01-10 16:21:31 +09:00
Peter Rowlands d3d429ef7f push/fetch: respect --jobs on worktree push/fetch 2023-01-10 16:21:31 +09:00
dependabot[bot] 6ac88104da build(deps-dev): Bump pylint from 2.15.9 to 2.15.10
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.15.9 to 2.15.10.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.15.9...v2.15.10)

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

Signed-off-by: dependabot[bot] <[email protected]>
2023-01-10 10:39:48 +05:45
skshetry d87cdba6d8 gha: remove env vars SHELL 2023-01-10 10:19:29 +05:45
Ruslan Kuprieiev b25ef5cdfb metrics/params: show: add hide_workspace flag 2023-01-09 16:57:18 +02:00
dberenbaum 435407185f params/metrics diff: address test failures for missing branch key 2023-01-09 16:57:18 +02:00
dberenbaum 0b192af6f4 params: fix diff bt active branch and workspace 2023-01-09 16:57:18 +02:00
dberenbaum c6b602a167 metrics: fix diff bt active branch and workspace 2023-01-09 16:57:18 +02:00
David de la Iglesia Castro 1d5de9c5ba repro: Disable run-cache if --no-commit.
As described in https://github.com/iterative/dvc/issues/4428#issuecomment-778263005.

Add `run_cache` argument to `Stage.save`. Set it to `False` if `no_commit`.
2023-01-09 12:38:57 +01:00
Ruslan Kuprieiev 0e5674ffea ls: compute relparts once for every dir
Cuts down `dvc ls . data/mnist/dataset --recursive` for me from ~25 to ~12
seconds.

`relparts` is an expensive operation as it calls `relpath` that calls `abspath`,
so we want to avoid calling it as much as possible.
2023-01-08 03:44:50 +02:00
Ruslan Kuprieiev 684fc32e36 dvcfs: ls: don't call info()
Cuts down `dvc ls . data/mnist/dataset --recursive` for me from ~37 to ~25
seconds.

Note that this also changes behaviour for ls with a broken symlink, as we
were not able to see it before because of a failing `info()` call, but now
we can follow a normal UNIX ls behaviour more closely.
2023-01-08 00:37:40 +02:00
Saugat Pachhai (सौगात) 81f37be9d6 ci: timeout tests if it takes >5min
It most likely means it's frozen and unlikely to resume again. We'd want
to see where it froze, so this will provide us with a traceback.
2023-01-06 20:16:20 +05:45
Saugat Pachhai (सौगात) 3dfdd577e7 tests: xfail experiments-related flakey tests 2023-01-06 18:59:53 +05:45
dependabot[bot] bfb6a32b51 build(deps-dev): Bump dvc-data from 0.28.4 to 0.28.5
Bumps [dvc-data](https://github.com/iterative/dvc-data) from 0.28.4 to 0.28.5.
- [Release notes](https://github.com/iterative/dvc-data/releases)
- [Commits](https://github.com/iterative/dvc-data/compare/0.28.4...0.28.5)

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

Signed-off-by: dependabot[bot] <[email protected]>
2023-01-06 16:36:48 +09:00
Daniele Trifirò 33de761560 deps: unpin dvc-http 2023-01-05 21:15:33 +01:00
Daniele Trifirò ff38aed927 config: expose timeouts in http config
- `read_timeout` timeout for reading new data portions from the peer
- `connect_timeout` timeout for establishing new connections

For more information about individual timeouts meanings:

- `read_timeout` maps to `aiohttp`'s `sock_read` timeout
- `connect_timeout` applies to `aiohttp`'s `connect`, `sock_connect`
  timeouts

https://docs.aiohttp.org/en/stable/client_reference.html?highlight=clienttimeout#clienttimeout
2023-01-05 21:15:33 +01:00
Daniele Trifirò b62f17ffaf tests: move tests/unit/remote/test_http.py to dvc-http repo 2023-01-05 19:54:14 +01:00
Daniele Trifirò a0c781a676 exp save: fix help for --force 2023-01-04 17:57:29 +01:00
dberenbaum 6ac7dfc008 exp: fix tests and remove reset checks 2023-01-04 17:29:12 +01:00
dberenbaum b0372918ea exp: stop checking for unchanged experiments 2023-01-04 17:29:12 +01:00
Daniele Trifirò 4b0fa4d559 update: prevent re-download on every update 2023-01-03 18:52:48 +01:00
Saugat Pachhai (सौगात) a24c389674 internal: hoist resolver/top-level metrics/plots/params data into file level 2023-01-03 17:46:06 +05:45
karajan1001 f386f7c082 exp apply can accept exp names and failed exps as target
fix: #8182

1. `exp apply` can apply to failed experiments.
2. `exp apply` can accept exp names as target.
3. Add a new functional test for it.
2022-12-30 17:40:18 +08:00
karajan1001 eadd0da394 Refactor get_ref_and_entry_by_names
1. Move get_ref_and_entry_by_names from `exp remove` to `celery` for
   other commands usage.
2022-12-30 17:40:18 +08:00
karajan1001 06673dd212 Use SIGINT as the default signal in queue kill
fix: #8624
1. Add a new flag `--force` for `queue kill`
2. Make `SIGINT` to be the default option and `SIGKILL` to be with
   `--force`
3. Add tests for `queue kill`
4. Bump dvc-task into 0.1.9
2022-12-30 16:19:39 +08:00
dependabot[bot] 5f0b1edb40 build(deps-dev): Bump filelock from 3.8.2 to 3.9.0
Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.8.2 to 3.9.0.
- [Release notes](https://github.com/tox-dev/py-filelock/releases)
- [Changelog](https://github.com/tox-dev/py-filelock/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/py-filelock/compare/3.8.2...3.9.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-12-30 09:20:26 +05:45
dependabot[bot] 05701e26db build(deps-dev): Bump scmrepo from 0.1.4 to 0.1.5
Bumps [scmrepo](https://github.com/iterative/scmrepo) from 0.1.4 to 0.1.5.
- [Release notes](https://github.com/iterative/scmrepo/releases)
- [Commits](https://github.com/iterative/scmrepo/compare/0.1.4...0.1.5)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-12-30 11:23:42 +09:00
Daniele Trifirò 109288e1cd exp save: update --help 2022-12-29 20:51:52 +01:00
Daniele Trifirò 9e6f44bc1c get/ls/import: improve clone error messages 2022-12-29 20:13:39 +01:00
David de la Iglesia Castro 68b24307f9 post_live_metrics: Pass params on start event. 2022-12-28 11:43:37 +01:00
skshetry ea8a4a3db4 GHA: remove unused env from tests workflow 2022-12-27 10:26:29 +05:45
karajan1001 08ae68caf4 Make temp dir executed checkpoint experiment return result to workspace.
fix: #8612
For checkpoint experiment in some case users might want to give it a early
stopping to reduce variance.  But currently if we interrupt/kill the
experiment it will be marked as failed, and all of the completed
checkpoints will be removed as we cleanup all the running directly after
the process failed.

1. We raise CheckpointKilledError intead of StageCmdFailed error if at
   least one checkpoint had been commited.
2. Temp_dir executor will continue collecting data if the Checkpoint
   stage was interrupted.
3. Raise warnings if a checkpoint stage was incomplete and the other
   stages were not forwarded.
4. Add a new functional test for this
2022-12-26 18:25:43 +05:45
karajan1001 4400fb4090 queue stop --kill can kill false running status tasks
fix: #8702

1. replace simple `proc.kill` in `queue stop --kill` with the same logic as in
   `queue kill` to make it possible to set false status `running` tasks
    as faileed.
2. Modified the tests.
2022-12-26 17:16:46 +09:00
Fabian Zills 28848c5407 exp run --run-all: re-enable --jobs concurrency (#8608)
* re-enable --jobs concurrency

* use `start_workers` instead of `spawn_worker`

* make `spawn_worker` a private method.

* update tests: make `spawn_worker` a private method.
2022-12-26 16:49:46 +09:00
David de la Iglesia Castro b13184fcec tests: Fix flaky test_show_only_changed.
Some of the random exp names could start with `bar`.

https://github.com/iterative/dvc/actions/runs/3757008396/jobs/6383731723#step:5:79
2022-12-22 13:01:46 +01:00
dependabot[bot] ee5fbc72ab build(deps-dev): Bump dvc-render from 0.0.15 to 0.0.17
Bumps [dvc-render](https://github.com/iterative/dvc-render) from 0.0.15 to 0.0.17.
- [Release notes](https://github.com/iterative/dvc-render/releases)
- [Commits](https://github.com/iterative/dvc-render/compare/0.0.15...0.0.17)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-12-22 12:17:56 +01:00
David de la Iglesia Castro d7fd68a1ae Add start and done calls with post_live_metrics. 2022-12-22 11:33:59 +01:00
Daniele Trifirò 59d6d5ea98 exp save: simplify implementation 2022-12-22 11:01:41 +01:00
dberenbaum 6fc0a6ff98 plots: accept any number of y data sources 2022-12-20 14:32:40 +01:00
dberenbaum b4b5db4b4e plots: accept list of y vals 2022-12-20 14:32:40 +01:00
Saugat Pachhai (सौगात) 23e4d376c9 index: remove unused APIs
These are being used by some users, but removing it for easier refactoring.
We are not using it ourselves.
2022-12-20 13:17:28 +05:45
dependabot[bot] 089a90c969 build(deps-dev): Bump pylint from 2.15.8 to 2.15.9
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.15.8 to 2.15.9.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.15.8...v2.15.9)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-12-19 11:28:58 +05:45
dependabot[bot] 198daec421 build(deps): Bump pyinstaller from 5.6.2 to 5.7.0
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 5.6.2 to 5.7.0.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v5.6.2...v5.7.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-12-15 19:05:41 +05:45
dependabot[bot] 04eb6a9f00 build(deps-dev): Bump mypy from 0.990 to 0.991
Bumps [mypy](https://github.com/python/mypy) from 0.990 to 0.991.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.990...v0.991)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-12-15 19:05:27 +05:45
Saugat Pachhai (सौगात) 0467f6a036 add index._plot_sources 2022-12-15 13:00:27 +05:45
David de la Iglesia Castro dc3d9c9677 bump DVCLive lower bound 2022-12-14 20:45:57 +01:00
Daniele Trifirò fcf92b2755 remove a few experiments names 2022-12-14 18:14:36 +01:00
David de la Iglesia CastroandDave Berenbaum c806275167 Bump dvclive lower bound (#8683)
* Bump dvclive lower bound

* Update pyproject.toml

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

Co-authored-by: Dave Berenbaum <[email protected]>
2022-12-14 14:13:20 +00:00
dependabot[bot] bd1add0b95 build(deps-dev): Bump dvc-task from 0.1.6 to 0.1.8
Bumps [dvc-task](https://github.com/iterative/dvc-task) from 0.1.6 to 0.1.8.
- [Release notes](https://github.com/iterative/dvc-task/releases)
- [Commits](https://github.com/iterative/dvc-task/compare/0.1.6...0.1.8)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-12-14 11:23:26 +08:00
Ivan Shcheklein e36f0e9d85 reset all indecies of brancher switch 2022-12-13 23:43:23 +02:00
dependabot[bot] bc1b8f3494 build(deps-dev): Bump pylint from 2.15.7 to 2.15.8
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.15.7 to 2.15.8.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.15.7...v2.15.8)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-12-13 14:53:57 +01:00
dependabot[bot] 9169d273e3 build(deps-dev): Bump filelock from 3.8.0 to 3.8.2
Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.8.0 to 3.8.2.
- [Release notes](https://github.com/tox-dev/py-filelock/releases)
- [Changelog](https://github.com/tox-dev/py-filelock/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/py-filelock/compare/3.8.0...3.8.2)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-12-12 20:38:22 +01:00
David de la Iglesia Castro bb7512687c exp: Generate a human-readable name beforehand.
- Add `get_random_exp_name` to utils.
Generates a name (`f"{adjective}-{noun}"`) by randomly chosing pairs of adjetive, noun.
Recieves `scm` and `baseline_rev` and retries the random choice until a non-existing ref is found.

Closes https://github.com/iterative/dvc/issues/8650
2022-12-09 10:32:38 +01:00
Saugat Pachhai (सौगात) 334f297ad3 deps: remove 3.11 checks for hydra; has 3.11 support now 2022-12-09 10:32:12 +05:45
Peter Rowlands ace3ba0ef9 worktree push: disable rev flags 2022-12-09 09:21:19 +09:00
Peter Rowlands 959d149381 worktree fetch: support revs flags 2022-12-09 09:21:19 +09:00
Peter Rowlands 7a65d67e7f worktree: support recursive/with_deps targets 2022-12-09 09:21:19 +09:00
Peter Rowlands e9be6779ea worktree: disable run-cache push/fetch 2022-12-09 09:21:19 +09:00
Peter Rowlands 876d123f04 index: use lazy view loading 2022-12-08 20:56:05 +09:00
Peter Rowlands 2bfe403a28 deps: bump dvc-data to 0.28.4 2022-12-08 20:56:05 +09:00
Peter Rowlands 9bef123182 worktree: fix default worktree remote/odb exception 2022-12-08 11:01:42 +09:00
Peter Rowlands e98ca36f39 worktree push: only index known (tracked) files/dirs in the remote 2022-12-07 18:48:34 +09:00
Peter Rowlands b9bf73f043 push/fetch: support targets in cloud version push/fetch 2022-12-07 18:48:34 +09:00
dependabot[bot] 7628fe9d44 build(deps-dev): Bump pytest-xdist from 3.0.2 to 3.1.0
Bumps [pytest-xdist](https://github.com/pytest-dev/pytest-xdist) from 3.0.2 to 3.1.0.
- [Release notes](https://github.com/pytest-dev/pytest-xdist/releases)
- [Changelog](https://github.com/pytest-dev/pytest-xdist/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-xdist/compare/v3.0.2...v3.1.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-12-06 20:16:21 +05:45
Peter Rowlands d415a05460 remote: disable gc and status -c for versioned remotes 2022-12-06 19:33:52 +09:00
Peter Rowlands f5699ce16b update: disable rev/to-remote/no-download for worktrees 2022-12-06 15:14:17 +09:00
Peter Rowlands baeedead3a update: add support for worktree update 2022-12-06 15:14:17 +09:00
Peter Rowlands 5630db55ad worktree: add support for updating outs 2022-12-06 15:14:17 +09:00
Peter Rowlands f1bd47ce0e index: make collect_targets public 2022-12-06 15:14:17 +09:00
dependabot[bot] 74a13a4d9a build(deps-dev): Bump dvc-render from 0.0.14 to 0.0.15
Bumps [dvc-render](https://github.com/iterative/dvc-render) from 0.0.14 to 0.0.15.
- [Release notes](https://github.com/iterative/dvc-render/releases)
- [Commits](https://github.com/iterative/dvc-render/compare/0.0.14...0.0.15)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-12-02 08:30:37 +01:00
dberenbaum b7732a591d stage add: don't fail if unable to create .gitignore 2022-12-01 16:43:31 +01:00
Saugat Pachhai (सौगात) d57bb23c93 fix windows path handling 2022-12-01 15:56:26 +01:00
Saugat Pachhai (सौगात) 28402dedf1 add tests 2022-12-01 15:56:26 +01:00
Saugat Pachhai (सौगात) df60f5d9bf collect top-level params/plots/metrics together with stages 2022-12-01 15:56:26 +01:00
Saugat Pachhai (सौगात) 7ea73b2596 wip: top level params and metrics 2022-12-01 15:56:26 +01:00
Peter Rowlands aa2e830511 deps: bump dvc-data to 0.28.3 2022-12-01 16:02:59 +09:00
dependabot[bot] 6fdd6673f3 build(deps-dev): Bump pylint from 2.15.5 to 2.15.7
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.15.5 to 2.15.7.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.15.5...v2.15.7)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-11-30 16:33:01 +01:00
dberenbaum fac5f18dea plots: add tests for errors in vega converter 2022-11-29 16:08:29 +01:00
dberenbaum d8068430e7 plots: fix multi-file plots 2022-11-29 16:08:29 +01:00
Peter Rowlands 25b1ef9971 worktree push: separate worktree from version_aware 2022-11-29 22:45:16 +09:00
Peter Rowlands df563e43c1 remote: make worktree imply version_aware 2022-11-29 22:45:16 +09:00
karajan1001 3ba6b1f0bf Solve the unexpected error at the end of the queued tasks running
fix: #8615
1. This is because, at the end of the tasks,
the temp running directly will be deleted

1. Only fetch result during running (not during the result collection)
2. Bump into scmrepo 0.1.4 in which we wrapped `KeyError` into
   `SCMError`.
2022-11-29 15:24:19 +08:00
Peter Rowlands 6a9fbaf3d2 deps: bump dvc-data to 0.28.2 2022-11-29 14:37:11 +09:00
David de la Iglesia Castro a036eaf817 exp: save: Add include_untracked.
Allow to include a list of potentially untracked files.
Covers the DVCLive use case where in the first run the generated files won't be tracked in Git yet.
2022-11-28 15:49:34 +01:00
David de la Iglesia Castro 665185b9e7 exp: save: Handle staged changes.
Staged changes were causing a merge error.
Warn and unstage instead of erroring, to match exp run behavior.
2022-11-28 15:49:34 +01:00
David de la Iglesia Castro 43e281521e exp save: cleanup.
Remove metrics.

Move tests to separate file.

Fix `experiments.get_exact_name` usage.

Remove unused checkpoint logic.
2022-11-28 15:49:34 +01:00
Daniele Trifirò 73b46a5789 exp save: initial implementation 2022-11-28 15:49:34 +01:00
David de la Iglesia Castro 8124396218 exp: Expose baseline and name via run_env.
Required by DVCLive for the experiments onboarding and live metrics features.

Related (not sure if closes): https://github.com/iterative/dvc/issues/6668
2022-11-28 13:13:04 +01:00
Peter Rowlands e95ae5613d index: skip data index load on empty view 2022-11-28 18:49:32 +09:00
karajan1001 ca4dd08e37 Use temp branch name to avoid locking the exec branches in temp dir executor
fix: #8448
In our current temp dir executor, we use will first set EXEC_* branches
to revisions retrieved from stash entry. And push them to remote, but
because the push operation is very slow, our locking operation will make
the whole repository unusable.

1. Now we set revisions from stash entry to some temp branches and then
   push them to remote.
2022-11-28 17:04:16 +08:00
karajan1001 0ebc253a43 rwlock auto solve corrupted lockfile 2022-11-28 17:04:16 +08:00
karajan1001 6bfce5a5db Use a more granular lock on exp operations.
For now in `experiment.new`, `init_executor` and `collect_exectuor` we
lock the whole scm repo. We can make a more granular lock in these
operations.
2022-11-28 17:04:16 +08:00
karajan1001 5fa33a06bc Move EXEC_TMP_DIR and EXEC_PID_DIR to experiment.utils 2022-11-28 17:04:16 +08:00
Peter Rowlands 6a75f3c327 deps: bump dvc-data to 0.28.1 2022-11-28 16:10:31 +09:00
Ivan Shcheklein d7587556c9 exp list: cleanup and move logic inside repo api 2022-11-25 16:15:17 -08:00
skshetry bb80eab77a deps: add support for hdfs in Python 3.11 2022-11-25 11:16:42 +05:45
Ruslan Kuprieiev ab3f8fb1a4 Create hook-asyncssh.py
Fixes #7577
2022-11-24 23:25:00 +02:00
Yury 6edca79b79 repro dry: show information if the stage is cached
Fixes: #8342
2022-11-24 21:49:52 +02:00
Ruslan Kuprieiev 2a888970e8 build: fpm: don't double quote cli option
Leftover from #8611
2022-11-24 01:34:18 +02:00
Ruslan Kuprieiev 73fdfc8059 build: fpm: don't create .build-id/* files
These are leaking into our rpm packages.

Related https://github.com/iterative/dvc-s3-repo/issues/86
2022-11-24 01:02:44 +02:00
Ruslan Kuprieiev 8dc11417d2 testing: api: test opening a file in subdir
Related to #8420
2022-11-23 17:47:13 +02:00
b02855fb93 ui: Fix WSL check in open_browser (#8604)
* ui: Fix WSL check in `open_browser`

* Update dvc/ui/__init__.py

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

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

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

Co-authored-by: Peter Rowlands (변기호) <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-11-23 09:57:57 +00:00
Peter Rowlands af255a46b4 worktree: use progress bars in push/fetch 2022-11-23 13:05:51 +09:00
Peter Rowlands 2748492f6b worktree push: don't push existing versions 2022-11-23 13:05:51 +09:00
Peter Rowlands c69d3188a6 deps: bump dvc-data to 0.28.0 2022-11-22 19:04:18 +09:00
karajan1001 9b9c04e633 Solve the problem during executor running
fix #7693

The problem was when we gathered experiments, and at the same time,
the executor are creating new commits. It will make the checkpoint
top/head return by `get_running_exps`/`get_ref`/`branch_revs` not the
same one. And cause the problem described in #7693

To solve this we did
1. move the `get_rev` to the beginning of `exp show` to make sure the
   return results in `branch_revs`
2. Add a new function to move all of the properties that only belong to
   `head` of the checkpoint from the wrong place to the top.
3. Add a new unit test for it.
4. Force fetch_running_exp to return data.
5. fix a bug in tmp_dir_executor.
2022-11-22 17:44:55 +08:00
karajan1001 350e0fb1f7 queue kill: we can manually mark problematic tasks as failure
fix: #8461
fix: #8473
For now, for those inactive but uncompleted tasks, they still shown as
Running in `exp show` and `task queue`, we cannot remove them because
they are regarded as running, we cann't kill them because their are no
real process behind them.

1. Now we can examine these false running tasks and mark them as failure
   in `queue kill` command. #8461
2. Better msg handle for initliaizing tasks in `queue kill`#8473
3. Add new unit tests for both cases.
4. bump into new dvc-task 0.1.6
2022-11-22 13:33:23 +08:00
dberenbaum f14358bc72 plots: add test for default x label 2022-11-21 21:05:12 +01:00
David de la Iglesia Castro 705bc6ec82 Added test_infer_x_label 2022-11-21 21:05:12 +01:00
dberenbaum 61a02e6f56 plots: set default x label 2022-11-21 21:05:12 +01:00
Saugat Pachhai (सौगात) 8e5226ece0 cli: remove version action 2022-11-21 20:37:05 +05:45
Peter Rowlands 2a1af0b880 add: merge existing file meta for versioned dirs 2022-11-21 19:18:41 +09:00
Peter Rowlands 014d149512 out: move restore_fields logic into Output 2022-11-21 19:18:41 +09:00
Peter Rowlands 77ae4aa92e deps: bump dvc-azure to 2.20.5 2022-11-21 18:21:02 +09:00
Peter Rowlands 588471a811 deps: bump dvc-data to 0.27.0 2022-11-21 18:21:02 +09:00
dependabot[bot] e59c19e752 build(deps-dev): Bump dvc-render from 0.0.13 to 0.0.14
Bumps [dvc-render](https://github.com/iterative/dvc-render) from 0.0.13 to 0.0.14.
- [Release notes](https://github.com/iterative/dvc-render/releases)
- [Commits](https://github.com/iterative/dvc-render/compare/0.0.13...0.0.14)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-11-21 10:08:09 +01:00
David de la Iglesia CastroandDave Berenbaum b0a92414fa hydra: Use OmegaConf.to_yaml for dumping .yaml output. (#8587)
* hydra: Use `OmegaConf.to_yaml` for dumping `.yaml` output.

Combining `OmegaConf.to_object` with our yaml DUMPER (ruaml.yaml) was not correctly handling strings like 'no'.

`OmegaConf.to_yaml` uses a custom string representer that correctly handles those cases.

Fixes #8583

* Update dvc/utils/hydra.py

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

Co-authored-by: Dave Berenbaum <[email protected]>
2022-11-19 12:37:18 +00:00
Peter Rowlands dae500d7c7 worktree: use view instead of full index in push/fetch 2022-11-19 09:55:58 +09:00
Peter Rowlands a01bc2918e push/fetch: move worktree logic into repo.worktree 2022-11-19 09:55:58 +09:00
David de la Iglesia Castro a33bbc97d5 parsing: Escape str interpolation in dict unpacking.
Use subprocess.list2cmdline (windows) / shlex.quote

Closes #8203
Closes #8370
2022-11-18 20:00:16 +01:00
Paweł Redzyński ba2e8bdac7 plots: data conversion: adjust for viewer backend (#8421)
* plots: vega: converter: support multi series data split and props inference

Fixes: #7754, #8399
Related: iterative/studio#4369

* plots: converter: make converter infer x and y labels if not provided

* plots: vega: converter: roll back to datapoints format

* plots: vega: converter: refactor

* plots: infer labels on

* plots: get_plot_id: fix naming

* plots: vega: converter: notes regarding converting
2022-11-18 12:36:00 +00:00
Peter Rowlands 4d6e045989 status -c: exclude push: false outs 2022-11-18 16:32:29 +09:00
Peter Rowlands 00eea4560e import-url: default to push: false for cloud-versioned imports 2022-11-18 16:32:29 +09:00
Peter Rowlands 6e02dddabd output: check push in get_used_objs 2022-11-18 16:32:29 +09:00
Peter Rowlands b550c5e625 output: add PARAM_PUSH 2022-11-18 16:32:29 +09:00
Ruslan KuprieievandPeter Rowlands b36861ada9 tests: import: use odb.clear()
Co-authored-by: Peter Rowlands <[email protected]>
2022-11-17 10:56:01 +02:00
Ruslan Kuprieiev d482e26c99 deps: bump dvc-data to 0.26.0 2022-11-17 10:56:01 +02:00
dependabot[bot] a8ad59ae4e build(deps-dev): Bump dvc-render from 0.0.12 to 0.0.13
Bumps [dvc-render](https://github.com/iterative/dvc-render) from 0.0.12 to 0.0.13.
- [Release notes](https://github.com/iterative/dvc-render/releases)
- [Commits](https://github.com/iterative/dvc-render/compare/0.0.12...0.0.13)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-11-16 11:13:05 +01:00
Ivan Shcheklein 76cc649f5e fix/8550+: cover with test 2022-11-16 15:16:37 +05:45
Ivan Shcheklein d52e1aa17b fix: exp list ref heads handling 2022-11-16 15:16:37 +05:45
Saugat Pachhai (सौगात) 65f61acffa tests: garbage collect sqlite connections on Windows + Python 3.11
There seems to be a regression in Python 3.11, where the sqlite
connections are not deallocated, due to some internal changes in
Python 3.11, where they are now using LRU cache. They are not deallocated
until `gc.collect()` is not called.

See https://github.com/python/cpython/issues/97641.
This affects only Windows, because when we try to remove the tempdir for
the exp run, the sqlite connection is open which prevents us from
deleting that folder.

Although this may happen in real scenario in `exp run`, I am only fixing
the tests by mocking `dvc.close()` and extending it by calling
`gc.collect()` after it. We could also mock `State.close()` but didnot
want to mock something that is not in dvc itself.

The `diskcache` uses threadlocal for connections, so they are expected
to be garbage collected, and therefore does not provide a good way to
close the connections. The only API it offers is `self.close()` and that
only closes main thread's connection. If we had access to connection,
an easier way would have been to explicitly call `conn.close()`.
But we don''t have such option at the moment.

Related: https://github.com/iterative/dvc/pull/8404#issuecomment-1310024661
GHA Failure: https://github.com/iterative/dvc/actions/runs/3437324559/jobs/5731929385#step:5:57
2022-11-16 08:02:43 +05:45
Peter Rowlands fdc689954f pyinstaller: drop google-api-client hooks 2022-11-15 22:07:05 +09:00
Peter Rowlands 076c48532d deps: bump dvc-gdrive to 2.19.1 2022-11-15 22:07:05 +09:00
David de la Iglesia Castro e4ea463c73 hydra: Raise error when name and sweeps. 2022-11-14 21:34:55 +01:00
Saugat Pachhai (सौगात) 87d8269365 typecheck: fix type error in _git_info 2022-11-14 10:21:31 +05:45
Peter Rowlands a753ee215f output: fix get_entry dep object generation for imports 2022-11-13 14:41:21 +02:00
dependabot[bot] 014a38fae2 build(deps-dev): Bump iterative-telemetry from 0.0.5 to 0.0.6
Bumps [iterative-telemetry](https://github.com/iterative/telemetry-python) from 0.0.5 to 0.0.6.
- [Release notes](https://github.com/iterative/telemetry-python/releases)
- [Commits](https://github.com/iterative/telemetry-python/compare/0.0.5...0.0.6)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-11-11 14:54:07 +09:00
Peter Rowlands 8913f5a6fe mypy: allow implicit-optional 2022-11-11 14:17:17 +09:00
dependabot[bot] 4ce9825c2b build(deps-dev): Bump mypy from 0.982 to 0.990
Bumps [mypy](https://github.com/python/mypy) from 0.982 to 0.990.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.982...v0.990)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-11-11 14:17:17 +09:00
dependabot[bot] e9c5b5fa84 build(deps-dev): Bump dvc-gs from 2.19.1 to 2.20.0
Bumps [dvc-gs](https://github.com/iterative/dvc-gs) from 2.19.1 to 2.20.0.
- [Release notes](https://github.com/iterative/dvc-gs/releases)
- [Commits](https://github.com/iterative/dvc-gs/compare/2.19.1...2.20.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-11-11 13:33:10 +09:00
karajan1001 fa54c1af7c Solve run_all flaky tests
1. Modify run all to include currently running exps.
2. bump dvc-task to 0.1.5
2022-11-11 07:04:43 +05:45
karajan1001 9fbbfd07a6 Fix some celery queue realted ci failure
fix: #8403
1. remove some of the flaky mark
2. In `get_result` make sure the celery task is completed.
2022-11-11 07:04:43 +05:45
karajan1001 929de7c651 Solve the crash on getting name of applied experiment branch
fix: #8526
1. The current `get_names` will crash if an experiment branch had
   already been given a new branch name to it.

1. Solve the crash when we getting the name of a newly fixed experiment
   branch.
2. Add a new unit test to it.
2022-11-10 13:18:45 +08:00
blakeNaccarato 49af6e1435 plots: support svg
Add SVG to parser and parametrize an existing functionality test on SVG as well as JPG.

Fixes #6714.
2022-11-09 19:23:49 +02:00
skshetry a6bfc7db4e ci: test with 3.11 on all OSes (#8543)
* ci: test with 3.11 on all OSes

* Update tests.yaml

* Update tests.yaml
2022-11-09 12:05:27 +00:00
Yury 872a0d663e dvc exceptions CyclicGraphError: add more clear message for the exception
Fixes: #7915
2022-11-09 14:33:08 +05:45
dependabot[bot] c01583f3dd build(deps-dev): Bump dvc-task from 0.1.4 to 0.1.5
Bumps [dvc-task](https://github.com/iterative/dvc-task) from 0.1.4 to 0.1.5.
- [Release notes](https://github.com/iterative/dvc-task/releases)
- [Commits](https://github.com/iterative/dvc-task/compare/0.1.4...0.1.5)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-11-09 16:37:14 +09:00
karajan1001 e0d41c7012 Make exp show handle errors better
fix: #8350

We shouldn't add locks to exp show to prevent race conditions
as this command is calling continuously by the extension.
So we make a better way to handle all kinds of reference failures.

1. Drop all based experiments if got a SCMError in baseline.
2. Drop all checkpoint experiments if any commit got a SCMError in
   one of checkpoint commit.
3. Drop the entire experiments if got a SCMError.
4. Add tests for each of the conditions above.
2022-11-08 20:12:22 +08:00
Peter Rowlands 628b992442 index: fix granular dir output view filtering 2022-11-08 19:28:10 +09:00
Peter Rowlands 99e7f52bbd index: support filtering by output in views 2022-11-08 19:28:10 +09:00
skshetry b2a275ee9e Create codeql.yml 2022-11-08 13:21:20 +05:45
skshetry 7e423b668f pkging: also post released packages to test.pypi.org 2022-11-07 18:57:53 +05:45
Saugat Pachhai (सौगात) 2c3956bdb0 exp init: fix live metrics path 2022-11-07 12:45:32 +01:00
Saugat Pachhai (सौगात) a48a19ce31 tests: ignore versions for subprojects 2022-11-07 12:45:32 +01:00
David de la Iglesia Castro 1de983f0de Remove live section support. 2022-11-07 12:45:32 +01:00
David de la Iglesia Castro eb20a8e049 deps: Bump dvclive to >= 1.0 2022-11-07 12:45:32 +01:00
David de la Iglesia Castro 78e25747a4 exp init: Update DVCLive paths.
Closes #8531
2022-11-07 12:45:32 +01:00
dberenbaum 240f0ebdc3 fix hydra_sweeps referenced before assignment 2022-11-04 15:59:26 +01:00
skshetry 69f78d3bb6 ci: bench: use 3.11 in benchmarks 2022-11-04 18:43:02 +05:45
skshetry 740fe504c9 Update README.rst 2022-11-04 18:27:50 +05:45
skshetry 7bb84286f5 readme: show downloads badge 2022-11-04 18:27:50 +05:45
Peter Rowlands 3891e81bdd index: load dir entries before generating target views 2022-11-04 19:09:48 +09:00
Peter Rowlands bb62833897 index: move import specific entry building out of index.data() property 2022-11-04 19:09:48 +09:00
Peter Rowlands fb703150ce output: load precomputed md5 from PARAM_FILES for non-local fs 2022-11-04 19:09:48 +09:00
Peter Rowlands 33d4ead8b6 import-url: save files for version-aware dir 2022-11-04 19:09:48 +09:00
Peter Rowlands 76b1f8c907 deps: bump dvc-data to 0.25.3 2022-11-04 19:09:48 +09:00
Paweł Redzyński d7152e360e Update dvc/repo/plots/__init__.py 2022-11-04 09:54:45 +05:45
dberenbaum ab69b88a21 plots: allow top-level strings 2022-11-04 09:54:45 +05:45
dependabot[bot] 43a8eab2e0 build(deps-dev): Bump dvc-s3 from 2.20.1 to 2.21.0
Bumps [dvc-s3](https://github.com/iterative/dvc-s3) from 2.20.1 to 2.21.0.
- [Release notes](https://github.com/iterative/dvc-s3/releases)
- [Commits](https://github.com/iterative/dvc-s3/compare/2.20.1...2.21.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-11-04 08:09:29 +05:45
Saugat Pachhai (सौगात) 15f35e827e fix windows 2022-11-03 23:07:28 +05:45
Saugat Pachhai (सौगात) 4d17c171f1 tests: use mocker 2022-11-03 23:07:28 +05:45
Saugat Pachhai (सौगात) c27005a8b7 migrate test_{repro,run}*.py to pytest 2022-11-03 23:07:28 +05:45
Saugat Pachhai (सौगात) ecd371f8ae migrate test_move.py to pytest 2022-11-03 23:07:28 +05:45
Saugat Pachhai (सौगात) bda1388bff migrate test_gc.py to pytest 2022-11-03 23:07:28 +05:45
Saugat Pachhai (सौगात) c02bd7f318 migrate some files to pytest 2022-11-03 23:07:28 +05:45
Saugat Pachhai (सौगात) fe1d805c80 migrate test_cli.py to pytest 2022-11-03 23:07:28 +05:45
Saugat Pachhai (सौगात) f9b0de883f migrate test_checkout.py to pytest 2022-11-03 23:07:28 +05:45
Saugat Pachhai (सौगात) 1cad8ea7fd migrate test_{add,analytics,init,unprotect,local,process}.py to pytest 2022-11-03 23:07:28 +05:45
David de la Iglesia Castro 08a643e005 hydra: Raise lazy DvcException for Python >= 3.11 2022-11-03 12:14:18 +01:00
Peter Rowlands 97a8b6978c deps: bump scmrepo to 0.1.3 2022-11-03 15:56:50 +09:00
Peter Rowlands b44099367e exp run: catch hydra import in 3.11 2022-11-03 15:38:53 +09:00
skshetry 29b3dc1f9e fix 3.10 in setup-python 2022-11-01 16:35:24 +05:45
Saugat Pachhai (सौगात) 6e73bd0b5b use setuptools_scm provided version 2022-11-01 12:44:31 +02:00
skshetry fa5a9abdeb build: bump pyinstaller packages python version to 3.10 (#8511)
bump pyinstaller packages python version to 3.10

As 3.11 is now released, let's bump pyinstaller packages to 3.10 (we always keep them 1 version behind).
2022-11-01 16:24:00 +05:45
dependabot[bot] 30775f7b31 build(deps): Bump pyinstaller from 5.6.1 to 5.6.2 (#8499)
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 5.6.1 to 5.6.2.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v5.6.1...v5.6.2)

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

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-01 15:19:02 +05:45
Peter Rowlands e07aca9fd3 commit: skip changed_entries check on force commit 2022-11-01 18:05:41 +09:00
skshetry d2265f36ba ci: run only single macOS job (#8509)
* ci: run only single macOS job

* ci: use alls-green@release gate check
2022-11-01 14:30:46 +05:45
Saugat Pachhai (सौगात) d548618291 codecov: disable patch status 2022-11-01 14:02:59 +05:45
skshetry 48ff332f4a tests: remove old cleanup handler for Windows subprocess (#8508) 2022-11-01 13:57:50 +05:45
Saugat Pachhai (सौगात) 97f5ffc3c6 codecov: re-add codecov.yml 2022-11-01 13:56:54 +05:45
skshetry f6849d2f06 chore: remove bin dir and simplify tests.__main__ (#8507) 2022-11-01 13:05:53 +05:45
skshetry a172240536 Update release.yml 2022-11-01 11:43:25 +05:45
skshetry 129a4fbb92 fix release note config, it has to be inside changelog key (#8504) 2022-11-01 11:42:05 +05:45
skshetry c101514677 ci: add release note config (#8503) 2022-11-01 11:37:11 +05:45
skshetry 5e883bc00c tests: remove TestDir (#8502)
remove TestDir
2022-11-01 11:28:09 +05:45
skshetry 4b720e5820 remove .codecov.yml and MANIFEST.in, move .coveragerc to pyproject.toml (#8500)
* remove .codecov.yml and MANIFEST.in, move .coveragerc to pyproject.toml

* remove scripts/pypi_upload.sh
2022-11-01 10:11:55 +05:45
skshetry c0054fa57e remove zip-safe (#8498)
See https://setuptools.pypa.io/en/latest/deprecated/zip_safe.html. `zip_safe` is not required these days.
2022-11-01 07:50:18 +05:45
skshetry 6049494cef set name so that dependency graph works (#8497)
See https://github.com/orgs/community/discussions/6456
2022-11-01 07:09:21 +05:45
skshetry 120a5879bb ci: push package to test.pypi.org (#8495)
* ci: push package to test.pypi.org

* include dvc.testing and license file
2022-10-31 23:34:00 +05:45
Step Security Bot c8e39523c3 [StepSecurity] ci: Harden GitHub Actions (#8496)
Signed-off-by: StepSecurity Bot <[email protected]>
2022-10-31 23:27:18 +05:45
skshetry 7648e715f8 move project config to pyproject.toml (#8493)
* move config to pyproject.toml

* ci: use pyproject.toml for cache key

* pyproject.toml: use recursive dependencies for dev and all

* Update pyproject.toml
2022-10-31 22:43:48 +05:45
skshetry ca5ad6457f ci: cache lint deps, decrease test timeout (#8494) 2022-10-31 22:15:54 +05:45
dependabot[bot] de848d9598 build(deps-dev): Bump pytest-xdist from 2.5.0 to 3.0.2 (#8474)
Bumps [pytest-xdist](https://github.com/pytest-dev/pytest-xdist) from 2.5.0 to 3.0.2.
- [Release notes](https://github.com/pytest-dev/pytest-xdist/releases)
- [Changelog](https://github.com/pytest-dev/pytest-xdist/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-xdist/compare/v2.5.0...v3.0.2)

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

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-31 21:23:57 +05:45
dependabot[bot] 4330f1a70f build(deps): Bump pyinstaller from 5.0 to 5.6.1 (#8475)
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 5.0 to 5.6.1.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v5.0...v5.6.1)

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

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-31 21:23:43 +05:45
dependabot[bot] 144e21b552 build(deps-dev): Bump pytest from 7.1.3 to 7.2.0 (#8479)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.1.3 to 7.2.0.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.1.3...7.2.0)

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

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-31 21:20:21 +05:45
dependabot[bot] 7945428e5e build(deps-dev): Bump pylint from 2.15.4 to 2.15.5 (#8463)
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.15.4 to 2.15.5.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.15.4...v2.15.5)

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

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-31 21:19:58 +05:45
skshetry 6df70d388e update pre-commit; add some more hooks (#8492)
* update pre-commit; add some more hooks

* Apply suggestions from code review
2022-10-31 21:04:11 +05:45
skshetry 4ca2158b9d tests: retry celery queue on timeout (#8488)
retry celery queue on timeout
2022-10-31 19:38:05 +05:45
Ruslan Kuprieiev f87e78d754 deps: bump dvc-data to 0.25.2 2022-10-31 14:41:23 +02:00
Ruslan Kuprieiev 5899440b46 deps: bump dvc-data to 0.25.1 2022-10-30 00:59:39 +03:00
Ruslan Kuprieiev 61ce52f259 yappi: add --yappi-separate-threads flag 2022-10-29 21:23:37 +03:00
Ruslan Kuprieiev 475ef3ce65 Delete release-drafter.yml 2022-10-29 19:39:55 +03:00
karajan1001 22eb9051f6 Move the function out of rwlock 2022-10-29 08:53:02 +08:00
karajan1001 9ef1d1d6e6 Move away tempdir related change from basequeue. 2022-10-29 08:53:02 +08:00
karajan1001 d5cc54ad35 dvc exp show auto solve corrupted rwlock info
fix: #8450

1. Add a new function check_rwlock to check and autocorrect rwlock info.
2. Add related tests for it.
2022-10-29 08:53:02 +08:00
karajan1001 47ece317a5 Refactor: move get_running_exps to queue
Move logic of `get_running_exps` from `repo.experiments` to each queue.
2022-10-29 08:53:02 +08:00
Ruslan Kuprieiev 8f82a421a0 index: support multiple targets within output in IndexView
stage_infos might contain the same stage multiple times, but with different
filter_info, so we shouldn't convert it to dict, as that makes us lose all
but one filter_info's.
2022-10-25 13:37:44 +03:00
karajan1001 ed2ccaf54c Use celery status as the exp show status
fix: #8349
`tasks status turned to queued for 1 second before turned into success.`
is because in previous the status is using the output of
`get_running_exps` in `repo.expereiments` and when entering the
collect result progress, the `get_running_exps` will mark the tasks to
be not running because the `info.result` is not None. Here we use the
output of the celery queue as the universal standard for the tasks
status.

1. Use celery status as the exp show status
2022-10-24 14:41:27 +08:00
Ruslan Kuprieiev 23534063ca push: use build_tree instead of tree_from_index 2022-10-21 17:02:33 +03:00
Ruslan Kuprieiev d56957416e index: data: don't forget to set entry.key 2022-10-21 17:02:33 +03:00
dependabot[bot] 269e12287c build(deps): Bump dvc-data from 0.20.0 to 0.22.0
Bumps [dvc-data](https://github.com/iterative/dvc-data) from 0.20.0 to 0.22.0.
- [Release notes](https://github.com/iterative/dvc-data/releases)
- [Commits](https://github.com/iterative/dvc-data/compare/0.20.0...0.22.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-10-21 17:02:33 +03:00
karajan1001 5413187c2d Give lock acquiring more time in concurrency situation.
fix: #8417

1. Give the executor initialization and result collection more time for
   acquiring locks.
2022-10-21 10:18:28 +08:00
karajan1001 d56376c70a exp show: Use batch call on scm.describe
fix: #8451

1. Change the call of scm.describe from individual revision to a
   collection of them. Accelerate the run speed of `exp show`
2. Bump scmrepo to 0.1.2
2022-10-20 17:22:25 +08:00
Saugat Pachhai (सौगात) 1d5d15644d debug: add --show-stack/--ss flag 2022-10-19 17:13:22 +03:00
Peter Rowlands a0e45cdd5d index: remove partial_imports in favor of repo.imports view 2022-10-19 17:12:16 +03:00
Peter Rowlands 7fe316b8e8 import-url: only unpartial imports in workspace 2022-10-19 17:12:16 +03:00
Peter Rowlands 3478a7e6a9 import-url: support cloud versioning 2022-10-19 17:12:16 +03:00
Peter Rowlands d1fa3d2c21 fetch: use data index save() for fetching imports 2022-10-19 17:12:16 +03:00
Peter Rowlands b594337a69 index/out: add out.get_entry() 2022-10-19 17:12:16 +03:00
Peter Rowlands 36f6f29ac8 index: set source fs/path for import data index entries 2022-10-19 17:12:16 +03:00
skshetry bbaf57c5b2 _debug: minor refactor on debugtools (#8454)
* _debug: minor refactor on debugtools

* delete test
2022-10-19 18:01:20 +05:45
David de la Iglesia Castro 61f8478885 tests: Remove test_ssh
Moved to dvc-ssh repo in https://github.com/iterative/dvc-ssh/pull/13
2022-10-19 12:35:11 +03:00
dependabot[bot] 4493da0f04 build(deps): Bump dvc-task from 0.1.3 to 0.1.4
Bumps [dvc-task](https://github.com/iterative/dvc-task) from 0.1.3 to 0.1.4.
- [Release notes](https://github.com/iterative/dvc-task/releases)
- [Commits](https://github.com/iterative/dvc-task/compare/0.1.3...0.1.4)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-10-19 15:40:56 +08:00
Peter Rowlands 7c225206ab fetch/push: use dvc-data TransferResult 2022-10-18 19:02:47 +09:00
Peter Rowlands be5a54bfb4 deps: bump dvc-data to 0.20.0 2022-10-18 19:02:47 +09:00
skshetry d9ce9b4fef testing.api_tests: remove comment from fixture (#8444) 2022-10-18 15:44:59 +05:45
dependabot[bot]andskshetry 689988592f build(deps-dev): Bump pylint from 2.15.2 to 2.15.4 (#8424)
* build(deps-dev): Bump pylint from 2.15.2 to 2.15.4

Bumps [pylint](https://github.com/PyCQA/pylint) from 2.15.2 to 2.15.4.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.15.2...v2.15.4)

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

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

* Remove astroid

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: skshetry <[email protected]>
2022-10-18 09:27:54 +05:45
dependabot[bot] 1ddfca856a build(deps): Bump dvc-data from 0.18.0 to 0.19.0 (#8442)
Bumps [dvc-data](https://github.com/iterative/dvc-data) from 0.18.0 to 0.19.0.
- [Release notes](https://github.com/iterative/dvc-data/releases)
- [Commits](https://github.com/iterative/dvc-data/compare/0.18.0...0.19.0)

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

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-18 09:27:27 +05:45
dependabot[bot] e451ce7d0b build(deps-dev): Bump pytest-mock from 3.9.0 to 3.10.0 (#8402)
Bumps [pytest-mock](https://github.com/pytest-dev/pytest-mock) from 3.9.0 to 3.10.0.
- [Release notes](https://github.com/pytest-dev/pytest-mock/releases)
- [Changelog](https://github.com/pytest-dev/pytest-mock/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-mock/compare/v3.9.0...v3.10.0)

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

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-18 07:52:16 +05:45
pre-commit-ci[bot] de05659a93 [pre-commit.ci] pre-commit autoupdate (#8441)
updates:
- [github.com/codespell-project/codespell: v2.2.1 → v2.2.2](https://github.com/codespell-project/codespell/compare/v2.2.1...v2.2.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-10-18 07:47:20 +05:45
Daniele Trifirò e9244924d7 plots: allow definition of plots section as list 2022-10-17 12:06:31 +02:00
David de la Iglesia Castro 3bbf8fa7d2 config: ssh: Add passphrase and ask_passphrase options.
Bump `dvc-ssh`.

Fixes #8137
2022-10-14 12:11:31 +03:00
Peter Rowlands ed87c308ba deps: bump dvc-data to 0.18.0
- callbacks: update to dvc-data 0.18.0 behavior
2022-10-13 20:00:19 +09:00
Peter Rowlands add28f73f1 brancher: clear repo.index on each branch 2022-10-11 16:12:00 +03:00
Peter Rowlands 2bc693fb4c index: add IndexView and Index.targets_view() 2022-10-11 16:12:00 +03:00
Peter Rowlands ac57fa53fe index: cache target collection 2022-10-11 16:12:00 +03:00
Peter Rowlands 0ca0d94cb2 output: mypy fixes 2022-10-11 16:12:00 +03:00
David de la Iglesia Castro c12deaa62c exp show: Preserve full branch and tag names.
Closes #7933
2022-10-11 19:55:36 +09:00
pre-commit-ci[bot] 5606608867 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/ambv/black: 22.8.0 → 22.10.0](https://github.com/ambv/black/compare/22.8.0...22.10.0)
2022-10-11 14:43:51 +08:00
Ruslan Kuprieiev 6711b653a5 tests: add tests for dvcignore.walk 2022-10-10 21:43:44 +03:00
Ruslan Kuprieiev faef8eec2c tests: ignore: use dvcignore.find directly 2022-10-10 21:43:44 +03:00
Ruslan Kuprieiev 11766c8839 ignore: walk: support detail=True 2022-10-10 21:43:44 +03:00
Peter Rowlands 1180a2f051 deps: bump dvc-data to 0.17.1 2022-10-10 16:25:07 +09:00
Ronan Lamy f59a99d442 Skip some tests on FSs that don't support dirs (like http) 2022-10-07 19:03:57 +03:00
Ronan Lamy d5f5e14183 make get-url testable with the dvc plugins 2022-10-07 19:03:57 +03:00
Ronan Lamy 10f60bed58 make get-url consistent with ls-url
Also, move parse_external_url() to dvc.fs as it's now used both by
ls_url() and get_url().
2022-10-07 19:03:57 +03:00
Ronan Lamy 6a0d7f9c51 Extract Output.download() into a function
Also, move the method to Dependency, since it's only used there.
2022-10-07 19:03:57 +03:00
dependabot[bot] 8640beb998 build(deps): Bump dvc-render from 0.0.11 to 0.0.12
Bumps [dvc-render](https://github.com/iterative/dvc-render) from 0.0.11 to 0.0.12.
- [Release notes](https://github.com/iterative/dvc-render/releases)
- [Commits](https://github.com/iterative/dvc-render/compare/0.0.11...0.0.12)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-10-06 17:30:35 +02:00
karajan1001 884182f6f2 Add "status" to replace "running" and "queued" in output of exp show
fix: #7986
1. Add two new flags  `--hide-queued` and  `--hide-failed` to `exp show`
2. Allow `exp show` to show failed experiments.
3. Add unit test for the failed experiments shown.
4. Add name support for failed exp
5. Add error msg to the `exp show` output
2022-10-06 17:20:03 +08:00
Peter Rowlands 7393ebdcb4 logger: init logging config before colorama 2022-10-04 21:27:24 +03:00
David de la Iglesia Castro cf651c52a1 hydra: Fix append and remove sweeps.
Closes #8376
2022-10-04 19:05:12 +02:00
Ronan Lamy a3f20eb6f4 ls-url: make config and recursive kwarg-only 2022-10-04 14:37:29 +03:00
Ronan Lamy d60edcd88e ls-url: fix doc URL 2022-10-04 14:37:29 +03:00
Ronan Lamy a6cdb9efd2 ls-url: remove redundant tests 2022-10-04 14:37:29 +03:00
Ronan Lamy 4a558edc59 ls-url: remove now unused _ls_url() 2022-10-04 14:37:29 +03:00
Ronan Lamy 8ff92cc7b0 ls-url: refactor tests so they work with all supported plugins.
Add `config` argument to ls_url() (only used in testing) to be able
to set up the FSs that do require it.
2022-10-04 14:37:29 +03:00
Ronan Lamy bd88000e2f ls-url: add ls-url tests for dvc plugins 2022-10-04 14:37:29 +03:00
Ronan Lamy 4da4c69985 ls-url: chain exceptions and simplify exc handling 2022-10-04 14:37:29 +03:00
Ronan Lamy 8a1c757497 fix test on Windows 2022-10-04 14:37:29 +03:00
Ronan Lamy 22655fa46d ls-url: update command test for --recursive 2022-10-04 14:37:29 +03:00
Ronan Lamy 103a0e2523 ls-url: simplify parse_external_url() 2022-10-04 14:37:29 +03:00
Ronan Lamy 0d025e5943 ls-url: add --recursive option 2022-10-04 14:37:29 +03:00
Ronan Lamy b01c4f8b05 ls-url: call get_cloud_fs() directly
Note: removes a spurious check on whether the target is a dvc file.
2022-10-04 14:37:29 +03:00
Ronan Lamy c27125d9c0 Replace Output with a local copy and remove all unused code 2022-10-04 14:37:29 +03:00
Ronan Lamy 71734a3d09 ls-url: fix test_file() on Windows and add another case 2022-10-04 14:37:29 +03:00
Ronan Lamy fa7619dfee ls-url: create basic version of dvc ls-url command
For now, this only takes a URL (or local path) and lists its contents
if the protocol supports it. The command fails if the target doesn't
exist and returns the (absolute) path to the target if it's not a
directory.
2022-10-04 14:37:29 +03:00
dependabot[bot] 67aaf6a192 build(deps-dev): Bump mypy from 0.981 to 0.982
Bumps [mypy](https://github.com/python/mypy) from 0.981 to 0.982.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.981...v0.982)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-10-04 12:02:42 +09:00
Daniele Trifirò 3adede8427 repo: use realpath to in _get_filepath when loading stages 2022-10-03 15:29:01 +02:00
Daniele Trifirò a686dded15 repo: fix crash while collecting stages with symlinks
closes #8345
2022-10-03 15:29:01 +02:00
Daniele Trifirò 8b1e61d3c5 update: update -help 2022-10-03 14:25:50 +02:00
Daniele Trifirò 4594087786 import: fix rev lock and pull with --no-download 2022-10-03 14:25:50 +02:00
Daniele Trifirò 5d69f57abf tests: add test_partial_import_pull 2022-10-03 14:25:50 +02:00
dependabot[bot] 53c886e407 build(deps-dev): Bump mypy from 0.971 to 0.981
Bumps [mypy](https://github.com/python/mypy) from 0.971 to 0.981.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.971...v0.981)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-10-02 12:44:42 +09:00
Ruslan Kuprieiev 890fbf3a6e deps: bump dvc-data to 0.14.0 2022-10-01 21:56:49 +03:00
skshetry 81abf2e412 dvcfs tests: copy pytest param instead of in-place update (#8388)
The param was being modified in-place, due to which successive runs would fail.
2022-10-01 14:07:38 +05:45
Ronan Lamy 3a9d0244b5 test: rename dvc.testing.test_*.py so pytest doesn't handle them as test files 2022-10-01 01:03:09 +03:00
dberenbaum 423166cb0f cli: remove foreach-group from help text 2022-09-30 19:54:03 +02:00
Peter Rowlands 4904286307 config: wrap UnicodeDecodeErrors on load 2022-09-30 11:45:43 +09:00
dependabot[bot] e556c632b3 build(deps-dev): Bump pytest-mock from 3.8.2 to 3.9.0
Bumps [pytest-mock](https://github.com/pytest-dev/pytest-mock) from 3.8.2 to 3.9.0.
- [Release notes](https://github.com/pytest-dev/pytest-mock/releases)
- [Changelog](https://github.com/pytest-dev/pytest-mock/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-mock/compare/v3.8.2...v3.9.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-29 14:37:06 +09:00
dependabot[bot] db0c6e9086 build(deps-dev): Bump pytest-cov from 3.0.0 to 4.0.0
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 3.0.0 to 4.0.0.
- [Release notes](https://github.com/pytest-dev/pytest-cov/releases)
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v3.0.0...v4.0.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-29 13:48:47 +09:00
dependabot[bot] 4177a5dc9e build(deps): Bump dvc-task from 0.1.2 to 0.1.3
Bumps [dvc-task](https://github.com/iterative/dvc-task) from 0.1.2 to 0.1.3.
- [Release notes](https://github.com/iterative/dvc-task/releases)
- [Commits](https://github.com/iterative/dvc-task/compare/0.1.2...0.1.3)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-29 13:47:49 +09:00
Ronan Lamy 04af944e22 fs: test and slightly refactor get_fs_config() 2022-09-28 22:06:40 +03:00
pre-commit-ci[bot] 13b2ac3bd1 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/ambv/black: 22.6.0 → 22.8.0](https://github.com/ambv/black/compare/22.6.0...22.8.0)
2022-09-28 17:07:09 +09:00
Paweł Redzyński 25ea5f35ec plots: pass all types of plots in --json option
Related: iterative/vscode-dvc#1757
2022-09-27 14:23:42 +02:00
karajan1001 6bd14b21ef Move infofile operation to executor and
Currently infofile dump was spread in both executor and queue classes.
put them all into executor to make them more managable. Move all ending
dump operations to the cleanup function, because the collect_result progress
might failed because of git operations, in which can guarantee them to
be run in a `finally` scope.
2022-09-26 16:25:47 +08:00
karajan1001 c2829f4ce8 Make executor info compatible with old versions. 2022-09-26 16:25:47 +08:00
karajan1001 d8a4ecd68f Remove the stale manager code 2022-09-26 16:25:47 +08:00
karajan1001 7fb9dcec12 Add tests for executor status
1. Move some basic test script from function tests to unit test.
2. Add success/failed tests for the status change of `tempdir`, `celery`, `workspace` running case.
2022-09-26 16:25:47 +08:00
karajan1001 f23bc71eea Use task status to replace collected.
fix: #8088

1. Use status to replace collected.
2. Make status show more accurate.

Fix lint
2022-09-26 16:25:47 +08:00
karajan1001 8b326137c6 Add a new attribute status to ExecutorInfo file
1. Add a new attribute status to ExecutorInfo file
2. Update running status to the executor infofile.
2022-09-26 16:25:47 +08:00
karajan1001 90109bccd7 Refactor seperate the initialization of executor and setup environment
Reason:
1. Need some more granular control over initialization progress of
   executor.

What done:
1. Seperate `init_git` and `init_cache` progress out from setup_executor
2. Move `set_ref` into `init_git`
2022-09-26 16:25:47 +08:00
Ruslan Kuprieiev 8c484645fa cloud-versioning: better handling for directories 2022-09-26 02:54:17 +03:00
dberenbaum c039e9a6e7 merge-driver: support removes and changes 2022-09-23 23:22:08 +03:00
Ruslan Kuprieiev 10cfa0d5d7 fetch: use md5() before save() 2022-09-23 15:12:19 +03:00
dependabot[bot] ecab4a22fe build(deps): Bump dvc-data from 0.10.1 to 0.12.0
Bumps [dvc-data](https://github.com/iterative/dvc-data) from 0.10.1 to 0.12.0.
- [Release notes](https://github.com/iterative/dvc-data/releases)
- [Commits](https://github.com/iterative/dvc-data/compare/0.10.1...0.12.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-23 15:12:19 +03:00
dberenbaum 4f354f5333 pull: hide glob option 2022-09-23 14:13:25 +03:00
skshetry f6b1f01a5c aiohttp: remove AIOHTTP_NO_EXTENSIONS envvar (#8353)
* aiohttp: remove AIOHTTP_NO_EXTENSIONS envvar

aiohttp now provides wheel for 3.11

* advertise 3.11 support
2022-09-23 16:23:37 +05:45
Saugat Pachhai (सौगात) 6d17bcce93 cli: add help for foreach 2022-09-23 13:36:34 +03:00
karajan1001 8da04c7c81 Solve the on_diverged function not executed error.
fix: #8348
2022-09-23 17:20:31 +08:00
Paweł Redzyńskiandpre-commit-ci[bot] c617769a1d deps: bump codespell (#8199)
[pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/codespell-project/codespell: v2.1.0 → v2.2.1](https://github.com/codespell-project/codespell/compare/v2.1.0...v2.2.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-09-22 19:31:24 +05:45
Daniele Trifirò 69c3c83833 import/import-url: ignore outs when using --no-download 2022-09-22 00:29:46 +03:00
David de la Iglesia Castro 122a68398e hydra: Fix sweeps on Defaults List.
Defaults List overrides (`defaults/foo=1,2`) were not working because of the inclusion of the `/` character.
Fixed it by updating how we construct the `merged_overrides` and added a regression test.
2022-09-21 15:48:19 +02:00
Martino Mensio 748c015638 fixed link to "get started: pipelines" docs 2022-09-21 13:45:39 +03:00
skshetry ba1ab86078 document dvcfs (#8332) 2022-09-21 11:30:15 +05:45
skshetry aaa467a96b fs.callback.RichCallback: add transient support (#8335) 2022-09-20 17:03:17 +05:45
Daniele Trifirò ed15e59115 deps: bump dvc-http to 2.27.2 2022-09-20 13:18:18 +03:00
skshetry e63cb31a66 dvcfs: remove get-file implementation, make dvcfs non-cachable (#8331)
dvcfs: remove get-file implementation
2022-09-20 15:24:44 +05:45
Peter Rowlands 4c1f172132 deps: bump dvc-data to 0.10.1 2022-09-20 18:30:35 +09:00
Ruslan Kuprieiev d2a31eb660 push: use update_pipeline=False for cloud versioning 2022-09-19 17:21:43 +03:00
Ruslan Kuprieiev 625b735502 deps: bump dvc-s3 2022-09-19 14:57:35 +03:00
skshetry 7e93fc8c4a lint: pin astroid version (#8323)
See https://github.com/PyCQA/pylint/issues/7488. Pylint is failing on our codebase.
2022-09-19 12:22:21 +05:45
skshetry 853a918d5c cli: rename cli util, hide data ls from the help (#8321) 2022-09-19 10:29:52 +05:45
skshetry b5f20f8467 use native concurrency (#8320) 2022-09-19 10:23:55 +05:45
skshetry 2f6bc70b45 remove mergify (#8319) 2022-09-19 09:50:56 +05:45
Daniele Trifirò 197f522175 deps: add testing group for dvc.testing requirements 2022-09-17 00:54:35 +03:00
Ruslan Kuprieiev 35e6f082a4 analytics: use iterative-telemetry for user_id lookup 2022-09-17 00:45:45 +03:00
Ruslan Kuprieiev 4531fec2b7 deps: bump dvc-data to 0.10.0 2022-09-16 22:56:56 +03:00
skshetry 391bb29be2 dvcfs: rename DvcFileSystem to DVCFileSystem (#8307) 2022-09-16 18:18:55 +05:45
Peter Rowlands 146bdbbe9b deps: bump dvc-azure to 2.20.4 2022-09-16 17:47:38 +09:00
Saugat Pachhai (सौगात) c1f659bdd7 dvcfs: prevent opening file object in write mode 2022-09-16 11:31:24 +03:00
dependabot[bot] 0fe8f4e368 build(deps): Bump dvc-render from 0.0.10 to 0.0.11
Bumps [dvc-render](https://github.com/iterative/dvc-render) from 0.0.10 to 0.0.11.
- [Release notes](https://github.com/iterative/dvc-render/releases)
- [Commits](https://github.com/iterative/dvc-render/compare/0.0.10...0.0.11)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-16 09:58:16 +02:00
Ruslan Kuprieiev 3cbf319e0d loader: don't forget to set meta.isdir
Leftover from #8273

Fixes #8300
2022-09-15 22:53:48 +03:00
Ruslan Kuprieiev 477a6e177f index: use tree obj if using inline files
Leftover from #8273
2022-09-15 21:25:28 +03:00
skshetry f854fc6365 try to split testsuites into two (#8297)
try to split tests
2022-09-15 22:11:02 +05:45
skshetry 1b90683e02 dvcfs: add fsspec-compliance tests (#8296)
dvcfs: add tests
2022-09-15 18:42:50 +05:45
skshetry d762372d55 schema: add strict schema validation for top-level plots (#8289)
schema: add strict schema validation for plots
2022-09-15 16:30:10 +05:45
Peter Rowlands f3d304580a schema: update lockfile schema for versioning and dir files 2022-09-15 13:13:31 +03:00
Peter Rowlands 0e0d46e232 output: lazy load tree from files list in get_obj 2022-09-15 13:13:31 +03:00
Peter Rowlands 95ae018ed8 push/fetch: support dirs in worktree remotes 2022-09-15 13:13:31 +03:00
Peter Rowlands c29e721823 output: support loading/dumping dir trees from metadata 2022-09-15 13:13:31 +03:00
Peter Rowlands a9c7032d50 dvc-data: bump to 0.8.0 2022-09-15 13:13:31 +03:00
Peter Rowlands ecb767d622 index: add workspace/key generation helper 2022-09-15 13:13:31 +03:00
Saugat Pachhai (सौगात) fa45a9bbba dvcfs: default open to binary mode 2022-09-15 13:11:52 +03:00
daniele f50ae589ba deps: remove unused mock dep (#8290) 2022-09-15 07:34:21 +05:45
Daniele Trifirò 611c114f01 import-url: pass fs_config down from imp_url to get_cloud_fs 2022-09-14 22:26:19 +03:00
skshetry fdc18d5483 expose dvcfs in dvc.api and add to fsspec's registry (#8287)
* expose dvcfs in dvc.api and add to fsspec's registry

* readd __all__ to avoid exposing modules
2022-09-14 20:02:56 +05:45
David de la Iglesia Castro 3b532b7d59 exp run: Support hydra basic sweeper.
Requires `--queue` to be used alongside `--set-param`.

Doesn't strictly require `hydra.config.enabled`.

Add `get_hydra_sweeps` to `dvc/utils/hydra.py`.  Uses hydra `BasicSweeper` to generate the different sweeps.

`glob` syntax for sweeps is not supported in this P.R.

Closes #8083

---

Using setup from https://hydra.cc/docs/tutorials/basic/your_first_app/composition/ :

```console
$ dvc exp run -S "db=mysql,postgresql" -S "schema=warehouse,school" --queue
Queueing with '{'params.yaml': ['db=mysql', 'schema=warehouse']}'.
Queued experiment '5ab98b8' for future execution.
Queueing with '{'params.yaml': ['db=mysql', 'schema=school']}'.
Queued experiment '57c2fb6' for future execution.
Queueing with '{'params.yaml': ['db=postgresql', 'schema=warehouse']}'.
Queued experiment 'b9d6391' for future execution.
Queueing with '{'params.yaml': ['db=postgresql', 'schema=school']}'.
Queued experiment '145cd55' for future execution.
```
2022-09-13 13:43:30 +02:00
skshetry e78c4455de dvcfs: remove unused config code for opening a repo, change signature (#8277)
dvcfs: remove unused code for opening a repo
2022-09-13 09:26:33 +05:45
skshetry 8e5f4a8f35 data ls: new command to show metadata with outputs (#8252)
data ls: add new command to show metadata for the outputs
2022-09-09 17:36:24 +05:45
Saugat Pachhai (सौगात) cb3159e586 dvcfs: remove config 2022-09-09 13:16:27 +03:00
skshetry 7b301b09a1 data status: fix type hints (#8275) 2022-09-09 13:38:41 +05:45
skshetry 55b16166c8 add/import/import-url: update help message for labels/type/desc (#8271) 2022-09-08 23:09:10 +05:45
Ronan Lamy c70d0bd14d fs: cleanup and rename _get_fs_pair_2() to _get_subrepo_info() 2022-09-08 17:39:32 +03:00
Ronan Lamy b99a0cd6ca fs: _get_dvc_path() cannot be called with dvc_fs=None 2022-09-08 17:39:32 +03:00
Ronan Lamy d4170b2ec0 fs: refactor _get_fs_pair_2() 2022-09-08 17:39:32 +03:00
Ronan Lamy a3181bb509 fs: remove _get_fs_pair() 2022-09-08 17:39:32 +03:00
Ronan Lamy 4d6b90502b fs: improve performance of _DvcFileSystem._update()
Avoid back and forth conversions between key tuples and path strings.
2022-09-08 17:39:32 +03:00
Ronan Lamy 947427f4d9 fs: remove dead code 2022-09-08 17:39:32 +03:00
Ronan Lamy 0482a1a88b fs: simplify data flow
This uses the fact that repo.fs is always self.repo.fs and that
self.repo.fs always exists.
2022-09-08 17:39:32 +03:00
Ronan Lamy b470459987 fs: simplify return values of _get_fs_pair_2() 2022-09-08 17:39:32 +03:00
Ronan Lamy 81a010674e fs: avoid costly redundant dvcignore check when calling ._info() from .ls() 2022-09-08 17:39:32 +03:00
Ronan Lamy d946a1328e fs: Extract ._info() from .info()
This avoids unnecessarily recomputing dvc_parts when calling .info()
from .ls()
2022-09-08 17:39:32 +03:00
Ronan Lamy ad7d39da20 fs: chop up ._get_fs_pair() 2022-09-08 17:39:32 +03:00
Saugat Pachhai (सौगात) 8432de5980 metadata: add support for dvc.yaml file 2022-09-08 15:10:33 +03:00
Saugat Pachhai (सौगात) c73764156e metadata: support custom metadata in .dvc files
This adds support for custom metadata in outputs under `meta` key.
Also adds `--meta` flag to `add`/`import`/`import-url` that supports
`key=value` values.
2022-09-08 14:10:35 +03:00
skshetry df60df5242 add metadata fields: label, type to data (#8232)
gto like metadata fields: labels and type
2022-09-08 16:33:47 +05:45
dependabot[bot] 504485ed6e build(deps-dev): Bump pylint from 2.15.0 to 2.15.2 (#8268)
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.15.0 to 2.15.2.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.15.0...v2.15.2)

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

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-08 10:57:12 +05:45
dependabot[bot] 1e989bde2c build(deps): Bump scmrepo from 0.1.0 to 0.1.1 (#8269)
Bumps [scmrepo](https://github.com/iterative/scmrepo) from 0.1.0 to 0.1.1.
- [Release notes](https://github.com/iterative/scmrepo/releases)
- [Commits](https://github.com/iterative/scmrepo/compare/0.1.0...0.1.1)

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

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-08 10:56:53 +05:45
Ruslan Kuprieiev 7e2248bbe5 deps: bump dvc-data to 0.7.1 2022-09-07 23:07:35 +03:00
Daniele Trifirò a2d80b2403 silence flake8 errors (abstract class with no abstract method) 2022-09-07 21:51:33 +03:00
Daniele Trifirò 76e7c76ae5 data status: fix for scmrepo 0.1.0 2022-09-07 21:48:53 +03:00
Ruslan Kuprieiev 6cada6dc12 dvc: cloud versioning POC 2022-09-07 21:43:34 +03:00
Ruslan Kuprieiev 89d4461908 deps: temporarily limit dulwich to <0.20.46
Looks like the new release broke some data status logic.
2022-09-07 02:56:14 +03:00
Ruslan Kuprieiev a124d7e866 deps: bump dvc-data to 0.6.3 2022-09-07 02:23:29 +03:00
Ruslan Kuprieiev 628be2ae49 deps: bump dvc-data to 0.6.1 2022-09-06 20:30:20 +03:00
dependabot[bot] 7a71e06946 build(deps): Bump dvc-render from 0.0.9 to 0.0.10
Bumps [dvc-render](https://github.com/iterative/dvc-render) from 0.0.9 to 0.0.10.
- [Release notes](https://github.com/iterative/dvc-render/releases)
- [Commits](https://github.com/iterative/dvc-render/compare/0.0.9...0.0.10)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-06 17:12:36 +02:00
dependabot[bot] 91e54d8c27 build(deps): Bump dvc-data from 0.4.0 to 0.5.3 2022-09-06 17:17:21 +03:00
skshetry 071b2b8f81 add support for foreach target (#8210)
add general support for foreach target
2022-09-06 06:39:37 +05:45
skshetry 0b2b59a601 dvc.yaml: preserve outputs' desc on rewrites/updates to the stage (#8247)
preserve outputs' desc on rewrites/updates to the stage

We were not loading `desc` of the output, due to which we were not preserving the desc field over rewrites. Note that the rewrite only happens in select commands for `dvc.yaml`: `freeze`/`unfreeze`/`run`/`stage-add`, so we did not notice this before.
2022-09-05 18:49:39 +05:45
dependabot[bot] 65dc1cd08f build(deps-dev): Bump pytest from 7.1.2 to 7.1.3
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.1.2 to 7.1.3.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.1.2...7.1.3)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-03 02:04:01 +03:00
dependabot[bot] 1b103297b7 build(deps-dev): Bump dvc-azure from 2.20.0 to 2.20.2
Bumps [dvc-azure](https://github.com/iterative/dvc-azure) from 2.20.0 to 2.20.2.
- [Release notes](https://github.com/iterative/dvc-azure/releases)
- [Commits](https://github.com/iterative/dvc-azure/compare/2.20.0...2.20.2)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-09-03 02:03:46 +03:00
John Henning 887c73b3c6 adjusted formatting for dvc-hdfs 2022-09-02 23:04:59 +03:00
Peter Rowlands 660c17f654 output: save version_id meta 2022-09-01 19:36:53 +03:00
skshetry 49da2eef02 tests: set celery ping_task_timeout 3x the default (#8221)
tests: set celery ping_timeout 3x the default
2022-09-01 16:48:19 +05:45
Ruslan Kuprieiev aff65d42bb deps: bump dvc-azure and dvc-s3 to 2.20.0 2022-09-01 13:00:47 +03:00
skshetry 8968405770 data status: fix path for committed changes in Windows (#8220)
The path we get from GitFileSystem is posixpath, even on Windows. We need to
convert that posixpath to os.path here.
2022-09-01 10:02:08 +05:45
dependabot[bot] b6e8ba6758 build(deps-dev): Bump pylint from 2.14.5 to 2.15.0
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.14.5 to 2.15.0.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.14.5...v2.15.0)

---
updated-dependencies:
- dependency-name: pylint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...
2022-09-01 10:42:16 +08:00
Ruslan Kuprieiev e307a62dc0 deps: bump dvc-data to 0.4.0 2022-09-01 02:59:24 +03:00
Saugat Pachhai (सौगात) 639eeed43b skip tests that depend on hydra.compose in 3.11 2022-08-31 17:24:09 +02:00
David de la Iglesia Castro 91f12395f1 exp run: Support composing and dumping Hydra config.
The feature will be used depending on whether `config.hydra.enabled` is True or False.

Uses `hydra.initialize_config_dir` and `hydra.compose` (from https://hydra.cc/docs/advanced/compose_api/) to build the config and dump it to `params.yaml`. The content of the output file will be overwritten.

`config.hydra.config_dir` and `config.hydra.config_name` can be used to customize the values passed to `hydra.initialize_config_dir` and `hydra.compose`.

Can be combined with `--set-param` overrides.

Closes #8082
2022-08-31 17:24:09 +02:00
Ruslan Kuprieiev e7daeb1c3e deps: bump dvc-data to 0.3.0 2022-08-30 22:46:27 +03:00
Saugat Pachhai (सौगात) d3569035c8 output.get_obj: catch ObjectCorruptedError 2022-08-30 21:31:33 +03:00
David de la Iglesia Castro daee52e176 info: skip missing subprojects 2022-08-30 20:15:41 +03:00
skshetry a8415b01d0 data status: fix quoting on command hints for untracked files (#8211)
data status: fix quoting on command hints
2022-08-30 22:51:39 +05:45
Daniele Trifirò 77f56090ff do not checkout partials imports on fetch 2022-08-30 20:05:09 +03:00
Dave Berenbaum 1d3102f29b data status: update hints to include fetch and checkout (#8209) 2022-08-30 22:05:32 +05:45
skshetry a0cbcaa3c8 Delete CODEOWNERS 2022-08-30 13:12:57 +05:45
David de la Iglesia Castro ab780879d1 info: Include subprojects. 2022-08-30 09:22:39 +02:00
skshetry bfad82a2e3 data status: remove --withdirs, show unknowns in CLI (#8189)
* data status: remove --withdirs, show unknowns in CLI

* data status: only consider unknown from newer object during diff
2022-08-30 12:11:02 +05:45
skshetry e5eec59cb8 test on 3.11 (#8196)
We don't have aiohttp support yet, due to which we have to install
without cythonized extension.

Similarly, there has been some changes in how dataclasses disallows some
mutable arguments to be set as a class property, due to which
`hydra.conf` fails to import in 3.11.

See https://github.com/python/cpython/pull/29867. For now, I have
patched the module and avoid importing as a pytest-plugin.

dvc-hdfs/pyarrow is not there yet, we'll usually see a release 2-3
months after official reelase.

dvc-hdfs is skipped in 3.11 in extras_requires. `--set-params` will
return a nice error message if hydra cannot be imported in 3.11. I
decided to only patch that in tests.
2022-08-29 19:58:17 +05:45
Paweł Redzyński 2a4b83060e plots: support dirs in top level definitions
Fixes: #7914
2022-08-29 10:51:16 +02:00
David de la Iglesia Castro 9e2ebe9fcc resolve_rev: raise DvcException on empty repo. 2022-08-29 01:50:14 +03:00
David de la Iglesia Castro 2cef130191 repo: Handle no commits for exp show and plots diff.
Make it consistent with all the other commands that use `brancher` (i.e. `dvc diff`, `metrics diff`, `params diff`)

Closes #8163
2022-08-29 01:50:14 +03:00
David de la Iglesia Castro 17b9b1571e api: Add details forparams_show stages syntax.
Explain `stages` syntax for cases where the api is used from a directory different to the one the `dvc.yam` is located.
2022-08-29 01:49:38 +03:00
skshetry 37c8ef3406 Update dvc/commands/plots.py 2022-08-29 01:46:47 +03:00
b7aea414e6 plots templates: change ui to not dump to file
Update dvc/commands/plots.py

Co-authored-by: David de la Iglesia Castro <[email protected]>

Update tests/unit/command/test_plots.py

Co-authored-by: David de la Iglesia Castro <[email protected]>

Update dvc/commands/plots.py

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

fixed test
2022-08-29 01:46:47 +03:00
pre-commit-ci[bot] 126a64618b [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2022-08-29 01:43:43 +03:00
Alex Hall 8bdf9b1138 Better error message when using remove on file instead of dvc file 2022-08-29 01:43:43 +03:00
Ruslan Kuprieiev 9cb242c8a5 deps: bump dvc-http and dvc-gs 2022-08-28 23:13:18 +03:00
Ruslan Kuprieiev 11802426de pyinstaller: don't forget dvc_* plugins
Fixes #8194
2022-08-28 21:05:25 +03:00
dependabot[bot] b376044c45 build(deps): Bump dvc-data from 0.1.23 to 0.2.0
Bumps [dvc-data](https://github.com/iterative/dvc-data) from 0.1.23 to 0.2.0.
- [Release notes](https://github.com/iterative/dvc-data/releases)
- [Commits](https://github.com/iterative/dvc-data/compare/0.1.23...0.2.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-08-27 03:18:20 +03:00
David de la Iglesia Castro 7b3b4b373b api: Fix params_show for untracked targets.
If an untracked target is provided alongside `stages` or `deps`, don't load the parameters from the target and raise no params found instead.

Closes #8161
2022-08-26 12:00:45 +02:00
Peter Rowlands (변기호) 512bfc4305 tests: mark exp queue func tests xfail (#8186) 2022-08-25 21:04:05 +05:45
skshetry 54ee8c0d77 data status: make untracked files to be relative paths (#8184) 2022-08-25 19:29:43 +05:45
skshetry 063eb6904d data status: test cleanup and improvements (#8185) 2022-08-25 18:50:46 +05:45
Ruslan Kuprieiev 3e183d62ed deps: bump dvc-* plugins to 2.19.0
These are the same packages but with correct versions to sync up with existing packages that we generated in conda over the years.
2022-08-25 14:44:13 +03:00
David de la Iglesia Castro e815bbe211 metrics: show: Fix show from subdir.
This was a regression introduced in #7712.
Calling `repo.metrics.show` from a subdir stopped returning metrics because `fs.isfile(metric)` check returned false.
2022-08-25 13:02:22 +02:00
Saugat Pachhai (सौगात) 2374c72105 status: add unknown state 2022-08-25 13:23:18 +03:00
Saugat Pachhai (सौगात) 5cf1421c61 fix tests 2022-08-25 13:23:18 +03:00
skshetry a6a2feea62 Update setup.cfg 2022-08-25 13:23:18 +03:00
Saugat Pachhai (सौगात) 6c1d00c2d9 fix diff when no object exists by falling back to shallow_diff 2022-08-25 13:23:18 +03:00
Saugat Pachhai (सौगात) f72456f67c add shallow_diff 2022-08-25 13:23:18 +03:00
skshetry 2c556f58d3 data status: fix not-in-cache hint (#8181)
See https://github.com/iterative/dvc.org/pull/3812\#discussion_r936625617.
2022-08-25 15:39:59 +05:45
Ruslan Kuprieiev 460d08057c Create hook-fsspec.py 2022-08-24 23:39:46 +03:00
Ronan Lamy cc6deb24c8 fs: remove obsolete special cases 2022-08-23 23:20:42 +03:00
Ronan Lamy e681030c72 fs: simplify filtering out dvcfiles 2022-08-23 23:20:42 +03:00
Ronan Lamy 7a51548214 fs: Add fast path for the no subrepo case in _get_fs_pair() 2022-08-23 23:20:42 +03:00
Ronan Lamy 8e99fd0091 ls: Use fs.walk(..., detail=True) instead of separate call to fs.info() 2022-08-23 23:20:42 +03:00
Ronan Lamy 7e5a586f85 fs: Remove redundant .isdvc() check
fs.walk(..., dvc_only=True) already outputs dvc objects only, so
fs.isdvc(path, recursive=True) is always True.
2022-08-23 23:20:42 +03:00
Ronan Lamy c76a84b7b1 fs: remove one call to ._get_key() in _DvcFS._get_fs_pair() 2022-08-23 23:20:42 +03:00
Daniele Trifirò 26008f1559 import-url/update: add --no-download flag
add --no-download flag to dvc import-url/dvc update to only create/update .dvc
files without downloading the associated data.

Created .dvc files can be fetched using `dvc pull` or can be
updated using `dvc update --no-download`.
2022-08-23 23:07:01 +03:00
Daniele Trifirò 92d680ff94 tests: fix typo in test name 2022-08-23 23:07:01 +03:00
Daniele Trifirò c69c46d08c fix error message on fetch/pull/checkout failure 2022-08-23 23:07:01 +03:00
skshetry 6477ade557 build: skip install fpm and ruby on Windows (#8169) 2022-08-23 18:02:47 +05:45
404 changed files with 15623 additions and 10634 deletions
-10
View File
@@ -1,10 +0,0 @@
comment: false
coverage:
status:
project:
default:
target: 90
threshold: 10%
patch:
default:
threshold: 25%
-16
View File
@@ -1,16 +0,0 @@
[run]
branch = True
source = dvc
omit =
*__main__.py
[report]
exclude_lines =
if __name__ == .__main__.:
if typing.TYPE_CHECKING:
if TYPE_CHECKING:
pass
raise NotImplementedError
# pragma: no cover
# pragma: no cover
\.\.\.$
-1
View File
@@ -1,4 +1,3 @@
/tests
/dvc
/.github
/bin
+19
View File
@@ -0,0 +1,19 @@
[flake8]
ignore=
# 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/
P1
# use `!r` instead of surrounding by quotes
B028
max_line_length = 79
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
-1
View File
@@ -1 +0,0 @@
* @iterative/dvc
+1 -1
View File
@@ -8,7 +8,7 @@ about: Create a bug report to help us improve DVC
<!--
## Issue name
Issue names must follow the pattern `command: description` where the command is the dvc command that you are trying to run. The description should describe the consequence of the bug.
Issue names must follow the pattern `command: description` where the command is the dvc command that you are trying to run. The description should describe the consequence of the bug.
Example: `repro: doesn't detect input changes`
-->
@@ -2,4 +2,3 @@
name: "\U0001F680 Feature Request"
about: Suggest an idea for this project
---
+12
View File
@@ -0,0 +1,12 @@
codecov:
notify:
wait_for_ci: true
coverage:
status:
project:
default:
target: auto
threshold: 2%
patch: off
github_checks:
annotations: false
-9
View File
@@ -1,9 +0,0 @@
pull_request_rules:
- name: backport patches to 1.11
conditions:
- base=main
- label=backport-1.11
actions:
backport:
branches:
- "1.11"
-49
View File
@@ -1,49 +0,0 @@
# Config for https://github.com/apps/release-drafter
name-template: "$RESOLVED_VERSION 🦉 "
tag-template: "$RESOLVED_VERSION"
categories:
- title: "🚀 New Features and Enhancements "
labels:
- "feature"
- "enhancement"
- "ui"
- title: "💣 Breaking Changes "
labels:
- "breaking-change"
- title: "🏇 Optimizations "
labels:
- "optimize"
- "performance"
- title: "🐛 Bug Fixes "
labels:
- "bugfix"
- "bug"
- title: "🔨 Maintenance "
labels:
- "maintenance"
- "refactoring"
- "chore"
- "build"
- "ci"
change-template: "- $TITLE (#$NUMBER) @$AUTHOR"
version-resolver:
major:
labels:
- "major"
minor:
labels:
- "minor"
patch:
labels:
- "patch"
default: patch
exclude-labels:
- "skip-changelog"
template: |
> Refer to https://dvc.org/doc/install for installation instructions.
## Changes
$CHANGES
Thanks again to $CONTRIBUTORS for the contributions! 🎉
+16
View File
@@ -0,0 +1,16 @@
changelog:
exclude:
labels: ["skip-changelog"]
categories:
- title: 🛠 Breaking Changes
labels: ["breaking-change"]
- title: 🚀 New Features and Enhancements
labels: ["feature", "enhancement", "ui"]
- title: ⚡ Optimizations
labels: ["optimize", "performance"]
- title: 🐛 Bug Fixes
labels: ["bugfix", "bug"]
- title: 🔨 Maintenance
labels: ["maintenance", "refactoring", "chore", "build", "ci"]
- title: Other Changes
labels: ["*"]
+6 -11
View File
@@ -1,23 +1,18 @@
name: benchmarks
on: [pull_request, workflow_dispatch]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
bench:
name: run benchmarks
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-18.04]
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/[email protected]
with:
access_token: ${{ github.token }}
- uses: actions/setup-python@v4
with:
python-version: 3.8
python-version: "3.11"
- uses: iterative/dvc-bench@main
with:
pytest_options: "-k 'test_init or test_help'"
+39
View File
@@ -0,0 +1,39 @@
name: "CodeQL"
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
schedule:
- cron: '32 19 * * 2'
workflow_dispatch:
permissions:
contents: read
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ['python']
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
+31 -12
View File
@@ -7,8 +7,13 @@ on:
types: [released, prereleased]
workflow_dispatch:
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:
@@ -24,26 +29,30 @@ jobs:
with:
fetch-depth: 0
- name: Set up Python 3.9
- name: Set up Python 3.10
uses: actions/setup-python@v4
with:
python-version: 3.9
python-version: "3.10"
cache: pip
cache-dependency-path: |
setup.cfg
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 -U wheel pip setuptools
pip install --upgrade pip wheel setuptools
pip install .[all]
pip install -r scripts/build-requirements.txt
gem install --no-document fpm
- name: Pull images
run: dvc pull
@@ -73,21 +82,31 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: 3.8
cache: pip
cache-dependency-path: setup.cfg
- name: Install
run: pip install --upgrade pip wheel
- name: Force version for Test PyPI uploads
if: ${{ !startsWith(github.ref, 'refs/tags') }}
run: |
pip install -U wheel pip build setuptools
pip install ".[all,tests]"
pip install setuptools_scm
echo version=$(python -m setuptools_scm | awk -F+ '{print $1}' | tail -1) >> $GITHUB_ENV
- name: Build packages
run: |
pip install -U pip
./scripts/build_package.sh
run: ./scripts/build_package.sh
env:
SETUPTOOLS_SCM_PRETEND_VERSION: ${{ env.version }}
- 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
+54
View File
@@ -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
+29 -52
View File
@@ -1,40 +1,21 @@
name: Tests
on:
pull_request: {}
push: {}
push:
branches: [main]
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:
contents: read
jobs:
lint:
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/[email protected]
with:
access_token: ${{ github.token }}
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/setup-python@v4
with:
python-version: 3.8
- name: Install requirements
run: |
pip install --upgrade pip setuptools wheel
pip install collective.checkdocs==0.2
pip install ".[dev]" pre-commit
- name: Check README
run: python setup.py checkdocs
- uses: pre-commit/[email protected]
tests:
timeout-minutes: 45
runs-on: ${{ matrix.os }}
@@ -42,7 +23,8 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-20.04, windows-latest, macos-latest]
pyv: ["3.8", "3.9", "3.10"]
pyv: ["3.8", "3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v3
with:
@@ -51,39 +33,34 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.pyv }}
- name: get pip cache dir
id: pip-cache-dir
run: |
echo "::set-output name=dir::$(pip cache dir)"
- name: set pip cache
id: pip-cache
uses: actions/cache@v3
with:
path: ${{ steps.pip-cache-dir.outputs.dir }}
key: ${{ runner.os }}-pip-${{ hashFiles('setup.cfg') }}
restore-keys: |
${{ runner.os }}-pip-
cache: pip
cache-dependency-path: pyproject.toml
- name: install
if: steps.cache.pip-cache-dir.cache-hit != 'true'
run: |
pip install --upgrade pip setuptools wheel
pip install --upgrade pip wheel
pip install -e ".[dev]"
- uses: pre-commit/[email protected]
- name: run tests
timeout-minutes: 40
run: >-
python -m tests -n=auto
--cov-report=xml --cov-report=term
${{ env.extra_test_args }}
timeout-minutes: 30
run: >
pytest -nauto --timeout=300 --durations=100
--cov --cov-report=xml --cov-report=term
- name: upload coverage report
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
fail_ci_if_error: false
check:
if: always()
needs: [tests]
runs-on: ubuntu-latest
steps:
- uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}
notify:
if: github.ref == 'refs/heads/main' && failure()
needs:
- lint
- tests
needs: [tests]
runs-on: ubuntu-latest
steps:
- name: Slack Notification
+51 -29
View File
@@ -1,39 +1,66 @@
default_language_version:
python: python3
ci:
skip: [mypy, pylint, dvc-pre-commit]
repos:
- hooks:
- id: black
language_version: python3
repo: https://github.com/ambv/black
rev: 22.6.0
- 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
- id: check-executables-have-shebangs
- id: check-json
- id: check-merge-conflict
exclude: "tests/func/utils/test_strict_yaml.py"
args: ['--assume-in-merge']
- id: check-toml
- id: check-yaml
- hooks:
- id: codespell
args:
- --ignore-words-list
- ba,datas,fo,uptodate,cachable
repo: https://github.com/codespell-project/codespell
rev: v2.1.0
- hooks:
- id: debug-statements
exclude: "dvc/_debug.py"
- id: end-of-file-fixer
- id: mixed-line-ending
- id: sort-simple-yaml
- id: trailing-whitespace
- repo: https://github.com/lovesegfault/beautysh
rev: v6.2.1
hooks:
- id: beautysh
args: ["-i", "2"]
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
hooks:
- id: pyupgrade
args:
- --py38-plus
- repo: https://github.com/psf/black
rev: 22.12.0
hooks:
- id: black
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
language_version: python3
repo: https://github.com/timothycrosley/isort
rev: 5.10.1
- hooks:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: 'v0.0.238'
hooks:
- id: ruff
- repo: https://github.com/codespell-project/codespell
rev: v2.2.2
hooks:
- id: codespell
additional_dependencies: ["tomli"]
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
hooks:
- id: flake8
language_version: python3
additional_dependencies:
- flake8-bugbear
- flake8-comprehensions
- flake8-debugger
- flake8-string-format
repo: https://gitlab.com/pycqa/flake8
rev: 3.9.2
- flake8-typing-imports
- repo: local
hooks:
- id: mypy
@@ -42,19 +69,14 @@ repos:
files: ^dvc/
language: system
types: [python]
require_serial: true
- id: pylint
name: pylint
entry: pylint
files: ^dvc/
language: system
types: [python]
- hooks:
- args:
- -i
- "2"
id: beautysh
language_version: python3
repo: https://github.com/lovesegfault/beautysh
rev: v6.2.1
require_serial: true
- hooks:
- args:
- git-hook
-1
View File
@@ -74,4 +74,3 @@ available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.ht
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
-3
View File
@@ -1,3 +0,0 @@
include LICENSE
recursive-include requirements *.txt
recursive-include dvc/repo/plots/templates *.json
+7 -3
View File
@@ -13,7 +13,7 @@
|CI| |Python Version| |Coverage| |VS Code| |DOI|
|PyPI| |Packages| |Brew| |Conda| |Choco| |Snap|
|PyPI| |PyPI Downloads| |Packages| |Brew| |Conda| |Choco| |Snap|
|
@@ -82,7 +82,7 @@ To share and back up the *data cache*, DVC supports multiple remote storage plat
|Flowchart|
`DVC pipelines <https://dvc.org/doc/start/data-management/pipelines>`_ (computational graphs) connect code and data together.
`DVC pipelines <https://dvc.org/doc/start/data-management/data-pipelines>`_ (computational graphs) connect code and data together.
They specify all steps required to produce a model: input dependencies including code, data, commands to run; and output information to be saved.
Last but not least, `DVC Experiment Versioning <https://dvc.org/doc/start/experiments>`_ lets you prepare and run a large number of experiments.
@@ -276,6 +276,10 @@ Barrak, A., Eghan, E.E. and Adams, B. `On the Co-evolution of ML Pipelines and S
:target: https://pypi.org/project/dvc
:alt: PyPI
.. |PyPI Downloads| image:: https://img.shields.io/pypi/dm/dvc.svg?color=blue&label=Downloads&logo=pypi&logoColor=gold
:target: https://pypi.org/project/dvc
:alt: PyPI Downloads
.. |Packages| image:: https://img.shields.io/github/v/release/iterative/dvc?label=deb|pkg|rpm|exe&logo=GitHub
:target: https://github.com/iterative/dvc/releases/latest
:alt: deb|pkg|rpm|exe
@@ -292,6 +296,6 @@ Barrak, A., Eghan, E.E. and Adams, B. `On the Co-evolution of ML Pipelines and S
:target: https://github.com/iterative/dvc/graphs/contributors
:alt: Contributors
.. |VS Code| image:: https://vsmarketplacebadge.apphb.com/version/Iterative.dvc.svg
.. |VS Code| image:: https://img.shields.io/visual-studio-marketplace/v/Iterative.dvc?color=blue&label=VSCode&logo=visualstudiocode&logoColor=blue
:target: https://marketplace.visualstudio.com/items?itemName=Iterative.dvc
:alt: VS Code Extension
-3
View File
@@ -1,3 +0,0 @@
#!/bin/bash
PYTHONPATH=$DVC_HOME python -m dvc $@
-3
View File
@@ -1,3 +0,0 @@
@echo OFF
set PYTHONPATH=%DVC_HOME%
python -m dvc %*
+1 -1
View File
@@ -4,6 +4,6 @@ DVC
Make your data science projects reproducible and shareable.
"""
import dvc.logger
from dvc.version import __version__ # noqa: F401
from dvc.version import __version__, version_tuple # noqa: F401
dvc.logger.setup()
+107 -27
View File
@@ -1,24 +1,28 @@
from contextlib import ExitStack, contextmanager
from datetime import datetime
from typing import TYPE_CHECKING, Callable, Union
from typing import TYPE_CHECKING, Callable, Optional, Union
if TYPE_CHECKING:
from argparse import Namespace
from types import FrameType
@contextmanager
def viztracer_profile(
path: Union[Callable[[], str], str],
max_stack_depth: int = -1,
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( # noqa: T201
"Failed to run profiler, viztracer is not installed"
)
yield
return
tracer = viztracer.VizTracer(max_stack_depth=max_stack_depth)
tracer = viztracer.VizTracer(max_stack_depth=depth, log_async=log_async)
tracer.start()
yield
@@ -29,12 +33,14 @@ def viztracer_profile(
@contextmanager
def yappi_profile(
path: Union[Callable[[], str], str] = None, wall_clock: bool = True
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
@@ -44,14 +50,26 @@ def yappi_profile(
yield
yappi.stop()
# pylint:disable=no-member
if path:
stats = yappi.get_func_stats()
fpath = path() if callable(path) else path
stats.save(fpath, "callgrind")
threads = yappi.get_thread_stats()
stats = {}
if separate_threads:
for thread in threads:
ctx_id = thread.id
stats[ctx_id] = yappi.get_func_stats(ctx_id=ctx_id)
else:
yappi.get_func_stats().print_all()
yappi.get_thread_stats().print_all()
stats[None] = yappi.get_func_stats()
fpath = path() if callable(path) else path
for ctx_id, st in stats.items():
if fpath:
out = f"{fpath}-{ctx_id}" if ctx_id is not None else fpath
st.save(out, type="callgrind")
else:
if ctx_id is not None:
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
yappi.clear_stats()
@@ -62,7 +80,9 @@ 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( # noqa: T201
"Failed to run profiler, pyinstrument is not installed"
)
yield
return
@@ -75,11 +95,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
@@ -103,14 +123,49 @@ 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
lines = "\u2015" * get_terminal_size().columns
stack = format_stack(frame)
print(lines, "\n", *stack, lines, sep="", file=sys.stderr) # noqa: T201
@contextmanager
def debugtools(args: "Namespace" = None, **kwargs):
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)
try:
signal.signal(signal.SIGINFO, _sigshow) # only available on macOS
except AttributeError:
pass
yield
def _get_path_func(tool: str, ext: str):
fmt = f"{tool}.dvc-{{now:%Y%m%d}}_{{now:%H%M%S}}.{ext}"
def func(now: Optional["datetime"] = None) -> str:
return fmt.format(now=now or datetime.now())
return func
@contextmanager
def debugtools(args: Optional["Namespace"] = None, **kwargs):
kw = vars(args) if args else {}
kw.update(kwargs)
@@ -121,19 +176,28 @@ def debugtools(args: "Namespace" = None, **kwargs):
stack.enter_context(profile(kw.get("cprofile_dump")))
if kw.get("instrument") or kw.get("instrument_open"):
stack.enter_context(instrument(kw.get("instrument_open", False)))
if kw.get("show_stack", False):
stack.enter_context(show_stack())
if kw.get("yappi"):
output = "callgrind.dvc-{0:%Y%m%d}_{0:%H%M%S}.out"
path_func = _get_path_func("callgrind", "out")
stack.enter_context(
yappi_profile(path=lambda: output.format(datetime.now()))
)
if kw.get("viztracer") or kw.get("viztracer_depth"):
output = "viztracer.dvc-{0:%Y%m%d}_{0:%H%M%S}.json"
stack.enter_context(
viztracer_profile(
path=lambda: output.format(datetime.now()),
max_stack_depth=kw.get("viztracer_depth") or -1,
yappi_profile(
path=path_func,
separate_threads=kw.get("yappi_separate_threads"),
)
)
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
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
@@ -146,10 +210,19 @@ def add_debugging_flags(parser):
parser.add_argument(
"--yappi", action="store_true", default=False, help=SUPPRESS
)
parser.add_argument(
"--yappi-separate-threads",
action="store_true",
default=False,
help=SUPPRESS,
)
parser.add_argument(
"--viztracer", action="store_true", default=False, help=SUPPRESS
)
parser.add_argument("--viztracer-depth", type=int, help=SUPPRESS)
parser.add_argument(
"--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
@@ -160,3 +233,10 @@ def add_debugging_flags(parser):
parser.add_argument(
"--instrument-open", action="store_true", default=False, help=SUPPRESS
)
parser.add_argument(
"--show-stack",
"--ss",
action="store_true",
default=False,
help=SUPPRESS,
)
+12 -46
View File
@@ -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,15 +101,24 @@ def _runtime_info():
"""
Gather information from the environment where DVC runs to fill a report.
"""
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,
}
@@ -123,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",
@@ -146,45 +154,3 @@ def _system_info():
# We don't collect data for any other system.
raise NotImplementedError
def _find_or_create_user_id():
"""
The user's ID is stored on a file under the global config directory.
The file should contain a JSON with a "user_id" key:
{"user_id": "16fd2706-8baf-433b-82eb-8c7fada847da"}
IDs are generated randomly with UUID.
"""
import uuid
from dvc.config import Config
from dvc.lock import Lock, LockError
from dvc.utils.fs import makedirs
config_dir = Config.get_dir("global")
fname = os.path.join(config_dir, "user_id")
lockfile = os.path.join(config_dir, "user_id.lock")
# Since the `fname` and `lockfile` are under the global config,
# we need to make sure such directory exist already.
makedirs(config_dir, exist_ok=True)
try:
with Lock(lockfile):
try:
with open(fname, encoding="utf-8") as fobj:
user_id = json.load(fobj)["user_id"]
except (FileNotFoundError, ValueError, KeyError):
user_id = str(uuid.uuid4())
with open(fname, "w", encoding="utf-8") as fobj:
json.dump({"user_id": user_id}, fobj)
return user_id
except LockError:
logger.debug("Failed to acquire '%s'", lockfile)
+35
View File
@@ -0,0 +1,35 @@
from dataclasses import asdict, dataclass, field, fields
from typing import Any, ClassVar, Dict, List, Optional
from funcy import compact
@dataclass
class Annotation:
PARAM_DESC: ClassVar[str] = "desc"
PARAM_TYPE: ClassVar[str] = "type"
PARAM_LABELS: ClassVar[str] = "labels"
PARAM_META: ClassVar[str] = "meta"
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 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))
ANNOTATION_FIELDS = [field.name for field in fields(Annotation)]
ANNOTATION_SCHEMA = {
Annotation.PARAM_DESC: str,
Annotation.PARAM_TYPE: str,
Annotation.PARAM_LABELS: [str],
Annotation.PARAM_META: object,
}
+12 -6
View File
@@ -1,9 +1,15 @@
from .data import ( # noqa, pylint: disable=redefined-builtin
get_url,
open,
read,
)
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
__all__ = ["get_url", "make_checkpoint", "open", "params_show", "read"]
__all__ = [
"get_url",
"make_checkpoint",
"open",
"params_show",
"read",
"DVCFileSystem",
]
+6 -2
View File
@@ -27,7 +27,7 @@ def get_url(path, repo=None, rev=None, remote=None):
if not dvc_info:
raise OutputNotFoundError(path, repo)
dvc_repo = info["repo"]
dvc_repo = info["repo"] # pylint: disable=unsubscriptable-object
md5 = dvc_info["md5"]
return dvc_repo.cloud.get_url_for(remote, checksum=md5)
@@ -38,7 +38,11 @@ class _OpenContextManager(GCM):
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(
+8 -3
View File
@@ -1,3 +1,4 @@
import typing
from collections import Counter
from typing import Dict, Iterable, Optional, Union
@@ -37,6 +38,10 @@ def params_show(
stages to retrieve parameters from.
Defaults to `None`.
If `None`, all parameters from all stages will be retrieved.
If this method is called from a different location to the one where
the `dvc.yaml` is found, the relative path to the `dvc.yaml` must
be provided as a prefix with the syntax `{relpath}:{stage}`.
For example: `subdir/dvc.yaml:stage-0` or `../dvc.yaml:stage-1`.
rev (str, optional): Name of the `Git revision`_ to retrieve parameters
from.
Defaults to `None`.
@@ -234,15 +239,15 @@ def params_show(
if isinstance(stages, str):
stages = [stages]
def _onerror_raise(result: Dict, exception: Exception, *args, **kwargs):
def _onerror_raise(_result: Dict, exception: Exception, *args, **kwargs):
raise exception
def _postprocess(params):
processed = {}
processed: Dict[str, Dict] = {}
for rev, rev_data in params.items():
processed[rev] = {}
counts = Counter()
counts: typing.Counter[str] = Counter()
for file_data in rev_data["data"].values():
for k in file_data["data"]:
counts[k] += 1
+34 -12
View File
@@ -45,7 +45,6 @@ def _log_unknown_exceptions() -> None:
if logger.isEnabledFor(logging.DEBUG):
logger.debug("Version info for developers:\n%s", get_dvc_info())
logger.info(FOOTER)
return None
def _log_exceptions(exc: Exception) -> Optional[int]:
@@ -58,8 +57,8 @@ 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")),
"%s to see how to handle this problem",
error_link("many-files"),
extra={"tb_only": True},
)
else:
@@ -92,8 +91,11 @@ 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 +104,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,14 +118,17 @@ 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
from dvc_data.build import IgnoreInCollectedDirError
from dvc_data.hashfile.build import IgnoreInCollectedDirError
if isinstance(exc, IgnoreInCollectedDirError):
logger.exception("")
@@ -168,12 +175,27 @@ 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 __version__
from dvc.utils.pkg import PKG
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
+24
View File
@@ -0,0 +1,24 @@
from argparse import _AppendAction
class CommaSeparatedArgs(_AppendAction): # pylint: disable=protected-access
def __call__(
self, parser, namespace, values, option_string=None # noqa: ARG002
):
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 # noqa: ARG002
):
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
View File
@@ -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 -16
View File
@@ -2,8 +2,8 @@
import argparse
import logging
import os
import sys
from dvc import __version__
from dvc.commands import (
add,
cache,
@@ -29,6 +29,7 @@ from dvc.commands import (
init,
install,
ls,
ls_url,
machine,
metrics,
move,
@@ -75,6 +76,7 @@ COMMANDS = [
install,
root,
ls,
ls_url,
freeze,
dag,
daemon,
@@ -126,17 +128,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.
@@ -184,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.",
)
+5 -2
View File
@@ -21,10 +21,13 @@ def append_doc_link(help_message, path):
)
def fix_plumbing_subparsers(subparsers):
# metavar needs to be explicitly set in order to hide plumbing subcommands
def hide_subparsers_from_help(subparsers):
# metavar needs to be explicitly set in order to hide subcommands
# from the 'positional arguments' choices list
# see: https://bugs.python.org/issue22848
# 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
]
+36 -9
View File
@@ -2,12 +2,43 @@ 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 run(self):
from dvc.exceptions import (
@@ -28,6 +59,9 @@ class CmdAdd(CmdBase):
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,
@@ -109,15 +143,8 @@ def add_parser(subparsers, parent_parser):
),
metavar="<number>",
)
parser.add_argument(
"--desc",
type=str,
metavar="<text>",
help=(
"User description of the data (optional). "
"This doesn't affect any DVC operations."
),
)
_add_annotating_args(parser)
parser.add_argument(
"targets", nargs="+", help="Input files/directories to add."
).complete = completion.FILE
+1 -1
View File
@@ -42,7 +42,7 @@ 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)
+2 -2
View File
@@ -103,7 +103,7 @@ def add_parser(subparsers, parent_parser):
"--all",
action="store_true",
default=False,
help="Include the target paths which don’t match any pattern "
help="Include the target paths which don't match any pattern "
"in the `--details` list.",
)
parser.add_argument(
@@ -111,7 +111,7 @@ def add_parser(subparsers, parent_parser):
"--non-matching",
action="store_true",
default=False,
help="Include the target paths which don’t match any pattern "
help="Include the target paths which don't match any pattern "
"in the `--details` list.",
)
parser.add_argument(
+1 -1
View File
@@ -110,7 +110,7 @@ def add_parser(subparsers, parent_parser):
nargs="*",
help=(
"Limit command scope to these tracked files/directories, "
".dvc files, or stage names."
".dvc files and stage names."
),
).complete = completion.DVC_FILE
checkout_parser.set_defaults(func=CmdCheckout)
+5 -5
View File
@@ -25,9 +25,7 @@ class CmdCommit(CmdBase):
)
except DvcException:
logger.exception(
"failed to commit{}".format(
(" " + target) if target else ""
)
"failed to commit%s", (" " + target) if target else ""
)
return 1
return 0
@@ -70,7 +68,9 @@ def add_parser(subparsers, parent_parser):
commit_parser.add_argument(
"targets",
nargs="*",
help="stages or .dvc files to commit. Optional. "
"(Finds all DVC files in the workspace by default.)",
help=(
"Limit command scope to these tracked files/directories, "
".dvc files and stage names."
),
).complete = completion.DVCFILES_AND_STAGE
commit_parser.set_defaults(func=CmdCommit)
+6 -7
View File
@@ -37,13 +37,12 @@ class CmdConfig(CmdBaseNoRepo):
self.config = Config(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()
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -204,7 +204,7 @@ def add_parser(subparsers, parent_parser):
dag_parser.add_argument(
"target",
nargs="?",
help="Stage or output to show pipeline for (optional). "
help="Stage name or output to show pipeline for. "
"Finds all stages in the workspace by default.",
)
dag_parser.set_defaults(func=CmdDAG)
+141 -23
View File
@@ -1,11 +1,18 @@
import argparse
import logging
from typing import TYPE_CHECKING
from operator import itemgetter
from typing import TYPE_CHECKING, Any, Dict, Iterable, Set
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
from dvc.cli.utils import (
append_doc_link,
fix_subparsers,
hide_subparsers_from_help,
)
from dvc.ui import ui
from dvc.utils import colorize
@@ -31,15 +38,22 @@ class CmdDataStatus(CmdBase):
"unchanged": "DVC unchanged files",
}
HINTS = {
"not_in_cache": 'use "dvc pull <file>..." '
"to update your local storage",
"committed": "git commit the corresponding dvc files "
"to update the repo",
"uncommitted": 'use "dvc commit <file>..." to track changes',
"untracked": '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',
"not_in_cache": ('use "dvc fetch <file>..." to download files',),
"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',
),
"git_dirty": (
"there are {}changes not tracked by dvc, "
'use "git status" to see',
),
}
@staticmethod
@@ -58,7 +72,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:
@@ -76,8 +90,9 @@ class CmdDataStatus(CmdBase):
color = cls.COLORS.get(stage, None)
ui.write(header)
if hint := cls.HINTS.get(stage):
ui.write(f" ({hint})")
if hints := cls.HINTS.get(stage):
for hint in hints:
ui.write(f" ({hint})")
if isinstance(stage_status, dict):
items = [
@@ -92,17 +107,19 @@ class CmdDataStatus(CmdBase):
out = "\n".join(tabs + item for item in chunk)
ui.write(colorize(out, color))
if (hint := cls.HINTS.get("git_dirty")) and git_info.get("is_dirty"):
message = hint.format("other " if result else "")
ui.write(f"[blue]({message})[/]", styled=True)
if (hints := cls.HINTS.get("git_dirty")) and git_info.get("is_dirty"):
for hint in hints:
message = hint.format("other " if result else "")
ui.write(f"[blue]({message})[/]", styled=True)
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,
with_dirs=self.args.with_dirs,
)
if not self.args.unchanged:
@@ -116,6 +133,65 @@ 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",
@@ -172,10 +248,52 @@ def add_parser(subparsers, parent_parser):
nargs="?",
help="Show untracked files.",
)
data_status_parser.add_argument(
"--with-dirs",
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=argparse.SUPPRESS,
help="Show tabulated output in the Markdown format (GFM).",
)
data_status_parser.set_defaults(func=CmdDataStatus)
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)
+6 -5
View File
@@ -43,7 +43,8 @@ class CmdDataPull(CmdDataBase):
)
self.log_summary(stats)
except (CheckoutError, DvcException) as exc:
self.log_summary(getattr(exc, "stats", {}))
if stats := getattr(exc, "stats", {}):
self.log_summary(stats)
logger.exception("failed to pull data from the cloud")
return 1
@@ -119,9 +120,9 @@ def shared_parent_parser():
nargs="*",
help=(
"Limit command scope to these tracked files/directories, "
".dvc files, or stage names."
".dvc files and stage names."
),
).complete = completion.DVC_FILE
).complete = completion.DVC_FILE # type: ignore[attr-defined]
return parent_parser
@@ -194,7 +195,7 @@ def add_parser(subparsers, _parent_parser):
"--glob",
action="store_true",
default=False,
help="Pull cache for targets matching shell-style wildcards.",
help=argparse.SUPPRESS,
)
pull_parser.set_defaults(func=CmdDataPull)
@@ -301,7 +302,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",
+1 -1
View File
@@ -109,7 +109,7 @@ class CmdDiff(CmdBase):
ui.write()
if not sum(summary.values()):
return None
return
states_summary = ", ".join(
f"{summary[state]} {state}"
+4 -2
View File
@@ -2,8 +2,8 @@ import argparse
from dvc.cli.utils import (
append_doc_link,
fix_plumbing_subparsers,
fix_subparsers,
hide_subparsers_from_help,
)
from dvc.commands.experiments import (
apply,
@@ -18,6 +18,7 @@ from dvc.commands.experiments import (
queue_worker,
remove,
run,
save,
show,
)
@@ -34,6 +35,7 @@ SUB_COMMANDS = [
queue_worker,
remove,
run,
save,
show,
]
@@ -59,7 +61,7 @@ def add_parser(subparsers, parent_parser):
fix_subparsers(experiments_subparsers)
for cmd in SUB_COMMANDS:
cmd.add_parser(experiments_subparsers, parent_parser)
fix_plumbing_subparsers(experiments_subparsers)
hide_subparsers_from_help(experiments_subparsers)
def add_rev_selection_flags(
+1 -1
View File
@@ -27,7 +27,7 @@ class CmdExperimentsDiff(CmdBase):
if self.args.json:
ui.write_json(diff)
else:
elif diff:
from dvc.compare import show_diff
precision = self.args.precision or DEFAULT_PRECISION
+4 -8
View File
@@ -3,6 +3,7 @@ import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.ui import ui
logger = logging.getLogger(__name__)
@@ -16,18 +17,13 @@ class CmdExperimentsList(CmdBase):
num=self.args.num,
git_remote=self.args.git_remote,
)
for baseline in exps:
tag = self.repo.scm.describe(baseline)
if not tag:
branch = self.repo.scm.describe(baseline, base="refs/heads")
if branch:
tag = branch.split("/")[-1]
name = tag if tag else baseline[:7]
if not name_only:
print(f"{name}:")
ui.write(f"{baseline}:")
for exp_name in exps[baseline]:
indent = "" if name_only else "\t"
print(f"{indent}{exp_name}")
ui.write(f"{indent}{exp_name}")
return 0
+1 -1
View File
@@ -68,7 +68,7 @@ def add_parser(experiments_subparsers, parent_parser):
"-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",
+79
View File
@@ -0,0 +1,79 @@
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,
)
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 a dvc 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",
"--show-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.set_defaults(func=CmdExperimentsSave)
+32 -19
View File
@@ -4,7 +4,7 @@ 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, Any, Dict, Set
from funcy import lmap
@@ -37,9 +37,9 @@ def _update_names(names, items):
def _collect_names(all_experiments, **kwargs):
metric_names = defaultdict(dict)
param_names = defaultdict(dict)
deps_names = set()
metric_names: Dict[str, Dict[str, Any]] = defaultdict(dict)
param_names: Dict[str, Dict[str, Any]] = defaultdict(dict)
deps_names: Set[str] = set()
for _, experiments in all_experiments.items():
for exp_data in experiments.values():
@@ -62,7 +62,7 @@ experiment_types = {
}
def _collect_rows(
def _collect_rows( # noqa: C901
base_rev,
experiments,
all_headers,
@@ -77,7 +77,7 @@ def _collect_rows(
fill_value=FILL_VALUE,
iso=False,
):
from scmrepo.git import Git
from dvc.scm import Git
if sort_by:
sort_path, sort_name, sort_type = _sort_column(
@@ -95,11 +95,8 @@ def _collect_rows(
exp = results.get("data", {})
if exp.get("running"):
state = "Running"
elif exp.get("queued"):
state = "Queued"
else:
state = exp.get("status")
if state == "Success":
state = fill_value
is_baseline = rev == "baseline"
@@ -317,7 +314,7 @@ def baseline_styler(typ):
return {"style": "bold"} if typ == "baseline" else {}
def show_experiments(
def show_experiments( # noqa: C901
all_experiments,
keep=None,
drop=None,
@@ -331,7 +328,7 @@ def show_experiments(
metric_names, param_names, deps_names = _collect_names(all_experiments)
headers = [
headers_list = [
"Experiment",
"rev",
"typ",
@@ -343,13 +340,13 @@ def show_experiments(
names = {**metric_names, **param_names}
counter = Counter(flatten_list([list(a.keys()) for a in names.values()]))
counter.update(headers)
counter.update(headers_list)
metric_headers = _normalize_headers(metric_names, counter)
param_headers = _normalize_headers(param_names, counter)
td = experiments_table(
all_experiments,
headers,
headers_list,
metric_headers,
metric_names,
param_headers,
@@ -362,7 +359,7 @@ def show_experiments(
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)
@@ -413,13 +410,15 @@ def show_experiments(
cols_to_drop = set()
if drop is not None:
cols_to_drop = {col for col in td.keys() if re.match(drop, col)}
cols_to_drop = {
col for col in td.keys() if re.match(drop, col) # noqa: SIM118
}
if pcp:
cols_to_drop.add("Created")
td.drop(*cols_to_drop)
if pcp:
subset = {x for x in td.keys() if x != "Experiment"}
subset = {x for x in td.keys() if x != "Experiment"} # noqa: SIM118
td.dropna(
"rows",
how="all",
@@ -476,6 +475,8 @@ class CmdExperimentsShow(CmdBase):
all_branches=self.args.all_branches,
all_tags=self.args.all_tags,
all_commits=self.args.all_commits,
hide_queued=self.args.hide_queued,
hide_failed=self.args.hide_failed,
revs=self.args.rev,
num=self.args.num,
sha_only=self.args.sha,
@@ -495,7 +496,7 @@ class CmdExperimentsShow(CmdBase):
else DEFAULT_PRECISION
)
fill_value = "" if self.args.csv else FILL_VALUE
iso = True if self.args.csv else False
iso = self.args.csv
show_experiments(
all_experiments,
@@ -594,6 +595,18 @@ def add_parser(experiments_subparsers, parent_parser):
default=False,
help="Always show git commit SHAs instead of branch/tag names.",
)
experiments_show_parser.add_argument(
"--hide-failed",
action="store_true",
default=False,
help="Hide failed experiments in the table.",
)
experiments_show_parser.add_argument(
"--hide-queued",
action="store_true",
default=False,
help="Hide queued experiments in the table.",
)
experiments_show_parser.add_argument(
"--json",
"--show-json",
+1 -1
View File
@@ -10,7 +10,7 @@ logger = logging.getLogger(__name__)
class CmdGC(CmdBase):
def run(self):
def run(self): # noqa: C901
from dvc.repo.gc import _validate_args
_validate_args(
+6 -5
View File
@@ -3,9 +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
from dvc.scm import CloneError
logger = logging.getLogger(__name__)
@@ -44,11 +44,12 @@ class CmdGet(CmdBaseNoRepo):
jobs=self.args.jobs,
)
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
+1 -2
View File
@@ -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__)
+5 -5
View File
@@ -64,17 +64,17 @@ 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)
our.merge(ancestor, their, allowed=["add", "remove", "change"])
return 0
finally:
+24 -13
View File
@@ -5,6 +5,7 @@ from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
from dvc.scm import CloneError
logger = logging.getLogger(__name__)
@@ -19,20 +20,29 @@ class CmdImport(CmdBase):
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."
@@ -69,20 +79,19 @@ def add_parser(subparsers, parent_parser):
help="Specify name of the .dvc file this command will generate.",
metavar="<filename>",
)
import_parser.add_argument(
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 downloading it.",
help="Only create .dvc file without actually importing target data.",
)
import_parser.add_argument(
"--desc",
type=str,
metavar="<text>",
help=(
"User description of the data (optional). "
"This doesn't affect any DVC operations."
),
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",
@@ -94,4 +103,6 @@ def add_parser(subparsers, parent_parser):
),
metavar="<number>",
)
_add_annotating_args(import_parser)
import_parser.set_defaults(func=CmdImport)
+30 -15
View File
@@ -17,23 +17,29 @@ class CmdImportUrl(CmdBase):
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,
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."
)
@@ -66,12 +72,6 @@ def add_parser(subparsers, parent_parser):
help="Specify name of the .dvc file this command will generate.",
metavar="<filename>",
).complete = completion.DIR
import_parser.add_argument(
"--no-exec",
action="store_true",
default=False,
help="Only create .dvc file without actually downloading it.",
)
import_parser.add_argument(
"--to-remote",
action="store_true",
@@ -84,6 +84,20 @@ 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",
@@ -95,12 +109,13 @@ def add_parser(subparsers, parent_parser):
metavar="<number>",
)
import_parser.add_argument(
"--desc",
type=str,
metavar="<text>",
"--version-aware",
action="store_true",
default=False,
help=(
"User description of the data (optional). "
"This doesn't affect any DVC operations."
"Import using cloud versioning. Implied if the URL contains a "
"version ID."
),
)
_add_annotating_args(import_parser)
import_parser.set_defaults(func=CmdImportUrl)
+1 -1
View File
@@ -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:
+43
View File
@@ -0,0 +1,43 @@
import argparse
import logging
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.ui import ui
from .ls import _prettify
logger = logging.getLogger(__name__)
class CmdListUrl(CmdBaseNoRepo):
def run(self):
from dvc.repo import Repo
entries = Repo.ls_url(self.args.url, recursive=self.args.recursive)
if entries:
entries = _prettify(entries, with_color=True)
ui.write("\n".join(entries))
return 0
def add_parser(subparsers, parent_parser):
LS_HELP = "List directory contents from URL."
lsurl_parser = subparsers.add_parser(
"list-url",
aliases=["ls-url"],
parents=[parent_parser],
description=append_doc_link(LS_HELP, "list-url"),
help=LS_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
lsurl_parser.add_argument(
"url", help="See `dvc import-url -h` for full list of supported URLs."
)
lsurl_parser.add_argument(
"-R",
"--recursive",
action="store_true",
help="Recursively list files.",
)
lsurl_parser.set_defaults(func=CmdListUrl)
+4 -3
View File
@@ -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
@@ -177,6 +177,7 @@ class CmdMachineRename(CmdBase):
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 +200,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
@@ -324,7 +325,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)
+22 -46
View File
@@ -25,7 +25,7 @@ def _show_json(renderers, split=False):
def _adjust_vega_renderers(renderers):
from dvc.render import VERSION_FIELD
from dvc.render import REVISION_FIELD, VERSION_FIELD
from dvc_render import VegaRenderer
for r in renderers:
@@ -42,6 +42,7 @@ def _adjust_vega_renderers(renderers):
dp["rev"] = "::".join(vi.values())
else:
for dp in r.datapoints:
dp[REVISION_FIELD] = dp[VERSION_FIELD]["revision"]
dp.pop(VERSION_FIELD, {})
@@ -66,18 +67,6 @@ def _data_versions_count(renderer):
return len(set(x))
def _filter_unhandled_renderers(renderers):
# filtering out renderers currently unhandled by vscode extension
from dvc_render import VegaRenderer
def _is_json_viable(r):
return not (
isinstance(r, VegaRenderer) and _data_versions_count(r) > 1
)
return list(filter(_is_json_viable, renderers))
class CmdPlots(CmdBase):
def _func(self, *args, **kwargs):
raise NotImplementedError
@@ -90,10 +79,8 @@ class CmdPlots(CmdBase):
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
return {self.args.from_config}
def _html_template_path(self):
html_template_path = self.args.html_template
@@ -102,12 +89,13 @@ class CmdPlots(CmdBase):
"html_template", None
)
if html_template_path and not os.path.isabs(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
from pathlib import Path
from dvc.render.match import match_defs_renderers
@@ -156,12 +144,10 @@ class CmdPlots(CmdBase):
templates_dir=self.repo.plots.templates_dir,
)
if self.args.json:
renderers = _filter_unhandled_renderers(renderers)
_show_json(renderers, self.args.split)
return 0
_adjust_vega_renderers(renderers)
if self.args.show_vega:
renderer = first(filter(lambda r: r.TYPE == "vega", renderers))
if renderer:
@@ -223,31 +209,21 @@ class CmdPlotsModify(CmdPlots):
class CmdPlotsTemplates(CmdBase):
TEMPLATES_CHOICES = [
"simple",
"linear",
"confusion",
"confusion_normalized",
"scatter",
"smooth",
"bar_horizontal_sorted",
"bar_horizontal",
]
def run(self):
from dvc_render.vega_templates import dump_templates
from dvc.exceptions import InvalidArgumentError
from dvc_render.vega_templates import TEMPLATES
try:
out = (
os.path.join(os.getcwd(), self.args.out)
if self.args.out
else self.repo.plots.templates_dir
)
target = self.args.template
if target:
for template in TEMPLATES:
if target == template.DEFAULT_NAME:
ui.write_json(template.DEFAULT_CONTENT)
return 0
raise InvalidArgumentError(f"Unexpected template: {target}.")
targets = [self.args.target] if self.args.target else None
dump_templates(output=out, targets=targets)
templates_path = os.path.relpath(out, os.getcwd())
ui.write(f"Templates have been written into '{templates_path}'.")
for template in TEMPLATES:
ui.write(template.DEFAULT_NAME)
return 0
except DvcException:
@@ -357,8 +333,7 @@ def add_parser(subparsers, parent_parser):
plots_modify_parser.set_defaults(func=CmdPlotsModify)
TEMPLATES_HELP = (
"Write built-in plots templates to a directory "
"(.dvc/plots by default)."
"List built-in plots templates or show JSON specification for one."
)
plots_templates_parser = plots_subparsers.add_parser(
"templates",
@@ -368,13 +343,14 @@ def add_parser(subparsers, parent_parser):
formatter_class=argparse.RawDescriptionHelpFormatter,
)
plots_templates_parser.add_argument(
"target",
"template",
default=None,
nargs="?",
choices=CmdPlotsTemplates.TEMPLATES_CHOICES,
help="Template to write. Writes all templates by default.",
help=(
"Template for which to show JSON specification. "
"List all template names by default."
),
)
_add_output_argument(plots_templates_parser, typ="templates")
plots_templates_parser.set_defaults(func=CmdPlotsTemplates)
+1 -1
View File
@@ -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)
+14 -2
View File
@@ -11,13 +11,18 @@ 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 +30,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="*",
+7 -6
View File
@@ -1,33 +1,34 @@
import argparse
import logging
from typing import List, Mapping, Optional
from typing import Dict, List, Optional
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands.experiments.show import format_time
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):
def run(self) -> int:
result: List[
Mapping[str, Optional[str]]
Dict[str, Optional[str]]
] = 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"],
]
+1 -1
View File
@@ -164,7 +164,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)
+10 -1
View File
@@ -55,7 +55,16 @@ def add_arguments(repro_parser):
repro_parser.add_argument(
"targets",
nargs="*",
help="Stages to reproduce. 'dvc.yaml' by default.",
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",
+3 -23
View File
@@ -67,7 +67,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)
)
@@ -76,7 +76,7 @@ class CmdStageList(CmdBase):
# removing duplicates while maintaining order
collected = chain.from_iterable(
self.repo.stage.collect(
target=target, recursive=self.args.recursive, accept_group=True
target=target, recursive=self.args.recursive
)
for target in self.args.targets
)
@@ -95,7 +95,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
@@ -238,26 +238,6 @@ def _add_common_args(parser):
help="Declare output plot file (do not put into DVC cache).",
metavar="<path>",
)
parser.add_argument(
"--live", help="Declare output as dvclive.", metavar="<path>"
)
parser.add_argument(
"--live-no-cache",
help="Declare output as dvclive (do not put into DVC cache).",
metavar="<path>",
)
parser.add_argument(
"--live-no-summary",
action="store_true",
default=False,
help=argparse.SUPPRESS,
)
parser.add_argument(
"--live-no-html",
action="store_true",
default=False,
help="Signal dvclive logger to not produce training report.",
)
parser.add_argument(
"-w",
"--wdir",
+10 -1
View File
@@ -18,6 +18,7 @@ class CmdUpdate(CmdBase):
rev=self.args.rev,
recursive=self.args.recursive,
to_remote=self.args.to_remote,
no_download=self.args.no_download,
remote=self.args.remote,
jobs=self.args.jobs,
)
@@ -30,7 +31,8 @@ class CmdUpdate(CmdBase):
def add_parser(subparsers, parent_parser):
UPDATE_HELP = (
"Update data artifact imported (via dvc import or dvc import-url) "
"from an external DVC repository or URL."
"from an external DVC repository or URL, or update data from a "
"DVC worktree remote."
)
update_parser = subparsers.add_parser(
"update",
@@ -55,6 +57,13 @@ def add_parser(subparsers, parent_parser):
default=False,
help="Update all stages in the specified directory.",
)
update_parser.add_argument(
"--no-download",
action="store_true",
default=False,
help="Update .dvc file git revision/hash value(s)"
" but do not download the file(s).",
)
update_parser.add_argument(
"--to-remote",
action="store_true",
+25 -24
View File
@@ -58,7 +58,7 @@ 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]:
@@ -167,7 +167,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
return buff.getvalue()
def to_parallel_coordinates(
self, output_path: "StrPath", color_by: str = None
self, output_path: "StrPath", color_by: Optional[str] = None
) -> "StrPath":
from dvc_render.html import render_html
from dvc_render.plotly import ParallelCoordinatesRenderer
@@ -195,7 +195,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 +209,14 @@ 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))
]
def dropna(
def dropna( # noqa: C901
self,
axis: str = "rows",
how="any",
@@ -228,7 +229,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
)
if how not in ["any", "all"]:
raise ValueError(
f"Invalid 'how' value {how}." "Choose one of ['any', 'all']"
f"Invalid 'how' value {how}. Choose one of ['any', 'all']"
)
match_line: Set = set()
@@ -244,8 +245,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,7 +256,7 @@ 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
@@ -267,7 +267,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
else:
self.drop(*to_drop)
def drop_duplicates(
def drop_duplicates( # noqa: C901
self,
axis: str = "rows",
subset: Optional[Iterable[str]] = None,
@@ -309,7 +309,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 +324,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 +346,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"
@@ -393,12 +394,12 @@ def show_diff(
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 +421,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 +461,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(
+28 -10
View File
@@ -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__)
@@ -84,7 +91,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
@@ -116,7 +127,7 @@ class Config(dict):
return site_config_dir(cls.APPNAME, cls.APPAUTHOR)
@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 +150,12 @@ 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:
@@ -169,14 +182,19 @@ class Config(dict):
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)
if fs.exists(filename):
with fs.open(filename) as fobj:
conf_obj = ConfigObj(fobj)
try:
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()))
@@ -275,7 +293,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
@@ -321,7 +339,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)
+12
View File
@@ -78,7 +78,9 @@ REMOTE_COMMON = {
"url": str,
"checksum_jobs": All(Coerce(int), Range(1)),
"jobs": All(Coerce(int), Range(1)),
Optional("worktree"): Bool,
Optional("no_traverse"): Bool, # obsoleted
Optional("version_aware"): Bool,
}
LOCAL_COMMON = {
"type": supported_cache_type,
@@ -95,6 +97,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 = {
@@ -102,6 +106,7 @@ WEBDAV_COMMON = {
"password": str,
"ask_password": Bool,
"token": str,
"custom_auth_header": str,
"cert_path": str,
"key_path": str,
"timeout": Coerce(int),
@@ -178,6 +183,8 @@ SCHEMA = {
"user": str,
"password": str,
"ask_password": Bool,
"passphrase": str,
"ask_passphrase": Bool,
"keyfile": str,
"timeout": Coerce(int),
"gss_auth": Bool,
@@ -289,4 +296,9 @@ SCHEMA = {
"bool": All(Lower, Choices("store_true", "boolean_optional")),
"list": All(Lower, Choices("nargs", "append")),
},
"hydra": {
Optional("enabled", default=False): Bool,
"config_dir": str,
"config_name": str,
},
}
+21 -16
View File
@@ -25,21 +25,24 @@ def _popen(cmd, **kwargs):
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 (
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(
cmd, env=env, creationflags=creationflags, startupinfo=startupinfo
)
def _spawn_posix(cmd, env):
@@ -49,17 +52,19 @@ def _spawn_posix(cmd, env):
# 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:
@@ -100,7 +105,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]):
+2 -3
View File
@@ -59,8 +59,7 @@ class AsciiCanvas:
def draw(self):
"""Draws ASCII canvas on the screen."""
lines = map("".join, self.canvas)
joined_lines = os.linesep.join(lines)
return joined_lines
return os.linesep.join(lines)
def point(self, x, y, char):
"""Create a point on ASCII canvas.
@@ -81,7 +80,7 @@ class AsciiCanvas:
self.canvas[y][x] = char
def line(self, x0, y0, x1, y1, char):
def line(self, x0, y0, x1, y1, char): # noqa: C901
"""Create a line on ASCII canvas.
Args:
+63 -29
View File
@@ -3,16 +3,40 @@
import logging
from typing import TYPE_CHECKING, Iterable, Optional
from dvc_data.db import get_index
from dvc.config import NoRemoteError, RemoteConfigError
from dvc.utils.objects import cached_property
from dvc_data.hashfile.db import get_index
if TYPE_CHECKING:
from dvc.fs import FileSystem
from dvc_data.hashfile.db import HashFileDB
from dvc_data.hashfile.hash_info import HashInfo
from dvc_data.status import CompareStatusResult
from dvc_data.hashfile.status import CompareStatusResult
from dvc_data.hashfile.transfer import TransferResult
logger = logging.getLogger(__name__)
class Remote:
def __init__(self, name: str, path: str, fs: "FileSystem", **config):
self.path = path
self.fs = fs
self.name = name
self.worktree: bool = config.pop("worktree", False)
self.config = config
@cached_property
def odb(self) -> "HashFileDB":
from dvc_data.hashfile.db import get_odb
path = self.path
if self.worktree:
path = self.fs.path.join(path, ".dvc", "cache")
return get_odb(self.fs, path, **self.config)
class DataCloud:
"""Class that manages dvc remotes.
@@ -27,18 +51,31 @@ class DataCloud:
def __init__(self, repo):
self.repo = repo
def get_remote_odb(
def get_remote(
self,
name: Optional[str] = None,
command: str = "<command>",
) -> "HashFileDB":
from dvc.config import NoRemoteError
) -> "Remote":
if not name:
name = self.repo.config["core"].get("remote")
if name:
return self._init_odb(name)
from dvc.fs import get_cloud_fs
cls, config, fs_path = get_cloud_fs(self.repo, name=name)
if config.get("worktree"):
version_aware = config.get("version_aware")
if version_aware is False:
raise RemoteConfigError(
"worktree remotes require version_aware cloud"
)
if version_aware is None:
config["version_aware"] = True
fs = cls(**config)
config["tmp_dir"] = self.repo.index_db_dir
return Remote(name, fs_path, fs, **config)
if bool(self.repo.config["remote"]):
error_msg = (
@@ -55,13 +92,17 @@ class DataCloud:
raise NoRemoteError(error_msg)
def _init_odb(self, name):
from dvc.fs import get_cloud_fs
from dvc_data.db import get_odb
cls, config, fs_path = get_cloud_fs(self.repo, name=name)
config["tmp_dir"] = self.repo.index_db_dir
return get_odb(cls(**config), fs_path, **config)
def get_remote_odb(
self,
name: Optional[str] = None,
command: str = "<command>",
) -> "HashFileDB":
remote = self.get_remote(name=name, command=command)
if remote.fs.version_aware or remote.worktree:
raise NoRemoteError(
f"'{command}' is unsupported for cloud versioned remotes"
)
return remote.odb
def _log_missing(self, status: "CompareStatusResult"):
if status.missing:
@@ -71,7 +112,8 @@ class DataCloud:
)
logger.warning(
"Some of the cache files do not exist neither locally "
f"nor on remote. Missing cache files:\n{missing_desc}"
"nor on remote. Missing cache files:\n%s",
missing_desc,
)
def transfer(
@@ -80,14 +122,10 @@ class DataCloud:
dest_odb: "HashFileDB",
objs: Iterable["HashInfo"],
**kwargs,
):
from dvc.exceptions import FileTransferError
from dvc_data.transfer import TransferError, transfer
) -> "TransferResult":
from dvc_data.hashfile.transfer import transfer
try:
return transfer(src_odb, dest_odb, objs, **kwargs)
except TransferError as exc:
raise FileTransferError(exc.fails) from exc
return transfer(src_odb, dest_odb, objs, **kwargs)
def push(
self,
@@ -95,7 +133,7 @@ class DataCloud:
jobs: Optional[int] = None,
remote: Optional[str] = None,
odb: Optional["HashFileDB"] = None,
):
) -> "TransferResult":
"""Push data items in a cloud-agnostic way.
Args:
@@ -122,7 +160,7 @@ class DataCloud:
jobs: Optional[int] = None,
remote: Optional[str] = None,
odb: Optional["HashFileDB"] = None,
):
) -> "TransferResult":
"""Pull data items in a cloud-agnostic way.
Args:
@@ -150,7 +188,6 @@ class DataCloud:
jobs: Optional[int] = None,
remote: Optional[str] = None,
odb: Optional["HashFileDB"] = None,
log_missing: bool = True,
):
"""Check status of data items in a cloud-agnostic way.
@@ -161,10 +198,8 @@ class DataCloud:
cache to. By default remote from core.remote config option
is used.
odb: optional ODB to check status from. Overrides remote.
log_missing: log warning messages if file doesn't exist
neither in cache, neither in cloud.
"""
from dvc_data.status import compare_status
from dvc_data.hashfile.status import compare_status
if not odb:
odb = self.get_remote_odb(remote, "status")
@@ -173,7 +208,6 @@ class DataCloud:
odb,
objs,
jobs=jobs,
log_missing=log_missing,
dest_index=get_index(odb),
cache_odb=self.repo.odb.local,
)
+20 -10
View File
@@ -1,7 +1,7 @@
from collections import defaultdict
from typing import Any, Mapping
from typing import Any, Mapping, Set
from dvc.output import ARTIFACT_SCHEMA, Output
from dvc.output import ARTIFACT_SCHEMA, DIR_FILES_SCHEMA, Output
from .base import Dependency
from .param import ParamsDependency
@@ -15,10 +15,11 @@ SCHEMA: Mapping[str, Any] = {
**ARTIFACT_SCHEMA,
**RepoDependency.REPO_SCHEMA,
**ParamsDependency.PARAM_SCHEMA,
Output.PARAM_FILES: [DIR_FILES_SCHEMA],
}
def _get(stage, p, info):
def _get(stage, p, info, **kwargs):
if info and info.get(RepoDependency.PARAM_REPO):
repo = info.pop(RepoDependency.PARAM_REPO)
return RepoDependency(repo, stage, p, info)
@@ -27,21 +28,30 @@ def _get(stage, p, info):
params = info.pop(ParamsDependency.PARAM_PARAMS)
return ParamsDependency(stage, p, params)
return Dependency(stage, p, info)
return Dependency(stage, p, info, **kwargs)
def loadd_from(stage, d_list):
ret = []
for d in d_list:
p = d.pop(Output.PARAM_PATH, None)
ret.append(_get(stage, p, d))
files = d.pop(Output.PARAM_FILES, None)
ret.append(_get(stage, p, d, files=files))
return ret
def loads_from(stage, s_list, erepo=None):
def loads_from(stage, s_list, erepo=None, fs_config=None):
assert isinstance(s_list, list)
info = {RepoDependency.PARAM_REPO: erepo} if erepo else {}
return [_get(stage, s, info.copy()) for s in s_list]
return [
_get(
stage,
s,
info.copy(),
fs_config=fs_config,
)
for s in s_list
]
def _merge_params(s_list):
@@ -49,7 +59,7 @@ def _merge_params(s_list):
default_file = ParamsDependency.DEFAULT_PARAMS_FILE
# figure out completely tracked params file, and ignore specific keys
wholly_tracked = set()
wholly_tracked: Set[str] = set()
for key in s_list:
if not isinstance(key, dict):
continue
@@ -64,7 +74,7 @@ def _merge_params(s_list):
if not isinstance(key, dict):
msg = "Only list of str/dict is supported. Got: "
msg += f"'{type(key).__name__}'."
raise ValueError(msg)
raise ValueError(msg) # noqa: TRY004
for k, params in key.items():
if k in wholly_tracked:
@@ -73,7 +83,7 @@ def _merge_params(s_list):
if not isinstance(params, list):
msg = "Expected list of params for custom params file "
msg += f"'{k}', got '{type(params).__name__}'."
raise ValueError(msg)
raise ValueError(msg) # noqa: TRY004
d[k].extend(params)
return d
+40 -7
View File
@@ -1,6 +1,7 @@
from typing import Type
from typing import Dict, Type
from dvc.exceptions import DvcException
from dvc.fs import download as fs_download
from dvc.output import Output
@@ -24,11 +25,43 @@ class DependencyIsStageFileError(DvcException):
class Dependency(Output):
IS_DEPENDENCY = True
DoesNotExistError = DependencyDoesNotExistError # type: Type[DvcException]
IsNotFileOrDirError = (
DependencyIsNotFileOrDirError
) # type: Type[DvcException]
IsStageFileError = DependencyIsStageFileError # type: Type[DvcException]
DoesNotExistError: Type[DvcException] = DependencyDoesNotExistError
IsNotFileOrDirError: Type[DvcException] = DependencyIsNotFileOrDirError
IsStageFileError: Type[DvcException] = DependencyIsStageFileError
def workspace_status(self) -> Dict[str, str]:
if self.fs.version_aware:
old_fs_path = self.fs_path
try:
self.fs_path = self.fs.path.version_path(self.fs_path, None)
if self.changed_meta():
return {str(self): "update available"}
finally:
self.fs_path = old_fs_path
return super().workspace_status()
def update(self, rev=None):
pass
if self.fs.version_aware:
self.fs_path = self.fs.path.version_path(self.fs_path, rev)
self.meta = self.get_meta()
self.def_path = self.fs.path.version_path(
self.def_path, self.meta.version_id
)
self.fs_path = self.fs.path.version_path(
self.fs_path, self.meta.version_id
)
def download(self, to, jobs=None):
fs_download(self.fs, self.fs_path, to, jobs=jobs)
def save(self):
super().save()
if self.fs.version_aware:
self.fs_path = self.fs.path.version_path(
self.fs_path, self.meta.version_id
)
def dumpd(self, **kwargs):
if self.fs.version_aware:
kwargs["with_files"] = True
return super().dumpd(**kwargs)
+23 -12
View File
@@ -2,7 +2,7 @@ import logging
import os
import typing
from collections import defaultdict
from typing import Dict
from typing import Dict, cast
import dpath.util
from voluptuous import Any
@@ -39,14 +39,18 @@ class ParamsDependency(Dependency):
def __init__(self, stage, path, params=None, repo=None):
self.params = list(params) if params else []
info = (
{self.PARAM_PARAMS: params} if isinstance(params, dict) else None
)
hash_info = HashInfo()
if isinstance(params, dict):
hash_info = HashInfo(
self.PARAM_PARAMS,
params, # type: ignore[arg-type]
)
repo = repo or stage.repo
path = path or os.path.join(repo.root_dir, self.DEFAULT_PARAMS_FILE)
super().__init__(stage, path, info=info, repo=repo)
super().__init__(stage, path, repo=repo)
self.hash_info = hash_info
def dumpd(self):
def dumpd(self, **kwargs):
ret = super().dumpd()
if not self.hash_info:
ret[self.PARAM_PARAMS] = self.params or {}
@@ -63,7 +67,10 @@ class ParamsDependency(Dependency):
for param in self.params:
if param in values:
info[param] = values[param]
self.hash_info = HashInfo(self.PARAM_PARAMS, info)
self.hash_info = HashInfo(
self.PARAM_PARAMS,
info, # type: ignore[arg-type]
)
def read_params(
self, flatten: bool = True, **kwargs: typing.Any
@@ -103,8 +110,8 @@ class ParamsDependency(Dependency):
from funcy import ldistinct
status = defaultdict(dict)
info = self.hash_info.value if self.hash_info else {}
status: Dict[str, Any] = defaultdict(dict)
info = cast(dict, self.hash_info.value) if self.hash_info else {}
actual = self.read_params()
# NOTE: we want to preserve the order of params as specified in the
@@ -118,9 +125,13 @@ class ParamsDependency(Dependency):
elif param not in info:
st = "new"
elif actual[param] != info[param]:
if (
isinstance(actual[param], tuple)
and list(actual[param]) == info[param]
):
continue
st = "modified"
else:
assert actual[param] == info[param]
continue
status[str(self)][param] = st
@@ -158,13 +169,13 @@ class ParamsDependency(Dependency):
)
)
return HashInfo(self.PARAM_PARAMS, info)
return HashInfo(self.PARAM_PARAMS, info) # type: ignore[arg-type]
def save(self):
if not self.exists:
raise self.DoesNotExistError(self)
if not self.isfile and not self.isdir:
if not self.isfile() and not self.isdir():
raise self.IsNotFileOrDirError(self)
self.ignore()
+53 -25
View File
@@ -1,7 +1,7 @@
import os
from collections import defaultdict
from copy import copy
from typing import TYPE_CHECKING, Dict, Optional, Set, Tuple
from typing import TYPE_CHECKING, Dict, Optional, Set, Tuple, Union
from voluptuous import Required
@@ -10,8 +10,14 @@ from dvc.prompt import confirm
from .base import Dependency
if TYPE_CHECKING:
from dvc_data.hashfile.file import HashFile
from typing import ContextManager
from dvc.output import Output
from dvc.repo import Repo
from dvc.stage import Stage
from dvc_data.hashfile.hash_info import HashInfo
from dvc_data.hashfile.meta import Meta
from dvc_data.hashfile.obj import HashFile
from dvc_objects.db import ObjectDB
@@ -29,12 +35,15 @@ class RepoDependency(Dependency):
}
}
def __init__(self, def_repo, stage, *args, **kwargs):
def __init__(
self, def_repo: Dict[str, str], stage: "Stage", *args, **kwargs
):
self.def_repo = def_repo
self._objs: Dict[str, "HashFile"] = {}
self._meta: Dict[str, "Meta"] = {}
super().__init__(stage, *args, **kwargs)
def _parse_path(self, fs, fs_path):
def _parse_path(self, fs, fs_path): # noqa: ARG002
return None
@property
@@ -59,11 +68,11 @@ class RepoDependency(Dependency):
def save(self):
pass
def dumpd(self):
def dumpd(self, **kwargs) -> Dict[str, Union[str, Dict[str, str]]]:
return {self.PARAM_PATH: self.def_path, self.PARAM_REPO: self.def_repo}
def download(self, to, jobs=None):
from dvc_data.checkout import checkout
def download(self, to: "Output", jobs: Optional[int] = None):
from dvc_data.hashfile.checkout import checkout
for odb, objs in self.get_used_objs().items():
self.repo.cloud.pull(objs, jobs=jobs, odb=odb)
@@ -79,13 +88,13 @@ class RepoDependency(Dependency):
prompt=confirm,
)
def update(self, rev=None):
def update(self, rev: Optional[str] = None):
if rev:
self.def_repo[self.PARAM_REV] = rev
with self._make_repo(locked=False) as repo:
self.def_repo[self.PARAM_REV_LOCK] = repo.get_rev()
def changed_checksum(self):
def changed_checksum(self) -> bool:
# From current repo point of view what describes RepoDependency is its
# origin project url and rev_lock, and it makes RepoDependency
# immutable, hence its impossible for checksum to change.
@@ -94,17 +103,19 @@ class RepoDependency(Dependency):
def get_used_objs(
self, **kwargs
) -> Dict[Optional["ObjectDB"], Set["HashInfo"]]:
used, _ = self._get_used_and_obj(**kwargs)
used, _, _ = self._get_used_and_obj(**kwargs)
return used
def _get_used_and_obj(
self, obj_only=False, **kwargs
) -> Tuple[Dict[Optional["ObjectDB"], Set["HashInfo"]], "HashFile"]:
self, obj_only: bool = False, **kwargs
) -> Tuple[
Dict[Optional["ObjectDB"], Set["HashInfo"]], "Meta", "HashFile"
]:
from dvc.config import NoRemoteError
from dvc.exceptions import NoOutputOrStageError, PathMissingError
from dvc.utils import as_posix
from dvc_data.build import build
from dvc_data.objects.tree import Tree, TreeError
from dvc_data.hashfile.build import build
from dvc_data.hashfile.tree import Tree, TreeError
local_odb = self.repo.odb.local
locked = kwargs.pop("locked", True)
@@ -128,10 +139,10 @@ class RepoDependency(Dependency):
self._check_circular_import(odb, obj_ids)
used_obj_ids[odb].update(obj_ids)
except (NoRemoteError, NoOutputOrStageError):
pass
pass # noqa: S110
try:
object_store, _, obj = build(
object_store, meta, obj = build(
local_odb,
as_posix(self.def_path),
repo.dvcfs,
@@ -145,16 +156,20 @@ class RepoDependency(Dependency):
object_store.read_only = True
self._objs[rev] = obj
self._meta[rev] = meta
used_obj_ids[object_store].add(obj.hash_info)
if isinstance(obj, Tree):
used_obj_ids[object_store].update(oid for _, _, oid in obj)
return used_obj_ids, obj
return used_obj_ids, meta, obj
def _check_circular_import(self, odb, obj_ids):
def _check_circular_import(
self, odb: "ObjectDB", obj_ids: Set["HashInfo"]
) -> None:
from dvc.exceptions import CircularImportError
from dvc.fs.dvc import DvcFileSystem
from dvc_data.db.reference import ReferenceHashFileDB
from dvc_data.objects.tree import Tree
from dvc.fs.dvc import DVCFileSystem
from dvc_data.hashfile.db.reference import ReferenceHashFileDB
from dvc_data.hashfile.tree import Tree
if not isinstance(odb, ReferenceHashFileDB):
return
@@ -165,11 +180,12 @@ class RepoDependency(Dependency):
tree = Tree.load(odb, hash_info)
yield from (odb.get(hi.value) for _, _, hi in tree)
else:
assert hash_info.value
yield odb.get(hash_info.value)
checked_urls = set()
for obj in iter_objs():
if not isinstance(obj.fs, DvcFileSystem):
if not isinstance(obj.fs, DVCFileSystem):
continue
if (
obj.fs.repo_url in checked_urls
@@ -190,19 +206,31 @@ class RepoDependency(Dependency):
rev = self._get_rev(locked=locked)
if rev in self._objs:
return self._objs[rev]
_, obj = self._get_used_and_obj(
_, _, obj = self._get_used_and_obj(
obj_only=True, filter_info=filter_info, **kwargs
)
return obj
def _make_repo(self, locked=True, **kwargs):
def get_meta(self, filter_info=None, **kwargs):
locked = kwargs.get("locked", True)
rev = self._get_rev(locked=locked)
if rev in self._meta:
return self._meta[rev]
_, meta, _ = self._get_used_and_obj(
obj_only=True, filter_info=filter_info, **kwargs
)
return meta
def _make_repo(
self, locked: bool = True, **kwargs
) -> "ContextManager[Repo]":
from dvc.external_repo import external_repo
d = self.def_repo
rev = self._get_rev(locked=locked)
return external_repo(d[self.PARAM_URL], rev=rev, **kwargs)
def _get_rev(self, locked=True):
def _get_rev(self, locked: bool = True):
d = self.def_repo
return (d.get(self.PARAM_REV_LOCK) if locked else None) or d.get(
self.PARAM_REV
+91 -47
View File
@@ -1,7 +1,17 @@
import contextlib
import logging
import os
from typing import TYPE_CHECKING, Any, Callable, Tuple, TypeVar, Union
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
List,
Optional,
Tuple,
TypeVar,
Union,
)
from dvc.exceptions import DvcException
from dvc.parsing.versions import LOCKFILE_VERSION, SCHEMA_KWD
@@ -11,21 +21,25 @@ from dvc.stage.exceptions import (
StageFileDoesNotExistError,
StageFileIsNotDvcFileError,
)
from dvc.types import AnyPath
from dvc.utils import relpath
from dvc.utils.collections import apply_diff
from dvc.utils.objects import cached_property
from dvc.utils.serialize import dump_yaml, modify_yaml
if TYPE_CHECKING:
from dvc.repo import Repo
from dvc.types import StrOrBytesPath
from .parsing import DataResolver
from .stage import Stage
logger = logging.getLogger(__name__)
_T = TypeVar("_T")
DVC_FILE = "Dvcfile"
DVC_FILE_SUFFIX = ".dvc"
PIPELINE_FILE = "dvc.yaml"
PIPELINE_LOCK = "dvc.lock"
PROJECT_FILE = "dvc.yaml"
LOCK_FILE = "dvc.lock"
class FileIsGitIgnored(DvcException):
@@ -44,7 +58,7 @@ class ParametrizedDumpError(DvcException):
def is_valid_filename(path):
return path.endswith(DVC_FILE_SUFFIX) or os.path.basename(path) in [
DVC_FILE,
PIPELINE_FILE,
PROJECT_FILE,
]
@@ -55,7 +69,7 @@ def is_dvc_file(path):
def is_lock_file(path):
return os.path.basename(path) == PIPELINE_LOCK
return os.path.basename(path) == LOCK_FILE
def is_git_ignored(repo, path):
@@ -75,7 +89,7 @@ def check_dvcfile_path(repo, path):
raise StageFileBadNameError(
"bad DVC file name '{}'. DVC files should be named "
"'{}' or have a '.dvc' suffix (e.g. '{}.dvc').".format(
relpath(path), PIPELINE_FILE, os.path.basename(path)
relpath(path), PROJECT_FILE, os.path.basename(path)
)
)
@@ -150,7 +164,7 @@ class FileMixin:
return self._load_yaml(**kwargs)
@classmethod
def validate(cls, d: _T, fname: str = None) -> _T:
def validate(cls, d: _T, fname: Optional[str] = None) -> _T:
from dvc.utils.strictyaml import validate
return validate(d, cls.SCHEMA, path=fname) # type: ignore[arg-type]
@@ -165,14 +179,15 @@ class FileMixin:
**kwargs,
)
def remove(self, force=False): # pylint: disable=unused-argument
# pylint: disable-next=unused-argument
def remove(self, force=False): # noqa: ARG002
with contextlib.suppress(FileNotFoundError):
os.unlink(self.path)
def dump(self, stage, **kwargs):
raise NotImplementedError
def merge(self, ancestor, other):
def merge(self, ancestor, other, allowed=None):
raise NotImplementedError
@@ -180,17 +195,21 @@ class SingleStageFile(FileMixin):
from dvc.schema import COMPILED_SINGLE_STAGE_SCHEMA as SCHEMA
from dvc.stage.loader import SingleStageLoader as LOADER
metrics: List[str] = []
plots: Any = {}
params: List[str] = []
@property
def stage(self):
def stage(self) -> "Stage":
data, raw = self._load()
return self.LOADER.load_stage(self, data, raw)
@property
def stages(self):
def stages(self) -> LOADER:
data, raw = self._load()
return self.LOADER(self, data, raw)
def dump(self, stage, **kwargs):
def dump(self, stage, **kwargs) -> None:
"""Dumps given stage appropriately in the dvcfile."""
from dvc.stage import PipelineStage
@@ -198,22 +217,23 @@ class SingleStageFile(FileMixin):
if self.verify:
check_dvcfile_path(self.repo, self.path)
logger.debug("Saving information to '%s'.", relpath(self.path))
dump_yaml(self.path, serialize.to_single_stage_file(stage))
dump_yaml(self.path, serialize.to_single_stage_file(stage, **kwargs))
self.repo.scm_context.track_file(self.relpath)
def remove_stage(self, stage): # pylint: disable=unused-argument
# pylint: disable-next=unused-argument
def remove_stage(self, stage): # noqa: ARG002
self.remove()
def merge(self, ancestor, other):
def merge(self, ancestor, other, allowed=None):
assert isinstance(ancestor, SingleStageFile)
assert isinstance(other, SingleStageFile)
stage = self.stage
stage.merge(ancestor.stage, other.stage)
stage.merge(ancestor.stage, other.stage, allowed=allowed)
self.dump(stage)
class PipelineFile(FileMixin):
class ProjectFile(FileMixin):
"""Abstraction for pipelines file, .yaml + .lock combined."""
from dvc.schema import COMPILED_MULTI_STAGE_SCHEMA as SCHEMA
@@ -223,6 +243,12 @@ class PipelineFile(FileMixin):
def _lockfile(self):
return Lockfile(self.repo, os.path.splitext(self.path)[0] + ".lock")
def _reset(self):
self.__dict__.pop("contents", None)
self.__dict__.pop("lockfile_contents", None)
self.__dict__.pop("resolver", None)
self.__dict__.pop("stages", None)
def dump(
self, stage, update_pipeline=True, update_lock=True, **kwargs
): # pylint: disable=arguments-differ
@@ -237,15 +263,17 @@ class PipelineFile(FileMixin):
self._dump_pipeline_file(stage)
if update_lock:
self._dump_lockfile(stage)
self._dump_lockfile(stage, **kwargs)
def _dump_lockfile(self, stage):
self._lockfile.dump(stage)
def _dump_lockfile(self, stage, **kwargs):
self._lockfile.dump(stage, **kwargs)
@staticmethod
def _check_if_parametrized(stage):
def _check_if_parametrized(stage, action: str = "dump") -> None:
if stage.raw_data.parametrized:
raise ParametrizedDumpError(f"cannot dump a parametrized {stage}")
raise ParametrizedDumpError(
f"cannot {action} a parametrized {stage}"
)
def _dump_pipeline_file(self, stage):
self._check_if_parametrized(stage)
@@ -273,14 +301,39 @@ class PipelineFile(FileMixin):
@property
def stage(self):
raise DvcException(
"PipelineFile has multiple stages. Please specify it's name."
"ProjectFile has multiple stages. Please specify it's name."
)
@cached_property
def contents(self) -> Dict[str, Any]:
return self._load()[0]
@cached_property
def lockfile_contents(self) -> Dict[str, Any]:
return self._lockfile.load()
@cached_property
def resolver(self) -> "DataResolver":
from .parsing import DataResolver
wdir = self.repo.fs.path.parent(self.path)
return DataResolver(self.repo, wdir, self.contents)
@cached_property
def stages(self) -> LOADER:
return self.LOADER(self, self.contents, self.lockfile_contents)
@property
def stages(self):
data, _ = self._load()
lockfile_data = self._lockfile.load()
return self.LOADER(self, data, lockfile_data)
def metrics(self) -> List[str]:
return self.contents.get("metrics", [])
@property
def plots(self) -> Any:
return self.contents.get("plots", {})
@property
def params(self) -> List[str]:
return self.contents.get("params", [])
def remove(self, force=False):
if not force:
@@ -291,6 +344,7 @@ class PipelineFile(FileMixin):
self._lockfile.remove()
def remove_stage(self, stage):
self._check_if_parametrized(stage, "remove")
self._lockfile.remove_stage(stage)
if not self.exists():
return
@@ -307,7 +361,7 @@ class PipelineFile(FileMixin):
else:
super().remove()
def merge(self, ancestor, other):
def merge(self, ancestor, other, allowed=None):
raise NotImplementedError
@@ -363,7 +417,7 @@ class Lockfile(FileMixin):
return {SCHEMA_KWD: version}
def dump(self, stage, **kwargs):
stage_data = serialize.to_lockfile(stage)
stage_data = serialize.to_lockfile(stage, **kwargs)
with modify_yaml(self.path, fs=self.repo.fs) as data:
version = LOCKFILE_VERSION.from_dict(data)
@@ -408,24 +462,14 @@ class Lockfile(FileMixin):
else:
self.remove()
def merge(self, ancestor, other):
def merge(self, ancestor, other, allowed=None):
raise NotImplementedError
class Dvcfile:
def __new__(cls, repo: "Repo", path: AnyPath, **kwargs: Any):
assert path
assert repo
return make_dvcfile(repo, path, **kwargs)
DVCFile = Union["PipelineFile", "SingleStageFile"]
def make_dvcfile(repo: "Repo", path: AnyPath, **kwargs: Any) -> DVCFile:
_, ext = os.path.splitext(str(path))
if ext in [".yaml", ".yml"]:
return PipelineFile(repo, path, **kwargs)
# fallback to single stage file for better error messages
def load_file(
repo: "Repo", path: "StrOrBytesPath", **kwargs: Any
) -> Union[ProjectFile, SingleStageFile]:
_, ext = os.path.splitext(path)
if ext in (".yaml", ".yml"):
return ProjectFile(repo, path, **kwargs)
return SingleStageFile(repo, path, **kwargs)
+2 -3
View File
@@ -2,11 +2,10 @@ DVC_CHECKPOINT = "DVC_CHECKPOINT"
DVC_DAEMON = "DVC_DAEMON"
DVC_PAGER = "DVC_PAGER"
DVC_ROOT = "DVC_ROOT"
DVCLIVE_PATH = "DVCLIVE_PATH"
DVCLIVE_SUMMARY = "DVCLIVE_SUMMARY"
DVCLIVE_HTML = "DVCLIVE_HTML"
DVCLIVE_RESUME = "DVCLIVE_RESUME"
DVC_IGNORE_ISATTY = "DVC_IGNORE_ISATTY"
DVC_EXP_BASELINE_REV = "DVC_EXP_BASELINE_REV"
DVC_EXP_NAME = "DVC_EXP_NAME"
DVC_EXP_GIT_REMOTE = "DVC_EXP_GIT_REMOTE"
DVC_EXP_AUTO_PUSH = "DVC_EXP_AUTO_PUSH"
DVC_NO_ANALYTICS = "DVC_NO_ANALYTICS"
+18 -4
View File
@@ -1,4 +1,6 @@
"""Exceptions raised by the dvc."""
from typing import Dict, List
from dvc.utils import format_link
@@ -132,10 +134,17 @@ class NotDvcRepoError(DvcException):
class CyclicGraphError(DvcException):
def __init__(self, stages):
assert isinstance(stages, list)
msg = "Pipeline has a cycle involving: {}.".format(
", ".join(s.addressing for s in stages)
stage_part = "stage" if len(stages) == 1 else "stages"
msg = (
"Same item(s) are defined as both a dependency and an output "
"in {stage_part}: {stage}."
)
super().__init__(
msg.format(
stage_part=stage_part,
stage=", ".join(s.addressing for s in stages),
)
)
super().__init__(msg)
class ConfirmRemoveError(DvcException):
@@ -220,7 +229,7 @@ class UploadError(FileTransferError):
class CheckoutError(DvcException):
def __init__(self, target_infos, stats=None):
def __init__(self, target_infos: List[str], stats: Dict[str, List[str]]):
from dvc.utils import error_link
self.target_infos = target_infos
@@ -278,6 +287,11 @@ class PathMissingError(DvcException):
self.dvc_only = dvc_only
class URLMissingError(DvcException):
def __init__(self, url):
super().__init__(f"The path '{url}' does not exist")
class RemoteCacheRequiredError(DvcException):
def __init__(self, scheme, fs_path):
+17 -11
View File
@@ -3,7 +3,7 @@ import os
import tempfile
import threading
from contextlib import contextmanager
from typing import TYPE_CHECKING, Dict
from typing import TYPE_CHECKING, Collection, Dict, Iterator, Optional, Tuple
from funcy import retry, wrap_with
@@ -20,7 +20,9 @@ from dvc.scm import CloneError, map_scm_exception
from dvc.utils import relpath
if TYPE_CHECKING:
from scmrepo import Git
from dvc.types import StrPath
from .scm import Git
logger = logging.getLogger(__name__)
@@ -28,12 +30,16 @@ logger = logging.getLogger(__name__)
@contextmanager
@map_scm_exception()
def external_repo(
url, rev=None, for_write=False, cache_dir=None, cache_types=None, **kwargs
):
from scmrepo.git import Git
url,
rev: Optional[str] = None,
for_write: bool = False,
cache_dir: Optional["StrPath"] = None,
cache_types: Optional[Collection[str]] = None,
**kwargs,
) -> Iterator["Repo"]:
from dvc.config import NoRemoteError
from dvc.fs import GitFileSystem
from dvc.scm import Git
logger.debug("Creating external repo %s@%s", url, rev)
path = _cached_clone(url, rev, for_write=for_write)
@@ -108,7 +114,7 @@ def erepo_factory(url, root_dir, cache_config):
return make_repo
CLONES: Dict[str, str] = {}
CLONES: Dict[str, Tuple[str, bool]] = {}
CACHE_DIRS: Dict[str, str] = {}
@@ -185,14 +191,14 @@ def _cached_clone(url, rev, for_write=False):
@wrap_with(threading.Lock())
def _clone_default_branch(url, rev, for_write=False):
def _clone_default_branch(url, rev, for_write=False): # noqa: C901
"""Get or create a clean clone of the url.
The cloned is reactualized with git pull unless rev is a known sha.
"""
from scmrepo.git import Git
from dvc.scm import Git
clone_path, shallow = CLONES.get(url, (None, False))
clone_path, shallow = CLONES.get(url) or (None, False)
git = None
try:
@@ -267,7 +273,7 @@ def _merge_upstream(git: "Git"):
def _git_checkout(repo_path, rev):
from scmrepo.git import Git
from dvc.scm import Git
logger.debug("erepo: git checkout %s@%s", repo_path, rev)
git = Git(repo_path)
+35 -12
View File
@@ -2,6 +2,9 @@ from urllib.parse import urlparse
from dvc_http import HTTPFileSystem, HTTPSFileSystem # noqa: F401
from dvc.config import ConfigError as RepoConfigError
from dvc.config_schema import SCHEMA, Invalid
# pylint: disable=unused-import
from dvc_objects.fs import utils # noqa: F401
from dvc_objects.fs import ( # noqa: F401
@@ -23,14 +26,15 @@ from dvc_objects.fs.errors import ( # noqa: F401
)
from dvc_objects.fs.path import Path # noqa: F401
from .callbacks import Callback
from .data import DataFileSystem # noqa: F401
from .dvc import DvcFileSystem # noqa: F401
from .dvc import DVCFileSystem # noqa: F401
from .git import GitFileSystem # noqa: F401
known_implementations.update(
{
"dvc": {
"class": "dvc.fs.dvc.DvcFileSystem",
"class": "dvc.fs.dvc.DVCFileSystem",
"err": "dvc is supported, but requires 'dvc' to be installed",
},
"git": {
@@ -44,7 +48,23 @@ known_implementations.update(
# pylint: enable=unused-import
def get_fs_config(repo, config, **kwargs):
def download(fs, fs_path, to, jobs=None):
with Callback.as_tqdm_callback(
desc=f"Downloading {fs.path.name(fs_path)}",
unit="files",
) as cb:
fs.get(fs_path, to.fs_path, batch_size=jobs, callback=cb)
def parse_external_url(url, config=None):
remote_config = dict(config) if config else {}
remote_config["url"] = url
fs_cls, fs_config, fs_path = get_cloud_fs(None, **remote_config)
fs = fs_cls(**fs_config)
return fs, fs_path
def get_fs_config(config, **kwargs):
name = kwargs.get("name")
if name:
try:
@@ -52,13 +72,15 @@ def get_fs_config(repo, config, **kwargs):
except KeyError:
from dvc.config import RemoteNotFoundError
raise RemoteNotFoundError(f"remote '{name}' doesn't exist")
raise RemoteNotFoundError( # noqa: B904
f"remote '{name}' doesn't exist"
)
else:
remote_conf = kwargs
return _resolve_remote_refs(repo, config, remote_conf)
return _resolve_remote_refs(config, remote_conf)
def _resolve_remote_refs(repo, config, remote_conf):
def _resolve_remote_refs(config, remote_conf):
# Support for cross referenced remotes.
# This will merge the settings, shadowing base ref with remote_conf.
# For example, having:
@@ -84,21 +106,22 @@ def _resolve_remote_refs(repo, config, remote_conf):
if parsed.scheme != "remote":
return remote_conf
base = get_fs_config(repo, config, name=parsed.netloc)
cls, _, _ = get_cloud_fs(repo, **base)
base = get_fs_config(config, name=parsed.netloc)
cls, _, _ = _get_cloud_fs(config, **base)
relpath = parsed.path.lstrip("/").replace("/", cls.sep)
url = cls.sep.join((base["url"], relpath))
return {**base, **remote_conf, "url": url}
def get_cloud_fs(repo, **kwargs):
from dvc.config import ConfigError as RepoConfigError
from dvc.config_schema import SCHEMA, Invalid
repo_config = repo.config if repo else {}
return _get_cloud_fs(repo_config, **kwargs)
def _get_cloud_fs(repo_config, **kwargs):
core_config = repo_config.get("core", {})
remote_conf = get_fs_config(repo, repo_config, **kwargs)
remote_conf = get_fs_config(repo_config, **kwargs)
try:
remote_conf = SCHEMA["remote"][str](remote_conf)
except Invalid as exc:
+31 -16
View File
@@ -1,9 +1,8 @@
# pylint: disable=unused-import
from contextlib import ExitStack
from typing import TYPE_CHECKING, Optional
from funcy import cached_property
from typing import TYPE_CHECKING, Any, BinaryIO, Dict, Optional, Union, cast
from dvc.utils.objects import cached_property
from dvc_objects.fs.callbacks import ( # noqa: F401
DEFAULT_CALLBACK,
Callback,
@@ -11,6 +10,8 @@ from dvc_objects.fs.callbacks import ( # noqa: F401
)
if TYPE_CHECKING:
from rich.progress import TaskID
from dvc.ui._rich_progress import RichTransferProgress
@@ -19,11 +20,12 @@ class RichCallback(Callback):
self,
size: Optional[int] = None,
value: int = 0,
progress: "RichTransferProgress" = None,
desc: str = None,
bytes: bool = False, # pylint: disable=redefined-builtin
unit: str = None,
progress: Optional["RichTransferProgress"] = None,
desc: Optional[str] = None,
bytes: bool = False, # noqa: A002, pylint: disable=redefined-builtin
unit: Optional[str] = None,
disable: bool = False,
transient: bool = True,
) -> None:
self._progress = progress
self.disable = disable
@@ -35,11 +37,12 @@ class RichCallback(Callback):
"visible": False,
"progress_type": None if bytes else "summary",
}
self._transient = transient
self._stack = ExitStack()
super().__init__(size=size, value=value)
@cached_property
def progress(self):
def progress(self) -> "RichTransferProgress":
from dvc.ui import ui
from dvc.ui._rich_progress import RichTransferProgress
@@ -47,24 +50,27 @@ class RichCallback(Callback):
return self._progress
progress = RichTransferProgress(
transient=True,
transient=self._transient,
disable=self.disable,
console=ui.error_console,
)
return self._stack.enter_context(progress)
return cast(RichTransferProgress, self._stack.enter_context(progress))
@cached_property
def task(self):
return self.progress.add_task(**self._task_kwargs)
def task(self) -> "TaskID":
return self.progress.add_task(
**self._task_kwargs # type: ignore[arg-type]
)
def __enter__(self):
return self
def close(self):
self.progress.clear_task(self.task)
if self._transient:
self.progress.clear_task(self.task)
self._stack.close()
def call(self, hook_name=None, **kwargs):
def call(self, hook_name=None, **kwargs): # noqa: ARG002
self.progress.update(
self.task,
completed=self.value,
@@ -72,8 +78,17 @@ class RichCallback(Callback):
visible=not self.disable,
)
def branch(self, path_1, path_2, kwargs, child: Optional[Callback] = None):
def branch(
self,
path_1: Union[str, BinaryIO],
path_2: str,
kwargs: Dict[str, Any],
child: Optional["Callback"] = None,
):
child = child or RichCallback(
progress=self.progress, desc=path_1, bytes=True
progress=self.progress,
desc=path_1 if isinstance(path_1, str) else path_2,
bytes=True,
transient=self._transient,
)
return super().branch(path_1, path_2, kwargs, child=child)
+12 -7
View File
@@ -1,13 +1,15 @@
import functools
import logging
import os
import threading
from funcy import cached_property, wrap_prop
from typing import TYPE_CHECKING
from dvc.utils import as_posix
from dvc_data.fs import DataFileSystem as _DataFileSystem
from dvc_objects.fs.base import FileSystem
if TYPE_CHECKING:
from dvc_data.fs import DataFileSystem as _DataFileSystem
logger = logging.getLogger(__name__)
@@ -19,9 +21,12 @@ class DataFileSystem(FileSystem):
def _prepare_credentials(self, **config):
return config
@wrap_prop(threading.Lock())
@cached_property
def fs(self):
@functools.cached_property
def fs( # pylint: disable=invalid-overridden-method
self,
) -> "_DataFileSystem":
from dvc_data.fs import DataFileSystem as _DataFileSystem
return _DataFileSystem(**self.fs_args)
def isdvc(self, path, **kwargs):
+175 -243
View File
@@ -1,50 +1,50 @@
import errno
import functools
import logging
import ntpath
import os
import posixpath
import threading
from contextlib import suppress
from itertools import takewhile
from typing import TYPE_CHECKING, Callable, Optional, Tuple, Type, Union
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
Optional,
Tuple,
Type,
Union,
)
from fsspec.spec import AbstractFileSystem
from funcy import cached_property, wrap_prop, wrap_with
from funcy import wrap_with
from dvc_objects.fs.base import FileSystem
from dvc_objects.fs.callbacks import DEFAULT_CALLBACK
from dvc_objects.fs.path import Path
from .data import DataFileSystem
if TYPE_CHECKING:
from dvc.repo import Repo
from dvc.types import StrPath
logger = logging.getLogger(__name__)
RepoFactory = Union[Callable[[str], "Repo"], Type["Repo"]]
RepoFactory = Union[Callable[..., "Repo"], Type["Repo"]]
Key = Tuple[str, ...]
def as_posix(path: str) -> str:
return path.replace(ntpath.sep, posixpath.sep)
def _wrap_walk(dvc_fs, *args, **kwargs):
for root, dnames, fnames in dvc_fs.walk(*args, **kwargs):
yield dvc_fs.path.join(dvc_fs.repo.root_dir, root), dnames, fnames
# NOT the same as dvc.dvcfile.is_dvc_file()!
def _is_dvc_file(fname):
from dvc.dvcfile import is_valid_filename
from dvc.ignore import DvcIgnore
def _ls(fs, path):
dnames = []
fnames = []
for entry in fs.ls(path, detail=True):
name = fs.path.name(entry["name"])
if entry["type"] == "directory":
dnames.append(name)
else:
fnames.append(name)
return dnames, fnames
return is_valid_filename(fname) or fname == DvcIgnore.DVCIGNORE_FILE
def _merge_info(repo, fs_info, dvc_info):
@@ -70,40 +70,68 @@ def _merge_info(repo, fs_info, dvc_info):
return ret
class _DvcFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
"""DVC + git-tracked files fs.
def _get_dvc_path(dvc_fs, subkey):
return dvc_fs.path.join(*subkey) if subkey else ""
Args:
repo: DVC or git repo.
subrepos: traverse to subrepos (by default, it ignores subrepos)
repo_factory: A function to initialize subrepo with, default is Repo.
kwargs: Additional keyword arguments passed to the `DataFileSystem()`.
"""
class _DVCFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
cachable = False
root_marker = "/"
PARAM_REPO_URL = "repo_url"
PARAM_REPO_ROOT = "repo_root"
PARAM_REV = "rev"
PARAM_CACHE_DIR = "cache_dir"
PARAM_CACHE_TYPES = "cache_types"
PARAM_SUBREPOS = "subrepos"
def __init__(
self,
url: Optional[str] = None,
rev: Optional[str] = None,
repo: Optional["Repo"] = None,
subrepos=False,
repo_factory: RepoFactory = None,
**kwargs,
):
super().__init__()
subrepos: bool = False,
repo_factory: Optional[RepoFactory] = None,
**repo_kwargs: Any,
) -> None:
"""DVC + git-tracked files fs.
Args:
path (str, optional): URL or path to a DVC/Git repository.
Defaults to a DVC repository in the current working directory.
Both HTTP and SSH protocols are supported for remote Git repos
(e.g. [user@]server:project.git).
rev (str, optional): Any Git revision such as a branch or tag name,
a commit hash or a dvc experiment name.
Defaults to the default branch in case of remote repositories.
In case of a local repository, if rev is unspecified, it will
default to the working directory.
If the repo is not a Git repo, this option is ignored.
repo (:obj:`Repo`, optional): `Repo` instance.
subrepos (bool): traverse to subrepos.
By default, it ignores subrepos.
repo_factory (callable): A function to initialize subrepo with.
The default is `Repo`.
Examples:
- Opening a filesystem from repo in current working directory
>>> fs = DVCFileSystem()
- Opening a filesystem from local repository
>>> fs = DVCFileSystem("path/to/local/repository")
- Opening a remote repository
>>> fs = DVCFileSystem(
... "https://github.com/iterative/example-get-started",
... rev="main",
... )
"""
from pygtrie import Trie
super().__init__()
if repo is None:
repo, repo_factory = self._repo_from_fs_config(
subrepos=subrepos, **kwargs
repo = self._make_repo(
url=url, rev=rev, subrepos=subrepos, **repo_kwargs
)
assert repo is not None
# pylint: disable=protected-access
repo_factory = repo._fs_conf["repo_factory"]
if not repo_factory:
from dvc.repo import Repo
@@ -114,6 +142,7 @@ class _DvcFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
def _getcwd():
relparts = ()
assert repo is not None
if repo.fs.path.isin(repo.fs.path.getcwd(), repo.root_dir):
relparts = repo.fs.path.relparts(
repo.fs.path.getcwd(), repo.root_dir
@@ -137,78 +166,33 @@ class _DvcFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
if hasattr(repo, "dvc_dir"):
self._datafss[key] = DataFileSystem(index=repo.index.data["repo"])
def _get_key(self, path):
def _get_key(self, path: "StrPath") -> Key:
parts = self.repo.fs.path.relparts(path, self.repo.root_dir)
if parts == (".",):
parts = ()
if parts == (os.curdir,):
return ()
return parts
def _get_key_from_relative(self, path) -> Key:
parts = self.path.relparts(path, self.root_marker)
if parts and parts[0] == os.curdir:
return parts[1:]
return parts
def _from_key(self, parts: Key) -> str:
return self.repo.fs.path.join(self.repo.root_dir, *parts)
@property
def repo_url(self):
if self.repo is None:
return None
return self.repo.url
@property
def config(self):
return {
self.PARAM_REPO_URL: self.repo_url,
self.PARAM_REPO_ROOT: self.repo.root_dir,
self.PARAM_REV: getattr(self.repo.fs, "rev", None),
self.PARAM_CACHE_DIR: os.path.abspath(self.repo.odb.local.path),
self.PARAM_CACHE_TYPES: self.repo.odb.local.cache_types,
self.PARAM_SUBREPOS: self._traverse_subrepos,
}
@classmethod
def _repo_from_fs_config(
cls, **config
) -> Tuple["Repo", Optional["RepoFactory"]]:
from dvc.external_repo import erepo_factory, external_repo
def _make_repo(cls, **kwargs) -> "Repo":
from dvc.repo import Repo
url = config.get(cls.PARAM_REPO_URL)
root = config.get(cls.PARAM_REPO_ROOT)
assert url or root
with Repo.open(uninitialized=True, **kwargs) as repo:
return repo
def _open(*args, **kwargs):
# NOTE: if original repo was an erepo (and has a URL),
# we cannot use Repo.open() since it will skip erepo
# cache/remote setup for local URLs
if url is None:
return Repo.open(*args, **kwargs)
return external_repo(*args, **kwargs)
cache_dir = config.get(cls.PARAM_CACHE_DIR)
cache_config = (
{}
if not cache_dir
else {
"cache": {
"dir": cache_dir,
"type": config.get(cls.PARAM_CACHE_TYPES),
}
}
)
repo_kwargs: dict = {
"rev": config.get(cls.PARAM_REV),
"subrepos": config.get(cls.PARAM_SUBREPOS, False),
"uninitialized": True,
}
factory: Optional["RepoFactory"] = None
if url is None:
repo_kwargs["config"] = cache_config
else:
repo_kwargs["cache_dir"] = cache_dir
factory = erepo_factory(url, root, cache_config)
with _open(
url if url else root,
**repo_kwargs,
) as repo:
return repo, factory
def _get_repo(self, path: str) -> "Repo":
def _get_repo(self, key: Key) -> "Repo":
"""Returns repo that the path falls in, using prefix.
If the path is already tracked/collected, it just returns the repo.
@@ -216,33 +200,21 @@ class _DvcFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
Otherwise, it collects the repos that might be in the path's parents
and then returns the appropriate one.
"""
if not self.repo.fs.path.isin_or_eq(path, self.repo.root_dir):
# outside of repo
return self.repo
key = self._get_key(path)
repo = self._subrepos_trie.get(key)
if repo:
return repo
prefix_key, repo = self._subrepos_trie.longest_prefix(key)
prefix = self.repo.fs.path.join(
self.repo.root_dir,
*prefix_key, # pylint: disable=not-an-iterable
)
parents = (parent for parent in self.repo.fs.path.parents(path))
dirs = [path] + list(takewhile(lambda p: p != prefix, parents))
dirs.reverse()
self._update(dirs, starting_repo=repo)
dir_keys = (key[:i] for i in range(len(prefix_key) + 1, len(key) + 1))
self._update(dir_keys, starting_repo=repo)
return self._subrepos_trie.get(key) or self.repo
@wrap_with(threading.Lock())
def _update(self, dirs, starting_repo):
def _update(self, dir_keys, starting_repo):
"""Checks for subrepo in directories and updates them."""
repo = starting_repo
for d in dirs:
key = self._get_key(d)
for key in dir_keys:
d = self._from_key(key)
if self._is_dvc_repo(d):
repo = self.repo_factory(
d,
@@ -265,108 +237,87 @@ class _DvcFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
repo_path = self.repo.fs.path.join(dir_path, Repo.DVC_DIR)
return self.repo.fs.isdir(repo_path)
def _get_fs_pair(
self, path
) -> Tuple[
Optional["Repo"],
Optional[FileSystem],
Optional[str],
Optional[DataFileSystem],
Optional[str],
]:
def _get_subrepo_info(
self, key: Key
) -> Tuple["Repo", Optional[DataFileSystem], Key]:
"""
Returns a pair of fss based on repo the path falls in, using prefix.
Returns information about the subrepo the key is part of.
"""
parts = self.path.relparts(path, self.root_marker)
if parts and parts[0] == os.curdir:
parts = parts[1:]
fs_path = self.repo.fs.path.join(self.repo.root_dir, *parts)
repo = self._get_repo(fs_path)
fs = repo.fs
repo_parts = fs.path.relparts(repo.root_dir, self.repo.root_dir)
if repo_parts[0] == os.curdir:
repo_parts = repo_parts[1:]
dvc_parts = parts[len(repo_parts) :]
if dvc_parts and dvc_parts[0] == os.curdir:
dvc_parts = dvc_parts[1:]
key = self._get_key(repo.root_dir)
dvc_fs = self._datafss.get(key)
if dvc_fs:
dvc_path = dvc_fs.path.join(*dvc_parts) if dvc_parts else ""
repo = self._get_repo(key)
repo_key: Key
if repo is self.repo:
repo_key = ()
subkey = key
else:
dvc_path = None
repo_key = self._get_key(repo.root_dir)
subkey = key[len(repo_key) :]
return repo, fs, fs_path, dvc_fs, dvc_path
dvc_fs = self._datafss.get(repo_key)
return repo, dvc_fs, subkey
def open(
self, path, mode="r", encoding="utf-8", **kwargs
def _open(
self, path, mode="rb", **kwargs
): # pylint: disable=arguments-renamed, arguments-differ
if "b" in mode:
encoding = None
if mode != "rb":
raise OSError(errno.EROFS, os.strerror(errno.EROFS))
_, fs, fs_path, dvc_fs, dvc_path = self._get_fs_pair(path)
key = self._get_key_from_relative(path)
fs_path = self._from_key(key)
try:
return fs.open(fs_path, mode=mode, encoding=encoding)
return self.repo.fs.open(fs_path, mode=mode)
except FileNotFoundError:
_, dvc_fs, subkey = self._get_subrepo_info(key)
if not dvc_fs:
raise
return dvc_fs.open(dvc_path, mode=mode, encoding=encoding, **kwargs)
dvc_path = _get_dvc_path(dvc_fs, subkey)
return dvc_fs.open(dvc_path, mode=mode)
def isdvc(self, path, **kwargs):
_, _, _, dvc_fs, dvc_path = self._get_fs_pair(path)
def isdvc(self, path, **kwargs) -> bool:
"""Is this entry dvc-tracked?"""
key = self._get_key_from_relative(path)
_, dvc_fs, subkey = self._get_subrepo_info(key)
dvc_path = _get_dvc_path(dvc_fs, subkey)
return dvc_fs is not None and dvc_fs.isdvc(dvc_path, **kwargs)
def ls( # pylint: disable=arguments-differ
self, path, detail=True, dvc_only=False, **kwargs
):
repo, fs, fs_path, dvc_fs, dvc_path = self._get_fs_pair(path)
key = self._get_key_from_relative(path)
repo, dvc_fs, subkey = self._get_subrepo_info(key)
dvcignore = repo.dvcignore
ignore_subrepos = kwargs.get("ignore_subrepos", True)
names = set()
dvc_infos = {}
if dvc_fs:
with suppress(FileNotFoundError):
for entry in dvc_fs.ls(dvc_path, detail=False):
names.add(dvc_fs.path.name(entry))
dvc_path = _get_dvc_path(dvc_fs, subkey)
for info in dvc_fs.ls(dvc_path, detail=True):
dvc_infos[dvc_fs.path.name(info["name"])] = info
if not dvc_only and fs:
fs_infos = {}
ignore_subrepos = kwargs.get("ignore_subrepos", True)
if not dvc_only:
fs = self.repo.fs
fs_path = self._from_key(key)
try:
for entry in dvcignore.ls(
fs, fs_path, detail=False, ignore_subrepos=ignore_subrepos
for info in repo.dvcignore.ls(
fs, fs_path, detail=True, ignore_subrepos=ignore_subrepos
):
names.add(fs.path.name(entry))
fs_infos[fs.path.name(info["name"])] = info
except (FileNotFoundError, NotADirectoryError):
pass
pass # noqa: S110
dvcfiles = kwargs.get("dvcfiles", False)
def _func(fname):
from dvc.dvcfile import is_valid_filename
from dvc.ignore import DvcIgnore
if dvcfiles:
return True
return not (
is_valid_filename(fname) or fname == DvcIgnore.DVCIGNORE_FILE
)
names = filter(_func, names)
infos = []
paths = []
names = set(dvc_infos.keys()) | set(fs_infos.keys())
for name in names:
entry_path = self.path.join(path, name)
try:
info = self.info(entry_path, ignore_subrepos=ignore_subrepos)
except FileNotFoundError:
if not dvcfiles and _is_dvc_file(name):
continue
entry_path = self.path.join(path, name)
info = _merge_info(repo, fs_infos.get(name), dvc_infos.get(name))
info["name"] = entry_path
infos.append(info)
paths.append(entry_path)
@@ -375,48 +326,41 @@ class _DvcFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
return infos
def get_file( # pylint: disable=arguments-differ
self, rpath, lpath, callback=DEFAULT_CALLBACK, **kwargs
):
_, fs, fs_path, dvc_fs, dvc_path = self._get_fs_pair(rpath)
if fs:
try:
fs.get_file(fs_path, lpath, callback=callback, **kwargs)
return
except FileNotFoundError:
if not dvc_fs:
raise
dvc_fs.get_file(dvc_path, lpath, callback=callback, **kwargs)
def info(self, path, **kwargs):
repo, fs, fs_path, dvc_fs, dvc_path = self._get_fs_pair(path)
dvcignore = repo.dvcignore
key = self._get_key_from_relative(path)
ignore_subrepos = kwargs.get("ignore_subrepos", True)
return self._info(key, path, ignore_subrepos=ignore_subrepos)
def _info( # noqa: C901
self, key, path, ignore_subrepos=True, check_ignored=True
):
repo, dvc_fs, subkey = self._get_subrepo_info(key)
dvc_info = None
if dvc_fs:
try:
dvc_info = dvc_fs.info(dvc_path)
dvc_info = dvc_fs.fs.index.info(subkey)
dvc_path = _get_dvc_path(dvc_fs, subkey)
dvc_info["name"] = dvc_path
except FileNotFoundError:
pass
fs_info = None
if fs:
try:
fs_info = fs.info(fs_path)
if dvcignore.is_ignored(
fs, fs_path, ignore_subrepos=ignore_subrepos
):
fs_info = None
except (FileNotFoundError, NotADirectoryError):
if not dvc_info:
raise
fs = self.repo.fs
fs_path = self._from_key(key)
try:
fs_info = fs.info(fs_path)
if check_ignored and repo.dvcignore.is_ignored(
fs, fs_path, ignore_subrepos=ignore_subrepos
):
fs_info = None
except (FileNotFoundError, NotADirectoryError):
if not dvc_info:
raise
# NOTE: if some parent in fs_path turns out to be a file, it means
# that the whole repofs branch doesn't exist.
if fs and not fs_info and dvc_info:
if dvc_info and not fs_info:
for parent in fs.path.parents(fs_path):
try:
if fs.info(parent)["type"] != "directory":
@@ -432,47 +376,35 @@ class _DvcFileSystem(AbstractFileSystem): # pylint:disable=abstract-method
info["name"] = path
return info
def checksum(self, path):
_, fs, fs_path, dvc_fs, dvc_path = self._get_fs_pair(path)
try:
return fs.checksum(fs_path)
except FileNotFoundError:
return dvc_fs.checksum(dvc_path)
class DvcFileSystem(FileSystem):
class DVCFileSystem(FileSystem):
protocol = "local"
PARAM_CHECKSUM = "md5"
def _prepare_credentials(self, **config):
def _prepare_credentials(self, **config) -> Dict[str, Any]:
return config
@wrap_prop(threading.Lock())
@cached_property
def fs(self):
return _DvcFileSystem(**self.fs_args)
@functools.cached_property
# pylint: disable-next=invalid-overridden-method
def fs(self) -> "DVCFileSystem":
return _DVCFileSystem(**self.fs_args)
def isdvc(self, path, **kwargs):
def isdvc(self, path, **kwargs) -> bool:
return self.fs.isdvc(path, **kwargs)
@property
def path(self): # pylint: disable=invalid-overridden-method
def path(self) -> Path: # pylint: disable=invalid-overridden-method
return self.fs.path
@property
def repo(self):
def repo(self) -> "Repo":
return self.fs.repo
@property
def repo_url(self):
def repo_url(self) -> str:
return self.fs.repo_url
@property
def config(self):
return self.fs.config
def from_os_path(self, path):
def from_os_path(self, path: str) -> str:
if os.path.isabs(path):
path = os.path.relpath(path, self.repo.root_dir)
+15 -14
View File
@@ -1,27 +1,27 @@
import threading
from typing import TYPE_CHECKING, Any
from funcy import cached_property, wrap_prop
import functools
from typing import TYPE_CHECKING, Any, Optional
from . import FileSystem
if TYPE_CHECKING:
from scmrepo.fs import GitFileSystem as FsspecGitFileSystem
from scmrepo.git import Git
from scmrepo.git.objects import GitTrie
from dvc.scm import Git
class GitFileSystem(FileSystem): # pylint:disable=abstract-method
"""Proxies the repo file access methods to Git objects"""
protocol = "local"
PARAM_CHECKSUM = "md5"
def __init__(
self,
path: str = None,
rev: str = None,
scm: "Git" = None,
trie: "GitTrie" = None,
path: Optional[str] = None,
rev: Optional[str] = None,
scm: Optional["Git"] = None,
trie: Optional["GitTrie"] = None,
**kwargs: Any,
) -> None:
from dvc.scm import resolve_rev
@@ -38,15 +38,16 @@ class GitFileSystem(FileSystem): # pylint:disable=abstract-method
}
)
@wrap_prop(threading.Lock())
@cached_property
def fs(self) -> "FsspecGitFileSystem":
@functools.cached_property
def fs( # pylint: disable=invalid-overridden-method
self,
) -> "FsspecGitFileSystem":
from scmrepo.fs import GitFileSystem as FsspecGitFileSystem
return FsspecGitFileSystem(**self.fs_args)
@cached_property
def path(self):
@functools.cached_property
def path(self): # pylint: disable=invalid-overridden-method
return self.fs.path
@property
+45 -22
View File
@@ -3,14 +3,17 @@ import os
import re
from collections import namedtuple
from itertools import chain, groupby, takewhile
from typing import TYPE_CHECKING, List, Optional
from pathspec.patterns import GitWildMatchPattern
from pathspec.util import normalize_file
from pygtrie import Trie
from dvc.fs import AnyFSPath, FileSystem, Schemes, localfs
from dvc.fs import Schemes, localfs
from dvc.pathspec_math import PatternInfo, merge_patterns
from dvc.types import List, Optional
if TYPE_CHECKING:
from dvc.fs import AnyFSPath, FileSystem
logger = logging.getLogger(__name__)
@@ -24,20 +27,27 @@ class DvcIgnore:
class DvcIgnorePatterns(DvcIgnore):
def __init__(self, pattern_list, dirname, sep):
if pattern_list:
if isinstance(pattern_list[0], str):
pattern_list = [
PatternInfo(pattern, "") for pattern in pattern_list
]
from pathspec.patterns.gitwildmatch import _DIR_MARK
if pattern_list and isinstance(pattern_list[0], str):
pattern_list = [
PatternInfo(pattern, "") for pattern in pattern_list
]
self.sep = sep
self.pattern_list = pattern_list
self.dirname = dirname
self.regex_pattern_list = [
GitWildMatchPattern.pattern_to_regex(pattern_info.patterns)
for pattern_info in pattern_list
]
self.regex_pattern_list = []
for count, pattern in enumerate(pattern_list):
pattern, group = GitWildMatchPattern.pattern_to_regex(
pattern.patterns
)
if pattern:
pattern = pattern.replace(
f"<{_DIR_MARK}>", f"<{_DIR_MARK}{count}>"
)
self.regex_pattern_list.append((pattern, group))
self.ignore_spec = [
(ignore, re.compile("|".join(item[0] for item in group)))
@@ -84,7 +94,7 @@ class DvcIgnorePatterns(DvcIgnore):
return False
if os.name == "nt":
path = normalize_file(path)
return normalize_file(path)
return path
def matches(self, dirname, basename, is_dir=False, details: bool = False):
@@ -195,8 +205,8 @@ class DvcIgnoreFilter:
def _get_key(self, path):
parts = self.fs.path.relparts(path, self.root_dir)
if parts == (".",):
parts = ()
if parts == (os.curdir,):
return ()
return parts
def _update_trie(self, dirname: str, trie: Trie) -> None:
@@ -299,18 +309,31 @@ class DvcIgnoreFilter:
return [fs_dict[name] for name in chain(dirs, nondirs)]
def walk(self, fs: FileSystem, path: AnyFSPath, **kwargs):
def walk(self, fs: "FileSystem", path: "AnyFSPath", **kwargs):
detail = kwargs.get("detail", False)
ignore_subrepos = kwargs.pop("ignore_subrepos", True)
if fs.protocol == Schemes.LOCAL:
for root, dirs, files in fs.walk(path, **kwargs):
dirs[:], files[:] = self(
root, dirs, files, ignore_subrepos=ignore_subrepos
)
if detail:
all_dnames = set(dirs.keys())
all_fnames = set(files.keys())
dnames, fnames = self(
root,
all_dnames,
all_fnames,
ignore_subrepos=ignore_subrepos,
)
list(map(dirs.pop, all_dnames - set(dnames)))
list(map(files.pop, all_fnames - set(fnames)))
else:
dirs[:], files[:] = self(
root, dirs, files, ignore_subrepos=ignore_subrepos
)
yield root, dirs, files
else:
yield from fs.walk(path, **kwargs)
def find(self, fs: FileSystem, path: AnyFSPath, **kwargs):
def find(self, fs: "FileSystem", path: "AnyFSPath", **kwargs):
if fs.protocol == Schemes.LOCAL:
for root, _, files in self.walk(fs, path, **kwargs):
for file in files:
@@ -366,7 +389,7 @@ class DvcIgnoreFilter:
return False
def is_ignored_dir(self, path: str, ignore_subrepos: bool = True) -> bool:
"Only used in LocalFileSystem"
# only used in LocalFileSystem
path = self.fs.path.abspath(path)
if path == self.root_dir:
return False
@@ -374,7 +397,7 @@ class DvcIgnoreFilter:
return self._is_ignored(path, True, ignore_subrepos=ignore_subrepos)
def is_ignored_file(self, path: str, ignore_subrepos: bool = True) -> bool:
"Only used in LocalFileSystem"
# only used in LocalFileSystem
path = self.fs.path.abspath(path)
return self._is_ignored(path, False, ignore_subrepos=ignore_subrepos)
@@ -400,7 +423,7 @@ class DvcIgnoreFilter:
return _no_match(target)
def is_ignored(
self, fs: FileSystem, path: str, ignore_subrepos: bool = True
self, fs: "FileSystem", path: str, ignore_subrepos: bool = True
) -> bool:
# NOTE: can't use self.check_ignore(path).match for now, see
# https://github.com/iterative/dvc/issues/4555
+26 -4
View File
@@ -14,15 +14,25 @@ from dvc.scm import SCMError
from dvc.utils import error_link
from dvc.utils.pkg import PKG
package = "" if PKG is None else f"({PKG})"
SUBPROJECTS = (
"dvc_data",
"dvc_objects",
"dvc_render",
"dvc_task",
"dvclive",
"scmrepo",
)
package = "" if PKG is None else f" ({PKG})"
def get_dvc_info():
dvc_version = f"DVC version: {__version__}{package}"
info = [
f"DVC version: {__version__} {package}",
"---------------------------------",
dvc_version,
"-" * len(dvc_version),
f"Platform: Python {platform.python_version()} on "
f"{platform.platform()}",
f"Subprojects:{_get_subprojects()}",
f"Supports:{_get_supported_remotes()}",
]
@@ -67,7 +77,7 @@ def _get_caches(cache):
def _get_remotes(config):
schemes = (
get_fs_cls(get_fs_config(None, config, name=remote)).protocol
get_fs_cls(get_fs_config(config, name=remote)).protocol
for remote in config["remote"]
)
@@ -88,6 +98,18 @@ def _get_linktype_support_info(repo):
return ", ".join(links)
def _get_subprojects():
subprojects = []
for subproject in SUBPROJECTS:
try:
version = importlib_metadata.version(subproject)
subprojects.append(f"{subproject} = {version}")
except ImportError:
pass
return "\n\t" + "\n\t".join(subprojects)
def _get_supported_remotes():
supported_remotes = []
for scheme in registry:
+5 -3
View File
@@ -116,7 +116,7 @@ class Lock(LockBase):
self._lock = zc.lockfile.LockFile(self._lockfile)
except zc.lockfile.LockError:
self._lock_failed = True
raise LockError(FAILED_TO_LOCK_MESSAGE)
raise LockError(FAILED_TO_LOCK_MESSAGE) # noqa: B904
def lock(self):
retries = 6
@@ -131,6 +131,7 @@ class Lock(LockBase):
if not self.is_locked:
raise DvcException("Unlock called on an unlocked lock")
assert self._lock
self._lock.close()
self._lock = None
@@ -185,14 +186,15 @@ class HardlinkLock(flufl.lock.Lock, LockBase):
try:
super().lock(timedelta(seconds=DEFAULT_TIMEOUT))
except flufl.lock.TimeOutError:
raise LockError(FAILED_TO_LOCK_MESSAGE)
raise LockError(FAILED_TO_LOCK_MESSAGE) # noqa: B904
def _set_claimfile(self):
super()._set_claimfile()
if self._tmp_dir is not None:
# Under Windows file path length is limited so we hash it
filename = hashlib.md5(self._claimfile.encode()).hexdigest()
hasher = hashlib.md5(self._claimfile.encode()) # noqa: S324
filename = hasher.hexdigest()
self._claimfile = os.path.join(self._tmp_dir, filename + ".lock")
+9 -6
View File
@@ -68,7 +68,7 @@ class LoggingException(Exception):
def excludeFilter(level):
class ExcludeLevelFilter(logging.Filter):
def filter(self, record):
def filter(self, record): # noqa: A003
return record.levelno < level
return ExcludeLevelFilter
@@ -94,7 +94,7 @@ class ColorFormatter(logging.Formatter):
"CRITICAL": colorama.Fore.RED,
}
def format(self, record):
def format(self, record): # noqa: A003
record.message = record.getMessage()
msg = self.formatMessage(record)
@@ -156,7 +156,7 @@ class LoggerHandler(logging.StreamHandler):
if not _is_verbose():
return
except Exception: # noqa, pylint: disable=broad-except
pass
pass # noqa: S110
msg = self.format(record)
Tqdm.write(
@@ -204,7 +204,7 @@ def disable_other_loggers():
loggerDict = logging.root.manager.loggerDict # pylint: disable=no-member
for logger_name, logger in loggerDict.items():
if logger_name != "dvc" and not logger_name.startswith("dvc."):
logger.disabled = True
logger.disabled = True # type: ignore[union-attr]
def set_loggers_level(level: int = logging.INFO) -> None:
@@ -213,8 +213,6 @@ def set_loggers_level(level: int = logging.INFO) -> None:
def setup(level: int = logging.INFO) -> None:
colorama.init()
if level >= logging.DEBUG:
# Unclosed session errors for asyncio/aiohttp are only available
# on the tracing mode for extensive debug purposes. They are really
@@ -297,3 +295,8 @@ def setup(level: int = logging.INFO) -> None:
"disable_existing_loggers": False,
}
)
# NOTE: colorama init needs to go after logging config to avoid potential
# conflicts with libs that modify logging (like tensorflow/abseil)
# https://github.com/iterative/dvc/issues/8387
colorama.init()
+9 -4
View File
@@ -13,10 +13,10 @@ from typing import (
)
from dvc.exceptions import DvcException
from dvc.types import StrPath
if TYPE_CHECKING:
from dvc.repo import Repo
from dvc.types import StrPath
from .backend.base import BaseMachineBackend
@@ -76,7 +76,7 @@ class MachineBackends(Mapping):
def __init__(
self,
selected: Optional[Iterable[str]],
tmp_dir: StrPath,
tmp_dir: "StrPath",
**kwargs,
) -> None:
selected = selected or list(self.DEFAULT)
@@ -127,6 +127,7 @@ class MachineManager:
self, repo: "Repo", backends: Optional[Iterable[str]] = None, **kwargs
):
self.repo = repo
assert self.repo.tmp_dir
tmp_dir = os.path.join(self.repo.tmp_dir, "machine")
self.backends = MachineBackends(backends, tmp_dir=tmp_dir, **kwargs)
@@ -167,7 +168,9 @@ class MachineManager:
except KeyError:
from dvc.config import MachineNotFoundError
raise MachineNotFoundError(f"machine '{name}' doesn't exist")
raise MachineNotFoundError( # noqa: B904
f"machine '{name}' doesn't exist"
)
else:
conf = kwargs
return conf
@@ -179,7 +182,9 @@ class MachineManager:
backend = self.CLOUD_BACKENDS[cloud]
return self.backends[backend]
except KeyError:
raise NoMachineError(f"Machine platform '{cloud}' unsupported")
raise NoMachineError( # noqa: B904
f"Machine platform '{cloud}' unsupported"
)
def create(self, name: Optional[str]):
"""Create and start the specified machine instance."""
+1 -1
View File
@@ -26,7 +26,7 @@ class BaseMachineBackend(ABC):
) -> Iterator[dict]:
"""Iterate over status of all instances of the specified machine."""
def close(self):
def close(self): # noqa: B027
pass
@abstractmethod
+2 -3
View File
@@ -6,7 +6,6 @@ from typing import TYPE_CHECKING, Iterator, Optional
from dvc_ssh import DEFAULT_PORT, SSHFileSystem
from dvc.exceptions import DvcException
from dvc.utils.fs import makedirs
from .base import BaseMachineBackend
@@ -30,7 +29,7 @@ def _sshfs(resource: dict):
class TerraformBackend(BaseMachineBackend):
def __init__(self, tmp_dir: "StrPath", **kwargs):
super().__init__(tmp_dir, **kwargs)
makedirs(tmp_dir, exist_ok=True)
os.makedirs(tmp_dir, exist_ok=True)
@contextmanager
def make_tpi(self, name: str):
@@ -39,7 +38,7 @@ class TerraformBackend(BaseMachineBackend):
try:
working_dir = os.path.join(self.tmp_dir, name)
makedirs(working_dir, exist_ok=True)
os.makedirs(working_dir, exist_ok=True)
yield TPIBackend(working_dir=working_dir)
except TPIError as exc:
raise DvcException("TPI operation failed") from exc
+2 -2
View File
@@ -1,5 +1,5 @@
from dvc.fs import GitFileSystem, Schemes
from dvc_data.db import get_odb
from dvc_data.hashfile.db import get_odb
def _get_odb(repo, settings, fs=None):
@@ -39,7 +39,7 @@ class ODBManager:
from dvc.config_schema import LOCAL_COMMON
settings = {"url": config["dir"]}
for opt in LOCAL_COMMON.keys():
for opt in LOCAL_COMMON:
if opt in config:
settings[str(opt)] = config.get(opt)
+362 -154
View File
@@ -1,11 +1,11 @@
import logging
import os
from collections import defaultdict
from typing import TYPE_CHECKING, Dict, Optional, Set, Type
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, cast
from urllib.parse import urlparse
from funcy import collecting, project
from voluptuous import And, Any, Coerce, Length, Lower, Required, SetTo
from funcy import collecting, first, project
from voluptuous import All, And, Any, Coerce, Length, Lower, Required, SetTo
from dvc import prompt
from dvc.exceptions import (
@@ -17,24 +17,33 @@ from dvc.exceptions import (
MergeError,
RemoteCacheRequiredError,
)
from dvc_data import Tree
from dvc_data import check as ocheck
from dvc_data import load as oload
from dvc_data.build import build
from dvc_data.checkout import checkout
from dvc.utils.objects import cached_property
from dvc_data.hashfile import Tree
from dvc_data.hashfile import check as ocheck
from dvc_data.hashfile import load as oload
from dvc_data.hashfile.build import build
from dvc_data.hashfile.checkout import checkout
from dvc_data.hashfile.hash_info import HashInfo
from dvc_data.hashfile.istextfile import istextfile
from dvc_data.hashfile.meta import Meta
from dvc_data.transfer import transfer as otransfer
from dvc_data.hashfile.transfer import transfer as otransfer
from dvc_data.index import DataIndexEntry
from dvc_objects.errors import ObjectFormatError
from .annotations import ANNOTATION_FIELDS, ANNOTATION_SCHEMA, Annotation
from .fs import LocalFileSystem, RemoteMissingDepsError, Schemes, get_cloud_fs
from .fs.callbacks import DEFAULT_CALLBACK
from .utils import relpath
from .utils.fs import path_isin
if TYPE_CHECKING:
from dvc_data.hashfile.obj import HashFile
from dvc_data.index import DataIndexKey
from dvc_objects.db import ObjectDB
from .fs.callbacks import Callback
from .ignore import DvcIgnoreFilter
logger = logging.getLogger(__name__)
@@ -80,9 +89,10 @@ def loadd_from(stage, d_list):
plot = d.pop(Output.PARAM_PLOT, False)
persist = d.pop(Output.PARAM_PERSIST, False)
checkpoint = d.pop(Output.PARAM_CHECKPOINT, False)
desc = d.pop(Output.PARAM_DESC, False)
live = d.pop(Output.PARAM_LIVE, False)
remote = d.pop(Output.PARAM_REMOTE, None)
annot = {field: d.pop(field, None) for field in ANNOTATION_FIELDS}
files = d.pop(Output.PARAM_FILES, None)
push = d.pop(Output.PARAM_PUSH, True)
ret.append(
_get(
stage,
@@ -93,9 +103,10 @@ def loadd_from(stage, d_list):
plot=plot,
persist=persist,
checkpoint=checkpoint,
desc=desc,
live=live,
remote=remote,
**annot,
files=files,
push=push,
)
)
return ret
@@ -109,8 +120,8 @@ def loads_from(
plot=False,
persist=False,
checkpoint=False,
live=False,
remote=None,
push=True,
):
return [
_get(
@@ -122,8 +133,8 @@ def loads_from(
plot=plot,
persist=persist,
checkpoint=checkpoint,
live=live,
remote=remote,
push=push,
)
for s in s_list
]
@@ -134,17 +145,19 @@ def _split_dict(d, keys):
def _merge_data(s_list):
d = defaultdict(dict)
d: Dict[str, Dict] = defaultdict(dict)
for key in s_list:
if isinstance(key, str):
d[key].update({})
continue
if not isinstance(key, dict):
raise ValueError(f"'{type(key).__name__}' not supported.")
raise ValueError( # noqa: TRY004
f"'{type(key).__name__}' not supported."
)
for k, flags in key.items():
if not isinstance(flags, dict):
raise ValueError(
raise ValueError( # noqa: TRY004
f"Expected dict for '{k}', got: '{type(flags).__name__}'"
)
d[k].update(flags)
@@ -157,29 +170,21 @@ def load_from_pipeline(stage, data, typ="outs"):
stage.PARAM_OUTS,
stage.PARAM_METRICS,
stage.PARAM_PLOTS,
stage.PARAM_LIVE,
):
raise ValueError(f"'{typ}' key is not allowed for pipeline files.")
metric = typ == stage.PARAM_METRICS
plot = typ == stage.PARAM_PLOTS
live = typ == stage.PARAM_LIVE
if live:
# `live` is single object
data = [data]
d = _merge_data(data)
for path, flags in d.items():
plt_d, live_d = {}, {}
plt_d = {}
if plot:
from dvc.schema import PLOT_PROPS
plt_d, flags = _split_dict(flags, keys=PLOT_PROPS.keys())
if live:
from dvc.schema import LIVE_PROPS
live_d, flags = _split_dict(flags, keys=LIVE_PROPS.keys())
extra = project(
flags,
[
@@ -187,6 +192,8 @@ def load_from_pipeline(stage, data, typ="outs"):
Output.PARAM_PERSIST,
Output.PARAM_CHECKPOINT,
Output.PARAM_REMOTE,
Output.PARAM_PUSH,
*ANNOTATION_FIELDS,
],
)
@@ -196,11 +203,34 @@ def load_from_pipeline(stage, data, typ="outs"):
info={},
plot=plt_d or plot,
metric=metric,
live=live_d or live,
**extra,
)
def split_file_meta_from_cloud(entry: Dict) -> Dict:
if remote_name := entry.pop(Meta.PARAM_REMOTE, None):
remote_meta = {}
for key in (
S3_PARAM_CHECKSUM,
HDFS_PARAM_CHECKSUM,
Meta.PARAM_VERSION_ID,
):
if value := entry.pop(key, None):
remote_meta[key] = value
if remote_meta:
entry[Output.PARAM_CLOUD] = {remote_name: remote_meta}
return entry
def merge_file_meta_from_cloud(entry: Dict) -> Dict:
cloud_meta = entry.pop(Output.PARAM_CLOUD, {})
if remote_name := first(cloud_meta):
entry.update(cloud_meta[remote_name])
entry[Meta.PARAM_REMOTE] = remote_name
return entry
class OutputDoesNotExistError(DvcException):
def __init__(self, path):
msg = f"output '{path}' does not exist"
@@ -240,6 +270,7 @@ class Output:
PARAM_PATH = "path"
PARAM_CACHE = "cache"
PARAM_CHECKPOINT = "checkpoint"
PARAM_FILES = "files"
PARAM_METRIC = "metric"
PARAM_METRIC_TYPE = "type"
PARAM_METRIC_XPATH = "xpath"
@@ -252,11 +283,9 @@ class Output:
PARAM_PLOT_TITLE = "title"
PARAM_PLOT_HEADER = "header"
PARAM_PERSIST = "persist"
PARAM_DESC = "desc"
PARAM_LIVE = "live"
PARAM_LIVE_SUMMARY = "summary"
PARAM_LIVE_HTML = "html"
PARAM_REMOTE = "remote"
PARAM_PUSH = "push"
PARAM_CLOUD = "cloud"
METRIC_SCHEMA = Any(
None,
@@ -267,10 +296,10 @@ class Output:
},
)
DoesNotExistError = OutputDoesNotExistError # type: Type[DvcException]
IsNotFileOrDirError = OutputIsNotFileOrDirError # type: Type[DvcException]
IsStageFileError = OutputIsStageFileError # type: Type[DvcException]
IsIgnoredError = OutputIsIgnoredError # type: Type[DvcException]
DoesNotExistError: Type[DvcException] = OutputDoesNotExistError
IsNotFileOrDirError: Type[DvcException] = OutputIsNotFileOrDirError
IsStageFileError: Type[DvcException] = OutputIsStageFileError
IsIgnoredError: Type[DvcException] = OutputIsIgnoredError
def __init__(
self,
@@ -282,13 +311,34 @@ class Output:
plot=False,
persist=False,
checkpoint=False,
live=False,
desc=None,
type=None, # noqa: A002, pylint: disable=redefined-builtin
labels=None,
meta=None,
remote=None,
repo=None,
fs_config=None,
files: Optional[List[Dict[str, Any]]] = None,
push: bool = True,
):
self.annot = Annotation(
desc=desc, type=type, labels=labels or [], meta=meta or {}
)
self.repo = stage.repo if not repo and stage else repo
fs_cls, fs_config, fs_path = get_cloud_fs(self.repo, url=path)
meta_d = merge_file_meta_from_cloud(info or {})
meta = Meta.from_dict(meta_d)
# NOTE: when version_aware is not passed into get_cloud_fs, it will be
# set based on whether or not path is versioned
fs_kwargs = {}
if meta.version_id or files:
fs_kwargs["version_aware"] = True
if fs_config is not None:
fs_kwargs.update(**fs_config)
fs_cls, fs_config, fs_path = get_cloud_fs(
self.repo, url=path, **fs_kwargs
)
self.fs = fs_cls(**fs_config)
if (
@@ -297,7 +347,7 @@ class Output:
and isinstance(stage.repo.fs, LocalFileSystem)
and path_isin(path, stage.repo.root_dir)
):
self.def_path = relpath(path, stage.wdir)
self.def_path: str = relpath(path, stage.wdir)
self.fs = stage.repo.fs
else:
self.def_path = path
@@ -320,21 +370,56 @@ class Output:
# By resolved path, which contains actual location,
# should be absolute and don't contain remote:// refs.
self.stage = stage
self.meta = Meta.from_dict(info)
self.hash_info = HashInfo.from_dict(info)
self.meta = meta
if files is not None:
files = [merge_file_meta_from_cloud(f) for f in files]
self.files = files
self.use_cache = False if self.IS_DEPENDENCY else cache
self.metric = False if self.IS_DEPENDENCY else metric
self.plot = False if self.IS_DEPENDENCY else plot
self.persist = persist
self.checkpoint = checkpoint
self.live = live
self.desc = desc
self.can_push = push
self.fs_path = self._parse_path(self.fs, fs_path)
self.obj = None
self.obj: Optional["HashFile"] = None
self.remote = remote
if self.fs.version_aware:
_, version_id = self.fs.path.coalesce_version(
self.def_path, self.meta.version_id
)
self.meta.version_id = version_id
if self.is_in_repo:
self.hash_name = "md5"
else:
self.hash_name = self.fs.PARAM_CHECKSUM
self.hash_info = HashInfo(
name=self.hash_name,
value=getattr(self.meta, self.hash_name, None),
)
self._compute_meta_hash_info_from_files()
def _compute_meta_hash_info_from_files(self) -> None:
if self.files:
tree = Tree.from_list(self.files, hash_name=self.hash_name)
tree.digest()
self.hash_info = tree.hash_info
self.meta.isdir = True
self.meta.nfiles = len(self.files)
self.meta.size = sum(f.get("size") for f in self.files)
self.meta.remote = first(f.get("remote") for f in self.files)
elif self.meta.nfiles or self.hash_info and self.hash_info.isdir:
self.meta.isdir = True
if not self.hash_info and self.hash_name != "md5":
md5 = getattr(self.meta, "md5", None)
if md5:
self.hash_info = HashInfo("md5", md5)
def _parse_path(self, fs, fs_path):
parsed = urlparse(self.def_path)
if (
@@ -375,6 +460,12 @@ class Output:
return self.fs.path.relpath(self.fs_path, self.repo.root_dir)
def clear(self):
self.hash_info = HashInfo.from_dict({})
self.meta = Meta.from_dict({})
self.obj = None
self.files = None
@property
def protocol(self):
return self.fs.protocol
@@ -414,31 +505,36 @@ class Output:
)
def get_hash(self):
_, hash_info = self._get_hash_meta()
return hash_info
def _get_hash_meta(self):
if self.use_cache:
odb = self.odb
name = self.odb.fs.PARAM_CHECKSUM
else:
odb = self.repo.odb.local
name = self.fs.PARAM_CHECKSUM
_, _, obj = build(
_, meta, obj = build(
odb,
self.fs_path,
self.fs,
name,
self.hash_name,
ignore=self.dvcignore,
dry_run=not self.use_cache,
)
return obj.hash_info
return meta, obj.hash_info
def get_meta(self) -> Meta:
meta, _ = self._get_hash_meta()
return meta
@property
def is_dir_checksum(self):
return self.hash_info.isdir
def _is_path_dvcignore(self, path) -> bool:
if not self.IS_DEPENDENCY and self.dvcignore:
if self.dvcignore.is_ignored(self.fs, path, ignore_subrepos=False):
return True
return False
if self.IS_DEPENDENCY or not self.dvcignore:
return False
return self.dvcignore.is_ignored(self.fs, path, ignore_subrepos=False)
@property
def exists(self):
@@ -447,6 +543,37 @@ class Output:
return self.fs.exists(self.fs_path)
@cached_property
def index_key(self) -> Tuple[str, "DataIndexKey"]:
if self.is_in_repo:
workspace = "repo"
key = self.repo.fs.path.relparts(self.fs_path, self.repo.root_dir)
else:
workspace = self.fs.protocol
no_drive = self.fs.path.flavour.splitdrive(self.fs_path)[1]
key = self.fs.path.parts(no_drive)[1:]
return workspace, key
def get_entry(self) -> "DataIndexEntry":
if self.files and not self.obj:
self.obj = self.get_obj()
entry = DataIndexEntry(
meta=self.meta,
obj=self.obj,
hash_info=self.hash_info,
)
if self.stage.is_import and not self.stage.is_repo_import:
dep = self.stage.deps[0]
entry.fs = dep.fs
entry.path = dep.fs_path
entry.meta = dep.meta
if not entry.obj:
if not dep.obj and dep.files:
dep.obj = dep.get_obj()
entry.obj = dep.obj
return entry
def changed_checksum(self):
return self.hash_info != self.get_hash()
@@ -464,7 +591,12 @@ class Output:
except (FileNotFoundError, ObjectFormatError):
return True
def workspace_status(self):
def changed_meta(self) -> bool:
if self.fs.version_aware and self.meta.version_id:
return self.meta.version_id != self.get_meta().version_id
return False
def workspace_status(self) -> Dict[str, str]:
if not self.exists:
return {str(self): "deleted"}
@@ -476,40 +608,40 @@ class Output:
return {}
def status(self):
def status(self) -> Dict[str, str]:
if self.hash_info and self.use_cache and self.changed_cache():
return {str(self): "not in cache"}
return self.workspace_status()
def changed(self):
def changed(self) -> bool:
status = self.status()
logger.debug(str(status))
return bool(status)
@property
def dvcignore(self):
def dvcignore(self) -> Optional["DvcIgnoreFilter"]:
if self.fs.protocol == "local":
return self.repo.dvcignore
return None
@property
def is_empty(self):
def is_empty(self) -> bool:
return self.fs.is_empty(self.fs_path)
def isdir(self):
def isdir(self) -> bool:
if self._is_path_dvcignore(self.fs_path):
return False
return self.fs.isdir(self.fs_path)
def isfile(self):
def isfile(self) -> bool:
if self._is_path_dvcignore(self.fs_path):
return False
return self.fs.isfile(self.fs_path)
# pylint: disable=no-member
def ignore(self):
def ignore(self) -> None:
if not self.use_scm_ignore:
return
@@ -518,7 +650,7 @@ class Output:
self.repo.scm_context.ignore(self.fspath)
def ignore_remove(self):
def ignore_remove(self) -> None:
if not self.use_scm_ignore:
return
@@ -526,11 +658,11 @@ class Output:
# pylint: enable=no-member
def save(self):
def save(self) -> None:
if not self.exists:
raise self.DoesNotExistError(self)
if not self.isfile and not self.isdir:
if not self.isfile() and not self.isdir():
raise self.IsNotFileOrDirError(self)
if self.is_empty:
@@ -541,52 +673,50 @@ class Output:
if self.metric:
self.verify_metric()
if not self.use_cache:
_, self.meta, obj = build(
if self.use_cache:
_, self.meta, self.obj = build(
self.odb,
self.fs_path,
self.fs,
self.hash_name,
ignore=self.dvcignore,
)
else:
_, self.meta, self.obj = build(
self.repo.odb.local,
self.fs_path,
self.fs,
self.fs.PARAM_CHECKSUM,
self.hash_name,
ignore=self.dvcignore,
dry_run=True,
)
self.hash_info = obj.hash_info
if not self.IS_DEPENDENCY:
logger.debug(
"Output '%s' doesn't use cache. Skipping saving.", self
)
return
assert not self.IS_DEPENDENCY
_, self.meta, self.obj = build(
self.odb,
self.fs_path,
self.fs,
self.odb.fs.PARAM_CHECKSUM,
ignore=self.dvcignore,
)
self.hash_info = self.obj.hash_info
self.files = None
def set_exec(self):
def set_exec(self) -> None:
if self.isfile() and self.meta.isexec:
self.odb.set_exec(self.fs_path)
def _checkout(self, *args, **kwargs):
from dvc_data.checkout import CheckoutError as _CheckoutError
from dvc_data.checkout import LinkError, PromptError
def _checkout(self, *args, **kwargs) -> Optional[bool]:
from dvc_data.hashfile.checkout import CheckoutError as _CheckoutError
from dvc_data.hashfile.checkout import LinkError, PromptError
kwargs.setdefault("ignore", self.dvcignore)
try:
return checkout(*args, **kwargs)
except PromptError as exc:
raise ConfirmRemoveError(exc.path)
raise ConfirmRemoveError(exc.path) # noqa: B904
except LinkError as exc:
raise CacheLinkError([exc.path])
raise CacheLinkError([exc.path]) # noqa: B904
except _CheckoutError as exc:
raise CheckoutError(exc.paths)
raise CheckoutError(exc.paths, {}) # noqa: B904
def commit(self, filter_info=None):
def commit(self, filter_info=None) -> None:
if not self.exists:
raise self.DoesNotExistError(self)
@@ -605,7 +735,7 @@ class Output:
self.odb,
filter_info or self.fs_path,
self.fs,
self.odb.fs.PARAM_CHECKSUM,
self.hash_name,
ignore=self.dvcignore,
)
otransfer(
@@ -626,7 +756,7 @@ class Output:
)
self.set_exec()
def _commit_granular_dir(self, filter_info):
def _commit_granular_dir(self, filter_info) -> Optional["HashFile"]:
prefix = self.fs.path.parts(
self.fs.path.relpath(filter_info, self.fs_path)
)
@@ -634,10 +764,11 @@ class Output:
self.odb,
self.fs_path,
self.fs,
self.odb.fs.PARAM_CHECKSUM,
self.hash_name,
ignore=self.dvcignore,
)
save_obj = save_obj.filter(prefix)
save_obj = cast(Tree, save_obj)
save_obj = cast(Tree, save_obj.filter(prefix))
checkout_obj = save_obj.get_obj(self.odb, prefix)
otransfer(
staging,
@@ -648,8 +779,19 @@ class Output:
)
return checkout_obj
def dumpd(self):
ret = {**self.hash_info.to_dict(), **self.meta.to_dict()}
def dumpd(self, **kwargs): # noqa: C901
ret: Dict[str, Any] = {}
with_files = (
(not self.IS_DEPENDENCY or self.stage.is_import)
and self.hash_info.isdir
and (kwargs.get("with_files") or self.files is not None)
)
if not with_files:
meta_d = self.meta.to_dict()
meta_d.pop("isdir", None)
ret.update(self.hash_info.to_dict())
ret.update(split_file_meta_from_cloud(meta_d))
if self.is_in_repo:
path = self.fs.path.as_posix(
@@ -660,40 +802,44 @@ class Output:
ret[self.PARAM_PATH] = path
if self.IS_DEPENDENCY:
return ret
if not self.IS_DEPENDENCY:
ret.update(self.annot.to_dict())
if not self.use_cache:
ret[self.PARAM_CACHE] = self.use_cache
if self.desc:
ret[self.PARAM_DESC] = self.desc
if not self.use_cache:
ret[self.PARAM_CACHE] = self.use_cache
if isinstance(self.metric, dict):
if (
self.PARAM_METRIC_XPATH in self.metric
isinstance(self.metric, dict)
and self.PARAM_METRIC_XPATH in self.metric
and not self.metric[self.PARAM_METRIC_XPATH]
):
del self.metric[self.PARAM_METRIC_XPATH]
if self.metric:
ret[self.PARAM_METRIC] = self.metric
if self.metric:
ret[self.PARAM_METRIC] = self.metric
if self.plot:
ret[self.PARAM_PLOT] = self.plot
if self.plot:
ret[self.PARAM_PLOT] = self.plot
if self.persist:
ret[self.PARAM_PERSIST] = self.persist
if self.persist:
ret[self.PARAM_PERSIST] = self.persist
if self.checkpoint:
ret[self.PARAM_CHECKPOINT] = self.checkpoint
if self.checkpoint:
ret[self.PARAM_CHECKPOINT] = self.checkpoint
if self.live:
ret[self.PARAM_LIVE] = self.live
if self.remote:
ret[self.PARAM_REMOTE] = self.remote
if self.remote:
ret[self.PARAM_REMOTE] = self.remote
if not self.can_push:
ret[self.PARAM_PUSH] = self.can_push
if with_files:
obj = self.obj or self.get_obj()
if obj:
obj = cast("Tree", obj)
ret[self.PARAM_FILES] = [
split_file_meta_from_cloud(f)
for f in obj.as_list(with_meta=True)
]
return ret
def verify_metric(self):
@@ -717,47 +863,48 @@ class Output:
f"binary file '{self.fs_path}' cannot be used as metrics."
)
def download(self, to, jobs=None):
from dvc.fs.callbacks import Callback
with Callback.as_tqdm_callback(
desc=f"Downloading {self.fs.path.name(self.fs_path)}",
unit="files",
) as cb:
self.fs.get(self.fs_path, to.fs_path, batch_size=jobs, callback=cb)
def get_obj(self, filter_info=None, **kwargs):
def get_obj(
self, filter_info: Optional[str] = None, **kwargs
) -> Optional["HashFile"]:
obj: Optional["HashFile"] = None
if self.obj:
obj = self.obj
elif self.hash_info:
try:
obj = oload(self.odb, self.hash_info)
except FileNotFoundError:
return None
if self.files:
tree = Tree.from_list(self.files, hash_name=self.hash_name)
tree.digest()
obj = tree
else:
try:
obj = oload(self.odb, self.hash_info)
except (FileNotFoundError, ObjectFormatError):
return None
else:
return None
assert obj
fs_path = self.fs.path
if filter_info and filter_info != self.fs_path:
prefix = fs_path.relparts(filter_info, self.fs_path)
obj = cast(Tree, obj)
obj = obj.get_obj(self.odb, prefix)
return obj
def checkout(
self,
force=False,
progress_callback=None,
relink=False,
filter_info=None,
allow_missing=False,
checkpoint_reset=False,
force: bool = False,
progress_callback: "Callback" = DEFAULT_CALLBACK,
relink: bool = False,
filter_info: Optional[str] = None,
allow_missing: bool = False,
checkpoint_reset: bool = False,
**kwargs,
):
) -> Optional[Tuple[bool, Optional[bool]]]:
if not self.use_cache:
if progress_callback:
progress_callback(
self.fs_path, self.get_files_number(filter_info)
if progress_callback != DEFAULT_CALLBACK:
progress_callback.relative_update(
self.get_files_number(filter_info)
)
return None
@@ -794,7 +941,7 @@ class Output:
return added, False if added else modified
def remove(self, ignore_remove=False):
self.fs.remove(self.fs_path)
self.fs.remove(self.fs_path, recursive=True)
if self.protocol != Schemes.LOCAL:
return
@@ -853,6 +1000,7 @@ class Output:
)
self.hash_info = obj.hash_info
self.files = None
return obj
def get_files_number(self, filter_info=None):
@@ -915,7 +1063,7 @@ class Output:
"Would you like to continue? Use '-f' to force."
)
if not force and not prompt.confirm(msg.format(self.fs_path)):
raise CollectCacheError(
raise CollectCacheError( # noqa: B904
"unable to fully collect used cache"
" without cache for directory '{}'".format(self)
)
@@ -923,13 +1071,15 @@ class Output:
obj = self.get_obj()
if filter_info and filter_info != self.fs_path:
assert obj
prefix = self.fs.path.parts(
self.fs.path.relpath(filter_info, self.fs_path)
)
obj = obj.filter(prefix)
return obj
obj = cast(Tree, obj)
return obj.filter(prefix)
return cast(Tree, obj)
def get_used_objs(
def get_used_objs( # noqa: C901
self, **kwargs
) -> Dict[Optional["ObjectDB"], Set["HashInfo"]]:
"""Return filtered set of used object IDs for this out."""
@@ -937,9 +1087,15 @@ class Output:
if not self.use_cache:
return {}
push: bool = kwargs.pop("push", False)
if self.stage.is_repo_import:
if push:
return {}
return self.get_used_external(**kwargs)
if push and not self.can_push:
return {}
if not self.hash_info:
msg = (
"Output '{}'({}) is missing version info. "
@@ -959,6 +1115,7 @@ class Output:
logger.warning(msg)
return {}
obj: Optional["HashFile"]
if self.is_dir_checksum:
obj = self._collect_used_dir_cache(**kwargs)
else:
@@ -1015,7 +1172,7 @@ class Output:
other = out.dumpd()
ignored = [
self.fs.PARAM_CHECKSUM,
self.hash_name,
Meta.PARAM_SIZE,
Meta.PARAM_NFILES,
]
@@ -1034,9 +1191,9 @@ class Output:
"unable to auto-merge outputs that are not directories"
)
def merge(self, ancestor, other):
from dvc_data.objects.tree import MergeError as TreeMergeError
from dvc_data.objects.tree import du, merge
def merge(self, ancestor, other, allowed=None):
from dvc_data.hashfile.tree import MergeError as TreeMergeError
from dvc_data.hashfile.tree import du, merge
assert other
@@ -1051,7 +1208,11 @@ class Output:
try:
merged = merge(
self.odb, ancestor_info, self.hash_info, other.hash_info
self.odb,
ancestor_info,
self.hash_info,
other.hash_info,
allowed=allowed,
)
except TreeMergeError as exc:
raise MergeError(str(exc)) from exc
@@ -1059,6 +1220,7 @@ class Output:
self.odb.add(merged.path, merged.fs, merged.oid)
self.hash_info = merged.hash_info
self.files = None
self.meta = Meta(
size=du(self.odb, merged),
nfiles=len(merged),
@@ -1074,28 +1236,74 @@ class Output:
@property
def is_metric(self) -> bool:
return bool(self.metric) or bool(self.live)
return bool(self.metric)
@property
def is_plot(self) -> bool:
return bool(self.plot) or bool(self.live)
return bool(self.plot)
def restore_fields(self, other: "Output"):
"""Restore attributes that need to be preserved when serialized."""
self.annot = other.annot
self.remote = other.remote
self.can_push = other.can_push
def merge_version_meta(self, other: "Output"):
"""Merge version meta for files which are unchanged from other."""
if not self.hash_info:
return
if self.hash_info.isdir:
return self._merge_dir_version_meta(other)
if self.hash_info != other.hash_info:
return
self.meta = other.meta
def _merge_dir_version_meta(self, other: "Output"):
from dvc_data.hashfile.tree import update_meta
if not self.obj or not other.hash_info.isdir:
return
other_obj = other.obj if other.obj is not None else other.get_obj()
assert isinstance(self.obj, Tree)
assert isinstance(other_obj, Tree)
updated = update_meta(self.obj, other_obj)
assert updated.hash_info == self.obj.hash_info
self.obj = updated
self.files = updated.as_list(with_meta=True)
META_SCHEMA = {
Meta.PARAM_SIZE: int,
Meta.PARAM_NFILES: int,
Meta.PARAM_ISEXEC: bool,
Meta.PARAM_VERSION_ID: str,
}
CLOUD_SCHEMA = All({str: {**META_SCHEMA, **CHECKSUMS_SCHEMA}}, Length(max=1))
ARTIFACT_SCHEMA = {
**CHECKSUMS_SCHEMA,
**META_SCHEMA,
Required(Output.PARAM_PATH): str,
Output.PARAM_PLOT: bool,
Output.PARAM_PERSIST: bool,
Output.PARAM_CHECKPOINT: bool,
Meta.PARAM_SIZE: int,
Meta.PARAM_NFILES: int,
Meta.PARAM_ISEXEC: bool,
Output.PARAM_CLOUD: CLOUD_SCHEMA,
}
DIR_FILES_SCHEMA: Dict[str, Any] = {
**CHECKSUMS_SCHEMA,
**META_SCHEMA,
Required(Tree.PARAM_RELPATH): str,
Output.PARAM_CLOUD: CLOUD_SCHEMA,
}
SCHEMA = {
**ARTIFACT_SCHEMA,
**ANNOTATION_SCHEMA,
Output.PARAM_CACHE: bool,
Output.PARAM_METRIC: Output.METRIC_SCHEMA,
Output.PARAM_DESC: str,
Output.PARAM_REMOTE: str,
Output.PARAM_PUSH: bool,
Output.PARAM_FILES: [DIR_FILES_SCHEMA],
}
+22 -19
View File
@@ -14,10 +14,11 @@ from typing import (
Union,
)
from funcy import cached_property, collecting, first, isa, join, reraise
from funcy import collecting, first, isa, join, reraise
from dvc.exceptions import DvcException
from dvc.parsing.interpolate import ParseError
from dvc.utils.objects import cached_property
from .context import (
Context,
@@ -25,7 +26,6 @@ from .context import (
KeyNotInContext,
MergeError,
Node,
SeqOrMap,
VarsAlreadyLoaded,
)
from .interpolate import (
@@ -36,9 +36,13 @@ from .interpolate import (
)
if TYPE_CHECKING:
from typing_extensions import NoReturn
from typing import NoReturn
from dvc.repo import Repo
from dvc.types import DictStrAny
from .context import SeqOrMap
logger = logging.getLogger(__name__)
@@ -52,7 +56,6 @@ DO_KWD = "do"
DEFAULT_PARAMS_FILE = "params.yaml"
JOIN = "@"
DictStr = Dict[str, Any]
class ResolveError(DvcException):
@@ -81,7 +84,7 @@ def format_and_raise(exc: Exception, msg: str, path: str) -> "NoReturn":
def _reraise_err(
exc_cls: Type[Exception], *args, from_exc: Exception = None
exc_cls: Type[Exception], *args, from_exc: Optional[Exception] = None
) -> "NoReturn":
err = exc_cls(*args)
if from_exc and logger.isEnabledFor(logging.DEBUG):
@@ -90,7 +93,7 @@ def _reraise_err(
def check_syntax_errors(
definition: DictStr, name: str, path: str, where: str = "stages"
definition: "DictStrAny", name: str, path: str, where: str = "stages"
):
for key, d in definition.items():
try:
@@ -109,8 +112,8 @@ def split_foreach_name(name: str) -> Tuple[str, Optional[str]]:
return group, first(keys)
def check_interpolations(data: DictStr, where: str, path: str):
def func(s: DictStr) -> None:
def check_interpolations(data: "DictStrAny", where: str, path: str):
def func(s: "DictStrAny") -> None:
if is_interpolated_string(s):
raise ResolveError(
_format_preamble(f"'{where}'", path)
@@ -124,7 +127,7 @@ Definition = Union["ForeachDefinition", "EntryDefinition"]
def make_definition(
resolver: "DataResolver", name: str, definition: DictStr, **kwargs
resolver: "DataResolver", name: str, definition: "DictStrAny", **kwargs
) -> Definition:
args = resolver, resolver.context, name, definition
if FOREACH_KWD in definition:
@@ -193,7 +196,7 @@ class DataResolver:
def has_key(self, key: str):
return self._has_group_and_key(*split_foreach_name(key))
def _has_group_and_key(self, group: str, key: str = None):
def _has_group_and_key(self, group: str, key: Optional[str] = None):
try:
definition = self.definitions[group]
except KeyError:
@@ -223,7 +226,7 @@ class EntryDefinition:
resolver: DataResolver,
context: Context,
name: str,
definition: DictStr,
definition: "DictStrAny",
where: str = STAGES_KWD,
):
self.resolver = resolver
@@ -235,7 +238,7 @@ class EntryDefinition:
self.where = where
def _resolve_wdir(
self, context: Context, name: str, wdir: str = None
self, context: Context, name: str, wdir: Optional[str] = None
) -> str:
if not wdir:
return self.wdir
@@ -252,7 +255,7 @@ class EntryDefinition:
except ContextError as exc:
format_and_raise(exc, f"stage '{self.name}'", self.relpath)
def resolve_stage(self, skip_checks: bool = False) -> DictStr:
def resolve_stage(self, skip_checks: bool = False) -> "DictStrAny":
context = self.context
name = self.name
if not skip_checks:
@@ -300,7 +303,7 @@ class EntryDefinition:
def _resolve(
self, context: "Context", value: Any, key: str, skip_checks: bool
) -> DictStr:
) -> "DictStrAny":
try:
return context.resolve(
value, skip_interpolation_checks=skip_checks, key=key
@@ -322,7 +325,7 @@ class ForeachDefinition:
resolver: DataResolver,
context: Context,
name: str,
definition: DictStr,
definition: "DictStrAny",
where: str = STAGES_KWD,
):
self.resolver = resolver
@@ -347,7 +350,7 @@ class ForeachDefinition:
def resolved_iterable(self):
return self._resolve_foreach_data()
def _resolve_foreach_data(self) -> SeqOrMap:
def _resolve_foreach_data(self) -> "SeqOrMap":
try:
iterable = self.context.resolve(self.foreach_data, unwrap=False)
except (ContextError, ParseError) as exc:
@@ -415,13 +418,13 @@ class ForeachDefinition:
def _generate_name(self, key: str) -> str:
return f"{self.name}{JOIN}{key}"
def resolve_all(self) -> DictStr:
def resolve_all(self) -> "DictStrAny":
return join(map(self.resolve_one, self.normalized_iterable))
def resolve_one(self, key: str) -> DictStr:
def resolve_one(self, key: str) -> "DictStrAny":
return self._each_iter(key)
def _each_iter(self, key: str) -> DictStr:
def _each_iter(self, key: str) -> "DictStrAny":
err_message = f"Could not find '{key}' in foreach group '{self.name}'"
with reraise(KeyError, EntryNotFound(err_message)):
value = self.normalized_iterable[key]
+22 -17
View File
@@ -159,7 +159,7 @@ class Value(Node):
PRIMITIVES = (int, float, str, bytes, bool)
class Container(Node, ABC):
class Container(Node, ABC): # noqa: B024
meta: Meta
data: Union[list, dict]
_key_transform = staticmethod(identity)
@@ -172,7 +172,7 @@ class Container(Node, ABC):
return self._convert_with_meta(value, meta)
@staticmethod
def _convert_with_meta(value, meta: Meta = None):
def _convert_with_meta(value, meta: Optional[Meta] = None):
if value is None or isinstance(value, PRIMITIVES):
assert meta
return Value(value, meta=meta)
@@ -228,7 +228,7 @@ class Container(Node, ABC):
rem = rems[0]
if not isinstance(d, Container):
raise ValueError(
raise ValueError( # noqa: TRY004
f"{index} is a primitive value, cannot get '{rem}'"
)
return d.select(rem)
@@ -240,7 +240,7 @@ class Container(Node, ABC):
class CtxList(Container, MutableSequence):
_key_transform = staticmethod(int)
def __init__(self, values: Sequence, meta: Meta = None):
def __init__(self, values: Sequence, meta: Optional[Meta] = None):
super().__init__(meta=meta)
self.data: list = []
self.extend(values)
@@ -263,7 +263,12 @@ class CtxList(Container, MutableSequence):
class CtxDict(Container, MutableMapping):
def __init__(self, mapping: Mapping = None, meta: Meta = None, **kwargs):
def __init__(
self,
mapping: Optional[Mapping] = None,
meta: Optional[Meta] = None,
**kwargs,
):
super().__init__(meta=meta)
self.data: dict = {}
@@ -301,7 +306,7 @@ class Context(CtxDict):
"""
super().__init__(*args, **kwargs)
self._track = False
self._tracked_data = defaultdict(dict)
self._tracked_data: Dict[str, Dict] = defaultdict(dict)
self.imports = {}
self._reserved_keys = {}
@@ -354,7 +359,7 @@ class Context(CtxDict):
@classmethod
def load_from(
cls, fs, path: str, select_keys: List[str] = None
cls, fs, path: str, select_keys: Optional[List[str]] = None
) -> "Context":
from dvc.utils.serialize import load_path
@@ -414,27 +419,27 @@ class Context(CtxDict):
self.imports[path].extend(cp)
def check_loaded(self, path, item, keys):
if not keys and isinstance(self.imports[path], list):
imported = self.imports[path]
if not keys and isinstance(imported, list):
raise VarsAlreadyLoaded(
f"cannot load '{item}' as it's partially loaded already"
)
elif keys and self.imports[path] is None:
if keys and imported is None:
raise VarsAlreadyLoaded(
f"cannot partially load '{item}' as it's already loaded."
)
elif isinstance(self.imports[path], list):
if not set(keys).isdisjoint(set(self.imports[path])):
raise VarsAlreadyLoaded(
f"cannot load '{item}' as it's partially loaded already"
)
if isinstance(imported, list) and set(keys) & set(imported):
raise VarsAlreadyLoaded(
f"cannot load '{item}' as it's partially loaded already"
)
def load_from_vars(
self,
fs,
vars_: List,
wdir: str,
stage_name: str = None,
default: str = None,
stage_name: Optional[str] = None,
default: Optional[str] = None,
):
if default:
to_import = fs.path.join(wdir, default)
@@ -482,7 +487,7 @@ class Context(CtxDict):
try:
yield
finally:
for key in new.keys():
for key in new:
self._reserved_keys.pop(key)
@contextmanager
+18 -9
View File
@@ -1,3 +1,4 @@
import os
import re
import typing
from collections.abc import Iterable, Mapping
@@ -9,10 +10,9 @@ from dvc.exceptions import DvcException
from dvc.utils.flatten import flatten
if typing.TYPE_CHECKING:
from typing import List, Match
from typing import List, Match, NoReturn
from pyparsing import ParseException
from typing_extensions import NoReturn
from .context import Context
@@ -71,6 +71,16 @@ def embrace(s: str):
return BRACE_OPEN + s + BRACE_CLOSE
def escape_str(value):
if os.name == "nt":
from subprocess import list2cmdline
return list2cmdline([value])
from shlex import quote
return quote(value)
@singledispatch
def to_str(obj) -> str:
return str(obj)
@@ -82,7 +92,7 @@ def _(obj: bool):
@to_str.register(dict)
def _(obj: dict):
def _(obj: dict): # noqa: C901
from dvc.config import Config
config = Config().get("parsing", {})
@@ -98,12 +108,12 @@ def _(obj: dict):
result += f"--no-{k} "
elif isinstance(v, str):
result += f"--{k} '{v}' "
result += f"--{k} {escape_str(v)} "
elif isinstance(v, Iterable):
for n, i in enumerate(v):
if isinstance(i, str):
i = f"'{i}'"
i = escape_str(i)
elif isinstance(i, Iterable):
raise ParseError(
f"Cannot interpolate nested iterable in '{k}'"
@@ -176,7 +186,7 @@ def parse_expr(s: str):
result = get_parser().parseString(s, parseAll=True)
except ParseException as exc:
format_and_raise_parse_error(exc)
raise AssertionError("unreachable")
raise AssertionError("unreachable") # noqa: B904
joined = result.asList()
assert len(joined) == 1
@@ -194,9 +204,8 @@ def validate_value(value, key):
not_primitive = value is not None and not isinstance(value, PRIMITIVES)
not_foreach = key is not None and "foreach" not in key
if not_primitive and not_foreach:
if isinstance(value, dict):
if key == "cmd":
return True
if isinstance(value, dict) and key == "cmd":
return True
raise ParseError(
f"Cannot interpolate data of type '{type(value).__name__}'"
)
+1 -2
View File
@@ -50,8 +50,7 @@ def change_rule(rule, rel):
rule = f"!/{rel}{rule}"
else:
rule = f"/{rel}{rule}"
rule = normalize_file(rule)
return rule
return normalize_file(rule)
def _change_dirname(dirname, pattern_list, new_dirname):

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