Compare commits

...
87 Commits
Author SHA1 Message Date
Ruslan Kuprieievandpre-commit-ci[bot] 0bb2f3d10c repo: site_cache_dir: separate subdir from root_dir (#10273)
* repo: site_cache_dir: separate subdir from root_dir

Currently we join scm root_dir with subrepo root dir to have something that would represent that subrepo, but that directory might not exist in reality as we might be using a bare git repo. So we should just treat those as separate things.

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

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

* Update __init__.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-02-01 09:32:22 +02:00
skshetry f42468bcaf Revert "build(deps-dev): update pytest requirement from <8,>=7 to >=7,<9" (#10269) 2024-01-31 12:31:21 +05:45
dependabot[bot] 28545c0cc5 build(deps-dev): update pytest requirement from <8,>=7 to >=7,<9 (#10259)
Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.0.0...8.0.0)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-31 11:12:41 +05:45
dependabot[bot] 0ee752d464 build(deps-dev): update pytest-docker requirement from <3,>=1 to >=1,<4 (#10262)
Updates the requirements on [pytest-docker](https://github.com/avast/pytest-docker) to permit the latest version.
- [Release notes](https://github.com/avast/pytest-docker/releases)
- [Changelog](https://github.com/avast/pytest-docker/blob/master/CHANGELOG.md)
- [Commits](https://github.com/avast/pytest-docker/compare/v1.0.0...v3.0.0)

---
updated-dependencies:
- dependency-name: pytest-docker
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-31 11:12:32 +05:45
skshetry 9a1bcd7f1d nit: fit statement/expr in the same line instead of hanging (#10267)
The code are wrapped due to us using line-length=79 before, but now some
of the code can easily fit in 88 chars.

The formatter did not change this automatically when we changed line-length,
because of magic trailing commas that gets added when they turn into multiple lines.

Black/ruff wrapping them in parens and splitting them into multiple lines
was making it difficult to read.
2024-01-30 18:23:04 +05:45
skshetry 422caa13c4 introduce dataset dependency (#10164)
* introduce dataset dependency

* support Annotated types

* split

* drop dvc.api.datasets

* drop pydantic and typing-extensions

* drop dvc.datasets
2024-01-30 16:44:28 +05:45
skshetry 3b56c40add remove Python 3.8 compat for threadpoolexecutor.shutdown(cancel_futures=True) (#10266) 2024-01-30 15:40:38 +05:45
skshetry b647475a64 drop Python 3.8 support (#10265)
* drop Python 3.8

Note that this PR only makes typing related changes.
Other changes can be done once this gets merged, they
are relatively few in numbers.

* bump build python version

* add typing_extensions to lint group
2024-01-30 11:33:25 +02:00
skshetry 966adec21c ruff: enable RET501 (#10264) 2024-01-30 14:03:49 +05:45
skshetry 4eb073868c update ruff to 0.1.15 (#10263)
update ruff
2024-01-30 13:09:55 +05:45
Peter Rowlands 196c1c7a17 exp show: output executor info for successful exps when available 2024-01-29 16:33:48 +09:00
Ruslan Kuprieievanddberenbaum df5b582c3f fetch: handle missing/modified imports (#10220)
tests: pull after imp-url no-download

Co-authored-by: dberenbaum <[email protected]>
2024-01-25 18:34:06 +02:00
Dave Berenbaum 20edd72a43 status: always show json (#10253) 2024-01-24 03:09:02 +02:00
Ruslan Kuprieiev ba06c0504a deps: bump dvc-data to >=3.8 (#10254) 2024-01-24 02:42:07 +02:00
18a9fea4af hydra: support plugins (#10240)
* hydra: support plugins

* Update dvc/utils/hydra.py

* fix tests

* load plugins after some checks run

---------

Co-authored-by: Ruslan Kuprieiev <[email protected]>
Co-authored-by: Saugat Pachhai (सौगात) <[email protected]>
Co-authored-by: skshetry <[email protected]>
2024-01-23 19:58:14 +05:45
pre-commit-ci[bot] bfb65870a4 [pre-commit.ci] pre-commit autoupdate (#10238)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.1.11 → v0.1.14](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.11...v0.1.14)

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

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

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-01-23 10:06:34 +05:45
Matt Seddon a122b80d52 update min version of dvc-render to 1.0.1 (#10250) 2024-01-23 04:29:43 +02:00
dberenbaum 71afff6db3 exp save: add recursive arg 2024-01-19 12:57:27 +09:00
dberenbaum 48ec86de28 exp save: add target arg 2024-01-19 12:57:27 +09:00
Ruslan KuprieievandDave Berenbaum 78ab929ad1 config: azure: don't forget Coerce() for timeouts (#10247)
Co-authored-by: Dave Berenbaum <[email protected]>
2024-01-19 03:19:37 +02:00
Dave Berenbaum 5c31189a91 repro --pull: always pull deps (#10187)
* repro --pull: always pull deps

* drop unused allow_missing args
2024-01-18 05:11:03 +02:00
skshetry b46bd9ca65 top-level plot collections: fail on empty dict (#10233)
* top-level plot collections: skip empty dict

* tighten schema for plots; require key name
2024-01-12 09:56:50 +05:45
skshetry d711ecda04 cli: remove fix_subparsers (#10229)
It does not look like we need this.
2024-01-11 15:54:44 +05:45
skshetry e3b0f190ab Revert "import-db: fix metavar" (#10228) 2024-01-11 13:30:10 +05:45
skshetry eae4fe21ce cli: lowercase metavar by default (#10227)
* cli: lowercase metavar by default

* add tests for help message
2024-01-11 12:53:17 +05:45
skshetry 1c06302a47 import-db: fix metavar (#10226) 2024-01-11 11:35:08 +05:45
skshetry 71669225c5 db: atomically create output file (#10224) 2024-01-09 14:32:26 +05:45
pre-commit-ci[bot] 60c5f202a1 [pre-commit.ci] pre-commit autoupdate (#10223)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.1.9 → v0.1.11](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.9...v0.1.11)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-01-09 13:28:14 +05:45
skshetry 426a7149a4 database: drop dbt implementation (#10222)
* remove dbt implementation in the database

* lazy load client

* fixup

* fixup

* fix server side cursors

* use exec_driver_sql for testing connection
2024-01-05 19:16:12 +05:45
skshetry 0100c14d6a import-db: support importing a table (#10219)
* import-db: support importing a table

* unhide the import-db command
2024-01-05 09:26:07 +05:45
Ruslan Kuprieiev f1de712710 index: data_tree: handle partial imports 2024-01-04 18:17:45 +02:00
dberenbaum bf5fe95c30 imp_url: gitignore during to_remote op 2024-01-03 16:12:45 +02:00
Ruslan Kuprieiev fe008d9748 index: load files from import 2024-01-03 13:16:05 +02:00
Ruslan Kuprieiev dad464285e deps: bump dvc-data to >=3.6 2024-01-02 18:20:34 +02:00
skshetry d218b6ac2b import-db: simplify error message (#10214) 2024-01-02 20:40:51 +05:45
Ruslan Kuprieiev 041ea17233 index: fix storage for imports 2024-01-02 13:38:39 +02:00
Ruslan Kuprieiev 987b1c4213 output: get rid of out.odb
We use out.files to build index these days, making out.odb redundant.
2024-01-02 07:24:43 +02:00
Ruslan Kuprieiev f2e479422f index: mark remote as read_only for imports
Fixes #10206
2023-12-28 08:08:45 +02:00
Ruslan Kuprieiev 73820cc596 push: don't try to update meta for non versioned remotes
Per https://github.com/iterative/dvc/pull/10191#issuecomment-1870716262
2023-12-28 06:11:16 +02:00
skshetry e7b695dbef fix partial fetch (#10205)
Closes #10199. Bumps dvc-data to >=3.4.
2023-12-27 19:05:33 +05:45
Guido Diepenandskshetry 27a8222e2a webhdfs: implement support for basic auth and proxy (#10075)
* remote: add user key to config schema webhdfs

This was already supported according to documentation, but missing in
code

Fixes: 10062

* remote: add password key to support Basic Auth

* remote: add data_proxy_target for webhdfs remote

This key is needed to construct a potential rewrite dictionary in
webhdfs to deal with HDFS behind High Availability Proxy servers

* bump dvc-webhdfs

---------

Co-authored-by: skshetry <[email protected]>
2023-12-26 08:37:07 +05:45
pre-commit-ci[bot] 5850639a97 [pre-commit.ci] pre-commit autoupdate (#10200)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.1.8 → v0.1.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.8...v0.1.9)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-12-26 08:36:30 +05:45
dberenbaum f2b56bbb8a drop lock from params_show and metrics_show 2023-12-25 10:15:38 +02:00
dependabot[bot] cf8195c829 build(deps-dev): bump mypy from 1.7.1 to 1.8.0 (#10189)
Bumps [mypy](https://github.com/python/mypy) from 1.7.1 to 1.8.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.7.1...v1.8.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-24 15:31:31 +05:45
Ruslan Kuprieiev 402c107823 ui: push: use blank pbar for the header
Same as in `dvc fetch`.
2023-12-23 04:40:23 +02:00
Ruslan Kuprieiev b73754edd1 deps: bump dvc-data to >=3.3 2023-12-23 01:19:21 +02:00
Ruslan Kuprieiev 333f2f04f0 push: set desc explicitly
Leftover from https://github.com/iterative/dvc/pull/10190
2023-12-22 08:03:29 +02:00
Ruslan Kuprieiev 470c50a22b push: use set_size instead of set_total
From https://github.com/iterative/dvc/pull/10191
2023-12-22 07:55:52 +02:00
Ruslan Kuprieiev 01f49b7f24 push: only collect needed files when updating version_ids 2023-12-22 07:38:26 +02:00
Ruslan Kuprieiev 19f2246bee push: leave ui messages
Same as we do in pull/fetch/checkout.
2023-12-22 05:51:54 +02:00
Ruslan Kuprieiev 6a44783d72 index: load meta from outs.files 2023-12-22 05:26:18 +02:00
Ruslan Kuprieiev 98a286d88d repo: don't forget to close data_index when dropping 2023-12-22 05:26:18 +02:00
Ruslan Kuprieiev 499c2dda01 testing: don't forget to check dvc.lock for cloud versioned dirs 2023-12-21 01:05:08 +02:00
Ruslan Kuprieiev 5f2615ff53 deps: bump dvc-data to >=0.3.1 2023-12-21 01:05:08 +02:00
dberenbaum 457d615a51 gc: fix test_gc_no_dir_cache 2023-12-20 00:21:23 +02:00
dberenbaum 827f9a7f03 gc: add --skip-failed 2023-12-20 00:21:23 +02:00
pre-commit-ci[bot] 7821e126fe [pre-commit.ci] pre-commit autoupdate (#10183)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.1.7 → v0.1.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.7...v0.1.8)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-12-19 15:31:12 +05:45
Helio Machadoandpre-commit-ci[bot] 72ab5a12df param: optimize fix for exponential memory allocation (#10180)
* param: simplify fix for exponential memory allocation

* Apply suggestions from code review

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

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

* Remove `deepcopy` again

* Restore deepcopy

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

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

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-12-18 08:37:31 +05:45
Helio Machadoandpre-commit-ci[bot] abcd70b9cf param: fix exponential memory allocation (#10178)
* param: fix exponential memory allocation

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

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

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-12-16 20:08:44 +05:45
Ruslan Kuprieiev 3e4e143bee deps: update dvc-data to >=3 2023-12-15 08:02:40 +02:00
dependabot[bot] 766b43bc2d build(deps): bump actions/upload-artifact from 3 to 4 (#10170)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-15 10:11:19 +05:45
dependabot[bot] 92c66b2331 build(deps): bump actions/download-artifact from 3 to 4 (#10171)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-15 10:11:06 +05:45
Ruslan Kuprieiev b1211366cd dvcfs: make repo creation lazy 2023-12-14 21:43:24 +02:00
Ruslan Kuprieiev 92823f1d84 tests: repro: don't depend on particular order 2023-12-14 21:43:24 +02:00
dependabot[bot] f3fa67440d build(deps): bump github/codeql-action from 2 to 3
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)

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

Signed-off-by: dependabot[bot] <[email protected]>
2023-12-14 06:04:54 +02:00
Ruslan Kuprieiev 3275f1ebf5 deps: bump dvc-data to >= 2.25.0 2023-12-14 05:04:36 +02:00
Ruslan Kuprieiev 4392af6d95 cli: debug: finalize profiler reports on errors
Even if a command failed, it might still be useful to look at the profiler report.
2023-12-13 20:34:30 +02:00
pre-commit-ci[bot] a02c5a7405 [pre-commit.ci] pre-commit autoupdate (#10153)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.1.6 → v0.1.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.6...v0.1.7)

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

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

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-12-13 19:56:38 +05:45
skshetry dec1ac7346 deps: update dvc-data to >=2.24 (#10158) 2023-12-13 16:55:45 +05:45
Ruslan Kuprieiev 8ccae0a086 dvcfs: implement basic du 2023-12-12 23:56:19 +02:00
skshetry 3c31234151 unprotect: add progress bar (#10152)
* unprotect: add progress bar

* Update pyproject.toml
2023-12-12 09:47:35 +05:45
skshetry 77b451d118 database: rename Adapter to Client (#10151) 2023-12-09 11:28:47 +05:45
Ruslan Kuprieiev 1d47b03cd5 bench: mark test_get/import as flaky (#10148)
Sometimes seeing those error out in 2.x and early 3.x with:

```
ERROR: unexpected error - [Errno 95] no more link types left to try out: [Errno 95] Operation not supported
```

It seems like we've fixed it at some point, but I was not able to quickly find a link.
2023-12-08 17:15:57 +02:00
Dave Berenbaum 7c898cfad7 exp: ignore workspace errors during push/pull (#10128)
* exp: ignore workspace errors during push/pull

* refactor fetch revs warning

* mention failed revs are skipped

* add workspace arg to brancher

* pass workspace=False only for dvc exp push/pull
2023-12-07 13:46:18 +02:00
dependabot[bot] 1f07023d17 build(deps): bump actions/setup-python from 4 to 5 (#10145)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-07 09:06:35 +05:45
Ruslan Kuprieiev 1e6b0242aa deps: bump dvc-data to 2.22.6 (#10144) 2023-12-06 18:41:52 +02:00
skshetry 3c1e9ee9be run test on 3.12 (#10140) 2023-12-06 21:47:50 +05:45
Ruslan Kuprieiev 9622157565 deps: bump dvc-data to >=2.22.5 (#10141) 2023-12-06 13:32:37 +02:00
Ruslan Kuprieiev 6ff4342f02 deps: bump dvc-data to >=2.22.4 (#10139)
* deps: bump dvc-data to >=2.22.4

* Update test_odb.py
2023-12-06 11:51:05 +02:00
Matt Seddon 5bb72b36cb plots: standardise across DVC/Studio/VS Code (#9931)
* standardise plot data output

* add data required for new anchors to renderer properties

* feed dvc-render updates into API output

* update --json --split api with new data types (send split anchor definitions as strings)

* accomodate zoom and pan anchor

* switch anchor_revs to revs_with_datapoints

* accomodate height and width anchors

* drop terrible idea of holding all data as strings

* fix plot having multiple x fields

* move inferred properties off vega converter class
2023-12-06 11:25:13 +11:00
Ruslan Kuprieiev 1aed230cd0 deps: bump dvc-oss to >=2.22.0
First release with unpinned fsspec version.
2023-12-05 18:48:36 +02:00
pre-commit-ci[bot] c50b2e1e71 [pre-commit.ci] pre-commit autoupdate (#10100)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.1.5 → v0.1.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.5...v0.1.6)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-12-05 21:36:22 +05:45
dependabot[bot] 34bc410075 build(deps-dev): bump mypy from 1.7.0 to 1.7.1 (#10108)
Bumps [mypy](https://github.com/python/mypy) from 1.7.0 to 1.7.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.7.0...v1.7.1)

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-05 21:23:17 +05:45
47ff40da0c studio: Add Studio authentication to DVC (#10074)
* Add Studio authentication to DVC

Implemented a new feature to authenticate DVC with Iterative Studio. This includes generating an access token, starting device login, checking if user code is authorized, and handling any request exceptions. Furthermore, the commit creates a new authentication command for DVC. This feature was added to allow DVC users to authenticate with Studio, which is necessary for functionality such as sharing live experiments and notifying Studio about pushed experiments.

* Add authentication validation and error handling to DVC Studio

This commit introduces validation for scopes used in DVC Studio's authentication process. If an unsupported scope is passed, the user will receive an error message listing the invalid scopes and the program will return an error status. Additionally, this commit changes the program's exit behavior when device code authorization fails or authentication process is successful, replacing 'sys.exit' with appropriate returned values.

* Add logout functionality to DVC Studio commands

This commit introduces the logout command for DVC Studio. It allows users to manually log out from DVC Studio via the command line interface. The logout command only removes the user's token if it exists, otherwise, it will print an error message and return an error status. This adds an extra layer of control for the users and enhances the security for the usage of DVC Studio.

* Improve User Authentication process for DVC Studio

Implemented a new 'token' command facilitating users to check their authentication token while operating with DVC Studio. This implementation enhances user experience by providing the ability to validate logged-in status. If not logged in, it rightly prompts an error, ensuring secure access.

* Add unit tests for auth commands

Unit tests are added for the authentication commands used in DVC Studio. The modifications include tests for login with invalid scope, standard login, logout, and token commands. The tests verify successful execution and appropriate responses for different user authentication scenarios, thereby increasing the robustness and reliability of the auth module.

* Update unit tests for auth commands

Unit tests for DVC Studio authentication commands have been updated. These checks verify successful execution and appropriate responses under various authentication scenarios, including login with invalid scope, standard login, logout, and token commands in order to enhance the robustness and reliability of the auth module.

* Add unit tests and modify authentication function in studio utils

This commit adds several new unit tests in test_studio.py. Tests for start_device_login and check_token_authorization functionality are included to verify the correct behavior of these methods. Moreover, it also modifies the authentication function in studio.py for improving the robustness. Changes in the function aimed to handle unexpected error scenarios and make the code more resilient.

* Refactor mock_response usage in tests and add test_auth.py

This commit refactors the way mock responses are used in test_studio.py to follow DRY principles, changing the private _mock_response to a public reusable function. It also adds a new file, test_auth.py, that provides functional tests for authorisation methods, ensuring their functionality. The patches in test_auth.py helps test different cases like expired authorization and successful authorization. Furthermore, test_auth.py also tests if correct configuration is set on successful authorization. Modifications on the method test_auth_login_arguments in test_auth.py were made to refine test cases, thus making the tests more precise and straightforward.

* Refactor error handling in `dvc/utils/studio.py`

Refactored error handling by removing redundant code blocks and introducing more detailed logging for request exceptions. This makes the failure cases handled explicitly and streamlines the traceability. Also, tests for authorization have been refined and more specific cases like 'expired authorization' have been included.

* Replace `auth` module with `studio` in DVC

Renamed all instances of `auth` to `studio`, as we are moving the authentication process into a new module `studio`. This includes changes to command names, classes, and test file names to reflect the updated module. It improves the organization of the authentication features related to DVC Studio, separating them from other types of authorization.

* Remove device login methods in utils/studio

Removed methods related to device login and token authorization in `utils/studio.py` and its corresponding test cases. These functionalities have been moved to the `dvc-studio-client` module to decouple the login process and make the code maintainable. Updated the dependencies in `pyproject.toml` to use the new `dvc-studio-client` module. Also, updated the command/studio to use the `dvc-studio-client` module for auth functionalities.

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

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

* Update dvc-studio-client dependency in pyproject.toml

The "dvc-studio-client" dependency in pyproject.toml is updated from a specific git commit to v0.16.0, removing the TODO comment. This change was made because the necessary features from the PR linked in the TODO comment have been merged and released in v0.16.0, making the git commit unnecessary. Now the code is cleaner and easier to track with versioned dependency.

* Update DVC Studio components and simplify authentication process

This commit modifies several parts of the ‘dvc/commands/studio.py’ and updates DVC Studio Client's version in 'pyproject.toml' for compatibility with the changes.

Primarily, the authentication process has been simplified. The unnecessary generation of a random token name is removed while initiating authorization in the run method of CmdStudioLogin class, enhancing the code quality.

Changes made in 'dvc/commands/studio.py' are mainly restructuring the code to adapt to the newer version of DVC Studio Client, including changes to the scopes and methods used from this client.

The corresponding changes were also reflected in unit tests. The test files 'tests/func/test_studio.py' and 'tests/unit/command/test_studio.py' are updated in accordance with the approach now used in 'dvc/commands/studio.py'.

* Remove unnecessary tests and update StudioClient methods

Removed functional test file `test_studio.py`from DVC to move it to studio client. The new mock response no longer needs a full testing file, simplifying our test environment.

In `dvc/commands/studio.py`, adjusted function imports to align with new StudioClient function names, which better indicate their purpose: `initiate_authorization` changed to `get_access_token`.

Additionally, `DEFAULT_SCOPES` was removed from both `dvc/commands/studio.py` and `tests/unit/command/test_studio.py`, simplifying the token acquisition process and removing unnecessary global variable. The `--scopes` command, if needed, can be passed directly without requiring a default fallback.

The corresponding changes were reflected in unit tests : the mock functions in `tests/unit/command/test_studio.py` were changed to align with the new authentication method, and updated the usage of the `get_access_token` function.

* Update token success message in dvc/commands/studio.py

This commit replaces f-string (formatted string literal) with a regular string for the success message after token authentication. The change was necessary as the variable `token_name` is already being passed as a formatted string, thus no need for f-string in the success message sentence. This makes code cleaner and more consistent.

* Refactor tests in `test_studio.py` for better readability

* Updated terminology in `studio.py` and `test_studio.py`

Changes terminology from 'authorize' to 'authenticate' to better align with common practices and improve understanding. Also fixed the `get_access_token` function in 'test_studio.py' where 'AuthorizationExpired' was replaced with 'AuthenticationExpired' for better error handling.

* Change 'dvc' to 'DVC' in `studio.py`

Updates all instances of 'dvc' to 'DVC' in `studio.py` for enhanced consistency with the rest of the project. This change aims to standardize the usage of the term across all project files.

* Improve Studio commands information clarity

Updated descriptions in various commands on 'dvc/commands/studio.py' focused on improving clarity and context. Authentication message, command helper, and descriptions have been expanded to provide better understanding. Also, 'dvc' has been changed to 'DVC' in 'studio.py' for consistency with the rest of the project files.

* Not relevant anymore

* Change argument name in studio login

Renamed the argument from "--use-device-code" to "--no-open" in the login function under the Studio authentication. The option's name was changed to make it more intuitive and better align with its actual effect, avoiding potential user confusion. Corresponding changes have also been made in unit tests.

* Modify flag for Studio login function

Updated the argument name from "-d" to "-o" in `studio.py` for the Studio login function. The change makes the argument's intention clearer for users. This is significant in improving user interaction with the feature and mitigating potential user misinterpretation.

* Apply suggestions from code review

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: skshetry <[email protected]>
2023-12-05 16:30:10 +05:45
Peter Rowlands ee35ec10a2 cache migrate: include dvc.lock dependencies in --dvc-files 2023-12-05 15:42:26 +09:00
Ruslan Kuprieiev e18fd34b2c testing: bench: use --dataset instead of --size (#10134)
This is no longer just about size (e.g. we have mnist/openorca/used-cars),
so the --dataset makes more sense.
2023-12-04 14:43:37 +02:00
Ruslan Kuprieiev c0b64b0a10 testing: bench: add openorca (#10133)
Need to move the data into dvc at some point.
2023-12-04 12:43:50 +02:00
309 changed files with 3846 additions and 4878 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
name: run benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- uses: iterative/dvc-bench@main
+5 -5
View File
@@ -23,9 +23,9 @@ jobs:
with:
fetch-depth: 0
- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: 3.12
- run: python -m pip install --upgrade pip setuptools_scm build twine
@@ -39,7 +39,7 @@ jobs:
SETUPTOOLS_SCM_PRETEND_VERSION_FOR_DVC: ${{ env.version }}
- run: twine check --strict dist/*
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: Packages
path: dist/
@@ -58,7 +58,7 @@ jobs:
id-token: write
steps:
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: Packages
path: dist
@@ -83,7 +83,7 @@ jobs:
id-token: write
steps:
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: Packages
path: dist
+2 -2
View File
@@ -31,10 +31,10 @@ jobs:
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
queries: security-extended
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
path: ${{ matrix.plugin }}
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.pyv }}
cache: pip
+2 -2
View File
@@ -23,14 +23,14 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-20.04, windows-latest, macos-latest]
pyv: ["3.8", "3.9", "3.10", "3.11"]
pyv: ["3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.pyv }}
cache: pip
+1 -1
View File
@@ -22,7 +22,7 @@ repos:
- id: sort-simple-yaml
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.1.5'
rev: 'v0.1.15'
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
+1 -4
View File
@@ -5,10 +5,7 @@ from PyInstaller.utils.hooks import collect_submodules, is_module_or_submodule
# Celery dynamically imports most celery internals at runtime
# pyinstaller hook must expose all modules loaded by
# kombu.utils.imports:symbol_by_name()
_EXCLUDES = (
"celery.bin",
"celery.contrib",
)
_EXCLUDES = ("celery.bin", "celery.contrib")
hiddenimports = collect_submodules(
"celery",
filter=lambda name: not any(
+45 -39
View File
@@ -23,10 +23,11 @@ def viztracer_profile(
tracer = viztracer.VizTracer(max_stack_depth=depth, log_async=log_async)
tracer.start()
yield
tracer.stop()
tracer.save(path() if callable(path) else path)
try:
yield
finally:
tracer.stop()
tracer.save(path() if callable(path) else path)
@contextmanager
@@ -45,31 +46,33 @@ def yappi_profile(
yappi.set_clock_type("wall" if wall_clock else "cpu")
yappi.start()
yield
yappi.stop()
try:
yield
finally:
yappi.stop()
threads = yappi.get_thread_stats()
stats = {}
if separate_threads:
for thread in threads:
ctx_id = thread.id
stats[ctx_id] = yappi.get_func_stats(ctx_id=ctx_id)
else:
stats[None] = yappi.get_func_stats()
fpath = path() if callable(path) else path
for ctx_id, st in stats.items():
if fpath:
out = f"{fpath}-{ctx_id}" if ctx_id is not None else fpath
st.save(out, type="callgrind")
threads = yappi.get_thread_stats()
stats = {}
if separate_threads:
for thread in threads:
ctx_id = thread.id
stats[ctx_id] = yappi.get_func_stats(ctx_id=ctx_id)
else:
if ctx_id is not None:
print(f"\nThread {ctx_id}") # noqa: T201
st.print_all()
if ctx_id is None:
threads.print_all()
stats[None] = yappi.get_func_stats()
yappi.clear_stats()
fpath = path() if callable(path) else path
for ctx_id, st in stats.items():
if fpath:
out = f"{fpath}-{ctx_id}" if ctx_id is not None else fpath
st.save(out, type="callgrind")
else:
if ctx_id is not None:
print(f"\nThread {ctx_id}") # noqa: T201
st.print_all()
if ctx_id is None:
threads.print_all()
yappi.clear_stats()
@contextmanager
@@ -85,13 +88,15 @@ def instrument(html_output=False):
profiler = Profiler()
profiler.start()
yield
profiler.stop()
try:
yield
finally:
profiler.stop()
if html_output:
profiler.open_in_browser()
return
print(profiler.output_text(unicode=True, color=True)) # noqa: T201
if html_output:
profiler.open_in_browser()
else:
print(profiler.output_text(unicode=True, color=True)) # noqa: T201
@contextmanager
@@ -102,13 +107,14 @@ def profile(dump_path: Optional[str] = None):
prof = cProfile.Profile()
prof.enable()
yield
prof.disable()
if not dump_path:
prof.print_stats(sort="cumtime")
return
prof.dump_stats(dump_path)
try:
yield
finally:
prof.disable()
if dump_path:
prof.dump_stats(dump_path)
else:
prof.print_stats(sort="cumtime")
@contextmanager
+10 -10
View File
@@ -1,5 +1,5 @@
from dataclasses import asdict, dataclass, field, fields
from typing import Any, ClassVar, Dict, List, Optional
from typing import Any, ClassVar, Optional
from funcy import compact
from voluptuous import Required
@@ -13,11 +13,11 @@ class Annotation:
PARAM_META: ClassVar[str] = "meta"
desc: Optional[str] = None
type: Optional[str] = None # noqa: A003
labels: List[str] = field(default_factory=list)
meta: Dict[str, Any] = field(default_factory=dict)
type: Optional[str] = None
labels: list[str] = field(default_factory=list)
meta: dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> Dict[str, str]:
def to_dict(self) -> dict[str, str]:
return compact(asdict(self))
@@ -31,11 +31,11 @@ class Artifact:
path: str
desc: Optional[str] = None
type: Optional[str] = None # noqa: A003
labels: List[str] = field(default_factory=list)
meta: Dict[str, Any] = field(default_factory=dict)
type: Optional[str] = None
labels: list[str] = field(default_factory=list)
meta: dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> Dict[str, str]:
def to_dict(self) -> dict[str, str]:
return compact(asdict(self))
@@ -46,7 +46,7 @@ ANNOTATION_SCHEMA = {
Annotation.PARAM_LABELS: [str],
Annotation.PARAM_META: object,
}
ARTIFACT_SCHEMA: Dict[Any, Any] = {
ARTIFACT_SCHEMA: dict[Any, Any] = {
Required(Artifact.PARAM_PATH): str,
**ANNOTATION_SCHEMA, # type: ignore[arg-type]
}
+2 -2
View File
@@ -7,6 +7,7 @@ from .scm import all_branches, all_commits, all_tags
from .show import metrics_show, params_show
__all__ = [
"DVCFileSystem",
"all_branches",
"all_commits",
"all_tags",
@@ -14,9 +15,8 @@ __all__ = [
"exp_save",
"exp_show",
"get_url",
"metrics_show",
"open",
"params_show",
"metrics_show",
"read",
"DVCFileSystem",
]
+3 -3
View File
@@ -1,4 +1,4 @@
from typing import Any, Dict, Optional
from typing import Any, Optional
from dvc.repo import Repo
@@ -8,7 +8,7 @@ def artifacts_show(
version: Optional[str] = None,
stage: Optional[str] = None,
repo: Optional[str] = None,
) -> Dict[str, str]:
) -> dict[str, str]:
"""
Return path and Git revision for an artifact in a DVC project.
@@ -36,7 +36,7 @@ def artifacts_show(
if version and stage:
raise ValueError("Artifact version and stage are mutually exclusive.")
repo_kwargs: Dict[str, Any] = {
repo_kwargs: dict[str, Any] = {
"subrepos": True,
"uninitialized": True,
}
+7 -15
View File
@@ -1,6 +1,6 @@
from contextlib import _GeneratorContextManager as GCM
from contextlib import contextmanager
from typing import Any, Dict, Optional
from typing import Any, Optional
from funcy import reraise
@@ -39,7 +39,7 @@ def get_url(path, repo=None, rev=None, remote=None):
from dvc.config import NoRemoteError
from dvc_data.index import StorageKeyError
repo_kwargs: Dict[str, Any] = {}
repo_kwargs: dict[str, Any] = {}
if remote:
repo_kwargs["config"] = {"core": {"remote": remote}}
with Repo.open(
@@ -57,11 +57,7 @@ def get_url(path, repo=None, rev=None, remote=None):
class _OpenContextManager(GCM):
def __init__(self, func, args, kwds):
self.gen = func(*args, **kwds)
self.func, self.args, self.kwds = ( # type: ignore[assignment]
func,
args,
kwds,
)
self.func, self.args, self.kwds = (func, args, kwds) # type: ignore[assignment]
def __getattr__(self, name):
raise AttributeError("dvc.api.open() should be used in a with statement.")
@@ -74,8 +70,8 @@ def open( # noqa: A001
remote: Optional[str] = None,
mode: str = "r",
encoding: Optional[str] = None,
config: Optional[Dict[str, Any]] = None,
remote_config: Optional[Dict[str, Any]] = None,
config: Optional[dict[str, Any]] = None,
remote_config: Optional[dict[str, Any]] = None,
):
"""
Opens a file tracked in a DVC project.
@@ -233,7 +229,7 @@ def _open(
config=None,
remote_config=None,
):
repo_kwargs: Dict[str, Any] = {
repo_kwargs: dict[str, Any] = {
"subrepos": True,
"uninitialized": True,
"remote": remote,
@@ -262,11 +258,7 @@ def _open(
fs_path = fs.from_os_path(path)
try:
with fs.open(
fs_path,
mode=mode,
encoding=encoding,
) as fobj:
with fs.open(fs_path, mode=mode, encoding=encoding) as fobj:
yield fobj
except FileNotFoundError as exc:
raise FileMissingError(path) from exc
+5 -5
View File
@@ -1,4 +1,4 @@
from typing import Dict, List, Optional, Union
from typing import Optional, Union
from rich.text import Text
@@ -9,7 +9,7 @@ from dvc.repo.experiments.show import tabulate
def exp_save(
name: Optional[str] = None,
force: bool = False,
include_untracked: Optional[List[str]] = None,
include_untracked: Optional[list[str]] = None,
):
"""
Create a new DVC experiment using `exp save`.
@@ -61,12 +61,12 @@ def _postprocess(exp_rows):
def exp_show(
repo: Optional[str] = None,
revs: Optional[Union[str, List[str]]] = None,
revs: Optional[Union[str, list[str]]] = None,
num: int = 1,
param_deps: bool = False,
force: bool = False,
config: Optional[Dict] = None,
) -> List[Dict]:
config: Optional[dict] = None,
) -> list[dict]:
"""Get DVC experiments tracked in `repo`.
Without arguments, this function will retrieve all experiments derived from
+4 -4
View File
@@ -1,9 +1,9 @@
from typing import List, Optional
from typing import Optional
from dvc.repo import Repo
def all_branches(repo: Optional[str] = None) -> List[str]:
def all_branches(repo: Optional[str] = None) -> list[str]:
"""Get all Git branches in a DVC repository.
Args:
@@ -20,7 +20,7 @@ def all_branches(repo: Optional[str] = None) -> List[str]:
return _repo.scm.list_branches()
def all_commits(repo: Optional[str] = None) -> List[str]:
def all_commits(repo: Optional[str] = None) -> list[str]:
"""Get all Git commits in a DVC repository.
Args:
@@ -37,7 +37,7 @@ def all_commits(repo: Optional[str] = None) -> List[str]:
return _repo.scm.list_all_commits()
def all_tags(repo: Optional[str] = None) -> List[str]:
def all_tags(repo: Optional[str] = None) -> list[str]:
"""Get all Git tags in a DVC repository.
Args:
+7 -6
View File
@@ -1,6 +1,7 @@
import typing
from collections import Counter
from typing import Dict, Iterable, Optional, Union
from collections.abc import Iterable
from typing import Optional, Union
from funcy import first
@@ -8,7 +9,7 @@ from dvc.repo import Repo
def _postprocess(results):
processed: Dict[str, Dict] = {}
processed: dict[str, dict] = {}
for rev, rev_data in results.items():
if not rev_data:
continue
@@ -36,8 +37,8 @@ def metrics_show(
*targets: str,
repo: Optional[str] = None,
rev: Optional[str] = None,
config: Optional[Dict] = None,
) -> Dict:
config: Optional[dict] = None,
) -> dict:
"""Get metrics tracked in `repo`.
Without arguments, this function will retrieve all metrics from all tracked
@@ -159,8 +160,8 @@ def params_show(
stages: Optional[Union[str, Iterable[str]]] = None,
rev: Optional[str] = None,
deps: bool = False,
config: Optional[Dict] = None,
) -> Dict:
config: Optional[dict] = None,
) -> dict:
"""Get parameters tracked in `repo`.
Without arguments, this function will retrieve all params from all tracked
+6 -12
View File
@@ -1,5 +1,5 @@
import os
from typing import TYPE_CHECKING, Optional, Tuple
from typing import TYPE_CHECKING, Optional
from dvc.fs import GitFileSystem, Schemes
from dvc_data.hashfile.db import get_odb
@@ -15,7 +15,7 @@ def _get_odb(
repo,
settings,
fs=None,
prefix: Optional[Tuple[str, ...]] = None,
prefix: Optional[tuple[str, ...]] = None,
hash_name: Optional[str] = None,
**kwargs,
):
@@ -27,7 +27,7 @@ def _get_odb(
cls, config, fs_path = get_cloud_fs(repo.config, **settings)
fs = fs or cls(**config)
if prefix:
fs_path = fs.path.join(fs_path, *prefix)
fs_path = fs.join(fs_path, *prefix)
if hash_name:
config["hash_name"] = hash_name
return get_odb(fs, fs_path, state=repo.state, **config)
@@ -86,7 +86,7 @@ class CacheManager:
return FileStorage(
key=(),
fs=self.local.fs,
path=self.local.fs.path.join(self.default_local_cache_dir, self.FS_DIR),
path=self.local.fs.join(self.default_local_cache_dir, self.FS_DIR),
)
def _init_odb(self, schemes):
@@ -144,15 +144,9 @@ def migrate_2_to_3(repo: "Repo", dry: bool = False):
)
return
with TqdmCallback(
desc="Computing DVC 3.0 hashes",
unit="files",
) as cb:
with TqdmCallback(desc="Computing DVC 3.0 hashes", unit="files") as cb:
migration = prepare(src, dest, callback=cb)
with TqdmCallback(
desc="Migrating to DVC 3.0 cache",
unit="files",
) as cb:
with TqdmCallback(desc="Migrating to DVC 3.0 cache", unit="files") as cb:
count = migrate(migration, callback=cb)
ui.write(f"Migrated {count} files to DVC 3.0 cache location.")
+14
View File
@@ -0,0 +1,14 @@
import argparse
class HelpFormatter(argparse.HelpFormatter):
def _get_default_metavar_for_optional(self, action: argparse.Action) -> str:
return action.dest
class RawTextHelpFormatter(HelpFormatter, argparse.RawTextHelpFormatter):
pass
class RawDescriptionHelpFormatter(HelpFormatter, argparse.RawDescriptionHelpFormatter):
pass
+9 -8
View File
@@ -19,6 +19,7 @@ from dvc.commands import (
data_sync,
destroy,
diff,
du,
experiments,
freeze,
gc,
@@ -42,13 +43,14 @@ from dvc.commands import (
repro,
root,
stage,
studio,
unprotect,
update,
version,
)
from dvc.log import logger
from . import DvcParserError
from . import DvcParserError, formatter
logger = logger.getChild(__name__)
@@ -93,6 +95,8 @@ COMMANDS = [
check_ignore,
data,
artifacts,
studio,
du,
]
@@ -161,7 +165,7 @@ def get_main_parser():
prog="dvc",
description=desc,
parents=[parent_parser],
formatter_class=argparse.RawTextHelpFormatter,
formatter_class=formatter.RawTextHelpFormatter,
add_help=False,
)
@@ -196,15 +200,12 @@ def get_main_parser():
# Sub commands
subparsers = parser.add_subparsers(
title="Available Commands",
metavar="COMMAND",
metavar="command",
dest="cmd",
help="Use `dvc COMMAND --help` for command-specific help.",
help="Use `dvc command --help` for command-specific help.",
required=True,
)
from .utils import fix_subparsers
fix_subparsers(subparsers)
for cmd in COMMANDS:
cmd.add_parser(subparsers, parent_parser)
-12
View File
@@ -26,18 +26,6 @@ class DictAction(argparse.Action):
setattr(args, self.dest, d)
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
+2 -4
View File
@@ -1,6 +1,4 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
@@ -71,7 +69,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(ADD_HELP, "add"),
help=ADD_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
parser.add_argument(
"--no-commit",
+5 -7
View File
@@ -1,8 +1,6 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import DictAction, append_doc_link, fix_subparsers
from dvc.cli.utils import DictAction, append_doc_link
from dvc.exceptions import DvcException
from dvc.log import logger
@@ -47,13 +45,13 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(ARTIFACTS_HELP, "artifacts"),
help=ARTIFACTS_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
artifacts_subparsers = artifacts_parser.add_subparsers(
dest="cmd",
help="Use `dvc artifacts CMD --help` to display command-specific help.",
required=True,
)
fix_subparsers(artifacts_subparsers)
ARTIFACTS_GET_HELP = "Download an artifact from a DVC project."
get_parser = artifacts_subparsers.add_parser(
@@ -61,7 +59,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(ARTIFACTS_GET_HELP, "artifacts/get"),
help=ARTIFACTS_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
get_parser.add_argument("url", help="Location of DVC repository to download from")
get_parser.add_argument(
+6 -7
View File
@@ -1,9 +1,9 @@
import argparse
import os
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.cli.utils import append_doc_link
from dvc.commands.config import CmdConfig
from dvc.ui import ui
@@ -58,12 +58,13 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(CACHE_HELP, "cache"),
help=CACHE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
cache_subparsers = cache_parser.add_subparsers(
dest="cmd",
help="Use `dvc cache CMD --help` for command-specific help.",
required=True,
)
parent_cache_config_parser = argparse.ArgumentParser(
@@ -76,7 +77,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser, parent_cache_config_parser],
description=append_doc_link(CACHE_HELP, "cache/dir"),
help=CACHE_DIR_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
cache_dir_parser.add_argument(
"-u",
@@ -103,7 +104,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(CACHE_HELP, "cache/migrate"),
help=CACHE_MIGRATE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
cache_migrate_parser.add_argument(
"--dvc-files",
@@ -122,5 +123,3 @@ def add_parser(subparsers, parent_parser):
action="store_true",
)
cache_migrate_parser.set_defaults(func=CmdCacheMigrate)
fix_subparsers(cache_subparsers)
+2 -4
View File
@@ -1,6 +1,4 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.ui import ui
@@ -85,7 +83,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(ADD_HELP, "check-ignore"),
help=ADD_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
parser.add_argument(
"-d",
+2 -3
View File
@@ -1,7 +1,6 @@
import argparse
import operator
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import CheckoutError
@@ -67,7 +66,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(CHECKOUT_HELP, "checkout"),
help=CHECKOUT_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
checkout_parser.add_argument(
"--summary",
+2 -4
View File
@@ -1,6 +1,4 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
@@ -40,7 +38,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(COMMIT_HELP, "commit"),
help=COMMIT_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
commit_parser.add_argument(
"-f",
+2 -3
View File
@@ -1,5 +1,4 @@
import argparse
from dvc.cli import formatter
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.completion import PREAMBLE
from dvc.cli.utils import append_doc_link
@@ -31,7 +30,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(COMPLETION_DESCRIPTION, "completion"),
help=COMPLETION_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
completion_parser.add_argument(
"-s",
+2 -1
View File
@@ -3,6 +3,7 @@ import os
from funcy import set_in
from dvc.cli import formatter
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.log import logger
@@ -209,7 +210,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_config_parser, parent_parser],
description=append_doc_link(CONFIG_HELP, "config"),
help=CONFIG_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
config_parser.add_argument(
"-u",
+1 -3
View File
@@ -1,6 +1,5 @@
from dvc.cli import completion
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import fix_subparsers
from dvc.log import logger
logger = logger.getChild(__name__)
@@ -53,10 +52,9 @@ def add_parser(subparsers, parent_parser):
daemon_subparsers = daemon_parser.add_subparsers(
dest="cmd",
help="Use `dvc daemon CMD --help` for command-specific help.",
required=True,
)
fix_subparsers(daemon_subparsers)
DAEMON_UPDATER_HELP = "Fetch latest available version."
daemon_updater_parser = daemon_subparsers.add_parser(
"updater",
+3 -3
View File
@@ -1,6 +1,6 @@
import argparse
from typing import TYPE_CHECKING
from dvc.cli import formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.ui import ui
@@ -86,7 +86,7 @@ def _collect_targets(repo, target, outs):
targets.extend([str(out) for out in stage.outs])
continue
for out in outs_trie.itervalues(prefix=repo.fs.path.parts(path)):
for out in outs_trie.itervalues(prefix=repo.fs.parts(path)):
targets.extend(str(out))
return targets
@@ -160,7 +160,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(DAG_HELP, "dag"),
help=DAG_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
dag_parser.add_argument(
"--dot",
+9 -9
View File
@@ -1,10 +1,10 @@
import argparse
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, ClassVar
from funcy import chunks, compact, log_durations
from dvc.cli import formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.cli.utils import append_doc_link
from dvc.log import logger
from dvc.ui import ui
from dvc.utils import colorize
@@ -17,14 +17,14 @@ logger = logger.getChild(__name__)
class CmdDataStatus(CmdBase):
COLORS = {
COLORS: ClassVar[dict[str, str]] = {
"not_in_remote": "red",
"not_in_cache": "red",
"committed": "green",
"uncommitted": "yellow",
"untracked": "cyan",
}
LABELS = {
LABELS: ClassVar[dict[str, str]] = {
"not_in_remote": "Not in remote",
"not_in_cache": "Not in cache",
"committed": "DVC committed changes",
@@ -32,7 +32,7 @@ class CmdDataStatus(CmdBase):
"untracked": "Untracked files",
"unchanged": "DVC unchanged files",
}
HINTS = {
HINTS: ClassVar[dict[str, tuple[str, ...]]] = {
"not_in_remote": ('use "dvc push <file>..." to upload files',),
"not_in_cache": ('use "dvc fetch <file>..." to download files',),
"committed": ("git commit the corresponding dvc files to update the repo",),
@@ -129,13 +129,13 @@ def add_parser(subparsers, parent_parser):
data_parser = subparsers.add_parser(
"data",
parents=[parent_parser],
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
data_subparsers = data_parser.add_subparsers(
dest="cmd",
help="Use `dvc data CMD --help` to display command-specific help.",
required=True,
)
fix_subparsers(data_subparsers)
DATA_STATUS_HELP = (
"Show changes between the last git commit, the dvcfiles and the workspace."
@@ -144,7 +144,7 @@ def add_parser(subparsers, parent_parser):
"status",
parents=[parent_parser],
description=append_doc_link(DATA_STATUS_HELP, "data/status"),
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
help=DATA_STATUS_HELP,
)
data_status_parser.add_argument(
+5 -5
View File
@@ -1,6 +1,6 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
@@ -145,7 +145,7 @@ def add_parser(subparsers, _parent_parser):
parents=[shared_parent_parser()],
description=append_doc_link(PULL_HELP, "pull"),
help=PULL_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
pull_parser.add_argument(
"-r", "--remote", help="Remote storage to pull from", metavar="<name>"
@@ -220,7 +220,7 @@ def add_parser(subparsers, _parent_parser):
parents=[shared_parent_parser()],
description=append_doc_link(PUSH_HELP, "push"),
help=PUSH_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
push_parser.add_argument(
"-r", "--remote", help="Remote storage to push to", metavar="<name>"
@@ -282,7 +282,7 @@ def add_parser(subparsers, _parent_parser):
parents=[shared_parent_parser()],
description=append_doc_link(FETCH_HELP, "fetch"),
help=FETCH_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
fetch_parser.add_argument(
"-r", "--remote", help="Remote storage to fetch from", metavar="<name>"
@@ -355,7 +355,7 @@ def add_parser(subparsers, _parent_parser):
description=append_doc_link(STATUS_HELP, "status"),
help=STATUS_HELP,
conflict_handler="resolve",
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
status_parser.add_argument(
"-q",
+2 -3
View File
@@ -1,5 +1,4 @@
import argparse
from dvc.cli import formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
@@ -41,7 +40,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(DESTROY_HELP, "destroy"),
help=DESTROY_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
destroy_parser.add_argument(
"-f",
+2 -3
View File
@@ -1,7 +1,6 @@
import argparse
import os
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
@@ -163,7 +162,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(DIFF_DESCRIPTION, "diff"),
help=DIFF_DESCRIPTION,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
diff_parser.add_argument(
"--targets",
+79
View File
@@ -0,0 +1,79 @@
import logging
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import DictAction, append_doc_link
from dvc.ui import ui
logger = logging.getLogger(__name__)
class CmdDU(CmdBaseNoRepo):
def run(self):
from dvc.repo import Repo
from dvc.utils.humanize import naturalsize
entries = Repo.du(
self.args.url,
self.args.path,
rev=self.args.rev,
summarize=self.args.summarize,
config=self.args.config,
remote=self.args.remote,
remote_config=self.args.remote_config,
)
ui.table([(naturalsize(size), path) for path, size in entries])
return 0
def add_parser(subparsers, parent_parser):
DU_HELP = "Show disk usage."
du_parser = subparsers.add_parser(
"du",
parents=[parent_parser],
description=append_doc_link(DU_HELP, "du"),
help=DU_HELP,
formatter_class=formatter.RawTextHelpFormatter,
)
du_parser.add_argument("url", help="Location of DVC repository")
du_parser.add_argument(
"--rev",
nargs="?",
help="Git revision (e.g. SHA, branch, tag)",
metavar="<commit>",
)
du_parser.add_argument(
"-s",
"--summarize",
action="store_true",
help="Show total disk usage.",
)
du_parser.add_argument(
"--config",
type=str,
help=(
"Path to a config file that will be merged with the config "
"in the target repository."
),
)
du_parser.add_argument(
"--remote",
type=str,
help="Remote name to set as a default in the target repository.",
)
du_parser.add_argument(
"--remote-config",
type=str,
nargs="*",
action=DictAction,
help=(
"Remote config options to merge with a remote's config (default or one "
"specified by '--remote') in the target repository."
),
)
du_parser.add_argument(
"path",
nargs="?",
help="Path to directory within the repository",
).complete = completion.DIR
du_parser.set_defaults(func=CmdDU)
+4 -5
View File
@@ -1,6 +1,5 @@
import argparse
from dvc.cli.utils import append_doc_link, fix_subparsers, hide_subparsers_from_help
from dvc.cli import formatter
from dvc.cli.utils import append_doc_link, hide_subparsers_from_help
from dvc.commands.experiments import (
apply,
branch,
@@ -44,16 +43,16 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
aliases=["exp"],
description=append_doc_link(EXPERIMENTS_HELP, "exp"),
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
help=EXPERIMENTS_HELP,
)
experiments_subparsers = experiments_parser.add_subparsers(
dest="cmd",
help="Use `dvc experiments CMD --help` to display command-specific help.",
required=True,
)
fix_subparsers(experiments_subparsers)
for cmd in SUB_COMMANDS:
cmd.add_parser(experiments_subparsers, parent_parser)
hide_subparsers_from_help(experiments_subparsers)
+2 -4
View File
@@ -1,6 +1,4 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
@@ -30,7 +28,7 @@ def add_parser(experiments_subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_APPLY_HELP, "exp/apply"),
help=EXPERIMENTS_APPLY_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
experiments_apply_parser.add_argument(
"--no-force",
+2 -3
View File
@@ -1,5 +1,4 @@
import argparse
from dvc.cli import formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
@@ -21,7 +20,7 @@ def add_parser(experiments_subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_BRANCH_HELP, "exp/branch"),
help=EXPERIMENTS_BRANCH_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
experiments_branch_parser.add_argument(
"experiment", help="Experiment to be promoted."
+2 -3
View File
@@ -1,5 +1,4 @@
import argparse
from dvc.cli import formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
@@ -20,6 +19,6 @@ def add_parser(experiments_subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_CLEAN_HELP, "exp/clean"),
help=EXPERIMENTS_CLEAN_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
experiments_clean_parser.set_defaults(func=CmdExperimentsClean)
+2 -4
View File
@@ -1,6 +1,4 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands.metrics import DEFAULT_PRECISION
@@ -59,7 +57,7 @@ def add_parser(experiments_subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_DIFF_HELP, "exp/diff"),
help=EXPERIMENTS_DIFF_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
experiments_diff_parser.add_argument(
"a_rev", nargs="?", help="Old experiment to compare (defaults to HEAD)"
+2 -3
View File
@@ -1,5 +1,4 @@
import argparse
from dvc.cli import formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import InvalidArgumentError
@@ -63,7 +62,7 @@ def add_parser(experiments_subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_LIST_HELP, "exp/list"),
help=EXPERIMENTS_LIST_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
add_rev_selection_flags(experiments_list_parser, "List")
display_group = experiments_list_parser.add_mutually_exclusive_group()
+2 -3
View File
@@ -1,5 +1,4 @@
import argparse
from dvc.cli import formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
@@ -49,7 +48,7 @@ def add_parser(experiments_subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_PULL_HELP, "exp/pull"),
help=EXPERIMENTS_PULL_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
add_rev_selection_flags(experiments_pull_parser, "Pull", True)
experiments_pull_parser.add_argument(
+4 -5
View File
@@ -1,7 +1,6 @@
import argparse
from typing import Any, Dict
from typing import Any
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
@@ -12,7 +11,7 @@ logger = logger.getChild(__name__)
class CmdExperimentsPush(CmdBase):
@staticmethod
def log_result(result: Dict[str, Any], remote: str):
def log_result(result: dict[str, Any], remote: str):
from dvc.utils import humanize
def join_exps(exps):
@@ -90,7 +89,7 @@ def add_parser(experiments_subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_PUSH_HELP, "exp/push"),
help=EXPERIMENTS_PUSH_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
add_rev_selection_flags(experiments_push_parser, "Push", True)
experiments_push_parser.add_argument(
+2 -3
View File
@@ -1,5 +1,4 @@
import argparse
from dvc.cli import formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import InvalidArgumentError
@@ -54,7 +53,7 @@ def add_parser(experiments_subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_REMOVE_HELP, "exp/remove"),
help=EXPERIMENTS_REMOVE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
remove_group = experiments_remove_parser.add_mutually_exclusive_group()
add_rev_selection_flags(experiments_remove_parser, "Remove", False)
+2 -3
View File
@@ -1,5 +1,4 @@
import argparse
from dvc.cli import formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import InvalidArgumentError
@@ -38,7 +37,7 @@ def add_parser(experiments_subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_RENAME_HELP, "exp/rename"),
help=EXPERIMENTS_RENAME_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
rename_group = experiments_rename_parser.add_mutually_exclusive_group()
rename_group.add_argument(
+3 -6
View File
@@ -1,5 +1,6 @@
import argparse
from dvc.cli import formatter
from dvc.cli.utils import append_doc_link
from dvc.commands.repro import CmdRepro
from dvc.commands.repro import add_arguments as add_repro_arguments
@@ -32,7 +33,7 @@ def add_parser(experiments_subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_RUN_HELP, "exp/run"),
help=EXPERIMENTS_RUN_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
_add_run_common(experiments_run_parser)
experiments_run_parser.set_defaults(func=CmdExperimentsRun)
@@ -106,8 +107,4 @@ def _add_run_common(parser):
default=None,
help="Custom commit message to use when committing the experiment.",
)
parser.add_argument(
"-M", # obsolete
dest="message",
help=argparse.SUPPRESS,
)
parser.add_argument("-M", dest="message", help=argparse.SUPPRESS) # obsolete
+17 -6
View File
@@ -1,5 +1,6 @@
import argparse
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
@@ -13,7 +14,9 @@ class CmdExperimentsSave(CmdBase):
def run(self):
try:
ref = self.repo.experiments.save(
targets=self.args.targets,
name=self.args.name,
recursive=self.args.recursive,
force=self.args.force,
include_untracked=self.args.include_untracked,
message=self.args.message,
@@ -38,7 +41,19 @@ def add_parser(experiments_subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_SAVE_HELP, "exp/save"),
help=EXPERIMENTS_SAVE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
save_parser.add_argument(
"targets",
nargs="*",
help=("Limit DVC caching to these .dvc files and stage names."),
).complete = completion.DVCFILES_AND_STAGE
save_parser.add_argument(
"-R",
"--recursive",
action="store_true",
default=False,
help="Cache subdirectories of the specified directory.",
)
save_parser.add_argument(
"-f",
@@ -78,9 +93,5 @@ def add_parser(experiments_subparsers, parent_parser):
default=None,
help="Custom commit message to use when committing the experiment.",
)
save_parser.add_argument(
"-M", # obsolete
dest="message",
help=argparse.SUPPRESS,
)
save_parser.add_argument("-M", dest="message", help=argparse.SUPPRESS) # obsolete
save_parser.set_defaults(func=CmdExperimentsSave)
+5 -3
View File
@@ -1,10 +1,12 @@
import argparse
import re
from collections.abc import Iterable
from datetime import date, datetime
from typing import TYPE_CHECKING, Dict, Iterable
from typing import TYPE_CHECKING
from funcy import lmap
from dvc.cli import formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands.metrics import DEFAULT_PRECISION
@@ -56,7 +58,7 @@ def baseline_styler(typ):
def show_experiments(
td: "TabularData",
headers: Dict[str, Iterable[str]],
headers: dict[str, Iterable[str]],
keep=None,
drop=None,
pager=True,
@@ -200,7 +202,7 @@ def add_parser(experiments_subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_SHOW_HELP, "exp/show"),
help=EXPERIMENTS_SHOW_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
add_rev_selection_flags(experiments_show_parser, "Show")
experiments_show_parser.add_argument(
+3 -5
View File
@@ -1,6 +1,4 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
@@ -38,7 +36,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(FREEZE_HELP, "freeze"),
help=FREEZE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
freeze_parser.add_argument(
"targets", nargs="+", help="Stages or .dvc files to freeze"
@@ -51,7 +49,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(UNFREEZE_HELP, "unfreeze"),
help=UNFREEZE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
unfreeze_parser.add_argument(
"targets", nargs="+", help="Stages or .dvc files to unfreeze"
+11 -4
View File
@@ -1,6 +1,6 @@
import argparse
import os
from dvc.cli import formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
@@ -86,6 +86,7 @@ class CmdGC(CmdBase):
num=self.args.num,
not_in_remote=self.args.not_in_remote,
dry=self.args.dry,
skip_failed=self.args.skip_failed,
)
return 0
@@ -101,7 +102,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(GC_DESCRIPTION, "gc"),
help=GC_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
gc_parser.add_argument(
"-w",
@@ -155,12 +156,12 @@ def add_parser(subparsers, parent_parser):
"--date",
type=str,
dest="commit_date",
metavar="<YYYY-MM-DD>",
metavar="<yyyy-mm-dd>",
default=None,
help=(
"Keep cached data referenced in the commits after ( inclusive )"
" a certain time. Date must match the extended ISO 8601 format "
"(YYYY-MM-DD)."
"(yyyy-mm-dd)."
),
)
gc_parser.add_argument(
@@ -188,6 +189,12 @@ def add_parser(subparsers, parent_parser):
help="Remote storage to collect garbage in",
metavar="<name>",
)
gc_parser.add_argument(
"--skip-failed",
action="store_true",
default=False,
help="Skip revisions that fail when collected.",
)
gc_parser.add_argument(
"-f",
"--force",
+2 -4
View File
@@ -1,6 +1,4 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import DictAction, append_doc_link
from dvc.exceptions import DvcException
@@ -59,7 +57,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(GET_HELP, "get"),
help=GET_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
get_parser.add_argument(
"url", help="Location of DVC or Git repository to download from"
+2 -4
View File
@@ -1,6 +1,4 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import DictAction, append_doc_link
from dvc.exceptions import DvcException
@@ -36,7 +34,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(GET_HELP, "get-url"),
help=GET_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
get_parser.add_argument(
"url", help="See `dvc import-url -h` for full list of supported URLs."
+3 -3
View File
@@ -1,7 +1,7 @@
import os
from dvc.cli import formatter
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import fix_subparsers
from dvc.exceptions import NotDvcRepoError
from dvc.log import logger
@@ -94,10 +94,9 @@ def add_parser(subparsers, parent_parser):
git_hook_subparsers = git_hook_parser.add_subparsers(
dest="cmd",
help="Use `dvc daemon CMD --help` for command-specific help.",
required=True,
)
fix_subparsers(git_hook_subparsers)
PRE_COMMIT_HELP = "Run pre-commit GIT hook."
pre_commit_parser = git_hook_subparsers.add_parser(
"pre-commit",
@@ -140,6 +139,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=MERGE_DRIVER_HELP,
help=MERGE_DRIVER_HELP,
formatter_class=formatter.HelpFormatter,
)
merge_driver_parser.add_argument(
"--ancestor",
+2 -4
View File
@@ -1,6 +1,4 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import DictAction, append_doc_link
from dvc.exceptions import DvcException
@@ -51,7 +49,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(IMPORT_HELP, "import"),
help=IMPORT_HELP,
formatter_class=argparse.RawTextHelpFormatter,
formatter_class=formatter.RawTextHelpFormatter,
)
import_parser.add_argument(
"url", help="Location of DVC or Git repository to download from"
+27 -94
View File
@@ -1,9 +1,5 @@
import argparse
from funcy import compact, merge
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase, CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.log import logger
from dvc.ui import ui
@@ -11,115 +7,55 @@ from dvc.ui import ui
logger = logger.getChild(__name__)
class CmdTestDb(CmdBase):
class CmdTestDb(CmdBaseNoRepo):
def run(self):
from dvc.database import get_adapter
from dvc.database.dbt_utils import DBT_PROJECT_FILE, is_dbt_project
from dvc.dependency.db import _get_dbt_config
from dvc.config import Config
from dvc.database import client
from dvc.exceptions import DvcException
connection = self.args.connection
db_config = self.repo.config.get("db", {})
config = db_config.get(connection, {})
if connection and not config:
connection = self.args.conn
db_config = Config.from_cwd().get("db", {})
if connection not in db_config:
raise DvcException(f"connection {connection} not found in config")
cli_config = compact(
{
"url": self.args.url,
"username": self.args.username,
"password": self.args.password,
}
)
conn_config = merge(config, cli_config)
cli_dbt_config = {"profile": self.args.profile, "target": self.args.target}
dbt_config = merge(_get_dbt_config(self.repo.config), cli_dbt_config)
project_dir = self.repo.root_dir
if not (conn_config or dbt_config):
if is_dbt_project(project_dir):
ui.write("Using", DBT_PROJECT_FILE, "for testing", styled=True)
else:
raise DvcException(
f"no config set and {DBT_PROJECT_FILE} is missing; "
"provide arguments or set a configuration"
)
adapter = get_adapter(conn_config, project_dir=project_dir, **dbt_config)
with adapter as db:
config = db_config.get(connection, {})
if self.args.url:
config["url"] = self.args.url
if self.args.username:
config["username"] = self.args.username
if self.args.password:
config["password"] = self.args.password
with client(config) as db:
ui.write(f"Testing with {db}", styled=True)
creds = getattr(db, "creds", {})
for k, v in creds.items():
ui.write("\t", f"{k}:", v, styled=True)
if creds:
ui.write()
db.test_connection()
ui.write("Connection successful", styled=True)
class CmdImportDb(CmdBase):
def run(self):
if not (self.args.sql or self.args.model):
raise argparse.ArgumentTypeError("Either of --sql or --model is required.")
self.repo.imp_db(
url=self.args.url,
rev=self.args.rev,
project_dir=self.args.project_dir,
sql=self.args.sql,
model=self.args.model,
profile=self.args.profile,
target=self.args.target,
table=self.args.table,
output_format=self.args.output_format,
out=self.args.out,
force=self.args.force,
connection=self.args.connection,
connection=self.args.conn,
)
return 0
def add_parser(subparsers, parent_parser):
IMPORT_HELP = (
"Download file or directory tracked by DVC or by Git "
"into the workspace, and track it."
)
IMPORT_HELP = "Snapshot a table or a SQL query result to a CSV/JSON format"
import_parser = subparsers.add_parser(
"import-db",
parents=[parent_parser],
description=append_doc_link(IMPORT_HELP, "import"),
add_help=False,
description=append_doc_link(IMPORT_HELP, "import-db"),
help=IMPORT_HELP,
formatter_class=formatter.RawTextHelpFormatter,
)
import_parser.add_argument(
"--url", help="Location of DVC or Git repository to download from"
)
import_parser.add_argument(
"--rev",
nargs="?",
help="Git revision (e.g. SHA, branch, tag)",
metavar="<commit>",
)
import_parser.add_argument(
"--project-dir", nargs="?", help="Subdirectory to the dbt project location"
)
group = import_parser.add_mutually_exclusive_group()
group.add_argument(
"--sql",
help="SQL query",
)
group.add_argument(
"--model",
help="Model name to download",
)
import_parser.add_argument("--profile", help="Profile to use")
import_parser.add_argument("--target", help="Target to use")
group = import_parser.add_mutually_exclusive_group(required=True)
group.add_argument("--sql", help="SQL query to snapshot")
group.add_argument("--table", help="Table to snapshot")
import_parser.add_argument(
"--output-format",
default="csv",
@@ -144,8 +80,7 @@ def add_parser(subparsers, parent_parser):
)
import_parser.add_argument(
"--conn",
dest="connection",
nargs="?",
required=True,
help="Database connection to use, needs to be set in config",
)
@@ -158,10 +93,8 @@ def add_parser(subparsers, parent_parser):
description=append_doc_link(TEST_DB_HELP, "test-db"),
add_help=False,
)
test_db_parser.add_argument("--conn", dest="connection")
test_db_parser.add_argument("--conn", required=True)
test_db_parser.add_argument("--url")
test_db_parser.add_argument("--password")
test_db_parser.add_argument("--username")
test_db_parser.add_argument("--profile")
test_db_parser.add_argument("--target")
test_db_parser.set_defaults(func=CmdTestDb)
+2 -4
View File
@@ -1,6 +1,4 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import DictAction, append_doc_link
from dvc.exceptions import DvcException
@@ -44,7 +42,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(IMPORT_HELP, "import-url"),
help=IMPORT_HELP,
formatter_class=argparse.RawTextHelpFormatter,
formatter_class=formatter.RawTextHelpFormatter,
)
import_parser.add_argument(
"url",
+2 -3
View File
@@ -1,8 +1,7 @@
import argparse
import colorama
from dvc import analytics
from dvc.cli import formatter
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.log import logger
@@ -69,7 +68,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(INIT_DESCRIPTION, "init"),
help=INIT_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
init_parser.add_argument(
"--no-scm",
+2 -3
View File
@@ -1,5 +1,4 @@
import argparse
from dvc.cli import formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
@@ -25,7 +24,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(INSTALL_HELP, "install"),
help=INSTALL_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
install_parser.add_argument(
"--use-pre-commit-tool",
+3 -9
View File
@@ -1,6 +1,4 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import DictAction, append_doc_link
from dvc.commands.ls.ls_colors import LsColors
@@ -75,7 +73,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(LIST_HELP, "list"),
help=LIST_HELP,
formatter_class=argparse.RawTextHelpFormatter,
formatter_class=formatter.RawTextHelpFormatter,
)
list_parser.add_argument("url", help="Location of DVC repository to list")
list_parser.add_argument(
@@ -121,11 +119,7 @@ def add_parser(subparsers, parent_parser):
"specified by '--remote') in the target repository."
),
)
list_parser.add_argument(
"--size",
action="store_true",
help="Show sizes.",
)
list_parser.add_argument("--size", action="store_true", help="Show sizes.")
list_parser.add_argument(
"path",
nargs="?",
+1 -1
View File
@@ -20,7 +20,7 @@ class LsColors:
else:
self._codes[code] = color
def format(self, entry): # noqa: A003
def format(self, entry):
text = entry["path"]
if entry.get("isout", False) and "out" in self._codes:
+4 -12
View File
@@ -1,5 +1,4 @@
import argparse
from dvc.cli import formatter
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import DictAction, append_doc_link
from dvc.log import logger
@@ -33,22 +32,15 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(LS_HELP, "list-url"),
help=LS_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
lsurl_parser.add_argument(
"url", help="See `dvc import-url -h` for full list of supported URLs."
)
lsurl_parser.add_argument(
"-R",
"--recursive",
action="store_true",
help="Recursively list files.",
)
lsurl_parser.add_argument(
"--size",
action="store_true",
help="Show sizes.",
"-R", "--recursive", action="store_true", help="Recursively list files."
)
lsurl_parser.add_argument("--size", action="store_true", help="Show sizes.")
lsurl_parser.add_argument(
"--fs-config",
type=str,
+6 -9
View File
@@ -1,8 +1,6 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.cli.utils import append_doc_link
from dvc.log import logger
from dvc.ui import ui
from dvc.utils.serialize import encode_exception
@@ -107,23 +105,22 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(METRICS_HELP, "metrics"),
help=METRICS_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
metrics_subparsers = metrics_parser.add_subparsers(
dest="cmd",
help="Use `dvc metrics CMD --help` to display command-specific help.",
required=True,
)
fix_subparsers(metrics_subparsers)
METRICS_SHOW_HELP = "Print metrics, with optional formatting."
metrics_show_parser = metrics_subparsers.add_parser(
"show",
parents=[parent_parser],
description=append_doc_link(METRICS_SHOW_HELP, "metrics/show"),
help=METRICS_SHOW_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
metrics_show_parser.add_argument(
"targets",
@@ -197,7 +194,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(METRICS_DIFF_HELP, "metrics/diff"),
help=METRICS_DIFF_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
metrics_diff_parser.add_argument(
"a_rev",
+2 -4
View File
@@ -1,6 +1,4 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
@@ -34,7 +32,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(MOVE_DESCRIPTION, "move"),
help=MOVE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
move_parser.add_argument(
"src", help="Source path to a data file or directory."
+5 -8
View File
@@ -1,8 +1,6 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.cli.utils import append_doc_link
from dvc.log import logger
from dvc.ui import ui
@@ -61,16 +59,15 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(PARAMS_HELP, "params"),
help=PARAMS_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
params_subparsers = params_parser.add_subparsers(
dest="cmd",
help="Use `dvc params CMD --help` to display command-specific help.",
required=True,
)
fix_subparsers(params_subparsers)
PARAMS_DIFF_HELP = (
"Show changes in params between commits in the DVC repository, or "
"between a commit and the workspace."
@@ -80,7 +77,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(PARAMS_DIFF_HELP, "params/diff"),
help=PARAMS_DIFF_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
params_diff_parser.add_argument(
"a_rev",
+16 -73
View File
@@ -1,12 +1,12 @@
import argparse
import os
from typing import TYPE_CHECKING, Dict, List, Optional
from typing import TYPE_CHECKING, Optional
from funcy import compact, first, get_in
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
from dvc.log import logger
from dvc.ui import ui
@@ -20,35 +20,26 @@ logger = logger.getChild(__name__)
def _show_json(
renderers_with_errors: List["RendererWithErrors"],
renderers_with_errors: list["RendererWithErrors"],
split=False,
errors: Optional[Dict[str, Exception]] = None,
errors: Optional[dict[str, Exception]] = None,
):
from dvc.render.convert import to_json
from dvc.utils.serialize import encode_exception
all_errors: List[Dict] = []
all_errors: list[dict] = []
data = {}
for renderer, src_errors, def_errors in renderers_with_errors:
name = renderer.name
data[name] = to_json(renderer, split)
all_errors.extend(
{
"name": name,
"rev": rev,
"source": source,
**encode_exception(e),
}
{"name": name, "rev": rev, "source": source, **encode_exception(e)}
for rev, per_rev_src_errors in src_errors.items()
for source, e in per_rev_src_errors.items()
)
all_errors.extend(
{
"name": name,
"rev": rev,
**encode_exception(e),
}
{"name": name, "rev": rev, **encode_exception(e)}
for rev, e in def_errors.items()
)
@@ -59,49 +50,6 @@ def _show_json(
ui.write_json(compact({"errors": all_errors, "data": data}), highlight=False)
def _adjust_vega_renderers(renderers):
from dvc.render import REVISION_FIELD, VERSION_FIELD
from dvc_render import VegaRenderer
for r in renderers:
if isinstance(r, VegaRenderer):
if _data_versions_count(r) > 1:
summary = _summarize_version_infos(r)
for dp in r.datapoints:
vi = dp.pop(VERSION_FIELD, {})
keys = list(vi.keys())
for key in keys:
if not (len(summary.get(key, set())) > 1):
vi.pop(key)
if vi:
dp["rev"] = "::".join(vi.values())
else:
for dp in r.datapoints:
dp[REVISION_FIELD] = dp[VERSION_FIELD]["revision"]
dp.pop(VERSION_FIELD, {})
def _summarize_version_infos(renderer):
from collections import defaultdict
from dvc.render import VERSION_FIELD
result = defaultdict(set)
for dp in renderer.datapoints:
for key, value in dp.get(VERSION_FIELD, {}).items():
result[key].add(value)
return dict(result)
def _data_versions_count(renderer):
from itertools import product
summary = _summarize_version_infos(renderer)
x = product(summary.get("filename", {None}), summary.get("field", {None}))
return len(set(x))
class CmdPlots(CmdBase):
def _func(self, *args, **kwargs):
raise NotImplementedError
@@ -144,10 +92,7 @@ class CmdPlots(CmdBase):
return 1
try:
plots_data = self._func(
targets=self.args.targets,
props=self._props(),
)
plots_data = self._func(targets=self.args.targets, props=self._props())
if not plots_data and not self.args.json:
ui.error_write(
@@ -175,11 +120,10 @@ class CmdPlots(CmdBase):
return 0
renderers = [r.renderer for r in renderers_with_errors]
_adjust_vega_renderers(renderers)
if self.args.show_vega:
renderer = first(filter(lambda r: r.TYPE == "vega", renderers))
if renderer:
ui.write_json(renderer.get_filled_template(as_string=False))
ui.write_json(renderer.get_filled_template())
return 0
output_file: Path = (Path.cwd() / out).resolve() / "index.html"
@@ -267,15 +211,14 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(PLOTS_HELP, "plots"),
help=PLOTS_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
plots_subparsers = plots_parser.add_subparsers(
dest="cmd",
help="Use `dvc plots CMD --help` to display command-specific help.",
required=True,
)
fix_subparsers(plots_subparsers)
SHOW_HELP = (
"Generate plots from target files or from `plots` definitions in `dvc.yaml`."
)
@@ -284,7 +227,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(SHOW_HELP, "plots/show"),
help=SHOW_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
plots_show_parser.add_argument(
"targets",
@@ -307,7 +250,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(PLOTS_DIFF_HELP, "plots/diff"),
help=PLOTS_DIFF_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
plots_diff_parser.add_argument(
"--targets",
@@ -343,7 +286,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(PLOTS_MODIFY_HELP, "plots/modify"),
help=PLOTS_MODIFY_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
plots_modify_parser.add_argument(
"target",
@@ -364,7 +307,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(TEMPLATES_HELP, "plots/templates"),
help=TEMPLATES_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
plots_templates_parser.add_argument(
"template",
+5 -13
View File
@@ -1,16 +1,8 @@
import argparse
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.cli import formatter
from dvc.cli.utils import append_doc_link
from dvc.commands.queue import kill, logs, remove, start, status, stop
SUB_COMMANDS = [
start,
stop,
status,
logs,
remove,
kill,
]
SUB_COMMANDS = [start, stop, status, logs, remove, kill]
def add_parser(subparsers, parent_parser):
@@ -20,15 +12,15 @@ def add_parser(subparsers, parent_parser):
"queue",
parents=[parent_parser],
description=append_doc_link(QUEUE_HELP, "queue"),
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
help=QUEUE_HELP,
)
queue_subparsers = queue_parser.add_subparsers(
dest="cmd",
help="Use `dvc queue CMD --help` to display command-specific help.",
required=True,
)
fix_subparsers(queue_subparsers)
for cmd in SUB_COMMANDS:
cmd.add_parser(queue_subparsers, parent_parser)
+2 -3
View File
@@ -1,5 +1,4 @@
import argparse
from dvc.cli import formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
@@ -27,7 +26,7 @@ def add_parser(queue_subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(QUEUE_KILL_HELP, "queue/kill"),
help=QUEUE_KILL_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
queue_kill_parser.add_argument(
"-f",
+3 -8
View File
@@ -1,5 +1,4 @@
import argparse
from dvc.cli import formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
@@ -29,7 +28,7 @@ def add_parser(queue_subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(QUEUE_LOGS_HELP, "queue/logs"),
help=QUEUE_LOGS_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
queue_logs_parser.add_argument(
"-e",
@@ -46,9 +45,5 @@ def add_parser(queue_subparsers, parent_parser):
),
action="store_true",
)
queue_logs_parser.add_argument(
"task",
help="Task to show.",
metavar="<task>",
)
queue_logs_parser.add_argument("task", help="Task to show.", metavar="<task>")
queue_logs_parser.set_defaults(func=CmdQueueLogs)
+2 -3
View File
@@ -1,5 +1,4 @@
import argparse
from dvc.cli import formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import InvalidArgumentError
@@ -62,7 +61,7 @@ def add_parser(queue_subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(QUEUE_REMOVE_HELP, "queue/remove"),
help=QUEUE_REMOVE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
queue_remove_parser.add_argument(
"--all",
+2 -3
View File
@@ -1,5 +1,4 @@
import argparse
from dvc.cli import formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
@@ -27,7 +26,7 @@ def add_parser(queue_subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(QUEUE_START_HELP, "queue/start"),
help=QUEUE_START_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
queue_start_parser.add_argument(
"-j",
+2 -3
View File
@@ -1,5 +1,4 @@
import argparse
from dvc.cli import formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.compare import TabularData
@@ -52,6 +51,6 @@ def add_parser(queue_subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(QUEUE_STATUS_HELP, "queue/status"),
help=QUEUE_STATUS_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
queue_status_parser.set_defaults(func=CmdQueueStatus)
+2 -3
View File
@@ -1,5 +1,4 @@
import argparse
from dvc.cli import formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
@@ -32,7 +31,7 @@ def add_parser(queue_subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(QUEUE_STOP_HELP, "queue/stop"),
help=QUEUE_STOP_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
queue_stop_parser.add_argument(
"--kill",
+10 -12
View File
@@ -1,6 +1,5 @@
import argparse
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.cli import formatter
from dvc.cli.utils import append_doc_link
from dvc.commands.config import CmdConfig
from dvc.ui import ui
from dvc.utils import format_link
@@ -155,23 +154,22 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(REMOTE_HELP, "remote"),
help=REMOTE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
remote_subparsers = remote_parser.add_subparsers(
dest="cmd",
help="Use `dvc remote CMD --help` for command-specific help.",
required=True,
)
fix_subparsers(remote_subparsers)
REMOTE_ADD_HELP = "Add a new data remote."
remote_add_parser = remote_subparsers.add_parser(
"add",
parents=[parent_config_parser, parent_parser],
description=append_doc_link(REMOTE_ADD_HELP, "remote/add"),
help=REMOTE_ADD_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
remote_add_parser.add_argument("name", help="Name of the remote")
remote_add_parser.add_argument(
@@ -202,7 +200,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_config_parser, parent_parser],
description=append_doc_link(REMOTE_DEFAULT_HELP, "remote/default"),
help=REMOTE_DEFAULT_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
remote_default_parser.add_argument("name", nargs="?", help="Name of the remote")
remote_default_parser.add_argument(
@@ -220,7 +218,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_config_parser, parent_parser],
description=append_doc_link(REMOTE_MODIFY_HELP, "remote/modify"),
help=REMOTE_MODIFY_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
remote_modify_parser.add_argument("name", help="Name of the remote")
remote_modify_parser.add_argument("option", help="Name of the option to modify.")
@@ -242,7 +240,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_config_parser, parent_parser],
description=append_doc_link(REMOTE_LIST_HELP, "remote/list"),
help=REMOTE_LIST_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
remote_list_parser.set_defaults(func=CmdRemoteList)
@@ -252,7 +250,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_config_parser, parent_parser],
description=append_doc_link(REMOTE_REMOVE_HELP, "remote/remove"),
help=REMOTE_REMOVE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
remote_remove_parser.add_argument("name", help="Name of the remote to remove.")
remote_remove_parser.set_defaults(func=CmdRemoteRemove)
@@ -262,7 +260,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_config_parser, parent_parser],
description=append_doc_link(REMOTE_RENAME_HELP, "remote/rename"),
help=REMOTE_RENAME_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
remote_rename_parser.add_argument("name", help="Remote to be renamed")
remote_rename_parser.add_argument("new", help="New name of the remote")
+2 -4
View File
@@ -1,6 +1,4 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
@@ -30,7 +28,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(REMOVE_HELP, "remove"),
help=REMOVE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
remove_parser.add_argument(
"--outs",
+2 -4
View File
@@ -1,6 +1,4 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands.status import CmdDataStatus
@@ -167,7 +165,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(REPRO_HELP, "repro"),
help=REPRO_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
# repro/exp run shared args
add_arguments(repro_parser)
+2 -3
View File
@@ -1,5 +1,4 @@
import argparse
from dvc.cli import formatter
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.log import logger
@@ -24,6 +23,6 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(ROOT_HELP, "root"),
help=ROOT_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
root_parser.set_defaults(func=CmdRoot)
+12 -12
View File
@@ -1,12 +1,13 @@
import argparse
import logging
from collections.abc import Iterable
from contextlib import contextmanager
from itertools import chain, filterfalse
from typing import TYPE_CHECKING, Dict, Iterable, List
from typing import TYPE_CHECKING
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.cli.utils import append_doc_link
from dvc.log import logger
from dvc.utils.cli_parse import parse_params
from dvc.utils.humanize import truncate_text
@@ -33,7 +34,7 @@ def generate_description(stage: "Stage") -> str:
def is_plot_or_metric(out: "Output"):
return bool(out.plot) or bool(out.metric)
desc: List[str] = []
desc: list[str] = []
outs = list(filterfalse(is_plot_or_metric, stage.outs))
if outs:
@@ -55,7 +56,7 @@ def prepare_stages_data(
stages: Iterable["Stage"],
description: bool = True,
max_length: int = MAX_TEXT_LENGTH,
) -> Dict[str, str]:
) -> dict[str, str]:
return {
stage.addressing: (
prepare_description(stage, max_length=max_length) if description else ""
@@ -67,7 +68,7 @@ def prepare_stages_data(
class CmdStageList(CmdBase):
def _get_stages(self) -> Iterable["Stage"]:
if self.args.all:
stages: List["Stage"] = self.repo.index.stages
stages: list["Stage"] = self.repo.index.stages
logger.trace("%d no. of stages found", len(stages))
return stages
@@ -96,7 +97,7 @@ class CmdStageList(CmdBase):
return 0
def parse_cmd(commands: List[str]) -> str:
def parse_cmd(commands: list[str]) -> str:
"""
We need to take into account two cases:
@@ -284,23 +285,22 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(STAGES_HELP, "stage"),
help=STAGES_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
stage_subparsers = stage_parser.add_subparsers(
dest="cmd",
help="Use `dvc stage CMD --help` to display command-specific help.",
required=True,
)
fix_subparsers(stage_subparsers)
STAGE_ADD_HELP = "Create stage"
stage_add_parser = stage_subparsers.add_parser(
"add",
parents=[parent_parser],
description=append_doc_link(STAGE_ADD_HELP, "stage/add"),
help=STAGE_ADD_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
stage_add_parser.add_argument(
"-n", "--name", help="Name of the stage to add", required=True
@@ -314,7 +314,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(STAGE_LIST_HELP, "stage/list"),
help=STAGE_LIST_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
stage_list_parser.add_argument(
"targets",
+3 -3
View File
@@ -65,13 +65,13 @@ class CmdDataStatus(CmdDataBase):
logger.exception("")
return 1
if self.args.quiet:
return bool(st)
if self.args.json:
ui.write_json(st)
return 0
if self.args.quiet:
return bool(st)
if st:
self._show(st, indent)
return 0
+175
View File
@@ -0,0 +1,175 @@
import os
from funcy import get_in
from dvc.cli import formatter
from dvc.cli.utils import append_doc_link
from dvc.commands.config import CmdConfig
from dvc.log import logger
logger = logger.getChild(__name__)
class CmdStudioLogin(CmdConfig):
def run(self):
from dvc_studio_client.auth import StudioAuthError, get_access_token
from dvc.env import DVC_STUDIO_URL
from dvc.ui import ui
from dvc.utils.studio import STUDIO_URL
name = self.args.name
hostname = self.args.hostname or os.environ.get(DVC_STUDIO_URL) or STUDIO_URL
scopes = self.args.scopes
try:
token_name, access_token = get_access_token(
token_name=name,
hostname=hostname,
scopes=scopes,
use_device_code=self.args.no_open,
client_name="DVC",
)
except StudioAuthError as e:
ui.error_write(str(e))
return 1
self.save_config(hostname, access_token)
ui.write(
"Authentication has been successfully completed."
"The generated token will now be accessible as"
f" {token_name} in the user's Studio profile."
)
return 0
def save_config(self, hostname, token):
with self.config.edit("global") as conf:
conf["studio"]["token"] = token
conf["studio"]["url"] = hostname
class CmdStudioLogout(CmdConfig):
def run(self):
from dvc.ui import ui
with self.config.edit("global") as conf:
if not get_in(conf, ["studio", "token"]):
ui.error_write("Not logged in to Studio.")
return 1
del conf["studio"]["token"]
ui.write("Logged out from Studio")
return 0
class CmdStudioToken(CmdConfig):
def run(self):
from dvc.ui import ui
conf = self.config.read("global")
token = get_in(conf, ["studio", "token"])
if not token:
ui.error_write("Not logged in to Studio.")
return 1
ui.write(token)
return 0
def add_parser(subparsers, parent_parser):
STUDIO_HELP = "Commands to authenticate DVC with Iterative Studio"
STUDIO_DESCRIPTION = (
"Authenticate DVC with Studio and set the token."
" Once this token has been properly configured,\n"
" DVC will utilize it for seamlessly sharing live experiments\n"
" and sending notifications to Studio regarding any experiments"
" that have been pushed."
)
studio_parser = subparsers.add_parser(
"studio",
parents=[parent_parser],
description=append_doc_link(STUDIO_DESCRIPTION, "studio"),
help=STUDIO_HELP,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
studio_subparser = studio_parser.add_subparsers(
dest="cmd",
help="Use `DVC studio CMD --help` to display command-specific help.",
required=True,
)
STUDIO_LOGIN_HELP = "Authenticate DVC with Studio host"
STUDIO_LOGIN_DESCRIPTION = (
"By default, this command authenticates the DVC with Studio\n"
" using default scopes and assigns a random name as the token name."
)
login_parser = studio_subparser.add_parser(
"login",
parents=[parent_parser],
description=append_doc_link(STUDIO_LOGIN_DESCRIPTION, "studio/login"),
help=STUDIO_LOGIN_HELP,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
login_parser.add_argument(
"-H",
"--hostname",
action="store",
default=None,
help="The hostname of the Studio instance to authenticate with.",
)
login_parser.add_argument(
"-s",
"--scopes",
action="store",
default=None,
help="The scopes for the authentication token. ",
)
login_parser.add_argument(
"-n",
"--name",
action="store",
default=None,
help="The name of the authentication token. It will be used to\n"
"identify token shown in Studio profile.",
)
login_parser.add_argument(
"--no-open",
action="store_true",
default=False,
help="Use authentication flow based on user code.\n"
"You will be presented with user code to enter in browser.\n"
"DVC will also use this if it cannot launch browser on your behalf.",
)
login_parser.set_defaults(func=CmdStudioLogin)
STUDIO_LOGOUT_HELP = "Logout user from Studio"
STUDIO_LOGOUT_DESCRIPTION = (
"This removes the studio token from your global config.\n"
)
logout_parser = studio_subparser.add_parser(
"logout",
parents=[parent_parser],
description=append_doc_link(STUDIO_LOGOUT_DESCRIPTION, "studio/logout"),
help=STUDIO_LOGOUT_HELP,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
logout_parser.set_defaults(func=CmdStudioLogout)
STUDIO_TOKEN_HELP = "View the token dvc uses to contact Studio" # noqa: S105 # nosec B105
logout_parser = studio_subparser.add_parser(
"token",
parents=[parent_parser],
description=append_doc_link(STUDIO_TOKEN_HELP, "studio/token"),
help=STUDIO_TOKEN_HELP,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
logout_parser.set_defaults(func=CmdStudioToken)
+2 -4
View File
@@ -1,6 +1,4 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
@@ -31,7 +29,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(UNPROTECT_HELP, "unprotect"),
help=UNPROTECT_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
unprotect_parser.add_argument(
"targets", nargs="+", help="Data files/directories to unprotect."
+2 -4
View File
@@ -1,6 +1,4 @@
import argparse
from dvc.cli import completion
from dvc.cli import completion, formatter
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
@@ -38,7 +36,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(UPDATE_HELP, "update"),
help=UPDATE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
)
update_parser.add_argument(
"targets", nargs="+", help=".dvc files to update."
+2 -3
View File
@@ -1,5 +1,4 @@
import argparse
from dvc.cli import formatter
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.log import logger
@@ -27,7 +26,7 @@ def add_parser(subparsers, parent_parser):
parents=[parent_parser],
description=append_doc_link(VERSION_HELP, "version"),
help=VERSION_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
formatter_class=formatter.RawDescriptionHelpFormatter,
aliases=["doctor"],
)
version_parser.set_defaults(func=CmdVersion)
+20 -28
View File
@@ -1,23 +1,15 @@
from collections import abc
from itertools import chain, repeat, zip_longest
from operator import itemgetter
from typing import (
TYPE_CHECKING,
Any,
Dict,
from collections.abc import (
ItemsView,
Iterable,
Iterator,
List,
Mapping,
MutableSequence,
Optional,
Sequence,
Set,
Tuple,
Union,
overload,
)
from itertools import chain, repeat, zip_longest
from operator import itemgetter
from typing import TYPE_CHECKING, Any, Optional, Union, overload
from funcy import reraise
@@ -25,7 +17,7 @@ if TYPE_CHECKING:
from dvc.ui.table import CellT
class Column(List["CellT"]):
class Column(list["CellT"]):
pass
@@ -35,13 +27,13 @@ def with_value(value, default):
class TabularData(MutableSequence[Sequence["CellT"]]):
def __init__(self, columns: Sequence[str], fill_value: Optional[str] = ""):
self._columns: Dict[str, Column] = {name: Column() for name in columns}
self._keys: List[str] = list(columns)
self._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()
self._protected: set[str] = set()
@property
def columns(self) -> List[Column]:
def columns(self) -> list[Column]:
return list(map(self.column, self.keys()))
def is_protected(self, col_name) -> bool:
@@ -60,10 +52,10 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
projection = {k: self.column(k) for k in self.keys()}
return projection.items()
def keys(self) -> List[str]:
def keys(self) -> list[str]:
return self._keys
def _iter_col_row(self, row: Sequence["CellT"]) -> Iterator[Tuple["CellT", Column]]:
def _iter_col_row(self, row: Sequence["CellT"]) -> Iterator[tuple["CellT", Column]]:
for val, col in zip_longest(row, self.columns):
if col is None:
break
@@ -81,7 +73,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
for val, col in self._iter_col_row(value):
col.insert(index, val)
def __iter__(self) -> Iterator[List["CellT"]]:
def __iter__(self) -> Iterator[list["CellT"]]:
return map(list, zip(*self.columns))
def __getattr__(self, item: str) -> Column:
@@ -127,7 +119,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
return len(self.columns[0])
@property
def shape(self) -> Tuple[int, int]:
def shape(self) -> tuple[int, int]:
return len(self.columns), len(self)
def drop(self, *col_names: str) -> None:
@@ -170,7 +162,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
if key not in keys:
self.add_column(key)
row: List["CellT"] = [
row: list["CellT"] = [
with_value(d.get(key), self._fill_value) for key in self.keys()
]
self.append(row)
@@ -185,7 +177,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
def as_dict(
self, cols: Optional[Iterable[str]] = None
) -> Iterable[Dict[str, "CellT"]]:
) -> Iterable[dict[str, "CellT"]]:
keys = self.keys() if cols is None else set(cols)
return [{k: self._columns[k][i] for k in keys} for i in range(len(self))]
@@ -202,7 +194,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
if how not in ["any", "all"]:
raise ValueError(f"Invalid 'how' value {how}. Choose one of ['any', 'all']")
match_line: Set = set()
match_line: set = set()
match_any = True
if how == "all":
match_any = False
@@ -245,7 +237,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
)
if axis == "cols":
cols_to_drop: List[str] = []
cols_to_drop: list[str] = []
for n_col, col in enumerate(self.columns):
if subset and self.keys()[n_col] not in subset:
continue
@@ -259,7 +251,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
elif axis == "rows":
unique_rows = []
rows_to_drop: List[int] = []
rows_to_drop: list[int] = []
for n_row, row in enumerate(self):
if subset:
row = [
@@ -319,7 +311,7 @@ def diff_table(
) -> TabularData:
a_rev = a_rev or "HEAD"
b_rev = b_rev or "workspace"
headers: List[str] = ["Path", title, a_rev, b_rev, "Change"]
headers: list[str] = ["Path", title, a_rev, b_rev, "Change"]
fill_value = "-"
td = TabularData(headers, fill_value=fill_value)
@@ -394,7 +386,7 @@ def metrics_table(
for branch, val in metrics.items():
for fname, metric in val.get("data", {}).items():
row_data: Dict[str, str] = {"Revision": branch, "Path": fname}
row_data: dict[str, str] = {"Revision": branch, "Path": fname}
metric = metric.get("data", {})
flattened = (
flatten(format_dict(metric))
+12 -12
View File
@@ -3,7 +3,7 @@ import os
import re
from contextlib import contextmanager
from functools import partial
from typing import TYPE_CHECKING, Dict, Optional
from typing import TYPE_CHECKING, Optional
from funcy import compact, memoize, re_find
@@ -14,7 +14,7 @@ from .utils.objects import cached_property
if TYPE_CHECKING:
from dvc.fs import FileSystem
from dvc.types import DictStrAny, StrPath
from dvc.types import DictStrAny
logger = logger.getChild(__name__)
@@ -89,8 +89,8 @@ class Config(dict):
def __init__(
self,
dvc_dir: Optional["StrPath"] = None,
local_dvc_dir: Optional["StrPath"] = None,
dvc_dir: Optional[str] = None,
local_dvc_dir: Optional[str] = None,
validate: bool = True,
fs: Optional["FileSystem"] = None,
config: Optional["DictStrAny"] = None,
@@ -105,7 +105,7 @@ class Config(dict):
self.fs = fs or self.wfs
if dvc_dir:
self.dvc_dir = self.fs.path.abspath(dvc_dir)
self.dvc_dir = self.fs.abspath(dvc_dir)
self.local_dvc_dir = local_dvc_dir
if not fs and not local_dvc_dir:
@@ -138,17 +138,17 @@ class Config(dict):
return system_config_dir()
@cached_property
def files(self) -> Dict[str, str]:
def files(self) -> dict[str, str]:
files = {
level: os.path.join(self.get_dir(level), self.CONFIG)
for level in ("system", "global")
}
if self.dvc_dir is not None:
files["repo"] = self.fs.path.join(self.dvc_dir, self.CONFIG)
files["repo"] = self.fs.join(self.dvc_dir, self.CONFIG)
if self.local_dvc_dir is not None:
files["local"] = self.wfs.path.join(self.local_dvc_dir, self.CONFIG_LOCAL)
files["local"] = self.wfs.join(self.local_dvc_dir, self.CONFIG_LOCAL)
return files
@@ -302,11 +302,11 @@ class Config(dict):
return path.def_path
if os.path.expanduser(path) != path:
return localfs.path.as_posix(path)
return localfs.as_posix(path)
if isinstance(path, RelPath) or not os.path.isabs(path):
path = relpath(path, conf_dir)
return localfs.path.as_posix(path)
return localfs.as_posix(path)
return path
@@ -344,7 +344,7 @@ class Config(dict):
return Schema(dirs_schema, extra=ALLOW_EXTRA)(conf)
def load_config_to_level(self, level=None):
merged_conf: Dict = {}
merged_conf: dict = {}
for merge_level in self.LEVELS:
if merge_level == level:
break
@@ -390,7 +390,7 @@ class Config(dict):
def _parse_named(conf):
result: Dict[str, Dict] = {"remote": {}, "machine": {}, "db": {}}
result: dict[str, dict] = {"remote": {}, "machine": {}, "db": {}}
for section, val in conf.items():
match = re_find(r'^\s*(remote|machine|db)\s*"(.*)"\s*$', section)
+7 -5
View File
@@ -240,6 +240,9 @@ SCHEMA = {
"ssl_verify": Any(Bool, str),
"token": str,
"use_https": Bool,
"user": str,
"password": str,
"data_proxy_target": str,
Optional("verify", default=False): Bool,
**REMOTE_COMMON,
},
@@ -257,9 +260,9 @@ SCHEMA = {
"exclude_shared_token_cache_credential": Bool,
"exclude_managed_identity_credential": Bool,
Optional("verify", default=False): Bool,
"timeout": int,
"read_timeout": int,
"connection_timeout": int,
"timeout": Coerce(int),
"read_timeout": Coerce(int),
"connection_timeout": Coerce(int),
**REMOTE_COMMON,
},
"oss": {
@@ -318,8 +321,6 @@ SCHEMA = {
"feature": FeatureSchema(
{
Optional("machine", default=False): Bool,
"dbt_profile": str,
"dbt_target": str,
},
),
"plots": {
@@ -345,6 +346,7 @@ SCHEMA = {
Exclusive("config_dir", "config_source"): str,
Exclusive("config_module", "config_source"): str,
"config_name": str,
"plugins_path": str,
},
"studio": {
"token": str,
+11 -15
View File
@@ -5,13 +5,14 @@ import logging
import os
import subprocess
import sys
from collections.abc import Mapping, Sequence
from contextlib import nullcontext
from typing import TYPE_CHECKING, Any, List, Mapping, Optional, Sequence, Union
from typing import TYPE_CHECKING, Any, Optional, Union
from dvc.log import logger
if TYPE_CHECKING:
from typing import ContextManager
from contextlib import AbstractContextManager
from dvc.env import DVC_DAEMON, DVC_DAEMON_LOGFILE
from dvc.utils import fix_env, is_binary
@@ -55,7 +56,7 @@ def _win_detached_subprocess(args: Sequence[str], **kwargs) -> int:
return popen.pid
def _get_dvc_args() -> List[str]:
def _get_dvc_args() -> list[str]:
args = [sys.executable]
if not is_binary():
root_dir = os.path.abspath(os.path.dirname(__file__))
@@ -106,12 +107,7 @@ def _posix_detached_subprocess(args: Sequence[str], **kwargs) -> int:
os.close(read_end)
return int(pid_str)
proc = subprocess.Popen(
args,
shell=False, # noqa: S603
close_fds=True,
**kwargs,
)
proc = subprocess.Popen(args, shell=False, close_fds=True, **kwargs) # noqa: S603
os.close(read_end)
os.write(write_end, str(proc.pid).encode("utf8"))
os.close(write_end)
@@ -136,7 +132,7 @@ def _map_log_level_to_flag() -> Optional[str]:
return flags.get(logger.getEffectiveLevel())
def daemon(args: List[str]) -> None:
def daemon(args: list[str]) -> None:
"""Launch a `dvc daemon` command in a detached process.
Args:
@@ -148,12 +144,12 @@ def daemon(args: List[str]) -> None:
def _spawn(
args: List[str],
executable: Optional[Union[str, List[str]]] = None,
args: list[str],
executable: Optional[Union[str, list[str]]] = None,
env: Optional[Mapping[str, str]] = None,
output_file: Optional[str] = None,
) -> int:
file: "ContextManager[Any]" = nullcontext()
file: "AbstractContextManager[Any]" = nullcontext()
kwargs = {}
if output_file:
file = open(output_file, "ab") # noqa: SIM115
@@ -168,9 +164,9 @@ def _spawn(
return _detached_subprocess(executable + args, env=env, **kwargs)
def daemonize(args: List[str], executable: Union[None, str, List[str]] = None) -> None:
def daemonize(args: list[str], executable: Union[None, str, list[str]] = None) -> None:
if os.name not in ("posix", "nt"):
return None
return
if os.environ.get(DVC_DAEMON):
logger.debug("skipping launching a new daemon.")
+11 -9
View File
@@ -1,9 +1,9 @@
"""Manages dvc remotes that user can use with push/pull/status commands."""
from typing import TYPE_CHECKING, Iterable, Optional, Set, Tuple
from collections.abc import Iterable
from typing import TYPE_CHECKING, Optional
from dvc.config import NoRemoteError, RemoteConfigError
from dvc.fs.callbacks import Callback
from dvc.log import logger
from dvc.utils.objects import cached_property
from dvc_data.hashfile.db import get_index
@@ -36,11 +36,9 @@ class Remote:
path = self.path
if self.worktree:
path = self.fs.path.join(
path, ".dvc", CacheManager.FILES_DIR, DEFAULT_ALGORITHM
)
path = self.fs.join(path, ".dvc", CacheManager.FILES_DIR, DEFAULT_ALGORITHM)
else:
path = self.fs.path.join(path, CacheManager.FILES_DIR, DEFAULT_ALGORITHM)
path = self.fs.join(path, CacheManager.FILES_DIR, DEFAULT_ALGORITHM)
return get_odb(self.fs, path, hash_name=DEFAULT_ALGORITHM, **self.config)
@cached_property
@@ -53,7 +51,7 @@ class Remote:
def _split_legacy_hash_infos(
hash_infos: Iterable["HashInfo"],
) -> Tuple[Set["HashInfo"], Set["HashInfo"]]:
) -> tuple[set["HashInfo"], set["HashInfo"]]:
from dvc.cachemgr import LEGACY_HASH_NAMES
legacy = set()
@@ -206,11 +204,13 @@ class DataCloud:
jobs: Optional[int] = None,
odb: "HashFileDB",
) -> "TransferResult":
from dvc.fs.callbacks import TqdmCallback
if odb.hash_name == "md5-dos2unix":
cache = self.repo.cache.legacy
else:
cache = self.repo.cache.local
with Callback.as_tqdm_callback(
with TqdmCallback(
desc=f"Pushing to {odb.fs.unstrip_protocol(odb.path)}",
unit="file",
) as cb:
@@ -265,11 +265,13 @@ class DataCloud:
jobs: Optional[int] = None,
odb: "HashFileDB",
) -> "TransferResult":
from dvc.fs.callbacks import TqdmCallback
if odb.hash_name == "md5-dos2unix":
cache = self.repo.cache.legacy
else:
cache = self.repo.cache.local
with Callback.as_tqdm_callback(
with TqdmCallback(
desc=f"Fetching from {odb.fs.unstrip_protocol(odb.path)}",
unit="file",
) as cb:
+134
View File
@@ -0,0 +1,134 @@
import os
from collections.abc import Iterator
from contextlib import contextmanager
from dataclasses import dataclass
from tempfile import NamedTemporaryFile
from typing import TYPE_CHECKING, Any, Callable, Optional, Union
from sqlalchemy import create_engine
from sqlalchemy.engine import make_url as _make_url
from sqlalchemy.exc import NoSuchModuleError
from dvc import env
from dvc.exceptions import DvcException
from dvc.log import logger
from dvc.types import StrOrBytesPath
from dvc.utils import env2bool
if TYPE_CHECKING:
from sqlalchemy.engine import URL, Connectable, Engine
from sqlalchemy.sql.expression import Selectable
logger = logger.getChild(__name__)
def noop(_):
pass
def make_url(url: Union["URL", str], **kwargs: Any) -> "URL":
return _make_url(url).set(**kwargs)
def url_from_config(config: Union[str, "URL", dict[str, str]]) -> "URL":
if isinstance(config, dict):
return make_url(**config)
return make_url(config)
@contextmanager
def atomic_file(file: StrOrBytesPath, mode: str = "w+b"):
head, tail = os.path.split(os.fsdecode(file))
with NamedTemporaryFile(mode, prefix=tail + "-", dir=head, delete=False) as f:
yield f
os.replace(f.name, file)
@dataclass
class Serializer:
sql: "Union[str, Selectable]"
con: "Union[str, Connectable]"
chunksize: int = 10_000
def to_csv(self, file: StrOrBytesPath, progress=noop):
import pandas as pd
idfs = pd.read_sql(self.sql, self.con, chunksize=self.chunksize)
with atomic_file(file) as f:
for i, df in enumerate(idfs):
df.to_csv(f, header=i == 0, index=False)
progress(len(df))
def to_json(self, file: StrOrBytesPath, progress=noop): # noqa: ARG002
import pandas as pd
df = pd.read_sql(self.sql, self.con)
with atomic_file(file) as f:
df.to_json(f, orient="records")
def export(self, file: StrOrBytesPath, format: str = "csv", progress=noop): # noqa: A002
if format == "json":
return self.to_json(file, progress=progress)
return self.to_csv(file, progress=progress)
@dataclass
class Client:
engine: "Engine"
def test_connection(self, onerror: Optional[Callable[[], Any]] = None) -> None:
try:
with self.engine.connect() as conn:
conn.exec_driver_sql("select 1")
except Exception as exc:
if callable(onerror):
onerror()
logger.exception(
"Could not connect to the database. "
"Check your database credentials and try again.",
exc_info=False,
)
raise DvcException("The database returned the following error") from exc
def export(
self,
sql: "Union[str, Selectable]",
file: StrOrBytesPath,
format: str = "csv", # noqa: A002
progress=noop,
) -> None:
con = self.engine.connect()
if format == "csv":
con = con.execution_options(stream_results=True) # use server-side cursors
with con:
serializer = Serializer(sql, con)
return serializer.export(file, format=format, progress=progress)
@contextmanager
def handle_error(url: "URL"):
try:
yield
except (ModuleNotFoundError, NoSuchModuleError) as e:
# TODO: write installation instructions
driver = url.drivername
raise DvcException(f"Could not load database driver for {driver!r}") from e
@contextmanager
def client(
url_or_config: Union[str, "URL", dict[str, str]], **engine_kwargs: Any
) -> Iterator[Client]:
url = url_from_config(url_or_config)
echo = env2bool(env.DVC_SQLALCHEMY_ECHO, False)
engine_kwargs.setdefault("echo", echo)
with handle_error(url):
engine = create_engine(url, **engine_kwargs)
try:
yield Client(engine)
finally:
engine.dispose()
-22
View File
@@ -1,22 +0,0 @@
from typing import Any, ContextManager, Optional, Union
from . import dbt_query, sqla
from .dbt_models import get_model
from .serializer import export
Adapter = Union[sqla.SQLAlchemyAdapter, dbt_query.DbtAdapter]
def get_adapter(
config,
project_dir: Optional[str] = None,
profile: Optional[str] = None,
target: Optional[str] = None,
**kwargs: Any,
) -> "ContextManager[Adapter]":
if config:
return sqla.adapter(config, **kwargs)
return dbt_query.adapter(project_dir=project_dir, profile=profile, target=target)
__all__ = ["export", "get_adapter", "get_model", "Adapter"]
-40
View File
@@ -1,40 +0,0 @@
from typing import List, Optional
from dvc.log import logger
from .dbt_runner import dbt_show
from .dbt_utils import check_dbt
from .serializer import AgateSerializer
logger = logger.getChild(__name__)
def ref(
name: str,
package: Optional[str] = None,
version: Optional[int] = None,
) -> str:
parts: List[str] = []
if package:
parts.append(repr(package))
parts.append(repr(name))
if version:
parts.append(f"{version=}")
inner = ",".join(parts)
return "{{ ref(" + inner + ") }}"
@check_dbt("model")
def get_model(
name: str,
package: Optional[str] = None,
version: Optional[int] = None,
profile: Optional[str] = None,
target: Optional[str] = None,
) -> AgateSerializer:
model = ref(name, package, version=version)
q = f"select * from {model}" # noqa: S608
table = dbt_show(inline=q, profile=profile, target=target)
return AgateSerializer(table)
-88
View File
@@ -1,88 +0,0 @@
import os
from contextlib import contextmanager
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, Optional, Union
from attrs import define, field
from dvc.exceptions import DvcException
from dvc.log import logger
from .dbt_utils import check_dbt
from .serializer import AgateSerializer, PandasSQLSerializer
if TYPE_CHECKING:
from dbt.adapters.base.impl import BaseAdapter
logger = logger.getChild(__name__)
@define
class DbtAdapter:
adapter: "BaseAdapter" = field(repr=lambda o: type(o).__qualname__)
creds: Dict[str, Any] = field(repr=False)
@contextmanager
def query(self, sql: str) -> Iterator[Union[AgateSerializer, PandasSQLSerializer]]:
from dbt.adapters.sql import SQLAdapter
with self.adapter.connection_named("execute"):
if isinstance(self.adapter, SQLAdapter):
# try to use connection to stream results
# otherwise fallback to fetch all at once
try:
connection = self.adapter.connections.get_thread_connection().handle
except Exception: # noqa: BLE001
logger.debug("", exc_info=True)
else:
# pandas raises warning that it does not support
# dbapi2-based raw connection, but it does seem to work.
yield PandasSQLSerializer(sql, connection)
return
_, table = self.adapter.execute(sql, fetch=True)
yield AgateSerializer(table)
def test_connection(self, onerror: Optional[Callable[[], Any]] = None) -> None:
with self.adapter.connection_named("debug"):
try:
self.adapter.debug_query()
except Exception as exc:
if callable(onerror):
onerror()
logger.exception(
"dvc was unable to connect to the specified database. "
"Please check your database credentials and try again.",
exc_info=False,
)
raise DvcException("The database returned the following error") from exc
@contextmanager
@check_dbt("query")
def adapter(
project_dir: Optional[str] = None,
profiles_dir: Optional[str] = None,
profile: Optional[str] = None,
target: Optional[str] = None,
) -> Iterator["DbtAdapter"]:
from dbt.adapters import factory as adapters_factory
from dbt.adapters.base.impl import BaseAdapter
from .dbt_utils import get_or_build_profile, get_profiles_dir, init_dbt
profiles_dir = profiles_dir or get_profiles_dir(project_dir)
flags = init_dbt(profiles_dir, os.getcwd(), target=target)
with flags, adapters_factory.adapter_management():
# likely invalid connection profile or no adapter
profile_obj = get_or_build_profile(project_dir, profile, target)
adapters_factory.register_adapter(profile_obj) # type: ignore[arg-type]
adapter = adapters_factory.get_adapter(profile_obj) # type: ignore[arg-type]
assert isinstance(adapter, BaseAdapter)
try:
creds = dict(profile_obj.credentials.connection_info())
except: # noqa: E722
creds = {}
yield DbtAdapter(adapter, creds)
-61
View File
@@ -1,61 +0,0 @@
from typing import TYPE_CHECKING, List, Optional, Sequence, Union
from dvc.exceptions import DvcException
from dvc.log import logger
if TYPE_CHECKING:
from agate import Table
from dbt.contracts.results import RunResult
logger = logger.getChild(__name__)
class DbtInternalError(DvcException):
pass
def _kw_to_cmd_args(**kwargs: Union[None, bool, str, int]) -> List[str]:
args: List[str] = []
for key, value in kwargs.items():
key = key.replace("_", "-")
if value is None:
continue # skip creating a flag in this case
if value is True:
args.append(f"--{key}")
elif value is False:
args.append(f"--no-{key}")
else:
args.extend([f"--{key}", str(value)])
return args
def _dbt_invoke(*posargs: str, quiet: bool = True, **kw: Union[None, bool, str, int]):
from dbt.cli.main import dbtRunner
args = _kw_to_cmd_args(quiet=quiet or None) # global options
args.extend([*posargs, *_kw_to_cmd_args(**kw)])
runner = dbtRunner()
result = runner.invoke(args)
if result.success:
return result.result
raise DbtInternalError(f"failed to run dbt {posargs[0]}") from result.exception
def dbt_show(
inline: Optional[str] = None,
limit: int = -1,
profile: Optional[str] = None,
target: Optional[str] = None,
) -> "Table":
from dbt.contracts.results import RunExecutionResult
result = _dbt_invoke(
"show", inline=inline, limit=limit, profile=profile, target=target
)
assert isinstance(result, RunExecutionResult)
run_results: Sequence["RunResult"] = result.results
run_result, *_ = run_results
assert run_result.agate_table is not None
return run_result.agate_table
-100
View File
@@ -1,100 +0,0 @@
import os
from contextlib import contextmanager
from importlib.util import find_spec
from typing import TYPE_CHECKING, Optional
from funcy import cut_prefix
from dvc.exceptions import DvcException
from dvc.log import logger
from dvc.utils import packaging
if TYPE_CHECKING:
from dbt.config.profile import Profile
logger = logger.getChild(__name__)
DBT_PROJECT_FILE = "dbt_project.yml"
@contextmanager
def check_dbt(action: Optional[str] = None):
if not (find_spec("dbt") and find_spec("dbt.cli")):
action = f" {action}" if action else ""
raise DvcException(f"Could not run{action}. dbt-core is not installed")
yield packaging.check_required_version(pkg="dbt-core")
@contextmanager
def init_dbt(
profiles_dir: str,
project_dir: str,
target: Optional[str] = None,
):
from argparse import Namespace
from dbt import flags
prev = flags.get_flags()
try:
args = Namespace(
use_colors=True,
project_dir=project_dir,
profiles_dir=profiles_dir,
target=target,
)
flags.set_from_args(args, None)
yield flags.get_flags()
finally:
flags.set_flags(prev)
def get_profiles_dir(project_dir: Optional[str] = None) -> str:
from dbt.cli.resolvers import default_profiles_dir
if profiles_dir := os.getenv("DBT_PROFILES_DIR"):
return profiles_dir
if project_dir and os.path.isfile(os.path.join(project_dir, "profiles.yml")):
return project_dir
return os.fspath(default_profiles_dir())
@contextmanager
def _handle_profile_parsing_error():
from dbt.exceptions import DbtRuntimeError
try:
yield
except DbtRuntimeError as e:
cause = e.__cause__ is not None and e.__cause__.__context__
if isinstance(cause, ModuleNotFoundError) and (
adapter := cut_prefix(cause.name, "dbt.adapters.")
):
# DbtRuntimeError is very noisy, so send it to debug
logger.debug("", exc_info=True)
raise DvcException(f"dbt-{adapter} dependency is missing") from cause
raise DvcException("failed to read connection profiles") from e
def is_dbt_project(project_dir: str):
return os.path.isfile(os.path.join(project_dir, DBT_PROJECT_FILE))
@_handle_profile_parsing_error()
def get_or_build_profile(
project_dir: Optional[str], profile: Optional[str], target: Optional[str]
) -> "Profile":
from dbt.config.profile import Profile
from dbt.config.renderer import ProfileRenderer
from dbt.config.runtime import load_profile
if project_dir and is_dbt_project(project_dir):
return load_profile(
project_dir, {}, profile_name_override=profile, target_override=target
)
if not profile:
raise DvcException("No profile specified to query from.")
renderer = ProfileRenderer({})
return Profile.render(renderer, profile, target_override=target)
-54
View File
@@ -1,54 +0,0 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING, Union
if TYPE_CHECKING:
import sqlalchemy as sa
from agate import Table
def noop(_):
pass
@dataclass
class PandasSQLSerializer:
sql: "Union[sa.TextClause, str]"
con: "sa.Connection"
chunksize: int = 10_000
def to_csv(self, file: str, progress=noop) -> None:
import pandas as pd
with open(file, mode="wb") as f:
idfs = pd.read_sql_query(self.sql, self.con, chunksize=self.chunksize)
for i, df in enumerate(idfs):
df.to_csv(f, header=i == 0, index=False)
progress(len(df))
def to_json(self, file: str, progress=noop) -> None: # noqa: ARG002
import pandas as pd
df = pd.read_sql_query(self.sql, self.con)
df.to_json(file, orient="records")
@dataclass
class AgateSerializer:
table: "Table"
def to_csv(self, file: str, progress=noop) -> None: # noqa: ARG002
return self.table.to_csv(file)
def to_json(self, file: str, progress=noop) -> None: # noqa: ARG002
return self.table.to_json(file)
def export(
serializer: Union[PandasSQLSerializer, AgateSerializer],
file: str,
format: str = "csv", # noqa: A002
progress=noop,
) -> None:
if format == "csv":
return serializer.to_csv(file, progress=progress)
return serializer.to_json(file, progress=progress)
-73
View File
@@ -1,73 +0,0 @@
from contextlib import contextmanager
from dataclasses import dataclass
from typing import Any, Callable, Dict, Iterator, Optional, Union
from sqlalchemy import create_engine, text
from sqlalchemy.engine import URL, Engine
from sqlalchemy.engine import make_url as _make_url
from sqlalchemy.exc import NoSuchModuleError
from dvc.exceptions import DvcException
from dvc.log import logger
from .serializer import PandasSQLSerializer
logger = logger.getChild(__name__)
def make_url(url: Union[URL, str], **kwargs: Any) -> URL:
return _make_url(url).set(**kwargs)
def url_from_config(config: Union[str, URL, Dict[str, str]]) -> URL:
if isinstance(config, (str, URL)):
return make_url(config)
return make_url(**config)
@dataclass
class SQLAlchemyAdapter:
engine: Engine
@contextmanager
def query(self, sql: str) -> Iterator[PandasSQLSerializer]:
with self.engine.connect().execution_options(stream_results=True) as conn:
yield PandasSQLSerializer(text(sql), conn)
def test_connection(self, onerror: Optional[Callable[[], Any]] = None) -> None:
try:
with self.engine.connect() as conn:
conn.execute(text("select 1"))
except Exception as exc:
if callable(onerror):
onerror()
logger.exception(
"dvc was unable to connect to the specified database. "
"Please check your database credentials and try again.",
exc_info=False,
)
raise DvcException("The database returned the following error") from exc
@contextmanager
def handle_error(url: URL):
try:
yield
except (ModuleNotFoundError, NoSuchModuleError) as e:
# TODO: write installation instructions
raise DvcException(
f"Could not load database driver for {url.drivername!r}"
) from e
@contextmanager
def adapter(
url_or_config: Union[str, URL, Dict[str, str]], **engine_kwargs: Any
) -> Iterator[SQLAlchemyAdapter]:
url = url_from_config(url_or_config)
with handle_error(url):
engine = create_engine(url, **engine_kwargs)
try:
yield SQLAlchemyAdapter(engine)
finally:
engine.dispose()
+12 -19
View File
@@ -1,10 +1,12 @@
from collections import defaultdict
from typing import Any, Dict, List, Mapping, Set
from collections.abc import Mapping
from typing import Any
from dvc.output import ARTIFACT_SCHEMA, DIR_FILES_SCHEMA, Output
from .base import Dependency
from .db import DB_SCHEMA, PARAM_DB, DbDependency, DbtDependency
from .dataset import DatasetDependency
from .db import DbDependency
from .param import ParamsDependency
from .repo import RepoDependency
@@ -15,7 +17,7 @@ from .repo import RepoDependency
SCHEMA: Mapping[str, Any] = {
**ARTIFACT_SCHEMA,
**RepoDependency.REPO_SCHEMA,
**DB_SCHEMA,
**DbDependency.DB_SCHEMA,
Output.PARAM_FILES: [DIR_FILES_SCHEMA],
Output.PARAM_FS_CONFIG: dict,
}
@@ -23,18 +25,17 @@ SCHEMA: Mapping[str, Any] = {
def _get(stage, p, info, **kwargs):
d = info or {}
db = d.get(PARAM_DB, {})
params = d.pop(ParamsDependency.PARAM_PARAMS, None)
repo = d.pop(RepoDependency.PARAM_REPO, None)
if params:
return ParamsDependency(stage, p, params)
if DbDependency.PARAM_QUERY in db:
return DbDependency(stage, info)
if db:
return DbtDependency(repo, stage, info)
if DbDependency.PARAM_DB in d:
return DbDependency(stage, d)
assert p
if DatasetDependency.is_dataset(p):
return DatasetDependency(stage, p, info)
if repo:
return RepoDependency(repo, stage, p, info)
return Dependency(stage, p, info, **kwargs)
@@ -58,23 +59,15 @@ def loads_from(stage, s_list, erepo=None, fs_config=None, db=None):
info = {RepoDependency.PARAM_REPO: erepo} if erepo else {}
if db:
info.update({"db": db})
return [
_get(
stage,
s,
info.copy(),
fs_config=fs_config,
)
for s in s_list
]
return [_get(stage, s, info.copy(), fs_config=fs_config) for s in s_list]
def _merge_params(s_list) -> Dict[str, List[str]]:
def _merge_params(s_list) -> dict[str, list[str]]:
d = defaultdict(list)
default_file = ParamsDependency.DEFAULT_PARAMS_FILE
# figure out completely tracked params file, and ignore specific keys
wholly_tracked: Set[str] = set()
wholly_tracked: set[str] = set()
for key in s_list:
if not isinstance(key, dict):
continue
+8 -10
View File
@@ -1,5 +1,3 @@
from typing import Dict, Type
from dvc.exceptions import DvcException
from dvc.fs import download as fs_download
from dvc.output import Output
@@ -25,15 +23,15 @@ class DependencyIsStageFileError(DvcException):
class Dependency(Output):
IS_DEPENDENCY = True
DoesNotExistError: Type[DvcException] = DependencyDoesNotExistError
IsNotFileOrDirError: Type[DvcException] = DependencyIsNotFileOrDirError
IsStageFileError: Type[DvcException] = DependencyIsStageFileError
DoesNotExistError: type[DvcException] = DependencyDoesNotExistError
IsNotFileOrDirError: type[DvcException] = DependencyIsNotFileOrDirError
IsStageFileError: type[DvcException] = DependencyIsStageFileError
def workspace_status(self) -> Dict[str, str]:
def workspace_status(self) -> dict[str, str]:
if self.fs.version_aware:
old_fs_path = self.fs_path
try:
self.fs_path = self.fs.path.version_path(self.fs_path, None)
self.fs_path = self.fs.version_path(self.fs_path, None)
if self.changed_meta():
return {str(self): "update available"}
finally:
@@ -42,9 +40,9 @@ class Dependency(Output):
def update(self, rev=None):
if self.fs.version_aware:
self.fs_path = self.fs.path.version_path(self.fs_path, rev)
self.fs_path = self.fs.version_path(self.fs_path, rev)
self.meta = self.get_meta()
self.fs_path = self.fs.path.version_path(self.fs_path, self.meta.version_id)
self.fs_path = self.fs.version_path(self.fs_path, self.meta.version_id)
def download(self, to, jobs=None):
fs_download(self.fs, self.fs_path, to.fs_path, jobs=jobs)
@@ -52,7 +50,7 @@ class Dependency(Output):
def save(self):
super().save()
if self.fs.version_aware:
self.fs_path = self.fs.path.version_path(self.fs_path, self.meta.version_id)
self.fs_path = self.fs.version_path(self.fs_path, self.meta.version_id)
def dumpd(self, **kwargs):
if self.fs.version_aware:
+69
View File
@@ -0,0 +1,69 @@
from typing import TYPE_CHECKING, Any, ClassVar
from urllib.parse import urlparse
from funcy import compact, merge
from dvc_data.hashfile.hash_info import HashInfo
from .db import AbstractDependency
if TYPE_CHECKING:
from dvc.stage import Stage
class DatasetDependency(AbstractDependency):
PARAM_DATASET = "dataset"
DATASET_SCHEMA: ClassVar[dict] = {PARAM_DATASET: dict}
def __init__(self, stage: "Stage", p, info, *args, **kwargs):
super().__init__(stage, info, *args, **kwargs)
self.def_path = p
self.name = urlparse(p).netloc
dataset_info = info.get(self.PARAM_DATASET) or {}
self.hash_info = HashInfo(self.PARAM_DATASET, dataset_info) # type: ignore[arg-type]
self.hash_name = self.PARAM_DATASET
def __repr__(self):
return f"{self.__class__.__name__}({self.def_path!r})"
def __str__(self):
return self.def_path
@classmethod
def is_dataset(cls, p: str):
return urlparse(p).scheme == "ds"
@property
def protocol(self):
return None
def dumpd(self, **kwargs):
return compact({self.PARAM_PATH: self.def_path, **self.hash_info.to_dict()})
def fill_values(self, values=None):
"""Load params values dynamically."""
self.hash_info = HashInfo(
self.PARAM_DATASET, merge(self.hash_info.value, values or {})
)
def workspace_status(self):
registered = self.repo.index.datasets.get(self.name, {})
info: dict[str, Any] = self.hash_info.value if self.hash_info else {} # type: ignore[assignment]
if info != registered:
return {str(self): "modified"}
return {}
def status(self):
return self.workspace_status()
def get_hash(self):
return HashInfo(self.PARAM_DATASET, self.repo.index.datasets.get(self.name, {}))
def save(self):
self.hash_info = self.get_hash()
def download(self, to, jobs=None):
raise NotImplementedError
def update(self, rev=None):
raise NotImplementedError
+41 -245
View File
@@ -1,58 +1,21 @@
import os
from contextlib import contextmanager, nullcontext
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, Optional, Union
from collections.abc import Iterator
from contextlib import contextmanager
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Optional
from funcy import compact, log_durations
from dvc.exceptions import DvcException
from dvc.log import logger
from dvc.scm import SCM
from .base import Dependency
if TYPE_CHECKING:
from rich.status import Status
from dvc.output import Output
from dvc.stage import Stage
logger = logger.getChild(__name__)
PARAM_DB = "db"
PARAM_PROFILE = "profile"
PARAM_FILE_FORMAT = "file_format"
def _get_dbt_config(config: Dict) -> Dict:
conf = config.get("feature", {})
pref = "dbt_"
return {k.lstrip(pref): v for k, v in conf.items() if k.startswith(pref)}
@contextmanager
def log_status(
msg, status: Optional["Status"] = None, log=logger.debug
) -> Iterator["Status"]:
from funcy import log_durations
from dvc.ui import ui
with log_durations(log, msg), status or ui.status(msg) as st:
st.update(msg)
yield st
@contextmanager
def chdir(path):
wdir = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(wdir)
@contextmanager
def download_progress(to: "Output") -> Iterator[Callable[[int], Any]]:
from dvc.ui import ui
@@ -69,7 +32,7 @@ def download_progress(to: "Output") -> Iterator[Callable[[int], Any]]:
class AbstractDependency(Dependency):
"""Dependency without workspace/fs/fs_path"""
def __init__(self, stage: "Stage", info: Dict[str, Any], *args, **kwargs):
def __init__(self, stage: "Stage", info: dict[str, Any], *args, **kwargs):
self.repo = stage.repo
self.stage = stage
self.fs = None
@@ -79,13 +42,28 @@ class AbstractDependency(Dependency):
class DbDependency(AbstractDependency):
PARAM_QUERY = "query"
PARAM_CONNECTION = "connection"
QUERY_SCHEMA = {PARAM_QUERY: str, PARAM_CONNECTION: str}
PARAM_DB = "db"
PARAM_QUERY = "query"
PARAM_TABLE = "table"
PARAM_FILE_FORMAT = "file_format"
DB_SCHEMA: ClassVar[dict] = {
PARAM_DB: {
PARAM_QUERY: str,
PARAM_CONNECTION: str,
PARAM_FILE_FORMAT: str,
PARAM_TABLE: str,
}
}
def __init__(self, stage: "Stage", info, *args, **kwargs):
super().__init__(stage, info, *args, **kwargs)
self.target = None
self.db_info: dict[str, str] = self.info.get(self.PARAM_DB, {})
self.connection = self.db_info.get(self.PARAM_CONNECTION)
@property
def sql(self) -> Optional[str]:
return self.db_info.get(self.PARAM_QUERY) or self.db_info.get(self.PARAM_TABLE)
def __repr__(self):
return "{}:{}".format(
@@ -95,9 +73,7 @@ class DbDependency(AbstractDependency):
def __str__(self):
from dvc.utils.humanize import truncate_text
db_info = self.info.get(PARAM_DB, {})
query = db_info.get(self.PARAM_QUERY, "[query]")
return truncate_text(query, 50)
return truncate_text(self.sql or "", 50)
def workspace_status(self):
return False # no workspace to check
@@ -109,8 +85,8 @@ class DbDependency(AbstractDependency):
"""nothing to save."""
def dumpd(self, **kwargs):
db_info = compact(self.info.get(PARAM_DB, {}))
return {PARAM_DB: db_info} if db_info else {}
db_info = compact(self.db_info)
return {self.PARAM_DB: db_info} if db_info else {}
def update(self, rev=None):
"""nothing to update."""
@@ -122,203 +98,23 @@ class DbDependency(AbstractDependency):
file_format: Optional[str] = None,
**kwargs: Any,
) -> None:
from dvc.database import export, get_adapter
from dvc.database import client
from dvc.ui import ui
db_info = self.info.get(PARAM_DB, {})
query = db_info.get(self.PARAM_QUERY)
if not query:
raise DvcException("Cannot download: no query specified")
sql = self.sql
if not sql:
raise DvcException("Cannot download: no query or table specified")
dbt_config = _get_dbt_config(self.repo.config)
profile = db_info.get(PARAM_PROFILE) or dbt_config.get(PARAM_PROFILE)
target = self.target or dbt_config.get("target")
db_config = self.repo.config.get(self.PARAM_DB, {})
config = db_config.get(self.connection)
if not config:
raise DvcException(f"connection {self.connection} not found in config")
connection = db_info.get(self.PARAM_CONNECTION)
db_config = self.repo.config.get("db", {})
config = db_config.get(connection)
if connection and not config:
raise DvcException(f"connection {connection} not found in config")
project_dir = self.repo.root_dir
with get_adapter(
config, project_dir=project_dir, profile=profile, target=target
) as db:
logger.debug("using adapter: %s", db)
with log_status("Testing connection") as status:
db.test_connection(onerror=status.stop)
file_format = file_format or db_info.get(PARAM_FILE_FORMAT, "csv")
assert file_format
with log_status("Executing query") as status, db.query(query) as serializer:
status.stop()
logger.debug("using serializer: %s", serializer)
with download_progress(to) as progress:
return export(
serializer, to.fs_path, format=file_format, progress=progress
)
class DbtDependency(AbstractDependency):
PARAM_MODEL = "model"
PARAM_VERSION = "version"
PARAM_PROJECT_DIR = "project_dir"
DBT_SCHEMA = {
PARAM_MODEL: str,
PARAM_VERSION: str,
PARAM_PROJECT_DIR: str,
}
def __init__(self, def_repo: Dict[str, Any], stage: "Stage", info, *args, **kwargs):
self.def_repo = def_repo or {}
self.target = None
super().__init__(stage, info, *args, **kwargs)
def __repr__(self):
return "{}:{}".format(
self.__class__.__name__,
"".join(f"{k}=={v}" for k, v in {**self.def_repo, **self.info}.items()),
)
def __str__(self):
from .repo import RepoDependency
repo = self.def_repo.get(RepoDependency.PARAM_URL)
rev = self.def_repo.get(RepoDependency.PARAM_REV)
db_info = self.info.get(PARAM_DB, {})
db = db_info.get(self.PARAM_MODEL, "")
project_dir = db_info.get(self.PARAM_PROJECT_DIR, "")
repo_info = ""
if repo:
repo_info += repo
if rev:
repo_info += f"@{rev}"
if project_dir:
repo_info += f":/{project_dir}"
return db + (f"({repo_info})" if repo_info else "")
@property
def locked_rev(self):
from .repo import RepoDependency
return self.def_repo.get(RepoDependency.PARAM_REV_LOCK)
@property
def rev(self):
from .repo import RepoDependency
return self.def_repo.get(RepoDependency.PARAM_REV)
def workspace_status(self):
if not self.def_repo:
return
current = self._get_clone(self.locked_rev or self.rev).get_rev()
updated = self._get_clone(self.rev).get_rev()
if current != updated:
return {str(self): "update available"}
return {}
def status(self):
return self.workspace_status()
def save(self):
from .repo import RepoDependency
if not self.def_repo:
return
rev = self._get_clone(self.locked_rev or self.rev).get_rev()
if self.def_repo.get(RepoDependency.PARAM_REV_LOCK) is None:
self.def_repo[RepoDependency.PARAM_REV_LOCK] = rev
def dumpd(self, **kwargs) -> Dict[str, Union[str, Dict[str, str]]]:
from .repo import RepoDependency
def_repo = {}
if self.def_repo:
def_repo = RepoDependency._dump_def_repo(self.def_repo)
db_info = compact(self.info.get(PARAM_DB, {}))
return compact({RepoDependency.PARAM_REPO: def_repo, PARAM_DB: db_info})
def _get_clone(self, rev):
from dvc.repo.open_repo import _cached_clone
from .repo import RepoDependency
url = self.def_repo.get(RepoDependency.PARAM_URL)
repo_root = self.repo.root_dir if self.repo else os.getcwd()
return SCM(_cached_clone(url, rev) if url else repo_root)
def update(self, rev=None):
from .repo import RepoDependency
if not self.def_repo:
return
if rev:
self.def_repo[RepoDependency.PARAM_REV] = rev
else:
rev = self.rev
self.def_repo[RepoDependency.PARAM_REV_LOCK] = self._get_clone(rev).get_rev()
def download(
self,
to: "Output",
jobs: Optional[int] = None, # noqa: ARG002
file_format: Optional[str] = None,
) -> None:
from .repo import RepoDependency
project_dir = self.info.get(PARAM_DB, {}).get(self.PARAM_PROJECT_DIR, "")
if self.def_repo:
ctx = repo = self._get_clone(self.locked_rev or self.rev)
self.def_repo[RepoDependency.PARAM_REV_LOCK] = repo.get_rev()
root = wdir = repo.root_dir
else:
ctx = nullcontext()
root = self.repo.root_dir
wdir = self.stage.wdir
project_path = os.path.join(wdir, project_dir) if project_dir else root
with ctx, chdir(project_path):
self._download_db(to, file_format=file_format)
def _download_db(
self,
to: "Output",
version: Optional[int] = None,
file_format: Optional[str] = None,
) -> None:
from dvc.database import export, get_model
db_info = self.info.get(PARAM_DB, {})
model = db_info.get(self.PARAM_MODEL)
version = version or db_info.get(self.PARAM_VERSION)
file_format = file_format or db_info.get(PARAM_FILE_FORMAT) or "csv"
file_format = file_format or self.db_info.get(self.PARAM_FILE_FORMAT, "csv")
assert file_format
if not model:
raise DvcException("Cannot download, no model specified")
dbt_config = _get_dbt_config(self.repo.config)
profile = db_info.get(PARAM_PROFILE) or dbt_config.get(PARAM_PROFILE)
target = self.target or db_info.get("target") or dbt_config.get("target")
with log_status("Downloading model"):
serializer = get_model(
model, version=version, profile=profile, target=target
)
# NOTE: we keep everything in memory, and then export it out later.
with download_progress(to) as progress:
export(serializer, to.fs_path, format=file_format, progress=progress)
DB_SCHEMA = {
PARAM_DB: {
PARAM_PROFILE: str,
PARAM_FILE_FORMAT: str,
**DbDependency.QUERY_SCHEMA,
**DbtDependency.DBT_SCHEMA,
},
}
with client(config) as db:
msg = "Testing connection"
with log_durations(logger.debug, msg), ui.status(msg) as status:
db.test_connection(onerror=status.stop)
with download_progress(to) as progress:
db.export(sql, to.fs_path, format=file_format, progress=progress)
+12 -15
View File
@@ -1,7 +1,7 @@
import os
import typing
from collections import defaultdict
from typing import TYPE_CHECKING, Any, Dict, List, Optional
from typing import TYPE_CHECKING, Any, Optional
import dpath
@@ -37,7 +37,7 @@ class BadParamFileError(DvcException):
def read_param_file(
fs: "FileSystem",
path: str,
key_paths: Optional[List[str]] = None,
key_paths: Optional[list[str]] = None,
flatten: bool = False,
**load_kwargs,
) -> Any:
@@ -54,12 +54,15 @@ def read_param_file(
continue
return ret
from dpath import merge
from copy import deepcopy
for key_path in key_paths:
from dpath import merge
from funcy import distinct
for key_path in distinct(key_paths):
merge(
ret,
dpath.search(config, key_path, separator="."),
deepcopy(dpath.search(config, key_path, separator=".")),
separator=".",
)
return ret
@@ -73,10 +76,7 @@ class ParamsDependency(Dependency):
self.params = list(params) if params else []
hash_info = HashInfo()
if isinstance(params, dict):
hash_info = HashInfo(
self.PARAM_PARAMS,
params, # type: ignore[arg-type]
)
hash_info = HashInfo(self.PARAM_PARAMS, params) # type: ignore[arg-type]
repo = repo or stage.repo
path = path or os.path.join(repo.root_dir, self.DEFAULT_PARAMS_FILE)
super().__init__(stage, path, repo=repo)
@@ -100,14 +100,11 @@ class ParamsDependency(Dependency):
for param in self.params:
if param in values:
info[param] = values[param]
self.hash_info = HashInfo(
self.PARAM_PARAMS,
info, # type: ignore[arg-type]
)
self.hash_info = HashInfo(self.PARAM_PARAMS, info) # type: ignore[arg-type]
def read_params(
self, flatten: bool = True, **kwargs: typing.Any
) -> Dict[str, typing.Any]:
) -> dict[str, typing.Any]:
try:
self.validate_filepath()
except MissingParamsFile:
@@ -131,7 +128,7 @@ class ParamsDependency(Dependency):
from funcy import ldistinct
status: Dict[str, Any] = defaultdict(dict)
status: dict[str, Any] = defaultdict(dict)
info = self.hash_info.value if self.hash_info else {}
assert isinstance(info, dict)
actual = self.read_params()
+5 -5
View File
@@ -1,5 +1,5 @@
from copy import deepcopy
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union
import voluptuous as vol
@@ -20,7 +20,7 @@ class RepoDependency(Dependency):
PARAM_CONFIG = "config"
PARAM_REMOTE = "remote"
REPO_SCHEMA = {
REPO_SCHEMA: ClassVar[dict] = {
PARAM_REPO: {
vol.Required(PARAM_URL): str,
PARAM_REV: str,
@@ -30,7 +30,7 @@ class RepoDependency(Dependency):
}
}
def __init__(self, def_repo: Dict[str, Any], stage: "Stage", *args, **kwargs):
def __init__(self, def_repo: dict[str, Any], stage: "Stage", *args, **kwargs):
self.def_repo = def_repo
super().__init__(stage, *args, **kwargs)
@@ -65,7 +65,7 @@ class RepoDependency(Dependency):
self.def_repo[self.PARAM_REV_LOCK] = rev
@classmethod
def _dump_def_repo(cls, def_repo) -> Dict[str, str]:
def _dump_def_repo(cls, def_repo) -> dict[str, str]:
repo = {cls.PARAM_URL: def_repo[cls.PARAM_URL]}
rev = def_repo.get(cls.PARAM_REV)
@@ -85,7 +85,7 @@ class RepoDependency(Dependency):
repo[cls.PARAM_REMOTE] = remote
return repo
def dumpd(self, **kwargs) -> Dict[str, Union[str, Dict[str, str]]]:
def dumpd(self, **kwargs) -> dict[str, Union[str, dict[str, str]]]:
return {
self.PARAM_PATH: self.def_path,
self.PARAM_REPO: self._dump_def_repo(self.def_repo),

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