Compare commits

...
315 Commits
Author SHA1 Message Date
dependabot[bot] 795fe24a5f build(deps-dev): Bump types-requests from 2.27.9 to 2.27.10
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.9 to 2.27.10.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-16 12:02:54 +08:00
karajan1001 d1f7cf4540 exp show: show exp when checkpoint is running(#7329)
fix: #7329
In a previous PR, we introduce a bug which use `HEAD` as the workspace
commit, while if there is a checkpoint is running, the `HEAD` is
actually changing all the time, we should use
`refs/exps/exec/EXEC_BASELINE` instead.

1. add `fix_exp_head` transfer in `dvc/scm.py::resolve_rev`
2. remove fix_exp_head from `diff`, `metrics diff`, `params diff` and
   `plots diff`
3. modify a functional test to simulate the running conditions
2022-02-15 21:17:42 +08:00
dependabot[bot] d5b809d705 build(deps-dev): Bump types-toml from 0.10.3 to 0.10.4
Bumps [types-toml](https://github.com/python/typeshed) from 0.10.3 to 0.10.4.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-11 08:28:58 +05:45
Daniele Trifirò 4db9d19387 destroy: also remove dvcignore file 2022-02-10 16:16:12 +01:00
David de la Iglesia Castro 31a3461465 live: Update tests 2022-02-10 16:46:32 +05:45
dependabot[bot] e6d52ac594 build(deps-dev): Bump dvclive[image] from 0.5.0 to 0.5.1
Bumps [dvclive[image]](https://github.com/iterative/dvclive) from 0.5.0 to 0.5.1.
- [Release notes](https://github.com/iterative/dvclive/releases)
- [Commits](https://github.com/iterative/dvclive/compare/0.5.0...0.5.1)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-10 16:46:32 +05:45
dependabot[bot] a78d63cf58 build(deps-dev): Bump types-requests from 2.27.8 to 2.27.9
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.8 to 2.27.9.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-10 15:52:07 +08:00
David de la Iglesia Castro d89555cd31 exp show: handle renaming of metrics/params.
_extend_row did not considered partially empty `items`, which can occur when metris or params are renamed between commits.

Fixes #7070
2022-02-09 21:40:15 +01:00
Nikita Kodenko b6fa1178e7 tests: migrate unittest to pytest 2022-02-08 18:51:17 +09:00
Ruslan Kuprieiev e430693fc8 dvcfs: use paths relative to the repo root
Currently we use both absolute and relative (depend on cwd) paths, which is
terrible for an api, as their interpretation depends on external factors.
This PR makes dvcfs accept only paths relative to the repo root for regular
outputs and absolute paths for external outputs.

Pre-requisite for making repofs use same relative paths.
2022-02-07 20:12:33 +02:00
dependabot[bot] e60ab00f51 build(deps): Bump pyinstaller from 4.8 to 4.9
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 4.8 to 4.9.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/v4.9/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v4.8...v4.9)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-07 18:02:45 +05:45
Ruslan Kuprieiev cb584c6a53 dvcfs: detach from pipeline outputs
Introducing simple repo tree that combines all workspaces into one structure,
and allows us to detach data representation from pipelines.

Pre-requisite for dvc-data extraction and using relpaths in dvcfs/repofs.
2022-02-07 13:12:52 +02:00
Ruslan Kuprieiev 9b5ccb7554 Revert "ls: don't use outs"
This reverts commit 27b090709b.
2022-02-04 11:04:00 +02:00
Ruslan Kuprieiev 27b090709b ls: don't use outs
ls doesn't support subrepos, so we can simply use dvcfs in --dvc-only.
2022-02-03 23:59:29 +02:00
Ruslan Kuprieiev 48641fb41f repofs: don't use outs
Using outs directly breaks isolation and makes it really hard to understand
what's going on.

Redundant abspaths will go away in followups when moving to relpaths.
2022-02-03 23:33:37 +02:00
Ruslan Kuprieiev 8497edab93 dvcfs: use info() instead of metadata()
metadata was introduced before our migration to fsspec and no longer needed
now. This is a pre-requisite to using data index in dvcfs/repofs.

Note that a bunch of awkward flags like isdvc/isout/etc are still kept, and
will be removed in followups.
2022-02-03 22:45:10 +02:00
Saugat Pachhai dc13179759 Delete scripts/schema directory
https://github.com/iterative/dvcyaml-schema provides the canonical schema,
this is not used anywhere and is stale.
2022-02-01 20:35:07 +05:45
David de la Iglesia Castro 37cfb8e8af exp show: Sort deps columns.
Closes #7312
2022-02-01 15:23:26 +01:00
Paweł Redzyński 7bc14a5ddb plots: make templates generation explicit
Fixes: #6389
2022-02-01 15:09:25 +01:00
dependabot[bot] 22ebfd1e2f build(deps-dev): Bump pytest-mock from 3.6.1 to 3.7.0
Bumps [pytest-mock](https://github.com/pytest-dev/pytest-mock) from 3.6.1 to 3.7.0.
- [Release notes](https://github.com/pytest-dev/pytest-mock/releases)
- [Changelog](https://github.com/pytest-dev/pytest-mock/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-mock/compare/v3.6.1...v3.7.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-01 19:53:26 +05:45
pre-commit-ci[bot] d213ccb686 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2022-02-01 19:53:13 +05:45
pre-commit-ci[bot] 0ce1f982b0 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/ambv/black: 21.12b0 → 22.1.0](https://github.com/ambv/black/compare/21.12b0...22.1.0)
2022-02-01 19:53:13 +05:45
dependabot[bot] a4b3da6acc build(deps-dev): Bump types-requests from 2.27.7 to 2.27.8
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.7 to 2.27.8.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-01 19:24:56 +05:45
dependabot[bot] b58a01cfd5 build(deps-dev): Bump dvclive[image] from 0.4.6 to 0.5.0
Bumps [dvclive[image]](https://github.com/iterative/dvclive) from 0.4.6 to 0.5.0.
- [Release notes](https://github.com/iterative/dvclive/releases)
- [Commits](https://github.com/iterative/dvclive/compare/0.4.6...0.5.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-01 14:24:23 +01:00
Daniele Trifirò 0562b4fd5f tests: update test_exceptions for rich 11.1.0 behaviour 2022-02-01 13:20:07 +01:00
tapadipti 95731e9a37 Doc URL correction for GitAuthError 2022-01-31 14:50:37 +05:45
Ruslan Kuprieiev aca6d229da output: move isexec to meta
With our staging process automatically capturing file metadata, we no longer
need to set isexec ourselves.

In followup PRs we will also use meta during checkout, so that we won't need
to use Output.set_exec by hand.
2022-01-27 18:55:32 +02:00
dependabot[bot] 60e502d361 build(deps-dev): Bump google-cloud-storage from 2.0.0 to 2.1.0
Bumps [google-cloud-storage](https://github.com/googleapis/python-storage) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/googleapis/python-storage/releases)
- [Changelog](https://github.com/googleapis/python-storage/blob/main/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-storage/compare/v2.0.0...v2.1.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-27 14:48:46 +05:45
David de la Iglesia Castro 8c2f8095da debug: Remove test_viztracer_internal 2022-01-27 09:36:06 +05:45
David de la Iglesia Castro 5e80fcce4f exp show: Include outs in --json output. 2022-01-26 18:45:48 +01:00
David de la Iglesia Castro 1a1e28282d exp show: Include deps columns.
Use `repo.index.deps` to collect dependencies associated with each experiment.
2022-01-26 18:45:48 +01:00
Daniele Trifirò e3b97cb3a7 ignore: move debug statetement to the proper location 2022-01-26 18:44:01 +02:00
David de la Iglesia Castro d882e21b9b debug: Remove viztracer-depth default 2022-01-25 20:28:41 +05:45
David de la Iglesia Castro 3276c76a02 tests: Add test_debug 2022-01-25 20:28:41 +05:45
Saugat Pachhai 758dc43575 Update dvc/_debug.py 2022-01-25 20:28:41 +05:45
David de la Iglesia Castro f1dc9e50a7 debug: Add support for viztracer profiler. 2022-01-25 20:28:41 +05:45
Saugat Pachhai (सौगात) 2f15f30b0e exp run: do not log message twice on workspace executor
Fixes #7276.
2022-01-25 19:01:48 +05:45
Peter Rowlands 47d10c4a8b utils: add decorator to wrap diskcache pickle errors 2022-01-25 16:21:53 +09:00
David de la Iglesia Castro 822b501069 add: Update help of --jobs 2022-01-24 21:06:47 +01:00
Saugat Pachhai (सौगात) 943481f0a3 exp init: handle exception properly on missing parameters file 2022-01-24 17:43:29 +05:45
Jeff Hale b30385885b Fix 3 README links so that they render in GitHub 2022-01-24 15:55:12 +09:00
David de la Iglesia Castro d4861d535a TabularData: Simplify drop internals.
Update `subset` type hint.
2022-01-22 09:41:29 +08:00
dberenbaum 646097a735 update exp init help text 2022-01-21 11:07:32 +05:45
David de la Iglesia Castro 43953ace53 exp show: Add --drop and --keep args
Removed `--include-metrics` / `--include-params` and
`--exclude-metrics` / `--exclude-params`.

Removed `--no-timestamp` . Can be done by `--drop Created`.

`--drop` and `--keep` operate directly on the table columns.

`--keep` does not perform any filtering. It's only used
to specify columns to keep despite the other filtering.

For example `--only-changed --keep foo` will prevent
`foo` from being removed by `--only-changed`.

Another example, `--drop train.* --keep train.dropout`
will remove all columns matching `train.*` except for
`train.dropout`.

Closes #7079
Closes #7080
Pre-requisite for #6434
2022-01-20 03:28:33 +01:00
David de la Iglesia Castro 255e6a1aec TabularData: Support protected`columns.
TabularData: Add subset arg to dropna and drop_duplicates.

Closes #7083
2022-01-20 03:28:33 +01:00
Saugat Pachhai (सौगात) 73fee513b9 cli: improve startup speed
fsspec callbacks were making us lose 35ms (~207 ms vs 170ms now).
These are now extracted from dvc.progress to dvc.fs._callback.
2022-01-19 20:38:19 +02:00
Saugat Pachhai (सौगात) b0f65cbd17 tests: increase timeout for exp init tests 2022-01-19 20:26:41 +05:45
Saugat Pachhai (सौगात) 672f079cc6 tests: clean repositories before each test runs 2022-01-19 20:21:48 +05:45
Saugat Pachhai (सौगात) 446516d788 refactor: rename dvc.command to dvc.commands 2022-01-19 19:41:40 +05:45
Saugat Pachhai (सौगात) 1b3e1251e6 cli refactor: move dvc.command.base to dvc.cli.command 2022-01-19 19:41:40 +05:45
Saugat Pachhai (सौगात) 4268b5a339 cli: don't inherit DvcParserError from DvcException, move to dvc.cli 2022-01-19 19:41:40 +05:45
Saugat Pachhai (सौगात) f5baf990cf cli: refactor into a module
By splitting dvc.cli into a module, we'll be able to share cli-related
utilities, split parser-related stuffs into smaller parts, improve
reusability and improve API for command extensions in the future.
2022-01-19 12:22:07 +02:00
dependabot[bot] 69ff760e4b build(deps-dev): Bump pytest-timeout from 2.0.2 to 2.1.0
Bumps [pytest-timeout](https://github.com/pytest-dev/pytest-timeout) from 2.0.2 to 2.1.0.
- [Release notes](https://github.com/pytest-dev/pytest-timeout/releases)
- [Commits](https://github.com/pytest-dev/pytest-timeout/compare/2.0.2...2.1.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-19 10:57:17 +05:45
Saugat Pachhai (सौगात) 4ba504be3e repro/exp run: display stage instead of filepath on failure
Before it used to say 'failed to reproduce dvc.yaml', which is now changed
to print 'failed to reproduce train' or something similar.

For stages that are in dvc.yaml in the current working directory, it will
just print the stage names. For stages in other dvc.yaml files, it'll print

in <relative_path>:<stage_name>.
2022-01-19 06:58:40 +05:45
Ruslan Kuprieiev 377b0bc689 find_root: unify error message
We used to use that message for special external repo cases, but we don't
need it anymore.

Fixes #7281

Leftover from #7214
2022-01-19 01:39:48 +02:00
Matt Seddon 89f2d42df8 plots: update linear template so colors can be externally overwritten 2022-01-18 12:42:17 +01:00
karajan1001 57263f15fa Pull: accept multiple as input(#7196)
fix: #7196

New feature: now `dvc exp pull` can accept a list of experiment names as input.

2. add support to list input for the `exp pull`
3. add function tests for the multi exp pull
4. add unit test for the arg parse
2022-01-18 17:31:55 +08:00
karajan1001 9bb6494e55 Push: accept multiple as input(#7196)
fix: #7196

New feature: now `dvc exp push` can accept a list of experiment names as input.

1. refactor of experiment push
2. add support to list input for the `exp push`
3. add function tests for the multi exp push
4. add unit test for the arg parse
2022-01-18 17:31:55 +08:00
Ruslan Kuprieiev 13238e9716 objects: fix imports 2022-01-15 21:39:39 +02:00
Ruslan Kuprieiev 85181e396f objects: fix empty line 2022-01-15 21:20:09 +02:00
Ruslan Kuprieiev 4c3c492b90 objects: don't forget __init__.py 2022-01-15 21:16:20 +02:00
dependabot[bot] 474812195e build(deps-dev): Bump wsgidav from 3.1.1 to 4.0.1
Bumps [wsgidav](https://github.com/mar10/wsgidav) from 3.1.1 to 4.0.1.
- [Release notes](https://github.com/mar10/wsgidav/releases)
- [Changelog](https://github.com/mar10/wsgidav/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mar10/wsgidav/compare/v3.1.1...v4.0.1)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-14 09:12:16 +05:45
Saugat Pachhai c83612be71 deps-dev: don't specify pygments explicitly
pygments is a transitive dependency for us, from `rich` and here it was for `collective.checkdocs`.
2022-01-14 09:11:20 +05:45
dependabot[bot] 161e7892ba build(deps-dev): Bump google-cloud-storage from 1.43.0 to 2.0.0
Bumps [google-cloud-storage](https://github.com/googleapis/python-storage) from 1.43.0 to 2.0.0.
- [Release notes](https://github.com/googleapis/python-storage/releases)
- [Changelog](https://github.com/googleapis/python-storage/blob/main/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-storage/compare/v1.43.0...v2.0.0)

---
updated-dependencies:
- dependency-name: google-cloud-storage
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-14 09:02:38 +05:45
dependabot[bot] 0a0e3c3f9c build(deps-dev): Bump types-requests from 2.27.5 to 2.27.7
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.5 to 2.27.7.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-14 09:02:25 +05:45
dependabot[bot] ac2feb2340 build(deps): Bump pyinstaller from 4.7 to 4.8
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 4.7 to 4.8.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES-3.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v4.7...v4.8)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-13 17:00:03 +09:00
Peter Rowlands dd5d999644 exp: add exp run --machine flag 2022-01-13 16:34:20 +09:00
Peter Rowlands 3c7695b39d exp: add SSHExecutor.reproduce via exp exec-run 2022-01-13 16:34:20 +09:00
Peter Rowlands 367e5d1429 exp: add exec-run plumbing command to run exp from ExecutorInfo 2022-01-13 16:34:20 +09:00
Peter Rowlands fb16cd06e3 machine: update behavior for exp ssh executors
- log startup script finished message
- add setup_script option for runtime env setup
2022-01-13 16:34:20 +09:00
Peter Rowlands 39eee9b5a6 exp: implement git + DVC cache collection for ssh executors 2022-01-13 16:34:20 +09:00
Peter Rowlands 84da78a62b exp: initial SSHExecutorManager implementation 2022-01-13 16:34:20 +09:00
Peter Rowlands 4b9d39c901 push/fetch: support push/fetch to specific ODB
- prerequisite for remote execution
- allow explicitly pushing imports which would normally be skipped
  (internal-use only, does not apply to `dvc push`)
2022-01-13 16:34:20 +09:00
David de la Iglesia Castro 5b8bf52eab plots: Fix --json requiring -o.
`-o` was being required even when renderers don't need output path.
2022-01-12 18:00:08 +01:00
karajan1001andPeter Rowlands b673ac24d0 exp show: add --rev flag (#7152)
fix: #7152
1. Add `--rev` flag to `dvc exp show`.
2. Add -1 support for `--num` flag`.
3. Extract revision found logic to `utils`.
4. Brancher use this revision found logic.
5. Update command unit test
6. Add a util unit test.

Co-authored-by: Peter Rowlands (변기호) <[email protected]>
2022-01-11 19:01:34 +08:00
Saugat Pachhai (सौगात) 87abeaddc2 rich-table: fix add_column args/kwargs
Rich added `vertical` kwarg in `add_column`, which linter
is now complaining (as it should). But this may keep on breaking
when rich adds/removes args/kwargs in the future.

So, this commit uses (*args, **kwargs) instead of specifying particular
parameters explicitly. This also sets `collapse` directly to the
`rich.table.Column` rather than extending through our one `Column`
implementation.
2022-01-11 11:28:54 +05:45
dependabot[bot] a3dece8236 build(deps-dev): Bump types-tabulate from 0.8.4 to 0.8.5
Bumps [types-tabulate](https://github.com/python/typeshed) from 0.8.4 to 0.8.5.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-11 09:19:36 +05:45
dependabot[bot] 221adf246b build(deps-dev): Bump types-toml from 0.10.1 to 0.10.3
Bumps [types-toml](https://github.com/python/typeshed) from 0.10.1 to 0.10.3.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-11 09:10:44 +05:45
dependabot[bot] 8681086bfb build(deps-dev): Bump mypy from 0.930 to 0.931
Bumps [mypy](https://github.com/python/mypy) from 0.930 to 0.931.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.930...v0.931)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-11 09:02:39 +05:45
dependabot[bot] 6c3b1370a3 build(deps-dev): Bump types-requests from 2.26.3 to 2.27.5
Bumps [types-requests](https://github.com/python/typeshed) from 2.26.3 to 2.27.5.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-11 08:36:12 +05:45
Ruslan Kuprieiev 9a01d98876 odb: move gc into a separate function 2022-01-11 01:52:34 +02:00
Ruslan Kuprieiev e190433249 lint: add new ignores
mypy:

```
dvc/utils/table.py: note: In member "add_column" of class "Table":
dvc/utils/table.py:24:5: error: Argument 7 of "add_column" is incompatible with
supertype "Table"; supertype defines the argument type as
"Union[Literal['top'], Literal['middle'], Literal['bottom']]"  [override]
        def add_column(  # pylint: disable=arguments-differ
        ^
dvc/utils/table.py:24:5: note: This violates the Liskov substitution principle
dvc/utils/table.py:24:5: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
dvc/utils/table.py:24:5: error: Argument 8 of "add_column" is incompatible with
supertype "Table"; supertype defines the argument type as
"Union[Literal['fold'], Literal['crop'], Literal['ellipsis'], Literal['ignore']]"
 [override]
        def add_column(  # pylint: disable=arguments-differ
        ^
dvc/utils/table.py:24:5: error: Argument 12 of "add_column" is incompatible
with supertype "Table"; supertype defines the argument type as "Optional[int]"
[override]
        def add_column(  # pylint: disable=arguments-differ
        ^
Found 3 errors in 1 file (checked 129 source files)
dvc/ui/table.py:87: note: In module imported here,
dvc/compare.py:25: note: ... from here:
dvc/utils/table.py: note: In member "add_column" of class "Table":
dvc/utils/table.py:24:5: error: Argument 7 of "add_column" is incompatible with
supertype "Table"; supertype defines the argument type as
"Union[Literal['top'], Literal['middle'], Literal['bottom']]"  [override]
        def add_column(  # pylint: disable=arguments-differ
        ^
dvc/utils/table.py:24:5: note: This violates the Liskov substitution principle
dvc/utils/table.py:24:5: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
dvc/utils/table.py:24:5: error: Argument 8 of "add_column" is incompatible with
supertype "Table"; supertype defines the argument type as
"Union[Literal['fold'], Literal['crop'], Literal['ellipsis'], Literal['ignore']]"
 [override]
        def add_column(  # pylint: disable=arguments-differ
        ^
dvc/utils/table.py:24:5: error: Argument 12 of "add_column" is incompatible
with supertype "Table"; supertype defines the argument type as "Optional[int]"
[override]
        def add_column(  # pylint: disable=arguments-differ
        ^
Found 3 errors in 1 file (checked 128 source files)
```

pylint:

```
************* Module dvc.utils.table
dvc/utils/table.py:24:4: W0237: Parameter 'vertical' has been renamed to 'overflow' in overridden 'Table.add_column' method (arguments-renamed)
dvc/utils/table.py:24:4: W0237: Parameter 'overflow' has been renamed to 'width' in overridden 'Table.add_column' method (arguments-renamed)
dvc/utils/table.py:24:4: W0237: Parameter 'width' has been renamed to 'min_width' in overridden 'Table.add_column' method (arguments-renamed)
dvc/utils/table.py:24:4: W0237: Parameter 'min_width' has been renamed to 'max_width' in overridden 'Table.add_column' method (arguments-renamed)
dvc/utils/table.py:24:4: W0237: Parameter 'max_width' has been renamed to 'ratio' in overridden 'Table.add_column' method (arguments-renamed)
dvc/utils/table.py:24:4: W0237: Parameter 'ratio' has been renamed to 'no_wrap' in overridden 'Table.add_column' method (arguments-renamed)
dvc/utils/table.py:24:4: W0237: Parameter 'no_wrap' has been renamed to 'collapse' in overridden 'Table.add_column' method (arguments-renamed)
```
2022-01-11 00:47:38 +02:00
Ruslan Kuprieiev f64018b75e dvc: separate data and objects
Initial simple separation to get the process of extracting dvc-objects
and dvc-data packages out of dvc.

dvc-objects is for:
 * defining base Object (aka HashFile) and ObjectDB
 * providing tools to work with those objects (check/transfer/etc)
 * etc

dvc-data is for:
 * defining higher level objects (e.g. Tree)
 * bulding objects from the workspace (aka staging)
 * projecting objects back into the workspace (aka checkout)
 * comparing Tree objects (aka diff)
 * defining misc object databases (e.g LocalObjectDB)
 * higher-level transfer (e.g. with Tree indexes)
 * etc

For example, the objects we use in run-cache are based on dvc-objects
and not on dvc-data, as they have absolutely nothing to do with data
management.

There are still some mixups in the code that will be addressed in the
followups.
2022-01-11 00:47:38 +02:00
Gavin Li 92f714e5ca fs: test_links: create destination directory first
`dvc.fs.utils.test_links()` fails with a CacheLinkError if the target `to_path` does not already exist, as is the case when "abc.dvc" exists (referring to a directory object) and we try to run "dvc pull abc/def". To avoid this issue, fully create `to_path` before attempting to create a link in it.
2022-01-08 13:56:51 +02:00
Jon Burdo 634246f81c add tests for Path.parts 2022-01-07 13:26:45 +02:00
Jon Burdo 05b5e9dc29 handle paths ending with sep in dvc.fs.path.Path.parts 2022-01-07 13:26:45 +02:00
Paweł Redzyński a3144482ba render: vega: data processing outside of renderers
Fixes: #6943
2022-01-07 10:39:12 +01:00
Amrit GhimireandSaugat Pachhai 44a1146915 test: fix test failure in case of different default branch (#6857)
* test: fix test failure in case of different default branch

When the git config has set global `init.defaultBranch` different than master,
a few test cases were failing.
Currently, the test case is assuming the default branch to be set as
master.

This commit will introduce **initial_branch** option to git init call
which will ensure *master* branch is used as default for out test cases.

Set default git branch to main for test CI

Since main is going to be new default branch, it will let the test case
handle the scenario where default branch is set other than master.

Introduce isolation for git configuration

Co-authored-by: Saugat Pachhai (सौगात) <[email protected]>
2022-01-07 11:13:04 +05:45
Saugat Pachhai (सौगात) ec3600eb8e output: don't raise RemoteCacheRequiredError on instantiation, make it lazy 2022-01-06 22:29:49 +02:00
Paweł Redzyński fe4fefd880 ls: handle broken symlink
Fixes: #7120
2022-01-06 22:12:17 +02:00
karajan1001andPeter Rowlands b6a2402d27 exp remove: refactor exp utils (#7196)
fix: #7196

1. Refactor exp_ref_by_name in `dvc/repo/experiments/utils.py`.

In previous the exp_ref_by_name walk throught all of the references to
match only one exp name. This makes multi exp name seeking to be a job
of `O(mn)` complexity, Walking though the exp ref and return all of the
exp ref with greatly reduce this complexity to `O(n)`

Co-authored-by: Peter Rowlands (변기호) <[email protected]>
2022-01-06 12:20:29 +08:00
Saugat Pachhai d90fe54df4 tests: remove tests.unit.scm
I forgot to remove it before during #6836.
2022-01-05 20:36:29 +05:45
Peter Rowlands 9e590edb70 odb: pin index pickle protocol version 2022-01-04 20:22:08 +02:00
karajan1001 6bb4889022 ui: remove None in CellT type
From https://github.com/iterative/dvc/pull/6893/files#r777784748
Table rendering does not support `None` for now, and we will change it
back after (#7167).
2022-01-04 15:41:18 +08:00
David de la Iglesia Castro 0a4958bafd TabularData: Decouple pcp from render. 2022-01-03 16:30:54 +01:00
David de la Iglesia Castro 5a9f861921 exp show: Rename html to pcp
Closes #7165
2022-01-03 16:30:54 +01:00
David de la Iglesia Castro fc1ab7d3a1 Update show.py 2022-01-03 16:14:37 +01:00
David de la Iglesia Castro 760c8fceb3 plots: Add auto_open hint 2022-01-03 16:07:23 +01:00
David de la Iglesia Castro d8e2c14927 config: Add plots.auto_open.
Option for enabling automatically open webbrowser with generated html.
Defaults False.

Closes #6091
2022-01-03 16:07:23 +01:00
dependabot[bot] a73accd071 build(deps-dev): Bump filelock from 3.4.0 to 3.4.2
Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.4.0 to 3.4.2.
- [Release notes](https://github.com/tox-dev/py-filelock/releases)
- [Changelog](https://github.com/tox-dev/py-filelock/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/py-filelock/compare/3.4.0...3.4.2)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-03 20:04:58 +05:45
dependabot[bot] 69ac43f131 build(deps-dev): Bump pylint-plugin-utils from 0.6 to 0.7
Bumps [pylint-plugin-utils](https://github.com/PyCQA/pylint-plugin-utils) from 0.6 to 0.7.
- [Release notes](https://github.com/PyCQA/pylint-plugin-utils/releases)
- [Commits](https://github.com/PyCQA/pylint-plugin-utils/compare/0.6...0.7)

---
updated-dependencies:
- dependency-name: pylint-plugin-utils
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-03 17:36:22 +05:45
Alexander Schepanovski 9ee428ef17 Fix memory leak in Index.build_graph()
`@memoize()` memory lives as long as decorated function lives, which is
permanent in this case. Since `self` is referenced as a memory key no
`Index` will ever be freed.

Changed to use `@cached_property` whose cache lives as long as the
parent instance.
2022-01-03 13:41:10 +02:00
Peter Rowlands 0e0e1725e6 exp: handle non-exp refs in get_exact_name() 2022-01-03 15:02:19 +05:45
Saugat Pachhai (सौगात) a1609cfb10 exp: raise InvalidExpRefError properly
Before it was just left instantiated, but was not raised.
2022-01-03 15:02:19 +05:45
David de la Iglesia Castro 175ffaeb22 exp show: Remove alternating background 2022-01-03 16:38:39 +09:00
dependabot[bot] 025e15d500 build(deps-dev): Bump types-tabulate from 0.8.3 to 0.8.4
Bumps [types-tabulate](https://github.com/python/typeshed) from 0.8.3 to 0.8.4.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-03 10:38:08 +05:45
dependabot[bot] 52a234c5b8 build(deps-dev): Bump pygments from 2.10.0 to 2.11.1
Bumps [pygments](https://github.com/pygments/pygments) from 2.10.0 to 2.11.1.
- [Release notes](https://github.com/pygments/pygments/releases)
- [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES)
- [Commits](https://github.com/pygments/pygments/compare/2.10.0...2.11.1)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-03 10:09:53 +05:45
Ruslan Kuprieiev 8ec1af1cf4 config: remove useless os.path.join 2021-12-31 19:03:05 +02:00
Ruslan Kuprieiev 35d364a527 repo: use fs instead of scm
SCM is used for both reading and writing operations, but in the former case -
as filesystem and in the latter - as actual scm/git. So we should distinguish
those and use fs directly, which follows the overall philosophy of being able
to use Repo on any fs.

Keeping `rev=` option for `Repo` because it is a very convenient way to get
repo instance from local path but for particular branch/tag/etc.

Pre-requisite for git/dvc/repofs refactorings.
2021-12-31 02:44:21 +02:00
David de la Iglesia Castro 9a5614d326 TabularData: Add ignore_empty flag to drop_duplicates.
Configures whether to consider missing values as relevant or not.
2021-12-29 21:33:36 +01:00
David de la Iglesia Castro 12a737d905 TabularData: Fix drop_duplicates for rich_text NaN.
When filling missing values with `ui.rich_text` (i.e. in experiments show CMD), those values were not being correctly matched against `self._fill_value`)
2021-12-29 21:33:36 +01:00
Paweł Redzyński 51a6116b92 plots: dir: handle no data in cache/remote
Fixes: iterative/cse#4
2021-12-29 13:21:37 +01:00
dependabot[bot] 3566b1941c build(deps-dev): Bump types-requests from 2.26.2 to 2.26.3
Bumps [types-requests](https://github.com/python/typeshed) from 2.26.2 to 2.26.3.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-29 12:53:59 +09:00
pre-commit-ci[bot] ceeda6d307 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.0.1 → v4.1.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.0.1...v4.1.0)
2021-12-28 15:01:38 +08:00
karajan1001 98d7f785e9 exp show: remove a useless function
The function `_raise_error_if_all_disabled` is copyed wrongly to this
place during sub command refactoring. It used to be used in `exp gc`,
can be deleted from this function.
2021-12-27 20:35:07 +01:00
Ruslan Kuprieiev 04e93da0bd fs: remove unused RemoteCmdError 2021-12-23 19:02:44 +02:00
Ruslan Kuprieiev 72aaca4343 odb: move verify option to config
These options are part of the default config for each particular remote.
Plugins will define their default config on theirown, but for now we keep
those in one config schema.
2021-12-23 12:49:16 +02:00
dependabot[bot] 60cb8322d9 build(deps-dev): Bump mypy from 0.921 to 0.930
Bumps [mypy](https://github.com/python/mypy) from 0.921 to 0.930.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.921...v0.930)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-23 09:50:23 +05:45
dependabot[bot] b0b9a74207 build(deps-dev): Bump wheel from 0.37.0 to 0.37.1
Bumps [wheel](https://github.com/pypa/wheel) from 0.37.0 to 0.37.1.
- [Release notes](https://github.com/pypa/wheel/releases)
- [Changelog](https://github.com/pypa/wheel/blob/main/docs/news.rst)
- [Commits](https://github.com/pypa/wheel/compare/0.37.0...0.37.1)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-23 09:50:05 +05:45
Saugat Pachhai (सौगात) 32fcc73078 deps: explicitly specify pyarrow for hdfs 2021-12-22 16:45:12 +02:00
Saugat Pachhai (सौगात) e3596178d4 deps: move requests-kerberos to webhdfs_kerberos extras instead of webhdfs 2021-12-22 16:44:52 +02:00
Saugat Pachhai (सौगात) ba40d91de6 fs.hdfs: skip port if not available 2021-12-22 16:43:56 +02:00
dependabot[bot] 02cfbcfc32 build(deps-dev): Bump mypy from 0.920 to 0.921
Bumps [mypy](https://github.com/python/mypy) from 0.920 to 0.921.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.920...v0.921)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-22 12:45:02 +02:00
dependabot[bot] 96acb64fec build(deps): Bump scmrepo from 0.0.6 to 0.0.7
Bumps [scmrepo](https://github.com/iterative/scmrepo) from 0.0.6 to 0.0.7.
- [Release notes](https://github.com/iterative/scmrepo/releases)
- [Commits](https://github.com/iterative/scmrepo/compare/0.0.6...0.0.7)

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

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

tests: fix invalid set_ref in exp func test
2021-12-22 17:52:30 +09:00
David de la Iglesia Castro 1507a6ed99 Update benchmarks.yaml 2021-12-21 09:03:19 +05:45
Saugat Pachhai 6034f0e36a s3: remove explicit dependency on aiobotocore; simplify constraints
Hoping to fix `dvc[s3]` installations in pipenv. Pipenv's resolver is in loop, backtracking ruamel.yaml from 0.17.17 to 0.17.11 continuously. I think this has to do
more with `s3`'s dependency than `ruamel.yaml` as it only fails here. And, we are specifying dependency in 1.0.1 whereas aiobotocore has released 2.0 versions which is what is being installed, so it's redundant anyway. I am hopeful that this may help resolver with the simplification in constraints.
2021-12-20 14:32:42 +05:45
Saugat Pachhai 6df1ee87ab Specify bcrypt explicitly 2021-12-20 12:16:14 +05:45
dependabot[bot] a6263cffc8 build(deps-dev): Bump types-requests from 2.26.1 to 2.26.2
Bumps [types-requests](https://github.com/python/typeshed) from 2.26.1 to 2.26.2.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-20 10:37:39 +08:00
Paweł Redzyński 8ca3a7b684 fs: repo: raise proper FileNotFoundError in metadata
Related: #7120
2021-12-17 15:48:29 +02:00
David de la Iglesia Castro 4d5be46ab4 exp show --html: Handle fill_value.
Adds new argument `fill_value` to ParallelCoordinatesRenderer.

For scalar columns, prevent converting to categorical by explictly inserting None when value is missing (value == fill_value).

For categorical columns, replace `fill_value` with `"Missing"` and ensure it is the last item so it gets rendered on top of the column.
2021-12-17 14:45:12 +09:00
David de la Iglesia Castro 5ee82d4000 exp show: Add parallel coordinates plot.
Uses `TabularData.to_parallel_coordinates`.
Adds new arguments: `html`, `out`, `open`

Reuses `--sort-by` to define colorscale.

Closes #4455
2021-12-17 14:45:12 +09:00
David de la Iglesia Castro a5b7c5ea19 TabularData: Add to_parallel_coordinates.
Uses `ParallelCoordinatesRenderer` and `dvc.render.html.write`.

pre-requisite #4455
2021-12-17 14:45:12 +09:00
David de la Iglesia Castro 19b3c82698 dvc render: Add plotly parallel coords renderer
New renderer based on plotly. Not exposed to `dvc plots`.
Generate plotly datapoints from `TabularData`.

pre-requisite #4455
2021-12-17 14:45:12 +09:00
David de la Iglesia Castro 9bd60f62d7 exp show: Add alternating row colors and update border style.
Use `box.HORIZONTALS`.

rich_table: Support `box.HORIZONTALS`

Closes #7128
2021-12-17 14:31:53 +09:00
dependabot[bot] 970cbfa64e build(deps-dev): Bump mypy from 0.910 to 0.920
Bumps [mypy](https://github.com/python/mypy) from 0.910 to 0.920.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.910...v0.920)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-16 16:04:47 +09:00
Ruslan Kuprieiev 0bf56fe29b rename master -> main 2021-12-15 22:19:36 +02:00
Ruslan Kuprieiev 30d6839a5f diff: use relpaths
Regression from path_info removal.

Also fixes #7147
2021-12-15 17:27:38 +02:00
Paweł Redzyński e24de3a0f3 plots: introduce --json option
Fixes: #7073
2021-12-15 11:05:57 +01:00
Peter Rowlands 25ef812ecf exp: use process manager dirs for serialized executors
- partially refactor existing pidfile behavior into using proc manager
- old pidfiles are now json-serialized executor info files placed in
  proc manager subdirs (alongside proc manager pidfile/logs/etc)
- move multiprocessing based (attached) execution into separate
  execution path from future "detached" execution support
2021-12-15 15:55:32 +09:00
Peter Rowlands fa819b0b70 exp: refactor workspace repro into executor classes 2021-12-15 15:55:32 +09:00
Peter Rowlands 37f8010b42 exp: initial executor manager refactor
- introduce ExecutorManager classes
- move executor init + execution from experiments.__init__ into the
  manager classes
- make executors serializable via ExecutorInfo
2021-12-15 15:55:32 +09:00
Saugat Pachhai 8319cc50c6 Update setup.cfg 2021-12-14 21:11:28 +05:45
dependabot[bot] 98c22e3277 build(deps): Bump scmrepo from 0.0.4 to 0.0.5
Bumps [scmrepo](https://github.com/iterative/scmrepo) from 0.0.4 to 0.0.5.
- [Release notes](https://github.com/iterative/scmrepo/releases)
- [Commits](https://github.com/iterative/scmrepo/compare/0.0.4...0.0.5)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-14 21:11:28 +05:45
Saugat Pachhai (सौगात) 3ca8e26169 tests: get rid of GitFS related tests
Also refactored unittest-based tests for LocalFileSystem to pytest
2021-12-14 17:55:26 +05:45
karajan1001andPeter Rowlands 0deab0118a exp: split exp sub command (#5489)
related to #5489
Current dvc/command/experiments.py is too long for switch between cmd class
and the argument parser.

Split the sub command in dvc/command/experiments.py to individual files

Co-authored-by: Peter Rowlands (변기호) <[email protected]>
2021-12-14 19:16:04 +08:00
dependabot[bot] 72af8b8605 build(deps-dev): Bump pytest-timeout from 2.0.1 to 2.0.2
Bumps [pytest-timeout](https://github.com/pytest-dev/pytest-timeout) from 2.0.1 to 2.0.2.
- [Release notes](https://github.com/pytest-dev/pytest-timeout/releases)
- [Commits](https://github.com/pytest-dev/pytest-timeout/compare/2.0.1...2.0.2)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-14 16:53:58 +08:00
Saugat Pachhai (सौगात) ba4955738f tests: remove func.test_scm 2021-12-13 21:03:24 +05:45
dependabot[bot] 602713e57b build(deps-dev): Bump pytest-xdist from 2.4.0 to 2.5.0
Bumps [pytest-xdist](https://github.com/pytest-dev/pytest-xdist) from 2.4.0 to 2.5.0.
- [Release notes](https://github.com/pytest-dev/pytest-xdist/releases)
- [Changelog](https://github.com/pytest-dev/pytest-xdist/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-xdist/compare/v2.4.0...v2.5.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-13 13:39:50 +09:00
Paweł Redzyński 7250e1b336 dvc: fix installation from archives
Fixes: #7124
2021-12-11 05:49:28 +02:00
Ruslan Kuprieiev 896bbf1fc6 fs: get rid of unused DETAIL_FIELDS 2021-12-10 18:02:22 +02:00
Ruslan Kuprieiev 81eda7e2b7 fs: get rid of CHECKSUM_DIR_SUFFIX and is_dir_hash
These are leftovers from old remotes, that clearly have nothing
to do with filesystems.
2021-12-10 10:53:10 +02:00
Ruslan Kuprieiev 82c5caee27 setup: unpin s3fs/ossfs/gcsfs 2021-12-09 15:20:22 +02:00
Paweł Redzyński eb41b8b3e0 setup: bump gcsfs
Related: #7053
2021-12-09 15:20:22 +02:00
Ruslan Kuprieiev ea49fb95f7 setup: bump s3fs
Fixes #7053
2021-12-09 13:57:40 +02:00
dependabot[bot] e977bcc6d9 build(deps-dev): Bump dvclive[image] from 0.4.5 to 0.4.6
Bumps [dvclive[image]](https://github.com/iterative/dvclive) from 0.4.5 to 0.4.6.
- [Release notes](https://github.com/iterative/dvclive/releases)
- [Commits](https://github.com/iterative/dvclive/compare/0.4.5...0.4.6)

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

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

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-08 12:01:56 +01:00
Ruslan Kuprieiev 064fbfd0d5 webdav: fix unstrip_protocol 2021-12-07 23:59:12 +02:00
Ruslan Kuprieiev e8e1c76504 fs: path: use ntpath/posixpath
This is less error-prone than implementing these methods ourselves.
2021-12-07 22:58:48 +02:00
Saugat Pachhai (सौगात) 532e115782 exp init: unhide from dvc exp --help 2021-12-07 09:55:40 +05:45
Saugat Pachhai 189f4a0216 Update dvc/repo/experiments/init.py 2021-12-07 09:55:03 +05:45
pre-commit-ci[bot] 0bc101964e [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2021-12-07 09:55:03 +05:45
Saugat Pachhai (सौगात) 3319808ed2 exp init: use shortlinks for doclinks 2021-12-07 09:55:03 +05:45
Saugat Pachhai (सौगात) cd37ebca37 exp init: minor refactoring 2021-12-07 08:40:46 +05:45
dberenbaum 4385720fab exp init: update tests 2021-12-07 07:34:31 +05:45
dberenbaum d52123f850 exp init: change default stage name to train 2021-12-07 07:34:31 +05:45
pre-commit-ci[bot] 9803db6a64 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2021-12-07 06:17:02 +05:45
pre-commit-ci[bot] bcb1ba0ea8 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/ambv/black: 21.11b1 → 21.12b0](https://github.com/ambv/black/compare/21.11b1...21.12b0)
2021-12-07 06:17:02 +05:45
Saugat Pachhai (सौगात) 389fd48b5a exp init: change type to --type=live to --type=dl 2021-12-07 05:56:23 +05:45
Ruslan Kuprieiev 2ecbb16769 setup: bump scmrepo 2021-12-06 19:27:16 +02:00
Ruslan Kuprieiev ec73300782 odb: make some listing methods private
These are not used anywhere outside of odb, so we can make them private
to reduce the scope for the upcoming refactoring.
2021-12-06 14:10:50 +02:00
Paweł Redzyński 954ac6ab03 plots: roll back templates in packages change
Fixes: #6911
Reverts: a1b146669c
2021-12-06 09:55:13 +02:00
dependabot[bot] 1020b06a16 build(deps-dev): Bump pylint from 2.12.1 to 2.12.2
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.12.1 to 2.12.2.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.12.1...v2.12.2)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-06 09:47:25 +02:00
karajan1001 9cf1f45e71 proc: implement cleanup/removal (#7063)
fix: #7063
Add `cleanup` and `removal` function to ProcessManager
2021-12-06 15:25:15 +09:00
dependabot[bot] b67e75c691 build(deps-dev): Bump requests-kerberos from 0.13.0 to 0.14.0
Bumps [requests-kerberos](https://github.com/requests/requests-kerberos) from 0.13.0 to 0.14.0.
- [Release notes](https://github.com/requests/requests-kerberos/releases)
- [Changelog](https://github.com/requests/requests-kerberos/blob/master/HISTORY.rst)
- [Commits](https://github.com/requests/requests-kerberos/compare/v0.13.0...v0.14.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-12-06 11:38:43 +08:00
David de la Iglesia Castro 728731d4d7 ui: Add open_browser.
Use `webbrowser.open` and relpath for WSL plattform.
2021-12-05 15:44:13 +02:00
Paweł Redzyński 6723a34bda render: image: make html less ugly 2021-12-05 15:42:07 +02:00
karajan1001 eb26b9a78d ci: fix ci failure
We didn't mock `os.kill` in `test_dead_process` and the pid it
kills might coincidentlly be the same with some real process.

1. Add a mocker to mock out `os.kill` in `test_dead_process`.
2. Use reset_mock to replace setting it again.
2021-12-04 19:40:57 +09:00
Ruslan Kuprieiev 42641ddd20 gha: remove remote leftovers
Followup for #7087
2021-12-03 23:11:22 +02:00
Ruslan Kuprieiev b6f2a8e653 tests: remove redundant remote tests
With introduction of https://github.com/iterative/dvc-plugins,
all important tests are being ran there, so there is no need
for dvc repo to run them again.

This PR also removes some redundant/broken remote tests.
2021-12-03 22:45:41 +02:00
Ruslan Kuprieiev 024f06d672 gdrive: fix path operations 2021-12-02 18:52:18 +02:00
Ruslan Kuprieiev 8dd3b28729 gdrive: fix typo 2021-12-02 17:22:47 +02:00
Ruslan Kuprieiev d4178c080b gdrive: don't forget bucket when generating a url 2021-12-02 17:21:51 +02:00
Ruslan Kuprieiev 302f5b07f6 gdrive: fix typo 2021-12-02 17:19:12 +02:00
karajan1001andPeter Rowlands 7d68863ba2 proc: implement process signaling(#7062)
fix: #7062
1. Add `send_signal`, `kill`, `terminate` to processManager.
2. Add tests for each of them.

Co-authored-by: Peter Rowlands (변기호) <[email protected]>
2021-12-02 16:39:41 +09:00
Ruslan Kuprieiev 09f48a5aaa tests: introduce dvc.testing
This is a very early version of test utils for dvc plugins. This module
consists of existing tests with minimal changes and is going to evolve
a lot as our plugins are getting filled in and pytest-test-utils matures.
2021-12-02 09:08:40 +02:00
dependabot[bot] 004e15c2c0 build(deps-dev): Bump types-requests from 2.26.0 to 2.26.1
Bumps [types-requests](https://github.com/python/typeshed) from 2.26.0 to 2.26.1.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-11-29 20:24:26 +05:45
dependabot[bot] 382edfc382 build(deps-dev): Bump dvclive[image] from 0.4.3 to 0.4.5
Bumps [dvclive[image]](https://github.com/iterative/dvclive) from 0.4.3 to 0.4.5.
- [Release notes](https://github.com/iterative/dvclive/releases)
- [Commits](https://github.com/iterative/dvclive/compare/0.4.3...0.4.5)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-11-29 17:17:46 +05:45
Peter Rowlands dd535f617b proc: initial ProcessManager skeleton 2021-11-29 07:00:38 +02:00
Peter Rowlands 15eb326188 proc: initial ManagedProcess implementation
- supports running (backgrounded) processes with redirected output
2021-11-29 07:00:38 +02:00
dependabot[bot] 34f402659c build(deps): Bump scmrepo from 0.0.1 to 0.0.2
Bumps [scmrepo](https://github.com/iterative/scmrepo) from 0.0.1 to 0.0.2.
- [Release notes](https://github.com/iterative/scmrepo/releases)
- [Commits](https://github.com/iterative/scmrepo/compare/0.0.1...0.0.2)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-11-26 09:36:57 +05:45
dependabot[bot] 3e808b6f37 build(deps-dev): Bump pylint from 2.11.1 to 2.12.1
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.11.1 to 2.12.1.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.11.1...v2.12.1)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-11-26 09:36:25 +05:45
Saugat Pachhai (सौगात) ea52ab2260 remove dvc.scm and dvc.fs.git module; rename dvc.fs.scm to dvc.fs.git 2021-11-25 19:09:58 +02:00
Paweł Redzyński 57c6b1036f gha: allow builds for new 3.9 versions
Closes: #6975
2021-11-25 14:40:40 +02:00
Saugat Pachhai 950b576cbd packages: build binary pkgs with Python 3.9 2021-11-25 14:40:11 +02:00
Saugat Pachhai (सौगात) 2cbe25ac90 scm: move warning on unsupported index to scmcontext
Also makes `Repo.init` avoid using `scm.add` directly.
2021-11-25 12:29:50 +02:00
Saugat Pachhai (सौगात) c9cd4a8c51 scm: remove dvc specific message; add client kwarg
This commit removes dvc specific message hardcoded in
scm.detach_head. Now that we are decoupling scm, we are
adding `client=` kwarg to be set by the caller.
2021-11-25 12:05:25 +02:00
Saugat Pachhai (सौगात) 95c726b297 fs.git: refactor GitFileSystem to be fsspec compliant
Also adds a wrapper for DVC to use as GitFileSystem in dvc.fs.scm.
This is temporary, and will be renamed back to dvc.fs.git after the
split.

Note that this filesystem is not fully compliant, it's only
meant to be compatible with our previous implementation.
2021-11-25 08:32:20 +05:45
Saugat Pachhai (सौगात) 281b97c8ef scm: remove dependency from dvc's utils in scm
This removes dependency from dvc.utils and dvc.types from DVC.
Some utils like fix_env, is_binary have been reimplemented.
Regarding fix_env and is_binary, I think eventually this
should be a responsibility of DVC itself to set.

relpath implementation in DVC also works with paths in
different drives in Windows. I have changed this to use
os.path.relpath for now.
2021-11-24 17:00:12 +02:00
Saugat Pachhai (सौगात) a282e0e48c scm: restructure scm
Makes it easier to decouple, we'd only need to rename
dvc/scm/__init__.py to dvc/scm.py.
2021-11-24 11:46:40 +05:45
Saugat Pachhai (सौगात) 48581715b6 scm: remove exp related behaviours from scm.resolve_rev 2021-11-24 11:39:39 +05:45
dependabot[bot] 13ca959e98 build(deps): Bump actions/cache from 2.1.6 to 2.1.7
Bumps [actions/cache](https://github.com/actions/cache) from 2.1.6 to 2.1.7.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v2.1.6...v2.1.7)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2021-11-24 08:47:13 +05:45
Saugat Pachhai (सौगात) a0f4e12fb4 scm: unified progress reporting in a structured way
This adds a support for reporting progress from Git in a structured
format. The gitpython already provides somewhat structured progress
reporting, dulwich on the other hand only provides git-like line
messages.

As gitpython uses git binary, it already parse this line format
in a structured way. This PR makes dulwich use the same machinery
to unify progress reporting for both dulwich and gitpython python.

This does add a dependency on gitpython even on dulwich backend,
so I am iffy if we should do this.
The alternative is to translate gitpython's progress reports to
string, and just use string messages as progress reports on both
dulwich and gitpython.
2021-11-23 19:50:12 +02:00
Saugat Pachhai (सौगात) d95dd27773 scm: map internal SCMError to DVC's exception type 2021-11-23 19:21:51 +02:00
Saugat Pachhai (सौगात) f76ef84c88 exceptions: migrate other scm exceptions to internal 2021-11-23 19:21:51 +02:00
Saugat Pachhai (सौगात) f2f4227656 exceptions: migrate RevError 2021-11-23 19:21:51 +02:00
Saugat Pachhai (सौगात) 7ca8fbbc82 exceptions: migrate CloneError 2021-11-23 19:21:51 +02:00
Saugat Pachhai (सौगात) 8ec1ff44de exceptions: move AuthError and InvalidRemote errors 2021-11-23 19:21:51 +02:00
Saugat Pachhai (सौगात) c19a11152a scm: move FileNotInRepoError and MergeConflictError to scm.exceptions 2021-11-23 19:21:51 +02:00
Saugat Pachhai (सौगात) cab60b54f8 live: import webbrowser lazily; only patch webbrowser.open 2021-11-23 22:08:21 +05:45
David de la Iglesia Castro fc7caa487f tests: Patch webbrowser.open on session level
Closes #7014
2021-11-23 22:08:21 +05:45
Saugat Pachhai (सौगात) 568abb3e28 scm: remove dependency from DvcException 2021-11-23 13:19:41 +02:00
Kwon-Young ChoiandPeter Rowlands 3e7cff3e35 exp: respect repo hardlink_lock config for the exp scm lock
- Will fix #7029

Co-authored-by: Peter Rowlands (변기호) <[email protected]>
2021-11-23 19:51:42 +09:00
Saugat Pachhai (सौगात) 7dc5420548 metrics/params diff: catch NoSCMError instead of a broad exception
instead of catching broad exception, NoSCMError should work here.
2021-11-23 13:21:15 +05:45
pre-commit-ci[bot] 644e9ede92 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/ambv/black: 21.10b0 → 21.11b1](https://github.com/ambv/black/compare/21.10b0...21.11b1)
2021-11-22 21:36:19 +02:00
Paweł Redzyński a9512e8585 plots: collect: return callable instead of data
Related to: #6923, iterative/viewer#2657
2021-11-22 21:31:20 +02:00
Saugat Pachhai (सौगात) 00e53c5c32 tests: fix black 2021-11-22 17:33:12 +05:45
Saugat Pachhai (सौगात) 0aaf96bce4 exp init: show 'n to omit' on --explicit mode 2021-11-22 17:20:16 +05:45
Saugat Pachhai (सौगात) e07b99ed33 tests: increase timeout to 2 second for prompts test 2021-11-22 15:10:55 +05:45
Saugat Pachhai (सौगात) 7bc906011f tests: timeout only on functions, excluding tests fixtures
Sometimes, `tmp_dir` fixtures take longer than a second. Using `func_only`
makes it only consider duration of test function run, excluding fixtures
duration.
2021-11-22 15:10:55 +05:45
Saugat Pachhai (सौगात) b84227db89 scm: remove ignore list 2021-11-22 07:16:54 +02:00
David de la Iglesia Castro 318b269ad1 live: Call webbrowser.open.
Use `funcy.once_per_args` decorator to prevent opening the html multiple times if user closes first time.

Closes iterative/dvclive#180
2021-11-22 03:35:23 +02:00
Ruslan Kuprieiev 1176b6d875 Update setup.cfg 2021-11-22 03:31:23 +02:00
dependabot[bot] ee6539292b build(deps): Bump adlfs from 2021.9.1 to 2021.10.0
Bumps [adlfs](https://github.com/dask/adlfs) from 2021.9.1 to 2021.10.0.
- [Release notes](https://github.com/dask/adlfs/releases)
- [Changelog](https://github.com/dask/adlfs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/dask/adlfs/compare/2021.09.1...2021.10.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-11-22 03:31:23 +02:00
Saugat Pachhai (सौगात) bf313d92aa exp init: timeout prompts' test
The `exp init` tests may just keep waiting on the prompts blocking tests.
The blocking of prompts is likely a failure in tests setup or a bug
in the implementation. It's hard to find out these failures or avoid it.
If they take longer than 1 second, they most likely have failed as
most of test takes 0.1 second at max.
2021-11-22 03:27:16 +02:00
Saugat Pachhai (सौगात) 46289182d2 tests: run on Windows with Python 3.10
pygit2 wheels should be now available.
Had to skip pytest-docker for Windows + Python 3.10 due to
a dependency issue with it's dependency, see:
https://github.com/docker/docker-py/issues/2902.
2021-11-20 18:52:31 +02:00
Saugat Pachhai (सौगात) cb941a836d tests: cover exp init 2021-11-19 18:58:23 +05:45
Gustavo José de Sousa a70b3087b4 build: bump sshfs from 2021.8.1 to 2021.11.2
This version uses bumps asyncssh to a version that supports ProxyJump.
2021-11-18 23:58:57 +02:00
Ruslan Kuprieiev c9dd137426 fs: BaseFileSystem -> FileSystem
Following fsspec conventions and new object conventions.
2021-11-18 19:58:23 +02:00
Saugat Pachhai (सौगात) 93e6526907 scm: split tracking of to-be git-ignored/git-staged into SCMContext 2021-11-18 15:34:45 +02:00
dependabot[bot] 230eb7087d build: bump google-cloud-storage from 1.42.3 to 1.43.0
Bumps [google-cloud-storage](https://github.com/googleapis/python-storage) from 1.42.3 to 1.43.0.
- [Release notes](https://github.com/googleapis/python-storage/releases)
- [Changelog](https://github.com/googleapis/python-storage/blob/main/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-storage/compare/v1.42.3...v1.43.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-11-18 16:36:00 +05:45
Saugat Pachhai (सौगात) c3b91e5f85 exp init: interactive mode refactor and simplification 2021-11-18 16:06:52 +05:45
Saugat Pachhai (सौगात) eac030598c ui.prompt: move prompts from exp init to ui.prompt
Some fixes along the way to make it more generic, but it still has
lots of features tied with exp init.
2021-11-18 13:22:45 +05:45
karajan1001 068b8371aa Machine: table output for machine status(#6892)
Change the output format of `dvc machine status` command to tables for a
better readability.
2021-11-18 11:22:13 +08:00
karajan1001 888b6392bb Machine:remove field name in config(#6892)
Because our machine name now is the key value of the machine config
dict. And the machine config value will been overwritten by it when the
config is loaded. We can remove the field `name` from the config.
2021-11-18 11:22:13 +08:00
karajan1001 b24a31b834 Machine: use table for list outputs(#6892)
fix: #6892

Current output of machine list is quite urgly, it is originaly used to
show configurations. In this PR, we use a new table format for it.
2021-11-18 11:22:13 +08:00
karajan1001 45553c5b30 TabularData: add a new how arg to dropna(#6892)
Borrowed this idea from pandas api. Current dropna methods will drop a
column or row if any data is missing. But it is also quite useful to
drop those columns or rows only if all of its elements are missing. This
is mostly used for pruning a table.
2021-11-18 11:22:13 +08:00
Peter Rowlands 28f57933db dulwich: flush stdin channel on asyncssh vendor write 2021-11-17 12:48:34 +02:00
Peter Rowlands 25dca0b665 dulwich: workaround github SSH server in asyncssh vendor
- allow mismatched RSA signature algo names in initial
  SSH_MSG_USERAUTH_PK_OK response
- unit test overridden asyncssh publickey auth compatibility with
  github ssh server
2021-11-17 12:48:34 +02:00
Peter Rowlands 45c4e1998d dulwich: fallback to default client config in asyncssh vendor
- vendor should use ssh-agent or default IdentityFile(s) when no SSH key
  has been provided by the user
- encrypted (fallback) keys will always be passed to ssh-agent
2021-11-17 12:48:34 +02:00
karajan1001 eea67a6396 machine:remove help text for irrelevant commands (#6940)
fix: #6940

`--global/system/project/local` help message are in some irrelevant
command `status`, `ssh`, `create`, `destroy`. These commands are more
related to running instances rather than `machine config`, level
options didn't work for them.

1. Remove `--global/system/project/local` options in `status`, `ssh`,
   `create`, `destroy`.
2. Add a test for it.
3. Correct a typo error.
2021-11-17 16:25:48 +08:00
karajan1001andDave Berenbaum f43c04f935 Make machine status name optional
fix: #6838
1. make machine status name optional.
2. show all machine status if no name provide.
3. UI changes for this cmd.
4. add a new test for it.

Co-authored-by: Dave Berenbaum <[email protected]>
2021-11-17 15:44:36 +08:00
dependabot[bot] 3486435a7f build: bump filelock from 3.3.2 to 3.4.0
Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.3.2 to 3.4.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.3.2...3.4.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-11-17 14:04:17 +08:00
Paweł Redzyński e0fd0790fc plots: special chars handling for vega plots paths
Fixes: #6894
2021-11-17 01:57:35 +02:00
dberenbaum f99d3cda57 scm: hint to autostage 2021-11-16 18:01:18 +02:00
pre-commit-ci[bot] 003461f9a2 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/timothycrosley/isort: 5.9.3 → 5.10.1](https://github.com/timothycrosley/isort/compare/5.9.3...5.10.1)
2021-11-16 07:55:56 +05:45
Ruslan Kuprieiev 88660f6a66 dvc: use transfer in checkout/doctor/odb
All three of those use more or less the same logic for testing different
link types and maybe then using them. So now instead of having 3
implementations, we'll have just one.

Filesystem `transfer` now will not try to catch particular errno's, but
instead will just ignore any OSError and move down to the next link type
to try out, and if the last one fails - it will raise the last exception,
so if it is a common problem - we'll be able to see it.

This patch also removes `odb.confirmed_cache_type`, as it was caching
link type that is not really a cache-level thing, but rather a checkout thing,
that should only be cached for particular objects we are linking, as multiple
objects could be linked to different filesystems from the same odb (e.g.
external local output on hdd and local data on your ssd).

Next step here will be to introduce metadata diff, so that we can get rid of
temporary Link wrapper and introduce Transfer and corresponding transfer
config, that will know what links to use and could have some other things
like callbacks, so we don't continue passing dozens of kwargs along the stack.

Continuing from #6963

Fixes #6980
2021-11-15 22:50:00 +02:00
David de la Iglesia Castro 7df6090a9c params: Use ast.literal_eval for python params.
Add unit tests for `parse_py`

Closes  #6953
2021-11-15 17:52:49 +02:00
Dimitar Todorov fdba79c519 change method override to call super instead of repeating the calls 2021-11-15 17:50:14 +02:00
Димитър Тодоров (Dimitar Todorov)andSaugat Pachhai 2f6b6cc586 Update dvc/fs/http.py
Co-authored-by: Saugat Pachhai <[email protected]>
2021-11-15 17:50:14 +02:00
Dimitar Todorov 0fea622266 push: fix http storage ignoring method setting
Add method overriding the default put_file in http.py
where the upload method is passed to the http implementation.

Fixes: #6694
2021-11-15 17:50:14 +02:00
David de la Iglesia Castro 4a1985d7cd dvc render: Add RENDERERS registry.
Related: #6840, #4455, #6943
2021-11-15 16:37:24 +01:00
David de la Iglesia Castro c8903dc3a6 live: Add test for logging images with dvclive
Closes iterative/dvclive#190
2021-11-15 12:47:02 +01:00
David de la Iglesia Castro 28dd39a1a0 exp diff: Remove --old option.
After #6814 the header of `diff_table` is `a_rev/`b_rev` instead of `Old`/`New` so the name of the option was counterintuitive.

In addition, make `a_rev` show by default, for consistency with output format of other `diff_table` uses (`dvc metrics diff` / `dvc params diff`).
2021-11-12 12:58:12 +02:00
dependabot[bot] ef9d8580cc build: bump types-requests from 2.25.12 to 2.26.0
Bumps [types-requests](https://github.com/python/typeshed) from 2.25.12 to 2.26.0.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-11-12 12:40:38 +02:00
dependabot[bot] 1af23f32b7 build: bump pyinstaller from 4.6 to 4.7
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 4.6 to 4.7.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/v4.7/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v4.6...v4.7)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-11-12 12:40:27 +02:00
Saugat Pachhai (सौगात) 7b488887b4 exp init: suppress plots if live is selected as a stage type 2021-11-12 11:25:39 +05:45
Saugat Pachhai (सौगात) 8262dbd37c exp init: hide metrics/plots from worktree on --type=live 2021-11-12 11:21:58 +05:45
Ruslan Kuprieiev 264d78b637 ostage: don't forget to convert https etags
Fixes #6966
2021-11-12 00:54:44 +02:00
Ruslan Kuprieiev 1b2400842d checkout: catch ENOSYS 2021-11-11 22:38:29 +02:00
Saugat Pachhai (सौगात) 81db879c51 hardlink: fallback to other link methods if 38 error code is received
Fixes #6962, where GDdrive mounted on Colab was failing when trying to hardlink
with the message that the 'Function not implemented'.
2021-11-11 22:38:29 +02:00
Saugat Pachhai (सौगात) d668cbeb45 exp init: warn for code/data if they don't exist 2021-11-11 08:09:37 +05:45
Saugat Pachhai (सौगात) a466241552 exp init: validate params as soon as it's entered in prompt
It'll give a error message to the user and reprompt them.
This mechanism can be extended to other prompts, and we
can show a warning message and/or reprompt them based on
validations.

Closes #6865. Part of #6446.
2021-11-11 08:09:37 +05:45
Ruslan Kuprieiev 7e3e257c7f utils: fs: remove unused fs_copy 2021-11-11 01:26:37 +02:00
Paweł Redzyński ab56e4bb9b gha: limit python version to 3.9.7
Related: https://bugs.python.org/issue45235
Closes: #6956
2021-11-11 01:14:03 +02:00
Paweł Redzyński 36ed182f26 argparse: dont override cmd
Related: #6956, https://bugs.python.org/issue45235
2021-11-11 01:14:03 +02:00
Ruslan Kuprieiev 045d1f5e3e localfs: remove LocalPath
fs.path is meant to be lightweight, and we should try to avoid using os.path.

Leftover from #6946
2021-11-10 20:10:48 +02:00
Ruslan Kuprieiev 5214aec1bb fspath: introduce relparts 2021-11-10 20:10:48 +02:00
David de la Iglesia Castro 72066f233b exp show: Add --only-changed option.
Use TabularData `drop_duplicates` to only show metrics/params with values varying across the selected experiments.

Fixes #5966
2021-11-10 16:52:18 +02:00
David de la Iglesia Castro cb6f625dd9 TabularData: Add drop_duplicates method.
This method mimics pandas `drop_duplicates` when axis=rows.

For axis=cols this method will drop columns with 0-variance (only
1 unique value). NaNs (`_fill_value`) won't be counted as unique values.

So the following column (`-` would be the `_fill_value`) will be dropped:

foo
-
foo
foo
2021-11-10 16:52:18 +02:00
dependabot[bot] 87fadd3225 build: bump dvclive from 0.4.2 to 0.4.3
Bumps [dvclive](https://github.com/iterative/dvclive) from 0.4.2 to 0.4.3.
- [Release notes](https://github.com/iterative/dvclive/releases)
- [Commits](https://github.com/iterative/dvclive/compare/0.4.2...0.4.3)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-11-10 11:46:41 +01:00
dependabot[bot] 8f028028b9 build: bump types-requests from 2.25.11 to 2.25.12
Bumps [types-requests](https://github.com/python/typeshed) from 2.25.11 to 2.25.12.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-11-10 11:45:42 +01:00
Batuhan Taskaya 9aec8f1926 dvc: get rid of path_info
Intermediate PR in removing path_info from our code base.

It is completely removed from the main codebase and is still
being used in tests, which will be migrated in the followups.

For fs-specific simple path manipulation, we've introduced
`fs.path` helper (similar to `os.path`).

Fixes #6774
2021-11-10 00:52:48 +02:00
dependabot[bot] 4175f9ffa4 build: bump s3/gcsfs from 2021.10.1 to 2021.11.0
Bumps [gcsfs](https://github.com/fsspec/gcsfs) from 2021.10.1 to 2021.11.0.
- [Release notes](https://github.com/fsspec/gcsfs/releases)
Bumps [s3fs](https://github.com/fsspec/s3fs) from 2021.10.1 to 2021.11.0.
- [Release notes](https://github.com/fsspec/s3fs/releases)
- [Changelog](https://github.com/fsspec/s3fs/blob/main/release-procedure.md)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-11-08 11:13:52 +08:00
Guðmundur HeimissonandRuslan Kuprieiev c5b1a73dc5 webhdfs: expose kerberos and https options (#6936)
* Add fsspec options for webhdfs

* Add requests-kerberos dependency

* Update dvc/fs/webhdfs.py

Simplify config.pop.

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

* Update dvc/fs/webhdfs.py

* Simplify kerb_kwargs config setting.

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

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2021-11-07 20:42:10 +02:00
Saugat Pachhai (सौगात) 044e79000d deps: bump rich to >=10.13.0
It comes with the support for no. of parameters that matches with json.dumps.
See: https://github.com/willmcgugan/rich/pull/1547 and
https://github.com/willmcgugan/rich/pull/1644.

Also added support for printing json to stderr. Before we used to use json.dumps
and rich.json.JSON based on isatty. Now we use rich.print_json on both
tty and non-tty conditions, which may avoid different behaviours in the
future.
2021-11-07 19:38:06 +02:00
karajan1001 be6128470f machine status: include two new field in the output
1. include instance_type and instance_gpu in the output.
2. rerange the field by importance.
2021-11-04 15:10:45 +02:00
Saugat Pachhai (सौगात) dd0d4f5a2c exp init: don't try tracking params if they are skipped 2021-11-03 19:21:10 +05:45
Saugat Pachhai (सौगात) 6e0dfb94e4 exp init: hide message about workspace tree on --explicit 2021-11-03 18:59:25 +05:45
Saugat Pachhai (सौगात) ce6c8a5678 exp init: fix --explicit failing with KeyError
Fixes #6917
2021-11-03 16:42:45 +05:45
dependabot[bot] b927337a30 build(deps): bump actions/checkout from 2.3.5 to 2.4.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 2.3.5 to 2.4.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2.3.5...v2.4.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2021-11-03 12:47:22 +02:00
Saugat Pachhai (सौगात) 03b372a073 exp init: ask for command before paths 2021-11-03 05:23:40 +05:45
Saugat Pachhai (सौगात) 915a755e2f build: remove numpy restriction 2021-11-03 05:23:17 +05:45
Paweł Redzyński 22cc2a5cbc plots: fix resolve template path bug
Fix: #6854
2021-11-02 16:49:56 +02:00
Saugat Pachhai (सौगात) a67d8a44b3 exp init: make confirmation prompt at the end similar to others 2021-11-02 17:24:09 +05:45
Saugat Pachhai (सौगात) 80a52e65ad exp init: minor refactor on way it returns prompted values 2021-11-02 14:57:40 +05:45
Saugat Pachhai (सौगात) 208ccb2430 exp init: autostage .gitignores, dvc.yaml and params file 2021-11-02 12:00:06 +05:45
dependabot[bot] 97e97c7f24 build(deps-dev): bump filelock from 3.3.1 to 3.3.2
Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.3.1 to 3.3.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.3.1...3.3.2)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-11-02 08:16:37 +05:45
dependabot[bot] 1d1451ddd3 build(deps): bump pyinstaller from 4.5.1 to 4.6
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 4.5.1 to 4.6.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/v4.6/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v4.5.1...v4.6)

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

Signed-off-by: dependabot[bot] <[email protected]>
2021-11-02 08:16:18 +05:45
pre-commit-ci[bot] 0cc7c4c02b [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/ambv/black: 21.9b0 → 21.10b0](https://github.com/ambv/black/compare/21.9b0...21.10b0)
2021-11-02 06:30:13 +05:45
Saugat Pachhai d3d459caa0 Revert "deps: require chardet as a dependency"
This reverts commit a4dc8ddc74.
2021-11-02 06:17:31 +05:45
Saugat Pachhai a4dc8ddc74 deps: require chardet as a dependency
Looks like azure-core is dependent on a dependency `chardet` that aiohttp brought it with, which has been replaced with `charset_normalizer`.
But azure-core depends on internal of aiohttp and looks like it tries to set it's own encoding parser, `chardet` which is now not available.
2021-11-01 18:25:39 +02:00
Saugat Pachhai 3eceeea3e8 Remove python 3.10 restriction for pyarrow
Pyarrow 6 is now available for Python 3.10. Fixes https://github.com/iterative/dvc/issues/6878.
2021-11-01 20:00:21 +05:45
Saugat Pachhai (सौगात) 8fc959060a tests: skip based on conditional import on pyarrow
From now on, pyarrow dependency is optional for the tests.
2021-11-01 19:15:28 +05:45
David de la Iglesia Castro 0638d99fe3 LiveTask: write output path at start/end
Writing at the end of each step (create_summary) can mess up with some ML Framework progress bars, I moved the message to the monitor task, as the path won't change during training.

Fixes iterative/dvclive#177
2021-10-29 14:41:03 +02:00
David de la Iglesia Castro 24abc6f328 TabularData: Add dropna method.
This method mimics pandas `dropna`.
2021-10-29 15:28:36 +03:00
David de la Iglesia Castro 4dd544121f plots: Use URI for --open.
We were previously using relpath in *all* cases, mainly to support WSL but relpath doesn't work
on macOS.

Added a check for using relpath *only* for WSL.

Fixes #6092
2021-10-29 13:27:12 +02:00
Ruslan Kuprieiev 869ea442ef output: save: remove redundant changed() call
No longer needed since object introduction, as we will compare staged
object during commit/checkout anyway.
2021-10-29 01:45:10 +03:00
Ruslan Kuprieiev 922eb20e44 staging: don't use move 2021-10-27 20:19:17 +03:00
Ruslan Kuprieiev a2f91ed5fd checkout: always confirm link type
Currently whether or not link confirmation will run depends on
particular filesystem and existing workspace link type, which introduces
inconsistency in behaviour, where we sometimes catch unsupported links
before touching user data and sometimes we don't.
2021-10-27 20:19:17 +03:00
Ruslan Kuprieiev 017762fc2c checkout: don't forget to protect() back objects
Depending on a filesyste (e.g. on NTFS), `_remove` might reset
read-only permissions in order to delete a hardlink to protected object,
which will also reset it for the object itself, making it unprotected,
so we need to protect it back.
2021-10-27 20:19:17 +03:00
Ruslan Kuprieiev 874aceccf8 system: use linkat instead of link
`os.link` is broken across platforms when dealing with symlinks.

See https://bugs.python.org/issue41355 for more info.
2021-10-27 20:19:17 +03:00
Ruslan Kuprieiev 41aee9a3b7 localfs: use mtime/size/inode for checksum
Currently it depends on the whole `info()` output, which has too much
information that doesn't really matter. To check if file contents didn't
change mtime, size, inode combo is enough.
2021-10-27 20:19:17 +03:00
Peter Rowlands (변기호) 0d895e2661 exp: add initial ssh executor (#6833)
* executor: move local specific git initialization out of base

* machine: return kwargs for executor init instead of executor object

* executor: add initial ssh executor

* ssh-executor: disable strict host checking for dvc-machine instances

* initial tests for ssh executor

* drop GIT_SSH_COMMAND usage

* push: accept destination odb as a parameter

* separate cache initialization into executor.init_cache

* add test for ssh executor cache init

* remove dead code

* machine: fixture-ize default test scenarios

* test SSHExecutor.from_machine

* tpi: return default SSH port for constructing executors
2021-10-26 16:08:17 +09:00
Saugat Pachhai 14eaba4cdb exp init: iterate on ui, tree and show to-be-generated yaml content (#6750)
* iterate on ui, has tree and shows to-be-generated yaml content

* order fs tree structure alphabetically

* always show the heading

* hide dvclive on tree if not on live mode

* Change message at the end

* s/skip/omit

* add link on the header

* remove newline on non-interactive mode
2021-10-26 08:22:33 +05:45
Dave Berenbaum 3064738a34 fix msg in for ExperimentExistsError (#6864) 2021-10-26 10:55:08 +09:00
GaoandPeter Rowlands c07592131a exp run: add experiment name check. (#6848)
* Add experiment name check.

1. Add experiment name check (https://git-scm.com/docs/git-check-ref-format)
2. Add duplicate exp name check.
3. Add some unit test for it.

* Ban slash / in dvc exp names

* Use dulwich backend for ref name checking

* Some bug fix

* Update dvc/repo/experiments/__init__.py

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

* Update dvc/repo/experiments/__init__.py

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

* Some review changes

* Make some funtion more reusable.

Co-authored-by: Peter Rowlands (변기호) <[email protected]>
2021-10-25 20:16:37 +09:00
Saugat Pachhai 52a9cddc14 exp init: remove default in cli, add default in help instead (#6862)
The default made it harder to detect between the default and explicitly provided values, which we need for interactive mode.
2021-10-25 16:32:17 +05:45
Ruslan Kuprieiev 38cbebfb28 fs: transfer: try reflink (#6853)
If supported, reflink, will provide the fastest transfer possible.
That being said, right now we use `move` in most of the places,
which still takes presidence.

Pre-requisite for #6387
2021-10-24 22:46:02 +03:00
dependabot[bot]andSaugat Pachhai 5017c2f282 build(deps): update pyparsing requirement from ==2.4.7 to >=2.4.7
build(deps): update pyparsing requirement from ==2.4.7 to >=2.4.7 

Updates the requirements on [pyparsing](https://github.com/pyparsing/pyparsing) to permit the latest version.
- [Release notes](https://github.com/pyparsing/pyparsing/releases)
- [Changelog](https://github.com/pyparsing/pyparsing/blob/master/CHANGES)
- [Commits](https://github.com/pyparsing/pyparsing/compare/pyparsing_2.4.7...pyparsing_3.0.0)

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]>
2021-10-24 15:22:46 +00:00
dependabot[bot] 1bf6cc7c2f build(deps): bump pylint-pytest from 1.0.3 to 1.1.2 (#5894)
Bumps [pylint-pytest](https://github.com/reverbc/pylint-pytest) from 1.0.3 to 1.1.2.
- [Release notes](https://github.com/reverbc/pylint-pytest/releases)
- [Changelog](https://github.com/reverbc/pylint-pytest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/reverbc/pylint-pytest/compare/pylint-pytest-1.0.3...pylint-pytest-1.1.2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-24 20:08:58 +05:45
dependabot[bot] 27666ffba7 build(deps-dev): Bump dvclive from 0.4.1 to 0.4.2 (#6846)
Bumps [dvclive](https://github.com/iterative/dvclive) from 0.4.1 to 0.4.2.
- [Release notes](https://github.com/iterative/dvclive/releases)
- [Commits](https://github.com/iterative/dvclive/compare/0.4.1...0.4.2)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-24 19:03:24 +05:45
Ruslan Kuprieiev e2d705fd76 link: use proper exceptions (#6852)
* link: use proper exceptions

* system: use ENOTSUP instead of NotImplementedErrorwq
2021-10-23 02:38:17 +03:00
Saugat Pachhai 719e93192f move scm.install to dvc.install (#6849)
scm.install does 3 different things, and all of those are
very much tied to DVC. So, as we are splitting scm out,
it should now belong to `dvc.install`.
2021-10-22 14:53:33 +03:00
Saugat Pachhai 501256708d use system hook for mypy; fix raised issues (#6851) 2021-10-22 15:40:54 +05:45
Saugat Pachhai 800fbc7ad0 Use -n=auto in tests
Linux/Windows machines have only 2 cores, whereas macOS machines have 3 cores. Maybe we are stretching available resources by 2x, let's see if `-n=auto` improves test freezes.
2021-10-22 08:46:55 +05:45
409 changed files with 14499 additions and 15130 deletions
+1
View File
@@ -0,0 +1 @@
ref-names: $Format:%D$
+1
View File
@@ -0,0 +1 @@
.git_archival.txt export-subst
+1 -1
View File
@@ -1,7 +1,7 @@
pull_request_rules:
- name: backport patches to 1.11
conditions:
- base=master
- base=main
- label=backport-1.11
actions:
backport:
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: 3.7
- uses: iterative/dvc-bench@master
- uses: iterative/dvc-bench@main
with:
pytest_options: "-k 'test_init or test_help'"
+14 -14
View File
@@ -2,7 +2,7 @@ name: Build packages
on:
push:
branches:
- master
- main
release:
types: [released, prereleased]
workflow_dispatch:
@@ -10,13 +10,13 @@ jobs:
deb:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2.3.5
- uses: actions/checkout@v2.4.0
with:
fetch-depth: 0
- name: Set up Python 3.8
- name: Set up Python 3.9
uses: actions/[email protected]
with:
python-version: 3.8
python-version: 3.9
- name: Set up Ruby 2.4
uses: actions/[email protected]
with:
@@ -41,13 +41,13 @@ jobs:
rpm:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2.3.5
- uses: actions/checkout@v2.4.0
with:
fetch-depth: 0
- name: Set up Python 3.8
- name: Set up Python 3.9
uses: actions/[email protected]
with:
python-version: 3.8
python-version: 3.9
- name: Set up Ruby 2.4
uses: actions/[email protected]
with:
@@ -72,13 +72,13 @@ jobs:
osxpkg:
runs-on: macos-10.15
steps:
- uses: actions/checkout@v2.3.5
- uses: actions/checkout@v2.4.0
with:
fetch-depth: 0
- name: Set up Python 3.8
- name: Set up Python 3.9
uses: actions/[email protected]
with:
python-version: 3.8
python-version: 3.9
- name: Set up Ruby 2.4
uses: actions/[email protected]
with:
@@ -106,13 +106,13 @@ jobs:
exe:
runs-on: windows-2019
steps:
- uses: actions/checkout@v2.3.5
- uses: actions/checkout@v2.4.0
with:
fetch-depth: 0
- name: Set up Python 3.8
- name: Set up Python 3.9
uses: actions/[email protected]
with:
python-version: 3.8
python-version: 3.9
- name: Install deps
run: |
pip install .[all]
@@ -136,7 +136,7 @@ jobs:
pip:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2.3.5
- uses: actions/checkout@v2.4.0
with:
fetch-depth: 0
- name: Set up Python 3.7
+10 -47
View File
@@ -20,7 +20,7 @@ jobs:
uses: styfle/[email protected]
with:
access_token: ${{ github.token }}
- uses: actions/checkout@v2.3.5
- uses: actions/checkout@v2.4.0
with:
fetch-depth: 0
- name: Set up Python 3.8
@@ -29,12 +29,8 @@ jobs:
python-version: 3.8
- name: Install requirements
run: |
pip install wheel
pip install ".[all,tests,terraform]" pre-commit
- name: install temporary dependencies
run: |
pip install git+https://github.com/isidentical/gdrivefs@service-account
pip install git+https://github.com/isidentical/pydata-google-auth@service-account
pip install --upgrade pip setuptools wheel
pip install ".[dev]" pre-commit
- name: Check README
run: python setup.py checkdocs
- uses: pre-commit/[email protected]
@@ -46,12 +42,8 @@ jobs:
matrix:
os: [ubuntu-20.04, windows-latest, macos-latest]
pyv: ["3.7", "3.8", "3.9", "3.10"]
exclude:
# no wheels for pygit2 yet
- os: windows-latest
pyv: "3.10"
steps:
- uses: actions/checkout@v2.3.5
- uses: actions/checkout@v2.4.0
with:
fetch-depth: 0
- name: Set up Python
@@ -64,7 +56,7 @@ jobs:
echo "::set-output name=dir::$(pip cache dir)"
- name: set pip cache
id: pip-cache
uses: actions/[email protected].6
uses: actions/[email protected].7
with:
path: ${{ steps.pip-cache-dir.outputs.dir }}
key: ${{ runner.os }}-pip-${{ hashFiles('setup.cfg') }}
@@ -74,49 +66,20 @@ jobs:
if: steps.cache.pip-cache-dir.cache-hit != 'true'
run: |
pip install --upgrade pip setuptools wheel
pip install -e ".[all,tests,terraform]"
- name: install temporary dependencies
run: |
pip install git+https://github.com/isidentical/gdrivefs@service-account
pip install git+https://github.com/isidentical/pydata-google-auth@service-account
- name: setup git
run: |
git config --global user.email "[email protected]"
git config --global user.name "DVC Tester"
- name: setup gs creds
shell: bash
env:
GS_CREDS_JSON: ${{ secrets.GS_CREDS_JSON }}
if: env.GS_CREDS_JSON != ''
run: |
mkdir -p scripts/ci
echo "$GS_CREDS_JSON" > scripts/ci/gcp-creds.json
- name: disable gdrive tests
if: >-
github.event_name != 'schedule' &&
(matrix.pyv != '3.8' || matrix.os != 'ubuntu-18.04')
run: |
echo "extra_test_args=--disable-gdrive" >> $GITHUB_ENV
pip install -e ".[dev]"
- name: run tests
timeout-minutes: 40
env:
GDRIVE_CREDENTIALS_DATA: ${{ secrets.GDRIVE_CREDENTIALS_DATA }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
OSS_ACCESS_KEY_ID: ${{ secrets.OSS_ACCESS_KEY_ID}}
OSS_ACCESS_KEY_SECRET: ${{ secrets.OSS_ACCESS_KEY_SECRET}}
OSS_ENDPOINT: ${{ secrets.OSS_ENDPOINT}}
run: >-
python -m tests -n=4
python -m tests -n=auto
--cov-report=xml --cov-report=term
--enable-ssh ${{ env.extra_test_args }}
${{ env.extra_test_args }}
- name: upload coverage report
uses: codecov/[email protected]
with:
file: ./coverage.xml
fail_ci_if_error: false
notify:
if: github.ref == 'refs/heads/master' && failure()
if: github.ref == 'refs/heads/main' && failure()
needs:
- lint
- tests
@@ -127,6 +90,6 @@ jobs:
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: 'CI Failed on master :boom:'
SLACK_MESSAGE: 'CI Failed on main :boom:'
SLACK_TITLE: CI Status
SLACK_USERNAME: DVC-CI
+11 -16
View File
@@ -1,14 +1,14 @@
ci:
skip: [pylint, dvc-pre-commit]
skip: [mypy, pylint, dvc-pre-commit]
repos:
- hooks:
- id: black
language_version: python3
repo: https://github.com/ambv/black
rev: 21.9b0
rev: 22.1.0
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
rev: v4.1.0
hooks:
- id: check-toml
- id: check-yaml
@@ -16,14 +16,14 @@ repos:
- id: codespell
args:
- --ignore-words-list
- ba,datas,fo,uptodate
- ba,datas,fo,uptodate,cachable
repo: https://github.com/codespell-project/codespell
rev: v2.1.0
- hooks:
- id: isort
language_version: python3
repo: https://github.com/timothycrosley/isort
rev: 5.9.3
rev: 5.10.1
- hooks:
- id: flake8
language_version: python3
@@ -34,19 +34,14 @@ repos:
- flake8-string-format
repo: https://gitlab.com/pycqa/flake8
rev: 3.9.2
- hooks:
- id: mypy
language_version: python3
files: ^dvc/
additional_dependencies:
- types-requests
- types-paramiko
- types-tabulate
- types-toml
repo: https://github.com/pre-commit/mirrors-mypy
rev: 'v0.910-1'
- repo: local
hooks:
- id: mypy
name: mypy
entry: mypy
files: ^dvc/
language: system
types: [python]
- id: pylint
name: pylint
entry: pylint
+6 -6
View File
@@ -103,7 +103,7 @@ Snap (Snapcraft/Linux)
This corresponds to the latest tagged release.
Add ``--beta`` for the latest tagged release candidate,
or ``--edge`` for the latest ``master`` version.
or ``--edge`` for the latest ``main`` version.
Choco (Chocolatey/Windows)
--------------------------
@@ -183,8 +183,8 @@ Fedora / CentOS (rpm)
Comparison to related technologies
==================================
#. Data Engineering tools such as `AirFlow <https://airflow.apache.org/>`,
`Luigi <https://github.com/spotify/luigi>`, and others - in DVC data,
#. Data Engineering tools such as `AirFlow <https://airflow.apache.org/>`_,
`Luigi <https://github.com/spotify/luigi>`_, and others - in DVC data,
model and ML pipelines represent a single ML project focused on data
scientists' experience. Data engineering tools orchestrate multiple data
projects and focus on efficient execution. A DVC project can be used from
@@ -212,7 +212,7 @@ Comparison to related technologies
#. `DVC Studio <https://studio.iterative.ai/>`_ - official online
platform for DVC projects. It can be used to manage data and models, run
and track experiments, and visualize and share results. Also, it
integrates with `CML (CI/CD for ML) <https://cml.dev/>` for training
integrates with `CML (CI/CD for ML) <https://cml.dev/>`__ for training
models in the cloud or Kubernetes.
@@ -254,7 +254,7 @@ Barrak, A., Eghan, E.E. and Adams, B. `On the Co-evolution of ML Pipelines and S
:target: https://dvc.org
:alt: DVC logo
.. |CI| image:: https://github.com/iterative/dvc/workflows/Tests/badge.svg?branch=master
.. |CI| image:: https://github.com/iterative/dvc/workflows/Tests/badge.svg?branch=main
:target: https://github.com/iterative/dvc/actions
:alt: GHA Tests
@@ -262,7 +262,7 @@ Barrak, A., Eghan, E.E. and Adams, B. `On the Co-evolution of ML Pipelines and S
:target: https://codeclimate.com/github/iterative/dvc
:alt: Code Climate
.. |Coverage| image:: https://codecov.io/gh/iterative/dvc/branch/master/graph/badge.svg
.. |Coverage| image:: https://codecov.io/gh/iterative/dvc/branch/main/graph/badge.svg
:target: https://codecov.io/gh/iterative/dvc
:alt: Codecov
+1 -1
View File
@@ -1,7 +1,7 @@
"""Main entry point for DVC command line tool."""
import sys
from dvc.main import main
from dvc.cli import main
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
+34 -2
View File
@@ -1,10 +1,32 @@
from contextlib import ExitStack, contextmanager
from datetime import datetime
from typing import TYPE_CHECKING, Callable, Union
if TYPE_CHECKING:
from argparse import Namespace
@contextmanager
def viztracer_profile(
path: Union[Callable[[], str], str],
max_stack_depth: int = -1,
):
try:
import viztracer # pylint: disable=import-error
except ImportError:
print("Failed to run profiler, viztracer is not installed")
yield
return
tracer = viztracer.VizTracer(max_stack_depth=max_stack_depth)
tracer.start()
yield
tracer.stop()
tracer.save(path() if callable(path) else path)
@contextmanager
def yappi_profile(
path: Union[Callable[[], str], str] = None, wall_clock: bool = True
@@ -100,12 +122,18 @@ def debugtools(args: "Namespace" = None, **kwargs):
if kw.get("instrument") or kw.get("instrument_open"):
stack.enter_context(instrument(kw.get("instrument_open", False)))
if kw.get("yappi"):
from datetime import datetime
output = "callgrind.dvc-{0:%Y%m%d}_{0:%H%M%S}.out"
stack.enter_context(
yappi_profile(path=lambda: output.format(datetime.now()))
)
if kw.get("viztracer") or kw.get("viztracer_depth"):
output = "viztracer.dvc-{0:%Y%m%d}_{0:%H%M%S}.json"
stack.enter_context(
viztracer_profile(
path=lambda: output.format(datetime.now()),
max_stack_depth=kw.get("viztracer_depth") or -1,
)
)
yield
@@ -118,6 +146,10 @@ def add_debugging_flags(parser):
parser.add_argument(
"--yappi", action="store_true", default=False, help=SUPPRESS
)
parser.add_argument(
"--viztracer", action="store_true", default=False, help=SUPPRESS
)
parser.add_argument("--viztracer-depth", type=int, help=SUPPRESS)
parser.add_argument("--cprofile-dump", help=SUPPRESS)
parser.add_argument(
"--pdb", action="store_true", default=False, help=SUPPRESS
+4 -2
View File
@@ -82,10 +82,12 @@ 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 SCM, NoSCM
from dvc.scm.base import SCMError
from .scm import SCM, SCMError
try:
scm = SCM(root_dir=Repo.find_root())
+6 -6
View File
@@ -4,7 +4,6 @@ from contextlib import _GeneratorContextManager as GCM
from funcy import reraise
from dvc.exceptions import OutputNotFoundError, PathMissingError
from dvc.path_info import PathInfo
from dvc.repo import Repo
@@ -20,15 +19,16 @@ def get_url(path, repo=None, rev=None, remote=None):
directory in the remote storage.
"""
with Repo.open(repo, rev=rev, subrepos=True, uninitialized=True) as _repo:
path_info = PathInfo(_repo.root_dir) / path
fs_path = _repo.fs.path.join(_repo.root_dir, path)
with reraise(FileNotFoundError, PathMissingError(path, repo)):
metadata = _repo.repo_fs.metadata(path_info)
info = _repo.repo_fs.info(fs_path)
if not metadata.is_dvc:
if not info["isdvc"]:
raise OutputNotFoundError(path, repo)
cloud = metadata.repo.cloud
md5 = metadata.repo.dvcfs.info(path_info)["md5"]
cloud = info["repo"].cloud
dvc_path = _repo.fs.path.relpath(fs_path, info["repo"].root_dir)
md5 = info["repo"].dvcfs.info(dvc_path)["md5"]
return cloud.get_url_for(remote, checksum=md5)
+138
View File
@@ -0,0 +1,138 @@
"""This module provides an entrypoint to the dvc cli and parsing utils."""
import logging
# Workaround for CPython bug. See [1] and [2] for more info.
# [1] https://github.com/aws/aws-cli/blob/1.16.277/awscli/clidriver.py#L55
# [2] https://bugs.python.org/issue29288
"".encode("idna")
logger = logging.getLogger("dvc")
class DvcParserError(Exception):
"""Base class for CLI parser errors."""
def __init__(self):
super().__init__("parser error")
def parse_args(argv=None):
"""Parses CLI arguments.
Args:
argv: optional list of arguments to parse. sys.argv is used by default.
Raises:
DvcParserError: raised for argument parsing errors.
"""
from .parser import get_main_parser
parser = get_main_parser()
args = parser.parse_args(argv)
return args
def main(argv=None): # noqa: C901
"""Main entry point for dvc CLI.
Args:
argv: optional list of arguments to parse. sys.argv is used by default.
Returns:
int: command's return code.
"""
from dvc._debug import debugtools
from dvc.config import ConfigError
from dvc.exceptions import DvcException, NotDvcRepoError
from dvc.logger import FOOTER, disable_other_loggers
args = None
disable_other_loggers()
outerLogLevel = logger.level
try:
args = parse_args(argv)
level = None
if args.quiet:
level = logging.CRITICAL
elif args.verbose == 1:
level = logging.DEBUG
elif args.verbose > 1:
level = logging.TRACE
if level is not None:
logger.setLevel(level)
logger.trace(args)
if not args.quiet:
from dvc.ui import ui
ui.enable()
with debugtools(args):
cmd = args.func(args)
ret = cmd.do_run()
except ConfigError:
logger.exception("configuration error")
ret = 251
except KeyboardInterrupt:
logger.exception("interrupted by the user")
ret = 252
except NotDvcRepoError:
logger.exception("")
ret = 253
except DvcException:
ret = 255
logger.exception("")
except DvcParserError:
ret = 254
except Exception as exc: # noqa, pylint: disable=broad-except
# pylint: disable=no-member
import errno
if isinstance(exc, OSError) and exc.errno == errno.EMFILE:
from dvc.utils import error_link
logger.exception(
"too many open files, please visit "
"{} to see how to handle this "
"problem".format(error_link("many-files")),
extra={"tb_only": True},
)
else:
from dvc.info import get_dvc_info
logger.exception("unexpected error")
dvc_info = get_dvc_info()
logger.debug("Version info for developers:\n%s", dvc_info)
logger.info(FOOTER)
ret = 255
try:
from dvc import analytics
if analytics.is_enabled():
analytics.collect_and_send_report(args, ret)
return ret
finally:
logger.setLevel(outerLogLevel)
from dvc.fs.pool import close_pools
# Closing pools by-hand to prevent weird messages when closing SSH
# connections. See https://github.com/iterative/dvc/issues/3248 for
# more info.
close_pools()
from dvc.external_repo import clean_repos
# Remove cached repos in the end of the call, these are anonymous
# so won't be reused by any other subsequent run anyway.
clean_repos()
@@ -5,29 +5,6 @@ from abc import ABC, abstractmethod
logger = logging.getLogger(__name__)
def fix_subparsers(subparsers):
"""Workaround for bug in Python 3. See more info at:
https://bugs.python.org/issue16308
https://github.com/iterative/dvc/issues/769
Args:
subparsers: subparsers to fix.
"""
subparsers.required = True
subparsers.dest = "cmd"
def append_doc_link(help_message, path):
from dvc.utils import format_link
if not path:
return help_message
doc_base = "https://man.dvc.org/"
return "{message}\nDocumentation: {link}".format(
message=help_message, link=format_link(doc_base + path)
)
class CmdBase(ABC):
UNINITIALIZED = False
+9 -20
View File
@@ -1,11 +1,10 @@
"""DVC command line interface"""
"""Main parser for the dvc cli."""
import argparse
import logging
import os
import sys
from ._debug import add_debugging_flags
from .command import (
from dvc.commands import (
add,
cache,
check_ignore,
@@ -45,8 +44,8 @@ from .command import (
update,
version,
)
from .command.base import fix_subparsers
from .exceptions import DvcParserError
from . import DvcParserError
logger = logging.getLogger(__name__)
@@ -96,7 +95,7 @@ def _find_parser(parser, cmd_cls):
defaults = parser._defaults # pylint: disable=protected-access
if not cmd_cls or cmd_cls == defaults.get("func"):
parser.print_help()
raise DvcParserError()
raise DvcParserError
actions = parser._actions # pylint: disable=protected-access
for action in actions:
@@ -143,6 +142,8 @@ def get_parent_parser():
When overwriting `-q` or `-v`, you need to instantiate a new object
in order to prevent some weird behavior.
"""
from dvc._debug import add_debugging_flags
parent_parser = argparse.ArgumentParser(add_help=False)
add_debugging_flags(parent_parser)
log_level_group = parent_parser.add_mutually_exclusive_group()
@@ -207,23 +208,11 @@ def get_main_parser():
help="Use `dvc COMMAND --help` for command-specific help.",
)
from .utils import fix_subparsers
fix_subparsers(subparsers)
for cmd in COMMANDS:
cmd.add_parser(subparsers, parent_parser)
return parser
def parse_args(argv=None):
"""Parses CLI arguments.
Args:
argv: optional list of arguments to parse. sys.argv is used by default.
Raises:
dvc.exceptions.DvcParserError: raised for argument parsing errors.
"""
parser = get_main_parser()
args = parser.parse_args(argv)
return args
+31
View File
@@ -0,0 +1,31 @@
def fix_subparsers(subparsers):
"""Workaround for bug in Python 3. See more info at:
https://bugs.python.org/issue16308
https://github.com/iterative/dvc/issues/769
Args:
subparsers: subparsers to fix.
"""
subparsers.required = True
subparsers.dest = "cmd"
def append_doc_link(help_message, path):
from dvc.utils import format_link
if not path:
return help_message
doc_base = "https://man.dvc.org/"
return "{message}\nDocumentation: {link}".format(
message=help_message, link=format_link(doc_base + path)
)
def fix_plumbing_subparsers(subparsers):
# metavar needs to be explicitly set in order to hide plumbing subcommands
# from the 'positional arguments' choices list
# see: https://bugs.python.org/issue22848
cmds = [
cmd for cmd, parser in subparsers.choices.items() if parser.add_help
]
subparsers.metavar = "{{{}}}".format(",".join(cmds))
File diff suppressed because it is too large Load Diff
+6 -3
View File
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
logger = logging.getLogger(__name__)
@@ -101,7 +102,9 @@ def add_parser(subparsers, parent_parser):
"--jobs",
type=int,
help=(
"Number of jobs to run simultaneously. "
"Only used along with '--to-remote'. "
"Number of jobs to run simultaneously "
"when pushing data to remote."
"The default value is 4 * cpu_count(). "
"For SSH remotes, the default is 4. "
),
@@ -1,8 +1,8 @@
import argparse
from dvc.command import completion
from dvc.command.base import append_doc_link, fix_subparsers
from dvc.command.config import CmdConfig
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.commands import completion
from dvc.commands.config import CmdConfig
from dvc.ui import ui
@@ -28,7 +28,7 @@ class CmdCacheDir(CmdConfig):
def add_parser(subparsers, parent_parser):
from dvc.command.config import parent_config_parser
from dvc.commands.config import parent_config_parser
CACHE_HELP = "Manage cache settings."
@@ -1,7 +1,8 @@
import argparse
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.ui import ui
@@ -1,8 +1,9 @@
import argparse
import operator
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.exceptions import CheckoutError
from dvc.ui import ui
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
logger = logging.getLogger(__name__)
@@ -3,7 +3,8 @@ import logging
import shtab
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.ui import ui
logger = logging.getLogger(__name__)
@@ -82,7 +83,7 @@ EXPERIMENT = {"bash": "_dvc_compgen_exps", "zsh": "_dvc_compadd_exps"}
class CmdCompletion(CmdBaseNoRepo):
def run(self):
from dvc.cli import get_main_parser
from dvc.cli.parser import get_main_parser
parser = get_main_parser()
shell = self.args.shell
@@ -2,7 +2,8 @@ import argparse
import logging
import os
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.ui import ui
logger = logging.getLogger(__name__)
@@ -1,5 +1,6 @@
from dvc.command import completion
from dvc.command.base import CmdBaseNoRepo, fix_subparsers
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import fix_subparsers
from dvc.commands import completion
class CmdDaemonBase(CmdBaseNoRepo):
+7 -4
View File
@@ -1,6 +1,7 @@
import argparse
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.ui import ui
@@ -38,12 +39,14 @@ def _collect_targets(repo, target, outs):
targets = []
outs_trie = repo.index.outs_trie
for stage, info in pairs:
if not info:
for stage, path in pairs:
if not path:
targets.extend([str(out) for out in stage.outs])
continue
for out in outs_trie.itervalues(prefix=info.parts): # noqa: B301
for out in outs_trie.itervalues(
prefix=repo.fs.path.parts(path)
): # noqa: B301
targets.extend(str(out))
return targets
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
logger = logging.getLogger(__name__)
@@ -18,7 +19,7 @@ class CmdDataBase(CmdBase):
class CmdDataPull(CmdDataBase):
def log_summary(self, stats):
from dvc.command.checkout import log_changes
from dvc.commands.checkout import log_changes
log_changes(stats)
super().log_summary(stats)
@@ -97,7 +98,7 @@ class CmdDataFetch(CmdDataBase):
def shared_parent_parser():
from dvc.cli import get_parent_parser
from dvc.cli.parser import get_parent_parser
# Parent parser used in pull/push/status
parent_parser = argparse.ArgumentParser(
@@ -127,7 +128,7 @@ def shared_parent_parser():
def add_parser(subparsers, _parent_parser):
from dvc.command.status import CmdDataStatus
from dvc.commands.status import CmdDataStatus
# Pull
PULL_HELP = "Download tracked files or directories from remote storage."
@@ -1,7 +1,8 @@
import argparse
import logging
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
logger = logging.getLogger(__name__)
+3 -2
View File
@@ -2,8 +2,9 @@ import argparse
import logging
import os
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.ui import ui
logger = logging.getLogger(__name__)
+60
View File
@@ -0,0 +1,60 @@
import argparse
from dvc.cli.utils import (
append_doc_link,
fix_plumbing_subparsers,
fix_subparsers,
)
from dvc.commands.experiments import (
apply,
branch,
diff,
exec_run,
gc,
init,
ls,
pull,
push,
remove,
run,
show,
)
SUB_COMMANDS = [
apply,
branch,
diff,
exec_run,
gc,
init,
ls,
pull,
push,
remove,
run,
show,
]
def add_parser(subparsers, parent_parser):
EXPERIMENTS_HELP = "Commands to run and compare experiments."
experiments_parser = subparsers.add_parser(
"experiments",
parents=[parent_parser],
aliases=["exp"],
description=append_doc_link(EXPERIMENTS_HELP, "exp"),
formatter_class=argparse.RawDescriptionHelpFormatter,
help=EXPERIMENTS_HELP,
)
experiments_subparsers = experiments_parser.add_subparsers(
dest="cmd",
help="Use `dvc experiments CMD --help` to display "
"command-specific help.",
)
fix_subparsers(experiments_subparsers)
for cmd in SUB_COMMANDS:
cmd.add_parser(experiments_subparsers, parent_parser)
fix_plumbing_subparsers(experiments_subparsers)
+42
View File
@@ -0,0 +1,42 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
logger = logging.getLogger(__name__)
class CmdExperimentsApply(CmdBase):
def run(self):
self.repo.experiments.apply(
self.args.experiment, force=self.args.force
)
return 0
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_APPLY_HELP = (
"Apply the changes from an experiment to your workspace."
)
experiments_apply_parser = experiments_subparsers.add_parser(
"apply",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_APPLY_HELP, "exp/apply"),
help=EXPERIMENTS_APPLY_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
experiments_apply_parser.add_argument(
"--no-force",
action="store_false",
dest="force",
help="Fail if this command would overwrite conflicting changes.",
)
experiments_apply_parser.add_argument(
"experiment", help="Experiment to be applied."
).complete = completion.EXPERIMENT
experiments_apply_parser.set_defaults(func=CmdExperimentsApply)
+34
View File
@@ -0,0 +1,34 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
logger = logging.getLogger(__name__)
class CmdExperimentsBranch(CmdBase):
def run(self):
self.repo.experiments.branch(self.args.experiment, self.args.branch)
return 0
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_BRANCH_HELP = "Promote an experiment to a Git branch."
experiments_branch_parser = experiments_subparsers.add_parser(
"branch",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_BRANCH_HELP, "exp/branch"),
help=EXPERIMENTS_BRANCH_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
experiments_branch_parser.add_argument(
"experiment", help="Experiment to be promoted."
)
experiments_branch_parser.add_argument(
"branch", help="Git branch name to use."
)
experiments_branch_parser.set_defaults(func=CmdExperimentsBranch)
+118
View File
@@ -0,0 +1,118 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.commands.metrics import DEFAULT_PRECISION
from dvc.exceptions import DvcException
from dvc.ui import ui
logger = logging.getLogger(__name__)
class CmdExperimentsDiff(CmdBase):
def run(self):
try:
diff = self.repo.experiments.diff(
a_rev=self.args.a_rev,
b_rev=self.args.b_rev,
all=self.args.all,
param_deps=self.args.param_deps,
)
except DvcException:
logger.exception("failed to show experiments diff")
return 1
if self.args.json:
ui.write_json(diff)
else:
from dvc.compare import show_diff
precision = self.args.precision or DEFAULT_PRECISION
diffs = [("metrics", "Metric"), ("params", "Param")]
for idx, (key, title) in enumerate(diffs):
if idx:
# we are printing tables even in `--quiet` mode
# so we should also be printing the "table" separator
ui.write(force=True)
show_diff(
diff[key],
title=title,
markdown=self.args.markdown,
no_path=self.args.no_path,
on_empty_diff="diff not supported",
precision=precision if key == "metrics" else None,
a_rev=self.args.a_rev,
b_rev=self.args.b_rev,
)
return 0
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_DIFF_HELP = (
"Show changes between experiments in the DVC repository."
)
experiments_diff_parser = experiments_subparsers.add_parser(
"diff",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_DIFF_HELP, "exp/diff"),
help=EXPERIMENTS_DIFF_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
experiments_diff_parser.add_argument(
"a_rev", nargs="?", help="Old experiment to compare (defaults to HEAD)"
).complete = completion.EXPERIMENT
experiments_diff_parser.add_argument(
"b_rev",
nargs="?",
help="New experiment to compare (defaults to the current workspace)",
).complete = completion.EXPERIMENT
experiments_diff_parser.add_argument(
"--all",
action="store_true",
default=False,
help="Show unchanged metrics/params as well.",
)
experiments_diff_parser.add_argument(
"--param-deps",
action="store_true",
default=False,
help="Show only params that are stage dependencies.",
)
experiments_diff_parser.add_argument(
"--json",
"--show-json",
action="store_true",
default=False,
help="Show output in JSON format.",
)
experiments_diff_parser.add_argument(
"--md",
"--show-md",
action="store_true",
default=False,
dest="markdown",
help="Show tabulated output in the Markdown format (GFM).",
)
experiments_diff_parser.add_argument(
"--no-path",
action="store_true",
default=False,
help="Don't show metric/param path.",
)
experiments_diff_parser.add_argument(
"--precision",
type=int,
help=(
"Round metrics/params to `n` digits precision after the decimal "
f"point. Rounds to {DEFAULT_PRECISION} digits by default."
),
metavar="<n>",
)
experiments_diff_parser.set_defaults(func=CmdExperimentsDiff)
+42
View File
@@ -0,0 +1,42 @@
import logging
from dvc.cli.command import CmdBaseNoRepo
logger = logging.getLogger(__name__)
class CmdExecutorRun(CmdBaseNoRepo):
"""Run an experiment executor."""
def run(self):
from dvc.repo.experiments.executor.base import (
BaseExecutor,
ExecutorInfo,
)
from dvc.utils.serialize import load_json
info = ExecutorInfo.from_dict(load_json(self.args.infofile))
BaseExecutor.reproduce(
info=info,
rev="",
queue=None,
log_level=logger.getEffectiveLevel(),
infofile=self.args.infofile,
)
return 0
def add_parser(experiments_subparsers, parent_parser):
EXEC_RUN_HELP = "Run an experiment executor."
exec_run_parser = experiments_subparsers.add_parser(
"exec-run",
parents=[parent_parser],
description=EXEC_RUN_HELP,
add_help=False,
)
exec_run_parser.add_argument(
"--infofile",
help="Path to executor info file",
default=None,
)
exec_run_parser.set_defaults(func=CmdExecutorRun)
+129
View File
@@ -0,0 +1,129 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import InvalidArgumentError
from dvc.ui import ui
logger = logging.getLogger(__name__)
def _raise_error_if_all_disabled(**kwargs):
if not any(kwargs.values()):
raise InvalidArgumentError(
"Either of `-w|--workspace`, `-a|--all-branches`, `-T|--all-tags` "
"or `--all-commits` needs to be set."
)
class CmdExperimentsGC(CmdBase):
def run(self):
_raise_error_if_all_disabled(
all_branches=self.args.all_branches,
all_tags=self.args.all_tags,
all_commits=self.args.all_commits,
workspace=self.args.workspace,
)
msg = "This will remove all experiments except those derived from "
msg += "the workspace"
if self.args.all_commits:
msg += " and all git commits"
elif self.args.all_branches and self.args.all_tags:
msg += " and all git branches and tags"
elif self.args.all_branches:
msg += " and all git branches"
elif self.args.all_tags:
msg += " and all git tags"
msg += " of the current repo."
if self.args.queued:
msg += " Run queued experiments will be preserved."
else:
msg += " Run queued experiments will be removed."
logger.warning(msg)
msg = "Are you sure you want to proceed?"
if not self.args.force and not ui.confirm(msg):
return 1
removed = self.repo.experiments.gc(
all_branches=self.args.all_branches,
all_tags=self.args.all_tags,
all_commits=self.args.all_commits,
workspace=self.args.workspace,
queued=self.args.queued,
)
if removed:
ui.write(
f"Removed {removed} experiments.",
"To remove unused cache files",
"use 'dvc gc'.",
)
else:
ui.write("No experiments to remove.")
return 0
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_GC_HELP = "Garbage collect unneeded experiments."
EXPERIMENTS_GC_DESCRIPTION = (
"Removes all experiments which are not derived from the specified"
"Git revisions."
)
experiments_gc_parser = experiments_subparsers.add_parser(
"gc",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_GC_DESCRIPTION, "exp/gc"),
help=EXPERIMENTS_GC_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
experiments_gc_parser.add_argument(
"-w",
"--workspace",
action="store_true",
default=False,
help="Keep experiments derived from the current workspace.",
)
experiments_gc_parser.add_argument(
"-a",
"--all-branches",
action="store_true",
default=False,
help="Keep experiments derived from the tips of all Git branches.",
)
experiments_gc_parser.add_argument(
"-T",
"--all-tags",
action="store_true",
default=False,
help="Keep experiments derived from all Git tags.",
)
experiments_gc_parser.add_argument(
"-A",
"--all-commits",
action="store_true",
default=False,
help="Keep experiments derived from all Git commits.",
)
experiments_gc_parser.add_argument(
"--queued",
action="store_true",
default=False,
help=(
"Keep queued experiments (experiments run queue will be cleared "
"by default)."
),
)
experiments_gc_parser.add_argument(
"-f",
"--force",
action="store_true",
default=False,
help="Force garbage collection - automatically agree to all prompts.",
)
experiments_gc_parser.set_defaults(func=CmdExperimentsGC)
+188
View File
@@ -0,0 +1,188 @@
import argparse
import logging
from funcy import compact
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import InvalidArgumentError
from dvc.ui import ui
logger = logging.getLogger(__name__)
class CmdExperimentsInit(CmdBase):
DEFAULT_NAME = "train"
CODE = "src"
DATA = "data"
MODELS = "models"
DEFAULT_METRICS = "metrics.json"
DEFAULT_PARAMS = "params.yaml"
PLOTS = "plots"
DVCLIVE = "dvclive"
DEFAULTS = {
"code": CODE,
"data": DATA,
"models": MODELS,
"metrics": DEFAULT_METRICS,
"params": DEFAULT_PARAMS,
"plots": PLOTS,
"live": DVCLIVE,
}
EXP_LINK = "https://s.dvc.org/g/exp/run"
def run(self):
from dvc.commands.stage import parse_cmd
cmd = parse_cmd(self.args.command)
if not self.args.interactive and not cmd:
raise InvalidArgumentError("command is not specified")
from dvc.repo.experiments.init import init
defaults = {}
if not self.args.explicit:
config = self.repo.config["exp"]
defaults.update({**self.DEFAULTS, **config})
cli_args = compact(
{
"cmd": cmd,
"code": self.args.code,
"data": self.args.data,
"models": self.args.models,
"metrics": self.args.metrics,
"params": self.args.params,
"plots": self.args.plots,
"live": self.args.live,
}
)
initialized_stage = init(
self.repo,
name=self.args.name,
type=self.args.type,
defaults=defaults,
overrides=cli_args,
interactive=self.args.interactive,
force=self.args.force,
)
text = ui.rich_text.assemble(
"\n" if self.args.interactive else "",
"Created ",
(self.args.name, "bright_blue"),
" stage in ",
("dvc.yaml", "green"),
".",
)
if not self.args.run:
text.append_text(
ui.rich_text.assemble(
" To run, use ",
('"dvc exp run"', "green"),
".\nSee ",
(self.EXP_LINK, "repr.url"),
".",
)
)
ui.write(text, styled=True)
if self.args.run:
return self.repo.experiments.run(
targets=[initialized_stage.addressing]
)
return 0
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_INIT_HELP = "Quickly setup any project to use experiments."
experiments_init_parser = experiments_subparsers.add_parser(
"init",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_INIT_HELP, "exp/init"),
formatter_class=argparse.RawDescriptionHelpFormatter,
help=EXPERIMENTS_INIT_HELP,
)
experiments_init_parser.add_argument(
"command",
nargs=argparse.REMAINDER,
help="Command to execute.",
metavar="command",
)
experiments_init_parser.add_argument(
"--run",
action="store_true",
help="Run the experiment after initializing it",
)
experiments_init_parser.add_argument(
"--interactive",
"-i",
action="store_true",
help="Prompt for values that are not provided",
)
experiments_init_parser.add_argument(
"-f",
"--force",
action="store_true",
default=False,
help="Overwrite existing stage",
)
experiments_init_parser.add_argument(
"--explicit",
action="store_true",
default=False,
help="Only use the path values explicitly provided",
)
experiments_init_parser.add_argument(
"--name",
"-n",
help="Name of the stage to create (default: %(default)s)",
default=CmdExperimentsInit.DEFAULT_NAME,
)
experiments_init_parser.add_argument(
"--code",
help="Path to the source file or directory "
"which your experiments depend"
f" (default: {CmdExperimentsInit.CODE})",
)
experiments_init_parser.add_argument(
"--data",
help="Path to the data file or directory "
"which your experiments depend"
f" (default: {CmdExperimentsInit.DATA})",
)
experiments_init_parser.add_argument(
"--models",
help="Path to the model file or directory for your experiments"
f" (default: {CmdExperimentsInit.MODELS})",
)
experiments_init_parser.add_argument(
"--params",
help="Path to the parameters file for your experiments"
f" (default: {CmdExperimentsInit.DEFAULT_PARAMS})",
)
experiments_init_parser.add_argument(
"--metrics",
help="Path to the metrics file for your experiments"
f" (default: {CmdExperimentsInit.DEFAULT_METRICS})",
)
experiments_init_parser.add_argument(
"--plots",
help="Path to the plots file or directory for your experiments"
f" (default: {CmdExperimentsInit.PLOTS})",
)
experiments_init_parser.add_argument(
"--live",
help="Path to log dvclive outputs for your experiments"
f" (default: {CmdExperimentsInit.DVCLIVE})",
)
experiments_init_parser.add_argument(
"--type",
choices=["default", "dl"],
default="default",
help="Select type of stage to create (default: %(default)s)",
)
experiments_init_parser.set_defaults(func=CmdExperimentsInit)
+73
View File
@@ -0,0 +1,73 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
logger = logging.getLogger(__name__)
class CmdExperimentsList(CmdBase):
def run(self):
names_only = self.args.names_only
exps = self.repo.experiments.ls(
rev=self.args.rev,
git_remote=self.args.git_remote,
all_=self.args.all,
)
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 names_only:
print(f"{name}:")
for exp_name in exps[baseline]:
indent = "" if names_only else "\t"
print(f"{indent}{exp_name}")
return 0
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_LIST_HELP = "List local and remote experiments."
experiments_list_parser = experiments_subparsers.add_parser(
"list",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_LIST_HELP, "exp/list"),
help=EXPERIMENTS_LIST_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
experiments_list_parser.add_argument(
"--rev",
type=str,
default=None,
help=(
"List experiments derived from the specified revision. "
"Defaults to HEAD if neither `--rev` nor `--all` are specified."
),
metavar="<rev>",
)
experiments_list_parser.add_argument(
"--all", action="store_true", help="List all experiments."
)
experiments_list_parser.add_argument(
"--names-only",
action="store_true",
help="Only output experiment names (without parent commits).",
)
experiments_list_parser.add_argument(
"git_remote",
nargs="?",
default=None,
help=(
"Optional Git remote name or Git URL. "
"If provided, experiments from the specified Git repository "
" will be listed instead of local ones."
),
metavar="[<git_remote>]",
)
experiments_list_parser.set_defaults(func=CmdExperimentsList)
+96
View File
@@ -0,0 +1,96 @@
import argparse
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__)
class CmdExperimentsPull(CmdBase):
def run(self):
self.repo.experiments.pull(
self.args.git_remote,
self.args.experiment,
force=self.args.force,
pull_cache=self.args.pull_cache,
dvc_remote=self.args.dvc_remote,
jobs=self.args.jobs,
run_cache=self.args.run_cache,
)
ui.write(
f"Pulled experiment '{self.args.experiment}'",
f"from Git remote '{self.args.git_remote}'.",
)
if not self.args.pull_cache:
ui.write(
"To pull cached outputs for this experiment"
"from DVC remote storage,"
"re-run this command without '--no-cache'."
)
return 0
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_PULL_HELP = "Pull an experiment from a Git remote."
experiments_pull_parser = experiments_subparsers.add_parser(
"pull",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_PULL_HELP, "exp/pull"),
help=EXPERIMENTS_PULL_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
experiments_pull_parser.add_argument(
"-f",
"--force",
action="store_true",
help="Replace local experiment already exists.",
)
experiments_pull_parser.add_argument(
"--no-cache",
action="store_false",
dest="pull_cache",
help=(
"Do not pull cached outputs for this experiment from DVC remote "
"storage."
),
)
experiments_pull_parser.add_argument(
"-r",
"--remote",
dest="dvc_remote",
metavar="<name>",
help="Name of the DVC remote to use when pulling cached outputs.",
)
experiments_pull_parser.add_argument(
"-j",
"--jobs",
type=int,
metavar="<number>",
help=(
"Number of jobs to run simultaneously when pulling from DVC "
"remote storage."
),
)
experiments_pull_parser.add_argument(
"--run-cache",
action="store_true",
default=False,
help="Pull run history for all stages.",
)
experiments_pull_parser.add_argument(
"git_remote",
help="Git remote name or Git URL.",
metavar="<git_remote>",
)
experiments_pull_parser.add_argument(
"experiment",
nargs="+",
help="Experiments to pull.",
metavar="<experiment>",
)
experiments_pull_parser.set_defaults(func=CmdExperimentsPull)
+97
View File
@@ -0,0 +1,97 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.ui import ui
logger = logging.getLogger(__name__)
class CmdExperimentsPush(CmdBase):
def run(self):
self.repo.experiments.push(
self.args.git_remote,
self.args.experiment,
force=self.args.force,
push_cache=self.args.push_cache,
dvc_remote=self.args.dvc_remote,
jobs=self.args.jobs,
run_cache=self.args.run_cache,
)
ui.write(
f"Pushed experiment '{self.args.experiment}'"
f"to Git remote '{self.args.git_remote}'."
)
if not self.args.push_cache:
ui.write(
"To push cached outputs",
"for this experiment to DVC remote storage,"
"re-run this command without '--no-cache'.",
)
return 0
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_PUSH_HELP = "Push a local experiment to a Git remote."
experiments_push_parser = experiments_subparsers.add_parser(
"push",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_PUSH_HELP, "exp/push"),
help=EXPERIMENTS_PUSH_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
experiments_push_parser.add_argument(
"-f",
"--force",
action="store_true",
help="Replace experiment in the Git remote if it already exists.",
)
experiments_push_parser.add_argument(
"--no-cache",
action="store_false",
dest="push_cache",
help=(
"Do not push cached outputs for this experiment to DVC remote "
"storage."
),
)
experiments_push_parser.add_argument(
"-r",
"--remote",
dest="dvc_remote",
metavar="<name>",
help="Name of the DVC remote to use when pushing cached outputs.",
)
experiments_push_parser.add_argument(
"-j",
"--jobs",
type=int,
metavar="<number>",
help=(
"Number of jobs to run simultaneously when pushing to DVC remote "
"storage."
),
)
experiments_push_parser.add_argument(
"--run-cache",
action="store_true",
default=False,
help="Push run history for all stages.",
)
experiments_push_parser.add_argument(
"git_remote",
help="Git remote name or Git URL.",
metavar="<git_remote>",
)
experiments_push_parser.add_argument(
"experiment",
nargs="+",
help="Experiments to push.",
metavar="<experiment>",
).complete = completion.EXPERIMENT
experiments_push_parser.set_defaults(func=CmdExperimentsPush)
+55
View File
@@ -0,0 +1,55 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
logger = logging.getLogger(__name__)
class CmdExperimentsRemove(CmdBase):
def run(self):
self.repo.experiments.remove(
exp_names=self.args.experiment,
queue=self.args.queue,
clear_all=self.args.all,
remote=self.args.git_remote,
)
return 0
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_REMOVE_HELP = "Remove experiments."
experiments_remove_parser = experiments_subparsers.add_parser(
"remove",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_REMOVE_HELP, "exp/remove"),
help=EXPERIMENTS_REMOVE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
remove_group = experiments_remove_parser.add_mutually_exclusive_group()
remove_group.add_argument(
"--queue", action="store_true", help="Remove all queued experiments."
)
remove_group.add_argument(
"-A",
"--all",
action="store_true",
help="Remove all committed experiments.",
)
remove_group.add_argument(
"-g",
"--git-remote",
metavar="<git_remote>",
help="Name or URL of the Git remote to remove the experiment from",
)
experiments_remove_parser.add_argument(
"experiment",
nargs="*",
help="Experiments to remove.",
metavar="<experiment>",
)
experiments_remove_parser.set_defaults(func=CmdExperimentsRemove)
+142
View File
@@ -0,0 +1,142 @@
import argparse
import logging
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.commands.repro import CmdRepro
from dvc.commands.repro import add_arguments as add_repro_arguments
from dvc.exceptions import InvalidArgumentError
from dvc.ui import ui
logger = logging.getLogger(__name__)
class CmdExperimentsRun(CmdRepro):
def run(self):
from dvc.compare import show_metrics
if self.args.checkpoint_resume:
if self.args.reset:
raise InvalidArgumentError(
"--reset and --rev are mutually exclusive."
)
if not (self.args.queue or self.args.tmp_dir):
raise InvalidArgumentError(
"--rev can only be used in conjunction with "
"--queue or --temp."
)
if self.args.reset:
ui.write("Any existing checkpoints will be reset and re-run.")
results = self.repo.experiments.run(
name=self.args.name,
queue=self.args.queue,
run_all=self.args.run_all,
jobs=self.args.jobs,
params=self.args.set_param,
checkpoint_resume=self.args.checkpoint_resume,
reset=self.args.reset,
tmp_dir=self.args.tmp_dir,
machine=self.args.machine,
**self._repro_kwargs,
)
if self.args.metrics and results:
metrics = self.repo.metrics.show(revs=list(results))
metrics.pop("workspace", None)
show_metrics(metrics)
return 0
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_RUN_HELP = "Run or resume an experiment."
experiments_run_parser = experiments_subparsers.add_parser(
"run",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_RUN_HELP, "exp/run"),
help=EXPERIMENTS_RUN_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
_add_run_common(experiments_run_parser)
experiments_run_parser.add_argument(
"-r",
"--rev",
type=str,
dest="checkpoint_resume",
help=(
"Continue the specified checkpoint experiment. Can only be used "
"in conjunction with --queue or --temp."
),
metavar="<experiment_rev>",
).complete = completion.EXPERIMENT
experiments_run_parser.add_argument(
"--reset",
action="store_true",
help="Reset existing checkpoints and restart the experiment.",
)
experiments_run_parser.set_defaults(func=CmdExperimentsRun)
def _add_run_common(parser):
"""Add common args for 'exp run' and 'exp resume'."""
# inherit arguments from `dvc repro`
add_repro_arguments(parser)
parser.add_argument(
"-n",
"--name",
default=None,
help=(
"Human-readable experiment name. If not specified, a name will "
"be auto-generated."
),
metavar="<name>",
)
parser.add_argument(
"-S",
"--set-param",
action="append",
default=[],
help="Use the specified param value when reproducing pipelines.",
metavar="[<filename>:]<param_name>=<param_value>",
)
parser.add_argument(
"--queue",
action="store_true",
default=False,
help="Stage this experiment in the run queue for future execution.",
)
parser.add_argument(
"--run-all",
action="store_true",
default=False,
help="Execute all experiments in the run queue. Implies --temp.",
)
parser.add_argument(
"-j",
"--jobs",
type=int,
default=1,
help="Run the specified number of experiments at a time in parallel.",
metavar="<number>",
)
parser.add_argument(
"--temp",
action="store_true",
dest="tmp_dir",
help=(
"Run this experiment in a separate temporary directory instead of "
"your workspace."
),
)
parser.add_argument(
"--machine",
default=None,
help=argparse.SUPPRESS,
# help=(
# "Run this experiment on the specified 'dvc machine' instance."
# )
# metavar="<name>",
)
+669
View File
@@ -0,0 +1,669 @@
import argparse
import logging
import os
import re
from collections import Counter, OrderedDict, defaultdict
from datetime import date, datetime
from typing import TYPE_CHECKING
from funcy import lmap
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.commands.metrics import DEFAULT_PRECISION
from dvc.exceptions import DvcException, InvalidArgumentError
from dvc.ui import ui
from dvc.utils.flatten import flatten
from dvc.utils.serialize import encode_exception
if TYPE_CHECKING:
from dvc.compare import TabularData
from dvc.ui import RichText
FILL_VALUE = "-"
FILL_VALUE_ERRORED = "!"
logger = logging.getLogger(__name__)
def _update_names(names, items):
for name, item in items:
item = item.get("data", {})
if isinstance(item, dict):
item = flatten(item)
names[name].update({key: None for key in item})
def _collect_names(all_experiments, **kwargs):
metric_names = defaultdict(dict)
param_names = defaultdict(dict)
deps_names = set()
for _, experiments in all_experiments.items():
for exp_data in experiments.values():
exp = exp_data.get("data", {})
_update_names(metric_names, exp.get("metrics", {}).items())
_update_names(param_names, exp.get("params", {}).items())
for dep_name in exp.get("deps", {}):
deps_names.add(dep_name)
return metric_names, param_names, sorted(deps_names)
experiment_types = {
"checkpoint_tip": "│ ╓",
"checkpoint_commit": "│ ╟",
"checkpoint_base": "├─╨",
"branch_commit": "├──",
"branch_base": "└──",
"baseline": "",
}
def _collect_rows(
base_rev,
experiments,
all_headers,
metric_headers,
param_headers,
metric_names,
param_names,
deps_names,
precision=DEFAULT_PRECISION,
sort_by=None,
sort_order=None,
fill_value=FILL_VALUE,
iso=False,
):
from scmrepo.git import Git
if sort_by:
sort_path, sort_name, sort_type = _sort_column(
sort_by, metric_names, param_names
)
reverse = sort_order == "desc"
experiments = _sort_exp(
experiments, sort_path, sort_name, sort_type, reverse
)
new_checkpoint = True
for i, (rev, results) in enumerate(experiments.items()):
fill_value = FILL_VALUE_ERRORED if results.get("error") else fill_value
row_dict = {k: fill_value for k in all_headers}
exp = results.get("data", {})
if exp.get("running"):
state = "Running"
elif exp.get("queued"):
state = "Queued"
else:
state = fill_value
is_baseline = rev == "baseline"
if is_baseline:
name_rev = base_rev[:7] if Git.is_sha(base_rev) else base_rev
else:
name_rev = rev[:7]
tip = exp.get("checkpoint_tip")
parent_rev = exp.get("checkpoint_parent", "")
parent_exp = experiments.get(parent_rev, {}).get("data", {})
parent_tip = parent_exp.get("checkpoint_tip")
parent = ""
if is_baseline:
typ = "baseline"
elif tip:
if tip == parent_tip:
typ = (
"checkpoint_tip" if new_checkpoint else "checkpoint_commit"
)
elif parent_rev == base_rev:
typ = "checkpoint_base"
else:
typ = "checkpoint_commit"
parent = parent_rev[:7]
elif i < len(experiments) - 1:
typ = "branch_commit"
else:
typ = "branch_base"
if not is_baseline:
new_checkpoint = not (tip and tip == parent_tip)
row_dict["Experiment"] = exp.get("name", "")
row_dict["rev"] = name_rev
row_dict["typ"] = typ
row_dict["Created"] = _format_time(
exp.get("timestamp"), fill_value, iso
)
row_dict["parent"] = parent
row_dict["State"] = state
row_dict["Executor"] = exp.get("executor", fill_value)
_extend_row(
row_dict,
metric_names,
metric_headers,
exp.get("metrics", {}).items(),
precision,
fill_value=fill_value,
)
_extend_row(
row_dict,
param_names,
param_headers,
exp.get("params", {}).items(),
precision,
fill_value=fill_value,
)
for dep in deps_names:
hash_info = exp.get("deps", {}).get(dep, {}).get("hash")
if hash_info is not None:
hash_info = hash_info[:7]
row_dict[dep] = hash_info
yield list(row_dict.values())
def _sort_column(sort_by, metric_names, param_names):
path, _, sort_name = sort_by.rpartition(":")
matches = set()
if path:
if path in metric_names and sort_name in metric_names[path]:
matches.add((path, sort_name, "metrics"))
if path in param_names and sort_name in param_names[path]:
matches.add((path, sort_name, "params"))
else:
for path in metric_names:
if sort_name in metric_names[path]:
matches.add((path, sort_name, "metrics"))
for path in param_names:
if sort_name in param_names[path]:
matches.add((path, sort_name, "params"))
if len(matches) == 1:
return matches.pop()
if len(matches) > 1:
raise InvalidArgumentError(
"Ambiguous sort column '{}' matched '{}'".format(
sort_by,
", ".join([f"{path}:{name}" for path, name, _ in matches]),
)
)
raise InvalidArgumentError(f"Unknown sort column '{sort_by}'")
def _sort_exp(experiments, sort_path, sort_name, typ, reverse):
def _sort(item):
rev, exp = item
exp_data = exp.get("data", {})
tip = exp_data.get("checkpoint_tip")
if tip and tip != rev:
# Sort checkpoint experiments by tip commit
return _sort((tip, experiments[tip]))
data = exp_data.get(typ, {}).get(sort_path, {}).get("data", {})
val = flatten(data).get(sort_name)
return val is None, val
ret = OrderedDict()
if "baseline" in experiments:
ret["baseline"] = experiments.pop("baseline")
ret.update(sorted(experiments.items(), key=_sort, reverse=reverse))
return ret
def _format_time(datetime_obj, fill_value=FILL_VALUE, iso=False):
if datetime_obj is None:
return fill_value
if iso:
return datetime_obj.isoformat()
if datetime_obj.date() == date.today():
fmt = "%I:%M %p"
else:
fmt = "%b %d, %Y"
return datetime_obj.strftime(fmt)
def _extend_row(row, names, headers, items, precision, fill_value=FILL_VALUE):
from dvc.compare import _format_field, with_value
for fname, data in items:
item = data.get("data", {})
item = flatten(item) if isinstance(item, dict) else {fname: item}
for name in names[fname]:
value = with_value(
item.get(name),
FILL_VALUE_ERRORED if data.get("error", None) else fill_value,
)
# wrap field data in ui.rich_text, otherwise rich may
# interpret unescaped braces from list/dict types as rich
# markup tags
value = ui.rich_text(str(_format_field(value, precision)))
if name in headers:
row[name] = value
else:
row[f"{fname}:{name}"] = value
def experiments_table(
all_experiments,
headers,
metric_headers,
metric_names,
param_headers,
param_names,
deps_names,
sort_by=None,
sort_order=None,
precision=DEFAULT_PRECISION,
fill_value=FILL_VALUE,
iso=False,
) -> "TabularData":
from funcy import lconcat
from dvc.compare import TabularData
all_headers = lconcat(headers, metric_headers, param_headers, deps_names)
td = TabularData(all_headers, fill_value=fill_value)
for base_rev, experiments in all_experiments.items():
rows = _collect_rows(
base_rev,
experiments,
all_headers,
metric_headers,
param_headers,
metric_names,
param_names,
deps_names,
sort_by=sort_by,
sort_order=sort_order,
precision=precision,
fill_value=fill_value,
iso=iso,
)
td.extend(rows)
return td
def prepare_exp_id(kwargs) -> "RichText":
exp_name = kwargs["Experiment"]
rev = kwargs["rev"]
typ = kwargs.get("typ", "baseline")
if typ == "baseline" or not exp_name:
text = ui.rich_text(exp_name or rev)
else:
text = ui.rich_text.assemble(rev, " [", (exp_name, "bold"), "]")
parent = kwargs.get("parent")
suff = f" ({parent})" if parent else ""
text.append(suff)
tree = experiment_types[typ]
pref = f"{tree} " if tree else ""
return ui.rich_text(pref) + text
def baseline_styler(typ):
return {"style": "bold"} if typ == "baseline" else {}
def show_experiments(
all_experiments,
keep=None,
drop=None,
pager=True,
csv=False,
markdown=False,
pcp=False,
**kwargs,
):
from funcy.seqs import flatten as flatten_list
metric_names, param_names, deps_names = _collect_names(all_experiments)
headers = [
"Experiment",
"rev",
"typ",
"Created",
"parent",
"State",
"Executor",
]
names = {**metric_names, **param_names}
counter = Counter(flatten_list([list(a.keys()) for a in names.values()]))
counter.update(headers)
metric_headers = _normalize_headers(metric_names, counter)
param_headers = _normalize_headers(param_names, counter)
td = experiments_table(
all_experiments,
headers,
metric_headers,
metric_names,
param_headers,
param_names,
deps_names,
kwargs.get("sort_by"),
kwargs.get("sort_order"),
kwargs.get("precision"),
kwargs.get("fill_value"),
kwargs.get("iso"),
)
if keep:
for col in td.keys():
if re.match(keep, col):
td.protect(col)
for col in ("State", "Executor"):
if td.is_empty(col):
td.drop(col)
row_styles = lmap(baseline_styler, td.column("typ"))
if not csv:
merge_headers = ["Experiment", "rev", "typ", "parent"]
td.column("Experiment")[:] = map(
prepare_exp_id, td.as_dict(merge_headers)
)
td.drop(*merge_headers[1:])
headers = {
"metrics": metric_headers,
"params": param_headers,
"deps": deps_names,
}
styles = {
"Experiment": {"no_wrap": True, "header_style": "black on grey93"},
"Created": {"header_style": "black on grey93"},
"State": {"header_style": "black on grey93"},
"Executor": {"header_style": "black on grey93"},
}
header_bg_colors = {
"metrics": "cornsilk1",
"params": "light_cyan1",
"deps": "plum2",
}
styles.update(
{
header: {
"justify": "right" if typ == "metrics" else "left",
"header_style": f"black on {header_bg_colors[typ]}",
"collapse": idx != 0,
"no_wrap": typ == "metrics",
}
for typ, hs in headers.items()
for idx, header in enumerate(hs)
}
)
if kwargs.get("only_changed", False) or pcp:
td.drop_duplicates("cols", ignore_empty=False)
cols_to_drop = set()
if drop is not None:
cols_to_drop = {col for col in td.keys() if re.match(drop, col)}
if pcp:
cols_to_drop.add("Created")
td.drop(*cols_to_drop)
if pcp:
subset = {x for x in td.keys() if x != "Experiment"}
td.dropna(
"rows",
how="all",
subset=subset,
)
td.drop_duplicates("rows", subset=subset)
td.column("Experiment")[:] = [
# remove tree characters
str(x).encode("ascii", "ignore").strip().decode()
for x in td.column("Experiment")
]
out = kwargs.get("out") or "dvc_plots"
ui.write(
td.to_parallel_coordinates(
output_path=os.path.abspath(out),
color_by=kwargs.get("sort_by") or "Experiment",
)
)
if kwargs.get("open"):
return ui.open_browser(os.path.join(out, "index.html"))
else:
td.render(
pager=pager,
borders="horizontals",
rich_table=True,
header_styles=styles,
row_styles=row_styles,
csv=csv,
markdown=markdown,
)
def _normalize_headers(names, count):
return [
name if count[name] == 1 else f"{path}:{name}"
for path in names
for name in names[path]
]
def _format_json(item):
if isinstance(item, (date, datetime)):
return item.isoformat()
return encode_exception(item)
class CmdExperimentsShow(CmdBase):
def run(self):
try:
all_experiments = self.repo.experiments.show(
all_branches=self.args.all_branches,
all_tags=self.args.all_tags,
all_commits=self.args.all_commits,
revs=self.args.rev,
num=self.args.num,
sha_only=self.args.sha,
param_deps=self.args.param_deps,
)
except DvcException:
logger.exception("failed to show experiments")
return 1
if self.args.json:
ui.write_json(all_experiments, default=_format_json)
else:
precision = (
self.args.precision or None
if self.args.csv
else DEFAULT_PRECISION
)
fill_value = "" if self.args.csv else FILL_VALUE
iso = True if self.args.csv else False
show_experiments(
all_experiments,
keep=self.args.keep,
drop=self.args.drop,
sort_by=self.args.sort_by,
sort_order=self.args.sort_order,
precision=precision,
fill_value=fill_value,
iso=iso,
pager=not self.args.no_pager,
csv=self.args.csv,
markdown=self.args.markdown,
only_changed=self.args.only_changed,
pcp=self.args.pcp,
out=self.args.out,
open=self.args.open,
)
return 0
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_SHOW_HELP = "Print experiments."
experiments_show_parser = experiments_subparsers.add_parser(
"show",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_SHOW_HELP, "exp/show"),
help=EXPERIMENTS_SHOW_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
experiments_show_parser.add_argument(
"-a",
"--all-branches",
action="store_true",
default=False,
help="Show experiments derived from the tip of all Git branches.",
)
experiments_show_parser.add_argument(
"-T",
"--all-tags",
action="store_true",
default=False,
help="Show experiments derived from all Git tags.",
)
experiments_show_parser.add_argument(
"-A",
"--all-commits",
action="store_true",
default=False,
help="Show experiments derived from all Git commits.",
)
experiments_show_parser.add_argument(
"--rev",
type=str,
default=None,
help=(
"Show experiments derived from the specified revision. "
"Defaults to HEAD if none of `--rev`,`-a`,`-A`,`-T` is specified."
),
metavar="<rev>",
)
experiments_show_parser.add_argument(
"-n",
"--num",
type=int,
default=1,
dest="num",
metavar="<num>",
help="Show the last `num` commits from <rev>.",
)
experiments_show_parser.add_argument(
"--no-pager",
action="store_true",
default=False,
help="Do not pipe output into a pager.",
)
experiments_show_parser.add_argument(
"--only-changed",
action="store_true",
default=False,
help=(
"Only show metrics/params with values varying "
"across the selected experiments."
),
)
experiments_show_parser.add_argument(
"--drop",
help="Remove the columns matching the specified regex pattern.",
metavar="<regex_pattern>",
)
experiments_show_parser.add_argument(
"--keep",
help="Preserve the columns matching the specified regex pattern.",
metavar="<regex_pattern>",
)
experiments_show_parser.add_argument(
"--param-deps",
action="store_true",
default=False,
help="Show only params that are stage dependencies.",
)
experiments_show_parser.add_argument(
"--sort-by",
help="Sort related experiments by the specified metric or param.",
metavar="<metric/param>",
)
experiments_show_parser.add_argument(
"--sort-order",
help=(
"Sort order to use with --sort-by."
" Defaults to ascending ('asc')."
),
choices=("asc", "desc"),
default="asc",
)
experiments_show_parser.add_argument(
"--sha",
action="store_true",
default=False,
help="Always show git commit SHAs instead of branch/tag names.",
)
experiments_show_parser.add_argument(
"--json",
"--show-json",
action="store_true",
default=False,
help="Print output in JSON format instead of a human-readable table.",
)
experiments_show_parser.add_argument(
"--csv",
"--show-csv",
action="store_true",
default=False,
help="Print output in csv format instead of a human-readable table.",
)
experiments_show_parser.add_argument(
"--md",
"--show-md",
action="store_true",
default=False,
dest="markdown",
help="Show tabulated output in the Markdown format (GFM).",
)
experiments_show_parser.add_argument(
"--precision",
type=int,
help=(
"Round metrics/params to `n` digits precision after the decimal "
f"point. Rounds to {DEFAULT_PRECISION} digits by default."
),
metavar="<n>",
)
experiments_show_parser.add_argument(
"--parallel-coordinates-plot",
"--pcp",
dest="pcp",
action="store_true",
default=False,
help="Generate a Parallel Coordinates Plot from the tabulated output.",
)
experiments_show_parser.add_argument(
"-o",
"--out",
default=None,
help="Destination folder to save the Parallel Coordinates Plot to",
metavar="<path>",
).complete = completion.DIR
experiments_show_parser.add_argument(
"--open",
action="store_true",
default=False,
help="Open the Parallel Coordinates Plot directly in the browser.",
)
experiments_show_parser.set_defaults(func=CmdExperimentsShow)
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
+2 -1
View File
@@ -2,7 +2,8 @@ import argparse
import logging
import os
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.ui import ui
logger = logging.getLogger(__name__)
+2 -1
View File
@@ -1,10 +1,11 @@
import argparse
import logging
from dvc.cli.command import CmdBaseNoRepo
from dvc.exceptions import DvcException
from ..cli.utils import append_doc_link
from . import completion
from .base import CmdBaseNoRepo, append_doc_link
logger = logging.getLogger(__name__)
@@ -1,10 +1,11 @@
import argparse
import logging
from dvc.cli.command import CmdBaseNoRepo
from dvc.exceptions import DvcException
from ..cli.utils import append_doc_link
from . import completion
from .base import CmdBaseNoRepo, append_doc_link
logger = logging.getLogger(__name__)
@@ -1,7 +1,8 @@
import logging
import os
from dvc.command.base import CmdBaseNoRepo, fix_subparsers
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import fix_subparsers
from dvc.exceptions import NotDvcRepoError
logger = logging.getLogger(__name__)
@@ -25,7 +26,7 @@ class CmdHookBase(CmdBaseNoRepo):
class CmdPreCommit(CmdHookBase):
def _run(self):
from dvc.main import main
from dvc.cli import main
return main(["status"])
@@ -49,14 +50,14 @@ class CmdPostCheckout(CmdHookBase):
if os.path.isdir(os.path.join(".git", "rebase-merge")):
return 0
from dvc.main import main
from dvc.cli import main
return main(["checkout"])
class CmdPrePush(CmdHookBase):
def _run(self):
from dvc.main import main
from dvc.cli import main
return main(["push"])
+3 -2
View File
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
+2 -1
View File
@@ -4,7 +4,8 @@ import logging
import colorama
from dvc import analytics
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.utils import boxify
from dvc.utils import format_link as fmt_link
@@ -1,7 +1,8 @@
import argparse
import logging
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
+10 -6
View File
@@ -1,8 +1,8 @@
import argparse
from pathlib import Path
from dvc.command import completion
from dvc.command.base import CmdBase, fix_subparsers
from dvc.cli.command import CmdBase
from dvc.cli.utils import fix_subparsers
from dvc.commands import completion
from dvc.ui import ui
@@ -10,13 +10,17 @@ class CmdLive(CmdBase):
UNINITIALIZED = True
def _run(self, target, revs=None):
from dvc.render.utils import match_renderers, render
metrics, plots = self.repo.live.show(target=target, revs=revs)
if plots:
html_path = Path.cwd() / (self.args.target + "_html")
from dvc.render.utils import render
from pathlib import Path
index_path = render(self.repo, plots, metrics, html_path)
html_path = Path.cwd() / (self.args.target + "_html")
renderers = match_renderers(plots, self.repo.plots.templates)
index_path = render(self.repo, renderers, metrics, html_path)
ui.write(index_path.as_uri())
return 0
return 1
@@ -1,9 +1,10 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.command.ls.ls_colors import LsColors
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.commands.ls.ls_colors import LsColors
from dvc.exceptions import DvcException
from dvc.ui import ui
@@ -1,9 +1,12 @@
import argparse
from dvc.command.base import CmdBase, append_doc_link, fix_subparsers
from dvc.command.config import CmdConfig
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.commands.config import CmdConfig
from dvc.compare import TabularData
from dvc.config import ConfigError
from dvc.exceptions import DvcException
from dvc.types import Dict, List
from dvc.ui import ui
from dvc.utils import format_link
@@ -71,18 +74,56 @@ class CmdMachineRemove(CmdMachineConfig):
class CmdMachineList(CmdMachineConfig):
def run(self):
TABLE_COLUMNS = [
"name",
"cloud",
"region",
"image",
"spot",
"spot_price",
"instance_hdd_size",
"instance_type",
"ssh_private",
"startup_script",
]
PRIVATE_COLUMNS = ["ssh_private", "startup_script"]
def _hide_private(self, conf):
for machine in conf:
for column in self.PRIVATE_COLUMNS:
if column in conf[machine]:
conf[machine][column] = "***"
def _show_origin(self):
levels = [self.args.level] if self.args.level else self.config.LEVELS
for level in levels:
conf = self.config.read(level)["machine"]
if self.args.name:
conf = conf.get(self.args.name, {})
prefix = self._config_file_prefix(
self.args.show_origin, self.config, level
)
self._hide_private(conf)
prefix = self._config_file_prefix(True, self.config, level)
configs = list(self._format_config(conf, prefix))
if configs:
ui.write("\n".join(configs))
def _show_table(self):
td = TabularData(self.TABLE_COLUMNS, fill_value="-")
conf = self.config.read()["machine"]
if self.args.name:
conf = {self.args.name: conf.get(self.args.name, {})}
self._hide_private(conf)
for machine, machine_config in conf.items():
machine_config["name"] = machine
td.row_from_dict(machine_config)
td.dropna("cols", "all")
td.render()
def run(self):
if self.args.show_origin:
self._show_origin()
else:
self._show_table()
return 0
@@ -193,18 +234,60 @@ class CmdMachineCreate(CmdBase):
class CmdMachineStatus(CmdBase):
SHOWN_FIELD = ["name", "cloud", "instance_hdd_size", "instance_ip"]
INSTANCE_FIELD = ["name", "instance", "status"]
SHOWN_FIELD = [
"cloud",
"instance_ip",
"instance_type",
"instance_hdd_size",
"instance_gpu",
]
FILL_VALUE = "-"
def _add_row(
self,
name: str,
all_status: List[Dict],
td: TabularData,
):
if not all_status:
row = [
name,
self.FILL_VALUE,
"offline",
] # back to `None` after #7167
td.append(row)
for i, status in enumerate(all_status, start=1):
row = [name, f"num_{i}", "running" if status else "offline"]
for field in self.SHOWN_FIELD:
value = str(status.get(field, ""))
row.append(value)
td.append(row)
def run(self):
if self.repo.machine is None:
raise MachineDisabledError
all_status = self.repo.machine.status(self.args.name)
for i, status in enumerate(all_status, start=1):
ui.write(f"instance_num_{i}:")
for field in self.SHOWN_FIELD:
value = status.get(field, None)
ui.write(f"\t{field:20}: {value}")
td = TabularData(
self.INSTANCE_FIELD + self.SHOWN_FIELD, fill_value=self.FILL_VALUE
)
if self.args.name:
all_status = list(self.repo.machine.status(self.args.name))
self._add_row(self.args.name, all_status, td)
else:
name_set = set()
for level in self.repo.config.LEVELS:
conf = self.repo.config.read(level)["machine"]
name_set.update(conf.keys())
name_list = list(name_set)
for name in sorted(name_list):
all_status = list(self.repo.machine.status(name))
self._add_row(name, all_status, td)
td.dropna("cols", "all")
td.render()
return 0
@@ -227,7 +310,7 @@ class CmdMachineSsh(CmdBase):
def add_parser(subparsers, parent_parser):
from dvc.command.config import parent_config_parser
from dvc.commands.config import parent_config_parser
machine_HELP = "Set up and manage cloud machines."
machine_parser = subparsers.add_parser(
@@ -373,7 +456,7 @@ def add_parser(subparsers, parent_parser):
machine_CREATE_HELP = "Create and start a machine instance."
machine_create_parser = machine_subparsers.add_parser(
"create",
parents=[parent_config_parser, parent_parser],
parents=[parent_parser],
description=append_doc_link(machine_CREATE_HELP, "machine/create"),
help=machine_CREATE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
@@ -383,23 +466,25 @@ def add_parser(subparsers, parent_parser):
)
machine_create_parser.set_defaults(func=CmdMachineCreate)
machine_STATUS_HELP = "List the status of a running machine."
machine_STATUS_HELP = (
"List the status of running instances for one/all machines."
)
machine_status_parser = machine_subparsers.add_parser(
"status",
parents=[parent_config_parser, parent_parser],
parents=[parent_parser],
description=append_doc_link(machine_STATUS_HELP, "machine/status"),
help=machine_STATUS_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
machine_status_parser.add_argument(
"name", help="Name of the running machine."
"name", nargs="?", help="(optional) Name of the machine."
)
machine_status_parser.set_defaults(func=CmdMachineStatus)
machine_DESTROY_HELP = "Destroy an machine instance."
machine_destroy_parser = machine_subparsers.add_parser(
"destroy",
parents=[parent_config_parser, parent_parser],
parents=[parent_parser],
description=append_doc_link(machine_DESTROY_HELP, "machine/destroy"),
help=machine_DESTROY_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
@@ -412,7 +497,7 @@ def add_parser(subparsers, parent_parser):
machine_SSH_HELP = "Connect to a machine via SSH."
machine_ssh_parser = machine_subparsers.add_parser(
"ssh",
parents=[parent_config_parser, parent_parser],
parents=[parent_parser],
description=append_doc_link(machine_SSH_HELP, "machine/ssh"),
help=machine_SSH_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link, fix_subparsers
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.commands import completion
from dvc.exceptions import DvcException
from dvc.ui import ui
from dvc.utils.serialize import encode_exception
+3 -2
View File
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link, fix_subparsers
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.commands import completion
from dvc.exceptions import DvcException
from dvc.ui import ui
+119 -24
View File
@@ -1,16 +1,31 @@
import argparse
import json
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link, fix_subparsers
from funcy import first
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.commands import completion
from dvc.exceptions import DvcException
from dvc.render.utils import find_vega, render
from dvc.ui import ui
from dvc.utils import format_link
logger = logging.getLogger(__name__)
def _show_json(renderers, path: None):
if any(r.needs_output_path for r in renderers) and not path:
raise DvcException("Output path ('-o') is required!")
result = {
renderer.filename: json.loads(renderer.as_json(path=path))
for renderer in renderers
}
if result:
ui.write_json(result)
class CmdPlots(CmdBase):
def _func(self, *args, **kwargs):
raise NotImplementedError
@@ -25,6 +40,9 @@ class CmdPlots(CmdBase):
def run(self):
from pathlib import Path
from dvc.render.utils import match_renderers, render
from dvc.render.vega import VegaRenderer
if self.args.show_vega:
if not self.args.targets:
logger.error("please specify a target for `--show-vega`")
@@ -34,8 +52,15 @@ class CmdPlots(CmdBase):
"you can only specify one target for `--show-vega`"
)
return 1
if self.args.json:
logger.error(
"'--show-vega' and '--json' are mutually exclusive "
"options."
)
return 1
try:
plots_data = self._func(
targets=self.args.targets, props=self._props()
)
@@ -46,35 +71,41 @@ class CmdPlots(CmdBase):
"visualization file will not be created."
)
renderers = match_renderers(
plots_data=plots_data, templates=self.repo.plots.templates
)
if self.args.show_vega:
target = self.args.targets[0]
plot_json = find_vega(self.repo, plots_data, target)
if plot_json:
ui.write(plot_json)
renderer = first(
filter(lambda r: isinstance(r, VegaRenderer), renderers)
)
if renderer:
ui.write_json(renderer.asdict())
return 0
if self.args.json:
_show_json(renderers, self.args.out)
return 0
rel: str = self.args.out or "dvc_plots"
path: Path = (Path.cwd() / rel).resolve()
index_path = render(
self.repo,
plots_data,
renderers,
path=path,
html_template_path=self.args.html_template,
)
assert index_path.is_absolute()
url = index_path.as_uri()
ui.write(url)
if self.args.open:
import webbrowser
opened = webbrowser.open(index_path)
if not opened:
ui.error_write(
"Failed to open. Please try opening it manually."
ui.write(index_path.as_uri())
auto_open = self.repo.config["plots"].get("auto_open", False)
if self.args.open or auto_open:
if not auto_open:
ui.write(
"To enable auto opening, you can run:\n"
"\n"
"\tdvc config plots.auto_open true"
)
return 1
return ui.open_browser(index_path)
return 0
except DvcException:
@@ -109,6 +140,37 @@ class CmdPlotsModify(CmdPlots):
return 0
class CmdPlotsTemplates(CmdBase):
TEMPLATES_CHOICES = [
"simple",
"linear",
"confusion",
"confusion_normalized",
"scatter",
"smooth",
]
def run(self):
import os
try:
out = (
os.path.join(os.getcwd(), self.args.out)
if self.args.out
else self.repo.plots.templates.templates_dir
)
targets = [self.args.target] if self.args.target else None
self.repo.plots.templates.init(output=out, targets=targets)
templates_path = os.path.relpath(out, os.getcwd())
ui.write(f"Templates have been written into '{templates_path}'.")
return 0
except DvcException:
logger.exception("")
return 1
def add_parser(subparsers, parent_parser):
PLOTS_HELP = (
"Commands to visualize and compare plot metrics in structured files "
@@ -145,7 +207,8 @@ def add_parser(subparsers, parent_parser):
"Shows all plots by default.",
).complete = completion.FILE
_add_props_arguments(plots_show_parser)
_add_output_arguments(plots_show_parser)
_add_output_argument(plots_show_parser)
_add_ui_arguments(plots_show_parser)
plots_show_parser.set_defaults(func=CmdPlotsShow)
PLOTS_DIFF_HELP = (
@@ -180,7 +243,8 @@ def add_parser(subparsers, parent_parser):
"revisions", nargs="*", default=None, help="Git commits to plot from"
)
_add_props_arguments(plots_diff_parser)
_add_output_arguments(plots_diff_parser)
_add_output_argument(plots_diff_parser)
_add_ui_arguments(plots_diff_parser)
plots_diff_parser.set_defaults(func=CmdPlotsDiff)
PLOTS_MODIFY_HELP = (
@@ -206,6 +270,27 @@ def add_parser(subparsers, parent_parser):
)
plots_modify_parser.set_defaults(func=CmdPlotsModify)
TEMPLATES_HELP = (
"Write built-in plots templates to a directory "
"(.dvc/plots by default)."
)
plots_templates_parser = plots_subparsers.add_parser(
"templates",
parents=[parent_parser],
description=append_doc_link(TEMPLATES_HELP, "plots/templates"),
help=TEMPLATES_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
plots_templates_parser.add_argument(
"target",
default=None,
nargs="?",
choices=CmdPlotsTemplates.TEMPLATES_CHOICES,
help="Template to write. Writes all templates by default.",
)
_add_output_argument(plots_templates_parser, typ="templates")
plots_templates_parser.set_defaults(func=CmdPlotsTemplates)
def _add_props_arguments(parser):
parser.add_argument(
@@ -245,20 +330,30 @@ def _add_props_arguments(parser):
)
def _add_output_arguments(parser):
def _add_output_argument(parser, typ="plots"):
parser.add_argument(
"-o",
"--out",
default=None,
help="Destination path to save plots to",
help=f"Directory to save {typ} to.",
metavar="<path>",
).complete = completion.DIR
def _add_ui_arguments(parser):
parser.add_argument(
"--show-vega",
action="store_true",
default=False,
help="Show output in Vega format.",
)
parser.add_argument(
"--json",
"--show-json",
action="store_true",
default=False,
help=argparse.SUPPRESS,
)
parser.add_argument(
"--open",
action="store_true",
@@ -1,7 +1,7 @@
import argparse
from dvc.command.base import append_doc_link, fix_subparsers
from dvc.command.config import CmdConfig
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.commands.config import CmdConfig
from dvc.ui import ui
from dvc.utils import format_link
@@ -151,7 +151,7 @@ class CmdRemoteRename(CmdRemote):
def add_parser(subparsers, parent_parser):
from dvc.command.config import parent_config_parser
from dvc.commands.config import parent_config_parser
REMOTE_HELP = "Set up and manage data remotes."
remote_parser = subparsers.add_parser(
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
@@ -1,8 +1,9 @@
import argparse
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.command.status import CmdDataStatus
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.commands.status import CmdDataStatus
class CmdRepro(CmdBase):
+2 -1
View File
@@ -1,7 +1,8 @@
import argparse
import logging
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.utils import relpath
logger = logging.getLogger(__name__)
+6 -5
View File
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command.base import CmdBase, append_doc_link
from dvc.command.stage import parse_cmd
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands.stage import parse_cmd
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
@@ -23,7 +24,7 @@ class CmdRun(CmdBase):
self.args.outs_persist_no_cache,
self.args.checkpoints,
self.args.params,
self.args.cmd,
self.args.command,
]
): # pragma: no cover
logger.error(
@@ -36,7 +37,7 @@ class CmdRun(CmdBase):
kwargs = vars(self.args)
kwargs.update(
{
"cmd": parse_cmd(self.args.cmd),
"cmd": parse_cmd(self.args.command),
"fname": kwargs.pop("file"),
"no_exec": (self.args.no_exec or bool(self.args.checkpoints)),
"run_cache": not kwargs.pop("no_run_cache"),
@@ -52,7 +53,7 @@ class CmdRun(CmdBase):
def add_parser(subparsers, parent_parser):
from dvc.command.stage import _add_common_args
from dvc.commands.stage import _add_common_args
RUN_HELP = (
"Generate a dvc.yaml file from a command and execute the command."
@@ -3,8 +3,9 @@ import logging
from itertools import chain, filterfalse
from typing import TYPE_CHECKING, Dict, Iterable, List
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link, fix_subparsers
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.commands import completion
from dvc.utils.cli_parse import parse_params
from dvc.utils.humanize import truncate_text
@@ -129,7 +130,7 @@ class CmdStageAdd(CmdBase):
kwargs = vars(self.args)
kwargs.update(
{
"cmd": parse_cmd(kwargs.pop("cmd")),
"cmd": parse_cmd(kwargs.pop("command")),
"params": parse_params(self.args.params),
}
)
@@ -281,7 +282,7 @@ def _add_common_args(parser):
),
)
parser.add_argument(
"cmd",
"command",
nargs=argparse.REMAINDER,
help="Command to execute.",
metavar="command",
@@ -1,6 +1,6 @@
import logging
from dvc.command.data_sync import CmdDataBase
from dvc.commands.data_sync import CmdDataBase
from dvc.exceptions import DvcException
from dvc.ui import ui
from dvc.utils import format_link
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
@@ -1,8 +1,9 @@
import argparse
import logging
from dvc.command import completion
from dvc.command.base import CmdBase, append_doc_link
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands import completion
from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
@@ -1,7 +1,8 @@
import argparse
import logging
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.ui import ui
logger = logging.getLogger(__name__)
+136 -3
View File
@@ -11,7 +11,9 @@ from typing import (
List,
Mapping,
MutableSequence,
Optional,
Sequence,
Set,
Tuple,
Union,
overload,
@@ -20,6 +22,7 @@ from typing import (
from funcy import reraise
if TYPE_CHECKING:
from dvc.types import StrPath
from dvc.ui.table import CellT
@@ -36,11 +39,21 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
self._columns: Dict[str, Column] = {name: Column() for name in columns}
self._keys: List[str] = list(columns)
self._fill_value = fill_value
self._protected: Set[str] = set()
@property
def columns(self) -> List[Column]:
return list(map(self.column, self.keys()))
def is_protected(self, col_name) -> bool:
return col_name in self._protected
def protect(self, *col_names: str):
self._protected.update(col_names)
def unprotect(self, *col_names: str):
self._protected = self._protected.difference(col_names)
def column(self, name: str) -> Column:
return self._columns[name]
@@ -115,6 +128,8 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
del col[item]
def __len__(self) -> int:
if not self._columns:
return 0
return len(self.columns[0])
@property
@@ -122,9 +137,10 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
return len(self.columns), len(self)
def drop(self, *col_names: str) -> None:
for col_name in col_names:
self._keys.remove(col_name)
self._columns.pop(col_name)
for col in col_names:
if not self.is_protected(col):
self._keys.remove(col)
self._columns.pop(col)
def rename(self, from_col_name: str, to_col_name: str) -> None:
self._columns[to_col_name] = self._columns.pop(from_col_name)
@@ -150,6 +166,20 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
writer.writerow(row)
return buff.getvalue()
def to_parallel_coordinates(
self, output_path: "StrPath", color_by: str = None
) -> str:
from dvc.render.html import write
from dvc.render.plotly import ParallelCoordinatesRenderer
index_path = write(
output_path,
renderers=[
ParallelCoordinatesRenderer(self, color_by, self._fill_value)
],
)
return index_path.as_uri()
def add_column(self, name: str) -> None:
self._columns[name] = Column([self._fill_value] * len(self))
self._keys.append(name)
@@ -181,6 +211,109 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
{k: self._columns[k][i] for k in keys} for i in range(len(self))
]
def dropna(
self,
axis: str = "rows",
how="any",
subset: Optional[Iterable[str]] = None,
):
if axis not in ["rows", "cols"]:
raise ValueError(
f"Invalid 'axis' value {axis}."
"Choose one of ['rows', 'cols']"
)
if how not in ["any", "all"]:
raise ValueError(
f"Invalid 'how' value {how}." "Choose one of ['any', 'all']"
)
match_line: Set = set()
match_any = True
if how == "all":
match_any = False
for n_row, row in enumerate(self):
for n_col, col in enumerate(row):
if subset and self.keys()[n_col] not in subset:
continue
if (col == self._fill_value) is match_any:
if axis == "rows":
match_line.add(n_row)
break
else:
match_line.add(self.keys()[n_col])
to_drop = match_line
if how == "all":
if axis == "rows":
to_drop = set(range(len(self)))
else:
to_drop = set(self.keys())
to_drop -= match_line
if axis == "rows":
for name in self.keys():
self._columns[name] = Column(
[
x
for n, x in enumerate(self._columns[name])
if n not in to_drop
]
)
else:
self.drop(*to_drop)
def drop_duplicates(
self,
axis: str = "rows",
subset: Optional[Iterable[str]] = None,
ignore_empty: bool = True,
):
if axis not in ["rows", "cols"]:
raise ValueError(
f"Invalid 'axis' value {axis}."
"Choose one of ['rows', 'cols']"
)
if axis == "cols":
cols_to_drop: List[str] = []
for n_col, col in enumerate(self.columns):
if subset and self.keys()[n_col] not in subset:
continue
# Cast to str because Text is not hashable error
unique_vals = {str(x) for x in col}
if ignore_empty and self._fill_value in unique_vals:
unique_vals -= {self._fill_value}
if len(unique_vals) == 1:
cols_to_drop.append(self.keys()[n_col])
self.drop(*cols_to_drop)
elif axis == "rows":
unique_rows = []
rows_to_drop: List[int] = []
for n_row, row in enumerate(self):
if subset:
row = [
col
for n_col, col in enumerate(row)
if self.keys()[n_col] in subset
]
tuple_row = tuple(row)
if tuple_row in unique_rows:
rows_to_drop.append(n_row)
else:
unique_rows.append(tuple_row)
for name in self.keys():
self._columns[name] = Column(
[
x
for n, x in enumerate(self._columns[name])
if n not in rows_to_drop
]
)
def _normalize_float(val: float, precision: int):
return f"{val:.{precision}g}"
+10 -4
View File
@@ -8,7 +8,6 @@ from functools import partial
from funcy import cached_property, compact, memoize, re_find
from dvc.exceptions import DvcException, NotDvcRepoError
from dvc.path_info import PathInfo
logger = logging.getLogger(__name__)
@@ -95,7 +94,7 @@ class Config(dict):
try:
from dvc.repo import Repo
self.dvc_dir = os.path.join(Repo.find_dvc_dir())
self.dvc_dir = Repo.find_dvc_dir()
except NotDvcRepoError:
self.dvc_dir = None
else:
@@ -229,6 +228,7 @@ class Config(dict):
@staticmethod
def _to_relpath(conf_dir, path):
from dvc.fs.local import localfs
from dvc.utils import relpath
from .config_schema import RelPath
@@ -238,8 +238,9 @@ class Config(dict):
if isinstance(path, RelPath) or not os.path.isabs(path):
path = relpath(path, conf_dir)
return localfs.path.as_posix(path)
return PathInfo(path).as_posix()
return path
@staticmethod
def _save_paths(conf, filename):
@@ -265,7 +266,12 @@ class Config(dict):
"key_path": func,
}
},
"machine": {str: {"startup_script": func}},
"machine": {
str: {
"startup_script": func,
"setup_script": func,
}
},
}
return Schema(dirs_schema, extra=ALLOW_EXTRA)(conf)
+21 -7
View File
@@ -79,13 +79,13 @@ REMOTE_COMMON = {
"checksum_jobs": All(Coerce(int), Range(1)),
"jobs": All(Coerce(int), Range(1)),
Optional("no_traverse"): Bool, # obsoleted
"verify": Bool,
}
LOCAL_COMMON = {
"type": supported_cache_type,
Optional("protected", default=False): Bool, # obsoleted
"shared": All(Lower, Choices("group")),
Optional("slow_link_warning", default=True): Bool,
Optional("verify", default=False): Bool,
}
HTTP_COMMON = {
"auth": All(Lower, Choices("basic", "digest", "custom")),
@@ -95,6 +95,7 @@ HTTP_COMMON = {
"ask_password": Bool,
"ssl_verify": Any(Bool, str),
"method": str,
Optional("verify", default=False): Bool,
}
WEBDAV_COMMON = {
"user": str,
@@ -105,6 +106,7 @@ WEBDAV_COMMON = {
"key_path": str,
"timeout": Coerce(int),
"ssl_verify": Any(Bool, str),
Optional("verify", default=False): Bool,
}
SCHEMA = {
@@ -158,11 +160,13 @@ SCHEMA = {
"cache_regions": bool,
"read_timeout": Coerce(int),
"connect_timeout": Coerce(int),
Optional("verify", default=False): Bool,
**REMOTE_COMMON,
},
"gs": {
"projectname": str,
"credentialpath": str,
Optional("verify", default=False): Bool,
**REMOTE_COMMON,
},
"ssh": {
@@ -175,14 +179,18 @@ SCHEMA = {
"timeout": Coerce(int),
"gss_auth": Bool,
"allow_agent": Bool,
Optional("verify", default=False): Bool,
**REMOTE_COMMON,
},
"hdfs": {"user": str, "kerb_ticket": str, **REMOTE_COMMON},
"webhdfs": {
"hdfscli_config": str,
"webhdfs_token": str,
"user": str,
"webhdfs_alias": str,
"kerberos": Bool,
"kerberos_principal": str,
"proxy_to": str,
"ssl_verify": Any(Bool, str),
"token": str,
"use_https": Bool,
Optional("verify", default=False): Bool,
**REMOTE_COMMON,
},
"azure": {
@@ -198,12 +206,14 @@ SCHEMA = {
"exclude_visual_studio_code_credential": Bool,
"exclude_shared_token_cache_credential": Bool,
"exclude_managed_identity_credential": Bool,
Optional("verify", default=False): Bool,
**REMOTE_COMMON,
},
"oss": {
"oss_key_id": str,
"oss_key_secret": str,
"oss_endpoint": str,
Optional("verify", default=True): Bool,
**REMOTE_COMMON,
},
"gdrive": {
@@ -214,6 +224,7 @@ SCHEMA = {
"gdrive_service_account_user_email": str,
"gdrive_service_account_json_file_path": str,
Optional("gdrive_trash_only", default=False): Bool,
Optional("verify", default=True): Bool,
**REMOTE_COMMON,
},
"http": {**HTTP_COMMON, **REMOTE_COMMON},
@@ -239,7 +250,6 @@ SCHEMA = {
Lower, Choices("us-west", "us-east", "eu-west", "eu-north")
),
"image": str,
"name": str,
"spot": Bool,
"spot_price": Coerce(float),
"instance_hdd_size": Coerce(int),
@@ -247,6 +257,7 @@ SCHEMA = {
"instance_gpu": Lower,
"ssh_private": str,
"startup_script": str,
"setup_script": str,
},
},
# section for experimental features
@@ -255,7 +266,10 @@ SCHEMA = {
# enabled by default. It's of no use, kept for backward compatibility.
Optional("parametrization", default=True): Bool,
},
"plots": {"html_template": str},
"plots": {
"html_template": str,
Optional("auto_open", default=False): Bool,
},
"exp": {
"code": str,
"data": str,
+1 -1
View File
@@ -42,7 +42,7 @@ def _spawn_windows(cmd, env):
def _spawn_posix(cmd, env):
from dvc.main import main
from dvc.cli import main
# NOTE: using os._exit instead of sys.exit, because dvc built
# with PyInstaller has trouble with SystemExit exception and throws
+33
View File
@@ -0,0 +1,33 @@
import logging
from typing import TYPE_CHECKING, Iterator, Union
from .tree import Tree
if TYPE_CHECKING:
from dvc.hash_info import HashInfo
from dvc.objects.db import ObjectDB
from dvc.objects.file import HashFile
logger = logging.getLogger(__name__)
def check(odb: "ObjectDB", obj: "HashFile", **kwargs):
if isinstance(obj, Tree):
for _, _, oid in obj:
odb.check(oid, **kwargs)
odb.check(obj.hash_info, **kwargs)
def load(odb: "ObjectDB", hash_info: "HashInfo") -> "HashFile":
if hash_info.isdir:
return Tree.load(odb, hash_info)
return odb.get(hash_info)
def iterobjs(
obj: Union["Tree", "HashFile"]
) -> Iterator[Union["Tree", "HashFile"]]:
if isinstance(obj, Tree):
yield from (entry_obj for _, entry_obj in obj)
yield obj
+265
View File
@@ -0,0 +1,265 @@
import logging
from itertools import chain
from dvc import prompt
from dvc.exceptions import CacheLinkError, CheckoutError, ConfirmRemoveError
from dvc.fs.utils import test_links, transfer
from dvc.ignore import DvcIgnoreFilter
from dvc.types import Optional
from .diff import ROOT
from .diff import diff as odiff
from .slow_link_detection import slow_link_guard # type: ignore[attr-defined]
from .stage import stage
logger = logging.getLogger(__name__)
def _remove(fs_path, fs, in_cache, force=False):
if not fs.exists(fs_path):
return
if force:
fs.remove(fs_path)
return
if not in_cache:
msg = (
f"file/directory '{fs_path}' is going to be removed. "
"Are you sure you want to proceed?"
)
if not prompt.confirm(msg):
raise ConfirmRemoveError(fs_path)
fs.remove(fs_path)
def _relink(link, cache, cache_info, fs, path, in_cache, force):
_remove(path, fs, in_cache, force=force)
link(cache, cache_info, fs, path)
# NOTE: Depending on a file system (e.g. on NTFS), `_remove` might reset
# read-only permissions in order to delete a hardlink to protected object,
# which will also reset it for the object itself, making it unprotected,
# so we need to protect it back.
cache.protect(cache_info)
def _checkout_file(
link,
fs_path,
fs,
change,
cache,
force,
progress_callback=None,
relink=False,
state=None,
):
"""The file is changed we need to checkout a new copy"""
modified = False
cache_fs_path = cache.hash_to_path(change.new.oid.value)
if change.old.oid:
if relink:
if fs.iscopy(fs_path) and cache.cache_types[0] == "copy":
cache.unprotect(fs_path)
else:
_relink(
link,
cache,
cache_fs_path,
fs,
fs_path,
change.old.in_cache,
force=force,
)
else:
modified = True
_relink(
link,
cache,
cache_fs_path,
fs,
fs_path,
change.old.in_cache,
force=force,
)
else:
link(cache, cache_fs_path, fs, fs_path)
modified = True
if state:
state.save(fs_path, fs, change.new.oid)
if progress_callback:
progress_callback(fs_path)
return modified
def _diff(
fs_path,
fs,
obj,
cache,
relink=False,
dvcignore: Optional[DvcIgnoreFilter] = None,
):
old = None
try:
_, _, old = stage(
cache,
fs_path,
fs,
obj.hash_info.name if obj else cache.fs.PARAM_CHECKSUM,
dry_run=True,
dvcignore=dvcignore,
)
except FileNotFoundError:
pass
diff = odiff(old, obj, cache)
if relink:
diff.modified.extend(diff.unchanged)
return diff
class Link:
def __init__(self, links):
self._links = links
@slow_link_guard
def __call__(self, cache, from_path, to_fs, to_path):
if to_fs.exists(to_path):
to_fs.remove(to_path) # broken symlink
cache.makedirs(cache.fs.path.parent(to_path))
try:
transfer(cache.fs, from_path, to_fs, to_path, links=self._links)
except FileNotFoundError as exc:
raise CheckoutError([to_path]) from exc
except OSError as exc:
raise CacheLinkError([to_path]) from exc
def _checkout(
diff,
fs_path,
fs,
cache,
force=False,
progress_callback=None,
relink=False,
state=None,
):
if not diff:
return
links = test_links(cache.cache_types, cache.fs, cache.fs_path, fs, fs_path)
if not links:
raise CacheLinkError([fs_path])
link = Link(links)
for change in diff.deleted:
entry_path = (
fs.path.join(fs_path, *change.old.key)
if change.old.key != ROOT
else fs_path
)
_remove(entry_path, fs, change.old.in_cache, force=force)
failed = []
for change in chain(diff.added, diff.modified):
entry_path = (
fs.path.join(fs_path, *change.new.key)
if change.new.key != ROOT
else fs_path
)
if change.new.oid.isdir:
fs.makedirs(entry_path)
continue
try:
_checkout_file(
link,
entry_path,
fs,
change,
cache,
force,
progress_callback,
relink,
state=state,
)
except CheckoutError as exc:
failed.extend(exc.target_infos)
if failed:
raise CheckoutError(failed)
def checkout(
fs_path,
fs,
obj,
cache,
force=False,
progress_callback=None,
relink=False,
quiet=False,
dvcignore: Optional[DvcIgnoreFilter] = None,
state=None,
):
# if scheme(fs_path) not in ["local", cache.fs.scheme]:
# raise NotImplementedError
diff = _diff(
fs_path,
fs,
obj,
cache,
relink=relink,
dvcignore=dvcignore,
)
failed = []
if not obj:
if not quiet:
logger.warning(
"No file hash info found for '%s'. It won't be created.",
fs_path,
)
failed.append(fs_path)
elif not diff:
logger.trace("Data '%s' didn't change.", fs_path) # type: ignore
try:
_checkout(
diff,
fs_path,
fs,
cache,
force=force,
progress_callback=progress_callback,
relink=relink,
state=state,
)
except CheckoutError as exc:
failed.extend(exc.target_infos)
if diff and state:
state.save_link(fs_path, fs)
if not failed:
state.save(fs_path, fs, obj.hash_info)
if failed or not diff:
if progress_callback and obj:
progress_callback(fs_path, len(obj))
if failed:
raise CheckoutError(failed)
return
return bool(diff) and not relink
@@ -6,22 +6,15 @@ if TYPE_CHECKING:
from .index import ObjectDBIndexBase
def get_odb(fs, path_info, **config):
from .base import ObjectDB
from .gdrive import GDriveObjectDB
def get_odb(fs, fs_path, **config):
from dvc.objects.db import ObjectDB
from .local import LocalObjectDB
from .oss import OSSObjectDB
if fs.scheme == Schemes.LOCAL:
return LocalObjectDB(fs, path_info, **config)
return LocalObjectDB(fs, fs_path, **config)
if fs.scheme == Schemes.GDRIVE:
return GDriveObjectDB(fs, path_info, **config)
if fs.scheme == Schemes.OSS:
return OSSObjectDB(fs, path_info, **config)
return ObjectDB(fs, path_info, **config)
return ObjectDB(fs, fs_path, **config)
def _get_odb(repo, settings):
@@ -30,9 +23,9 @@ def _get_odb(repo, settings):
if not settings:
return None
cls, config, path_info = get_cloud_fs(repo, **settings)
cls, config, fs_path = get_cloud_fs(repo, **settings)
config["tmp_dir"] = repo.tmp_dir
return get_odb(cls(**config), path_info, state=repo.state, **config)
return get_odb(cls(**config), fs_path, state=repo.state, **config)
def get_index(odb) -> "ObjectDBIndexBase":
@@ -43,8 +36,9 @@ def get_index(odb) -> "ObjectDBIndexBase":
cls = ObjectDBIndex if odb.tmp_dir else ObjectDBIndexNoop
return cls(
odb.tmp_dir,
hashlib.sha256(odb.path_info.url.encode("utf-8")).hexdigest(),
odb.fs.CHECKSUM_DIR_SUFFIX,
hashlib.sha256(
odb.fs.unstrip_protocol(odb.fs_path).encode("utf-8")
).hexdigest(),
)
@@ -1,9 +1,10 @@
import logging
import os
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Iterable, Optional, Set
from typing import TYPE_CHECKING, Iterable, Set
from ..errors import ObjectDBError
from dvc.objects.errors import ObjectDBError
from dvc.utils.decorators import with_diskcache
if TYPE_CHECKING:
from dvc.types import StrPath
@@ -14,7 +15,9 @@ logger = logging.getLogger(__name__)
class ObjectDBIndexBase(ABC):
@abstractmethod
def __init__(
self, tmp_dir: "StrPath", name: str, dir_suffix: Optional[str] = None
self,
tmp_dir: "StrPath",
name: str,
):
pass
@@ -50,7 +53,9 @@ class ObjectDBIndexNoop(ObjectDBIndexBase):
"""No-op class for ODBs which are not indexed."""
def __init__(
self, tmp_dir: "StrPath", name: str, dir_suffix: Optional[str] = None
self,
tmp_dir: "StrPath",
name: str,
): # pylint: disable=super-init-not-called
pass
@@ -80,9 +85,11 @@ class ObjectDBIndex(ObjectDBIndexBase):
INDEX_DIR = "index"
def __init__(
self, tmp_dir: "StrPath", name: str, dir_suffix: Optional[str] = None
self,
tmp_dir: "StrPath",
name: str,
): # pylint: disable=super-init-not-called
from diskcache import Index
from diskcache import Cache, Index
from dvc.fs.local import LocalFileSystem
from dvc.utils.fs import makedirs
@@ -90,25 +97,28 @@ class ObjectDBIndex(ObjectDBIndexBase):
self.index_dir = os.path.join(tmp_dir, self.INDEX_DIR, name)
makedirs(self.index_dir, exist_ok=True)
self.fs = LocalFileSystem()
self.index = Index(self.index_dir)
if not dir_suffix:
dir_suffix = self.fs.CHECKSUM_DIR_SUFFIX
self.dir_suffix = dir_suffix
self.index = Index.fromcache(
Cache(
self.index_dir,
disk_pickle_protocol=4,
eviction_policy="none",
)
)
@with_diskcache(name="index")
def __iter__(self):
return iter(self.index)
@with_diskcache(name="index")
def __contains__(self, hash_):
return hash_ in self.index
@with_diskcache(name="index")
def dir_hashes(self):
"""Iterate over .dir hashes stored in the index."""
yield from (hash_ for hash_, is_dir in self.index.items() if is_dir)
def is_dir_hash(self, hash_: str):
return hash_.endswith(self.dir_suffix)
@with_diskcache(name="index")
def clear(self):
"""Clear this index (to force re-indexing later)."""
from diskcache import Timeout
@@ -118,6 +128,7 @@ class ObjectDBIndex(ObjectDBIndexBase):
except Timeout as exc:
raise ObjectDBError("Failed to clear ODB index") from exc
@with_diskcache(name="index")
def update(self, dir_hashes: Iterable[str], file_hashes: Iterable[str]):
"""Update this index, adding the specified hashes."""
from diskcache import Timeout
@@ -132,6 +143,7 @@ class ObjectDBIndex(ObjectDBIndexBase):
except Timeout as exc:
raise ObjectDBError("Failed to update ODB index") from exc
@with_diskcache(name="index")
def intersection(self, hashes: Set[str]):
"""Iterate over values from `hashes` which exist in the index."""
yield from hashes.intersection(self.index.keys())
@@ -6,14 +6,12 @@ from funcy import cached_property
from shortuuid import uuid
from dvc.hash_info import HashInfo
from dvc.objects.db import ObjectDB
from dvc.objects.errors import ObjectFormatError
from dvc.path_info import PathInfo
from dvc.progress import Tqdm
from dvc.utils import relpath
from dvc.utils.fs import copyfile, remove, umask, walk_files
from .base import ObjectDB
logger = logging.getLogger(__name__)
@@ -22,9 +20,9 @@ class LocalObjectDB(ObjectDB):
CACHE_MODE = 0o444
UNPACKED_DIR_SUFFIX = ".unpacked"
def __init__(self, fs, path_info, **config):
super().__init__(fs, path_info, **config)
self.cache_dir = path_info.fspath
def __init__(self, fs, fs_path, **config):
super().__init__(fs, fs_path, **config)
self.cache_dir = fs_path
shared = config.get("shared")
if shared:
@@ -36,11 +34,11 @@ class LocalObjectDB(ObjectDB):
@property
def cache_dir(self):
return self.path_info.fspath if self.path_info else None
return self.fs_path if self.fs_path else None
@cache_dir.setter
def cache_dir(self, value):
self.path_info = PathInfo(value) if value else None
self.fs_path = value
@cached_property
def cache_path(self):
@@ -50,10 +48,10 @@ class LocalObjectDB(ObjectDB):
super().move(from_info, to_info)
os.chmod(to_info, self._file_mode)
def makedirs(self, path_info):
def makedirs(self, fs_path):
from dvc.utils.fs import makedirs
makedirs(path_info, exist_ok=True, mode=self._dir_mode)
makedirs(fs_path, exist_ok=True, mode=self._dir_mode)
def hash_to_path(self, hash_):
# NOTE: `self.cache_path` is already normalized so we can simply use
@@ -81,26 +79,30 @@ class LocalObjectDB(ObjectDB):
return ret
def _list_paths(self, prefix=None, progress_callback=None):
assert self.path_info is not None
assert self.fs_path is not None
if prefix:
path_info = self.path_info / prefix[:2]
if not self.fs.exists(path_info):
fs_path = self.fs.path.join(self.fs_path, prefix[:2])
if not self.fs.exists(fs_path):
return
else:
path_info = self.path_info
fs_path = self.fs_path
# NOTE: use utils.fs walk_files since fs.walk_files will not follow
# symlinks
if progress_callback:
for path in walk_files(path_info):
for path in walk_files(fs_path):
progress_callback()
yield path
else:
yield from walk_files(path_info)
yield from walk_files(fs_path)
def _remove_unpacked_dir(self, hash_):
info = self.hash_to_path_info(hash_)
path_info = info.with_name(info.name + self.UNPACKED_DIR_SUFFIX)
self.fs.remove(path_info)
hash_fs_path = self.hash_to_path(hash_)
fs_path = self.fs.path.with_name(
hash_fs_path,
self.fs.path.name(hash_fs_path) + self.UNPACKED_DIR_SUFFIX,
)
self.fs.remove(fs_path)
def _unprotect_file(self, path):
if self.fs.is_symlink(path) or self.fs.is_hardlink(path):
@@ -126,47 +128,47 @@ class LocalObjectDB(ObjectDB):
os.chmod(path, self._file_mode)
def _unprotect_dir(self, path):
for fname in self.fs.walk_files(path):
for fname in self.fs.find(path):
self._unprotect_file(fname)
def unprotect(self, path_info):
if not os.path.exists(path_info):
def unprotect(self, fs_path):
if not os.path.exists(fs_path):
from dvc.exceptions import DvcException
raise DvcException(
f"can't unprotect non-existing data '{path_info}'"
f"can't unprotect non-existing data '{fs_path}'"
)
if os.path.isdir(path_info):
self._unprotect_dir(path_info)
if os.path.isdir(fs_path):
self._unprotect_dir(fs_path)
else:
self._unprotect_file(path_info)
self._unprotect_file(fs_path)
def protect(self, path_info):
def protect(self, fs_path):
try:
os.chmod(path_info, self.CACHE_MODE)
os.chmod(fs_path, self.CACHE_MODE)
except OSError:
# NOTE: not being able to protect cache file is not fatal, it
# might happen on funky filesystems (e.g. Samba, see #5255),
# read-only filesystems or in a shared cache scenario.
logger.trace("failed to protect '%s'", path_info, exc_info=True)
logger.trace("failed to protect '%s'", fs_path, exc_info=True)
def is_protected(self, path_info):
def is_protected(self, fs_path):
try:
mode = os.stat(path_info).st_mode
mode = os.stat(fs_path).st_mode
except FileNotFoundError:
return False
return stat.S_IMODE(mode) == self.CACHE_MODE
def set_exec(self, path_info):
mode = os.stat(path_info).st_mode | stat.S_IEXEC
def set_exec(self, fs_path):
mode = os.stat(fs_path).st_mode | stat.S_IEXEC
try:
os.chmod(path_info, mode)
os.chmod(fs_path, mode)
except OSError:
logger.trace(
"failed to chmod '%s' '%s'",
oct(mode),
path_info,
fs_path,
exc_info=True,
)
@@ -3,16 +3,16 @@ import logging
import os
from typing import TYPE_CHECKING, Dict
from dvc.objects.db import ObjectDB
from dvc.objects.errors import ObjectFormatError
from dvc.scheme import Schemes
from ..errors import ObjectFormatError
from ..reference import ReferenceHashFile
from .base import ObjectDB
if TYPE_CHECKING:
from dvc.fs.base import BaseFileSystem
from dvc.fs.base import FileSystem
from dvc.hash_info import HashInfo
from dvc.types import AnyPath, DvcPath
from dvc.types import AnyPath
logger = logging.getLogger(__name__)
@@ -26,7 +26,7 @@ class ReferenceObjectDB(ObjectDB):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._fs_cache: Dict[tuple, "BaseFileSystem"] = {}
self._fs_cache: Dict[tuple, "FileSystem"] = {}
self._obj_cache: Dict["HashInfo", "ReferenceHashFile"] = {}
def get(self, hash_info: "HashInfo"):
@@ -36,9 +36,9 @@ class ReferenceObjectDB(ObjectDB):
return self._obj_cache[hash_info]
except KeyError:
pass
path_info = self.hash_to_path(hash_info.value)
fs_path = self.hash_to_path(hash_info.value)
try:
with self.fs.open(path_info, "rb") as fobj:
with self.fs.open(fs_path, "rb") as fobj:
ref_file = ReferenceHashFile.from_bytes(
fobj.read(), fs_cache=self._fs_cache
)
@@ -47,23 +47,27 @@ class ReferenceObjectDB(ObjectDB):
try:
ref_file.check(self, check_hash=False)
except ObjectFormatError:
self.fs.remove(path_info)
self.fs.remove(fs_path)
raise
self._obj_cache[hash_info] = ref_file
return ref_file
def _add_file(
self,
from_fs: "BaseFileSystem",
from_fs: "FileSystem",
from_info: "AnyPath",
to_info: "DvcPath",
to_info: "AnyPath",
hash_info: "HashInfo",
move: bool = False,
hardlink: bool = False,
):
self.makedirs(to_info.parent)
self.makedirs(self.fs.path.parent(to_info))
if hash_info.isdir:
return super()._add_file(
from_fs, from_info, to_info, hash_info, move
from_fs,
from_info,
to_info,
hash_info,
hardlink=hardlink,
)
ref_file = ReferenceHashFile(from_info, from_fs, hash_info)
self._obj_cache[hash_info] = ref_file
+2 -3
View File
@@ -3,8 +3,7 @@ from typing import TYPE_CHECKING, List, Optional, Tuple
if TYPE_CHECKING:
from dvc.hash_info import HashInfo
from .file import HashFile
from dvc.objects.file import HashFile
ADD = "add"
MODIFY = "modify"
@@ -96,7 +95,7 @@ def diff(
return entry_obj.hash_info if entry_obj else None
def _in_cache(oid, cache):
from .errors import ObjectFormatError
from dvc.objects.errors import ObjectFormatError
if not oid:
return False
+40
View File
@@ -0,0 +1,40 @@
def gc(odb, used, jobs=None, cache_odb=None, shallow=True):
from dvc.data.tree import Tree
from dvc.objects.errors import ObjectDBPermissionError
if odb.read_only:
raise ObjectDBPermissionError("Cannot gc read-only ODB")
if not cache_odb:
cache_odb = odb
used_hashes = set()
for hash_info in used:
used_hashes.add(hash_info.value)
if hash_info.isdir and not shallow:
tree = Tree.load(cache_odb, hash_info)
used_hashes.update(
entry_obj.hash_info.value for _, entry_obj in tree
)
def _is_dir_hash(_hash):
from dvc.hash_info import HASH_DIR_SUFFIX
return _hash.endswith(HASH_DIR_SUFFIX)
removed = False
# hashes must be sorted to ensure we always remove .dir files first
for hash_ in sorted(
odb.all(jobs, odb.fs_path),
key=_is_dir_hash,
reverse=True,
):
if hash_ in used_hashes:
continue
fs_path = odb.hash_to_path(hash_)
if _is_dir_hash(hash_):
# backward compatibility
# pylint: disable=protected-access
odb._remove_unpacked_dir(hash_)
odb.fs.remove(fs_path)
removed = True
return removed
+17 -2
View File
@@ -1,15 +1,25 @@
from collections import OrderedDict
from dataclasses import dataclass, field
from typing import Optional
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from dvc.objects.db import ObjectDB
from dvc.objects.file import HashFile
@dataclass
class Meta:
PARAM_SIZE = "size"
PARAM_NFILES = "nfiles"
PARAM_ISEXEC = "isexec"
size: Optional[int] = field(default=None)
nfiles: Optional[int] = field(default=None)
isexec: Optional[bool] = field(default=False)
obj: Optional["HashFile"] = field(default=None)
odb: Optional["ObjectDB"] = field(default=None)
remote: Optional[str] = field(default=None)
@classmethod
def from_dict(cls, d):
@@ -18,7 +28,9 @@ class Meta:
size = d.pop(cls.PARAM_SIZE, None)
nfiles = d.pop(cls.PARAM_NFILES, None)
return cls(size=size, nfiles=nfiles)
isexec = d.pop(cls.PARAM_ISEXEC, False)
return cls(size=size, nfiles=nfiles, isexec=isexec)
def to_dict(self):
ret = OrderedDict()
@@ -29,4 +41,7 @@ class Meta:
if self.nfiles is not None:
ret[self.PARAM_NFILES] = self.nfiles
if self.isexec:
ret[self.PARAM_ISEXEC] = self.isexec
return ret
@@ -4,16 +4,15 @@ import os
import pickle
from typing import TYPE_CHECKING, Optional
from .errors import ObjectFormatError
from .file import HashFile
from dvc.objects.errors import ObjectFormatError
from dvc.objects.file import HashFile
if TYPE_CHECKING:
from dvc.fs.base import BaseFileSystem
from dvc.fs.base import FileSystem
from dvc.hash_info import HashInfo
from dvc.objects.db import ObjectDB
from dvc.types import AnyPath
from .db.base import ObjectDB
logger = logging.getLogger(__name__)
@@ -25,23 +24,23 @@ class ReferenceHashFile(HashFile):
def __init__(
self,
path_info: "AnyPath",
fs: "BaseFileSystem",
fs_path: "AnyPath",
fs: "FileSystem",
hash_info: "HashInfo",
checksum: Optional[str] = None,
**kwargs,
):
super().__init__(path_info, fs, hash_info, **kwargs)
self.checksum = checksum or fs.checksum(path_info)
super().__init__(fs_path, fs, hash_info, **kwargs)
self.checksum = checksum or fs.checksum(fs_path)
def __str__(self):
return f"ref object {self.hash_info} -> {self.path_info}"
return f"ref object {self.hash_info} -> {self.fs_path}"
def check(self, odb: "ObjectDB", check_hash: bool = True):
assert self.fs
if not self.fs.exists(self.path_info):
if not self.fs.exists(self.fs_path):
raise FileNotFoundError(
errno.ENOENT, os.strerror(errno.ENOENT), self.path_info
errno.ENOENT, os.strerror(errno.ENOENT), self.fs_path
)
if self.checksum != self._get_checksum():
raise ObjectFormatError(f"{self} is changed")
@@ -50,23 +49,22 @@ class ReferenceHashFile(HashFile):
def _get_checksum(self) -> str:
assert self.fs
return self.fs.checksum(self.path_info)
return self.fs.checksum(self.fs_path)
def to_bytes(self):
from dvc.fs.repo import RepoFileSystem
from dvc.path_info import PathInfo
# NOTE: dumping reference FS's this way is insecure, as the
# fully parsed remote FS config will include credentials
#
# ReferenceHashFiles should currently only be serialized in
# memory and not to disk
path_info = self.path_info
fs_path = self.fs_path
if isinstance(self.fs, RepoFileSystem):
path_info = PathInfo(path_info).relative_to(self.fs.root_dir)
fs_path = self.fs.path.relpath(fs_path, self.fs.root_dir)
dict_ = {
self.PARAM_PATH: path_info,
self.PARAM_PATH: fs_path,
self.PARAM_HASH: self.hash_info,
self.PARAM_CHECKSUM: self.checksum,
self.PARAM_FS_CONFIG: self.config_tuple(self.fs),
@@ -87,7 +85,7 @@ class ReferenceHashFile(HashFile):
raise ObjectFormatError("ReferenceHashFile is corrupted") from exc
try:
path_info = dict_[cls.PARAM_PATH]
fs_path = dict_[cls.PARAM_PATH]
hash_info = dict_[cls.PARAM_HASH]
except KeyError as exc:
raise ObjectFormatError("ReferenceHashFile is corrupted") from exc
@@ -98,19 +96,19 @@ class ReferenceHashFile(HashFile):
config = dict(config_pairs)
if RepoFileSystem.PARAM_REPO_URL in config:
fs = RepoFileSystem(**config)
path_info = fs.root_dir / path_info
fs_path = fs.path.join(fs.root_dir, fs_path)
else:
fs_cls = get_fs_cls(config, scheme=path_info.scheme)
fs_cls = get_fs_cls(config, scheme=scheme)
fs = fs_cls(**config)
return ReferenceHashFile(
path_info,
fs_path,
fs,
hash_info,
checksum=dict_.get(cls.PARAM_CHECKSUM),
)
@staticmethod
def config_tuple(fs: "BaseFileSystem"):
def config_tuple(fs: "FileSystem"):
return (
fs.scheme,
tuple(
@@ -25,15 +25,15 @@ this.message = (
def slow_link_guard(f):
@wraps(f)
def wrapper(odb, *args, **kwargs):
def wrapper(self, odb, *args, **kwargs):
if this.already_displayed:
return f(odb, *args, **kwargs)
return f(self, odb, *args, **kwargs)
if not odb.slow_link_warning or odb.cache_types:
return f(odb, *args, **kwargs)
return f(self, odb, *args, **kwargs)
start = time.time()
result = f(odb, *args, **kwargs)
result = f(self, odb, *args, **kwargs)
delta = time.time() - start
if delta >= this.timeout_seconds:
+99 -73
View File
@@ -1,26 +1,24 @@
import hashlib
import logging
import os
import pathlib
from concurrent.futures import ThreadPoolExecutor
from functools import partial
from typing import TYPE_CHECKING, Dict, Tuple
from typing import TYPE_CHECKING, Dict, Optional, Tuple
from dvc.exceptions import DvcIgnoreInCollectedDirError
from dvc.hash_info import HashInfo
from dvc.ignore import DvcIgnore
from dvc.objects.file import HashFile
from dvc.progress import Tqdm
from dvc.utils import file_md5
from dvc.utils import file_md5, is_exec
from .db.reference import ReferenceObjectDB
from .file import HashFile
from .meta import Meta
if TYPE_CHECKING:
from dvc.fs.base import BaseFileSystem
from dvc.types import AnyPath, DvcPath
from .db.base import ObjectDB
from dvc.fs.base import FileSystem
from dvc.objects.db import ObjectDB
from dvc.types import AnyPath
logger = logging.getLogger(__name__)
@@ -28,74 +26,92 @@ logger = logging.getLogger(__name__)
_STAGING_MEMFS_PATH = "dvc-staging"
def _upload_file(path_info, fs, odb, upload_odb):
def _upload_file(from_fs_path, fs, odb, upload_odb):
from dvc.utils import tmp_fname
from dvc.utils.stream import HashedStreamReader
tmp_info = upload_odb.path_info / tmp_fname()
with fs.open(path_info, mode="rb", chunk_size=fs.CHUNK_SIZE) as stream:
fs_path = upload_odb.fs.path
tmp_info = fs_path.join(upload_odb.fs_path, tmp_fname())
with fs.open(from_fs_path, mode="rb", chunk_size=fs.CHUNK_SIZE) as stream:
stream = HashedStreamReader(stream)
size = fs.getsize(path_info)
upload_odb.fs.upload(stream, tmp_info, desc=path_info.name, total=size)
size = fs.getsize(from_fs_path)
upload_odb.fs.upload(
stream, tmp_info, desc=fs_path.name(from_fs_path), total=size
)
odb.add(tmp_info, upload_odb.fs, stream.hash_info)
meta = Meta(size=size)
return path_info, meta, odb.get(stream.hash_info)
return from_fs_path, meta, odb.get(stream.hash_info)
def _get_file_hash(path_info, fs, name):
info = fs.info(path_info)
def _adapt_info(info, scheme):
if scheme == "s3" and "ETag" in info:
info["etag"] = info["ETag"].strip('"')
elif scheme == "gs" and "etag" in info:
import base64
info["etag"] = base64.b64decode(info["etag"]).hex()
elif scheme.startswith("http") and (
"ETag" in info or "Content-MD5" in info
):
info["checksum"] = info.get("ETag") or info.get("Content-MD5")
return info
def _get_file_hash(fs_path, fs, name):
info = _adapt_info(fs.info(fs_path), fs.scheme)
if name in info:
assert not info[name].endswith(".dir")
hash_value = info[name]
elif hasattr(fs, name):
func = getattr(fs, name)
hash_value = func(path_info)
hash_value = func(fs_path)
elif name == "md5":
hash_value = file_md5(path_info, fs)
hash_value = file_md5(fs_path, fs)
else:
raise NotImplementedError
meta = Meta(size=info["size"])
meta = Meta(size=info["size"], isexec=is_exec(info.get("mode", 0)))
hash_info = HashInfo(name, hash_value)
return meta, hash_info
def get_file_hash(path_info, fs, name, state=None):
def get_file_hash(fs_path, fs, name, state=None):
if state:
meta, hash_info = state.get( # pylint: disable=assignment-from-none
path_info, fs
fs_path, fs
)
if hash_info:
return meta, hash_info
meta, hash_info = _get_file_hash(path_info, fs, name)
meta, hash_info = _get_file_hash(fs_path, fs, name)
if state:
assert ".dir" not in hash_info.value
state.save(path_info, fs, hash_info)
state.save(fs_path, fs, hash_info)
return meta, hash_info
def _stage_file(path_info, fs, name, odb=None, upload_odb=None, dry_run=False):
def _stage_file(fs_path, fs, name, odb=None, upload_odb=None, dry_run=False):
state = odb.state if odb else None
meta, hash_info = get_file_hash(path_info, fs, name, state=state)
meta, hash_info = get_file_hash(fs_path, fs, name, state=state)
if upload_odb and not dry_run:
assert odb and name == "md5"
return _upload_file(path_info, fs, odb, upload_odb)
return _upload_file(fs_path, fs, odb, upload_odb)
if dry_run:
obj = HashFile(path_info, fs, hash_info)
obj = HashFile(fs_path, fs, hash_info)
else:
odb.add(path_info, fs, hash_info, move=False)
odb.add(fs_path, fs, hash_info, hardlink=False)
obj = odb.get(hash_info)
return path_info, meta, obj
return fs_path, meta, obj
def _build_objects(
path_info,
fs_path,
fs,
name,
dvcignore=None,
@@ -104,9 +120,9 @@ def _build_objects(
**kwargs,
):
if dvcignore:
walk_iterator = dvcignore.walk_files(fs, path_info)
walk_iterator = dvcignore.find(fs, fs_path)
else:
walk_iterator = fs.walk_files(path_info)
walk_iterator = fs.find(fs_path)
with Tqdm(
unit="md5",
desc="Computing file/dir hashes (only done once)",
@@ -126,18 +142,18 @@ def _build_objects(
yield from executor.map(worker, walk_iterator)
def _iter_objects(path_info, fs, name, **kwargs):
yield from _build_objects(path_info, fs, name, **kwargs)
def _iter_objects(fs_path, fs, name, **kwargs):
yield from _build_objects(fs_path, fs, name, **kwargs)
def _build_tree(path_info, fs, name, **kwargs):
def _build_tree(fs_path, fs, name, **kwargs):
from .tree import Tree
tree_meta = Meta(size=0, nfiles=0)
tree = Tree(None, None, None)
for file_info, meta, obj in _iter_objects(path_info, fs, name, **kwargs):
if DvcIgnore.DVCIGNORE_FILE == file_info.name:
raise DvcIgnoreInCollectedDirError(file_info.parent)
for file_fs_path, meta, obj in _iter_objects(fs_path, fs, name, **kwargs):
if DvcIgnore.DVCIGNORE_FILE == fs.path.name(file_fs_path):
raise DvcIgnoreInCollectedDirError(fs.path.parent(file_fs_path))
# NOTE: this is lossy transformation:
# "hey\there" -> "hey/there"
@@ -147,15 +163,18 @@ def _build_tree(path_info, fs, name, **kwargs):
#
# Yes, this is a BUG, as long as we permit "/" in
# filenames on Windows and "\" on Unix
tree.add(file_info.relative_to(path_info).parts, meta, obj.hash_info)
tree_meta.size += meta.size
key = fs.path.relparts(file_fs_path, fs_path)
assert key
tree.add(key, meta, obj.hash_info)
tree_meta.size += meta.size or 0
tree_meta.nfiles += 1
return tree_meta, tree
def _stage_tree(path_info, fs, fs_info, name, odb=None, **kwargs):
def _stage_tree(fs_path, fs, fs_info, name, odb=None, **kwargs):
from .tree import Tree
value = fs_info.get(name)
@@ -167,41 +186,46 @@ def _stage_tree(path_info, fs, fs_info, name, odb=None, **kwargs):
except FileNotFoundError:
pass
meta, tree = _build_tree(path_info, fs, name, odb=odb, **kwargs)
meta, tree = _build_tree(fs_path, fs, name, odb=odb, **kwargs)
state = odb.state if odb and odb.state else None
hash_info = None
if state:
_, hash_info = state.get( # pylint: disable=assignment-from-none
path_info, fs
fs_path, fs
)
tree.digest(hash_info=hash_info)
odb.add(tree.path_info, tree.fs, tree.hash_info, move=True)
odb.add(tree.fs_path, tree.fs, tree.hash_info, hardlink=False)
raw = odb.get(tree.hash_info)
# cleanup unneeded memfs tmpfile and return tree based on the
# ODB fs/path
if odb.fs != tree.fs:
tree.fs.remove(tree.path_info)
tree.fs.remove(tree.fs_path)
tree.fs = raw.fs
tree.path_info = raw.path_info
tree.fs_path = raw.fs_path
return meta, tree
_url_cache: Dict[str, str] = {}
def _make_staging_url(path_info: "AnyPath"):
from dvc.path_info import CloudURLInfo
def _make_staging_url(
fs: "FileSystem", odb: "ObjectDB", fs_path: Optional[str]
):
from dvc.scheme import Schemes
url = CloudURLInfo(f"{Schemes.MEMORY}://{_STAGING_MEMFS_PATH}")
if path_info:
if isinstance(path_info, (str, pathlib.PurePath)):
path = os.path.abspath(path_info)
else:
path = str(path_info)
if path not in _url_cache:
_url_cache[path] = hashlib.sha256(path.encode("utf-8")).hexdigest()
url /= _url_cache[path]
url = f"{Schemes.MEMORY}://{_STAGING_MEMFS_PATH}"
if fs_path is not None:
if odb.fs.scheme == Schemes.LOCAL:
fs_path = os.path.abspath(fs_path)
if fs_path not in _url_cache:
_url_cache[fs_path] = hashlib.sha256(
fs_path.encode("utf-8")
).hexdigest()
url = fs.path.join(url, _url_cache[fs_path])
return url
@@ -214,18 +238,19 @@ def _get_staging(odb: "ObjectDB") -> "ObjectDB":
from dvc.fs.memory import MemoryFileSystem
fs = MemoryFileSystem()
path_info = _make_staging_url(odb.path_info)
fs_path = _make_staging_url(fs, odb, odb.fs_path)
state = odb.state
return ReferenceObjectDB(fs, path_info, state=state)
return ReferenceObjectDB(fs, fs_path, state=state)
def _load_from_state(odb, staging, path_info, fs, name):
def _load_from_state(odb, staging, fs_path, fs, name):
from dvc.objects.errors import ObjectFormatError
from . import check, load
from .errors import ObjectFormatError
from .tree import Tree
state = odb.state
meta, hash_info = state.get(path_info, fs)
meta, hash_info = state.get(fs_path, fs)
if hash_info:
for odb_ in (odb, staging):
if odb_.exists(hash_info):
@@ -248,10 +273,10 @@ def _stage_external_tree_info(odb, tree, name):
# that would be universal for all clouds.
assert odb and name != "md5"
odb.add(tree.path_info, tree.fs, tree.hash_info)
odb.add(tree.fs_path, tree.fs, tree.hash_info)
raw = odb.get(tree.hash_info)
_, hash_info = get_file_hash(raw.path_info, raw.fs, name, state=odb.state)
tree.path_info = raw.path_info
_, hash_info = get_file_hash(raw.fs_path, raw.fs, name, state=odb.state)
tree.fs_path = raw.fs_path
tree.fs = raw.fs
tree.hash_info.name = hash_info.name
tree.hash_info.value = hash_info.value
@@ -262,8 +287,8 @@ def _stage_external_tree_info(odb, tree, name):
def stage(
odb: "ObjectDB",
path_info: "DvcPath",
fs: "BaseFileSystem",
fs_path: "AnyPath",
fs: "FileSystem",
name: str,
upload: bool = False,
dry_run: bool = False,
@@ -282,19 +307,20 @@ def stage(
ODB filesystem, and staged objects will reference the uploaded path rather
than the original source path.
"""
assert path_info and path_info.scheme == fs.scheme
assert fs_path
# assert scheme(fs_path) == fs.scheme
details = fs.info(path_info)
details = fs.info(fs_path)
staging = _get_staging(odb)
if odb:
try:
return _load_from_state(odb, staging, path_info, fs, name)
return _load_from_state(odb, staging, fs_path, fs, name)
except FileNotFoundError:
pass
if details["type"] == "directory":
meta, obj = _stage_tree(
path_info,
fs_path,
fs,
details,
name,
@@ -308,7 +334,7 @@ def stage(
obj = _stage_external_tree_info(odb, obj, name)
else:
_, meta, obj = _stage_file(
path_info,
fs_path,
fs,
name,
odb=staging,
@@ -317,6 +343,6 @@ def stage(
)
if odb and odb.state and obj.hash_info:
odb.state.save(path_info, fs, obj.hash_info)
odb.state.save(fs_path, fs, obj.hash_info)
return staging, meta, obj
+6 -7
View File
@@ -7,9 +7,10 @@ from dvc.scheme import Schemes
from .tree import Tree
if TYPE_CHECKING:
from .db.base import ObjectDB
from dvc.objects.db import ObjectDB
from dvc.objects.file import HashFile
from .db.index import ObjectDBIndexBase
from .file import HashFile
logger = logging.getLogger(__name__)
@@ -86,7 +87,7 @@ def status(
exists: objs that exist in odb
missing: objs that do not exist in ODB
"""
logger.debug("Preparing to collect status from '%s'", odb.path_info)
logger.debug("Preparing to collect status from '%s'", odb.fs_path)
if not name:
name = odb.fs.PARAM_CHECKSUM
@@ -116,7 +117,7 @@ def status(
hashes: Set[str] = set(hash_infos.keys())
exists: Set[str] = set()
logger.debug("Collecting status from '%s'", odb.path_info)
logger.debug("Collecting status from '%s'", odb.fs_path)
if index and hashes:
if dir_objs:
exists = hashes.intersection(
@@ -128,9 +129,7 @@ def status(
hashes.difference_update(exists)
if hashes:
exists.update(
odb.hashes_exist(hashes, name=str(odb.path_info), **kwargs)
)
exists.update(odb.hashes_exist(hashes, name=odb.fs_path, **kwargs))
return StatusResult(
{hash_infos[hash_] for hash_ in exists},
{hash_infos[hash_] for hash_ in (hashes - exists)},
@@ -10,8 +10,8 @@ from dvc.utils.threadpool import ThreadPoolExecutor
if TYPE_CHECKING:
from dvc.hash_info import HashInfo
from dvc.objects.db import ObjectDB
from .db.base import ObjectDB
from .db.index import ObjectDBIndexBase
from .tree import Tree
@@ -20,9 +20,9 @@ logger = logging.getLogger(__name__)
def _log_exceptions(func):
@wraps(func)
def wrapper(path_info, *args, **kwargs):
def wrapper(fs_path, *args, **kwargs):
try:
func(path_info, *args, **kwargs)
func(fs_path, *args, **kwargs)
return 0
except Exception as exc: # pylint: disable=broad-except
# NOTE: this means we ran out of file descriptors and there is no
@@ -31,7 +31,7 @@ def _log_exceptions(func):
if isinstance(exc, OSError) and exc.errno == errno.EMFILE:
raise
logger.exception("failed to transfer '%s'", path_info)
logger.exception("failed to transfer '%s'", fs_path)
return 1
return wrapper
@@ -40,7 +40,8 @@ def _log_exceptions(func):
def find_tree_by_obj_id(
odbs: Iterable[Optional["ObjectDB"]], obj_id: "HashInfo"
) -> Optional["Tree"]:
from .errors import ObjectFormatError
from dvc.objects.errors import ObjectFormatError
from .tree import Tree
for odb in odbs:
@@ -87,8 +88,8 @@ def _do_transfer(
)
logger.error(
"failed to upload '%s' to '%s'",
src.get(dir_obj.hash_info).path_info,
dest.get(dir_obj.hash_info).path_info,
src.get(dir_obj.hash_info).fs_path,
dest.get(dir_obj.hash_info).fs_path,
)
total_fails += dir_fails + 1
elif entry_ids.intersection(missing_ids):
@@ -133,7 +134,7 @@ def transfer(
obj_ids: Iterable["HashInfo"],
jobs: Optional[int] = None,
verify: bool = False,
move: bool = False,
hardlink: bool = False,
**kwargs,
) -> int:
"""Transfer (copy) the specified objects from one ODB to another.
@@ -144,8 +145,8 @@ def transfer(
logger.debug(
"Preparing to transfer data from '%s' to '%s'",
src.path_info,
dest.path_info,
src.fs_path,
dest.fs_path,
)
if src == dest:
return 0
@@ -157,7 +158,11 @@ def transfer(
def func(hash_info: "HashInfo") -> None:
obj = src.get(hash_info)
return dest.add(
obj.path_info, obj.fs, obj.hash_info, verify=verify, move=move
obj.fs_path,
obj.fs,
obj.hash_info,
verify=verify,
hardlink=hardlink,
)
total = len(status.new)
+58 -18
View File
@@ -1,22 +1,40 @@
import json
import logging
import posixpath
from typing import TYPE_CHECKING, Dict, Optional, Tuple
from typing import TYPE_CHECKING, Dict, Iterable, Optional, Tuple
from funcy import cached_property
from .errors import ObjectFormatError
from .file import HashFile
from dvc.objects.errors import ObjectFormatError
from dvc.objects.file import HashFile
from .stage import get_file_hash
if TYPE_CHECKING:
from dvc.hash_info import HashInfo
from dvc.objects.db import ObjectDB
from .meta import Meta
logger = logging.getLogger(__name__)
def _try_load(
odbs: Iterable["ObjectDB"],
hash_info: "HashInfo",
) -> Optional["HashFile"]:
for odb in odbs:
if not odb:
continue
try:
return Tree.load(odb, hash_info)
except (FileNotFoundError, ObjectFormatError):
pass
return None
class Tree(HashFile):
PARAM_RELPATH = "relpath"
@@ -25,7 +43,7 @@ class Tree(HashFile):
self._dict: Dict[Tuple[str], Tuple["Meta", "HashInfo"]] = {}
@cached_property
def trie(self):
def _trie(self):
from pygtrie import Trie
return Trie(self._dict)
@@ -36,22 +54,45 @@ class Tree(HashFile):
def digest(self, hash_info: Optional["HashInfo"] = None):
from dvc.fs.memory import MemoryFileSystem
from dvc.path_info import CloudURLInfo
from dvc.utils import tmp_fname
memfs = MemoryFileSystem()
path_info = CloudURLInfo("memory://{}".format(tmp_fname("")))
with memfs.open(path_info, "wb") as fobj:
fs_path = "memory://{}".format(tmp_fname(""))
with memfs.open(fs_path, "wb") as fobj:
fobj.write(self.as_bytes())
self.fs = memfs
self.path_info = path_info
self.fs_path = fs_path
if hash_info:
self.hash_info = hash_info
else:
_, self.hash_info = get_file_hash(path_info, memfs, "md5")
_, self.hash_info = get_file_hash(fs_path, memfs, "md5")
assert self.hash_info.value
self.hash_info.value += ".dir"
def _load(self, key, meta, hash_info):
if hash_info and hash_info.isdir and not meta.obj:
meta.obj = _try_load([meta.odb, meta.remote], hash_info)
if meta.obj:
for ikey, value in meta.obj.iteritems():
self._trie[key + ikey] = value
self._dict[key + ikey] = value
def iteritems(self, prefix=None):
kwargs = {}
if prefix:
kwargs = {"prefix": prefix}
item = self._trie.longest_prefix(prefix)
if item:
key, (meta, hash_info) = item
self._load(key, meta, hash_info)
for key, (meta, hash_info) in self._trie.iteritems(**kwargs):
self._load(key, meta, hash_info)
yield key, (meta, hash_info)
def shortest_prefix(self, *args, **kwargs):
return self._trie.shortest_prefix(*args, **kwargs)
def __len__(self):
return len(self._dict)
@@ -101,7 +142,7 @@ class Tree(HashFile):
obj = odb.get(hash_info)
try:
with obj.fs.open(obj.path_info, "r") as fobj:
with obj.fs.open(obj.fs_path, "r") as fobj:
raw = json.load(fobj)
except ValueError as exc:
raise ObjectFormatError(f"{obj} is corrupted") from exc
@@ -109,12 +150,12 @@ class Tree(HashFile):
if not isinstance(raw, list):
logger.error(
"dir cache file format error '%s' [skipping the file]",
obj.path_info,
obj.fs_path,
)
raise ObjectFormatError(f"{obj} is corrupted")
tree = cls.from_list(raw)
tree.path_info = obj.path_info
tree.fs_path = obj.fs_path
tree.fs = obj.fs
tree.hash_info = hash_info
@@ -125,13 +166,13 @@ class Tree(HashFile):
inside prefix.
The returned tree will contain the original tree's hash_info and
path_info.
fs_path.
Returns an empty tree if no object exists at the specified prefix.
"""
tree = Tree(self.path_info, self.fs, self.hash_info)
tree = Tree(self.fs_path, self.fs, self.hash_info)
try:
for key, (meta, oid) in self.trie.items(prefix):
for key, (meta, oid) in self._trie.items(prefix):
tree.add(key, meta, oid)
except KeyError:
pass
@@ -149,7 +190,7 @@ class Tree(HashFile):
tree = Tree(None, None, None)
depth = len(prefix)
try:
for key, (meta, entry_oid) in self.trie.items(prefix):
for key, (meta, entry_oid) in self._trie.items(prefix):
tree.add(key[depth:], meta, entry_oid)
except KeyError:
return None
@@ -160,8 +201,7 @@ class Tree(HashFile):
def du(odb, tree):
try:
return sum(
odb.fs.getsize(odb.hash_to_path_info(oid.value))
for _, _, oid in tree
odb.fs.getsize(odb.hash_to_path(oid.value)) for _, _, oid in tree
)
except FileNotFoundError:
return None
+11 -10
View File
@@ -3,11 +3,11 @@
import logging
from typing import TYPE_CHECKING, Iterable, Optional
from dvc.objects.db import get_index
from dvc.data.db import get_index
if TYPE_CHECKING:
from dvc.hash_info import HashInfo
from dvc.objects.db.base import ObjectDB
from dvc.objects.db import ObjectDB
logger = logging.getLogger(__name__)
@@ -55,12 +55,12 @@ class DataCloud:
raise NoRemoteError(error_msg)
def _init_odb(self, name):
from dvc.data.db import get_odb
from dvc.fs import get_cloud_fs
from dvc.objects.db import get_odb
cls, config, path_info = get_cloud_fs(self.repo, name=name)
cls, config, fs_path = get_cloud_fs(self.repo, name=name)
config["tmp_dir"] = self.repo.index_db_dir
return get_odb(cls(**config), path_info, **config)
return get_odb(cls(**config), fs_path, **config)
def push(
self,
@@ -78,7 +78,7 @@ class DataCloud:
By default remote from core.remote config option is used.
odb: optional ODB to push to. Overrides remote.
"""
from dvc.objects.transfer import transfer
from dvc.data.transfer import transfer
if not odb:
odb = self.get_remote_odb(remote, "push")
@@ -107,7 +107,7 @@ class DataCloud:
By default remote from core.remote config option is used.
odb: optional ODB to pull from. Overrides remote.
"""
from dvc.objects.transfer import transfer
from dvc.data.transfer import transfer
if not odb:
odb = self.get_remote_odb(remote, "pull")
@@ -141,7 +141,7 @@ class DataCloud:
log_missing: log warning messages if file doesn't exist
neither in cache, neither in cloud.
"""
from dvc.objects.status import compare_status
from dvc.data.status import compare_status
if not odb:
odb = self.get_remote_odb(remote, "status")
@@ -156,5 +156,6 @@ class DataCloud:
)
def get_url_for(self, remote, checksum):
remote_odb = self.get_remote_odb(remote)
return str(remote_odb.hash_to_path_info(checksum))
odb = self.get_remote_odb(remote)
path = odb.hash_to_path(checksum)
return odb.fs.unstrip_protocol(path)
+42 -7
View File
@@ -1,3 +1,4 @@
import errno
import logging
import os
from collections import defaultdict
@@ -18,6 +19,14 @@ class MissingParamsError(DvcException):
pass
class MissingParamsFile(DvcException):
pass
class ParamsIsADirectoryError(DvcException):
pass
class BadParamFileError(DvcException):
pass
@@ -27,9 +36,9 @@ class ParamsDependency(Dependency):
PARAM_SCHEMA = {PARAM_PARAMS: Any(dict, list, None)}
DEFAULT_PARAMS_FILE = "params.yaml"
def __init__(self, stage, path, params):
def __init__(self, stage, path, params=None, repo=None):
info = {}
self.params = []
self.params = params or []
if params:
if isinstance(params, list):
self.params = params
@@ -43,6 +52,7 @@ class ParamsDependency(Dependency):
path
or os.path.join(stage.repo.root_dir, self.DEFAULT_PARAMS_FILE),
info=info,
repo=repo,
)
def dumpd(self):
@@ -88,19 +98,44 @@ class ParamsDependency(Dependency):
def status(self):
return self.workspace_status()
def _read(self):
def validate_filepath(self):
if not self.exists:
return {}
raise FileNotFoundError(
errno.ENOENT, os.strerror(errno.ENOENT), str(self)
)
if self.isdir():
raise IsADirectoryError(
errno.EISDIR, os.strerror(errno.EISDIR), str(self)
)
def read_file(self):
_, ext = os.path.splitext(self.fs_path)
loader = LOADERS[ext]
suffix = self.path_info.suffix.lower()
loader = LOADERS[suffix]
try:
return loader(self.path_info, fs=self.repo.fs)
self.validate_filepath()
except FileNotFoundError as exc:
raise MissingParamsFile(
f"Parameters file '{self}' does not exist"
) from exc
except IsADirectoryError as exc:
raise ParamsIsADirectoryError(
f"'{self}' is a directory, expected a parameters file"
) from exc
try:
return loader(self.fs_path, fs=self.repo.fs)
except ParseError as exc:
raise BadParamFileError(
f"Unable to read parameters from '{self}'"
) from exc
def _read(self):
try:
return self.read_file()
except MissingParamsFile:
return {}
def read_params_d(self, **kwargs):
config = self._read()
+11 -13
View File
@@ -5,13 +5,11 @@ from typing import TYPE_CHECKING, Dict, Optional, Set, Tuple
from voluptuous import Required
from dvc.path_info import PathInfo
from .base import Dependency
if TYPE_CHECKING:
from dvc.hash_info import HashInfo
from dvc.objects.db.base import ObjectDB
from dvc.objects.db import ObjectDB
from dvc.objects.file import HashFile
@@ -34,7 +32,7 @@ class RepoDependency(Dependency):
self._staged_objs: Dict[str, "HashFile"] = {}
super().__init__(stage, *args, **kwargs)
def _parse_path(self, fs, path_info):
def _parse_path(self, fs, fs_path):
return None
@property
@@ -63,14 +61,14 @@ class RepoDependency(Dependency):
return {self.PARAM_PATH: self.def_path, self.PARAM_REPO: self.def_repo}
def download(self, to, jobs=None):
from dvc.objects.checkout import checkout
from dvc.data.checkout import checkout
for odb, objs in self.get_used_objs().items():
self.repo.cloud.pull(objs, jobs=jobs, odb=odb)
obj = self.get_obj()
checkout(
to.path_info,
to.fs_path,
to.fs,
obj,
self.repo.odb.local,
@@ -100,9 +98,9 @@ class RepoDependency(Dependency):
self, obj_only=False, **kwargs
) -> Tuple[Dict[Optional["ObjectDB"], Set["HashInfo"]], "HashFile"]:
from dvc.config import NoRemoteError
from dvc.data.stage import stage
from dvc.data.tree import Tree
from dvc.exceptions import NoOutputOrStageError, PathMissingError
from dvc.objects.stage import stage
from dvc.objects.tree import Tree
local_odb = self.repo.odb.local
locked = kwargs.pop("locked", True)
@@ -114,11 +112,11 @@ class RepoDependency(Dependency):
if locked and self.def_repo.get(self.PARAM_REV_LOCK) is None:
self.def_repo[self.PARAM_REV_LOCK] = rev
path_info = PathInfo(repo.root_dir) / str(self.def_path)
path = os.path.abspath(os.path.join(repo.root_dir, self.def_path))
if not obj_only:
try:
for odb, obj_ids in repo.used_objs(
[os.fspath(path_info)],
[path],
force=True,
jobs=kwargs.get("jobs"),
recursive=True,
@@ -134,7 +132,7 @@ class RepoDependency(Dependency):
try:
staging, _, staged_obj = stage(
local_odb,
path_info,
path,
repo.repo_fs,
local_odb.fs.PARAM_CHECKSUM,
)
@@ -152,10 +150,10 @@ class RepoDependency(Dependency):
return used_obj_ids, staged_obj
def _check_circular_import(self, odb, obj_ids):
from dvc.data.db.reference import ReferenceObjectDB
from dvc.data.tree import Tree
from dvc.exceptions import CircularImportError
from dvc.fs.repo import RepoFileSystem
from dvc.objects.db.reference import ReferenceObjectDB
from dvc.objects.tree import Tree
if not isinstance(odb, ReferenceObjectDB):
return
+4 -4
View File
@@ -60,7 +60,7 @@ def is_lock_file(path):
def is_git_ignored(repo, path):
from dvc.fs.local import LocalFileSystem
from dvc.scm.base import NoSCMError
from dvc.scm import NoSCMError
try:
return isinstance(repo.fs, LocalFileSystem) and repo.scm.is_ignored(
@@ -199,7 +199,7 @@ class SingleStageFile(FileMixin):
check_dvcfile_path(self.repo, self.path)
logger.debug(f"Saving information to '{relpath(self.path)}'.")
dump_yaml(self.path, serialize.to_single_stage_file(stage))
self.repo.scm.track_file(self.relpath)
self.repo.scm_context.track_file(self.relpath)
def remove_stage(self, stage): # pylint: disable=unused-argument
self.remove()
@@ -268,7 +268,7 @@ class PipelineFile(FileMixin):
else:
data["stages"].update(stage_data)
self.repo.scm.track_file(self.relpath)
self.repo.scm_context.track_file(self.relpath)
@property
def stage(self):
@@ -388,7 +388,7 @@ class Lockfile(FileMixin):
data["stages"].update(stage_data)
if modified:
self.repo.scm.track_file(self.relpath)
self.repo.scm_context.track_file(self.relpath)
def remove_stage(self, stage):
if not self.exists():
+9 -28
View File
@@ -128,13 +128,6 @@ class NotDvcRepoError(DvcException):
"""Thrown if a directory is not a DVC repo"""
class DvcParserError(DvcException):
"""Base class for CLI parser errors."""
def __init__(self):
super().__init__("parser error")
class CyclicGraphError(DvcException):
def __init__(self, stages):
assert isinstance(stages, list)
@@ -157,9 +150,9 @@ class InitError(DvcException):
class ReproductionError(DvcException):
def __init__(self, dvc_file_name):
self.path = dvc_file_name
super().__init__(f"failed to reproduce '{dvc_file_name}'")
def __init__(self, name):
self.name = name
super().__init__(f"failed to reproduce '{name}'")
class BadMetricError(DvcException):
@@ -216,18 +209,6 @@ class DvcIgnoreInCollectedDirError(DvcException):
)
class GitHookAlreadyExistsError(DvcException):
def __init__(self, hook_name):
from dvc.utils import format_link
super().__init__(
"Hook '{}' already exists. Please refer to {} for more "
"info.".format(
hook_name, format_link("https://man.dvc.org/install")
)
)
class FileTransferError(DvcException):
_METHOD = "transfer"
@@ -305,7 +286,7 @@ class PathMissingError(DvcException):
class RemoteCacheRequiredError(DvcException):
def __init__(self, path_info):
def __init__(self, scheme, fs_path):
from dvc.utils import format_link
super().__init__(
@@ -314,8 +295,8 @@ class RemoteCacheRequiredError(DvcException):
"existing cache on '{}' remote. See {} for information on how "
"to set up remote cache."
).format(
path_info,
path_info.scheme,
fs_path,
scheme,
format_link("https://man.dvc.org/config#cache"),
)
)
@@ -350,12 +331,12 @@ class CacheLinkError(DvcException):
)
)
def __init__(self, path_infos):
def __init__(self, fs_paths):
msg = "No possible cache link types for '{}'. {}".format(
", ".join([str(path) for path in path_infos]), self.SUPPORT_LINK
", ".join(fs_paths), self.SUPPORT_LINK
)
super().__init__(msg)
self.path_infos = path_infos
self.fs_paths = fs_paths
class CircularImportError(DvcException):
+21 -10
View File
@@ -16,17 +16,21 @@ from dvc.exceptions import (
PathMissingError,
)
from dvc.repo import Repo
from dvc.scm import CloneError, map_scm_exception
from dvc.utils import relpath
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
from dvc.config import NoRemoteError
from dvc.scm.git import Git
from dvc.fs.git import GitFileSystem
logger.debug("Creating external repo %s@%s", url, rev)
path = _cached_clone(url, rev, for_write=for_write)
@@ -42,12 +46,18 @@ def external_repo(
config = _get_remote_config(url) if os.path.isdir(url) else {}
config.update(cache_config)
root_dir = path if for_write else os.path.realpath(path)
if for_write:
root_dir = path
fs = None
else:
root_dir = os.path.realpath(path)
scm = Git(root_dir)
fs = GitFileSystem(scm=scm, rev=rev)
repo_kwargs = dict(
root_dir=root_dir,
url=url,
scm=None if for_write else Git(root_dir),
rev=None if for_write else rev,
fs=fs,
config=config,
repo_factory=erepo_factory(url, cache_config),
**kwargs,
@@ -83,7 +93,7 @@ def erepo_factory(url, cache_config, *args, **kwargs):
def make_repo(path, **_kwargs):
_config = cache_config.copy()
if os.path.isdir(url):
rel = os.path.relpath(path, _kwargs["scm"].root_dir)
rel = os.path.relpath(path, _kwargs["fs"].fs_args["scm"].root_dir)
repo_path = os.path.join(url, rel)
_config.update(_get_remote_config(repo_path))
return Repo(path, config=_config, **_kwargs)
@@ -173,7 +183,7 @@ def _clone_default_branch(url, rev, for_write=False):
The cloned is reactualized with git pull unless rev is a known sha.
"""
from dvc.scm.git import Git
from scmrepo.git import Git
clone_path, shallow = CLONES.get(url, (None, False))
@@ -198,14 +208,15 @@ def _clone_default_branch(url, rev, for_write=False):
logger.debug("erepo: git pull '%s'", url)
git.pull()
else:
from dvc.scm import clone
logger.debug("erepo: git clone '%s' to a temporary dir", url)
clone_path = tempfile.mkdtemp("dvc-clone")
if not for_write and rev and not Git.is_sha(rev):
# If rev is a tag or branch name try shallow clone first
from dvc.scm.base import CloneError
try:
git = Git.clone(url, clone_path, shallow_branch=rev)
git = clone(url, clone_path, shallow_branch=rev)
shallow = True
logger.debug(
"erepo: using shallow clone for branch '%s'", rev
@@ -213,7 +224,7 @@ def _clone_default_branch(url, rev, for_write=False):
except CloneError:
pass
if not git:
git = Git.clone(url, clone_path)
git = clone(url, clone_path)
shallow = False
CLONES[url] = (clone_path, shallow)
finally:
@@ -237,7 +248,7 @@ def _unshallow(git):
def _git_checkout(repo_path, rev):
from dvc.scm.git import Git
from scmrepo.git import Git
logger.debug("erepo: git checkout %s@%s", repo_path, rev)
git = Git(repo_path)
+10 -11
View File
@@ -1,4 +1,3 @@
import posixpath
from urllib.parse import urlparse
from ..scheme import Schemes
@@ -39,7 +38,7 @@ def get_fs_cls(remote_conf, scheme=None):
return FS_MAP.get(scheme, LocalFileSystem)
def get_fs_config(config, **kwargs):
def get_fs_config(repo, config, **kwargs):
name = kwargs.get("name")
if name:
try:
@@ -50,10 +49,10 @@ def get_fs_config(config, **kwargs):
raise RemoteNotFoundError(f"remote '{name}' doesn't exist")
else:
remote_conf = kwargs
return _resolve_remote_refs(config, remote_conf)
return _resolve_remote_refs(repo, config, remote_conf)
def _resolve_remote_refs(config, remote_conf):
def _resolve_remote_refs(repo, config, remote_conf):
# Support for cross referenced remotes.
# This will merge the settings, shadowing base ref with remote_conf.
# For example, having:
@@ -79,8 +78,10 @@ def _resolve_remote_refs(config, remote_conf):
if parsed.scheme != "remote":
return remote_conf
base = get_fs_config(config, name=parsed.netloc)
url = posixpath.join(base["url"], parsed.path.lstrip("/"))
base = get_fs_config(repo, config, name=parsed.netloc)
cls, _, _ = get_cloud_fs(repo, **base)
relpath = parsed.path.lstrip("/").replace("/", cls.sep)
url = cls.sep.join((base["url"], relpath))
return {**base, **remote_conf, "url": url}
@@ -91,7 +92,7 @@ def get_cloud_fs(repo, **kwargs):
repo_config = repo.config if repo else {}
core_config = repo_config.get("core", {})
remote_conf = get_fs_config(repo_config, **kwargs)
remote_conf = get_fs_config(repo, repo_config, **kwargs)
try:
remote_conf = SCHEMA["remote"][str](remote_conf)
except Invalid as exc:
@@ -113,10 +114,8 @@ def get_cloud_fs(repo, **kwargs):
remote_conf["gdrive_credentials_tmp_dir"] = repo.tmp_dir
url = remote_conf.pop("url")
path_info = cls.PATH_CLS(
cls._strip_protocol(url) # pylint:disable=protected-access
)
fs_path = cls._strip_protocol(url) # pylint:disable=protected-access
extras = cls._get_kwargs_from_urls(url) # pylint:disable=protected-access
conf = {**extras, **remote_conf} # remote config takes priority
return cls, conf, path_info
return cls, conf, fs_path
+49
View File
@@ -0,0 +1,49 @@
import fsspec
from dvc.progress import Tqdm
class FsspecCallback(fsspec.Callback):
def __init__(self, progress_bar):
self.progress_bar = progress_bar
super().__init__()
def set_size(self, size):
if size is not None:
self.progress_bar.total = size
self.progress_bar.refresh()
super().set_size(size)
def relative_update(self, inc=1):
self.progress_bar.update(inc)
super().relative_update(inc)
def absolute_update(self, value):
self.progress_bar.update_to(value)
super().absolute_update(value)
@staticmethod
def wrap_fn(cb, fn):
def wrapped(*args, **kwargs):
res = fn(*args, **kwargs)
cb.relative_update()
return res
return wrapped
def tdqm_or_callback_wrapped(
fobj, method, total, callback=None, **pbar_kwargs
):
if callback:
from funcy import nullcontext
from tqdm.utils import CallbackIOWrapper
callback.set_size(total)
wrapper = CallbackIOWrapper(callback.relative_update, fobj, method)
return nullcontext(wrapper)
return Tqdm.wrapattr(fobj, method, total=total, bytes=True, **pbar_kwargs)
DEFAULT_CALLBACK = fsspec.callbacks.NoOpCallback()
-72
View File
@@ -1,72 +0,0 @@
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, List
from dvc.output import Output
from dvc.path_info import PathInfo
if TYPE_CHECKING:
from dvc.repo import Repo
@dataclass
class Metadata:
"""
Container for storing metadata for a given path, similar in spirit to
`os.stat_result`.
"""
# required field
path_info: PathInfo
repo: "Repo"
# computed fields
is_output: bool = field(init=False, default=False) # is it an output?
# is the path part of an output?
part_of_output: bool = field(init=False, default=False)
# does the path contain outputs?
contains_outputs: bool = field(init=False, default=False)
# is the path tracked by dvc? equivalent to (is_output or part_of_output)
is_dvc: bool = field(init=False, default=False)
# does dvc has any output in that path or inside it? equiv. to bool(outs)
output_exists: bool = field(init=False)
# optional fields
isdir: bool = False # is it a directory?
is_exec: bool = False # is it an executable?
outs: List[Output] = field(default_factory=list) # list of outputs
def __post_init__(self):
self.output_exists = bool(self.outs)
if not self.output_exists:
return
# it can contain multiple outputs
if len(self.outs) > 1:
self.contains_outputs = True
else:
out = self.outs[0]
# or, the path itself could be an output
self.is_output = self.path_info == out.path_info
# or, a directory must have an output somewhere deep inside it
if not self.is_output:
self.contains_outputs = out.path_info.isin(self.path_info)
# or, the path could be a part of an output, i.e. inside of an output
self.part_of_output = not self.is_output and not self.contains_outputs
self.is_dvc = self.is_output or self.part_of_output
# if it contains outputs, it must be a dir
self.isdir = self.contains_outputs
@property
def isfile(self):
# self.isdir might require additional computation, especially if it's
# a dir that's inside a dvc-tracked dir
# since, `Metadata()` does not support being used as null (i.e. when
# path does not exist), `isfile` becomes `isdir`'s complement.
return not self.isdir
def __str__(self):
return f"Metadata: {self.path_info}"
+7 -8
View File
@@ -7,7 +7,6 @@ from fsspec.utils import infer_storage_options
from funcy import cached_property, memoize, wrap_prop
from dvc.exceptions import DvcException
from dvc.path_info import CloudURLInfo
from dvc.scheme import Schemes
from dvc.utils import format_link
@@ -41,9 +40,7 @@ def _az_config():
# pylint:disable=abstract-method
class AzureFileSystem(CallbackMixin, ObjectFSWrapper):
scheme = Schemes.AZURE
PATH_CLS = CloudURLInfo
PARAM_CHECKSUM = "etag"
DETAIL_FIELDS = frozenset(("etag", "size"))
REQUIRES = {
"adlfs": "adlfs",
"knack": "knack",
@@ -52,12 +49,14 @@ class AzureFileSystem(CallbackMixin, ObjectFSWrapper):
@classmethod
def _strip_protocol(cls, path: str):
bucket = infer_storage_options(path).get("host")
if bucket:
return path
opts = infer_storage_options(path)
if opts.get("host"):
return "{host}{path}".format(**opts)
bucket = _az_config().get("storage", "container_name", None)
return f"azure://{bucket}"
return _az_config().get("storage", "container_name", None)
def unstrip_protocol(self, path: str) -> str:
return "azure://" + path.lstrip("/")
@staticmethod
def _get_kwargs_from_urls(urlpath):
+42 -59
View File
@@ -4,13 +4,13 @@ import os
from concurrent.futures import ThreadPoolExecutor, as_completed
from functools import partialmethod
from multiprocessing import cpu_count
from typing import Any, ClassVar, Dict, FrozenSet, Optional
from typing import ClassVar, Dict, Optional
from funcy import cached_property
from tqdm.utils import CallbackIOWrapper
from dvc.exceptions import DvcException
from dvc.path_info import URLInfo
from dvc.progress import DEFAULT_CALLBACK, FsspecCallback
from dvc.fs._callback import DEFAULT_CALLBACK, FsspecCallback
from dvc.ui import ui
from dvc.utils import tmp_fname
from dvc.utils.fs import makedirs, move
@@ -18,14 +18,6 @@ from dvc.utils.fs import makedirs, move
logger = logging.getLogger(__name__)
class RemoteCmdError(DvcException):
def __init__(self, remote, cmd, ret, err):
super().__init__(
"{remote} command '{cmd}' finished with non-zero return code"
" {ret}': {err}".format(remote=remote, cmd=cmd, ret=ret, err=err)
)
class RemoteActionNotImplemented(DvcException):
def __init__(self, action, scheme):
m = f"{action} is not supported for {scheme} remotes"
@@ -36,15 +28,13 @@ class RemoteMissingDepsError(DvcException):
pass
class BaseFileSystem:
class FileSystem:
sep = "/"
scheme = "base"
REQUIRES: ClassVar[Dict[str, str]] = {}
PATH_CLS = URLInfo # type: Any
_JOBS = 4 * cpu_count()
CHECKSUM_DIR_SUFFIX = ".dir"
HASH_JOBS = max(1, min(4, cpu_count() // 2))
LIST_OBJECT_PAGE_SIZE = 1000
TRAVERSE_WEIGHT_MULTIPLIER = 5
@@ -56,7 +46,6 @@ class BaseFileSystem:
CHUNK_SIZE = 64 * 1024 * 1024 # 64 MiB
PARAM_CHECKSUM: ClassVar[Optional[str]] = None
DETAIL_FIELDS: FrozenSet[str] = frozenset()
def __init__(self, **kwargs):
self._check_requires(**kwargs)
@@ -69,10 +58,19 @@ class BaseFileSystem:
def config(self):
return self._config
@cached_property
def path(self):
from .path import Path
return Path(self.sep)
@classmethod
def _strip_protocol(cls, path: str):
return path
def unstrip_protocol(self, path):
return path
@staticmethod
def _get_kwargs_from_urls(urlpath): # pylint:disable=unused-argument
return {}
@@ -129,36 +127,30 @@ class BaseFileSystem:
f"dependencies: {missing}. {hint}"
)
def checksum(self, path_info) -> str:
def checksum(self, path) -> str:
raise NotImplementedError
def open(self, path_info, mode: str = "r", encoding: str = None, **kwargs):
def open(self, path, mode: str = "r", encoding: str = None, **kwargs):
raise RemoteActionNotImplemented("open", self.scheme)
def exists(self, path_info) -> bool:
def exists(self, path) -> bool:
raise NotImplementedError
# pylint: disable=unused-argument
def isdir(self, path_info):
def isdir(self, path):
"""Optional: Overwrite only if the remote has a way to distinguish
between a directory and a file.
"""
return False
def isfile(self, path_info):
def isfile(self, path):
"""Optional: Overwrite only if the remote has a way to distinguish
between a directory and a file.
"""
return True
def isexec(self, path_info):
"""Optional: Overwrite only if the remote has a way to distinguish
between executable and non-executable file.
"""
return False
def iscopy(self, path_info):
def iscopy(self, path):
"""Check if this file is an independent copy."""
return False # We can't be sure by default
@@ -166,28 +158,28 @@ class BaseFileSystem:
"""Return a generator with (root, dirs, files)."""
raise NotImplementedError
def walk_files(self, path_info, **kwargs):
"""Return a generator with `PathInfo`s to all the files.
def find(self, path, prefix=None):
"""Return a generator with `str`s to all the files.
Optional kwargs:
prefix (bool): If true `path_info` will be treated as a prefix
prefix (bool): If true `path` will be treated as a prefix
rather than directory path.
"""
raise NotImplementedError
def is_empty(self, path_info):
def is_empty(self, path):
return False
def info(self, path_info):
def info(self, path):
raise NotImplementedError
def getsize(self, path_info):
return self.info(path_info).get("size")
def getsize(self, path):
return self.info(path).get("size")
def remove(self, path_info):
def remove(self, path):
raise RemoteActionNotImplemented("remove", self.scheme)
def makedirs(self, path_info, **kwargs):
def makedirs(self, path, **kwargs):
"""Optional: Implement only if the remote needs to create
directories before copying/linking/moving data
"""
@@ -210,12 +202,6 @@ class BaseFileSystem:
# pylint: enable=unused-argument
@classmethod
def is_dir_hash(cls, hash_):
if not hash_:
return False
return hash_.endswith(cls.CHECKSUM_DIR_SUFFIX)
def upload(
self,
from_info,
@@ -231,11 +217,10 @@ class BaseFileSystem:
if not hasattr(self, method):
raise RemoteActionNotImplemented(method, self.scheme)
if to_info.scheme != self.scheme:
raise NotImplementedError
if not is_file_obj:
desc = desc or from_info.name
from .local import localfs
desc = desc or localfs.path.name(from_info)
stack = contextlib.ExitStack()
if not callback:
@@ -261,9 +246,6 @@ class BaseFileSystem:
# pylint: disable=no-member
return self.upload_fobj(wrapped, to_info, size=total)
if from_info.scheme != "local":
raise NotImplementedError
logger.debug("Uploading '%s' to '%s'", from_info, to_info)
# pylint: disable=no-member
return self.put_file(
@@ -281,18 +263,14 @@ class BaseFileSystem:
_only_file=False,
**kwargs,
):
from .local import localfs
if not hasattr(self, "get_file"):
raise RemoteActionNotImplemented("get_file", self.scheme)
if from_info.scheme != self.scheme:
raise NotImplementedError
if to_info.scheme != "local":
raise NotImplementedError
download_dir = not _only_file and self.isdir(from_info)
desc = name or to_info.name
desc = name or localfs.path.name(to_info)
stack = contextlib.ExitStack()
if not callback:
pbar_kwargs = {"unit": "Files"} if download_dir else {}
@@ -323,13 +301,16 @@ class BaseFileSystem:
jobs=None,
**kwargs,
):
from_infos = list(self.walk_files(from_info, **kwargs))
from .local import localfs
from_infos = list(self.find(from_info, **kwargs))
if not from_infos:
makedirs(to_info, exist_ok=True)
return None
to_infos = (
to_info / info.relative_to(from_info) for info in from_infos
localfs.path.join(to_info, *self.path.relparts(info, from_info))
for info in from_infos
)
callback.set_size(len(from_infos))
@@ -362,7 +343,9 @@ class BaseFileSystem:
to_info,
callback=DEFAULT_CALLBACK,
):
makedirs(to_info.parent, exist_ok=True)
from .local import localfs
makedirs(localfs.path.parent(to_info), exist_ok=True)
tmp_file = tmp_fname(to_info)
logger.debug("Downloading '%s' to '%s'", from_info, to_info)

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