Compare commits

...
284 Commits
Author SHA1 Message Date
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
Ruslan Kuprieievandpre-commit-ci[bot] 3927e75ef1 testing: bench: add pytest_report_header (#10132)
* testing: bench: add pytest_report_header

Useful for sanity checks in logs.

* Update dvc/testing/benchmarks/plugin.py

* [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-04 11:23:12 +02:00
skshetry e5ae5ef24b add security-extend to codeql queries (#10131) 2023-12-04 13:29:29 +05:45
skshetry e187508f40 database: fix ui issues (#10129) 2023-12-04 13:27:05 +05:45
skshetry 8768b50aed experimental: support connection strings for databases (#10125)
* experimental: support connection strings for databases using connectorx

This adds an experimental support for connector strings using connectorx.

User has to provide config for the connection strings with:

Example:
```console
dvc config --local db_connection.pg postgres://user:pass@localhost:5432/database
```

And then, they can import using:
```console
dvc import-db --sql 'query' --conn pg
```

Check supported databases and how to configure in https://sfu-db.github.io/connector-x/databases.html.
This `db_connection` config is subject to change.

connectorx still has the same problem with dbt as before: no support for streaming,
but it'll be a bit more simpler to configure with the connection strings.

* add pandas to deps

* sqlalchemy

* dbt streaming sql

* fix test

* remove connectorx

* remove check in set

* use mode='wb' for serialization
2023-12-03 19:23:09 +05:45
skshetry 7c61463d2d tests: do not setup pytest plugin entrypoint (#10121)
We should not register a pytest plugin automaticallly, as we may
have fixtures that might override user's fixtures. Also, it might
raise import errors as in https://github.com/iterative/dvc/issues/10118.

We can still use this as a plugin ourselves by setting the following
in `conftest.py` file.
```python
pytest_plugins = ['dvc.testing.plugin']
```
2023-11-29 12:05:46 +05:45
Peter Rowlands b0d0315689 import/get: support pre-fetching LFS objects from Git-LFS repos 2023-11-29 14:39:54 +09:00
Peter Rowlands 7edcb6b1e5 deps: bump scmrepo minver to 1.5.0 2023-11-29 14:39:54 +09:00
Ruslan Kuprieiev b6d231f9cf testing: import virtualenv lazily
Fixes https://github.com/iterative/dvc/issues/10118
2023-11-28 22:20:34 +02:00
Ruslan Kuprieiev 322fcec5ca bench: test_sharing: use fetch instead of pull 2023-11-28 19:31:32 +02:00
Ruslan Kuprieiev 1e5b3603cc bench: test fetch instead of pull
`pull` is literally `fetch` + `checkout` and our main interest there is downloading, so let's just test fetch instead.
2023-11-28 19:31:32 +02:00
Ruslan Kuprieiev ffa630e8d5 push: handle missing/failed version_aware file
Previously we used recorded index which had stub entries
for missing/failed outputs, but now we actually collect
everything ourselves and those are missing.
2023-11-28 18:52:46 +02:00
Ruslan Kuprieiev 1e9b60dc48 deps: bump dvc-data to >=2.22.1 2023-11-27 16:17:42 +02:00
skshetry 83dd2b2a7b test: import-db: test for external model (#10112)
* test: import-db: test for external model

* close repo
2023-11-27 16:13:44 +05:45
Ivan Shcheklein d238350a2d fix(config): respect global config env variable 2023-11-23 20:51:52 +02:00
skshetry 06b9196371 pytest.ini_options: add --benchmark-skip to suppress pytest-benchmark warning with xdist (#10106)
We don't run benchmarks here, and benchmarking should always be a separate command than regular test run.
2023-11-23 19:55:23 +05:45
skshetry b4f42242c9 tmp_dir: fix object initialization for Python 3.12 (#10105) 2023-11-23 16:00:30 +05:45
skshetryandpre-commit-ci[bot] fbdb566079 remove pytest-virtualenv (#10103)
* remove pytest-virtualenv

It does not look like it's being actively maintained.

* force virtualenv

* [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-11-23 14:53:39 +05:45
skshetry 3d7bbb8492 tests: add functional test for db (#10101)
* tests: add functional test for db

* fix test
2023-11-22 16:15:07 +05:45
skshetry fd7c891217 implement import-db command (#10040)
* implement import-db command

* generate .dvc file

* update

* fix tests

* pin dbt-fal

* try to run on ci

* use os.chdir

* fix

* revert

* require dbt-core>=1.5

* add db config schema

* remove platformdirs constraints

* use logger.getChild

* improve error messaging

* rename cli flag --export-format to --output-format

* use feature flag for db config

* hide command from dvc commands list

* refactor dependency._get

* remove todo

* refactor dependency._get

* fix dbt installation check
2023-11-21 19:38:50 +05:45
Ruslan Kuprieiev 2b3b15f767 deps: bump dvc-data to 2.22.0
Related https://github.com/iterative/dvc/issues/10059
2023-11-17 20:01:57 +02:00
Ruslan Kuprieiev 8c9358957c push: fix cloud versioning 2023-11-15 16:28:17 +02:00
Ivan Shcheklein 86dd0785a0 add tests for the index param keys 2023-11-14 13:21:44 +02:00
Ivan Shcheklein a8aec51c35 add params collection to index 2023-11-14 13:21:44 +02:00
Ruslan Kuprieiev e9f2da4451 repo: drop_data_index: delete all first level nodes 2023-11-13 11:23:08 +02:00
dependabot[bot]andRuslan Kuprieiev 8d23a5e65a build(deps-dev): bump mypy from 1.6.1 to 1.7.0 (#10088)
* build(deps-dev): bump mypy from 1.6.1 to 1.7.0

Bumps [mypy](https://github.com/python/mypy) from 1.6.1 to 1.7.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.6.1...v1.7.0)

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

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

* fix misc mypy errors

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ruslan Kuprieiev <[email protected]>
2023-11-13 10:59:32 +02:00
skshetry b2fc194e2b fix name of the loggers used (#10085)
__name__ looks something like `dvc.commands.diff`, but if we are already instantiating
root logger with `dvc`, it adds up to create something like `dvc.dvc.commands.diff`.

We should be using a root logger here instead.
2023-11-10 15:54:08 +05:45
dependabot[bot]andSaugat Pachhai 0def298b8c build(deps-dev): bump mypy from 1.5.1 to 1.6.1 (#10035)
* build(deps-dev): bump mypy from 1.5.1 to 1.6.1

Bumps [mypy](https://github.com/python/mypy) from 1.5.1 to 1.6.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.5.1...v1.6.1)

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

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

* pre-commit autoupdate

* mypy fixes

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Saugat Pachhai (सौगात) <[email protected]>
2023-11-10 15:40:57 +05:45
skshetry d678773b53 remove more pylint suppression comments (#10084) 2023-11-10 15:19:52 +05:45
skshetry 519cce3fe4 logger: type-safe logger.trace (#10083)
* use logger.getChild from `dvc.log` instead of `logging.getLogger`

I got tired adding `# type: ignore[attr-defined]` while using `logger.trace`.
So, now you need to import logger from `from dvc.log import logger` and
create a child logger using `logger.getChild(__name__)`. `logger.getChild`
will return a logger type-hinted as a logger having a `trace` method.

So, from now on, instead of:
```python
import logging

logger = logging.getogger(__name__)
```

you'll have to do following:
```python
from dvc.log import logger

logger = logger.getChild(__name__)
```

* remove all dvc imports from dvc.logger

* use Tqdm back
2023-11-10 12:55:49 +05:45
skshetry cbc111b519 remove flake8/bandit (#10082)
ruff already runs most of the flake8/bandit checks
2023-11-09 21:26:37 +05:45
skshetry 4ee8f554a5 use ruff formatter (#10081)
Replaces black with ruff-formatter, and isort with ruff.
2023-11-09 20:58:58 +05:45
skshetry 06565e18b4 remove pylint suppression comments (#10080) 2023-11-09 20:31:50 +05:45
skshetry 2085bc8053 drop pylint from linter checks (#10079)
drop pylint
2023-11-09 19:25:28 +05:45
Peter Rowlands 14cbf6470d artifacts get: fix issue where global/system config could be ignored 2023-11-09 16:19:02 +09:00
Ivan Shcheklein 36fa116e75 chore(deps): bump scmerepo to 1.4.1+
After this fix https://github.com/iterative/scmrepo/issues/280, to make sure that we don't install by mistake an older version in Studio.
2023-11-06 15:23:18 +09:00
Ivan Shcheklein 97f3d07480 fix(readme): link to VS Code extension (#10063) 2023-11-02 20:37:32 -07:00
dberenbaum 4a2c4f76e5 fix for DVCFileSystem().ls() 2023-10-31 23:38:21 +02:00
dberenbaum eb8999a86b gc: force no prompts during dry run 2023-10-31 10:24:01 -05:00
dberenbaum db870335e3 gc: don't prompt when for dry run 2023-10-31 10:24:01 -05:00
Piotr Bielak 23d2ed3d59 Unroll dict-comprehension in matrix test 2023-10-31 08:05:38 -05:00
skshetry 46db3ac419 Update dvc/parsing/__init__.py 2023-10-31 08:05:38 -05:00
Piotr Bielak fcc69ab6eb parsing: Add key to matrix stages
This commit adds the `key` property to the templaing context of `matrix`
stages. Similarly to `foreach` stages, the `key` is available using
`${key}` substitution.

Fixes #10043
2023-10-31 08:05:38 -05:00
Ruslan Kuprieiev d60557dd08 deps: bump dvc-data to >=2.20.0 2023-10-31 04:51:30 +02:00
dberenbaum fc5147767f api: make exp_show return float instead of None for zeroes 2023-10-30 13:54:31 -05:00
skshetry 89b4beb7a4 daemon: ipc to log pid from the child (#10058)
* daemon: ipc to log pid from the child

* fix tests to use pid

* also test for updater/analytics keyword
2023-10-30 20:47:52 +05:45
skshetry 48f1f52c1b refactor: dvc.daemon (#10045)
* minor refactor to dvc.daemon

* fix tests

* run daemon only inside a subprocess
2023-10-30 11:09:55 +05:45
Ruslan Kuprieiev 3fee81f1be push: use index push 2023-10-26 20:58:45 +03:00
Peter Rowlands 0bfce0b643 exp apply: use force dvc-checkout by default 2023-10-26 14:48:41 +09:00
Peter Rowlands d9b2e67a0b Revert "Enable forcing exp apply (unsaved files in the workspace)"
This reverts commit 3661b8998b.
2023-10-26 14:48:41 +09:00
Ruslan Kuprieiev 4a0d56a793 cli: DictAction: set default metavar (#10047) 2023-10-20 16:46:14 +03:00
skshetry 825c823a37 avoid failing on standard streams that are None. (#10041)
See https://docs.python.org/3/library/sys.html#sys.__stdin__.

> [!Note]
> Under some conditions stdin, stdout and stderr as well as the original values __stdin__, __stdout__ and __stderr__ can be None. It is usually the case for Windows GUI apps that aren’t connected to a console and Python apps started with pythonw.

This is a best-effort solution. External libraries (eg: `tqdm`) and future code that is unaware of this stuff may still fail.
As a console application, `stdout`/`stderr` being `None` is not really something that we should support in general.
Regarding the failure of the daemon, it has been fixed so that it'll always have a stream attached (if not, it falls back to
/dev/null). Today, this is a problem in daemon only if the main process is run with no streams attached, which is not
something that we support or go on length to support.

It is tricky to write tests for this case, and I could not come up with one. Also decided not to test daemon as it should no longer be a problem.
2023-10-19 22:16:26 +05:45
skshetry b23ba58cd6 tests: daemon: log body of the request (#10039)
log body of the request
2023-10-19 17:16:34 +05:45
skshetry 8fb67b71a5 expose test webserver as an executable for easier testing (#10037) 2023-10-19 16:28:47 +05:45
skshetry 5693775459 refactor: use int as the factory (#10036)
makes it thread-safe if we need in the future. Also looks a bit clean.
2023-10-19 15:31:33 +05:45
skshetry 17f5508f49 daemon: add logging support, tests and generalize popen based runs for future (#10033)
* use popen to run updater/analytics in detached mode

subprocess does not provide a way to avoid a zombie process. Not to confuse with orphaned processes, which will be `wait`-ed by `pid 1`.

It might not work for long-running processes, but I don't think zombie processes are going to be a problem for us, as the parent process immediately exits after spawning a child process.

Even in case of `queue` based experiment runner, parent will exit before than the child process. So I don't think it's a requirement here. But it does feel wrong to leak the processes, so I have to sleep on that.

---

This PR:

1. makes `daemonized` process inherit the logging verbosity, so if the parent process used `-vv`, the daemonized process will run the same way.
2. Add `DVC_DAEMON_LOGFILE` envvar, that the daemon will log to the file. By default, the verbosity is the same as running parent processes. Without `DVC_DAEMON_LOGFILE`, the output will be redirected to `/dev/null`.
3. Adds debug/trace for updater/analytics.

I hope by doing this, we can make our life easier on debugging and make daemon more transparent/observable.

* add tests

* fixes

* use kwargs

* pass all os.environ with some modified
2023-10-19 15:14:41 +05:45
skshetry c39ce6157d redirect fds rather than closing (#10028) 2023-10-18 22:29:45 +05:45
skshetry d59231febf log exit code and pid on verbose-verbose logging (#10031)
log exit code and pid on verbose logging

Is useful for debugging on daemon.
2023-10-18 17:11:07 +05:45
Ruslan Kuprieiev b35b9420a1 config: azure: expose timeouts (#10027) 2023-10-17 19:10:08 +03:00
Ruslan Kuprieiev 46c65fdba2 worktree: don't modify index entries in place (#10024) 2023-10-17 16:30:19 +03:00
Matt Seddon 5f8afb911e Remove VegaConverter TODO and update misleading comment 2023-10-16 20:42:48 -04:00
Dave Berenbaum ef226a96cd analytics: fix mac not sending reports (#10026) 2023-10-16 23:28:29 +03:00
Heinz-Alexander Fuetterer 8ea9d3edef chore: fix typos (#10022) 2023-10-14 12:54:57 +05:45
dependabot[bot] cafcefa758 build(deps-dev): bump pylint from 2.17.6 to 3.0.1
Bumps [pylint](https://github.com/pylint-dev/pylint) from 2.17.6 to 3.0.1.
- [Release notes](https://github.com/pylint-dev/pylint/releases)
- [Commits](https://github.com/pylint-dev/pylint/compare/v2.17.6...v3.0.1)

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

Signed-off-by: dependabot[bot] <[email protected]>
2023-10-13 23:50:16 +03:00
Ruslan Kuprieiev 72fd265a8f get-url/ls-url: support remote:// with in-repo config 2023-10-13 18:58:23 +03:00
Brady Johnson c2f8115c9a exp rename: verify exp name follows ref format 2023-10-13 11:27:05 +09:00
Peter Rowlands e5dd2bb2cc cache migrate: add --dvc-files flag to migrate .dvc and dvc.lock files 2023-10-12 09:47:08 +09:00
Ruslan Kuprieiev 50d6788411 exp: preserve hidden alias -M for --message (#10006)
Followup for #10005
2023-10-10 04:41:43 +03:00
Matt Seddon 37a4a36dc7 exp save: use -m for --message short option 2023-10-09 09:03:44 -04:00
Dave Berenbaum 279f8cb68f repro --pull: don't fail for cloud-versioned remotes (#10001)
* repro --pull: don't fail for cloud-versioned remotes

* raise RunCacheNotSupported for cloud-versioned remotes
2023-10-06 18:11:30 +03:00
dberenbaum 8a70877748 fix artifact name test 2023-10-05 13:39:06 -04:00
dberenbaum f60091e64f bump gto 2023-10-05 13:39:06 -04:00
Matt Seddon 3661b8998b Enable forcing exp apply (unsaved files in the workspace) 2023-10-04 16:12:52 -04:00
Matt Seddon dcb678d606 exp rename: update help text 2023-10-04 15:14:49 -04:00
Ruslan Kuprieiev 6394a503be deps: bump dvc-data to >=2.18.1
To ensure people are getting a fix related to iterative/dvc#9826
2023-10-04 16:03:22 +03:00
Ruslan Kuprieiev 00c10eb6bd repo: bump site_cache_dir salt
Caused by a cache corruption fix in https://github.com/iterative/dvc-data/pull/447
2023-10-04 04:01:26 +03:00
Ruslan Kuprieiev b0a25f11b7 deps: bump dvc-data to >= 2.18.0 2023-10-04 04:01:26 +03:00
Dave Berenbaum bc1a700254 gc: add dry option and log num removed objs (#9952)
Closes #9330
2023-09-30 02:33:44 +00:00
Ruslan Kuprieiev 9f4b67cec4 site_cache_dir: use .dvc/tmp/btime and fallback to st_birthtime
This will ensure that we won't be using index cache if you delete your repo
and start/clone fresh.
2023-09-29 20:30:28 +03:00
Ruslan Kuprieiev 4d5a0163ea diff: handle granular targets 2023-09-28 21:54:06 +03:00
Ruslan Kuprieiev 0053b1ccd2 index: check parents when trying to detect hash_name 2023-09-28 21:54:06 +03:00
Ruslan Kuprieiev f981500862 cli: data_sync: show warning about missing default remote (#9979) 2023-09-26 23:29:33 +03:00
David de la Iglesia Castro a6e0d68cd2 run-cache: Load newest instead of first found in os.listdir. (#9908)
Closes https://github.com/iterative/dvc/issues/9420
2023-09-26 15:07:13 +03:00
David de la Iglesia Castro 2aa2bef9d6 artifacts: Bring back name_is_compatible. (#9972)
* artifacts: Bring back `name_is_compatible`.

Unfortunately, DVCLive was importing this function so I am bringing back the name for backwards compatibility.

* Update artifacts.py
2023-09-26 14:32:27 +03:00
skshetry 95c9790368 Revert "build: temporarily pin setuptools-scm" (#9978)
This reverts commit 7b40968741.
2023-09-26 13:47:31 +05:45
pre-commit-ci[bot] b8106e50fb [pre-commit.ci] pre-commit autoupdate (#9976)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.0.290 → v0.0.291](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.290...v0.0.291)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-09-26 08:55:57 +05:45
dependabot[bot] ccc84a0a43 build(deps-dev): bump pylint from 2.17.5 to 2.17.6 (#9977)
Bumps [pylint](https://github.com/pylint-dev/pylint) from 2.17.5 to 2.17.6.
- [Release notes](https://github.com/pylint-dev/pylint/releases)
- [Commits](https://github.com/pylint-dev/pylint/compare/v2.17.5...v2.17.6)

---
updated-dependencies:
- dependency-name: pylint
  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-09-26 08:55:03 +05:45
Ruslan Kuprieiev 2e1166d520 fetch: don't clear pbars
There is more we can do during the `Fetching` stage, but it will
require additional tweaks, so just keeping it simple here.

Similar to https://github.com/iterative/dvc/pull/9974
2023-09-26 01:27:59 +03:00
Ruslan Kuprieiev 18b4d610d6 checkout: tweak messages and don't clear pbars 2023-09-25 23:17:26 +03:00
Ruslan Kuprieiev b77218aa5b fetch: use ui.progress instead of raw Callback 2023-09-25 22:21:42 +03:00
Peter Rowlands (변기호) 12b572596f repo: add API and CLI for reading artifacts (#9770)
* deps: add GTO as a dependency

* deps: bump dvc-studio-client to 0.13.0

* fs: return downloaded count in fs.download()

* utils: add type hints for resolve_output

* artifacts: add `dvc artifacts get`

* api: add `api.artifacts_show`

* artifacts get: support --remote, --remote-config

* gto: update to 1.3.0
2023-09-25 20:21:34 +05:45
dberenbaum e8362db9bc exp: force add include_untracked paths 2023-09-25 19:02:38 +09:00
skshetry 6502234022 ci: build: split publishing and building into separate jobs (#9970)
https://github.com/pypa/gh-action-pypi-publish suggests separating building from publishing
for security reasons.
2023-09-23 18:05:03 +05:45
Ruslan Kuprieiev 1fa9f1f6ea index: fix fs cache location
Existing logic might result in weird fs cache location like
`.dvc/cache/files/md5/fs`, while we really only want
`.dvc/cache/fs`, since it is not an odb cache.
2023-09-23 05:44:05 +03:00
Peter Rowlands 4645975ee3 gc -c: always gc both 2.x and 3.x odbs for remotes 2023-09-22 09:55:50 +09:00
Peter Rowlands 5139da5f8e output: return both 2.x and 3.x remote odbs in get_used_objects 2023-09-22 09:55:50 +09:00
Peter Rowlands f801eb4380 studio: remove redundant type cast 2023-09-21 15:20:27 +09:00
Peter Rowlands acb54a3462 deps: bump dvc-data to 2.16.3 2023-09-21 15:20:27 +09:00
Peter Rowlands 7b40968741 build: temporarily pin setuptools-scm 2023-09-21 15:08:28 +09:00
pre-commit-ci[bot] b85608121f [pre-commit.ci] pre-commit autoupdate (#9960)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.0.288 → v0.0.290](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.288...v0.0.290)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-09-19 09:29:23 +05:45
Ruslan Kuprieiev 4bf19debb4 index: don't load external outs 2023-09-19 01:21:04 +03:00
BradyJ27anddaavoo 51dc78a135 exp: add rename command (#9812)
* Add renaming for finished experiments

* Update arg parser

* Add rename by rev

* Add rename tests

* Change remove to rename; Refactor arguments error

* Remove --rev; Use resolve_name; Add test

* Update testing; Fix python3.10 rename failure

* Update rename test

* Update tests and error handling

* exp rename: Add `--force`.

Fail if experiment new_name exists but allow to overwrite it.

---------

Co-authored-by: daavoo <[email protected]>
2023-09-18 17:01:32 +00:00
Peter Rowlands 784a5b5199 api.DVCFileSystem: fix fsspec compliance 2023-09-16 00:05:31 +09:00
dberenbaum bf2af86397 drop irrelevant errors in tests/unit/render/test_match.py 2023-09-14 10:36:46 -04:00
dberenbaum 08d37912d6 add util function to group plot definitions 2023-09-14 10:36:46 -04:00
dberenbaum 0e66aa2b6a add integration test for dvclive 2.x->3.x plots 2023-09-14 10:36:46 -04:00
dberenbaum f9340ed7b9 add test cases for plots group_definitions 2023-09-14 10:36:46 -04:00
dberenbaum e6ddb31525 keep config_file in plot_id if multiple config_files 2023-09-14 10:36:46 -04:00
dberenbaum 7f2ddf943f dropped config_file from calls to _matches 2023-09-14 10:36:46 -04:00
dberenbaum 3d664f1dbf drop unused arg 2023-09-14 10:36:46 -04:00
Dave BerenbaumandDavid de la Iglesia Castro cdbed33386 Update dvc/render/match.py
Co-authored-by: David de la Iglesia Castro <[email protected]>
2023-09-14 10:36:46 -04:00
dberenbaum e1599ae280 add test for merging plot ids across config files 2023-09-14 10:36:46 -04:00
pre-commit-ci[bot] ee367dcb1b [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2023-09-14 10:36:46 -04:00
dberenbaum 7e7d0e1dd8 merge plot ids across dvc.yaml files 2023-09-14 10:36:46 -04:00
dberenbaum d81a1bbf56 make exp run --dry imply --temp 2023-09-13 15:06:46 +09:00
Bilel Omrani ba0834caa0 Fix Repo instantiation error when using fsspec chaining API 2023-09-12 21:41:48 +03:00
pre-commit-ci[bot] 9a4578cc72 [pre-commit.ci] pre-commit autoupdate (#9937)
updates:
- [github.com/psf/black: 23.7.0 → 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1)
- [github.com/astral-sh/ruff-pre-commit: v0.0.287 → v0.0.288](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.287...v0.0.288)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-09-12 20:44:17 +05:45
Helio Machadoandskshetry ccf461bc84 Migrate from PyPI tokens to Trusted Publishers (#9933)
* Migrate Python repositories to Trusted Publishers

* Update build.yaml

* Update .github/workflows/build.yaml

Co-authored-by: Helio Machado <[email protected]>

* Update build.yaml

---------

Co-authored-by: skshetry <[email protected]>
2023-09-12 20:27:37 +05:45
Ruslan Kuprieiev e9c7cd63a2 dvcfs: add tests for fsid 2023-09-11 21:20:12 +03:00
skshetry 33b6c1d876 params/metrics: cache remote files (#9932) 2023-09-11 19:13:47 +05:45
skshetry f2673acb53 metrics/params: support reading from cached files and directories, use repo-relative path consistently (#9909)
* metrics

* fix path handling

* fix dvc metrics diff

* params show/diff

* share common utils

* remove onerror_collect from exp_show

* extract read_params

* move around code

* fix path handling on Windows

* rename _show to _gather_metrics

* remove _collect_vars

* add tests

* Revert "remove _collect_vars"

This reverts commit 6d2cb500c32a882497406d66fb13e613790c3436.

* bring back _collect_vars

* fix tests

* fix paths
2023-09-11 17:41:37 +05:45
David de la Iglesia Castro 7e52c233ae import: Expose --force flag (#9927) 2023-09-08 18:43:13 +05:45
Peter Rowlands (변기호) 408d23f285 repo: use is_repo_import to check for imports in outs_graph (#9925)
repo: use is_repo_import to check for imports
2023-09-08 16:13:11 +05:45
David de la Iglesia Castro 7fb8a9a427 checkout: Expose --allow-missing arg. (#9919) 2023-09-07 09:55:50 +00:00
Ruslan Kuprieiev 784f5a5d63 deps: bump dvc-data to 2.16.0 2023-09-07 04:13:56 +03:00
Ruslan Kuprieiev 1710e379f5 dvcfs: add basic fsid
Implements fsspec's fsid https://github.com/fsspec/filesystem_spec/pull/1122

Required for #9904
2023-09-07 04:13:56 +03:00
Joe Scott aa9636e2ae Typo rwlock.py#107 Remove -> Removed
closes #9920
2023-09-06 09:21:29 -04:00
Peter Rowlands 65d412bfa3 stage.run: always set DVC_ROOT env var 2023-09-06 18:43:52 +09:00
Peter Rowlands 20606d480b exp show: drop internal legacy checkpoint support 2023-09-06 14:58:53 +09:00
dependabot[bot] e2aedb7e97 build(deps): bump actions/checkout from 3 to 4 (#9911)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  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-09-05 08:43:43 +05:45
pre-commit-ci[bot] bacae48f96 [pre-commit.ci] pre-commit autoupdate (#9910)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.0.286 → v0.0.287](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.286...v0.0.287)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-09-05 08:34:38 +05:45
Peter Rowlands bf119e70a9 exp run: track files for skipped/run-cached stages 2023-09-04 16:23:54 +09:00
BradyJ27 3528c37b66 move: Alias mv to move (#9905)
* Alias mv to move

* Add alias for mv

* Add aliases for rm, exp rm, exp ls
2023-09-03 22:11:41 +03:00
Ruslan Kuprieiev f47d21b0c2 [WIP] ls-url/get-url/import-url: introduce --fs-config (#9888)
ls-url/get-url/import-url: introduce --fs-config

Fixes #9886
2023-09-01 02:17:17 +03:00
Ruslan Kuprieiev 7a9da0f4a1 cli: utils: DictAction: limit split to 2 elements (#9887) 2023-08-30 03:15:21 +03:00
Ruslan Kuprieiev 8bfe8e3328 site_cache_dir: add salt to token (#9884)
See comment in the code.
2023-08-29 17:48:38 +03:00
Ruslan Kuprieiev c290d5c008 repo: don't try to gitignore custom cache locations (#9883)
If user uses some custom cache location, there is really no reason for us
to try to gitignore it, as we have no idea what he is planning to do with
that and overall it often might be sign of this being misused. Maybe we could
also have some warnings/errors, but that's out of scope for this PR.

Fixes https://github.com/iterative/studio/issues/7405
2023-08-29 16:31:37 +03:00
Matt Seddon 3c2c1fe72a Remove dead Stage method (#9881) 2023-08-29 13:10:40 +00:00
pre-commit-ci[bot] 0602eca00e [pre-commit.ci] pre-commit autoupdate (#9882)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.0.285 → v0.0.286](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.285...v0.0.286)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-08-29 07:17:32 +05:45
Matt Seddon 5b69a16ea7 Add DVC_ROOT env var into queued experiment runs (#9879) 2023-08-28 22:37:03 +00:00
Dave Berenbaumandpre-commit-ci[bot] ef260eaf2e exp show: show metrics that include separator (#9819)
* exp show: show metrics that include separator

* sort using partition instead of lpartition

* handle ':' in file and metric names

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

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

* fix sorting with leading :

* [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-08-24 10:31:56 -04:00
Peter Rowlands 88431b1c0a update: preserve def_path for version-aware import update 2023-08-24 16:16:43 +09:00
dependabot[bot] bd2dc9dfed build(deps): bump rtCamp/action-slack-notify from 2.2.0 to 2.2.1 (#9874)
Bumps [rtCamp/action-slack-notify](https://github.com/rtcamp/action-slack-notify) from 2.2.0 to 2.2.1.
- [Release notes](https://github.com/rtcamp/action-slack-notify/releases)
- [Commits](https://github.com/rtcamp/action-slack-notify/compare/v2.2.0...v2.2.1)

---
updated-dependencies:
- dependency-name: rtCamp/action-slack-notify
  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-08-24 08:30:32 +05:45
Dave Berenbaumandpre-commit-ci[bot] 428c094b8d update msg for multiple overlapping outputs (#9862)
* update msg for multiple overlapping outputs

* update msg for multiple overlapping outputs

* fix type checking

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

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

* use sets of stages to fix mypy errors

* fix tests/func/test_add.py::test_try_adding_multiple_overlaps

* [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-08-24 00:06:32 +03:00
Dave Berenbaum fc955c1917 Rename partial add/remove benchmarks (#9868)
* Rename partial add/remove benchmarks

* Update dvc/testing/benchmarks/cli/stories/test_modify_data.py
2023-08-23 19:04:51 +05:45
skshetry 8cb7e4948d tests: dvcfs: use fspath while testing (#9872)
get_file does not support PathLike objects. fsspec does not support them in get_file.

See https://github.com/fsspec/filesystem_spec/issues/896.
Fixes test failures in dvc-webhdfs and dvc-hdfs.
2023-08-23 17:33:22 +05:45
skshetry 8e353f9fce deps: unpin dvc-oss (#9871) 2023-08-23 17:20:41 +05:45
dependabot[bot] a4e2bce5b2 build(deps-dev): bump dvc-oss from 2.19 to 2.20.0 (#9870)
Bumps [dvc-oss](https://github.com/iterative/dvc-oss) from 2.19 to 2.20.0.
- [Release notes](https://github.com/iterative/dvc-oss/releases)
- [Commits](https://github.com/iterative/dvc-oss/compare/2.19.0...2.20.0)

---
updated-dependencies:
- dependency-name: dvc-oss
  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-08-23 08:45:43 +05:45
Ruslan Kuprieiev 736ca9a5e5 cache: check-missing: add type, oid name and oid 2023-08-23 02:07:32 +03:00
Ruslan Kuprieiev cae1b1b647 ls/ls-url: use tqmd instead of humanize.naturalsize
Per https://github.com/iterative/dvc/pull/9857#issuecomment-1683185006
2023-08-22 20:21:08 +03:00
pre-commit-ci[bot]andSaugat Pachhai 3783d65dde [pre-commit.ci] pre-commit autoupdate (#9841)
* [pre-commit.ci] pre-commit autoupdate

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

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

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

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

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

* fix ruff issues

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Saugat Pachhai (सौगात) <[email protected]>
2023-08-22 15:22:57 +05:45
skshetry 0342e4f1c3 fix type hints (#9863) 2023-08-22 14:56:11 +05:45
skshetry fc9af7a976 assert that the test is getting parametrized correctly (#9859)
Otherwise this may end up generating 0 or not enough commands, and passing tests
2023-08-19 13:57:48 +05:45
skshetry fea59cfc52 tests: simplify test parametrization (#9858)
i.e. remove metafunc/pytest_generate_tests
2023-08-19 10:02:31 +05:45
Ruslan Kuprieiev 31ffce4fba ls/ls-url: use humanize.naturalsize instead of tqdm
Currently we get odd formatting like

```
456    .devcontainer.json
139    .dvcignore
61.0   .gitattributes
96.0   .github
7.00   .gitignore
5.76k  README.md
160    data
1.84k  dvc.lock
519    dvc.yaml
4.00   foo
96.0   models
96.0   notebooks
225    params.yaml
46.0   requirements.txt
128    results
160    src
```

(notice stuff like 46.0)

and there doesn't seem to be a simple way to fix that in tqdm, in addition to
the general feeling that we are abusing tqdm's util for this.

So let's just use `humanize`, which gets us a more pleasing result:

```
456B  .devcontainer.json
139B  .dvcignore
61B   .gitattributes
96B   .github
7B    .gitignore
5.8K  README.md
160B  data
1.8K  dvc.lock
519B  dvc.yaml
4B    foo
96B   models
96B   notebooks
225B  params.yaml
46B   requirements.txt
128B  results
160B  src
```
2023-08-18 02:54:31 +03:00
dependabot[bot] 2da959acd1 build(deps-dev): update pytest-docker requirement from <2,>=1 to >=1,<3
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...v2.0.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2023-08-18 00:41:47 +03:00
Ruslan Kuprieiev 72a42fce20 Update local.py 2023-08-18 00:31:52 +03:00
Dave BerenbaumandRuslan Kuprieiev 1cbe64face Update tests/unit/command/test_experiments.py
Co-authored-by: Ruslan Kuprieiev <[email protected]>
2023-08-18 00:31:52 +03:00
dberenbaum 057b0112ea exp: make run/save less noisy 2023-08-18 00:31:52 +03:00
Ruslan Kuprieiev 1805edbd32 import: set core.remote when both --remote and --remote-config are specified
From https://github.com/iterative/dvc/pull/9853#issuecomment-1682797083
2023-08-18 00:16:33 +03:00
Ruslan Kuprieiev 1723abcd79 ls/ls-url: introduce --size
I really need it only for studio right now, but this is really nice to have in a daily life
as well.

E.g.

```
$ dvc ls-url . --size
108    Metadata-and-Results.dvc
97     AudioMP3.dvc
97     AudioWAV.dvc
12181  README.md
54     .gitignore
168    .gitattributes
99     VideoFlash.dvc
312    LICENSE.txt
139    .dvcignore
96     AudioMP3
96     Asset
192    .dvc
64     Metadata-and-Results
64     AudioWAV
192    Scripts
64     VideoFlash
384    .git
96     .vscode
```

```
$ dvc list . --size
139    .dvcignore
168    .gitattributes
54     .gitignore
96     .vscode
96     Asset
96     AudioMP3
97     AudioMP3.dvc
64     AudioWAV
97     AudioWAV.dvc
312    LICENSE.txt
64     Metadata-and-Results
108    Metadata-and-Results.dvc
12181  README.md
192    Scripts
64     VideoFlash
99     VideoFlash.dvc
```
2023-08-18 00:08:48 +03:00
Ruslan Kuprieiev 3081c60414 dep: repo: handle dict as config (#9853) 2023-08-17 19:37:04 +03:00
dependabot[bot] 152991d878 build(deps-dev): bump mypy from 1.4.1 to 1.5.1 (#9852)
Bumps [mypy](https://github.com/python/mypy) from 1.4.1 to 1.5.1.
- [Commits](https://github.com/python/mypy/compare/v1.4.1...v1.5.1)

---
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-08-17 18:43:31 +05:45
skshetry 8098fd109d plots: fix caching of legacy format (#9849)
* plots: fix caching of legacy format

* deps: bump dvc-data minversion to 2.15.0
2023-08-17 07:40:07 +05:45
Ruslan Kuprieiev f0c1ff0219 fs: make _prepare_credentails/fs_args lazy (#9848)
* tests: unit: move s3 tests to dvc-s3

* deps: bump dvc-data to 2.14.0

* tests: unit: move azure tests to dvc-azure
2023-08-16 05:51:20 +03:00
Ruslan Kuprieiev 27c548575c pyinstaller: declare dvc_ssh as hidden import (#9840)
Likely with removal of dvc-machine pyinstaller stopped picking up dvc_ssh automatically, so we need to declare it explicitly just like the rest of plugins.
2023-08-13 00:26:43 +03:00
Dave Berenbaum 79cc28501b don't unprotect outs if not cached (#9838)
* don't unprotect outs if not cached

* fix tests
2023-08-11 18:12:25 +03:00
Peter Rowlands a051b8b60f remove support for dvc machine 2023-08-11 14:42:52 +09:00
Ruslan Kuprieiev bee04c6d5f deps: bump dvc-data to 2.13.1
Fixes #9733
2023-08-11 05:53:19 +03:00
Tibor MachandTibor Mach 04e891cef9 exp: Unify dvc exp list and show output (#9808)
* use _describe in exp list

* fix pre-commit checks

* simplistic fix

* single _describe, too complex

* slightly simpler

* even simpler

* simple, but with regex

* less substitutions

* describe public

* name handling in UI (tests need updating)

* fixed old tests

* lazy imports

---------

Co-authored-by: Tibor Mach <[email protected]>
2023-08-10 19:23:27 +09:00
Peter Rowlands f5dda2d3cc deps: bump scmrepo minver to 1.2.1 2023-08-10 19:21:06 +09:00
Ruslan Kuprieiev e9545c8b55 fetch: merge config instead of updating
Update will replace sections instead of merging them together, which
is not desirable here.

Related https://github.com/iterative/studio/pull/6541
2023-08-10 04:52:08 +03:00
Ruslan Kuprieiev 09cfd911bb config: use local config from local_dvc_dir
This used to work like so but by accident, when we didn't reload config
at all when collecting other revisions.

Regression from https://github.com/iterative/dvc/pull/9758
2023-08-10 04:36:42 +03:00
Ruslan Kuprieiev 4f61a63afd config: resolve: handle ~ home dir
Fixes #9822
2023-08-10 03:28:32 +03:00
Ruslan Kuprieiev c71b07b996 index: plot_keys: relparts -> parts
`_plot_sources` is messed up and needs a rewrite hard, but this will
do for now for studio purposes.

Related https://github.com/iterative/studio/pull/6541
2023-08-08 17:14:28 +03:00
Ruslan Kuprieiev f4c0946f65 repo: cache: support some view options 2023-08-08 01:09:50 +03:00
Ruslan Kuprieiev e35fb76dac repo: add basic cache_check_missing 2023-08-07 22:23:49 +03:00
Ruslan Kuprieiev 722839dda6 fetch: add basic onerror
Related https://github.com/iterative/studio/pull/6541
2023-08-07 03:24:39 +03:00
Ruslan Kuprieiev d5533348f7 deps: bump dvc-data to 2.12.0 2023-08-07 02:42:41 +03:00
dberenbaum 5d2ca11557 exp: abstract random name gen from repo 2023-08-06 13:13:11 +02:00
009b238807 Feature/hydra config modules (#9783)
* hydra: enable config modules

Lets the user specify Hydra configs in installed Python modules as well
as inside directories. Adds `hydra.config_module` to `.dvc/config`.

Fixes #9740

old tests green

tests for config modules

* hydra: fix linter errors

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

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

* hydra: fix mypy error

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

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

* hydra: fix tests

* Update dvc/utils/hydra.py

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

* address comments

* convince mypy not to worry

* split long line

* fix test

* check for raised error in test

---------

Co-authored-by: Dom Miketa <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: David de la Iglesia Castro <[email protected]>
2023-08-06 10:39:09 +02:00
Ruslan Kuprieiev 43b371c43a deps: bump dvc-data to 2.11.0 2023-08-04 15:42:25 +03:00
skshetry d1160a28e0 dvc.yaml: add matrix-do (#9725)
* dvc.yaml: add matrix-do

* remove docs

* no do in matrix

* add types
2023-08-04 13:29:15 +05:45
skshetry bc86fa0643 completion: fix broken completion generation for zsh (#9797)
`dvc commit --help` was also completely broken, which is also fixed.

This adds unit tests for completion generation for both zsh/bash, and
tests that --help works for all commands.
2023-08-04 09:21:01 +05:45
Saugat Pachhai (सौगात) 4067a86926 fix flaky test for repro --keep-going and --ignore 2023-08-04 05:46:58 +03:00
Ruslan Kuprieiev 9d1d51caa7 deps: bump dvc-data to 2.10.0 2023-08-04 03:40:36 +03:00
Peter Rowlands 761422638f output: add progress for commit 2023-08-03 18:19:19 +09:00
Peter Rowlands d01e2355d4 push/pull: generate explicit progress bars for dvc-data transfers 2023-08-03 18:19:19 +09:00
Peter Rowlands aa94bfd66b deps: bump dvc-data to 2.9.1 2023-08-03 18:19:19 +09:00
skshetry b9b9f91084 ruff: enable autofix (#9790) 2023-08-03 11:08:29 +05:45
Ruslan Kuprieiev 8a382f51fd deps: bump dvc-data to 2.9.0 2023-08-03 02:05:14 +03:00
Ruslan Kuprieiev b235487ecc ls/import/get: introduce --remote and --remote-config
Fixes #9656
2023-08-02 16:28:16 +03:00
pre-commit-ci[bot]andSaugat Pachhai 502b3df6c9 [pre-commit.ci] pre-commit autoupdate (#9756)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/asottile/pyupgrade: v3.9.0 → v3.10.1](https://github.com/asottile/pyupgrade/compare/v3.9.0...v3.10.1)
- [github.com/astral-sh/ruff-pre-commit: v0.0.278 → v0.0.281](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.278...v0.0.281)
- [github.com/PyCQA/flake8: 6.0.0 → 6.1.0](https://github.com/PyCQA/flake8/compare/6.0.0...6.1.0)

* fix issues reported by ruff

- also remove redundant flake8 plugins
- remove beautysh as we don't have any scripts now
- remove pyupgrade as it is redundant due to ruff

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Saugat Pachhai (सौगात) <[email protected]>
2023-08-02 18:08:30 +05:45
dependabot[bot] 5c2fd67e6d build(deps-dev): bump pylint from 2.17.4 to 2.17.5 (#9769)
Bumps [pylint](https://github.com/pylint-dev/pylint) from 2.17.4 to 2.17.5.
- [Release notes](https://github.com/pylint-dev/pylint/releases)
- [Commits](https://github.com/pylint-dev/pylint/compare/v2.17.4...v2.17.5)

---
updated-dependencies:
- dependency-name: pylint
  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-07-31 16:35:40 +05:45
Fabian Zills ed34c0f0bd dvc commit update description (#9760)
* Update commit.py

* fix formatting
2023-07-31 08:45:52 +00:00
Ruslan Kuprieiev 7c60dfd3c5 deps: bump dvc-data to >=2.8.1 2023-07-30 04:18:09 +03:00
Ruslan Kuprieiev 225814fdde dvc: don't use realpath where not needed 2023-07-30 02:24:00 +03:00
Ruslan Kuprieiev ca0ad239f7 deps: bump dvc-data to 0.28.0 2023-07-28 01:37:46 +03:00
Ruslan Kuprieiev f57ac609e9 deps: bump dvc-data to 0.27.1 2023-07-27 20:44:34 +03:00
Ruslan Kuprieiev cc4f505f99 api: open/read: introduce remote_config 2023-07-26 16:29:14 +03:00
Ruslan Kuprieiev 4d011718a6 out: str: don't try to relpath paths outside of the repo
This is not very useful overall, but also results in a ValueError from
relpath on windows if your repo and output are on different drives.
2023-07-25 23:49:27 +03:00
Ruslan Kuprieiev 9c89841637 fetch: support passing config
For now not exposing it in CLI since there is no need/use yet.

Requirement for https://github.com/iterative/studio/pull/6541
2023-07-25 20:03:38 +03:00
Peter Rowlands 92cae74512 exp.collect: strip metrics from queued experiments 2023-07-25 20:57:15 +09:00
Ruslan Kuprieiev 73dad4780f repo: brancher: reset parsed fields
Related #9754 and fixes `dvc fetch`.
2023-07-25 14:00:33 +03:00
Ruslan Kuprieiev 65ba9ba527 fs: get_cloud_fs: accept config instead of repo
Config is a more immutable thing and using `repo` is confusing and might be error
prone in the bigger picture.

Related to #9754
2023-07-25 00:58:47 +03:00
Peter Rowlands 7806afd4ad fs: prefer generic.copy over fsspec fs.get() 2023-07-24 17:01:34 +09:00
Ivan Shcheklein a9d0ac2ba9 fix(exceptions): add parent ctr init in InvalidArgumentError 2023-07-21 08:27:53 +02:00
Ruslan Kuprieiev 41df96fcdb cli: ls/import/get: fix help
Leftover from https://github.com/iterative/dvc/pull/9747
2023-07-20 21:53:14 +03:00
Ruslan Kuprieiev 53ec0fa41d ls/import/get: introduce --config
Adds support for `--config` to all three commands. For `ls` and `get`, it is
a purely runtime option that will merge the config you provide with target
repo's configs (similar to how .dvc/config.local works). E.g.

```
$ cat myconfig
[remote "myremote"]
    access_key_id = 12345
    secret_access_key = 98765

$ dvc get https://github.com/efiop/mydataregistry recent-grads.csv --config myconfig
```

In case of `dvc import`, the semantics are the same, but the value is also
recorded in the resulting dvcfile. E.g.

```
$ dvc import https://github.com/efiop/mydataregistry recent-grads.csv --config myconfig
...
$ cat recent-grads.csv.dvc
md5: 85c86eeb3afa6d1f7d11beedc644d28e
frozen: true
deps:
- path: recent-grads.csv
  repo:
    url: https://github.com/efiop/mydataregistry
    config: myconfig
    rev_lock: af22e06bbfe34d62a140437abf32f48b535944a7
outs:
- md5: f447e23a170a9f25a7799f069a1ba934
  size: 26872
  hash: md5
  path: recent-grads.csv
```

This is the most general and powerful mechanism that we can give, that works
for everything from reconfiguring your default remote to completely changing
your remote layout and beyond (will be handy in the future).

Fixes #2466
2023-07-20 18:41:10 +03:00
Dave BerenbaumandRuslan Kuprieiev 41194b7975 dvcfs: handle NotADirectoryError from datafs.ls (#9746)
* dvc_fs: ignore NotADirectoryError in ls

* deps: bump dvc-data to >= 2.6.0

* add test

---------

Co-authored-by: Ruslan Kuprieiev <[email protected]>
2023-07-19 20:54:55 +03:00
skshetry ea5f7d40c1 ruff: enable a few more rules, and merge tests' config with the main config (#9739) 2023-07-15 14:33:34 +05:45
dependabot[bot] eb06351062 build(deps-dev): bump mypy from 1.3.0 to 1.4.1 (#9664)
Bumps [mypy](https://github.com/python/mypy) from 1.3.0 to 1.4.1.
- [Commits](https://github.com/python/mypy/compare/v1.3.0...v1.4.1)

---
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-07-14 20:30:47 +05:45
pre-commit-ci[bot]andSaugat Pachhai ca3704997b [pre-commit.ci] pre-commit autoupdate (#9632)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/asottile/pyupgrade: v3.3.1 → v3.9.0](https://github.com/asottile/pyupgrade/compare/v3.3.1...v3.9.0)
- https://github.com/charliermarsh/ruff-pre-commit → https://github.com/astral-sh/ruff-pre-commit
- [github.com/astral-sh/ruff-pre-commit: v0.0.261 → v0.0.277](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.261...v0.0.277)
- [github.com/codespell-project/codespell: v2.2.4 → v2.2.5](https://github.com/codespell-project/codespell/compare/v2.2.4...v2.2.5)

* fix ruff complaints

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Saugat Pachhai (सौगात) <[email protected]>
2023-07-14 20:11:15 +05:45
Saugat Pachhai (सौगात) f1764bdc77 stage add: remove --external 2023-07-14 15:38:57 +03:00
skshetry eb212a7ba3 repro: introduce --keep-going/--ignore to continue on error (#9712)
* repro: introduce --keep-going/--ignore to continue on error

`-k`/`--keep-going` will skip all dependents of a stage if it fails.
It'll still try to reproduce the other dependencies of the pending
targets, before it exits with a non-zero status.

`--ignore-errors` will treat any error during stage execution as a
success, and will continue executing other dependencies as if it
succeeded. Exits with 0 even on failure.

Also fixes:
* --force-downstream used to force everything even if one upstream nodes changed.
  This used to work when we supported a single targets, but there could be independent
  pipelines, so  we should not force everything if any node changes.
  Now we'll force reproduce the stage only if one of its dependency changed upstream
  or it's upstream node was also forced.

* Makes `--downstream` more correct, in relation to `--allow-missing` and `--pull`.
  Now we use an active graph for downstream/upstream logic instead of a subgraph.
  Note that this does not affect the downstream logic though, as dvc always goes
  downstream, so subgraph was no different than active graph.
  The subgraph only did not have the upstream nodes.

* add tests

* add spacing in the message
2023-07-12 21:52:41 +05:45
skshetry 4c8f539c56 plots: log failures during datapoint conversions (#9726)
Fixes #9441.

Note that we do merge props, and use that for all revision. So you may
see warning for multiple revisions.
2023-07-12 19:14:31 +05:45
skshetry 45aefc8423 parsing: pass config instead of loading every time (#9729) 2023-07-12 17:22:42 +05:45
Peter Rowlands 67a747a356 stage.cache: override both legacy+local cache link types on save/commit 2023-07-12 17:17:49 +09:00
Peter Rowlands a5dda1b8ad open_repo: return without entering context for both local and erepo 2023-07-12 14:45:11 +09:00
Peter Rowlands dd4035bc05 repo: add get_data_index_entry to handle subrepo lookup 2023-07-12 14:45:11 +09:00
Peter Rowlands f69b3533e4 erepo: drop dvcx for_write support 2023-07-12 14:45:11 +09:00
Ruslan Kuprieiev 1b2291ca83 fetch: catch collection errors in revisions
https://github.com/iterative/studio/pull/6541
2023-07-11 04:03:26 +03:00
Ruslan Kuprieiev 4de4f0fd65 fetch: introduce --type metrics/plots
Needed for https://github.com/iterative/studio/pull/6541 but is also generally useful.
2023-07-10 15:24:30 +03:00
Ruslan Kuprieiev 2ef2caafd3 deps: bump dvc-data to 2.5.0 2023-07-09 20:30:35 +03:00
Ruslan Kuprieiev adcefbec66 output: pass explicit callback to hashfile.build() 2023-07-09 17:40:49 +03:00
Ruslan Kuprieiev 2102ba8465 output: fix formatting (#9716)
Regression from https://github.com/iterative/dvc/pull/9715
2023-07-09 19:50:00 +05:45
skshetry d736d0a3ef output: checkout: use f-string (#9715)
Regression from https://github.com/iterative/dvc/pull/9673.
2023-07-09 11:49:22 +05:45
dberenbaum 0d1f1f6436 update link for external data sources 2023-07-05 19:42:07 +03:00
Ruslan Kuprieiev 63de16ced1 readme: point to dvc.org for packages
We no longer attach bin packages to github releases, so old link is completely misleading.
2023-07-04 18:47:32 +03:00
Ruslan Kuprieiev 2f6c308c84 repo: site_cache_dir: take major version into account
So that different major dvc versions don't use the same cache path.
2023-07-04 18:24:20 +03:00
skshetry c8d180d2b0 repro: move code around (#9701)
emphasizing on building the graph correctly,
and preparing for being able to work with graph directly.
2023-07-04 21:02:53 +05:45
skshetry d69972d199 repro: ignore --downstream if used together with --pipeline (#9692)
* repro: no need to build pipelines for --all-pipeline

* repro: ignore --downstream when using --pipeline
2023-07-04 19:03:09 +05:45
Peter Rowlands eb1ed40228 repo.open_repo: use abspath for local fs URL 2023-07-04 12:28:09 +03:00
Peter Rowlands 120289128e dvcfs: cleanup subrepo contexts on close() 2023-07-04 12:28:09 +03:00
Peter Rowlands a607e7b3d7 dvcfs: return md5-dos2unix in info 2023-07-04 12:28:09 +03:00
Peter Rowlands 86e199c6f2 api.get_url: use index remote storage for getting remote URL 2023-07-04 12:28:09 +03:00
Ruslan Kuprieiev 8661abf224 fetch: introduce --max-size (#9696)
Needed for iterative/studio#4782
2023-07-04 02:17:53 +03:00
Jon Crallandskshetry 541def388b environment variable to show debug options (#9693)
* environment variable to show debug options

* lint

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

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

* Add message to debug opts

* spelling

* Add argument group and move cprofile-dump

line length

linelen

lint

* use DVC_DEBUG instead of DVC_SHOW_DEBUG_OPTIONS

* use -v instead of environ

* Update dvc/_debug.py

Co-authored-by: skshetry <[email protected]>

* fix

---------

Co-authored-by: skshetry <[email protected]>
2023-07-04 01:35:33 +03:00
Ruslan Kuprieiev 58eb145b6a Delete hook-google_compute_engine.logger.py 2023-07-03 22:13:22 +03:00
Ruslan Kuprieiev 16465e0b63 Delete hook-dvc.system.py 2023-07-03 22:13:22 +03:00
Ruslan Kuprieiev 6614efd35a Delete hook-dvc.tree.gs.py 2023-07-03 22:13:22 +03:00
skshetry 2becfd9344 refactor repro: split logic for planning stages to run for repro (#9690)
The planning logic has now been moved to `plan_repro` api, that allows us
to test without creating stages.
2023-07-03 17:53:48 +05:45
skshetry b7b6642428 repro: do not autostage stage files (#9689)
The logic is now moved to experiments, where it tracks all the stage related files
itself. The `_get_stage_files()` has been modified to also return lockfile path,
so that it can be staged, and has been moved to `stage/utils.py`, and out of `reproduce.py`.

Fixes #9688.
2023-07-03 14:42:22 +05:45
skshetry fea2e6bb30 repro: simplify _reproduce_stages (#9687) 2023-07-03 12:25:25 +05:45
skshetry bfa5b1a0c3 repro: make stages run in order of the graph (#9675)
* repro: make stages run in order of the graph.

In case of --single-item, this means it'll run in the order of arguments passed.
For --downstream, this fixes running of stage out of order when multiple arguments are passed.
For normal case, this enforces the same logic, so that they are never run out-of-order.

* add tests

* tests: repro: add test for --single-item with multiple targets

* graph: support when no nodes are provided
2023-07-03 11:44:51 +05:45
Peter Rowlands 24d9a05d69 fetch: warn if cloud-versioned file has no version ID 2023-07-03 14:05:15 +09:00
skshetry 1f31a9c696 bandit: add exclusion for dvc/repo/experiments/utils.py back (#9686)
See https://github.com/PyCQA/bandit/pull/940.

Will not be needed when new version of bandit comes.
2023-07-03 09:56:45 +05:45
skshetry 097e48fcc9 remove scripts/ related suppresses (#9685) 2023-07-03 09:46:27 +05:45
skshetry 453a8f9516 ci: use kebab-case (#9684) 2023-07-03 09:16:53 +05:45
skshetry 9f59d8652e remove build scripts and scripts directory (#9683) 2023-07-03 09:05:06 +05:45
Ruslan Kuprieiev b1834f34e7 deps: limit flufl-lock to <8 (#9681)
Looks like flufl-lock switched to pdm in 8.0 which triggered a

```
pkg_resources.DistributionNotFound: The 'flufl.lock>=5' distribution was not found and is required by dvc
```

error in multiple scenarious. Can't put my finger on anything specific
in flufl-lock yet, so will just limit its version for now.

Fixes https://github.com/iterative/dvc/issues/9654
2023-07-03 03:13:50 +03:00
Ruslan Kuprieiev 536273ab1e build: pyinstaller: use __pyinstaller (#9680) 2023-07-03 01:47:31 +03:00
Ruslan Kuprieiev 5cff4891fd deps: bump dvc-data to >=2.3.2 (#9679)
Fixes #9462
Fixes #9512
2023-07-02 23:09:27 +03:00
Jon Crall e29da6eae1 Add doctest for dagascii.draw (#9678) 2023-07-01 20:36:14 +03:00
390 changed files with 9627 additions and 5785 deletions
-19
View File
@@ -1,19 +0,0 @@
[flake8]
ignore=
# Whitespace before ':'
E203
# Too many leading '#' for block comment
E266
# Line break occurred before a binary operator
W503
# unindexed parameters in the str.format, see:
# https://pypi.org/project/flake8-string-format/
P1
# use `!r` instead of surrounding by quotes
B028
max_line_length = 88
max-complexity = 10
select = B,C,E,F,W,T4,B902,T,P,TYP
show_source = true
count = true
min_python_version = 3.8.0
+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
+92
View File
@@ -0,0 +1,92 @@
name: Build and upload package
on:
push:
branches:
- main
release:
types:
- published
workflow_dispatch:
env:
FORCE_COLOR: 1
permissions:
contents: read
jobs:
build:
name: Build & Verify package
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: 3.8
- run: python -m pip install --upgrade pip setuptools_scm build twine
- name: Force version for Test PyPI upload
if: ${{ !startsWith(github.ref, 'refs/tags') }}
run: echo version=$(python -m setuptools_scm | awk -F+ '{print $1}' | tail -1) >> $GITHUB_ENV
- run: echo 'PKG = "pip"'>dvc/_build.py
- run: python -m build
env:
SETUPTOOLS_SCM_PRETEND_VERSION_FOR_DVC: ${{ env.version }}
- run: twine check --strict dist/*
- uses: actions/upload-artifact@v4
with:
name: Packages
path: dist/
test-pypi-publish:
name: Publish dev package to test.pypi.org
runs-on: ubuntu-latest
needs: build
if: ${{ github.event.action == 'published' || (github.repository == 'iterative/dvc' && github.event_name == 'push' && github.ref == 'refs/heads/main') }}
environment:
name: test-pypi
url: https://test.pypi.org/p/dvc
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
name: Packages
path: dist
- name: Upload package to Test PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
skip-existing: true
pypi-publish:
name: Publish released package to pypi.org
runs-on: ubuntu-latest
needs: build
if: github.event.action == 'published'
environment:
name: pypi
url: https://pypi.org/p/dvc
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
name: Packages
path: dist
- name: Upload package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
+4 -3
View File
@@ -28,12 +28,13 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
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
-51
View File
@@ -1,51 +0,0 @@
name: Build packages
on:
push:
branches:
- main
release:
types: [released, prereleased]
workflow_dispatch:
permissions: # added using https://github.com/step-security/secure-workflows
contents: read
jobs:
pip:
runs-on: ubuntu-20.04
permissions:
id-token: write
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/setup-python@v4
with:
python-version: 3.8
- name: Install
run: pip install --upgrade pip wheel
- name: Force version for Test PyPI uploads
if: ${{ !startsWith(github.ref, 'refs/tags') }}
run: |
pip install setuptools_scm
echo version=$(python -m setuptools_scm | awk -F+ '{print $1}' | tail -1) >> $GITHUB_ENV
- name: Build packages
run: ./scripts/build_package.sh
env:
SETUPTOOLS_SCM_PRETEND_VERSION: ${{ env.version }}
- name: Publish packages to PyPI
if: github.event_name == 'release'
uses: pypa/gh-action-pypi-publish@release/v1
- name: Publish to Test PyPI
if: ${{ github.event_name == 'release' || (github.event_name == 'push' && github.ref == 'refs/heads/main') }}
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository_url: https://test.pypi.org/legacy/
skip_existing: true
+3 -3
View File
@@ -23,18 +23,18 @@ jobs:
plugin: ["dvc-s3"]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
path: dvc
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
repository: iterative/${{ matrix.plugin }}
ref: main
path: ${{ matrix.plugin }}
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.pyv }}
cache: pip
+4 -4
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.8", "3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v3
- 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
@@ -66,7 +66,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Slack Notification
uses: rtCamp/[email protected].0
uses: rtCamp/[email protected].1
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_COLOR: ${{ job.status }}
+7 -50
View File
@@ -2,11 +2,11 @@ default_language_version:
python: python3
ci:
skip: [mypy, pylint, dvc-pre-commit]
skip: [mypy, dvc-pre-commit]
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: check-added-large-files
- id: check-case-conflict
@@ -17,57 +17,21 @@ repos:
args: ['--assume-in-merge']
- id: check-toml
- id: check-yaml
- id: debug-statements
exclude: "dvc/_debug.py"
- id: end-of-file-fixer
- id: mixed-line-ending
- id: sort-simple-yaml
- id: trailing-whitespace
- repo: https://github.com/lovesegfault/beautysh
rev: v6.2.1
hooks:
- id: beautysh
args: ["-i", "2"]
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
hooks:
- id: pyupgrade
args:
- --py38-plus
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: 'v0.0.261'
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.1.8'
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
- repo: https://github.com/codespell-project/codespell
rev: v2.2.4
rev: v2.2.6
hooks:
- id: codespell
additional_dependencies: ["tomli"]
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
hooks:
- id: flake8
additional_dependencies:
- flake8-bugbear
- flake8-comprehensions
- flake8-debugger
- flake8-string-format
- flake8-typing-imports
- repo: https://github.com/PyCQA/bandit
rev: '1.7.5'
hooks:
- id: bandit
args: ["-c", "pyproject.toml", "--quiet", "--format=custom"]
files: ^dvc/
additional_dependencies: [".[toml]"]
- repo: local
hooks:
- id: mypy
@@ -77,13 +41,6 @@ repos:
language: system
types: [python]
require_serial: true
- id: pylint
name: pylint
entry: pylint
files: ^dvc/
language: system
types: [python]
require_serial: true
- hooks:
- args:
- git-hook
+4 -4
View File
@@ -90,8 +90,8 @@ Their results can be filtered and compared based on hyperparameters and metrics,
.. _`VS Code Extension`:
Visual Studio Code Extension
============================
VS Code Extension
=================
|VS Code|
@@ -280,8 +280,8 @@ Barrak, A., Eghan, E.E. and Adams, B. `On the Co-evolution of ML Pipelines and S
:target: https://pypi.org/project/dvc
:alt: PyPI Downloads
.. |Packages| image:: https://img.shields.io/github/v/release/iterative/dvc?label=deb|pkg|rpm|exe&logo=GitHub
:target: https://github.com/iterative/dvc/releases/latest
.. |Packages| image:: https://img.shields.io/badge/deb|pkg|rpm|exe-blue
:target: https://dvc.org/doc/install
:alt: deb|pkg|rpm|exe
.. |DOI| image:: https://img.shields.io/badge/DOI-10.5281/zenodo.3677553-blue.svg
+5
View File
@@ -0,0 +1,5 @@
import os
def get_hook_dirs():
return [os.path.dirname(__file__)]
@@ -1 +1,2 @@
# ruff: noqa: N999
hiddenimports = ["win32timezone"]
@@ -1,4 +1,5 @@
# pylint: disable=import-error
# ruff: noqa: N999
from PyInstaller.utils.hooks import collect_submodules, is_module_or_submodule
# Celery dynamically imports most celery internals at runtime
@@ -1,4 +1,5 @@
from PyInstaller.utils.hooks import copy_metadata # pylint:disable=import-error
# ruff: noqa: N999
from PyInstaller.utils.hooks import copy_metadata
# needed for `dvc doctor` to show dep versions
datas = copy_metadata("adlfs", recursive=True)
@@ -21,6 +22,7 @@ hiddenimports = [
"dvc_hdfs",
"dvc_oss",
"dvc_s3",
"dvc_ssh",
"dvc_webdav",
"dvc_webhdfs",
# https://github.com/pypa/setuptools/issues/1963
@@ -0,0 +1,4 @@
# ruff: noqa: N999
from PyInstaller.utils.hooks import copy_metadata
datas = copy_metadata("flatten-dict")
@@ -1,4 +1,5 @@
# pylint: disable=import-error
# ruff: noqa: N999
from PyInstaller.utils.hooks import collect_submodules
hiddenimports = collect_submodules("dvc_task")
@@ -1 +1,2 @@
# ruff: noqa: N999
hiddenimports = ["fsspec.implementations.memory"]
+4
View File
@@ -0,0 +1,4 @@
# ruff: noqa: N999
from PyInstaller.utils.hooks import copy_metadata
datas = copy_metadata("pydrive2")
+118 -60
View File
@@ -14,7 +14,7 @@ def viztracer_profile(
log_async: bool = False,
):
try:
import viztracer # pylint: disable=import-error
import viztracer
except ImportError:
print("Failed to run profiler, viztracer is not installed") # noqa: T201
yield
@@ -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
@@ -36,7 +37,7 @@ def yappi_profile(
separate_threads: Optional[bool] = False,
):
try:
import yappi # pylint: disable=import-error
import yappi
except ImportError:
print("Failed to run profiler, yappi is not installed") # noqa: T201
yield
@@ -45,38 +46,40 @@ 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() # pylint:disable=no-member
if ctx_id is None:
threads.print_all() # pylint:disable=no-member
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
def instrument(html_output=False):
"""Run a statistical profiler"""
try:
from pyinstrument import Profiler # pylint: disable=import-error
from pyinstrument import Profiler
except ImportError:
print("Failed to run profiler, pyinstrument is not installed") # noqa: T201
yield
@@ -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,22 +107,23 @@ 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
def debug():
try:
yield
except Exception: # pylint: disable=broad-except
except Exception:
try:
import ipdb as pdb # noqa: T100, pylint: disable=import-error
import ipdb as pdb # noqa: T100
except ImportError:
import pdb # type: ignore[no-redef] # noqa: T100
pdb.post_mortem()
@@ -145,7 +151,8 @@ def show_stack():
signal.signal(signal.SIGQUIT, _sigshow)
try:
signal.signal(signal.SIGINFO, _sigshow) # only available on macOS
# only available on macOS
signal.signal(signal.SIGINFO, _sigshow) # type: ignore[attr-defined]
except AttributeError:
pass
yield
@@ -198,33 +205,84 @@ def debugtools(args: Optional["Namespace"] = None, **kwargs):
def add_debugging_flags(parser):
from argparse import SUPPRESS
parser.add_argument("--cprofile", action="store_true", default=False, help=SUPPRESS)
parser.add_argument("--yappi", action="store_true", default=False, help=SUPPRESS)
# For detailed info see:
# https://github.com/iterative/dvc/wiki/Debugging,-Profiling-and-Benchmarking-DVC
args, _ = parser.parse_known_args()
verbose = args.verbose
def debug_help(msg):
if verbose:
return msg
return SUPPRESS
parser = parser.add_argument_group("debug options")
parser.add_argument(
"--cprofile",
action="store_true",
default=False,
help=debug_help("Generate cprofile data for tools like snakeviz / tuna"),
)
parser.add_argument(
"--cprofile-dump", help=debug_help("Location to dump cprofile file")
)
parser.add_argument(
"--yappi",
action="store_true",
default=False,
help=debug_help(
"Generate a callgrind file for use with tools like "
"kcachegrind / qcachegrind"
),
)
parser.add_argument(
"--yappi-separate-threads",
action="store_true",
default=False,
help=SUPPRESS,
help=debug_help("Generate one callgrind file per thread"),
)
parser.add_argument(
"--viztracer", action="store_true", default=False, help=SUPPRESS
)
parser.add_argument("--viztracer-depth", type=int, help=SUPPRESS)
parser.add_argument(
"--viztracer-async", action="store_true", default=False, help=SUPPRESS
)
parser.add_argument("--cprofile-dump", help=SUPPRESS)
parser.add_argument("--pdb", action="store_true", default=False, help=SUPPRESS)
parser.add_argument(
"--instrument", action="store_true", default=False, help=SUPPRESS
"--viztracer",
action="store_true",
default=False,
help=debug_help("Generate a viztracer file for use with vizviewer"),
)
parser.add_argument(
"--instrument-open", action="store_true", default=False, help=SUPPRESS
"--viztracer-depth",
type=int,
help=debug_help("Set viztracer maximum stack depth"),
)
parser.add_argument(
"--viztracer-async",
action="store_true",
default=False,
help=debug_help("Treat async tasks as threads"),
)
parser.add_argument(
"--pdb",
action="store_true",
default=False,
help=debug_help("Drop into the pdb/ipdb debugger on any exception"),
)
parser.add_argument(
"--instrument",
action="store_true",
default=False,
help=debug_help("Use pyinstrument CLI profiler"),
)
parser.add_argument(
"--instrument-open",
action="store_true",
default=False,
help=debug_help("Use pyinstrument web profiler"),
)
parser.add_argument(
"--show-stack",
"--ss",
action="store_true",
default=False,
help=SUPPRESS,
help=debug_help(
r"Use Ctrl+T on macOS or Ctrl+\ on Linux to print the stack "
"frame currently executing. Unavailable on Windows."
),
)
+14 -6
View File
@@ -1,10 +1,11 @@
import json
import logging
import os
from .env import DVC_NO_ANALYTICS
from dvc.log import logger
logger = logging.getLogger(__name__)
from .env import DVC_ANALYTICS_ENDPOINT, DVC_NO_ANALYTICS
logger = logger.getChild(__name__)
def collect_and_send_report(args=None, return_code=None):
@@ -34,6 +35,8 @@ def collect_and_send_report(args=None, return_code=None):
with tempfile.NamedTemporaryFile(delete=False, mode="w") as fobj:
json.dump(report, fobj)
logger.trace("Saving analytics report to %s", fobj.name)
daemon(["analytics", fobj.name])
@@ -65,7 +68,7 @@ def send(path):
"""
import requests
url = "https://analytics.dvc.org"
url = os.environ.get(DVC_ANALYTICS_ENDPOINT, "https://analytics.dvc.org")
headers = {"content-type": "application/json"}
with open(path, encoding="utf-8") as fobj:
@@ -73,11 +76,16 @@ def send(path):
report.update(_runtime_info())
logger.debug("uploading report to %s", url)
logger.trace("Sending %s to %s", report, url)
try:
requests.post(url, json=report, headers=headers, timeout=5)
except requests.exceptions.RequestException:
logger.debug("failed to send analytics report", exc_info=True)
except requests.exceptions.RequestException as e:
logger.trace("", exc_info=True)
logger.debug("failed to send analytics report %s", str(e))
logger.trace("removing report %s", path)
os.remove(path)
+1 -1
View File
@@ -46,7 +46,7 @@ ANNOTATION_SCHEMA = {
Annotation.PARAM_LABELS: [str],
Annotation.PARAM_META: object,
}
ARTIFACT_SCHEMA = {
ARTIFACT_SCHEMA: Dict[Any, Any] = {
Required(Artifact.PARAM_PATH): str,
**ANNOTATION_SCHEMA, # type: ignore[arg-type]
}
+3 -2
View File
@@ -1,7 +1,7 @@
from dvc.fs.dvc import _DVCFileSystem as DVCFileSystem
from .data import open # pylint: disable=redefined-builtin
from .data import get_url, read
from .artifacts import artifacts_show
from .data import get_url, open, read
from .experiments import exp_save, exp_show
from .scm import all_branches, all_commits, all_tags
from .show import metrics_show, params_show
@@ -10,6 +10,7 @@ __all__ = [
"all_branches",
"all_commits",
"all_tags",
"artifacts_show",
"exp_save",
"exp_show",
"get_url",
+47
View File
@@ -0,0 +1,47 @@
from typing import Any, Dict, Optional
from dvc.repo import Repo
def artifacts_show(
name: str,
version: Optional[str] = None,
stage: Optional[str] = None,
repo: Optional[str] = None,
) -> Dict[str, str]:
"""
Return path and Git revision for an artifact in a DVC project.
The resulting path and revision can be used in conjunction with other dvc.api
calls to open and read the artifact.
Args:
name (str): name of the artifact to open.
version (str, optional): version of the artifact to open. Defaults to
the latest version.
stage (str, optional): name of the model registry stage.
repo: (str, optional): path or URL for the DVC repo.
Returns:
Dictionary of the form:
{
"rev": ...,
"path": ...,
}
Raises:
dvc.exceptions.ArtifactNotFoundError: The specified artifact was not found in
the repo.
"""
if version and stage:
raise ValueError("Artifact version and stage are mutually exclusive.")
repo_kwargs: Dict[str, Any] = {
"subrepos": True,
"uninitialized": True,
}
with Repo.open(repo, **repo_kwargs) as _repo:
rev = _repo.artifacts.get_rev(name, version=version, stage=stage)
with _repo.switch(rev):
path = _repo.artifacts.get_path(name)
return {"rev": rev, "path": path}
+45 -25
View File
@@ -36,25 +36,26 @@ def get_url(path, repo=None, rev=None, remote=None):
NOTE: This function does not check for the actual existence of the file or
directory in the remote storage.
"""
with Repo.open(repo, rev=rev, subrepos=True, uninitialized=True) as _repo:
with _wrap_exceptions(_repo, path):
fs_path = _repo.dvcfs.from_os_path(path)
from dvc.config import NoRemoteError
from dvc_data.index import StorageKeyError
with reraise(FileNotFoundError, PathMissingError(path, repo)):
info = _repo.dvcfs.info(fs_path)
dvc_info = info.get("dvc_info")
if not dvc_info:
raise OutputNotFoundError(path, repo)
dvc_repo = info["repo"] # pylint: disable=unsubscriptable-object
md5 = dvc_info["md5"]
return dvc_repo.cloud.get_url_for(remote, checksum=md5)
repo_kwargs: Dict[str, Any] = {}
if remote:
repo_kwargs["config"] = {"core": {"remote": remote}}
with Repo.open(
repo, rev=rev, subrepos=True, uninitialized=True, **repo_kwargs
) as _repo:
index, entry = _repo.get_data_index_entry(path)
with reraise(
(StorageKeyError, ValueError),
NoRemoteError(f"no remote specified in {_repo}"),
):
remote_fs, remote_path = index.storage_map.get_remote(entry)
return remote_fs.unstrip_protocol(remote_path)
class _OpenContextManager(GCM):
def __init__(self, func, args, kwds): # pylint: disable=super-init-not-called
def __init__(self, func, args, kwds):
self.gen = func(*args, **kwds)
self.func, self.args, self.kwds = ( # type: ignore[assignment]
func,
@@ -66,7 +67,7 @@ class _OpenContextManager(GCM):
raise AttributeError("dvc.api.open() should be used in a with statement.")
def open( # noqa, pylint: disable=redefined-builtin
def open( # noqa: A001
path: str,
repo: Optional[str] = None,
rev: Optional[str] = None,
@@ -74,6 +75,7 @@ def open( # noqa, pylint: disable=redefined-builtin
mode: str = "r",
encoding: Optional[str] = None,
config: Optional[Dict[str, Any]] = None,
remote_config: Optional[Dict[str, Any]] = None,
):
"""
Opens a file tracked in a DVC project.
@@ -117,6 +119,9 @@ def open( # noqa, pylint: disable=redefined-builtin
Mirrors the namesake parameter in builtin `open()`_.
config(dict, optional): config to be passed to the DVC repository.
Defaults to None.
remote_config(dict, optional): remote config to be passed to the DVC
repository.
Defaults to None.
Returns:
_OpenContextManager: A context manager that generatse a corresponding
@@ -213,22 +218,27 @@ def open( # noqa, pylint: disable=redefined-builtin
"mode": mode,
"encoding": encoding,
"config": config,
"remote_config": remote_config,
}
return _OpenContextManager(_open, args, kwargs)
def _open(path, repo=None, rev=None, remote=None, mode="r", encoding=None, config=None):
if remote:
if config is not None:
raise ValueError(
"can't specify both `remote` and `config` at the same time"
)
config = {"core": {"remote": remote}}
def _open(
path,
repo=None,
rev=None,
remote=None,
mode="r",
encoding=None,
config=None,
remote_config=None,
):
repo_kwargs: Dict[str, Any] = {
"subrepos": True,
"uninitialized": True,
"remote": remote,
"config": config,
"remote_config": remote_config,
}
with Repo.open(repo, rev=rev, **repo_kwargs) as _repo:
@@ -264,7 +274,16 @@ def _open(path, repo=None, rev=None, remote=None, mode="r", encoding=None, confi
raise DvcIsADirectoryError(f"'{path}' is a directory") from exc
def read(path, repo=None, rev=None, remote=None, mode="r", encoding=None, config=None):
def read(
path,
repo=None,
rev=None,
remote=None,
mode="r",
encoding=None,
config=None,
remote_config=None,
):
"""
Returns the contents of a tracked file (by DVC or Git). For Git repos, HEAD
is used unless a rev argument is supplied. The default remote is tried
@@ -278,5 +297,6 @@ def read(path, repo=None, rev=None, remote=None, mode="r", encoding=None, config
mode=mode,
encoding=encoding,
config=config,
remote_config=remote_config,
) as fd:
return fd.read()
+1 -1
View File
@@ -53,7 +53,7 @@ def _postprocess(exp_rows):
except ValueError:
exp_row[k] = v_str
if not exp_row[k]:
elif not exp_row[k]:
exp_row[k] = None
return exp_rows
+10 -8
View File
@@ -7,10 +7,6 @@ from funcy import first
from dvc.repo import Repo
def _onerror_raise(exception: Exception, *args, **kwargs):
raise exception
def _postprocess(results):
processed: Dict[str, Dict] = {}
for rev, rev_data in results.items():
@@ -23,7 +19,6 @@ def _postprocess(results):
for file_data in rev_data["data"].values():
for k in file_data["data"]:
counts[k] += 1
for file_name, file_data in rev_data["data"].items():
to_merge = {
(k if counts[k] == 1 else f"{file_name}:{k}"): v
@@ -138,13 +133,17 @@ def metrics_show(
.. _Git revision:
https://git-scm.com/docs/revisions
"""
from dvc.repo.metrics.show import to_relpath
with Repo.open(repo, config=config) as _repo:
metrics = _repo.metrics.show(
targets=targets,
revs=rev if rev is None else [rev],
onerror=_onerror_raise,
on_error="raise",
)
metrics = {
k: to_relpath(_repo.fs, _repo.root_dir, v) for k, v in metrics.items()
}
metrics = _postprocess(metrics)
@@ -382,6 +381,8 @@ def params_show(
https://git-scm.com/docs/revisions
"""
from dvc.repo.metrics.show import to_relpath
if isinstance(stages, str):
stages = [stages]
@@ -389,10 +390,11 @@ def params_show(
params = _repo.params.show(
revs=rev if rev is None else [rev],
targets=targets,
deps=deps,
onerror=_onerror_raise,
deps_only=deps,
on_error="raise",
stages=stages,
)
params = {k: to_relpath(_repo.fs, _repo.root_dir, v) for k, v in params.items()}
params = _postprocess(params)
-1
View File
@@ -2,7 +2,6 @@ from typing import Optional
try:
# file is created during dvc build
# pylint:disable=unused-import
from . import _build # type: ignore[attr-defined, import]
PKG: Optional[str] = _build.PKG # type: ignore[assignment]
+27 -7
View File
@@ -24,10 +24,10 @@ def _get_odb(
if not settings:
return None
cls, config, fs_path = get_cloud_fs(repo, **settings)
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)
@@ -36,17 +36,15 @@ def _get_odb(
class CacheManager:
CACHE_DIR = "cache"
FILES_DIR = "files"
FS_DIR = "fs"
def __init__(self, repo):
self._repo = repo
self.config = config = repo.config["cache"]
self._odb = {}
default = None
if repo and repo.local_dvc_dir:
default = os.path.join(repo.local_dvc_dir, self.CACHE_DIR)
local = config.get("local")
default = self.default_local_cache_dir
if local:
settings = {"name": local}
@@ -76,6 +74,21 @@ class CacheManager:
legacy_odb = _get_odb(repo, settings, hash_name="md5-dos2unix", **kwargs)
self._odb["legacy"] = legacy_odb
@property
def fs_cache(self):
"""Filesystem-based cache.
Currently used as a temporary location to download files that we don't
yet have a regular oid (e.g. md5) for.
"""
from dvc_data.index import FileStorage
return FileStorage(
key=(),
fs=self.local.fs,
path=self.local.fs.join(self.default_local_cache_dir, self.FS_DIR),
)
def _init_odb(self, schemes):
for scheme in schemes:
remote = self.config.get(scheme)
@@ -103,6 +116,13 @@ class CacheManager:
"""
return self.legacy.path
@property
def default_local_cache_dir(self) -> Optional[str]:
repo = self._repo
if repo and repo.local_dvc_dir:
return os.path.join(repo.local_dvc_dir, self.CACHE_DIR)
return None
def migrate_2_to_3(repo: "Repo", dry: bool = False):
"""Migrate legacy 2.x objects to 3.x cache.
@@ -117,7 +137,7 @@ def migrate_2_to_3(repo: "Repo", dry: bool = False):
src = repo.cache.legacy
dest = repo.cache.local
if dry:
oids = list(src._list_oids()) # pylint: disable=protected-access
oids = list(src._list_oids())
ui.write(
f"{len(oids)} files will be re-hashed and migrated to the DVC 3.0 cache "
"location."
+16 -11
View File
@@ -2,15 +2,17 @@
import logging
import sys
from typing import Optional
from dvc.log import logger
# Workaround for CPython bug. See [1] and [2] for more info.
# [1] https://github.com/aws/aws-cli/blob/1.16.277/awscli/clidriver.py#L55
# [2] https://bugs.python.org/issue29288
from typing import Optional
"".encode("idna")
logger = logging.getLogger("dvc")
logger = logger.getChild(__name__)
class DvcParserError(Exception):
@@ -165,18 +167,18 @@ def main(argv=None): # noqa: C901, PLR0912, PLR0915
# the copied parent's standard file descriptors. If we make any logging
# calls in this state it will cause an exception due to writing to a closed
# file descriptor.
if sys.stderr.closed: # pylint: disable=using-constant-test
if not sys.stderr or sys.stderr.closed:
logging.disable()
elif sys.stdout.closed: # pylint: disable=using-constant-test
elif not sys.stdout or sys.stdout.closed:
logging.disable(logging.INFO)
args = None
outer_log_level = logger.level
level = None
try:
args = parse_args(argv)
level = None
if args.quiet:
level = logging.CRITICAL
elif args.verbose == 1:
@@ -192,14 +194,14 @@ def main(argv=None): # noqa: C901, PLR0912, PLR0915
from dvc import PKG, __version__
pyv = " ".join([python_implementation(), python_version()])
pyv = f"{python_implementation()} {python_version()}"
pkg = f" ({PKG})" if PKG else ""
logger.debug("v%s%s, %s on %s", __version__, pkg, pyv, platform())
logger.debug("command: %s", " ".join(argv or sys.argv))
logger.trace(args) # type: ignore[attr-defined]
logger.trace(args)
if not sys.stdout.closed and not args.quiet:
if sys.stdout and not sys.stdout.closed and not args.quiet:
from dvc.ui import ui
ui.enable()
@@ -230,16 +232,19 @@ def main(argv=None): # noqa: C901, PLR0912, PLR0915
logger.exception("")
except DvcParserError:
ret = 254
except Exception as exc: # noqa, pylint: disable=broad-except
# pylint: disable=no-member
except Exception as exc: # noqa: BLE001
ret = _log_exceptions(exc) or 255
try:
import os
from dvc import analytics
if analytics.is_enabled():
analytics.collect_and_send_report(args, ret)
logger.trace("Process %s exiting with %s", os.getpid(), ret)
return ret
finally:
logger.setLevel(outer_log_level)
+4 -3
View File
@@ -1,12 +1,13 @@
import logging
import os
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any
from dvc.log import logger
if TYPE_CHECKING:
from dvc.config import Config
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdBase(ABC):
@@ -31,7 +32,7 @@ class CmdBase(ABC):
class CmdBaseNoRepo(CmdBase):
def __init__(self, args): # pylint: disable=super-init-not-called
def __init__(self, args):
self.args = args
os.chdir(args.cd)
+14 -8
View File
@@ -1,12 +1,12 @@
"""Main parser for the dvc cli."""
import argparse
import logging
import os
from functools import lru_cache
from dvc import __version__
from dvc.commands import (
add,
artifacts,
cache,
check_ignore,
checkout,
@@ -19,6 +19,7 @@ from dvc.commands import (
data_sync,
destroy,
diff,
du,
experiments,
freeze,
gc,
@@ -26,12 +27,12 @@ from dvc.commands import (
get_url,
git_hook,
imp,
imp_db,
imp_url,
init,
install,
ls,
ls_url,
machine,
metrics,
move,
params,
@@ -42,14 +43,16 @@ from dvc.commands import (
repro,
root,
stage,
studio,
unprotect,
update,
version,
)
from dvc.log import logger
from . import DvcParserError
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
COMMANDS = [
init,
@@ -65,6 +68,7 @@ COMMANDS = [
data_sync,
gc,
imp,
imp_db,
imp_url,
config,
checkout,
@@ -89,18 +93,20 @@ COMMANDS = [
stage,
experiments,
check_ignore,
machine,
data,
artifacts,
studio,
du,
]
def _find_parser(parser, cmd_cls):
defaults = parser._defaults # pylint: disable=protected-access
defaults = parser._defaults
if not cmd_cls or cmd_cls == defaults.get("func"):
parser.print_help()
raise DvcParserError
actions = parser._actions # pylint: disable=protected-access
actions = parser._actions
for action in actions:
if not isinstance(action.choices, dict):
# NOTE: we are only interested in subparsers
@@ -112,7 +118,7 @@ def _find_parser(parser, cmd_cls):
class DvcParser(argparse.ArgumentParser):
"""Custom parser class for dvc CLI."""
def error(self, message, cmd_cls=None): # pylint: disable=arguments-differ
def error(self, message, cmd_cls=None):
logger.error(message)
_find_parser(self, cmd_cls)
@@ -137,7 +143,6 @@ def get_parent_parser():
from dvc._debug import add_debugging_flags
parent_parser = argparse.ArgumentParser(add_help=False)
add_debugging_flags(parent_parser)
log_level_group = parent_parser.add_mutually_exclusive_group()
log_level_group.add_argument(
"-q", "--quiet", action="count", default=0, help="Be quiet."
@@ -145,6 +150,7 @@ def get_parent_parser():
log_level_group.add_argument(
"-v", "--verbose", action="count", default=0, help="Be verbose."
)
add_debugging_flags(parent_parser)
return parent_parser
+29 -3
View File
@@ -1,3 +1,31 @@
import argparse
class DictAction(argparse.Action):
def __init__(self, *args, **kwargs):
kwargs.setdefault("metavar", "<name>=<value>")
super().__init__(*args, **kwargs)
def __call__(self, parser, args, values, option_string=None): # noqa: ARG002
d = getattr(args, self.dest) or {}
if isinstance(values, list):
kvs = values
else:
kvs = [values]
for kv in kvs:
key, value = kv.split("=", 1)
if not value:
raise argparse.ArgumentError(
self,
f'Could not parse argument "{values}" as k1=v1 k2=v2 ... format',
)
d[key] = value
setattr(args, self.dest, d)
def fix_subparsers(subparsers):
"""Workaround for bug in Python 3. See more info at:
https://bugs.python.org/issue16308
@@ -16,9 +44,7 @@ def append_doc_link(help_message, path):
if not path:
return help_message
doc_base = "https://man.dvc.org/"
return "{message}\nDocumentation: {link}".format(
message=help_message, link=format_link(doc_base + path)
)
return f"{help_message}\nDocumentation: {format_link(doc_base + path)}"
def hide_subparsers_from_help(subparsers):
+2 -2
View File
@@ -1,11 +1,11 @@
import argparse
import logging
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdAdd(CmdBase):
+133
View File
@@ -0,0 +1,133 @@
import argparse
from dvc.cli import completion
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import DictAction, append_doc_link, fix_subparsers
from dvc.exceptions import DvcException
from dvc.log import logger
logger = logger.getChild(__name__)
class CmdArtifactsGet(CmdBaseNoRepo):
def run(self):
from dvc.repo.artifacts import Artifacts
from dvc.scm import CloneError
from dvc.ui import ui
try:
count, out = Artifacts.get(
self.args.url,
name=self.args.name,
version=self.args.rev,
stage=self.args.stage,
force=self.args.force,
config=self.args.config,
remote=self.args.remote,
remote_config=self.args.remote_config,
out=self.args.out,
)
ui.write(f"Downloaded {count} file(s) to '{out}'")
return 0
except CloneError:
logger.exception("failed to get '%s'", self.args.name)
return 1
except DvcException:
logger.exception(
"failed to get '%s' from '%s'", self.args.name, self.args.url
)
return 1
def add_parser(subparsers, parent_parser):
ARTIFACTS_HELP = "DVC model registry artifact commands."
artifacts_parser = subparsers.add_parser(
"artifacts",
parents=[parent_parser],
description=append_doc_link(ARTIFACTS_HELP, "artifacts"),
help=ARTIFACTS_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
artifacts_subparsers = artifacts_parser.add_subparsers(
dest="cmd",
help="Use `dvc artifacts CMD --help` to display command-specific help.",
)
fix_subparsers(artifacts_subparsers)
ARTIFACTS_GET_HELP = "Download an artifact from a DVC project."
get_parser = artifacts_subparsers.add_parser(
"get",
parents=[parent_parser],
description=append_doc_link(ARTIFACTS_GET_HELP, "artifacts/get"),
help=ARTIFACTS_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
get_parser.add_argument("url", help="Location of DVC repository to download from")
get_parser.add_argument(
"name", help="Name of artifact in the repository"
).complete = completion.FILE
get_parser.add_argument(
"--rev",
nargs="?",
help="Artifact version",
metavar="<version>",
)
get_parser.add_argument(
"--stage",
nargs="?",
help="Artifact stage",
metavar="<stage>",
)
get_parser.add_argument(
"-o",
"--out",
nargs="?",
help="Destination path to download artifact to",
metavar="<path>",
).complete = completion.DIR
get_parser.add_argument(
"-j",
"--jobs",
type=int,
help=(
"Number of jobs to run simultaneously. "
"The default value is 4 * cpu_count(). "
),
metavar="<number>",
)
get_parser.add_argument(
"-f",
"--force",
action="store_true",
default=False,
help="Override local file or folder if exists.",
)
get_parser.add_argument(
"--config",
type=str,
help=(
"Path to a config file that will be merged with the config "
"in the target repository."
),
)
get_parser.add_argument(
"--remote",
type=str,
help=(
"Remote name to set as a default in the target repository "
"(only applicable when downloading from DVC remote)."
),
)
get_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 (only applicable "
"when downloading from DVC remote)."
),
)
get_parser.set_defaults(func=CmdArtifactsGet)
+11
View File
@@ -40,8 +40,11 @@ class CmdCacheDir(CmdConfig):
class CmdCacheMigrate(CmdBase):
def run(self):
from dvc.cachemgr import migrate_2_to_3
from dvc.repo.commit import commit_2_to_3
migrate_2_to_3(self.repo, dry=self.args.dry)
if self.args.dvc_files:
commit_2_to_3(self.repo, dry=self.args.dry)
return 0
@@ -102,6 +105,14 @@ def add_parser(subparsers, parent_parser):
help=CACHE_MIGRATE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
cache_migrate_parser.add_argument(
"--dvc-files",
help=(
"Migrate entries in all existing DVC files in the repository "
"to the DVC 3.0 format."
),
action="store_true",
)
cache_migrate_parser.add_argument(
"--dry",
help=(
+7
View File
@@ -37,6 +37,7 @@ class CmdCheckout(CmdBase):
force=self.args.force,
relink=self.args.relink,
recursive=self.args.recursive,
allow_missing=self.args.allow_missing,
)
except CheckoutError as _exc:
exc = _exc
@@ -101,6 +102,12 @@ def add_parser(subparsers, parent_parser):
default=False,
help="Recreate links or copies from cache to workspace.",
)
checkout_parser.add_argument(
"--allow-missing",
action="store_true",
default=False,
help="Ignore errors if some of the files or directories are missing.",
)
checkout_parser.add_argument(
"targets",
nargs="*",
+6 -3
View File
@@ -1,11 +1,11 @@
import argparse
import logging
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdCommit(CmdBase):
@@ -47,7 +47,10 @@ def add_parser(subparsers, parent_parser):
"--force",
action="store_true",
default=False,
help="Commit even if hash value for dependencies/outputs changed.",
help=(
"Commit data even if hash values for dependencies or "
"outputs did not change."
),
)
commit_parser.add_argument(
"-d",
+7 -4
View File
@@ -1,12 +1,15 @@
import argparse
import logging
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.completion import PREAMBLE
from dvc.cli.utils import append_doc_link
from dvc.log import logger
from dvc.ui import ui
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
SUPPORTED_SHELLS = ["bash", "zsh"]
class CmdCompletion(CmdBaseNoRepo):
@@ -15,7 +18,7 @@ class CmdCompletion(CmdBaseNoRepo):
shell = self.args.shell
parser = self.args.parser
script = shtab.complete(parser, shell=shell, preamble=PREAMBLE) # nosec B604
script = shtab.complete(parser, shell=shell, preamble=PREAMBLE)
ui.write(script, force=True)
return 0
@@ -35,6 +38,6 @@ def add_parser(subparsers, parent_parser):
"--shell",
help="Shell syntax for completions.",
default="bash",
choices=["bash", "zsh"],
choices=SUPPORTED_SHELLS,
)
completion_parser.set_defaults(func=CmdCompletion)
+23 -21
View File
@@ -1,14 +1,16 @@
import argparse
import logging
import os
from funcy import set_in
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.log import logger
from dvc.ui import ui
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
NAME_REGEX = r"^(?P<remote>remote\.)?(?P<section>[^\.]*)\.(?P<option>[^\.]*)$"
NAME_REGEX = r"^(?P<top>(remote|db)\.)?(?P<section>[^\.]*)\.(?P<option>[^\.]*)$"
def _name_type(value):
@@ -17,10 +19,12 @@ def _name_type(value):
match = re.match(NAME_REGEX, value)
if not match:
raise argparse.ArgumentTypeError(
"name argument should look like remote.name.option or section.option"
"name argument should look like remote.name.option or "
"db.name.option or section.option"
)
top = match.group("top")
return (
bool(match.group("remote")),
top.strip(".") if top else None,
match.group("section").lower(),
match.group("option").lower(),
)
@@ -49,12 +53,11 @@ class CmdConfig(CmdBaseNoRepo):
logger.error("name argument is required")
return 1
remote, section, opt = self.args.name
remote_or_db, section, opt = self.args.name
if self.args.value is None and not self.args.unset:
return self._get(remote, section, opt)
return self._set(remote, section, opt)
return self._get(remote_or_db, section, opt)
return self._set(remote_or_db, section, opt)
def _list(self):
if any((self.args.name, self.args.value, self.args.unset)):
@@ -75,18 +78,18 @@ class CmdConfig(CmdBaseNoRepo):
return 0
def _get(self, remote, section, opt):
def _get(self, remote_or_db, section, opt):
from dvc.config import ConfigError
levels = self._get_appropriate_levels(self.args.level)[::-1]
for level in levels:
conf = self.config.read(level)
if remote:
conf = conf["remote"]
if remote_or_db:
conf = conf[remote_or_db]
try:
self._check(conf, remote, section, opt)
self._check(conf, remote_or_db, section, opt)
except ConfigError:
if self.args.level:
raise
@@ -99,16 +102,15 @@ class CmdConfig(CmdBaseNoRepo):
return 0
def _set(self, remote, section, opt):
def _set(self, remote_or_db, section, opt):
with self.config.edit(self.args.level) as conf:
if remote:
conf = conf["remote"]
if remote_or_db:
conf = conf[remote_or_db]
if self.args.unset:
self._check(conf, remote, section, opt)
self._check(conf, remote_or_db, section, opt)
del conf[section][opt]
else:
self._check(conf, remote, section)
conf[section][opt] = self.args.value
conf.update(set_in(conf, [section, opt], self.args.value))
if self.args.name == "cache.type":
logger.warning(
@@ -119,10 +121,10 @@ class CmdConfig(CmdBaseNoRepo):
return 0
def _check(self, conf, remote, section, opt=None):
def _check(self, conf, remote_or_db, section, opt=None):
from dvc.config import ConfigError
name = "remote" if remote else "section"
name = remote_or_db or "section"
if section not in conf:
raise ConfigError(f"{name} '{section}' doesn't exist")
+6
View File
@@ -1,6 +1,9 @@
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__)
class CmdDaemonBase(CmdBaseNoRepo):
@@ -21,6 +24,8 @@ class CmdDaemonUpdater(CmdDaemonBase):
config = Config(dvc_dir, validate=False)
hardlink_lock = config.get("core", {}).get("hardlink_lock", False)
updater = Updater(tmp_dir, hardlink_lock=hardlink_lock)
logger.info("Starting updater to fetch the latest version")
updater.fetch(detach=False)
return 0
@@ -30,6 +35,7 @@ class CmdDaemonAnalytics(CmdDaemonBase):
def run(self):
from dvc import analytics
logger.info("Sending analytics")
analytics.send(self.args.target)
return 0
+1 -1
View File
@@ -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)): # noqa: B301
for out in outs_trie.itervalues(prefix=repo.fs.parts(path)):
targets.extend(str(out))
return targets
+4 -8
View File
@@ -1,11 +1,11 @@
import argparse
import logging
from typing import TYPE_CHECKING
from funcy import chunks, compact, log_durations
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.log import logger
from dvc.ui import ui
from dvc.utils import colorize
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
from dvc.repo.data import Status as DataStatus
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdDataStatus(CmdBase):
@@ -90,9 +90,7 @@ class CmdDataStatus(CmdBase):
ui.write(f" ({hint})")
if isinstance(stage_status, dict):
items = [
": ".join([state, file]) for file, state in stage_status.items()
]
items = [f"{state}: {file}" for file, state in stage_status.items()]
else:
items = stage_status
@@ -108,9 +106,7 @@ class CmdDataStatus(CmdBase):
return 0
def run(self) -> int:
with log_durations(
logger.trace, "in data_status" # type: ignore[attr-defined]
):
with log_durations(logger.trace, "in data_status"):
status = self.repo.data_status(
granular=self.args.granular,
untracked_files=self.args.untracked_files,
+24 -2
View File
@@ -1,11 +1,11 @@
import argparse
import logging
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdDataBase(CmdBase):
@@ -14,6 +14,10 @@ class CmdDataBase(CmdBase):
from dvc.utils.humanize import get_summary
default_msg = "Everything is up to date."
if not self.args.remote and not self.repo.config["core"].get("remote"):
ui.warn("No remote provided and no default remote set.")
ui.write(get_summary(stats.items()) or default_msg)
@@ -91,6 +95,8 @@ class CmdDataFetch(CmdDataBase):
with_deps=self.args.with_deps,
recursive=self.args.recursive,
run_cache=self.args.run_cache,
max_size=self.args.max_size,
types=self.args.types,
)
self.log_summary({"fetched": processed_files_count})
except DvcException:
@@ -322,6 +328,22 @@ def add_parser(subparsers, _parent_parser):
default=False,
help="Fetch run history for all stages.",
)
fetch_parser.add_argument(
"--max-size",
type=int,
help="Fetch data files/directories that are each below specified size (bytes).",
)
fetch_parser.add_argument(
"--type",
dest="types",
action="append",
default=[],
help=(
"Only fetch data files/directories that are of a particular "
"type (metrics, plots)."
),
choices=["metrics", "plots"],
)
fetch_parser.set_defaults(func=CmdDataFetch)
# Status
+3 -3
View File
@@ -1,10 +1,10 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdDestroy(CmdBase):
@@ -21,7 +21,7 @@ class CmdDestroy(CmdBase):
)
if not self.args.force and not ui.confirm(statement):
raise DvcException(
raise DvcException( # noqa: TRY301
"cannot destroy without a confirmation from the user."
" Use `-f` to force."
)
+2 -2
View File
@@ -1,13 +1,13 @@
import argparse
import logging
import os
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
from dvc.ui import ui
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
def _digest(checksum):
+80
View File
@@ -0,0 +1,80 @@
import argparse
import logging
from dvc.cli import completion
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=argparse.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)
+2
View File
@@ -12,6 +12,7 @@ from dvc.commands.experiments import (
push,
queue_worker,
remove,
rename,
run,
save,
show,
@@ -28,6 +29,7 @@ SUB_COMMANDS = [
push,
queue_worker,
remove,
rename,
run,
save,
show,
+2 -2
View File
@@ -1,12 +1,12 @@
import argparse
import logging
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
from dvc.ui import ui
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdExperimentsApply(CmdBase):
+2 -2
View File
@@ -1,10 +1,10 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdExperimentsBranch(CmdBase):
+2 -2
View File
@@ -1,10 +1,10 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdExperimentsClean(CmdBase):
+2 -2
View File
@@ -1,14 +1,14 @@
import argparse
import logging
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands.metrics import DEFAULT_PRECISION
from dvc.exceptions import DvcException
from dvc.log import logger
from dvc.ui import ui
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdExperimentsDiff(CmdBase):
+3 -4
View File
@@ -1,8 +1,7 @@
import logging
from dvc.cli.command import CmdBaseNoRepo
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdExecutorRun(CmdBaseNoRepo):
@@ -49,7 +48,7 @@ def add_parser(experiments_subparsers, parent_parser):
),
)
exec_run_parser.add_argument(
"-M",
"-m",
"--message",
type=str,
default=None,
+17 -9
View File
@@ -1,12 +1,12 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import InvalidArgumentError
from dvc.log import logger
from dvc.ui import ui
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdExperimentsList(CmdBase):
@@ -23,15 +23,22 @@ class CmdExperimentsList(CmdBase):
git_remote=git_remote,
)
from dvc.repo.experiments.utils import describe
from dvc.scm import Git
if name_only or sha_only:
names = {}
else:
assert isinstance(self.repo.scm, Git)
names = describe(
self.repo.scm,
(baseline for baseline in exps),
logger=logger,
)
for baseline in exps:
if not (name_only or sha_only):
tag_base = "refs/tags/"
branch_base = "refs/heads/"
name = baseline[:7]
if baseline.startswith(tag_base):
name = baseline[len(tag_base) :]
elif baseline.startswith(branch_base):
name = baseline[len(branch_base) :]
name = names.get(baseline) or baseline[:7]
ui.write(f"{name}:")
for exp_name, rev in exps[baseline]:
if name_only:
@@ -52,6 +59,7 @@ def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_LIST_HELP = "List local and remote experiments."
experiments_list_parser = experiments_subparsers.add_parser(
"list",
aliases=["ls"],
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_LIST_HELP, "exp/list"),
help=EXPERIMENTS_LIST_HELP,
+2 -2
View File
@@ -1,11 +1,11 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
from dvc.ui import ui
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdExperimentsPull(CmdBase):
+2 -2
View File
@@ -1,13 +1,13 @@
import argparse
import logging
from typing import Any, Dict
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
from dvc.ui import ui
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdExperimentsPush(CmdBase):
+2 -3
View File
@@ -1,8 +1,7 @@
import logging
from dvc.cli.command import CmdBase
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdQueueWorker(CmdBase):
+3 -2
View File
@@ -1,12 +1,12 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import InvalidArgumentError
from dvc.log import logger
from dvc.ui import ui
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdExperimentsRemove(CmdBase):
@@ -50,6 +50,7 @@ def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_REMOVE_HELP = "Remove experiments."
experiments_remove_parser = experiments_subparsers.add_parser(
"remove",
aliases=["rm"],
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_REMOVE_HELP, "exp/remove"),
help=EXPERIMENTS_REMOVE_HELP,
+68
View File
@@ -0,0 +1,68 @@
import argparse
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import InvalidArgumentError
from dvc.log import logger
from dvc.ui import ui
logger = logger.getChild(__name__)
class CmdExperimentsRename(CmdBase):
def run(self):
from dvc.utils import humanize
if not (self.args.experiment and self.args.name):
raise InvalidArgumentError(
"An experiment to rename and a new experiment name are required."
)
renamed = self.repo.experiments.rename(
exp_name=self.args.experiment,
new_name=self.args.name,
git_remote=self.args.git_remote,
force=self.args.force,
)
if renamed:
ui.write(f"Renamed experiments: {humanize.join(map(repr, renamed))}")
else:
ui.write("No experiments to rename.")
return 0
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_RENAME_HELP = "Rename experiments."
experiments_rename_parser = experiments_subparsers.add_parser(
"rename",
parents=[parent_parser],
description=append_doc_link(EXPERIMENTS_RENAME_HELP, "exp/rename"),
help=EXPERIMENTS_RENAME_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
rename_group = experiments_rename_parser.add_mutually_exclusive_group()
rename_group.add_argument(
"-g",
"--git-remote",
metavar="<git_remote>",
help="Name or URL of the Git remote to rename the experiment from",
)
experiments_rename_parser.add_argument(
"experiment",
help="Experiment to rename.",
nargs="?",
metavar="<experiment>",
)
experiments_rename_parser.add_argument(
"name",
help="New name for the experiment.",
metavar="<name>",
)
experiments_rename_parser.add_argument(
"-f",
"--force",
action="store_true",
default=False,
help="Replace experiment if it already exists.",
)
experiments_rename_parser.set_defaults(func=CmdExperimentsRename)
+8 -13
View File
@@ -1,11 +1,11 @@
import argparse
import logging
from dvc.cli.utils import append_doc_link
from dvc.commands.repro import CmdRepro
from dvc.commands.repro import add_arguments as add_repro_arguments
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdExperimentsRun(CmdRepro):
@@ -17,7 +17,6 @@ class CmdExperimentsRun(CmdRepro):
jobs=self.args.jobs,
params=self.args.set_param,
tmp_dir=self.args.tmp_dir,
machine=self.args.machine,
copy_paths=self.args.copy_paths,
message=self.args.message,
**self._common_kwargs,
@@ -27,7 +26,7 @@ class CmdExperimentsRun(CmdRepro):
def add_parser(experiments_subparsers, parent_parser):
EXPERIMENTS_RUN_HELP = "Run or resume an experiment."
EXPERIMENTS_RUN_HELP = "Run an experiment."
experiments_run_parser = experiments_subparsers.add_parser(
"run",
parents=[parent_parser],
@@ -90,15 +89,6 @@ def _add_run_common(parser):
"your workspace."
),
)
parser.add_argument(
"--machine",
default=None,
help=argparse.SUPPRESS,
# help=(
# "Run this experiment on the specified 'dvc machine' instance."
# )
# metavar="<name>",
)
parser.add_argument(
"-C",
"--copy-paths",
@@ -116,3 +106,8 @@ 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,
)
+8 -7
View File
@@ -1,12 +1,12 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
from dvc.log import logger
from dvc.ui import ui
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdExperimentsSave(CmdBase):
@@ -27,10 +27,6 @@ class CmdExperimentsSave(CmdBase):
else:
name = self.repo.experiments.get_exact_name([ref])[ref]
ui.write(f"Experiment has been saved as: {name}")
ui.write(
"\nTo promote an experiment to a Git branch run:\n\n"
"\tdvc exp branch <exp> <branch>\n"
)
return 0
@@ -76,10 +72,15 @@ def add_parser(experiments_subparsers, parent_parser):
metavar="<path>",
)
save_parser.add_argument(
"-M",
"-m",
"--message",
type=str,
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.set_defaults(func=CmdExperimentsSave)
+3 -3
View File
@@ -1,5 +1,4 @@
import argparse
import logging
import re
from datetime import date, datetime
from typing import TYPE_CHECKING, Dict, Iterable
@@ -10,6 +9,7 @@ from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.commands.metrics import DEFAULT_PRECISION
from dvc.exceptions import DvcException
from dvc.log import logger
from dvc.ui import ui
from dvc.utils.serialize import encode_exception
@@ -21,7 +21,7 @@ FILL_VALUE = "-"
FILL_VALUE_ERRORED = "!"
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
experiment_types = {
@@ -54,7 +54,7 @@ def baseline_styler(typ):
return {"style": "bold"} if typ == "baseline" else {}
def show_experiments( # noqa: C901
def show_experiments(
td: "TabularData",
headers: Dict[str, Iterable[str]],
keep=None,
+2 -2
View File
@@ -1,12 +1,12 @@
import argparse
import logging
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdFreezeBase(CmdBase):
+13 -2
View File
@@ -1,12 +1,12 @@
import argparse
import logging
import os
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
from dvc.ui import ui
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdGC(CmdBase):
@@ -26,6 +26,10 @@ class CmdGC(CmdBase):
not_in_remote=self.args.not_in_remote,
)
# Don't prompt during dry run
if self.args.dry:
self.args.force = True
if self.args.rev:
self.args.num = self.args.num or 1
@@ -81,6 +85,7 @@ class CmdGC(CmdBase):
rev=self.args.rev,
num=self.args.num,
not_in_remote=self.args.not_in_remote,
dry=self.args.dry,
)
return 0
@@ -213,4 +218,10 @@ def add_parser(subparsers, parent_parser):
),
metavar="<paths>",
)
gc_parser.add_argument(
"--dry",
action="store_true",
default=False,
help=("Only print what would get removed without actually removing."),
)
gc_parser.set_defaults(func=CmdGC)
+29 -3
View File
@@ -1,12 +1,12 @@
import argparse
import logging
from dvc.cli import completion
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.cli.utils import DictAction, append_doc_link
from dvc.exceptions import DvcException
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdGet(CmdBaseNoRepo):
@@ -37,6 +37,9 @@ class CmdGet(CmdBaseNoRepo):
rev=self.args.rev,
jobs=self.args.jobs,
force=self.args.force,
config=self.args.config,
remote=self.args.remote,
remote_config=self.args.remote_config,
)
return 0
except CloneError:
@@ -102,4 +105,27 @@ def add_parser(subparsers, parent_parser):
default=False,
help="Override local file or folder if exists.",
)
get_parser.add_argument(
"--config",
type=str,
help=(
"Path to a config file that will be merged with the config "
"in the target repository."
),
)
get_parser.add_argument(
"--remote",
type=str,
help="Remote name to set as a default in the target repository.",
)
get_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."
),
)
get_parser.set_defaults(func=CmdGet)
+13 -3
View File
@@ -1,16 +1,17 @@
import argparse
import logging
from dvc.cli import completion
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.cli.utils import DictAction, append_doc_link
from dvc.exceptions import DvcException
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdGetUrl(CmdBaseNoRepo):
def run(self):
from dvc.config import Config
from dvc.repo import Repo
try:
@@ -19,6 +20,8 @@ class CmdGetUrl(CmdBaseNoRepo):
out=self.args.out,
jobs=self.args.jobs,
force=self.args.force,
fs_config=self.args.fs_config,
config=Config.from_cwd(),
)
return 0
except DvcException:
@@ -58,4 +61,11 @@ def add_parser(subparsers, parent_parser):
default=False,
help="Override local file or folder if exists.",
)
get_parser.add_argument(
"--fs-config",
type=str,
nargs="*",
action=DictAction,
help="Config options for the target url.",
)
get_parser.set_defaults(func=CmdGetUrl)
+2 -2
View File
@@ -1,11 +1,11 @@
import logging
import os
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import fix_subparsers
from dvc.exceptions import NotDvcRepoError
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdHookBase(CmdBaseNoRepo):
+37 -3
View File
@@ -1,12 +1,12 @@
import argparse
import logging
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.cli.utils import DictAction, append_doc_link
from dvc.exceptions import DvcException
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdImport(CmdBase):
@@ -22,6 +22,10 @@ class CmdImport(CmdBase):
no_exec=self.args.no_exec,
no_download=self.args.no_download,
jobs=self.args.jobs,
config=self.args.config,
remote=self.args.remote,
remote_config=self.args.remote_config,
force=self.args.force,
)
except CloneError:
logger.exception("failed to import '%s'", self.args.path)
@@ -62,6 +66,13 @@ def add_parser(subparsers, parent_parser):
help="Destination path to download files to",
metavar="<path>",
).complete = completion.DIR
import_parser.add_argument(
"-f",
"--force",
action="store_true",
default=False,
help="Override destination file or folder if exists.",
)
import_parser.add_argument(
"--rev",
nargs="?",
@@ -94,4 +105,27 @@ def add_parser(subparsers, parent_parser):
),
metavar="<number>",
)
import_parser.add_argument(
"--config",
type=str,
help=(
"Path to a config file that will be merged with the config "
"in the target repository."
),
)
import_parser.add_argument(
"--remote",
type=str,
help="Remote name to set as a default in the target repository.",
)
import_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."
),
)
import_parser.set_defaults(func=CmdImport)
+167
View File
@@ -0,0 +1,167 @@
import argparse
from funcy import compact, merge
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
from dvc.ui import ui
logger = logger.getChild(__name__)
class CmdTestDb(CmdBase):
def run(self):
from dvc.database import get_client
from dvc.database.dbt_utils import DBT_PROJECT_FILE, is_dbt_project
from dvc.dependency.db import _get_dbt_config
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:
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_client(conn_config, project_dir=project_dir, **dbt_config)
with adapter 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,
output_format=self.args.output_format,
out=self.args.out,
force=self.args.force,
connection=self.args.connection,
)
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_parser = subparsers.add_parser(
"import-db",
parents=[parent_parser],
description=append_doc_link(IMPORT_HELP, "import"),
add_help=False,
)
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")
import_parser.add_argument(
"--output-format",
default="csv",
const="csv",
nargs="?",
choices=["csv", "json"],
help="Export format",
)
import_parser.add_argument(
"-o",
"--out",
nargs="?",
help="Destination path to download files to",
metavar="<path>",
).complete = completion.FILE
import_parser.add_argument(
"-f",
"--force",
action="store_true",
default=False,
help="Override destination file or folder if exists.",
)
import_parser.add_argument(
"--conn",
dest="connection",
nargs="?",
help="Database connection to use, needs to be set in config",
)
import_parser.set_defaults(func=CmdImportDb)
TEST_DB_HELP = "Test the database connection"
test_db_parser = subparsers.add_parser(
"test-db",
parents=[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("--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)
+11 -3
View File
@@ -1,12 +1,12 @@
import argparse
import logging
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.cli.utils import DictAction, append_doc_link
from dvc.exceptions import DvcException
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdImportUrl(CmdBase):
@@ -22,6 +22,7 @@ class CmdImportUrl(CmdBase):
jobs=self.args.jobs,
force=self.args.force,
version_aware=self.args.version_aware,
fs_config=self.args.fs_config,
)
except DvcException:
logger.exception(
@@ -114,4 +115,11 @@ def add_parser(subparsers, parent_parser):
default=False,
help="Import using cloud versioning. Implied if the URL contains a version ID.",
)
import_parser.add_argument(
"--fs-config",
type=str,
nargs="*",
action=DictAction,
help="Config options for the target url.",
)
import_parser.set_defaults(func=CmdImportUrl)
+2 -2
View File
@@ -1,15 +1,15 @@
import argparse
import logging
import colorama
from dvc import analytics
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.log import logger
from dvc.utils import boxify
from dvc.utils import format_link as fmt_link
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
def _welcome_message():
+2 -2
View File
@@ -1,11 +1,11 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdInstall(CmdBase):
+53 -7
View File
@@ -1,17 +1,28 @@
import argparse
import logging
from dvc.cli import completion
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.cli.utils import DictAction, append_doc_link
from dvc.commands.ls.ls_colors import LsColors
from dvc.exceptions import DvcException
from dvc.log import logger
from dvc.ui import ui
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
def _prettify(entries, with_color=False):
def _format_entry(entry, fmt):
from dvc.utils.humanize import naturalsize
size = entry.get("size")
if size is None:
size = ""
else:
size = naturalsize(size)
return size, fmt(entry)
def show_entries(entries, with_color=False, with_size=False):
if with_color:
ls_colors = LsColors()
fmt = ls_colors.format
@@ -20,7 +31,12 @@ def _prettify(entries, with_color=False):
def fmt(entry):
return entry["path"]
return [fmt(entry) for entry in entries]
if with_size:
ui.table([_format_entry(entry, fmt) for entry in entries])
return
# NOTE: this is faster than ui.table for very large number of entries
ui.write("\n".join(fmt(entry) for entry in entries))
class CmdList(CmdBaseNoRepo):
@@ -34,12 +50,14 @@ class CmdList(CmdBaseNoRepo):
rev=self.args.rev,
recursive=self.args.recursive,
dvc_only=self.args.dvc_only,
config=self.args.config,
remote=self.args.remote,
remote_config=self.args.remote_config,
)
if self.args.json:
ui.write_json(entries)
elif entries:
entries = _prettify(entries, with_color=True)
ui.write("\n".join(entries))
show_entries(entries, with_color=True, with_size=self.args.size)
return 0
except DvcException:
logger.exception("failed to list '%s'", self.args.url)
@@ -80,6 +98,34 @@ def add_parser(subparsers, parent_parser):
help="Git revision (e.g. SHA, branch, tag)",
metavar="<commit>",
)
list_parser.add_argument(
"--config",
type=str,
help=(
"Path to a config file that will be merged with the config "
"in the target repository."
),
)
list_parser.add_argument(
"--remote",
type=str,
help="Remote name to set as a default in the target repository.",
)
list_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."
),
)
list_parser.add_argument(
"--size",
action="store_true",
help="Show sizes.",
)
list_parser.add_argument(
"path",
nargs="?",
+24 -8
View File
@@ -1,23 +1,27 @@
import argparse
import logging
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.ui import ui
from dvc.cli.utils import DictAction, append_doc_link
from dvc.log import logger
from .ls import _prettify
from .ls import show_entries
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdListUrl(CmdBaseNoRepo):
def run(self):
from dvc.config import Config
from dvc.repo import Repo
entries = Repo.ls_url(self.args.url, recursive=self.args.recursive)
entries = Repo.ls_url(
self.args.url,
recursive=self.args.recursive,
fs_config=self.args.fs_config,
config=Config.from_cwd(),
)
if entries:
entries = _prettify(entries, with_color=True)
ui.write("\n".join(entries))
show_entries(entries, with_color=True, with_size=self.args.size)
return 0
@@ -40,4 +44,16 @@ def add_parser(subparsers, parent_parser):
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,
nargs="*",
action=DictAction,
help="Config options for the target url.",
)
lsurl_parser.set_defaults(func=CmdListUrl)
-490
View File
@@ -1,490 +0,0 @@
import argparse
from typing import Dict, List
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.commands.config import CmdConfig
from dvc.compare import TabularData
from dvc.config import ConfigError
from dvc.exceptions import DvcException
from dvc.ui import ui
from dvc.utils import format_link
class MachineDisabledError(ConfigError):
def __init__(self):
super().__init__("Machine feature is disabled")
class CmdMachineConfig(CmdConfig):
def __init__(self, args):
super().__init__(args)
if not self.config["feature"].get("machine", False):
raise MachineDisabledError
if getattr(self.args, "name", None):
self.args.name = self.args.name.lower()
def _check_exists(self, conf):
if self.args.name not in conf["machine"]:
raise ConfigError(f"machine '{self.args.name}' doesn't exist.")
class CmdMachineAdd(CmdMachineConfig):
def run(self):
from dvc.machine import validate_name
validate_name(self.args.name)
if self.args.default:
ui.write(f"Setting '{self.args.name}' as a default machine.")
with self.config.edit(self.args.level) as conf:
if self.args.name in conf["machine"] and not self.args.force:
raise ConfigError(
"machine '{}' already exists. Use `-f|--force` to "
"overwrite it.".format(self.args.name)
)
conf["machine"][self.args.name] = {"cloud": self.args.cloud}
if self.args.default:
conf["core"]["machine"] = self.args.name
return 0
class CmdMachineRemove(CmdMachineConfig):
def run(self):
with self.config.edit(self.args.level) as conf:
self._check_exists(conf)
del conf["machine"][self.args.name]
up_to_level = self.args.level or "repo"
# Remove core.machine refs to this machine in any shadowing configs
for level in reversed(self.config.LEVELS):
with self.config.edit(level) as conf:
if conf["core"].get("machine") == self.args.name:
del conf["core"]["machine"]
if level == up_to_level:
break
return 0
class CmdMachineList(CmdMachineConfig):
TABLE_COLUMNS = [
"name",
"cloud",
"region",
"image",
"spot",
"spot_price",
"instance_hdd_size",
"instance_type",
"ssh_private",
"startup_script",
]
PRIVATE_COLUMNS = ["ssh_private", "startup_script"]
def _hide_private(self, conf):
for machine in conf:
for column in self.PRIVATE_COLUMNS:
if column in conf[machine]:
conf[machine][column] = "***"
def _show_origin(self):
levels = [self.args.level] if self.args.level else self.config.LEVELS
for level in levels:
conf = self.config.read(level)["machine"]
if self.args.name:
conf = conf.get(self.args.name, {})
self._hide_private(conf)
prefix = self._config_file_prefix(True, self.config, level)
configs = list(self._format_config(conf, prefix))
if configs:
ui.write("\n".join(configs))
def _show_table(self):
td = TabularData(self.TABLE_COLUMNS, fill_value="-")
conf = self.config.read()["machine"]
if self.args.name:
conf = {self.args.name: conf.get(self.args.name, {})}
self._hide_private(conf)
for machine, machine_config in conf.items():
machine_config["name"] = machine
td.row_from_dict(machine_config)
td.dropna("cols", "all")
td.render()
def run(self):
if self.args.show_origin:
self._show_origin()
else:
self._show_table()
return 0
class CmdMachineModify(CmdMachineConfig):
def run(self):
from dvc.config import merge
with self.config.edit(self.args.level) as conf:
merged = self.config.load_config_to_level(self.args.level)
merge(merged, conf)
self._check_exists(merged)
if self.args.name not in conf["machine"]:
conf["machine"][self.args.name] = {}
section = conf["machine"][self.args.name]
if self.args.unset:
section.pop(self.args.option, None)
else:
section[self.args.option] = self.args.value
return 0
class CmdMachineRename(CmdBase):
def _check_exists(self, conf):
if self.args.name not in conf["machine"]:
raise ConfigError(f"machine '{self.args.name}' doesn't exist.")
def _rename_default(self, conf):
if conf["core"].get("machine") == self.args.name:
conf["core"]["machine"] = self.args.new
def _check_before_rename(self):
from dvc.machine import validate_name
validate_name(self.args.new)
all_config = self.config.load_config_to_level(None)
if self.args.new in all_config.get("machine", {}):
raise ConfigError(
f"Rename failed. Machine '{self.args.new}' already exists."
)
ui.write(f"Rename machine '{self.args.name}' to '{self.args.new}'.")
def run(self):
self._check_before_rename()
with self.config.edit(self.args.level) as conf:
self._check_exists(conf)
conf["machine"][self.args.new] = conf["machine"][self.args.name]
try:
assert self.repo.machine
self.repo.machine.rename(self.args.name, self.args.new)
except DvcException as error:
del conf["machine"][self.args.new]
raise ConfigError("terraform rename failed") from error
del conf["machine"][self.args.name]
self._rename_default(conf)
up_to_level = self.args.level or "repo"
for level in reversed(self.config.LEVELS):
if level == up_to_level:
break
with self.config.edit(level) as level_conf:
self._rename_default(level_conf)
return 0
class CmdMachineDefault(CmdMachineConfig):
def run(self):
if self.args.name is None and not self.args.unset:
conf = self.config.read(self.args.level)
try:
ui.write(conf["core"]["machine"])
except KeyError:
ui.write("No default machine set")
return 1
else:
with self.config.edit(self.args.level) as conf:
if self.args.unset:
conf["core"].pop("machine", None)
else:
merged_conf = self.config.load_config_to_level(self.args.level)
if (
self.args.name in conf["machine"]
or self.args.name in merged_conf["machine"]
):
conf["core"]["machine"] = self.args.name
else:
raise ConfigError(
"default machine must be present in machine list."
)
return 0
class CmdMachineCreate(CmdBase):
def run(self):
if self.repo.machine is None:
raise MachineDisabledError
self.repo.machine.create(self.args.name)
return 0
class CmdMachineStatus(CmdBase):
INSTANCE_FIELD = ["name", "instance", "status"]
SHOWN_FIELD = [
"cloud",
"instance_ip",
"instance_type",
"instance_hdd_size",
"instance_gpu",
]
FILL_VALUE = "-"
def _add_row(
self,
name: str,
all_status: List[Dict],
td: TabularData,
):
if not all_status:
row = [
name,
self.FILL_VALUE,
"offline",
] # back to `None` after #7167
td.append(row)
for i, status in enumerate(all_status, start=1):
row = [name, f"num_{i}", "running" if status else "offline"]
for field in self.SHOWN_FIELD:
value = str(status.get(field, ""))
row.append(value)
td.append(row)
def run(self):
if self.repo.machine is None:
raise MachineDisabledError
td = TabularData(
self.INSTANCE_FIELD + self.SHOWN_FIELD, fill_value=self.FILL_VALUE
)
if self.args.name:
all_status = list(self.repo.machine.status(self.args.name))
self._add_row(self.args.name, all_status, td)
else:
name_set = set()
for level in self.repo.config.LEVELS:
conf = self.repo.config.read(level)["machine"]
name_set.update(conf.keys())
name_list = list(name_set)
for name in sorted(name_list):
all_status = list(self.repo.machine.status(name))
self._add_row(name, all_status, td)
td.dropna("cols", "all")
td.render()
return 0
class CmdMachineDestroy(CmdBase):
def run(self):
if self.repo.machine is None:
raise MachineDisabledError
self.repo.machine.destroy(self.args.name)
return 0
class CmdMachineSsh(CmdBase):
def run(self):
if self.repo.machine is None:
raise MachineDisabledError
self.repo.machine.run_shell(self.args.name)
return 0
def add_parser(subparsers, parent_parser): # noqa: PLR0915
from dvc.commands.config import parent_config_parser
machine_HELP = "Set up and manage cloud machines."
machine_parser = subparsers.add_parser(
"machine",
parents=[parent_parser],
description=append_doc_link(machine_HELP, "machine"),
# NOTE: suppress help during development to hide command
# help=machine_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
machine_subparsers = machine_parser.add_subparsers(
dest="cmd",
help="Use `dvc machine CMD --help` for command-specific help.",
)
fix_subparsers(machine_subparsers)
machine_ADD_HELP = "Add a new data machine."
machine_add_parser = machine_subparsers.add_parser(
"add",
parents=[parent_config_parser, parent_parser],
description=append_doc_link(machine_ADD_HELP, "machine/add"),
help=machine_ADD_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
machine_add_parser.add_argument("name", help="Name of the machine")
machine_add_parser.add_argument(
"cloud",
help="Machine cloud. See full list of supported clouds at {}".format(
format_link(
"https://github.com/iterative/terraform-provider-iterative#machine"
)
),
)
machine_add_parser.add_argument(
"-d",
"--default",
action="store_true",
default=False,
help="Set as default machine.",
)
machine_add_parser.add_argument(
"-f",
"--force",
action="store_true",
default=False,
help="Force overwriting existing configs",
)
machine_add_parser.set_defaults(func=CmdMachineAdd)
machine_DEFAULT_HELP = "Set/unset the default machine."
machine_default_parser = machine_subparsers.add_parser(
"default",
parents=[parent_config_parser, parent_parser],
description=append_doc_link(machine_DEFAULT_HELP, "machine/default"),
help=machine_DEFAULT_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
machine_default_parser.add_argument("name", nargs="?", help="Name of the machine")
machine_default_parser.add_argument(
"-u",
"--unset",
action="store_true",
default=False,
help="Unset default machine.",
)
machine_default_parser.set_defaults(func=CmdMachineDefault)
machine_LIST_HELP = "List the configuration of one/all machines."
machine_list_parser = machine_subparsers.add_parser(
"list",
parents=[parent_config_parser, parent_parser],
description=append_doc_link(machine_LIST_HELP, "machine/list"),
help=machine_LIST_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
machine_list_parser.add_argument(
"--show-origin",
default=False,
action="store_true",
help="Show the source file containing each config value.",
)
machine_list_parser.add_argument(
"name",
nargs="?",
type=str,
help="name of machine to specify",
)
machine_list_parser.set_defaults(func=CmdMachineList)
machine_MODIFY_HELP = "Modify the configuration of an machine."
machine_modify_parser = machine_subparsers.add_parser(
"modify",
parents=[parent_config_parser, parent_parser],
description=append_doc_link(machine_MODIFY_HELP, "machine/modify"),
help=machine_MODIFY_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
machine_modify_parser.add_argument("name", help="Name of the machine")
machine_modify_parser.add_argument("option", help="Name of the option to modify.")
machine_modify_parser.add_argument(
"value", nargs="?", help="(optional) Value of the option."
)
machine_modify_parser.add_argument(
"-u",
"--unset",
default=False,
action="store_true",
help="Unset option.",
)
machine_modify_parser.set_defaults(func=CmdMachineModify)
machine_RENAME_HELP = "Rename a machine "
machine_rename_parser = machine_subparsers.add_parser(
"rename",
parents=[parent_config_parser, parent_parser],
description=append_doc_link(machine_RENAME_HELP, "remote/rename"),
help=machine_RENAME_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
machine_rename_parser.add_argument("name", help="Machine to be renamed")
machine_rename_parser.add_argument("new", help="New name of the machine")
machine_rename_parser.set_defaults(func=CmdMachineRename)
machine_REMOVE_HELP = "Remove an machine."
machine_remove_parser = machine_subparsers.add_parser(
"remove",
parents=[parent_config_parser, parent_parser],
description=append_doc_link(machine_REMOVE_HELP, "machine/remove"),
help=machine_REMOVE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
machine_remove_parser.add_argument("name", help="Name of the machine to remove.")
machine_remove_parser.set_defaults(func=CmdMachineRemove)
machine_CREATE_HELP = "Create and start a machine instance."
machine_create_parser = machine_subparsers.add_parser(
"create",
parents=[parent_parser],
description=append_doc_link(machine_CREATE_HELP, "machine/create"),
help=machine_CREATE_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
machine_create_parser.add_argument("name", help="Name of the machine to create.")
machine_create_parser.set_defaults(func=CmdMachineCreate)
machine_STATUS_HELP = "List the status of running instances for one/all machines."
machine_status_parser = machine_subparsers.add_parser(
"status",
parents=[parent_parser],
description=append_doc_link(machine_STATUS_HELP, "machine/status"),
help=machine_STATUS_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
machine_status_parser.add_argument(
"name", nargs="?", help="(optional) Name of the machine."
)
machine_status_parser.set_defaults(func=CmdMachineStatus)
machine_DESTROY_HELP = "Destroy an machine instance."
machine_destroy_parser = machine_subparsers.add_parser(
"destroy",
parents=[parent_parser],
description=append_doc_link(machine_DESTROY_HELP, "machine/destroy"),
help=machine_DESTROY_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
machine_destroy_parser.add_argument(
"name", help="Name of the machine instance to destroy."
)
machine_destroy_parser.set_defaults(func=CmdMachineDestroy)
machine_SSH_HELP = "Connect to a machine via SSH."
machine_ssh_parser = machine_subparsers.add_parser(
"ssh",
parents=[parent_parser],
description=append_doc_link(machine_SSH_HELP, "machine/ssh"),
help=machine_SSH_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
machine_ssh_parser.add_argument(
"name", help="Name of the machine instance to connect to."
)
machine_ssh_parser.set_defaults(func=CmdMachineSsh)
+44 -24
View File
@@ -1,14 +1,13 @@
import argparse
import logging
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.exceptions import DvcException
from dvc.log import logger
from dvc.ui import ui
from dvc.utils.serialize import encode_exception
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
DEFAULT_PRECISION = 5
@@ -20,17 +19,25 @@ class CmdMetricsBase(CmdBase):
class CmdMetricsShow(CmdMetricsBase):
def run(self):
try:
metrics = self.repo.metrics.show(
self.args.targets,
all_branches=self.args.all_branches,
all_tags=self.args.all_tags,
all_commits=self.args.all_commits,
recursive=self.args.recursive,
from dvc.repo.metrics.show import to_relpath
from dvc.utils import errored_revisions
metrics = self.repo.metrics.show(
self.args.targets,
all_branches=self.args.all_branches,
all_tags=self.args.all_tags,
all_commits=self.args.all_commits,
)
metrics = {
k: to_relpath(self.repo.fs, self.repo.root_dir, v)
for k, v in metrics.items()
}
if errored := errored_revisions(metrics):
ui.error_write(
"DVC failed to load some metrics for following revisions:"
f" '{', '.join(errored)}'."
)
except DvcException:
logger.exception("")
return 1
if self.args.json:
ui.write_json(metrics, default=encode_exception)
@@ -52,17 +59,26 @@ class CmdMetricsShow(CmdMetricsBase):
class CmdMetricsDiff(CmdMetricsBase):
def run(self):
try:
diff = self.repo.metrics.diff(
a_rev=self.args.a_rev,
b_rev=self.args.b_rev,
targets=self.args.targets,
recursive=self.args.recursive,
all=self.args.all,
import os
from os.path import relpath
diff_result = self.repo.metrics.diff(
a_rev=self.args.a_rev,
b_rev=self.args.b_rev,
targets=self.args.targets,
all=self.args.all,
)
errored = [rev for rev, err in diff_result.get("errors", {}).items() if err]
if errored:
ui.error_write(
"DVC failed to load some metrics for following revisions:"
f" '{', '.join(errored)}'."
)
except DvcException:
logger.exception("failed to show metrics diff")
return 1
start = relpath(os.getcwd(), self.repo.root_dir)
diff = diff_result.get("diff", {})
diff = {relpath(path, start): result for path, result in diff.items()}
if self.args.json:
ui.write_json(diff)
@@ -184,10 +200,14 @@ def add_parser(subparsers, parent_parser):
formatter_class=argparse.RawDescriptionHelpFormatter,
)
metrics_diff_parser.add_argument(
"a_rev", nargs="?", help="Old Git commit to compare (defaults to HEAD)"
"a_rev",
nargs="?",
help="Old Git commit to compare (defaults to HEAD)",
default="HEAD",
)
metrics_diff_parser.add_argument(
"b_rev",
default="workspace",
nargs="?",
help="New Git commit to compare (defaults to the current workspace)",
)
+3 -2
View File
@@ -1,12 +1,12 @@
import argparse
import logging
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdMove(CmdBase):
@@ -30,6 +30,7 @@ def add_parser(subparsers, parent_parser):
move_parser = subparsers.add_parser(
"move",
aliases=["mv"],
parents=[parent_parser],
description=append_doc_link(MOVE_DESCRIPTION, "move"),
help=MOVE_HELP,
+27 -14
View File
@@ -1,30 +1,39 @@
import argparse
import logging
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.exceptions import DvcException
from dvc.log import logger
from dvc.ui import ui
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdParamsDiff(CmdBase):
UNINITIALIZED = True
def run(self):
try:
diff = self.repo.params.diff(
a_rev=self.args.a_rev,
b_rev=self.args.b_rev,
targets=self.args.targets,
all=self.args.all,
deps=self.args.deps,
import os
from os.path import relpath
diff_result = self.repo.params.diff(
a_rev=self.args.a_rev,
b_rev=self.args.b_rev,
targets=self.args.targets,
all=self.args.all,
deps_only=self.args.deps,
)
errored = [rev for rev, err in diff_result.get("errors", {}).items() if err]
if errored:
ui.error_write(
"DVC failed to load some metrics for following revisions:"
f" '{', '.join(errored)}'."
)
except DvcException:
logger.exception("failed to show params diff")
return 1
start = relpath(os.getcwd(), self.repo.root_dir)
diff = diff_result.get("diff", {})
diff = {relpath(path, start): result for path, result in diff.items()}
if self.args.json:
ui.write_json(diff)
@@ -74,10 +83,14 @@ def add_parser(subparsers, parent_parser):
formatter_class=argparse.RawDescriptionHelpFormatter,
)
params_diff_parser.add_argument(
"a_rev", nargs="?", help="Old Git commit to compare (defaults to HEAD)"
"a_rev",
nargs="?",
default="HEAD",
help="Old Git commit to compare (defaults to HEAD)",
)
params_diff_parser.add_argument(
"b_rev",
default="workspace",
nargs="?",
help="New Git commit to compare (defaults to the current workspace)",
)
+3 -47
View File
@@ -1,5 +1,4 @@
import argparse
import logging
import os
from typing import TYPE_CHECKING, Dict, List, Optional
@@ -9,6 +8,7 @@ from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.exceptions import DvcException
from dvc.log import logger
from dvc.ui import ui
from dvc.utils import format_link
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
from dvc.render.match import RendererWithErrors
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
def _show_json(
@@ -59,49 +59,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
@@ -175,11 +132,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"
+2 -2
View File
@@ -1,10 +1,10 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdQueueKill(CmdBase):
+2 -2
View File
@@ -1,10 +1,10 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdQueueLogs(CmdBase):
+2 -2
View File
@@ -1,12 +1,12 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import InvalidArgumentError
from dvc.log import logger
from dvc.ui import ui
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdQueueRemove(CmdBase):
+2 -2
View File
@@ -1,11 +1,11 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
from dvc.ui import ui
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdQueueStart(CmdBase):
+2 -2
View File
@@ -1,12 +1,12 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.compare import TabularData
from dvc.log import logger
from dvc.ui import ui
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdQueueStatus(CmdBase):
+2 -2
View File
@@ -1,11 +1,11 @@
import argparse
import logging
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.log import logger
from dvc.ui import ui
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdQueueStop(CmdBase):
+3 -5
View File
@@ -30,8 +30,8 @@ class CmdRemoteAdd(CmdRemote):
with self.config.edit(self.args.level) as conf:
if self.args.name in conf["remote"] and not self.args.force:
raise ConfigError(
"remote '{}' already exists. Use `-f|--force` to "
"overwrite it.".format(self.args.name)
f"remote '{self.args.name}' already exists. Use `-f|--force` to "
"overwrite it."
)
conf["remote"][self.args.name] = {"url": self.args.url}
@@ -127,9 +127,7 @@ class CmdRemoteRename(CmdRemote):
all_config = self.config.load_config_to_level(None)
if self.args.new in all_config.get("remote", {}):
raise ConfigError(
"Rename failed. Remote name '{}' already exists.".format(
{self.args.new}
)
f"Rename failed. Remote name {self.args.new!r} already exists."
)
with self.config.edit(self.args.level) as conf:
+3 -2
View File
@@ -1,12 +1,12 @@
import argparse
import logging
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdRemove(CmdBase):
@@ -26,6 +26,7 @@ def add_parser(subparsers, parent_parser):
)
remove_parser = subparsers.add_parser(
"remove",
aliases=["rm"],
parents=[parent_parser],
description=append_doc_link(REMOVE_HELP, "remove"),
help=REMOVE_HELP,
+21
View File
@@ -33,6 +33,7 @@ class CmdRepro(CmdBase):
"force_downstream": self.args.force_downstream,
"pull": self.args.pull,
"allow_missing": self.args.allow_missing,
"on_error": self.args.on_error,
}
@property
@@ -137,6 +138,26 @@ and then the stage name name.
"Only print the commands that would be executed without actually executing."
),
)
repro_parser.add_argument(
"-k",
"--keep-going",
action="store_const",
default="fail",
const="keep-going",
dest="on_error",
help=(
"Continue executing, skipping stages having dependencies "
"on the failed stages"
),
)
repro_parser.add_argument(
"--ignore-errors",
action="store_const",
default="fail",
const="ignore",
dest="on_error",
help="Ignore errors from stages.",
)
def add_parser(subparsers, parent_parser):
+2 -2
View File
@@ -1,11 +1,11 @@
import argparse
import logging
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.log import logger
from dvc.utils import relpath
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdRoot(CmdBaseNoRepo):
+3 -10
View File
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Dict, Iterable, List
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link, fix_subparsers
from dvc.log import logger
from dvc.utils.cli_parse import parse_params
from dvc.utils.humanize import truncate_text
@@ -14,7 +15,7 @@ if TYPE_CHECKING:
from dvc.output import Output
from dvc.stage import Stage
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
MAX_TEXT_LENGTH = 80
@@ -67,9 +68,7 @@ class CmdStageList(CmdBase):
def _get_stages(self) -> Iterable["Stage"]:
if self.args.all:
stages: List["Stage"] = self.repo.index.stages
logger.trace( # type: ignore[attr-defined]
"%d no. of stages found", len(stages)
)
logger.trace("%d no. of stages found", len(stages))
return stages
# removing duplicates while maintaining order
@@ -195,12 +194,6 @@ def _add_common_args(parser):
help="Declare output file or directory (do not put into DVC cache).",
metavar="<filename>",
).complete = completion.FILE
parser.add_argument(
"--external",
action="store_true",
default=False,
help="Allow outputs that are outside of the DVC repository.",
)
parser.add_argument(
"--outs-persist",
action="append",
+2 -3
View File
@@ -1,11 +1,10 @@
import logging
from dvc.commands.data_sync import CmdDataBase
from dvc.exceptions import DvcException
from dvc.log import logger
from dvc.ui import ui
from dvc.utils import format_link
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdDataStatus(CmdDataBase):
+175
View File
@@ -0,0 +1,175 @@
import argparse
import os
from funcy import get_in
from dvc.cli.utils import append_doc_link, fix_subparsers
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=argparse.RawDescriptionHelpFormatter,
)
studio_subparser = studio_parser.add_subparsers(
dest="cmd",
help="Use `DVC studio CMD --help` to display command-specific help.",
)
fix_subparsers(studio_subparser)
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=argparse.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=argparse.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=argparse.RawDescriptionHelpFormatter,
)
logout_parser.set_defaults(func=CmdStudioToken)
+2 -2
View File
@@ -1,12 +1,12 @@
import argparse
import logging
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdUnprotect(CmdBase):
+2 -2
View File
@@ -1,12 +1,12 @@
import argparse
import logging
from dvc.cli import completion
from dvc.cli.command import CmdBase
from dvc.cli.utils import append_doc_link
from dvc.exceptions import DvcException
from dvc.log import logger
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdUpdate(CmdBase):
+2 -2
View File
@@ -1,11 +1,11 @@
import argparse
import logging
from dvc.cli.command import CmdBaseNoRepo
from dvc.cli.utils import append_doc_link
from dvc.log import logger
from dvc.ui import ui
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class CmdVersion(CmdBaseNoRepo):
+4 -5
View File
@@ -57,7 +57,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
return self._columns[name]
def items(self) -> ItemsView[str, Column]:
projection = {k: self.column(k) for k in self.keys()} # noqa: SIM118
projection = {k: self.column(k) for k in self.keys()}
return projection.items()
def keys(self) -> List[str]:
@@ -171,8 +171,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
self.add_column(key)
row: List["CellT"] = [
with_value(d.get(key), self._fill_value)
for key in self.keys() # noqa: SIM118
with_value(d.get(key), self._fill_value) for key in self.keys()
]
self.append(row)
@@ -227,7 +226,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
to_drop -= match_line
if axis == "rows":
for name in self.keys(): # noqa: SIM118
for name in self.keys():
self._columns[name] = Column(
[x for n, x in enumerate(self._columns[name]) if n not in to_drop]
)
@@ -275,7 +274,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
else:
unique_rows.append(tuple_row)
for name in self.keys(): # noqa: SIM118
for name in self.keys():
self._columns[name] = Column(
[
x
+94 -38
View File
@@ -1,5 +1,4 @@
"""DVC config objects."""
import logging
import os
import re
from contextlib import contextmanager
@@ -9,14 +8,15 @@ from typing import TYPE_CHECKING, Dict, Optional
from funcy import compact, memoize, re_find
from dvc.exceptions import DvcException, NotDvcRepoError
from dvc.log import logger
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 = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class ConfigError(DvcException):
@@ -89,21 +89,31 @@ class Config(dict):
def __init__(
self,
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,
): # pylint: disable=super-init-not-called
remote: Optional[str] = None,
remote_config: Optional["DictStrAny"] = None,
):
from dvc.fs import LocalFileSystem
dvc_dir = os.fspath(dvc_dir) if dvc_dir else None
self.dvc_dir = dvc_dir
self.wfs = LocalFileSystem()
self.fs = fs or self.wfs
if dvc_dir:
self.dvc_dir = self.fs.path.abspath(self.fs.path.realpath(dvc_dir))
self.dvc_dir = self.fs.abspath(dvc_dir)
self.load(validate=validate, config=config)
self.local_dvc_dir = local_dvc_dir
if not fs and not local_dvc_dir:
self.local_dvc_dir = dvc_dir
self.load(
validate=validate, config=config, remote=remote, remote_config=remote_config
)
@classmethod
def from_cwd(cls, fs: Optional["FileSystem"] = None, **kwargs):
@@ -135,8 +145,10 @@ class Config(dict):
}
if self.dvc_dir is not None:
files["repo"] = self.fs.path.join(self.dvc_dir, self.CONFIG)
files["local"] = self.fs.path.join(self.dvc_dir, self.CONFIG_LOCAL)
files["repo"] = self.fs.join(self.dvc_dir, self.CONFIG)
if self.local_dvc_dir is not None:
files["local"] = self.wfs.join(self.local_dvc_dir, self.CONFIG_LOCAL)
return files
@@ -154,7 +166,16 @@ class Config(dict):
with open(config_file, "w+", encoding="utf-8"):
return Config(dvc_dir)
def load(self, validate: bool = True, config: Optional["DictStrAny"] = None):
def merge(self, config):
merge(self, config)
def load(
self,
validate: bool = True,
config: Optional["DictStrAny"] = None,
remote: Optional[str] = None,
remote_config: Optional["DictStrAny"] = None,
):
"""Loads config from all the config files.
Raises:
@@ -169,6 +190,17 @@ class Config(dict):
conf = self.validate(conf)
self.clear()
if remote:
conf["core"]["remote"] = remote
if remote_config:
remote = remote or conf["core"].get("remote")
if not remote:
raise ValueError("Missing remote name")
merge(conf, {"remote": {remote: remote_config}})
self.update(conf)
def _get_fs(self, level):
@@ -176,23 +208,32 @@ class Config(dict):
# the repo.
return self.fs if level == "repo" else self.wfs
def _load_config(self, level):
@staticmethod
def load_file(path, fs=None) -> dict:
from configobj import ConfigObj, ConfigObjError
from dvc.fs import localfs
fs = fs or localfs
with fs.open(path) as fobj:
try:
conf_obj = ConfigObj(fobj)
except UnicodeDecodeError as exc:
raise ConfigError(str(exc)) from exc
except ConfigObjError as exc:
raise ConfigError(str(exc)) from exc
return _parse_named(_lower_keys(conf_obj.dict()))
def _load_config(self, level):
filename = self.files[level]
fs = self._get_fs(level)
if fs.exists(filename):
with fs.open(filename) as fobj:
try:
conf_obj = ConfigObj(fobj)
except UnicodeDecodeError as exc:
raise ConfigError(str(exc)) from exc
except ConfigObjError as exc:
raise ConfigError(str(exc)) from exc
else:
conf_obj = ConfigObj()
return _parse_named(_lower_keys(conf_obj.dict()))
try:
return self.load_file(filename, fs=fs)
except FileNotFoundError:
return {}
def _save_config(self, level, conf_dict):
from configobj import ConfigObj
@@ -220,21 +261,30 @@ class Config(dict):
return conf
@staticmethod
def _load_paths(conf, filename):
abs_conf_dir = os.path.abspath(os.path.dirname(filename))
def _resolve(conf_dir, path):
from .config_schema import ExpPath, RelPath
def resolve(path):
from .config_schema import RelPath
if re.match(r"\w+://", path):
return path
if os.path.isabs(path) or re.match(r"\w+://", path):
return path
if os.path.isabs(path):
return path
# on windows convert slashes to backslashes
# to have path compatible with abs_conf_dir
if os.path.sep == "\\" and "/" in path:
path = path.replace("/", "\\")
# on windows convert slashes to backslashes
# to have path compatible with abs_conf_dir
if os.path.sep == "\\" and "/" in path:
path = path.replace("/", "\\")
return RelPath(os.path.join(abs_conf_dir, path))
expanded = os.path.expanduser(path)
if os.path.isabs(expanded):
return ExpPath(expanded, path)
return RelPath(os.path.abspath(os.path.join(conf_dir, path)))
@classmethod
def _load_paths(cls, conf, filename):
conf_dir = os.path.abspath(os.path.dirname(filename))
resolve = partial(cls._resolve, conf_dir)
return Config._map_dirs(conf, resolve)
@@ -243,14 +293,20 @@ class Config(dict):
from dvc.fs import localfs
from dvc.utils import relpath
from .config_schema import RelPath
from .config_schema import ExpPath, RelPath
if re.match(r"\w+://", path):
return path
if isinstance(path, ExpPath):
return path.def_path
if os.path.expanduser(path) != 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
@@ -334,10 +390,10 @@ class Config(dict):
def _parse_named(conf):
result: Dict[str, Dict] = {"remote": {}, "machine": {}}
result: Dict[str, Dict] = {"remote": {}, "machine": {}, "db": {}}
for section, val in conf.items():
match = re_find(r'^\s*(remote|machine)\s*"(.*)"\s*$', section)
match = re_find(r'^\s*(remote|machine|db)\s*"(.*)"\s*$', section)
if match:
key, name = match
result[key][name] = val
@@ -352,7 +408,7 @@ def _pack_named(conf):
result = compact(conf)
# Transform remote.name -> 'remote "name"'
for key in ("remote", "machine"):
for key in ("remote", "machine", "db"):
for name, val in conf[key].items():
result[f'{key} "{name}"'] = val
result.pop(key, None)
+30 -4
View File
@@ -1,4 +1,3 @@
import logging
import os
from urllib.parse import urlparse
@@ -8,6 +7,7 @@ from voluptuous import (
All,
Any,
Coerce,
Exclusive,
Invalid,
Lower,
Optional,
@@ -15,7 +15,9 @@ from voluptuous import (
Schema,
)
logger = logging.getLogger(__name__)
from dvc.log import logger
logger = logger.getChild(__name__)
Bool = All(
Lower,
@@ -74,8 +76,19 @@ def ByUrl(mapping): # noqa: N802
return validate
class ExpPath(str):
__slots__ = ("def_path",)
def_path: str
def __new__(cls, string, def_path):
ret = super().__new__(cls, string)
ret.def_path = def_path
return ret
class RelPath(str):
pass
__slots__ = ()
class FeatureSchema(Schema):
@@ -244,6 +257,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,
**REMOTE_COMMON,
},
"oss": {
@@ -302,6 +318,8 @@ SCHEMA = {
"feature": FeatureSchema(
{
Optional("machine", default=False): Bool,
"dbt_profile": str,
"dbt_target": str,
},
),
"plots": {
@@ -324,7 +342,8 @@ SCHEMA = {
},
"hydra": {
Optional("enabled", default=False): Bool,
"config_dir": str,
Exclusive("config_dir", "config_source"): str,
Exclusive("config_module", "config_source"): str,
"config_name": str,
},
"studio": {
@@ -333,4 +352,11 @@ SCHEMA = {
"repo_url": str,
Optional("offline", default=False): Bool,
},
"db": {
str: {
"url": str,
"username": str,
"password": str,
},
},
}
+128 -79
View File
@@ -3,136 +3,185 @@
import inspect
import logging
import os
import platform
import subprocess
import sys
from subprocess import Popen # nosec B404
from typing import List
from contextlib import nullcontext
from typing import TYPE_CHECKING, Any, List, Mapping, Optional, Sequence, Union
from dvc.env import DVC_DAEMON
from dvc.log import logger
if TYPE_CHECKING:
from typing import ContextManager
from dvc.env import DVC_DAEMON, DVC_DAEMON_LOGFILE
from dvc.utils import fix_env, is_binary
from dvc.utils.collections import ensure_list
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
def _suppress_resource_warning(popen: Popen):
def _suppress_resource_warning(popen: subprocess.Popen) -> None:
"""Sets the returncode to avoid ResourceWarning when popen is garbage collected."""
# only use for daemon processes.
# See https://bugs.python.org/issue38890.
popen.returncode = 0
def _popen(cmd, **kwargs) -> Popen:
prefix = [sys.executable]
def _win_detached_subprocess(args: Sequence[str], **kwargs) -> int:
assert os.name == "nt"
from subprocess import ( # type: ignore[attr-defined]
CREATE_NEW_PROCESS_GROUP,
CREATE_NO_WINDOW,
STARTF_USESHOWWINDOW,
STARTUPINFO,
)
# https://stackoverflow.com/a/7006424
# https://bugs.python.org/issue41619
creationflags = CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW
startupinfo = STARTUPINFO()
startupinfo.dwFlags |= STARTF_USESHOWWINDOW
popen = subprocess.Popen(
args,
close_fds=True,
shell=False, # noqa: S603
startupinfo=startupinfo,
creationflags=creationflags,
**kwargs,
)
_suppress_resource_warning(popen)
return popen.pid
def _get_dvc_args() -> List[str]:
args = [sys.executable]
if not is_binary():
main_entrypoint = os.path.join(
os.path.abspath(os.path.dirname(__file__)), "__main__.py"
)
prefix += [main_entrypoint]
return Popen(prefix + cmd, close_fds=True, shell=False, **kwargs) # nosec B603
root_dir = os.path.abspath(os.path.dirname(__file__))
main_entrypoint = os.path.join(root_dir, "__main__.py")
args.append(main_entrypoint)
return args
def _spawn_windows(cmd, env):
if sys.platform == "win32":
from subprocess import ( # nosec B404
CREATE_NEW_PROCESS_GROUP,
CREATE_NO_WINDOW,
STARTF_USESHOWWINDOW,
STARTUPINFO,
)
# https://stackoverflow.com/a/7006424
# https://bugs.python.org/issue41619
creationflags = CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW
startupinfo = STARTUPINFO()
startupinfo.dwFlags |= STARTF_USESHOWWINDOW
popen = _popen(
cmd, env=env, creationflags=creationflags, startupinfo=startupinfo
)
_suppress_resource_warning(popen)
def _spawn_posix(cmd, env):
from dvc.cli import main
# `fork` will copy buffers, so we need to flush them before forking.
# Otherwise, we will get duplicated outputs.
if sys.stdout and not sys.stdout.closed:
sys.stdout.flush()
if sys.stderr and not sys.stderr.closed:
sys.stderr.flush()
def _fork_process() -> int:
assert os.name == "posix"
# NOTE: using os._exit instead of sys.exit, because dvc built
# with PyInstaller has trouble with SystemExit exception and throws
# errors such as "[26338] Failed to execute script __main__"
try:
# pylint: disable-next=no-member
pid = os.fork() # type: ignore[attr-defined]
if pid > 0:
return
return pid
except OSError:
logger.exception("failed at first fork")
os._exit(1) # pylint: disable=protected-access
os._exit(1)
os.setsid() # type: ignore[attr-defined] # pylint: disable=no-member
os.setsid() # type: ignore[attr-defined]
try:
# pylint: disable-next=no-member
pid = os.fork() # type: ignore[attr-defined]
if pid > 0:
os._exit(0) # pylint: disable=protected-access
os._exit(0)
except OSError:
logger.exception("failed at second fork")
os._exit(1) # pylint: disable=protected-access
os._exit(1)
sys.stdin.close()
sys.stdout.close()
sys.stderr.close()
os.closerange(0, 3)
if platform.system() == "Darwin":
# workaround for MacOS bug
# https://github.com/iterative/dvc/issues/4294
_popen(cmd, env=env).communicate()
else:
os.environ.update(env)
main(cmd)
os._exit(0) # pylint: disable=protected-access
# disconnect from the terminal
fd = os.open(os.devnull, os.O_RDWR)
for fd2 in range(3):
os.dup2(fd, fd2)
os.close(fd)
return pid
def _spawn(cmd, env):
logger.debug("Trying to spawn '%s'", cmd)
def _posix_detached_subprocess(args: Sequence[str], **kwargs) -> int:
# double fork and execute a subprocess so that there are no zombies
read_end, write_end = os.pipe()
pid = _fork_process()
if pid > 0: # in parent
os.close(write_end)
pid_str = os.read(read_end, 32).decode("utf8")
os.close(read_end)
return int(pid_str)
proc = subprocess.Popen(
args,
shell=False, # noqa: S603
close_fds=True,
**kwargs,
)
os.close(read_end)
os.write(write_end, str(proc.pid).encode("utf8"))
os.close(write_end)
exit_code = proc.wait()
os._exit(exit_code)
def _detached_subprocess(args: Sequence[str], **kwargs) -> int:
"""Run in a detached subprocess."""
kwargs.setdefault("stdin", subprocess.DEVNULL)
kwargs.setdefault("stdout", subprocess.DEVNULL)
kwargs.setdefault("stderr", subprocess.DEVNULL)
if os.name == "nt":
_spawn_windows(cmd, env)
elif os.name == "posix":
_spawn_posix(cmd, env)
else:
raise NotImplementedError
logger.debug("Spawned '%s'", cmd)
return _win_detached_subprocess(args, **kwargs)
return _posix_detached_subprocess(args, **kwargs)
def daemon(args):
def _map_log_level_to_flag() -> Optional[str]:
flags = {logging.DEBUG: "-v", logging.TRACE: "-vv"} # type: ignore[attr-defined]
return flags.get(logger.getEffectiveLevel())
def daemon(args: List[str]) -> None:
"""Launch a `dvc daemon` command in a detached process.
Args:
args (list): list of arguments to append to `dvc daemon` command.
"""
daemonize(["daemon", "-q", *args])
if flag := _map_log_level_to_flag():
args = [*args, flag]
daemonize(["daemon", *args])
def daemonize(cmd: List[str]):
def _spawn(
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()
kwargs = {}
if output_file:
file = open(output_file, "ab") # noqa: SIM115
kwargs = {"stdout": file, "stderr": file}
if executable is None:
executable = _get_dvc_args()
else:
executable = ensure_list(executable)
with file:
return _detached_subprocess(executable + args, env=env, **kwargs)
def daemonize(args: List[str], executable: Union[None, str, List[str]] = None) -> None:
if os.name not in ("posix", "nt"):
return None
if os.environ.get(DVC_DAEMON):
logger.debug("skipping launching a new daemon.")
return
env = fix_env()
env[DVC_DAEMON] = "1"
if not is_binary():
file_path = os.path.abspath(inspect.stack()[0][1])
env["PYTHONPATH"] = os.path.dirname(os.path.dirname(file_path))
env[DVC_DAEMON] = "1"
_spawn(cmd, env)
logger.debug("Trying to spawn %r", args)
pid = _spawn(args, executable, env, output_file=env.get(DVC_DAEMON_LOGFILE))
logger.debug("Spawned %r with pid %s", args, pid)
+33 -11
View File
@@ -1,6 +1,5 @@
"""Draws DAG in ASCII."""
import logging
import math
import os
@@ -8,7 +7,9 @@ from grandalf.graphs import Edge, Graph, Vertex
from grandalf.layouts import SugiyamaLayout
from grandalf.routing import EdgeViewer, route_with_lines
logger = logging.getLogger(__name__)
from dvc.log import logger
logger = logger.getChild(__name__)
class VertexViewer:
@@ -22,17 +23,16 @@ class VertexViewer:
HEIGHT = 3 # top and bottom box edges + text
def __init__(self, name):
# pylint: disable=invalid-name
self._h = self.HEIGHT # top and bottom box edges + text
self._w = len(name) + 2 # right and left bottom edges + text
@property
def h(self): # pylint: disable=invalid-name
def h(self):
"""Height of the box."""
return self._h
@property
def w(self): # pylint: disable=invalid-name
def w(self):
"""Width of the box."""
return self._w
@@ -90,7 +90,6 @@ class AsciiCanvas:
y1 (int): y coordinate where the line should end.
char (str): character to draw the line with.
"""
# pylint: disable=too-many-arguments, too-many-branches
if x0 > x1:
x1, x0 = x0, x1
y1, y0 = y0, y1
@@ -209,12 +208,36 @@ def draw(vertices, edges):
Args:
vertices (list): list of graph vertices.
edges (list): list of graph edges.
Returns:
str: ASCII representation
Example:
>>> from dvc.dagascii import draw
>>> vertices = [1, 2, 3, 4]
>>> edges = [(1, 2), (2, 3), (2, 4), (1, 4)]
>>> print(draw(vertices, edges))
+---+ +---+
| 3 | | 4 |
+---+ *+---+
* ** *
* ** *
* * *
+---+ *
| 2 | *
+---+ *
* *
* *
**
+---+
| 1 |
+---+
"""
# pylint: disable=too-many-locals
# NOTE: coordinates might me negative, so we need to shift
# everything to the positive plane before we actually draw it.
Xs = [] # noqa: N806, pylint: disable=invalid-name
Ys = [] # noqa: N806, pylint: disable=invalid-name
Xs = [] # noqa: N806
Ys = [] # noqa: N806
sug = _build_sugiyama_layout(vertices, edges)
@@ -226,7 +249,7 @@ def draw(vertices, edges):
Ys.append(vertex.view.xy[1] + vertex.view.h)
for edge in sug.g.sE:
for x, y in edge.view._pts: # pylint: disable=protected-access
for x, y in edge.view._pts:
Xs.append(x)
Ys.append(y)
@@ -242,7 +265,6 @@ def draw(vertices, edges):
# NOTE: first draw edges so that node boxes could overwrite them
for edge in sug.g.sE:
# pylint: disable=protected-access
assert len(edge.view._pts) > 1
for index in range(1, len(edge.view._pts)):
start = edge.view._pts[index - 1]
+39 -27
View File
@@ -1,9 +1,9 @@
"""Manages dvc remotes that user can use with push/pull/status commands."""
import logging
from typing import TYPE_CHECKING, Iterable, Optional, Set, Tuple
from dvc.config import NoRemoteError, RemoteConfigError
from dvc.log import logger
from dvc.utils.objects import cached_property
from dvc_data.hashfile.db import get_index
from dvc_data.hashfile.transfer import TransferResult
@@ -14,7 +14,7 @@ if TYPE_CHECKING:
from dvc_data.hashfile.hash_info import HashInfo
from dvc_data.hashfile.status import CompareStatusResult
logger = logging.getLogger(__name__)
logger = logger.getChild(__name__)
class Remote:
@@ -35,11 +35,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
@@ -90,7 +88,7 @@ class DataCloud:
if name:
from dvc.fs import get_cloud_fs
cls, config, fs_path = get_cloud_fs(self.repo, name=name)
cls, config, fs_path = get_cloud_fs(self.repo.config, name=name)
if config.get("worktree"):
version_aware = config.get("version_aware")
@@ -134,7 +132,7 @@ class DataCloud:
remote = self.get_remote(name=name, command=command)
if remote.fs.version_aware or remote.worktree:
raise NoRemoteError(
raise RemoteConfigError(
f"'{command}' is unsupported for cloud versioned remotes"
)
if hash_name in LEGACY_HASH_NAMES:
@@ -205,19 +203,26 @@ 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
return self.transfer(
cache,
odb,
objs,
jobs=jobs,
dest_index=get_index(odb),
cache_odb=cache,
validate_status=self._log_missing,
)
with TqdmCallback(
desc=f"Pushing to {odb.fs.unstrip_protocol(odb.path)}",
unit="file",
) as cb:
return self.transfer(
cache,
odb,
objs,
jobs=jobs,
dest_index=get_index(odb),
cache_odb=cache,
validate_status=self._log_missing,
callback=cb,
)
def pull(
self,
@@ -259,20 +264,27 @@ 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
return self.transfer(
odb,
cache,
objs,
jobs=jobs,
src_index=get_index(odb),
cache_odb=cache,
verify=odb.verify,
validate_status=self._log_missing,
)
with TqdmCallback(
desc=f"Fetching from {odb.fs.unstrip_protocol(odb.path)}",
unit="file",
) as cb:
return self.transfer(
odb,
cache,
objs,
jobs=jobs,
src_index=get_index(odb),
cache_odb=cache,
verify=odb.verify,
validate_status=self._log_missing,
callback=cb,
)
def status(
self,
+22
View File
@@ -0,0 +1,22 @@
from typing import Any, ContextManager, Optional, Union
from . import dbt_query, sqla
from .dbt_models import get_model
from .serializer import export
Client = Union[sqla.SQLAlchemyClient, dbt_query.DbtClient]
def get_client(
config,
project_dir: Optional[str] = None,
profile: Optional[str] = None,
target: Optional[str] = None,
**kwargs: Any,
) -> "ContextManager[Client]":
if config:
return sqla.client(config, **kwargs)
return dbt_query.client(project_dir=project_dir, profile=profile, target=target)
__all__ = ["export", "get_client", "get_model", "Client"]

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