mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-04 07:18:27 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c3956bdb0 | ||
|
|
a48a19ce31 | ||
|
|
1de983f0de | ||
|
|
eb20a8e049 | ||
|
|
78e25747a4 | ||
|
|
240f0ebdc3 | ||
|
|
69f78d3bb6 | ||
|
|
740fe504c9 | ||
|
|
7bb84286f5 | ||
|
|
3891e81bdd | ||
|
|
bb62833897 | ||
|
|
fb703150ce | ||
|
|
33d4ead8b6 | ||
|
|
76b1f8c907 | ||
|
|
d7152e360e | ||
|
|
ab69b88a21 | ||
|
|
43a8eab2e0 | ||
|
|
15f35e827e | ||
|
|
4d17c171f1 | ||
|
|
c27005a8b7 | ||
|
|
ecd371f8ae | ||
|
|
bda1388bff | ||
|
|
c02bd7f318 | ||
|
|
fe1d805c80 | ||
|
|
f9b0de883f | ||
|
|
1cad8ea7fd | ||
|
|
08a643e005 | ||
|
|
97a8b6978c | ||
|
|
b44099367e | ||
|
|
29b3dc1f9e | ||
|
|
6e73bd0b5b | ||
|
|
fa5a9abdeb | ||
|
|
30775f7b31 | ||
|
|
e07aca9fd3 | ||
|
|
d2265f36ba | ||
|
|
d548618291 | ||
|
|
48ff332f4a | ||
|
|
97f5ffc3c6 | ||
|
|
f6849d2f06 | ||
|
|
a172240536 | ||
|
|
129a4fbb92 | ||
|
|
c101514677 | ||
|
|
5e883bc00c | ||
|
|
4b720e5820 | ||
|
|
c0054fa57e | ||
|
|
6049494cef | ||
|
|
120a5879bb | ||
|
|
c8e39523c3 | ||
|
|
7648e715f8 | ||
|
|
ca5ad6457f | ||
|
|
de848d9598 | ||
|
|
4330f1a70f | ||
|
|
144e21b552 | ||
|
|
7945428e5e | ||
|
|
6df70d388e | ||
|
|
4ca2158b9d | ||
|
|
f87e78d754 |
@@ -1,10 +0,0 @@
|
||||
comment: false
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 90
|
||||
threshold: 10%
|
||||
patch:
|
||||
default:
|
||||
threshold: 25%
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
[run]
|
||||
branch = True
|
||||
source = dvc
|
||||
omit =
|
||||
*__main__.py
|
||||
|
||||
[report]
|
||||
exclude_lines =
|
||||
if __name__ == .__main__.:
|
||||
if typing.TYPE_CHECKING:
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
raise NotImplementedError
|
||||
# pragma: no cover
|
||||
# pragma: no cover
|
||||
\.\.\.$
|
||||
@@ -1,4 +1,3 @@
|
||||
/tests
|
||||
/dvc
|
||||
/.github
|
||||
/bin
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[flake8]
|
||||
ignore=
|
||||
E203, # Whitespace before ':'
|
||||
E266, # Too many leading '#' for block comment
|
||||
W503, # Line break occurred before a binary operator
|
||||
P1, # unindexed parameters in the str.format, see:
|
||||
# https://pypi.org/project/flake8-string-format/
|
||||
max_line_length = 79
|
||||
max-complexity = 15
|
||||
select = B,C,E,F,W,T4,B902,T,P
|
||||
show_source = true
|
||||
count = true
|
||||
@@ -8,7 +8,7 @@ about: Create a bug report to help us improve DVC
|
||||
<!--
|
||||
## Issue name
|
||||
|
||||
Issue names must follow the pattern `command: description` where the command is the dvc command that you are trying to run. The description should describe the consequence of the bug.
|
||||
Issue names must follow the pattern `command: description` where the command is the dvc command that you are trying to run. The description should describe the consequence of the bug.
|
||||
|
||||
Example: `repro: doesn't detect input changes`
|
||||
-->
|
||||
|
||||
@@ -2,4 +2,3 @@
|
||||
name: "\U0001F680 Feature Request"
|
||||
about: Suggest an idea for this project
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
codecov:
|
||||
notify:
|
||||
wait_for_ci: true
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 2%
|
||||
patch: off
|
||||
github_checks:
|
||||
annotations: false
|
||||
@@ -0,0 +1,16 @@
|
||||
changelog:
|
||||
exclude:
|
||||
labels: ["skip-changelog"]
|
||||
categories:
|
||||
- title: 🛠 Breaking Changes
|
||||
labels: ["breaking-change"]
|
||||
- title: 🚀 New Features and Enhancements
|
||||
labels: ["feature", "enhancement", "ui"]
|
||||
- title: ⚡ Optimizations
|
||||
labels: ["optimize", "performance"]
|
||||
- title: 🐛 Bug Fixes
|
||||
labels: ["bugfix", "bug"]
|
||||
- title: 🔨 Maintenance
|
||||
labels: ["maintenance", "refactoring", "chore", "build", "ci"]
|
||||
- title: Other Changes
|
||||
labels: ["*"]
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
python-version: "3.11"
|
||||
- uses: iterative/dvc-bench@main
|
||||
with:
|
||||
pytest_options: "-k 'test_init or test_help'"
|
||||
|
||||
@@ -7,8 +7,13 @@ on:
|
||||
types: [released, prereleased]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: # added using https://github.com/step-security/secure-workflows
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
binary:
|
||||
permissions:
|
||||
contents: write # for actions/upload-release-asset to upload release asset
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -24,13 +29,13 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python 3.9
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: "3.10"
|
||||
cache: pip
|
||||
cache-dependency-path: |
|
||||
setup.cfg
|
||||
pyproject.toml
|
||||
scripts/build-requirements.txt
|
||||
|
||||
- name: Set up Ruby 2.6
|
||||
@@ -45,7 +50,7 @@ jobs:
|
||||
|
||||
- name: Install deps
|
||||
run: |
|
||||
pip install -U wheel pip setuptools
|
||||
pip install --upgrade pip wheel setuptools
|
||||
pip install .[all]
|
||||
pip install -r scripts/build-requirements.txt
|
||||
|
||||
@@ -77,21 +82,31 @@ jobs:
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.8
|
||||
cache: pip
|
||||
cache-dependency-path: setup.cfg
|
||||
|
||||
- name: Install
|
||||
run: pip install --upgrade pip wheel
|
||||
|
||||
- name: Force version for Test PyPI uploads
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags') }}
|
||||
run: |
|
||||
pip install -U wheel pip build setuptools
|
||||
pip install ".[all,tests]"
|
||||
pip install setuptools_scm
|
||||
echo version=$(python -m setuptools_scm | awk -F+ '{print $1}' | tail -1) >> $GITHUB_ENV
|
||||
|
||||
- name: Build packages
|
||||
run: |
|
||||
pip install -U pip
|
||||
./scripts/build_package.sh
|
||||
run: ./scripts/build_package.sh
|
||||
env:
|
||||
SETUPTOOLS_SCM_PRETEND_VERSION: ${{ env.version }}
|
||||
|
||||
- name: Publish packages to PyPI
|
||||
if: github.event_name == 'release'
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
password: ${{ secrets.PYPI_TOKEN }}
|
||||
|
||||
- name: Publish to Test PyPI
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
password: ${{ secrets.TEST_PYPI_TOKEN }}
|
||||
repository_url: https://test.pypi.org/legacy/
|
||||
skip_existing: true
|
||||
|
||||
@@ -17,6 +17,9 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: # added using https://github.com/step-security/secure-workflows
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
timeout-minutes: 10
|
||||
@@ -29,13 +32,12 @@ jobs:
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.8
|
||||
cache: pip
|
||||
cache-dependency-path: pyproject.toml
|
||||
- name: Install requirements
|
||||
run: |
|
||||
pip install --upgrade pip setuptools wheel
|
||||
pip install collective.checkdocs==0.2
|
||||
pip install --upgrade pip wheel
|
||||
pip install ".[dev]"
|
||||
- name: Check README
|
||||
run: python setup.py checkdocs
|
||||
- uses: pre-commit/[email protected]
|
||||
tests:
|
||||
timeout-minutes: 45
|
||||
@@ -43,13 +45,14 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, windows-latest, macos-latest]
|
||||
os: [ubuntu-20.04, windows-latest]
|
||||
pyv: ["3.8", "3.9", "3.10"]
|
||||
pytest-filter:
|
||||
pytest-filter:
|
||||
- "import or plot or live or experiment"
|
||||
- "not (import or plot or live or experiment)"
|
||||
include:
|
||||
- {os: ubuntu-latest, pyv: "3.11-dev"}
|
||||
- {os: ubuntu-latest, pyv: "3.11"}
|
||||
- {os: macos-latest, pyv: "3.8"}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -60,25 +63,30 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ matrix.pyv }}
|
||||
cache: pip
|
||||
cache-dependency-path: setup.cfg
|
||||
cache-dependency-path: pyproject.toml
|
||||
- name: install
|
||||
if: steps.cache.pip-cache-dir.cache-hit != 'true'
|
||||
run: |
|
||||
pip install --upgrade pip setuptools wheel
|
||||
pip install --upgrade pip wheel
|
||||
pip install -e ".[dev]"
|
||||
- name: run tests
|
||||
timeout-minutes: 40
|
||||
run: pytest -nauto -ra --durations 100 --cov=dvc --cov-report=xml --cov-report=term -k "${{ matrix.pytest-filter }}"
|
||||
timeout-minutes: 20
|
||||
run: pytest -nauto --durations 100 --cov --cov-report=xml --cov-report=term -k "${{ matrix.pytest-filter }}"
|
||||
- name: upload coverage report
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
fail_ci_if_error: false
|
||||
check:
|
||||
if: always()
|
||||
needs: [lint, tests]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: re-actors/alls-green@release/v1
|
||||
with:
|
||||
jobs: ${{ toJSON(needs) }}
|
||||
notify:
|
||||
if: github.ref == 'refs/heads/main' && failure()
|
||||
needs:
|
||||
- lint
|
||||
- tests
|
||||
needs: [lint, tests]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Slack Notification
|
||||
|
||||
+43
-28
@@ -1,39 +1,62 @@
|
||||
default_language_version:
|
||||
python: python3
|
||||
|
||||
ci:
|
||||
skip: [mypy, pylint, dvc-pre-commit]
|
||||
|
||||
repos:
|
||||
- hooks:
|
||||
- id: black
|
||||
language_version: python3
|
||||
repo: https://github.com/ambv/black
|
||||
rev: 22.10.0
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.3.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-case-conflict
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-json
|
||||
- id: check-merge-conflict
|
||||
exclude: "tests/func/utils/test_strict_yaml.py"
|
||||
args: ['--assume-in-merge']
|
||||
- id: check-toml
|
||||
- id: check-yaml
|
||||
- hooks:
|
||||
- id: codespell
|
||||
args:
|
||||
- --ignore-words-list
|
||||
- ba,datas,fo,uptodate,cachable,falsy
|
||||
repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.2
|
||||
- hooks:
|
||||
- id: isort
|
||||
language_version: python3
|
||||
repo: https://github.com/timothycrosley/isort
|
||||
- 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.2.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args:
|
||||
- --py38-plus
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.10.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.10.1
|
||||
- hooks:
|
||||
hooks:
|
||||
- id: isort
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.2
|
||||
hooks:
|
||||
- id: codespell
|
||||
additional_dependencies: ["tomli"]
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 5.0.4
|
||||
hooks:
|
||||
- id: flake8
|
||||
language_version: python3
|
||||
additional_dependencies:
|
||||
- flake8-broken-line
|
||||
- flake8-bugbear
|
||||
- flake8-comprehensions
|
||||
- flake8-debugger
|
||||
- flake8-string-format
|
||||
repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.9.2
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: mypy
|
||||
@@ -47,14 +70,6 @@ repos:
|
||||
entry: pylint
|
||||
language: system
|
||||
types: [python]
|
||||
- hooks:
|
||||
- args:
|
||||
- -i
|
||||
- "2"
|
||||
id: beautysh
|
||||
language_version: python3
|
||||
repo: https://github.com/lovesegfault/beautysh
|
||||
rev: v6.2.1
|
||||
- hooks:
|
||||
- args:
|
||||
- git-hook
|
||||
|
||||
@@ -74,4 +74,3 @@ available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.ht
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
include LICENSE
|
||||
recursive-include requirements *.txt
|
||||
recursive-include dvc/repo/plots/templates *.json
|
||||
+6
-2
@@ -13,7 +13,7 @@
|
||||
|
||||
|CI| |Python Version| |Coverage| |VS Code| |DOI|
|
||||
|
||||
|PyPI| |Packages| |Brew| |Conda| |Choco| |Snap|
|
||||
|PyPI| |PyPI Downloads| |Packages| |Brew| |Conda| |Choco| |Snap|
|
||||
|
||||
|
|
||||
|
||||
@@ -276,6 +276,10 @@ Barrak, A., Eghan, E.E. and Adams, B. `On the Co-evolution of ML Pipelines and S
|
||||
:target: https://pypi.org/project/dvc
|
||||
:alt: PyPI
|
||||
|
||||
.. |PyPI Downloads| image:: https://img.shields.io/pypi/dm/dvc.svg?color=blue&label=Downloads&logo=pypi&logoColor=gold
|
||||
:target: https://pypi.org/project/dvc
|
||||
:alt: PyPI Downloads
|
||||
|
||||
.. |Packages| image:: https://img.shields.io/github/v/release/iterative/dvc?label=deb|pkg|rpm|exe&logo=GitHub
|
||||
:target: https://github.com/iterative/dvc/releases/latest
|
||||
:alt: deb|pkg|rpm|exe
|
||||
@@ -292,6 +296,6 @@ Barrak, A., Eghan, E.E. and Adams, B. `On the Co-evolution of ML Pipelines and S
|
||||
:target: https://github.com/iterative/dvc/graphs/contributors
|
||||
:alt: Contributors
|
||||
|
||||
.. |VS Code| image:: https://vsmarketplacebadge.apphb.com/version/Iterative.dvc.svg
|
||||
.. |VS Code| image:: https://img.shields.io/visual-studio-marketplace/v/Iterative.dvc?color=blue&label=VSCode&logo=visualstudiocode&logoColor=blue
|
||||
:target: https://marketplace.visualstudio.com/items?itemName=Iterative.dvc
|
||||
:alt: VS Code Extension
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
@echo OFF
|
||||
set PYTHONPATH=%DVC_HOME%
|
||||
python -m dvc %*
|
||||
+1
-1
@@ -4,6 +4,6 @@ DVC
|
||||
Make your data science projects reproducible and shareable.
|
||||
"""
|
||||
import dvc.logger
|
||||
from dvc.version import __version__ # noqa: F401
|
||||
from dvc.version import __version__, version_tuple # noqa: F401
|
||||
|
||||
dvc.logger.setup()
|
||||
|
||||
@@ -238,26 +238,6 @@ def _add_common_args(parser):
|
||||
help="Declare output plot file (do not put into DVC cache).",
|
||||
metavar="<path>",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--live", help="Declare output as dvclive.", metavar="<path>"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--live-no-cache",
|
||||
help="Declare output as dvclive (do not put into DVC cache).",
|
||||
metavar="<path>",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--live-no-summary",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--live-no-html",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Signal dvclive logger to not produce training report.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-w",
|
||||
"--wdir",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from collections import defaultdict
|
||||
from typing import Any, Mapping
|
||||
|
||||
from dvc.output import ARTIFACT_SCHEMA, Output
|
||||
from dvc.output import ARTIFACT_SCHEMA, DIR_FILES_SCHEMA, Output
|
||||
|
||||
from .base import Dependency
|
||||
from .param import ParamsDependency
|
||||
@@ -15,10 +15,11 @@ SCHEMA: Mapping[str, Any] = {
|
||||
**ARTIFACT_SCHEMA,
|
||||
**RepoDependency.REPO_SCHEMA,
|
||||
**ParamsDependency.PARAM_SCHEMA,
|
||||
Output.PARAM_FILES: [DIR_FILES_SCHEMA],
|
||||
}
|
||||
|
||||
|
||||
def _get(stage, p, info, fs_config=None):
|
||||
def _get(stage, p, info, **kwargs):
|
||||
if info and info.get(RepoDependency.PARAM_REPO):
|
||||
repo = info.pop(RepoDependency.PARAM_REPO)
|
||||
return RepoDependency(repo, stage, p, info)
|
||||
@@ -27,14 +28,15 @@ def _get(stage, p, info, fs_config=None):
|
||||
params = info.pop(ParamsDependency.PARAM_PARAMS)
|
||||
return ParamsDependency(stage, p, params)
|
||||
|
||||
return Dependency(stage, p, info, fs_config=fs_config)
|
||||
return Dependency(stage, p, info, **kwargs)
|
||||
|
||||
|
||||
def loadd_from(stage, d_list):
|
||||
ret = []
|
||||
for d in d_list:
|
||||
p = d.pop(Output.PARAM_PATH, None)
|
||||
ret.append(_get(stage, p, d))
|
||||
files = d.pop(Output.PARAM_FILES, None)
|
||||
ret.append(_get(stage, p, d, files=files))
|
||||
return ret
|
||||
|
||||
|
||||
|
||||
@@ -62,3 +62,8 @@ class Dependency(Output):
|
||||
self.fs_path = self.fs.path.version_path(
|
||||
self.fs_path, self.meta.version_id
|
||||
)
|
||||
|
||||
def dumpd(self, **kwargs):
|
||||
if self.fs.version_aware:
|
||||
kwargs["with_files"] = True
|
||||
return super().dumpd(**kwargs)
|
||||
|
||||
@@ -2,9 +2,6 @@ DVC_CHECKPOINT = "DVC_CHECKPOINT"
|
||||
DVC_DAEMON = "DVC_DAEMON"
|
||||
DVC_PAGER = "DVC_PAGER"
|
||||
DVC_ROOT = "DVC_ROOT"
|
||||
DVCLIVE_PATH = "DVCLIVE_PATH"
|
||||
DVCLIVE_SUMMARY = "DVCLIVE_SUMMARY"
|
||||
DVCLIVE_HTML = "DVCLIVE_HTML"
|
||||
DVCLIVE_RESUME = "DVCLIVE_RESUME"
|
||||
DVC_IGNORE_ISATTY = "DVC_IGNORE_ISATTY"
|
||||
DVC_EXP_GIT_REMOTE = "DVC_EXP_GIT_REMOTE"
|
||||
|
||||
@@ -26,7 +26,7 @@ class BaseMachineBackend(ABC):
|
||||
) -> Iterator[dict]:
|
||||
"""Iterate over status of all instances of the specified machine."""
|
||||
|
||||
def close(self):
|
||||
def close(self): # noqa: B027
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
|
||||
+56
-64
@@ -87,7 +87,6 @@ def loadd_from(stage, d_list):
|
||||
plot = d.pop(Output.PARAM_PLOT, False)
|
||||
persist = d.pop(Output.PARAM_PERSIST, False)
|
||||
checkpoint = d.pop(Output.PARAM_CHECKPOINT, False)
|
||||
live = d.pop(Output.PARAM_LIVE, False)
|
||||
remote = d.pop(Output.PARAM_REMOTE, None)
|
||||
annot = {field: d.pop(field, None) for field in ANNOTATION_FIELDS}
|
||||
files = d.pop(Output.PARAM_FILES, None)
|
||||
@@ -101,7 +100,6 @@ def loadd_from(stage, d_list):
|
||||
plot=plot,
|
||||
persist=persist,
|
||||
checkpoint=checkpoint,
|
||||
live=live,
|
||||
remote=remote,
|
||||
**annot,
|
||||
files=files,
|
||||
@@ -118,7 +116,6 @@ def loads_from(
|
||||
plot=False,
|
||||
persist=False,
|
||||
checkpoint=False,
|
||||
live=False,
|
||||
remote=None,
|
||||
):
|
||||
return [
|
||||
@@ -131,7 +128,6 @@ def loads_from(
|
||||
plot=plot,
|
||||
persist=persist,
|
||||
checkpoint=checkpoint,
|
||||
live=live,
|
||||
remote=remote,
|
||||
)
|
||||
for s in s_list
|
||||
@@ -166,29 +162,21 @@ def load_from_pipeline(stage, data, typ="outs"):
|
||||
stage.PARAM_OUTS,
|
||||
stage.PARAM_METRICS,
|
||||
stage.PARAM_PLOTS,
|
||||
stage.PARAM_LIVE,
|
||||
):
|
||||
raise ValueError(f"'{typ}' key is not allowed for pipeline files.")
|
||||
|
||||
metric = typ == stage.PARAM_METRICS
|
||||
plot = typ == stage.PARAM_PLOTS
|
||||
live = typ == stage.PARAM_LIVE
|
||||
if live:
|
||||
# `live` is single object
|
||||
data = [data]
|
||||
|
||||
d = _merge_data(data)
|
||||
|
||||
for path, flags in d.items():
|
||||
plt_d, live_d = {}, {}
|
||||
plt_d = {}
|
||||
if plot:
|
||||
from dvc.schema import PLOT_PROPS
|
||||
|
||||
plt_d, flags = _split_dict(flags, keys=PLOT_PROPS.keys())
|
||||
if live:
|
||||
from dvc.schema import LIVE_PROPS
|
||||
|
||||
live_d, flags = _split_dict(flags, keys=LIVE_PROPS.keys())
|
||||
extra = project(
|
||||
flags,
|
||||
[
|
||||
@@ -206,7 +194,6 @@ def load_from_pipeline(stage, data, typ="outs"):
|
||||
info={},
|
||||
plot=plt_d or plot,
|
||||
metric=metric,
|
||||
live=live_d or live,
|
||||
**extra,
|
||||
)
|
||||
|
||||
@@ -263,9 +250,6 @@ class Output:
|
||||
PARAM_PLOT_TITLE = "title"
|
||||
PARAM_PLOT_HEADER = "header"
|
||||
PARAM_PERSIST = "persist"
|
||||
PARAM_LIVE = "live"
|
||||
PARAM_LIVE_SUMMARY = "summary"
|
||||
PARAM_LIVE_HTML = "html"
|
||||
PARAM_REMOTE = "remote"
|
||||
|
||||
METRIC_SCHEMA = Any(
|
||||
@@ -292,7 +276,6 @@ class Output:
|
||||
plot=False,
|
||||
persist=False,
|
||||
checkpoint=False,
|
||||
live=False,
|
||||
desc=None,
|
||||
type=None, # pylint: disable=redefined-builtin
|
||||
labels=None,
|
||||
@@ -355,7 +338,6 @@ class Output:
|
||||
self.plot = False if self.IS_DEPENDENCY else plot
|
||||
self.persist = persist
|
||||
self.checkpoint = checkpoint
|
||||
self.live = live
|
||||
|
||||
self.fs_path = self._parse_path(self.fs, fs_path)
|
||||
self.obj: Optional["HashFile"] = None
|
||||
@@ -377,8 +359,12 @@ class Output:
|
||||
name=self.hash_name,
|
||||
value=getattr(self.meta, self.hash_name, None),
|
||||
)
|
||||
if self.hash_info and self.hash_info.isdir:
|
||||
if self.meta.nfiles or self.hash_info and self.hash_info.isdir:
|
||||
self.meta.isdir = True
|
||||
if not self.hash_info and self.hash_name != "md5":
|
||||
md5 = getattr(self.meta, "md5", None)
|
||||
if md5:
|
||||
self.hash_info = HashInfo("md5", md5)
|
||||
|
||||
def _parse_path(self, fs, fs_path):
|
||||
parsed = urlparse(self.def_path)
|
||||
@@ -523,7 +509,10 @@ class Output:
|
||||
except NoRemoteError:
|
||||
remote = None
|
||||
|
||||
return DataIndexEntry(
|
||||
if self.files and not self.obj:
|
||||
self.obj = self.get_obj()
|
||||
|
||||
entry = DataIndexEntry(
|
||||
meta=self.meta,
|
||||
obj=self.obj,
|
||||
hash_info=self.hash_info,
|
||||
@@ -531,6 +520,16 @@ class Output:
|
||||
cache=self.odb,
|
||||
remote=remote,
|
||||
)
|
||||
if self.stage.is_import and not self.stage.is_repo_import:
|
||||
dep = self.stage.deps[0]
|
||||
entry.fs = dep.fs
|
||||
entry.path = dep.fs_path
|
||||
entry.meta = dep.meta
|
||||
if not entry.obj:
|
||||
if not dep.obj:
|
||||
dep.obj = dep.get_obj()
|
||||
entry.obj = dep.obj
|
||||
return entry
|
||||
|
||||
def changed_checksum(self):
|
||||
return self.hash_info != self.get_hash()
|
||||
@@ -631,8 +630,16 @@ class Output:
|
||||
if self.metric:
|
||||
self.verify_metric()
|
||||
|
||||
if not self.use_cache:
|
||||
_, self.meta, obj = build(
|
||||
if self.use_cache:
|
||||
_, self.meta, self.obj = build(
|
||||
self.odb,
|
||||
self.fs_path,
|
||||
self.fs,
|
||||
self.hash_name,
|
||||
ignore=self.dvcignore,
|
||||
)
|
||||
else:
|
||||
_, self.meta, self.obj = build(
|
||||
self.repo.odb.local,
|
||||
self.fs_path,
|
||||
self.fs,
|
||||
@@ -640,23 +647,11 @@ class Output:
|
||||
ignore=self.dvcignore,
|
||||
dry_run=True,
|
||||
)
|
||||
self.hash_info = obj.hash_info
|
||||
self.files = None
|
||||
if not self.IS_DEPENDENCY:
|
||||
logger.debug(
|
||||
"Output '%s' doesn't use cache. Skipping saving.", self
|
||||
)
|
||||
return
|
||||
|
||||
assert not self.IS_DEPENDENCY
|
||||
|
||||
_, self.meta, self.obj = build(
|
||||
self.odb,
|
||||
self.fs_path,
|
||||
self.fs,
|
||||
self.hash_name,
|
||||
ignore=self.dvcignore,
|
||||
)
|
||||
self.hash_info = self.obj.hash_info
|
||||
self.files = None
|
||||
|
||||
@@ -754,40 +749,37 @@ class Output:
|
||||
|
||||
ret[self.PARAM_PATH] = path
|
||||
|
||||
if self.IS_DEPENDENCY:
|
||||
return ret
|
||||
if not self.IS_DEPENDENCY:
|
||||
ret.update(self.annot.to_dict())
|
||||
if not self.use_cache:
|
||||
ret[self.PARAM_CACHE] = self.use_cache
|
||||
|
||||
ret.update(self.annot.to_dict())
|
||||
if not self.use_cache:
|
||||
ret[self.PARAM_CACHE] = self.use_cache
|
||||
if isinstance(self.metric, dict):
|
||||
if (
|
||||
self.PARAM_METRIC_XPATH in self.metric
|
||||
and not self.metric[self.PARAM_METRIC_XPATH]
|
||||
):
|
||||
del self.metric[self.PARAM_METRIC_XPATH]
|
||||
|
||||
if isinstance(self.metric, dict):
|
||||
if (
|
||||
self.PARAM_METRIC_XPATH in self.metric
|
||||
and not self.metric[self.PARAM_METRIC_XPATH]
|
||||
):
|
||||
del self.metric[self.PARAM_METRIC_XPATH]
|
||||
if self.metric:
|
||||
ret[self.PARAM_METRIC] = self.metric
|
||||
|
||||
if self.metric:
|
||||
ret[self.PARAM_METRIC] = self.metric
|
||||
if self.plot:
|
||||
ret[self.PARAM_PLOT] = self.plot
|
||||
|
||||
if self.plot:
|
||||
ret[self.PARAM_PLOT] = self.plot
|
||||
if self.persist:
|
||||
ret[self.PARAM_PERSIST] = self.persist
|
||||
|
||||
if self.persist:
|
||||
ret[self.PARAM_PERSIST] = self.persist
|
||||
if self.checkpoint:
|
||||
ret[self.PARAM_CHECKPOINT] = self.checkpoint
|
||||
|
||||
if self.checkpoint:
|
||||
ret[self.PARAM_CHECKPOINT] = self.checkpoint
|
||||
if self.remote:
|
||||
ret[self.PARAM_REMOTE] = self.remote
|
||||
|
||||
if self.live:
|
||||
ret[self.PARAM_LIVE] = self.live
|
||||
|
||||
if self.remote:
|
||||
ret[self.PARAM_REMOTE] = self.remote
|
||||
|
||||
if self.hash_info.isdir and (
|
||||
kwargs.get("with_files") or self.files is not None
|
||||
if (
|
||||
(not self.IS_DEPENDENCY or self.stage.is_import)
|
||||
and self.hash_info.isdir
|
||||
and (kwargs.get("with_files") or self.files is not None)
|
||||
):
|
||||
if self.obj:
|
||||
obj = self.obj
|
||||
@@ -1182,11 +1174,11 @@ class Output:
|
||||
|
||||
@property
|
||||
def is_metric(self) -> bool:
|
||||
return bool(self.metric) or bool(self.live)
|
||||
return bool(self.metric)
|
||||
|
||||
@property
|
||||
def is_plot(self) -> bool:
|
||||
return bool(self.plot) or bool(self.live)
|
||||
return bool(self.plot)
|
||||
|
||||
|
||||
META_SCHEMA = {
|
||||
|
||||
@@ -36,7 +36,7 @@ from .interpolate import (
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import NoReturn
|
||||
from typing import NoReturn
|
||||
|
||||
from dvc.repo import Repo
|
||||
|
||||
|
||||
@@ -9,10 +9,9 @@ from dvc.exceptions import DvcException
|
||||
from dvc.utils.flatten import flatten
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from typing import List, Match
|
||||
from typing import List, Match, NoReturn
|
||||
|
||||
from pyparsing import ParseException
|
||||
from typing_extensions import NoReturn
|
||||
|
||||
from .context import Context
|
||||
|
||||
|
||||
@@ -269,7 +269,7 @@ class Repo:
|
||||
except SCMError:
|
||||
if self._uninitialized:
|
||||
# might not be a git/dvc repo at all
|
||||
# used in `params/metrics/plots/live` targets
|
||||
# used in `params/metrics/plots` targets
|
||||
return SCM(self.root_dir, no_scm=True)
|
||||
raise
|
||||
|
||||
|
||||
+6
-3
@@ -56,10 +56,13 @@ def commit(
|
||||
]
|
||||
for stage_info in stages_info:
|
||||
stage = stage_info.stage
|
||||
changes = stage.changed_entries()
|
||||
if any(changes):
|
||||
prompt_to_commit(stage, changes, force=force)
|
||||
if force:
|
||||
stage.save(allow_missing=allow_missing)
|
||||
else:
|
||||
changes = stage.changed_entries()
|
||||
if any(changes):
|
||||
prompt_to_commit(stage, changes, force=force)
|
||||
stage.save(allow_missing=allow_missing)
|
||||
stage.commit(
|
||||
filter_info=stage_info.filter_info, allow_missing=allow_missing
|
||||
)
|
||||
|
||||
@@ -248,11 +248,6 @@ def init(
|
||||
except MissingParamsFile:
|
||||
pass
|
||||
|
||||
models = context.get("models")
|
||||
live_path = context.pop("live", None)
|
||||
live_metrics = f"{live_path}.json" if live_path else None
|
||||
live_plots = os.path.join(live_path, "scalars") if live_path else None
|
||||
|
||||
if type == "checkpoint":
|
||||
outs_key = "checkpoints"
|
||||
metrics_key = "metrics_persist_no_cache"
|
||||
@@ -262,6 +257,13 @@ def init(
|
||||
metrics_key = "metrics_no_cache"
|
||||
plots_key = "plots_no_cache"
|
||||
|
||||
models = [context.get("models")]
|
||||
metrics = [context.get("metrics")]
|
||||
plots = [context.get("plots")]
|
||||
if live_path := context.pop("live", None):
|
||||
metrics.append(os.path.join(live_path, "metrics.json"))
|
||||
plots.append(os.path.join(live_path, "plots"))
|
||||
|
||||
stage = repo.stage.create(
|
||||
name=name,
|
||||
cmd=context["cmd"],
|
||||
@@ -269,9 +271,9 @@ def init(
|
||||
params=[{params: None}] if params else None,
|
||||
force=force,
|
||||
**{
|
||||
outs_key: compact([models]),
|
||||
metrics_key: compact([context.get("metrics"), live_metrics]),
|
||||
plots_key: compact([context.get("plots"), live_plots]),
|
||||
outs_key: compact(models),
|
||||
metrics_key: compact(metrics),
|
||||
plots_key: compact(plots),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import asdict, dataclass
|
||||
from typing import (
|
||||
@@ -269,6 +268,7 @@ class BaseStashQueue(ABC):
|
||||
finish any active experiments before shutting down.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def logs(
|
||||
self,
|
||||
rev: str,
|
||||
@@ -525,16 +525,9 @@ class BaseStashQueue(ABC):
|
||||
.. _Hydra Override:
|
||||
https://hydra.cc/docs/advanced/override_grammar/basic/
|
||||
"""
|
||||
logger.debug("Using experiment params '%s'", params)
|
||||
from dvc.utils.hydra import apply_overrides, compose_and_dump
|
||||
|
||||
try:
|
||||
from dvc.utils.hydra import apply_overrides, compose_and_dump
|
||||
except ValueError:
|
||||
if sys.version_info >= (3, 11):
|
||||
raise DvcException(
|
||||
"--set-param is not supported in Python >= 3.11"
|
||||
)
|
||||
raise
|
||||
logger.debug("Using experiment params '%s'", params)
|
||||
|
||||
hydra_config = self.repo.config.get("hydra", {})
|
||||
hydra_enabled = hydra_config.get("enabled", False)
|
||||
|
||||
+13
-13
@@ -28,14 +28,25 @@ def run(
|
||||
Returns a dict mapping new experiment SHAs to the results
|
||||
of `repro` for that experiment.
|
||||
"""
|
||||
from dvc.utils.hydra import to_hydra_overrides
|
||||
|
||||
if run_all:
|
||||
entries = list(repo.experiments.celery_queue.iter_queued())
|
||||
return repo.experiments.reproduce_celery(entries, jobs=jobs)
|
||||
|
||||
hydra_sweep = None
|
||||
if params:
|
||||
from dvc.utils.hydra import to_hydra_overrides
|
||||
|
||||
path_overrides = to_path_overrides(params)
|
||||
hydra_sweep = any(
|
||||
x.is_sweep_override()
|
||||
for param_file in path_overrides
|
||||
for x in to_hydra_overrides(path_overrides[param_file])
|
||||
)
|
||||
|
||||
if hydra_sweep and not queue:
|
||||
raise InvalidArgumentError(
|
||||
"Sweep overrides can't be used without `--queue`"
|
||||
)
|
||||
else:
|
||||
path_overrides = {}
|
||||
|
||||
@@ -45,17 +56,6 @@ def run(
|
||||
# Force `_update_params` even if `--set-param` was not used
|
||||
path_overrides[hydra_output_file] = []
|
||||
|
||||
hydra_sweep = any(
|
||||
x.is_sweep_override()
|
||||
for param_file in path_overrides
|
||||
for x in to_hydra_overrides(path_overrides[param_file])
|
||||
)
|
||||
|
||||
if hydra_sweep and not queue:
|
||||
raise InvalidArgumentError(
|
||||
"Sweep overrides can't be used without `--queue`"
|
||||
)
|
||||
|
||||
if not queue:
|
||||
return repo.experiments.reproduce_one(
|
||||
targets=targets, params=path_overrides, tmp_dir=tmp_dir, **kwargs
|
||||
|
||||
+9
-12
@@ -242,15 +242,8 @@ class Index:
|
||||
workspace, key = out.index_key
|
||||
data_index = by_workspace[workspace]
|
||||
|
||||
if out.files:
|
||||
out.obj = out.get_obj()
|
||||
|
||||
entry = out.get_entry()
|
||||
entry.key = key
|
||||
if out.stage.is_import and not out.stage.is_repo_import:
|
||||
entry.fs = out.stage.deps[0].fs
|
||||
entry.path = out.stage.deps[0].fs_path
|
||||
entry.meta = out.stage.deps[0].meta
|
||||
data_index.add(entry)
|
||||
|
||||
return dict(by_workspace)
|
||||
@@ -418,14 +411,18 @@ class IndexView:
|
||||
|
||||
def key_filter(workspace: str, key: "DataIndexKey"):
|
||||
try:
|
||||
return key in self._data_prefixes[workspace]
|
||||
return key in self._data_prefixes[workspace] or any(
|
||||
key[: len(prefix)] == prefix
|
||||
for prefix in self._data_prefixes[workspace]
|
||||
)
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
return {
|
||||
workspace: view(index, partial(key_filter, workspace))
|
||||
for workspace, index in self._index.data.items()
|
||||
}
|
||||
data = {}
|
||||
for workspace, data_index in self._index.data.items():
|
||||
data_index.load()
|
||||
data[workspace] = view(data_index, partial(key_filter, workspace))
|
||||
return data
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dvc.output import Output
|
||||
|
||||
|
||||
def summary_fs_path(out: "Output") -> Optional[str]:
|
||||
from dvc.output import Output
|
||||
|
||||
assert out.live
|
||||
has_summary = True
|
||||
if isinstance(out.live, dict):
|
||||
has_summary = out.live.get(Output.PARAM_LIVE_SUMMARY, True)
|
||||
if has_summary:
|
||||
return out.fs.path.with_suffix(out.fs_path, ".json")
|
||||
return None
|
||||
@@ -8,7 +8,6 @@ from dvc.fs.dvc import DVCFileSystem
|
||||
from dvc.output import Output
|
||||
from dvc.repo import locked
|
||||
from dvc.repo.collect import StrPaths, collect
|
||||
from dvc.repo.live import summary_fs_path
|
||||
from dvc.scm import NoSCMError
|
||||
from dvc.utils import error_handler, errored_revisions, onerror_collect
|
||||
from dvc.utils.collections import ensure_list
|
||||
@@ -18,7 +17,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _is_metric(out: Output) -> bool:
|
||||
return bool(out.metric) or bool(out.live)
|
||||
return bool(out.metric)
|
||||
|
||||
|
||||
def _to_fs_paths(metrics: List[Output]) -> StrPaths:
|
||||
@@ -26,10 +25,6 @@ def _to_fs_paths(metrics: List[Output]) -> StrPaths:
|
||||
for out in metrics:
|
||||
if out.metric:
|
||||
result.append(out.repo.dvcfs.from_os_path(out.fs_path))
|
||||
elif out.live:
|
||||
fs_path = summary_fs_path(out)
|
||||
if fs_path:
|
||||
result.append(out.repo.dvcfs.from_os_path(fs_path))
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ from typing import (
|
||||
List,
|
||||
Optional,
|
||||
Set,
|
||||
Union,
|
||||
)
|
||||
|
||||
import dpath.options
|
||||
@@ -265,7 +266,7 @@ class Plots:
|
||||
|
||||
|
||||
def _is_plot(out: "Output") -> bool:
|
||||
return bool(out.plot) or bool(out.live)
|
||||
return bool(out.plot)
|
||||
|
||||
|
||||
def _resolve_data_sources(plots_data: Dict):
|
||||
@@ -444,16 +445,22 @@ def _collect_pipeline_files(repo, targets: List[str], props, onerror=None):
|
||||
if isinstance(dvcfile, PipelineFile):
|
||||
dvcfile_path = _relpath(repo.dvcfs, dvcfile.path)
|
||||
dvcfile_defs = dvcfile.load().get("plots", {})
|
||||
dvcfile_defs_dict: Dict[str, Union[Dict, None]] = {}
|
||||
if isinstance(dvcfile_defs, list):
|
||||
dvcfile_defs = {
|
||||
k: v for elem in dvcfile_defs for k, v in elem.items()
|
||||
}
|
||||
for elem in dvcfile_defs:
|
||||
if isinstance(elem, str):
|
||||
dvcfile_defs_dict[elem] = None
|
||||
else:
|
||||
k, v = list(elem.items())[0]
|
||||
dvcfile_defs_dict[k] = v
|
||||
else:
|
||||
dvcfile_defs_dict = dvcfile_defs
|
||||
resolved = _resolve_definitions(
|
||||
repo.dvcfs,
|
||||
targets,
|
||||
props,
|
||||
dvcfile_path,
|
||||
dvcfile_defs,
|
||||
dvcfile_defs_dict,
|
||||
onerror=onerror,
|
||||
)
|
||||
dpath.util.merge(
|
||||
@@ -555,7 +562,7 @@ def parse(fs, path, props=None, **kwargs):
|
||||
def _plot_props(out: "Output") -> Dict:
|
||||
from dvc.schema import PLOT_PROPS
|
||||
|
||||
if not (out.plot or out.live):
|
||||
if not (out.plot):
|
||||
raise NotAPlotError(out)
|
||||
if isinstance(out.plot, list):
|
||||
raise DvcException("Multiple plots per data file not supported.")
|
||||
|
||||
@@ -72,12 +72,6 @@ def _get_stage_files(stage: "Stage") -> typing.Iterator[str]:
|
||||
for out in stage.outs:
|
||||
if not out.use_scm_ignore and out.is_in_repo:
|
||||
yield out.fs_path
|
||||
if out.live:
|
||||
from dvc.repo.live import summary_fs_path
|
||||
|
||||
summary = summary_fs_path(out)
|
||||
if summary:
|
||||
yield summary
|
||||
|
||||
|
||||
def _track_stage(stage: "Stage") -> None:
|
||||
|
||||
+1
-9
@@ -67,13 +67,6 @@ PLOT_PROPS = {
|
||||
PLOT_PROPS_SCHEMA = {**OUT_PSTAGE_DETAILED_SCHEMA[str], **PLOT_PROPS}
|
||||
PLOT_PSTAGE_SCHEMA = {str: Any(PLOT_PROPS_SCHEMA, [PLOT_PROPS_SCHEMA])}
|
||||
|
||||
LIVE_PROPS = {
|
||||
Output.PARAM_LIVE_SUMMARY: bool,
|
||||
Output.PARAM_LIVE_HTML: bool,
|
||||
}
|
||||
LIVE_PROPS_SCHEMA = {**PLOT_PROPS_SCHEMA, **LIVE_PROPS}
|
||||
LIVE_PSTAGE_SCHEMA = {str: LIVE_PROPS_SCHEMA}
|
||||
|
||||
PARAM_PSTAGE_NON_DEFAULT_SCHEMA = {str: [str]}
|
||||
|
||||
VARS_SCHEMA = [str, dict]
|
||||
@@ -93,7 +86,6 @@ STAGE_DEFINITION = {
|
||||
Any(str, OUT_PSTAGE_DETAILED_SCHEMA)
|
||||
],
|
||||
Optional(StageParams.PARAM_PLOTS): [Any(str, PLOT_PSTAGE_SCHEMA)],
|
||||
Optional(StageParams.PARAM_LIVE): Any(str, LIVE_PSTAGE_SCHEMA),
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +119,7 @@ SINGLE_PIPELINE_STAGE_SCHEMA = {
|
||||
str: either_or(STAGE_DEFINITION, FOREACH_IN, [FOREACH_KWD, DO_KWD])
|
||||
}
|
||||
MULTI_STAGE_SCHEMA = {
|
||||
PLOTS: Any(SINGLE_PLOT_SCHEMA, [SINGLE_PLOT_SCHEMA]),
|
||||
PLOTS: Any(SINGLE_PLOT_SCHEMA, [Any(str, SINGLE_PLOT_SCHEMA)]),
|
||||
STAGES: SINGLE_PIPELINE_STAGE_SCHEMA,
|
||||
VARS_KWD: VARS_SCHEMA,
|
||||
}
|
||||
|
||||
+2
-17
@@ -280,22 +280,7 @@ class Stage(params.StageParams):
|
||||
|
||||
def _read_env(self, out, checkpoint_func=None) -> Env:
|
||||
env: Env = {}
|
||||
if out.live:
|
||||
from dvc.env import DVCLIVE_HTML, DVCLIVE_PATH, DVCLIVE_SUMMARY
|
||||
from dvc.output import Output
|
||||
from dvc.schema import LIVE_PROPS
|
||||
|
||||
env[DVCLIVE_PATH] = relpath(out.fs_path, self.wdir)
|
||||
if isinstance(out.live, dict):
|
||||
config = project(out.live, LIVE_PROPS)
|
||||
|
||||
env[DVCLIVE_SUMMARY] = str(
|
||||
int(config.get(Output.PARAM_LIVE_SUMMARY, True))
|
||||
)
|
||||
env[DVCLIVE_HTML] = str(
|
||||
int(config.get(Output.PARAM_LIVE_HTML, True))
|
||||
)
|
||||
elif out.checkpoint and checkpoint_func:
|
||||
if out.checkpoint and checkpoint_func:
|
||||
from dvc.env import DVC_CHECKPOINT
|
||||
|
||||
env.update({DVC_CHECKPOINT: "1"})
|
||||
@@ -379,7 +364,7 @@ class Stage(params.StageParams):
|
||||
def remove_outs(self, ignore_remove=False, force=False):
|
||||
"""Used mainly for `dvc remove --outs` and :func:`Stage.reproduce`."""
|
||||
for out in self.outs:
|
||||
if (out.persist or out.checkpoint or out.live) and not force:
|
||||
if (out.persist or out.checkpoint) and not force:
|
||||
out.unprotect()
|
||||
continue
|
||||
|
||||
|
||||
@@ -97,7 +97,6 @@ class StageLoader(Mapping):
|
||||
stage.PARAM_OUTS,
|
||||
stage.PARAM_METRICS,
|
||||
stage.PARAM_PLOTS,
|
||||
stage.PARAM_LIVE,
|
||||
],
|
||||
)
|
||||
stage.outs = lcat(
|
||||
|
||||
@@ -20,10 +20,6 @@ class CheckpointKilledError(StageCmdFailedError):
|
||||
pass
|
||||
|
||||
|
||||
class LiveKilledError(StageCmdFailedError):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class MonitorTask:
|
||||
stage: "Stage"
|
||||
|
||||
@@ -12,4 +12,3 @@ class StageParams:
|
||||
PARAM_METRICS = "metrics"
|
||||
PARAM_PLOTS = "plots"
|
||||
PARAM_DESC = "desc"
|
||||
PARAM_LIVE = "live"
|
||||
|
||||
+3
-10
@@ -52,8 +52,6 @@ def _get_flags(out):
|
||||
# `out.plot` is in the same order as is in the file when read
|
||||
# and, should be dumped as-is without any sorting
|
||||
yield from out.plot.items()
|
||||
if out.live and isinstance(out.live, dict):
|
||||
yield from out.live.items()
|
||||
if out.remote:
|
||||
yield PARAM_REMOTE, out.remote
|
||||
|
||||
@@ -65,19 +63,15 @@ def _serialize_out(out):
|
||||
|
||||
@no_type_check
|
||||
def _serialize_outs(outputs: List[Output]):
|
||||
outs, metrics, plots, live = [], [], [], None
|
||||
outs, metrics, plots = [], [], []
|
||||
for out in sort_by_path(outputs):
|
||||
bucket = outs
|
||||
if out.plot:
|
||||
bucket = plots
|
||||
elif out.metric:
|
||||
bucket = metrics
|
||||
elif out.live:
|
||||
assert live is None
|
||||
live = _serialize_out(out)
|
||||
continue
|
||||
bucket.append(_serialize_out(out))
|
||||
return outs, metrics, plots, live
|
||||
return outs, metrics, plots
|
||||
|
||||
|
||||
def _serialize_params_keys(params):
|
||||
@@ -126,7 +120,7 @@ def to_pipeline_file(stage: "PipelineStage"):
|
||||
deps = sorted(d.def_path for d in deps)
|
||||
params = _serialize_params_keys(params)
|
||||
|
||||
outs, metrics, plots, live = _serialize_outs(stage.outs)
|
||||
outs, metrics, plots = _serialize_outs(stage.outs)
|
||||
|
||||
cmd = stage.cmd
|
||||
assert cmd, (
|
||||
@@ -142,7 +136,6 @@ def to_pipeline_file(stage: "PipelineStage"):
|
||||
(stage.PARAM_OUTS, outs),
|
||||
(stage.PARAM_METRICS, metrics),
|
||||
(stage.PARAM_PLOTS, plots),
|
||||
(stage.PARAM_LIVE, live),
|
||||
(stage.PARAM_FROZEN, stage.frozen),
|
||||
(stage.PARAM_ALWAYS_CHANGED, stage.always_changed),
|
||||
(stage.PARAM_META, stage.meta),
|
||||
|
||||
+1
-43
@@ -2,7 +2,7 @@ import os
|
||||
import pathlib
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
from funcy import concat, first, lsplit, rpartial, without
|
||||
from funcy import concat, first, lsplit, rpartial
|
||||
|
||||
from dvc.annotations import ANNOTATION_FIELDS
|
||||
from dvc.exceptions import InvalidArgumentError
|
||||
@@ -66,8 +66,6 @@ def fill_stage_outputs(stage, **kwargs):
|
||||
|
||||
stage.outs = []
|
||||
|
||||
stage.outs += _load_live_output(stage, **kwargs)
|
||||
|
||||
for key in keys:
|
||||
stage.outs += loads_from(
|
||||
stage,
|
||||
@@ -80,34 +78,6 @@ def fill_stage_outputs(stage, **kwargs):
|
||||
)
|
||||
|
||||
|
||||
def _load_live_output(
|
||||
stage,
|
||||
live=None,
|
||||
live_no_cache=None,
|
||||
live_summary=False,
|
||||
live_html=False,
|
||||
**kwargs,
|
||||
):
|
||||
from dvc.output import Output, loads_from
|
||||
|
||||
outs = []
|
||||
if live or live_no_cache:
|
||||
assert bool(live) != bool(live_no_cache)
|
||||
|
||||
path = live or live_no_cache
|
||||
outs += loads_from(
|
||||
stage,
|
||||
[path],
|
||||
use_cache=not bool(live_no_cache),
|
||||
live={
|
||||
Output.PARAM_LIVE_SUMMARY: live_summary,
|
||||
Output.PARAM_LIVE_HTML: live_html,
|
||||
},
|
||||
)
|
||||
|
||||
return outs
|
||||
|
||||
|
||||
def fill_stage_dependencies(
|
||||
stage, deps=None, erepo=None, params=None, fs_config=None
|
||||
):
|
||||
@@ -274,7 +244,6 @@ def prepare_file_path(kwargs):
|
||||
kwargs.get("outs_persist", []),
|
||||
kwargs.get("outs_persist_no_cache", []),
|
||||
kwargs.get("checkpoints", []),
|
||||
without([kwargs.get("live", None)], None),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -332,15 +301,4 @@ def validate_kwargs(single_stage: bool = False, fname: str = None, **kwargs):
|
||||
if single_stage:
|
||||
kwargs.pop("name", None)
|
||||
|
||||
if kwargs.get("live") and kwargs.get("live_no_cache"):
|
||||
raise InvalidArgumentError(
|
||||
"cannot specify both `--live` and `--live-no-cache`"
|
||||
)
|
||||
|
||||
kwargs.update(
|
||||
{
|
||||
"live_summary": not kwargs.pop("live_no_summary", False),
|
||||
"live_html": not kwargs.pop("live_no_html", False),
|
||||
}
|
||||
)
|
||||
return kwargs
|
||||
|
||||
@@ -65,7 +65,7 @@ class Cloud(ABC):
|
||||
self._gen(struct)
|
||||
return struct.keys()
|
||||
|
||||
def close(self):
|
||||
def close(self): # noqa: B027
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
|
||||
+37
-8
@@ -1,15 +1,24 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
from hydra import compose, initialize_config_dir
|
||||
from hydra._internal.config_loader_impl import ConfigLoaderImpl
|
||||
from hydra._internal.core_plugins.basic_sweeper import BasicSweeper
|
||||
from hydra.core.override_parser.overrides_parser import OverridesParser
|
||||
from hydra.core.override_parser.types import ValueType
|
||||
from hydra.errors import ConfigCompositionException, OverrideParseException
|
||||
from omegaconf import OmegaConf
|
||||
try:
|
||||
from hydra import compose, initialize_config_dir
|
||||
from hydra._internal.config_loader_impl import ConfigLoaderImpl
|
||||
from hydra._internal.core_plugins.basic_sweeper import BasicSweeper
|
||||
from hydra.core.override_parser.overrides_parser import OverridesParser
|
||||
from hydra.core.override_parser.types import ValueType
|
||||
from hydra.errors import ConfigCompositionException, OverrideParseException
|
||||
from omegaconf import OmegaConf
|
||||
|
||||
from dvc.exceptions import InvalidArgumentError
|
||||
hydra_compatible = True
|
||||
except ValueError:
|
||||
if sys.version_info >= (3, 11):
|
||||
hydra_compatible = False
|
||||
else:
|
||||
raise
|
||||
|
||||
from dvc.exceptions import DvcException, InvalidArgumentError
|
||||
|
||||
from .collections import merge_dicts, remove_missing_keys, to_omegaconf
|
||||
from .serialize import DUMPERS, MODIFIERS
|
||||
@@ -37,6 +46,11 @@ def compose_and_dump(
|
||||
.. _Hydra Override:
|
||||
https://hydra.cc/docs/advanced/override_grammar/basic/
|
||||
"""
|
||||
if not hydra_compatible:
|
||||
raise DvcException(
|
||||
"hydra functionality is not supported in Python >= 3.11"
|
||||
)
|
||||
|
||||
with initialize_config_dir(config_dir, version_base=None):
|
||||
cfg = compose(config_name=config_name, overrides=overrides)
|
||||
|
||||
@@ -53,6 +67,11 @@ def apply_overrides(path: "StrPath", overrides: List[str]) -> None:
|
||||
.. _Hydra Override:
|
||||
https://hydra.cc/docs/next/advanced/override_grammar/basic/
|
||||
"""
|
||||
if not hydra_compatible:
|
||||
raise DvcException(
|
||||
"hydra functionality is not supported in Python >= 3.11"
|
||||
)
|
||||
|
||||
suffix = Path(path).suffix.lower()
|
||||
|
||||
hydra_errors = (ConfigCompositionException, OverrideParseException)
|
||||
@@ -78,6 +97,11 @@ def apply_overrides(path: "StrPath", overrides: List[str]) -> None:
|
||||
|
||||
|
||||
def to_hydra_overrides(path_overrides):
|
||||
if not hydra_compatible:
|
||||
raise DvcException(
|
||||
"hydra functionality is not supported in Python >= 3.11"
|
||||
)
|
||||
|
||||
parser = OverridesParser.create()
|
||||
return parser.parse_overrides(overrides=path_overrides)
|
||||
|
||||
@@ -89,6 +113,11 @@ def dict_product(dicts):
|
||||
|
||||
|
||||
def get_hydra_sweeps(path_overrides):
|
||||
if not hydra_compatible:
|
||||
raise DvcException(
|
||||
"hydra functionality is not supported in Python >= 3.11"
|
||||
)
|
||||
|
||||
path_sweeps = {}
|
||||
for path, overrides in path_overrides.items():
|
||||
overrides = to_hydra_overrides(overrides)
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
"""Common utilities for serialize."""
|
||||
import os
|
||||
from contextlib import contextmanager
|
||||
from typing import TYPE_CHECKING, Any, Callable, ContextManager, Dict, Union
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
ContextManager,
|
||||
Dict,
|
||||
Protocol,
|
||||
Union,
|
||||
)
|
||||
|
||||
from funcy import reraise
|
||||
from typing_extensions import Protocol
|
||||
|
||||
from dvc.exceptions import DvcException
|
||||
|
||||
|
||||
@@ -4,3 +4,4 @@ try:
|
||||
from ._dvc_version import version_tuple # noqa: F401
|
||||
except ImportError:
|
||||
__version__ = "UNKNOWN"
|
||||
version_tuple = (0, 0, __version__) # type: ignore[assignment]
|
||||
|
||||
+159
-2
@@ -1,7 +1,140 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=48", "setuptools_scm[toml]>=7"]
|
||||
requires = ["setuptools>=61", "setuptools_scm[toml]>=7"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "dvc"
|
||||
description = "Git for data scientists - manage your code and data together"
|
||||
readme = "README.rst"
|
||||
keywords = [
|
||||
"data-science",
|
||||
"data-version-control",
|
||||
"machine-learning",
|
||||
"git",
|
||||
"developer-tools",
|
||||
"reproducibility",
|
||||
"collaboration",
|
||||
"ai",
|
||||
]
|
||||
license = { text = "Apache License 2.0" }
|
||||
authors = [{ name = "Dmitry Petrov", email = "[email protected]" }]
|
||||
maintainers = [{ name = "Iterative", email = "[email protected]" }]
|
||||
requires-python = ">=3.8"
|
||||
dependencies = [
|
||||
"colorama>=0.3.9",
|
||||
"configobj>=5.0.6",
|
||||
"voluptuous>=0.11.7",
|
||||
"requests>=2.22.0",
|
||||
# See https://github.com/bdcht/grandalf/issues/26
|
||||
"grandalf==0.6",
|
||||
"distro>=1.3.0",
|
||||
"appdirs>=1.4.3",
|
||||
"ruamel.yaml>=0.17.11",
|
||||
"tomlkit>=0.11.1",
|
||||
"funcy>=1.14",
|
||||
"pathspec>=0.9.0,<0.10.0",
|
||||
"shortuuid>=0.5.0",
|
||||
"tqdm>=4.63.1,<5",
|
||||
"packaging>=19.0",
|
||||
"zc.lockfile>=1.2.1",
|
||||
"flufl.lock>=5",
|
||||
"networkx>=2.5",
|
||||
"psutil>=5.8.0",
|
||||
"pydot>=1.2.4",
|
||||
"importlib-resources>=5.2.2; python_version < '3.9'",
|
||||
"flatten_dict>=0.4.1,<1",
|
||||
"tabulate>=0.8.7",
|
||||
"pygtrie>=2.3.2",
|
||||
"dpath>=2.0.2,<3",
|
||||
"shtab>=1.3.4,<2",
|
||||
"rich>=10.13.0",
|
||||
"pyparsing>=2.4.7",
|
||||
"typing-extensions>=3.7.4",
|
||||
"scmrepo==0.1.3",
|
||||
"dvc-render==0.0.12",
|
||||
"dvc-task==0.1.4",
|
||||
"dvclive>=1.0",
|
||||
"dvc-data==0.25.3",
|
||||
"dvc-http==2.27.2",
|
||||
"hydra-core>=1.1.0",
|
||||
"iterative-telemetry==0.0.5",
|
||||
]
|
||||
dynamic = ["version"]
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
azure = ["dvc-azure==2.20.4"]
|
||||
gdrive = ["dvc-gdrive==2.19.0"]
|
||||
gs = ["dvc-gs==2.19.1"]
|
||||
hdfs = ["dvc-hdfs==2.19.0; python_version < '3.11'"]
|
||||
oss = ["dvc-oss==2.19.0"]
|
||||
s3 = ["dvc-s3==2.21.0"]
|
||||
ssh = ["dvc-ssh==2.20.0"]
|
||||
ssh_gssapi = ["dvc-ssh[gssapi]==2.20.0"]
|
||||
webdav = ["dvc-webdav==2.19.0"]
|
||||
webhdfs = ["dvc-webhdfs==2.19.0"]
|
||||
webhdfs_kerberos = ["dvc-webhdfs[kerberos]==2.19.0"]
|
||||
terraform = ["tpi[ssh]>=2.1.0"]
|
||||
testing = ["pytest-test-utils==0.0.8"]
|
||||
tests = [
|
||||
"tpi[ssh]>=2.1.0",
|
||||
"dvc-ssh==2.20.0",
|
||||
"pytest-test-utils==0.0.8",
|
||||
# Test requirements
|
||||
"pytest==7.2.0",
|
||||
"pytest-cov==4.0.0",
|
||||
"pytest-xdist==3.0.2",
|
||||
"pytest-mock==3.10.0",
|
||||
"pytest-lazy-fixture==0.6.3",
|
||||
# https://github.com/docker/docker-py/issues/2902
|
||||
"pytest-docker==0.11.0; python_version < '3.10' or sys_platform != 'win32'",
|
||||
"flaky==3.7.0",
|
||||
"pytest-timeout==2.1.0",
|
||||
"filelock==3.8.0",
|
||||
# optional dependencies
|
||||
"dvclive>=1.0",
|
||||
"pywin32>=225; sys_platform == 'win32'",
|
||||
"beautifulsoup4==4.11.1",
|
||||
# pylint requirements
|
||||
"pylint==2.15.5",
|
||||
# we use this to suppress pytest-related false positives in our tests.
|
||||
"pylint-pytest==1.1.2",
|
||||
# we use this to suppress some messages in tests, eg: foo/bar naming,
|
||||
# and, protected method calls in our tests
|
||||
"pylint-plugin-utils==0.7",
|
||||
# type-checking
|
||||
"mypy==0.982",
|
||||
"types-requests>=2.27.15",
|
||||
"types-tabulate>=0.8.6",
|
||||
"types-toml>=0.10.4",
|
||||
]
|
||||
all = ["dvc[azure,gdrive,gs,hdfs,oss,s3,ssh,webdav,webhdfs]"]
|
||||
dev = ["dvc[azure,gdrive,gs,hdfs,oss,s3,ssh,webdav,webhdfs,tests]"]
|
||||
|
||||
[project.urls]
|
||||
Documentation = "https://dvc.org/doc"
|
||||
"Bug Tracker" = "https://github.com/iterative/dvc/issues"
|
||||
Source = "https://github.com/iterative/dvc"
|
||||
|
||||
[project.scripts]
|
||||
dvc = "dvc.cli:main"
|
||||
|
||||
[project.entry-points."fsspec.specs"]
|
||||
dvc = "dvc.api:DVCFileSystem"
|
||||
|
||||
[tool.setuptools]
|
||||
license-files = ["LICENSE"]
|
||||
|
||||
[tool.setuptools.packages]
|
||||
find = {namespaces = false, exclude=["tests", "tests.*"]}
|
||||
|
||||
[tool.setuptools_scm]
|
||||
write_to = "dvc/_dvc_version.py"
|
||||
|
||||
@@ -30,13 +163,34 @@ line_length = 79
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
log_level = "debug"
|
||||
addopts = "-ra -p no:hydra_pytest"
|
||||
addopts = "-ra -p no:hydra_pytest --cov-config pyproject.toml"
|
||||
markers = [
|
||||
"needs_internet: Might need network access for the tests",
|
||||
"vscode: Tests verifying contract between DVC and VSCode plugin",
|
||||
"studio: Tests verifying contract between DVC and Studio",
|
||||
]
|
||||
xfail_strict = true
|
||||
testpaths = ["tests"]
|
||||
filterwarnings = ["error::pytest_mock.PytestMockWarning"]
|
||||
|
||||
[tool.coverage.run]
|
||||
branch = true
|
||||
source = ["dvc", "tests"]
|
||||
|
||||
[tool.coverage.paths]
|
||||
source = ["dvc"]
|
||||
|
||||
[tool.coverage.report]
|
||||
show_missing = true
|
||||
exclude_lines = [
|
||||
"pragma: no cover",
|
||||
"if __name__ == .__main__.:",
|
||||
"if typing.TYPE_CHECKING:",
|
||||
"if TYPE_CHECKING:",
|
||||
"raise NotImplementedError",
|
||||
"raise AssertionError",
|
||||
"@overload",
|
||||
]
|
||||
|
||||
[tool.mypy]
|
||||
# Error output
|
||||
@@ -81,3 +235,6 @@ signature-mutators = ["funcy.decorators.decorator"]
|
||||
[tool.pylint.variables]
|
||||
dummy-variables-rgx = "_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_"
|
||||
ignored-argument-names = "_.*|^ignored_|^unused_|args|kwargs"
|
||||
|
||||
[tool.codespell]
|
||||
ignore-words-list = "ba,datas,fo,uptodate,cachable,falsy"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# NOTE: using strict version due to
|
||||
# https://github.com/iterative/dvc/issues/7949
|
||||
PyInstaller==5.0
|
||||
PyInstaller==5.6.2
|
||||
|
||||
+2
-18
@@ -1,7 +1,5 @@
|
||||
import argparse
|
||||
import importlib.util
|
||||
import pathlib
|
||||
import textwrap
|
||||
from subprocess import STDOUT, check_call
|
||||
|
||||
path = pathlib.Path(__file__).parent.absolute()
|
||||
@@ -25,22 +23,8 @@ args = parser.parse_args()
|
||||
|
||||
(dvc / "utils" / "build.py").write_text(f'PKG = "{args.pkg}"')
|
||||
|
||||
# Autogenerate version, similar to what we do in setup.py
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"dvc.version", dvc / "version.py"
|
||||
)
|
||||
dvc_version = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(dvc_version)
|
||||
version = dvc_version.__version__
|
||||
|
||||
(dvc / "version.py").write_text(
|
||||
textwrap.dedent(
|
||||
f"""\
|
||||
# AUTOGENERATED at build time by scripts/build.py
|
||||
__version__ = "{version}"
|
||||
"""
|
||||
)
|
||||
)
|
||||
if not (dvc / "_dvc_version.py").exists():
|
||||
raise Exception("no version info found")
|
||||
|
||||
check_call(
|
||||
["python", "build.py"],
|
||||
|
||||
@@ -10,5 +10,6 @@ fi
|
||||
|
||||
echo 'PKG = "pip"' >dvc/utils/build.py
|
||||
|
||||
python -m pip install -U build setuptools>=58.2
|
||||
python -m build --sdist --wheel --outdir dist/
|
||||
python -m pip install build twine
|
||||
python -m build
|
||||
twine check dist/*
|
||||
|
||||
@@ -24,8 +24,8 @@ function addSymLinkPermissions($accountToAdd){
|
||||
Write-Host "Account SID: $($sidstr)" -ForegroundColor DarkCyan
|
||||
$tmp = [System.IO.Path]::GetTempFileName()
|
||||
Write-Host "Export current Local Security Policy" -ForegroundColor DarkCyan
|
||||
secedit.exe /export /cfg "$($tmp)"
|
||||
$c = Get-Content -Path $tmp
|
||||
secedit.exe /export /cfg "$($tmp)"
|
||||
$c = Get-Content -Path $tmp
|
||||
$currentSetting = ""
|
||||
foreach($s in $c) {
|
||||
if( $s -like "SECreateSymbolicLinkPrivilege*") {
|
||||
@@ -35,7 +35,7 @@ function addSymLinkPermissions($accountToAdd){
|
||||
}
|
||||
if( $currentSetting -notlike "*$($sidstr)*" ) {
|
||||
Write-Host "Need to add permissions to SymLink" -ForegroundColor Yellow
|
||||
|
||||
|
||||
Write-Host "Modify Setting ""Create SymLink""" -ForegroundColor DarkCyan
|
||||
|
||||
if( [string]::IsNullOrEmpty($currentSetting) ) {
|
||||
@@ -58,8 +58,8 @@ SECreateSymbolicLinkPrivilege = $($currentSetting)
|
||||
$outfile | Set-Content -Path $tmp2 -Encoding Unicode -Force
|
||||
Push-Location (Split-Path $tmp2)
|
||||
try {
|
||||
secedit.exe /configure /db "secedit.sdb" /cfg "$($tmp2)" /areas USER_RIGHTS
|
||||
} finally {
|
||||
secedit.exe /configure /db "secedit.sdb" /cfg "$($tmp2)" /areas USER_RIGHTS
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if [ ! -d "dvc" ]; then
|
||||
echo "Please run this script from repository root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf dist/
|
||||
pip install twine
|
||||
python -m pip install -U build setuptools>=58.2
|
||||
python -m build --sdist --wheel --outdir dist/
|
||||
twine upload dist/*
|
||||
@@ -1,166 +0,0 @@
|
||||
[metadata]
|
||||
description = Git for data scientists - manage your code and data together
|
||||
name = dvc
|
||||
long_description = file: README.rst
|
||||
long_description_content_type = text/x-rst
|
||||
license = Apache License 2.0
|
||||
license_file = LICENSE
|
||||
url = http://dvc.org
|
||||
project_urls =
|
||||
Documentation = https://dvc.org/doc
|
||||
Source = https://github.com/iterative/dvc
|
||||
download_url = https://github.com/iterative/dvc
|
||||
author = Dmitry Petrov
|
||||
author_email = [email protected]
|
||||
maintainer = Iterative
|
||||
maintainer_email = [email protected]
|
||||
keywords = data-science, data-version-control, machine-learning, git
|
||||
developer-tools, reproducibility, collaboration, ai
|
||||
classifiers =
|
||||
Development Status :: 4 - Beta
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Programming Language :: Python :: 3.10
|
||||
Programming Language :: Python :: 3.11
|
||||
|
||||
[options]
|
||||
setup_requires =
|
||||
setuptools>=48
|
||||
setuptools_scm[toml]>=7
|
||||
|
||||
python_requires = >=3.8
|
||||
zip_safe = False
|
||||
packages = find:
|
||||
include_package_data = True
|
||||
install_requires =
|
||||
colorama>=0.3.9
|
||||
configobj>=5.0.6
|
||||
voluptuous>=0.11.7
|
||||
requests>=2.22.0
|
||||
# See https://github.com/bdcht/grandalf/issues/26
|
||||
grandalf==0.6
|
||||
distro>=1.3.0
|
||||
appdirs>=1.4.3
|
||||
ruamel.yaml>=0.17.11
|
||||
tomlkit>=0.11.1
|
||||
funcy>=1.14
|
||||
pathspec>=0.9.0,<0.10.0
|
||||
shortuuid>=0.5.0
|
||||
tqdm>=4.63.1,<5
|
||||
packaging>=19.0
|
||||
zc.lockfile>=1.2.1
|
||||
flufl.lock>=5
|
||||
networkx>=2.5
|
||||
psutil>=5.8.0
|
||||
pydot>=1.2.4
|
||||
importlib-resources>=5.2.2; python_version < '3.9'
|
||||
flatten_dict>=0.4.1,<1
|
||||
tabulate>=0.8.7
|
||||
pygtrie>=2.3.2
|
||||
dpath>=2.0.2,<3
|
||||
shtab>=1.3.4,<2
|
||||
rich>=10.13.0
|
||||
pyparsing>=2.4.7
|
||||
typing-extensions>=3.7.4
|
||||
scmrepo==0.1.2
|
||||
dvc-render==0.0.12
|
||||
dvc-task==0.1.4
|
||||
dvclive>=0.10.0
|
||||
dvc-data==0.25.1
|
||||
dvc-http==2.27.2
|
||||
hydra-core>=1.1.0
|
||||
iterative-telemetry==0.0.5
|
||||
|
||||
[options.extras_require]
|
||||
all =
|
||||
%(azure)s
|
||||
%(gdrive)s
|
||||
%(gs)s
|
||||
%(hdfs)s
|
||||
%(oss)s
|
||||
%(s3)s
|
||||
%(ssh)s
|
||||
%(webdav)s
|
||||
%(webhdfs)s
|
||||
dev =
|
||||
%(all)s
|
||||
%(tests)s
|
||||
dvc-data[cli]
|
||||
# Extra dependencies for remote integrations
|
||||
azure = dvc-azure==2.20.4
|
||||
gdrive = dvc-gdrive==2.19.0
|
||||
gs = dvc-gs==2.19.1
|
||||
hdfs =
|
||||
dvc-hdfs==2.19.0; python_version < '3.11'
|
||||
oss = dvc-oss==2.19.0
|
||||
s3 = dvc-s3==2.20.1
|
||||
ssh = dvc-ssh==2.20.0
|
||||
# gssapi should not be included in all_remotes, because it doesn't have wheels
|
||||
# for Linux, so it will fail to compile if user doesn't have all the
|
||||
# requirements, including kerberos itself.
|
||||
ssh_gssapi = dvc-ssh[gssapi]==2.20.0
|
||||
webdav = dvc-webdav==2.19.0
|
||||
webhdfs = dvc-webhdfs==2.19.0
|
||||
# requests-kerberos requires krb5 & gssapi, which does not provide wheels Linux
|
||||
webhdfs_kerberos = dvc-webhdfs[kerberos]==2.19.0
|
||||
terraform = tpi[ssh]>=2.1.0
|
||||
testing =
|
||||
pytest-test-utils==0.0.8
|
||||
tests =
|
||||
%(terraform)s
|
||||
dvc-ssh==2.20.0
|
||||
# Test requirements
|
||||
pytest==7.1.3
|
||||
pytest-cov==4.0.0
|
||||
pytest-xdist==2.5.0
|
||||
pytest-mock==3.10.0
|
||||
pytest-lazy-fixture==0.6.3
|
||||
%(testing)s
|
||||
# https://github.com/docker/docker-py/issues/2902
|
||||
pytest-docker==0.11.0; python_version < '3.10' or sys_platform != 'win32'
|
||||
flaky==3.7.0
|
||||
pytest-timeout==2.1.0
|
||||
filelock==3.8.0
|
||||
# pylint requirements
|
||||
pylint==2.15.4
|
||||
# we use this to suppress pytest-related false positives in our tests.
|
||||
pylint-pytest==1.1.2
|
||||
# we use this to suppress some messages in tests, eg: foo/bar naming,
|
||||
# and, protected method calls in our tests
|
||||
pylint-plugin-utils==0.7
|
||||
# type-checking
|
||||
mypy==0.982
|
||||
types-requests>=2.27.15
|
||||
types-tabulate>=0.8.6
|
||||
types-toml>=0.10.4
|
||||
# optional dependencies
|
||||
pywin32>=225; sys_platform == 'win32'
|
||||
dvclive[image]>=0.7.3
|
||||
beautifulsoup4==4.11.1
|
||||
|
||||
[options.packages.find]
|
||||
exclude =
|
||||
dvc.testing
|
||||
dvc.testing.*
|
||||
tests
|
||||
tests.*
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
dvc = dvc.cli:main
|
||||
fsspec.specs =
|
||||
dvc = dvc.api:DVCFileSystem
|
||||
|
||||
[flake8]
|
||||
ignore=
|
||||
E203, # Whitespace before ':'
|
||||
E266, # Too many leading '#' for block comment
|
||||
W503, # Line break occurred before a binary operator
|
||||
P1, # unindexed parameters in the str.format, see:
|
||||
# https://pypi.org/project/flake8-string-format/
|
||||
max_line_length = 79
|
||||
max-complexity = 15
|
||||
select = B,C,E,F,W,T4,B902,T,P
|
||||
show_source = true
|
||||
count = true
|
||||
@@ -1,41 +1,13 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Increasing fd ulimit for tests
|
||||
if os.name == "nt":
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
import win32file # pylint: disable=import-error
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
win32file._setmaxstdio(4096)
|
||||
|
||||
# Workaround for two bugs:
|
||||
#
|
||||
# 1) gitpython-developers/GitPython#546
|
||||
# GitPython leaves git cat-file --batch/--batch-check processes that are
|
||||
# not cleaned up correctly, so Popen._active list has their defunct
|
||||
# process handles, that it is not able to cleanup because of bug 2)
|
||||
#
|
||||
# 2) https://bugs.python.org/issue37380
|
||||
# subprocess.Popen._internal_poll on windows is getting
|
||||
#
|
||||
# OSError: [WinError 6] The handle is invalid
|
||||
#
|
||||
# exception, which it doesn't ignore and so Popen is not able to cleanup
|
||||
# old processes and that prevents it from creating any new processes at
|
||||
# all, which results in our tests failing whenever they try to use Popen.
|
||||
# This patch was released in 3.9.0 and backported to some earlier
|
||||
# versions.
|
||||
if sys.version_info < (3, 9, 0):
|
||||
|
||||
def noop():
|
||||
pass
|
||||
|
||||
subprocess._cleanup = noop
|
||||
subprocess._active = None
|
||||
else:
|
||||
import resource # pylint: disable=import-error
|
||||
|
||||
|
||||
+4
-18
@@ -1,20 +1,6 @@
|
||||
# Needs investigation, pylint was running tests
|
||||
# Could be `pylint_pytest` plugin, skipping check for this file for now
|
||||
# pylint: skip-file
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
import os
|
||||
import sys
|
||||
from subprocess import check_call
|
||||
import pytest
|
||||
|
||||
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
os.chdir(REPO_ROOT)
|
||||
|
||||
os.putenv(
|
||||
"PATH", ":".join([os.path.join(REPO_ROOT, "bin"), os.getenv("PATH")])
|
||||
)
|
||||
os.putenv("DVC_HOME", REPO_ROOT)
|
||||
|
||||
opts = "-v -n=auto --dist loadscope --cov=dvc --durations=50"
|
||||
params = " ".join(sys.argv[1:])
|
||||
cmd = f"{sys.executable} -m pytest {opts} {params}"
|
||||
check_call(cmd, shell=True)
|
||||
sys.exit(pytest.main(["-v", "-n=auto", *sys.argv[1:]]))
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
import warnings
|
||||
from unittest import TestCase
|
||||
|
||||
import pytest
|
||||
import shortuuid
|
||||
|
||||
from dvc.repo import Repo as DvcRepo
|
||||
from dvc.utils.fs import remove
|
||||
|
||||
logger = logging.getLogger("dvc")
|
||||
|
||||
|
||||
class TestDirFixture:
|
||||
DATA_DIR = "data_dir"
|
||||
DATA_SUB_DIR = os.path.join(DATA_DIR, "data_sub_dir")
|
||||
DATA = os.path.join(DATA_DIR, "data")
|
||||
DATA_SUB = os.path.join(DATA_SUB_DIR, "data_sub")
|
||||
DATA_CONTENTS = DATA
|
||||
DATA_SUB_CONTENTS = DATA_SUB
|
||||
FOO = "foo"
|
||||
FOO_CONTENTS = FOO
|
||||
BAR = "bar"
|
||||
# NOTE: len(FOO_CONTENTS) must be != len(BAR_CONTENTS)
|
||||
#
|
||||
# Our state database relies on file mtime and file size to determine
|
||||
# that a file has changed. Usually, mtime is enough by itself but on
|
||||
# some filesystems like APFS on macOS mtime resolution is so bad,
|
||||
# that our tests can overwrite a file in that time window without dvc
|
||||
# being able to detect that, thus causing tests to fail. Usually,
|
||||
# in tests, we replace foo with bar, so we need to make sure that when we
|
||||
# modify a file in our tests, its content length changes.
|
||||
BAR_CONTENTS = BAR + "r"
|
||||
CODE = "code.py"
|
||||
CODE_CONTENTS = (
|
||||
"import sys\nimport shutil\n"
|
||||
"shutil.copyfile(sys.argv[1], sys.argv[2])"
|
||||
)
|
||||
UNICODE = "тест"
|
||||
UNICODE_CONTENTS = "проверка"
|
||||
|
||||
def __init__(self):
|
||||
root_dir = self.mkdtemp()
|
||||
self._root_dir = os.path.realpath(root_dir)
|
||||
self._saved_dir = None
|
||||
|
||||
@property
|
||||
def root_dir(self):
|
||||
return self._root_dir
|
||||
|
||||
def _pushd(self, d):
|
||||
if not self._saved_dir:
|
||||
self._saved_dir = os.path.realpath(os.curdir)
|
||||
os.chdir(d)
|
||||
|
||||
def _popd(self):
|
||||
os.chdir(self._saved_dir)
|
||||
self._saved_dir = None
|
||||
|
||||
def create(self, name, contents):
|
||||
dname = os.path.dirname(name)
|
||||
if len(dname) > 0 and not os.path.isdir(dname):
|
||||
os.makedirs(dname)
|
||||
|
||||
with open(name, "a", encoding="utf-8") as f:
|
||||
f.write(
|
||||
contents
|
||||
if isinstance(contents, str)
|
||||
else contents.decode("utf-8")
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def mkdtemp(base_directory=None):
|
||||
prefix = f"dvc-test.{os.getpid()}."
|
||||
suffix = f".{shortuuid.uuid()}"
|
||||
return tempfile.mkdtemp(
|
||||
prefix=prefix, suffix=suffix, dir=base_directory
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
self._pushd(self._root_dir)
|
||||
self.create(self.FOO, self.FOO_CONTENTS)
|
||||
self.create(self.BAR, self.BAR_CONTENTS)
|
||||
self.create(self.CODE, self.CODE_CONTENTS)
|
||||
os.mkdir(self.DATA_DIR)
|
||||
os.mkdir(self.DATA_SUB_DIR)
|
||||
self.create(self.DATA, self.DATA_CONTENTS)
|
||||
self.create(self.DATA_SUB, self.DATA_SUB_CONTENTS)
|
||||
self.create(self.UNICODE, self.UNICODE_CONTENTS)
|
||||
|
||||
def tearDown(self):
|
||||
self._popd()
|
||||
try:
|
||||
remove(self._root_dir)
|
||||
except OSError as exc:
|
||||
# pylint: disable=no-member
|
||||
# We ignore this under Windows with a warning because it happened
|
||||
# to be really hard to trace all not properly closed files.
|
||||
#
|
||||
# Best guess so far is that gitpython is the culprit:
|
||||
# it opens files and uses __del__ to close them, which can happen
|
||||
# late in current pythons. TestGitFixture and TestDvcFixture try
|
||||
# to close that and it works on most of the tests, but not all.
|
||||
# Repos and thus git repos are created all over the dvc ;)
|
||||
if os.name == "nt" and exc.winerror == 32:
|
||||
warnings.warn("Failed to remove test dir: " + str(exc))
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
class TestGitFixture(TestDirFixture):
|
||||
N_RETRIES = 5
|
||||
|
||||
def setUp(self):
|
||||
from scmrepo.git import Git
|
||||
|
||||
super().setUp()
|
||||
self.git = Git.init(".")
|
||||
self.git.add_commit(self.CODE, message="add code")
|
||||
|
||||
def tearDown(self):
|
||||
self.git.close()
|
||||
super().tearDown()
|
||||
|
||||
|
||||
class TestDvcFixture(TestDirFixture):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.dvc = DvcRepo.init(self.root_dir, no_scm=True)
|
||||
|
||||
def tearDown(self):
|
||||
self.dvc.close()
|
||||
super().tearDown()
|
||||
|
||||
|
||||
class TestDvcGitFixture(TestGitFixture):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.dvc = DvcRepo.init(self.root_dir)
|
||||
self.dvc.scm.commit("init dvc")
|
||||
|
||||
def tearDown(self):
|
||||
self.dvc.close()
|
||||
super().tearDown()
|
||||
|
||||
|
||||
# NOTE: Inheritance order in the classes below is important.
|
||||
|
||||
|
||||
class TestDir(TestDirFixture, TestCase):
|
||||
def __init__(self, methodName):
|
||||
TestDirFixture.__init__(self)
|
||||
TestCase.__init__(self, methodName)
|
||||
|
||||
|
||||
class TestDvc(TestDvcFixture, TestCase):
|
||||
def __init__(self, methodName):
|
||||
TestDvcFixture.__init__(self)
|
||||
TestCase.__init__(self, methodName)
|
||||
self._caplog = None
|
||||
self._capsys = None
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def inject_fixtures(self, caplog, capsys):
|
||||
self._caplog = caplog
|
||||
self._capsys = capsys
|
||||
|
||||
|
||||
class TestDvcGit(TestDvcGitFixture, TestCase):
|
||||
def __init__(self, methodName):
|
||||
TestDvcGitFixture.__init__(self)
|
||||
TestCase.__init__(self, methodName)
|
||||
self._caplog = None
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def inject_fixtures(self, caplog):
|
||||
self._caplog = caplog
|
||||
+2
-5
@@ -9,7 +9,7 @@ from dvc.testing.fixtures import * # noqa, pylint: disable=wildcard-import
|
||||
|
||||
from .dir_helpers import * # noqa, pylint: disable=wildcard-import
|
||||
from .remotes import * # noqa, pylint: disable=wildcard-import
|
||||
from .utils.scriptify import scriptify
|
||||
from .scripts import * # noqa, pylint: disable=wildcard-import
|
||||
|
||||
# Prevent updater and analytics from running their processes
|
||||
os.environ["DVC_TEST"] = "true"
|
||||
@@ -172,9 +172,6 @@ def custom_template(tmp_dir, dvc):
|
||||
return template
|
||||
|
||||
|
||||
scriptify_fixture = pytest.fixture(lambda: scriptify, name="scriptify")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mocked_webbrowser_open(mocker):
|
||||
mocker.patch("webbrowser.open")
|
||||
@@ -218,7 +215,7 @@ defaultBranch=master
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def run_copy_metrics(tmp_dir, run_copy):
|
||||
def run_copy_metrics(tmp_dir, copy_script):
|
||||
def run(
|
||||
file1,
|
||||
file2,
|
||||
|
||||
+2
-29
@@ -47,7 +47,6 @@ from global repo template to creating everything inplace, which:
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -67,17 +66,7 @@ disable_other_loggers()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def run_copy(tmp_dir, dvc):
|
||||
tmp_dir.gen(
|
||||
"copy.py",
|
||||
(
|
||||
"import sys, shutil, os\n"
|
||||
"shutil.copyfile(sys.argv[1], sys.argv[2]) "
|
||||
"if os.path.isfile(sys.argv[1]) "
|
||||
"else shutil.copytree(sys.argv[1], sys.argv[2])"
|
||||
),
|
||||
)
|
||||
|
||||
def run_copy(tmp_dir, copy_script, dvc):
|
||||
def run_copy(src, dst, **run_kwargs):
|
||||
wdir = pathlib.Path(run_kwargs.get("wdir", "."))
|
||||
wdir = pathlib.Path("../" * len(wdir.parts))
|
||||
@@ -94,23 +83,7 @@ def run_copy(tmp_dir, dvc):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def run_head(tmp_dir, dvc):
|
||||
"""Output first line of each file to different file with '-1' appended.
|
||||
Useful for tracking multiple outputs/dependencies which are not a copy
|
||||
of each others.
|
||||
"""
|
||||
tmp_dir.gen(
|
||||
{
|
||||
"head.py": dedent(
|
||||
"""
|
||||
import sys
|
||||
for file in sys.argv[1:]:
|
||||
with open(file) as f, open(file +"-1","w+") as w:
|
||||
w.write(f.readline())
|
||||
"""
|
||||
)
|
||||
}
|
||||
)
|
||||
def run_head(tmp_dir, head_script, dvc):
|
||||
script = os.path.abspath(tmp_dir / "head.py")
|
||||
|
||||
def run(*args, **run_kwargs):
|
||||
|
||||
@@ -13,7 +13,7 @@ from dvc.repo.experiments.utils import exp_refs_by_rev
|
||||
from dvc.scm import resolve_rev
|
||||
from dvc.stage.exceptions import StageFileDoesNotExistError
|
||||
from dvc.utils.serialize import PythonFileCorruptedError
|
||||
from tests.func.test_repro_multistage import COPY_SCRIPT
|
||||
from tests.scripts import COPY_SCRIPT
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name", [None, "foo"])
|
||||
@@ -209,8 +209,7 @@ def test_get_baseline(tmp_dir, scm, dvc, exp_stage):
|
||||
assert dvc.experiments.get_baseline(f"{CELERY_STASH}@{{1}}") == init_rev
|
||||
|
||||
|
||||
def test_update_py_params(tmp_dir, scm, dvc, test_queue):
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
def test_update_py_params(tmp_dir, scm, dvc, test_queue, copy_script):
|
||||
tmp_dir.gen("params.py", "INT = 1\n")
|
||||
stage = dvc.run(
|
||||
cmd="python copy.py params.py metrics.py",
|
||||
@@ -371,8 +370,7 @@ def test_no_scm(tmp_dir):
|
||||
getattr(dvc.experiments, cmd)()
|
||||
|
||||
|
||||
def test_untracked(tmp_dir, scm, dvc, caplog, workspace):
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
def test_untracked(tmp_dir, scm, dvc, caplog, workspace, copy_script):
|
||||
tmp_dir.scm_gen("params.yaml", "foo: 1", commit="track params")
|
||||
stage = dvc.run(
|
||||
cmd="python copy.py params.yaml metrics.yaml",
|
||||
@@ -600,9 +598,10 @@ def test_fix_exp_head(tmp_dir, scm, tail):
|
||||
"params, target",
|
||||
itertools.product(("foo: 1", "foo: 2"), (True, False)),
|
||||
)
|
||||
def test_modified_data_dep(tmp_dir, scm, dvc, workspace, params, target):
|
||||
def test_modified_data_dep(
|
||||
tmp_dir, scm, dvc, workspace, params, target, copy_script
|
||||
):
|
||||
tmp_dir.dvc_gen("data", "data")
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
tmp_dir.gen("params.yaml", "foo: 1")
|
||||
exp_stage = dvc.run(
|
||||
cmd="python copy.py params.yaml metrics.yaml",
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import pytest
|
||||
from funcy import first
|
||||
|
||||
from tests.func.test_repro_multistage import COPY_SCRIPT
|
||||
|
||||
|
||||
@pytest.mark.parametrize("queued, expected", [(True, 0), (False, 1)])
|
||||
def test_workspace(tmp_dir, scm, dvc, queued, expected):
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
def test_workspace(tmp_dir, scm, dvc, queued, expected, copy_script):
|
||||
tmp_dir.gen("params.yaml", "foo: 1")
|
||||
|
||||
stage = dvc.run(
|
||||
@@ -26,8 +23,7 @@ def test_workspace(tmp_dir, scm, dvc, queued, expected):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("queued, expected", [(True, 0), (False, 2)])
|
||||
def test_all_commits(tmp_dir, scm, dvc, queued, expected):
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
def test_all_commits(tmp_dir, scm, dvc, queued, expected, copy_script):
|
||||
tmp_dir.gen("params.yaml", "foo: 1")
|
||||
|
||||
stage = dvc.run(
|
||||
@@ -55,8 +51,7 @@ def test_all_commits(tmp_dir, scm, dvc, queued, expected):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("queued, expected", [(True, 2), (False, 3)])
|
||||
def test_all_branches(tmp_dir, scm, dvc, queued, expected):
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
def test_all_branches(tmp_dir, scm, dvc, queued, expected, copy_script):
|
||||
tmp_dir.gen("params.yaml", "foo: 1")
|
||||
|
||||
stage = dvc.run(
|
||||
@@ -91,8 +86,7 @@ def test_all_branches(tmp_dir, scm, dvc, queued, expected):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("queued, expected", [(True, 2), (False, 3)])
|
||||
def test_all_tags(tmp_dir, scm, dvc, queued, expected):
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
def test_all_tags(tmp_dir, scm, dvc, queued, expected, copy_script):
|
||||
tmp_dir.gen("params.yaml", "foo: 1")
|
||||
|
||||
stage = dvc.run(
|
||||
|
||||
@@ -345,11 +345,13 @@ def test_init_interactive_live(
|
||||
"train": {
|
||||
"cmd": "python script.py",
|
||||
"deps": ["data", "script.py"],
|
||||
"metrics": [{"dvclive.json": {"cache": False}}],
|
||||
"metrics": [
|
||||
{os.path.join("dvclive", "metrics.json"): {"cache": False}}
|
||||
],
|
||||
"outs": ["models"],
|
||||
"params": [{"params.yaml": None}],
|
||||
"plots": [
|
||||
{os.path.join("dvclive", "scalars"): {"cache": False}}
|
||||
{os.path.join("dvclive", "plots"): {"cache": False}}
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -434,12 +436,12 @@ def test_init_with_type_default_and_live_provided(
|
||||
"cmd": "cmd",
|
||||
"deps": ["data", "src"],
|
||||
"metrics": [
|
||||
{"live.json": {"cache": False}},
|
||||
{os.path.join("live", "metrics.json"): {"cache": False}},
|
||||
],
|
||||
"outs": ["models"],
|
||||
"params": [{"params.yaml": None}],
|
||||
"plots": [
|
||||
{os.path.join("live", "scalars"): {"cache": False}},
|
||||
{os.path.join("live", "plots"): {"cache": False}},
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -477,13 +479,13 @@ def test_init_with_live_and_metrics_plots_provided(
|
||||
"cmd": "cmd",
|
||||
"deps": ["data", "src"],
|
||||
"metrics": [
|
||||
{"live.json": {"cache": False}},
|
||||
{os.path.join("live", "metrics.json"): {"cache": False}},
|
||||
{"metrics.json": {"cache": False}},
|
||||
],
|
||||
"outs": ["models"],
|
||||
"params": [{"params.yaml": None}],
|
||||
"plots": [
|
||||
{os.path.join("live", "scalars"): {"cache": False}},
|
||||
{os.path.join("live", "plots"): {"cache": False}},
|
||||
{"plots": {"cache": False}},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ from ..utils.test_hydra import hydra_setup
|
||||
def test_modify_params(params_repo, dvc, changes, expected):
|
||||
dvc.experiments.run(params=changes)
|
||||
# pylint: disable=unspecified-encoding
|
||||
with open("params.yaml", mode="r") as fobj:
|
||||
with open("params.yaml") as fobj:
|
||||
assert fobj.read().strip() == expected
|
||||
|
||||
|
||||
@@ -110,6 +110,11 @@ def test_hydra_compose_and_dump(
|
||||
["foo=bar,baz"],
|
||||
[{"params.yaml": ["foo=bar"]}, {"params.yaml": ["foo=baz"]}],
|
||||
),
|
||||
(
|
||||
False,
|
||||
[],
|
||||
[{}],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_hydra_sweep(
|
||||
|
||||
@@ -20,7 +20,6 @@ from dvc.repo.experiments.utils import exp_refs_by_rev
|
||||
from dvc.utils import relpath
|
||||
from dvc.utils.fs import makedirs
|
||||
from dvc.utils.serialize import YAMLFileCorruptedError
|
||||
from tests.func.test_repro_multistage import COPY_SCRIPT
|
||||
|
||||
LOCK_CONTENTS = {
|
||||
"read": {
|
||||
@@ -313,10 +312,10 @@ def test_show_filter(
|
||||
scm,
|
||||
dvc,
|
||||
capsys,
|
||||
copy_script,
|
||||
):
|
||||
capsys.readouterr()
|
||||
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
params_file = tmp_dir / "params.yaml"
|
||||
params_data = {
|
||||
"foo": 1,
|
||||
@@ -679,8 +678,7 @@ def test_show_csv(tmp_dir, scm, dvc, exp_stage, capsys):
|
||||
)
|
||||
|
||||
|
||||
def test_show_only_changed(tmp_dir, dvc, scm, capsys):
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
def test_show_only_changed(tmp_dir, dvc, scm, capsys, copy_script):
|
||||
params_file = tmp_dir / "params.yaml"
|
||||
params_data = {
|
||||
"foo": 1,
|
||||
@@ -726,13 +724,14 @@ def test_show_only_changed(tmp_dir, dvc, scm, capsys):
|
||||
assert "metrics.yaml:bar" in cap.out
|
||||
|
||||
|
||||
def test_show_parallel_coordinates(tmp_dir, dvc, scm, mocker, capsys):
|
||||
def test_show_parallel_coordinates(
|
||||
tmp_dir, dvc, scm, mocker, capsys, copy_script
|
||||
):
|
||||
from dvc.commands.experiments import show
|
||||
|
||||
webbroser_open = mocker.patch("webbrowser.open")
|
||||
show_experiments = mocker.spy(show, "show_experiments")
|
||||
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
params_file = tmp_dir / "params.yaml"
|
||||
params_data = {
|
||||
"foo": 1,
|
||||
@@ -807,8 +806,7 @@ def test_show_parallel_coordinates(tmp_dir, dvc, scm, mocker, capsys):
|
||||
|
||||
|
||||
@pytest.mark.vscode
|
||||
def test_show_outs(tmp_dir, dvc, scm, erepo_dir):
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
def test_show_outs(tmp_dir, dvc, scm, erepo_dir, copy_script):
|
||||
params_file = tmp_dir / "params.yaml"
|
||||
params_data = {
|
||||
"foo": 1,
|
||||
@@ -889,8 +887,7 @@ def test_show_outs(tmp_dir, dvc, scm, erepo_dir):
|
||||
}
|
||||
|
||||
|
||||
def test_metrics_renaming(tmp_dir, dvc, scm, capsys):
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
def test_metrics_renaming(tmp_dir, dvc, scm, capsys, copy_script):
|
||||
params_file = tmp_dir / "params.yaml"
|
||||
params_data = {
|
||||
"foo": 1,
|
||||
|
||||
+145
-194
@@ -4,8 +4,7 @@ import os
|
||||
import shutil
|
||||
import stat
|
||||
import textwrap
|
||||
import time
|
||||
from unittest.mock import call, patch
|
||||
from unittest.mock import call
|
||||
|
||||
import colorama
|
||||
import pytest
|
||||
@@ -35,12 +34,11 @@ from dvc.stage.exceptions import (
|
||||
StagePathNotFoundError,
|
||||
)
|
||||
from dvc.testing.workspace_tests import TestAdd
|
||||
from dvc.utils import LARGE_DIR_SIZE, relpath
|
||||
from dvc.utils import LARGE_DIR_SIZE
|
||||
from dvc.utils.fs import path_isin
|
||||
from dvc.utils.serialize import YAMLFileCorruptedError, load_yaml
|
||||
from dvc.utils.serialize import YAMLFileCorruptedError
|
||||
from dvc_data.hashfile.hash import file_md5
|
||||
from dvc_data.hashfile.hash_info import HashInfo
|
||||
from tests.basic_env import TestDvc
|
||||
from tests.utils import get_gitignore_content
|
||||
|
||||
|
||||
@@ -120,49 +118,38 @@ def test_add_directory(tmp_dir, dvc):
|
||||
assert "\\" not in part
|
||||
|
||||
|
||||
class TestAddDirectoryRecursive(TestDvc):
|
||||
def test(self):
|
||||
stages = self.dvc.add(self.DATA_DIR, recursive=True)
|
||||
self.assertEqual(len(stages), 2)
|
||||
def test_add_directory_recursive(tmp_dir, dvc):
|
||||
tmp_dir.gen("data", {"file1": "file1", "sub": {"file2": "file2"}})
|
||||
stages = dvc.add("data", recursive=True)
|
||||
assert len(stages) == 2
|
||||
|
||||
|
||||
class TestAddCmdDirectoryRecursive(TestDvc):
|
||||
def test(self):
|
||||
ret = main(["add", "--recursive", self.DATA_DIR])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
def test_warn_about_large_directories(self):
|
||||
warning = (
|
||||
"You are adding a large directory 'large-dir' recursively."
|
||||
"\nConsider tracking it as a whole instead with "
|
||||
"`{cyan}dvc add large-dir{nc}`"
|
||||
).format(
|
||||
cyan=colorama.Fore.CYAN,
|
||||
nc=colorama.Style.RESET_ALL,
|
||||
)
|
||||
|
||||
os.mkdir("large-dir")
|
||||
|
||||
# Create a lot of files
|
||||
for iteration in range(LARGE_DIR_SIZE + 1):
|
||||
path = os.path.join("large-dir", str(iteration))
|
||||
with open(path, "w", encoding="utf-8") as fobj:
|
||||
fobj.write(path)
|
||||
|
||||
assert main(["add", "--recursive", "large-dir"]) == 0
|
||||
assert warning in self._capsys.readouterr()[1]
|
||||
def test_add_cmd_directory_recursive(tmp_dir, dvc):
|
||||
tmp_dir.gen("data", {"file1": "file1", "sub": {"file2": "file2"}})
|
||||
assert main(["add", "--recursive", "data"]) == 0
|
||||
|
||||
|
||||
class TestAddDirectoryWithForwardSlash(TestDvc):
|
||||
def test(self):
|
||||
dname = "directory/"
|
||||
os.mkdir(dname)
|
||||
self.create(os.path.join(dname, "file"), "file")
|
||||
stages = self.dvc.add(dname)
|
||||
self.assertEqual(len(stages), 1)
|
||||
stage = stages[0]
|
||||
self.assertTrue(stage is not None)
|
||||
self.assertEqual(os.path.abspath("directory.dvc"), stage.path)
|
||||
def test_warn_about_large_directories_recursive_add(tmp_dir, dvc, capsys):
|
||||
warning = (
|
||||
"You are adding a large directory 'large-dir' recursively."
|
||||
"\nConsider tracking it as a whole instead with "
|
||||
"`{cyan}dvc add large-dir{nc}`"
|
||||
).format(
|
||||
cyan=colorama.Fore.CYAN,
|
||||
nc=colorama.Style.RESET_ALL,
|
||||
)
|
||||
|
||||
tmp_dir.gen(
|
||||
"large-dir", {f"{i}": f"{i}" for i in range(LARGE_DIR_SIZE + 1)}
|
||||
)
|
||||
assert main(["add", "--recursive", "large-dir"]) == 0
|
||||
assert warning in capsys.readouterr()[1]
|
||||
|
||||
|
||||
def test_add_directory_with_forward_slash(tmp_dir, dvc):
|
||||
tmp_dir.gen("directory", {"file": "file"})
|
||||
(stage,) = dvc.add("directory/")
|
||||
assert stage.relpath == "directory.dvc"
|
||||
|
||||
|
||||
def test_add_tracked_file(tmp_dir, scm, dvc):
|
||||
@@ -178,33 +165,23 @@ def test_add_tracked_file(tmp_dir, scm, dvc):
|
||||
dvc.add(path)
|
||||
|
||||
|
||||
class TestAddDirWithExistingCache(TestDvc):
|
||||
def test(self):
|
||||
dname = "a"
|
||||
fname = os.path.join(dname, "b")
|
||||
os.mkdir(dname)
|
||||
shutil.copyfile(self.FOO, fname)
|
||||
def test_add_dir_with_existing_cache(tmp_dir, dvc):
|
||||
tmp_dir.gen({"foo": "foo", "dir": {"file": "foo"}})
|
||||
|
||||
stages = self.dvc.add(self.FOO)
|
||||
self.assertEqual(len(stages), 1)
|
||||
self.assertTrue(stages[0] is not None)
|
||||
stages = self.dvc.add(dname)
|
||||
self.assertEqual(len(stages), 1)
|
||||
self.assertTrue(stages[0] is not None)
|
||||
(stage,) = dvc.add("foo")
|
||||
assert stage is not None
|
||||
(stage,) = dvc.add("dir")
|
||||
assert stage is not None
|
||||
|
||||
|
||||
class TestAddModifiedDir(TestDvc):
|
||||
def test(self):
|
||||
stages = self.dvc.add(self.DATA_DIR)
|
||||
self.assertEqual(len(stages), 1)
|
||||
self.assertTrue(stages[0] is not None)
|
||||
os.unlink(self.DATA)
|
||||
def test_add_modified_dir(tmp_dir, dvc):
|
||||
tmp_dir.gen("data", {"foo": "foo", "sub": {"bar": "bar"}})
|
||||
(stage,) = dvc.add("data")
|
||||
assert stage is not None
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
stages = self.dvc.add(self.DATA_DIR)
|
||||
self.assertEqual(len(stages), 1)
|
||||
self.assertTrue(stages[0] is not None)
|
||||
(tmp_dir / "data" / "foo").unlink()
|
||||
(stage,) = dvc.add("data")
|
||||
assert stage is not None
|
||||
|
||||
|
||||
def test_add_file_in_dir(tmp_dir, dvc):
|
||||
@@ -326,57 +303,47 @@ def test_add_external_relpath(tmp_dir, dvc, local_cloud):
|
||||
assert dvc.status() == {}
|
||||
|
||||
|
||||
class TestAddLocalRemoteFile(TestDvc):
|
||||
def test(self):
|
||||
"""
|
||||
Making sure that 'remote' syntax is handled properly for local outs.
|
||||
"""
|
||||
cwd = os.getcwd()
|
||||
remote = "myremote"
|
||||
def test_add_local_remote_file(tmp_dir, dvc):
|
||||
"""
|
||||
Making sure that 'remote' syntax is handled properly for local outs.
|
||||
"""
|
||||
tmp_dir.gen({"foo": "foo", "bar": "bar"})
|
||||
tmp_dir.add_remote(url=tmp_dir.fs_path, name="myremote")
|
||||
|
||||
ret = main(["remote", "add", remote, cwd])
|
||||
self.assertEqual(ret, 0)
|
||||
assert main(["add", "remote://myremote/foo"]) == 0
|
||||
d = (tmp_dir / "foo.dvc").load_yaml()
|
||||
assert d["outs"][0]["path"] == "remote://myremote/foo"
|
||||
|
||||
self.dvc.config.load()
|
||||
|
||||
foo = f"remote://{remote}/{self.FOO}"
|
||||
ret = main(["add", foo])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
d = load_yaml("foo.dvc")
|
||||
self.assertEqual(d["outs"][0]["path"], foo)
|
||||
|
||||
bar = os.path.join(cwd, self.BAR)
|
||||
ret = main(["add", bar])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
d = load_yaml("bar.dvc")
|
||||
self.assertEqual(d["outs"][0]["path"], self.BAR)
|
||||
assert main(["add", (tmp_dir / "bar").fs_path]) == 0
|
||||
d = (tmp_dir / "bar.dvc").load_yaml()
|
||||
assert d["outs"][0]["path"] == "bar"
|
||||
|
||||
|
||||
class TestCmdAdd(TestDvc):
|
||||
def test(self):
|
||||
ret = main(["add", self.FOO])
|
||||
self.assertEqual(ret, 0)
|
||||
def test_cmd_add(tmp_dir, dvc):
|
||||
tmp_dir.gen("foo", "foo")
|
||||
ret = main(["add", "foo"])
|
||||
assert ret == 0
|
||||
|
||||
ret = main(["add", "non-existing-file"])
|
||||
self.assertNotEqual(ret, 0)
|
||||
ret = main(["add", "non-existing-file"])
|
||||
assert ret != 0
|
||||
|
||||
|
||||
class TestDoubleAddUnchanged(TestDvc):
|
||||
def test_file(self):
|
||||
ret = main(["add", self.FOO])
|
||||
self.assertEqual(ret, 0)
|
||||
def test_double_add_unchanged_file(tmp_dir, dvc):
|
||||
tmp_dir.gen("foo", "foo")
|
||||
ret = main(["add", "foo"])
|
||||
assert ret == 0
|
||||
|
||||
ret = main(["add", self.FOO])
|
||||
self.assertEqual(ret, 0)
|
||||
ret = main(["add", "foo"])
|
||||
assert ret == 0
|
||||
|
||||
def test_dir(self):
|
||||
ret = main(["add", self.DATA_DIR])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
ret = main(["add", self.DATA_DIR])
|
||||
self.assertEqual(ret, 0)
|
||||
def test_double_add_unchanged_dir(tmp_dir, dvc):
|
||||
tmp_dir.gen("data", {"foo": "foo"})
|
||||
ret = main(["add", "data"])
|
||||
assert ret == 0
|
||||
|
||||
ret = main(["add", "data"])
|
||||
assert ret == 0
|
||||
|
||||
|
||||
def test_should_update_state_entry_for_file_after_add(mocker, dvc, tmp_dir):
|
||||
@@ -443,19 +410,17 @@ def test_should_update_state_entry_for_directory_after_add(
|
||||
assert file_md5_counter.mock.call_count == 10
|
||||
|
||||
|
||||
class TestAddCommit(TestDvc):
|
||||
def test(self):
|
||||
ret = main(["add", self.FOO, "--no-commit"])
|
||||
self.assertEqual(ret, 0)
|
||||
self.assertTrue(os.path.isfile(self.FOO))
|
||||
self.assertFalse(os.path.exists(self.dvc.odb.local.path))
|
||||
def test_add_commit(tmp_dir, dvc):
|
||||
tmp_dir.gen("foo", "foo")
|
||||
ret = main(["add", "foo", "--no-commit"])
|
||||
assert ret == 0
|
||||
assert os.path.isfile("foo")
|
||||
assert not os.path.exists(dvc.odb.local.path)
|
||||
|
||||
ret = main(["commit", self.FOO + ".dvc"])
|
||||
self.assertEqual(ret, 0)
|
||||
self.assertTrue(os.path.isfile(self.FOO))
|
||||
self.assertTrue(
|
||||
self.dvc.odb.local.exists("acbd18db4cc2f85cedef654fccc4a4d8")
|
||||
)
|
||||
ret = main(["commit", "foo" + ".dvc"])
|
||||
assert ret == 0
|
||||
assert os.path.isfile("foo")
|
||||
assert dvc.odb.local.exists("acbd18db4cc2f85cedef654fccc4a4d8")
|
||||
|
||||
|
||||
def test_should_collect_dir_cache_only_once(mocker, tmp_dir, dvc):
|
||||
@@ -474,80 +439,66 @@ def test_should_collect_dir_cache_only_once(mocker, tmp_dir, dvc):
|
||||
assert counter.mock.call_count == 5
|
||||
|
||||
|
||||
class TestShouldPlaceStageInDataDirIfRepositoryBelowSymlink(TestDvc):
|
||||
def test(self):
|
||||
def is_symlink_true_below_dvc_root(path):
|
||||
if path == os.path.dirname(self.dvc.root_dir):
|
||||
return True
|
||||
return False
|
||||
def test_should_place_stage_in_data_dir_if_repository_below_symlink(
|
||||
mocker, tmp_dir, dvc
|
||||
):
|
||||
def is_symlink_true_below_dvc_root(path):
|
||||
return path == os.path.dirname(dvc.root_dir)
|
||||
|
||||
with patch.object(
|
||||
system, "is_symlink", side_effect=is_symlink_true_below_dvc_root
|
||||
):
|
||||
tmp_dir.gen({"data": {"foo": "foo"}})
|
||||
mocker.patch.object(
|
||||
system, "is_symlink", side_effect=is_symlink_true_below_dvc_root
|
||||
)
|
||||
ret = main(["add", os.path.join("data", "foo")])
|
||||
assert ret == 0
|
||||
|
||||
ret = main(["add", self.DATA])
|
||||
self.assertEqual(0, ret)
|
||||
|
||||
stage_file_path_on_data_below_symlink = (
|
||||
os.path.basename(self.DATA) + DVC_FILE_SUFFIX
|
||||
)
|
||||
self.assertFalse(
|
||||
os.path.exists(stage_file_path_on_data_below_symlink)
|
||||
)
|
||||
|
||||
stage_file_path = self.DATA + DVC_FILE_SUFFIX
|
||||
self.assertTrue(os.path.exists(stage_file_path))
|
||||
assert not (tmp_dir / "foo.dvc").exists()
|
||||
assert (tmp_dir / "data" / "foo.dvc").exists()
|
||||
|
||||
|
||||
class TestShouldThrowProperExceptionOnCorruptedStageFile(TestDvc):
|
||||
def test(self):
|
||||
ret = main(["add", self.FOO])
|
||||
assert 0 == ret
|
||||
def test_should_throw_proper_exception_on_corrupted_stage_file(
|
||||
caplog, tmp_dir, dvc
|
||||
):
|
||||
tmp_dir.gen({"foo": "foo", "bar": " bar"})
|
||||
assert main(["add", "foo"]) == 0
|
||||
|
||||
foo_stage = relpath(self.FOO + DVC_FILE_SUFFIX)
|
||||
with (tmp_dir / "foo.dvc").open("a+") as f:
|
||||
f.write("this will break yaml file structure")
|
||||
|
||||
# corrupt stage file
|
||||
with open(foo_stage, "a+", encoding="utf-8") as file:
|
||||
file.write("this will break yaml file structure")
|
||||
|
||||
self._caplog.clear()
|
||||
|
||||
ret = main(["add", self.BAR])
|
||||
assert 1 == ret
|
||||
|
||||
expected_error = (
|
||||
f"unable to read: '{foo_stage}', YAML file structure is corrupted"
|
||||
)
|
||||
|
||||
assert expected_error in self._caplog.text
|
||||
caplog.clear()
|
||||
assert main(["add", "bar"]) == 1
|
||||
expected_error = (
|
||||
"unable to read: 'foo.dvc', YAML file structure is corrupted"
|
||||
)
|
||||
assert expected_error in caplog.text
|
||||
|
||||
|
||||
class TestAddFilename(TestDvc):
|
||||
def test(self):
|
||||
ret = main(["add", self.FOO, self.BAR, "--file", "error.dvc"])
|
||||
self.assertNotEqual(0, ret)
|
||||
def test_add_filename(tmp_dir, dvc):
|
||||
tmp_dir.gen({"foo": "foo", "bar": "bar", "data": {"file": "file"}})
|
||||
ret = main(["add", "foo", "bar", "--file", "error.dvc"])
|
||||
assert ret != 0
|
||||
|
||||
ret = main(["add", "-R", self.DATA_DIR, "--file", "error.dvc"])
|
||||
self.assertNotEqual(0, ret)
|
||||
ret = main(["add", "-R", "data", "--file", "error.dvc"])
|
||||
assert ret != 0
|
||||
|
||||
with self.assertRaises(RecursiveAddingWhileUsingFilename):
|
||||
self.dvc.add(self.DATA_DIR, recursive=True, fname="error.dvc")
|
||||
with pytest.raises(RecursiveAddingWhileUsingFilename):
|
||||
dvc.add("data", recursive=True, fname="error.dvc")
|
||||
|
||||
ret = main(["add", self.DATA_DIR, "--file", "data_directory.dvc"])
|
||||
self.assertEqual(0, ret)
|
||||
self.assertTrue(os.path.exists("data_directory.dvc"))
|
||||
ret = main(["add", "data", "--file", "data_directory.dvc"])
|
||||
assert ret == 0
|
||||
assert (tmp_dir / "data_directory.dvc").exists()
|
||||
|
||||
ret = main(["add", self.FOO, "--file", "bar.dvc"])
|
||||
self.assertEqual(0, ret)
|
||||
self.assertTrue(os.path.exists("bar.dvc"))
|
||||
self.assertFalse(os.path.exists("foo.dvc"))
|
||||
ret = main(["add", "foo", "--file", "bar.dvc"])
|
||||
assert ret == 0
|
||||
assert (tmp_dir / "bar.dvc").exists()
|
||||
assert not (tmp_dir / "foo.dvc").exists()
|
||||
|
||||
os.remove("bar.dvc")
|
||||
(tmp_dir / "bar.dvc").unlink()
|
||||
|
||||
ret = main(["add", self.FOO, "--file", "bar.dvc"])
|
||||
self.assertEqual(0, ret)
|
||||
self.assertTrue(os.path.exists("bar.dvc"))
|
||||
self.assertFalse(os.path.exists("foo.dvc"))
|
||||
ret = main(["add", "foo", "--file", "bar.dvc"])
|
||||
assert ret == 0
|
||||
assert (tmp_dir / "bar.dvc").exists()
|
||||
assert not (tmp_dir / "foo.dvc").exists()
|
||||
|
||||
|
||||
def test_failed_add_cleanup(tmp_dir, scm, dvc):
|
||||
@@ -577,25 +528,25 @@ def test_should_not_track_git_internal_files(mocker, dvc, tmp_dir):
|
||||
assert ".git" not in fname
|
||||
|
||||
|
||||
class TestAddUnprotected(TestDvc):
|
||||
def test(self):
|
||||
ret = main(["config", "cache.type", "hardlink"])
|
||||
self.assertEqual(ret, 0)
|
||||
def test_add_unprotected(tmp_dir, dvc):
|
||||
tmp_dir.gen("foo", "foo")
|
||||
ret = main(["config", "cache.type", "hardlink"])
|
||||
assert ret == 0
|
||||
|
||||
ret = main(["add", self.FOO])
|
||||
self.assertEqual(ret, 0)
|
||||
ret = main(["add", "foo"])
|
||||
assert ret == 0
|
||||
|
||||
self.assertFalse(os.access(self.FOO, os.W_OK))
|
||||
self.assertTrue(system.is_hardlink(self.FOO))
|
||||
assert not os.access("foo", os.W_OK)
|
||||
assert system.is_hardlink("foo")
|
||||
|
||||
ret = main(["unprotect", self.FOO])
|
||||
self.assertEqual(ret, 0)
|
||||
ret = main(["unprotect", "foo"])
|
||||
assert ret == 0
|
||||
|
||||
ret = main(["add", self.FOO])
|
||||
self.assertEqual(ret, 0)
|
||||
ret = main(["add", "foo"])
|
||||
assert ret == 0
|
||||
|
||||
self.assertFalse(os.access(self.FOO, os.W_OK))
|
||||
self.assertTrue(system.is_hardlink(self.FOO))
|
||||
assert not os.access("foo", os.W_OK)
|
||||
assert system.is_hardlink("foo")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -12,7 +12,7 @@ from tests.utils import ANY
|
||||
def test_daemon_analytics(mocker, tmp_path):
|
||||
mock_send = mocker.patch("dvc.analytics.send")
|
||||
report = os.fspath(tmp_path)
|
||||
assert 0 == main(["daemon", "analytics", report])
|
||||
assert main(["daemon", "analytics", report]) == 0
|
||||
|
||||
mock_send.assert_called_with(report)
|
||||
|
||||
@@ -21,7 +21,7 @@ def test_main_analytics(mocker, tmp_dir, dvc):
|
||||
mock_is_enabled = mocker.patch("dvc.analytics.collect_and_send_report")
|
||||
mock_report = mocker.patch("dvc.analytics.is_enabled", return_value=True)
|
||||
tmp_dir.gen("foo", "text")
|
||||
assert 0 == main(["add", "foo"])
|
||||
assert main(["add", "foo"]) == 0
|
||||
assert mock_is_enabled.called
|
||||
assert mock_report.called
|
||||
|
||||
|
||||
+305
-409
@@ -1,16 +1,13 @@
|
||||
import collections
|
||||
import filecmp
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import textwrap
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from dvc.cli import main
|
||||
from dvc.dvcfile import DVC_FILE_SUFFIX, PIPELINE_FILE, Dvcfile
|
||||
from dvc.dvcfile import PIPELINE_FILE, Dvcfile
|
||||
from dvc.exceptions import (
|
||||
CheckoutError,
|
||||
CheckoutErrorSuggestGit,
|
||||
@@ -24,8 +21,7 @@ from dvc.stage.exceptions import StageFileDoesNotExistError
|
||||
from dvc.utils import relpath
|
||||
from dvc.utils.fs import remove
|
||||
from dvc.utils.serialize import dump_yaml, load_yaml
|
||||
from tests.basic_env import TestDvc, TestDvcGit
|
||||
from tests.func.test_repro import TestRepro
|
||||
from tests.utils import get_gitignore_content
|
||||
|
||||
logger = logging.getLogger("dvc")
|
||||
|
||||
@@ -36,465 +32,365 @@ def walk_files(directory):
|
||||
yield os.path.join(root, f)
|
||||
|
||||
|
||||
class TestCheckout(TestRepro):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
def test_checkout(tmp_dir, dvc, copy_script):
|
||||
tmp_dir.dvc_gen({"foo": "foo", "data": {"file": "file"}})
|
||||
dvc.run(
|
||||
fname="file1.dvc",
|
||||
outs=["file1"],
|
||||
deps=["foo", "copy.py"],
|
||||
cmd="python copy.py foo file1",
|
||||
single_stage=True,
|
||||
)
|
||||
remove(tmp_dir / "foo")
|
||||
remove("data")
|
||||
|
||||
stages = self.dvc.add(self.DATA_DIR)
|
||||
self.assertEqual(len(stages), 1)
|
||||
self.data_dir_stage = stages[0]
|
||||
self.assertTrue(self.data_dir_stage is not None)
|
||||
|
||||
self.orig = "orig"
|
||||
shutil.copy(self.FOO, self.orig)
|
||||
os.unlink(self.FOO)
|
||||
|
||||
self.orig_dir = "orig_dir"
|
||||
shutil.copytree(self.DATA_DIR, self.orig_dir)
|
||||
shutil.rmtree(self.DATA_DIR)
|
||||
|
||||
def test(self):
|
||||
self.dvc.checkout(force=True)
|
||||
self._test_checkout()
|
||||
|
||||
def _test_checkout(self):
|
||||
self.assertTrue(os.path.isfile(self.FOO))
|
||||
self.assertTrue(filecmp.cmp(self.FOO, self.orig, shallow=False))
|
||||
dvc.checkout(force=True)
|
||||
assert (tmp_dir / "foo").read_text() == "foo"
|
||||
assert (tmp_dir / "data").read_text() == {"file": "file"}
|
||||
|
||||
|
||||
class TestCheckoutSingleStage(TestCheckout):
|
||||
def test(self):
|
||||
ret = main(["checkout", "--force", self.foo_stage.path])
|
||||
self.assertEqual(ret, 0)
|
||||
def test_checkout_cli(tmp_dir, dvc, copy_script):
|
||||
tmp_dir.dvc_gen({"foo": "foo", "data": {"file": "file"}})
|
||||
dvc.run(
|
||||
fname="file1.dvc",
|
||||
outs=["file1"],
|
||||
deps=["foo", "copy.py"],
|
||||
cmd="python copy.py foo file1",
|
||||
single_stage=True,
|
||||
)
|
||||
remove(tmp_dir / "foo")
|
||||
remove("data")
|
||||
|
||||
ret = main(["checkout", "--force", self.data_dir_stage.path])
|
||||
self.assertEqual(ret, 0)
|
||||
assert main(["checkout", "--force"]) == 0
|
||||
assert (tmp_dir / "foo").read_text() == "foo"
|
||||
assert (tmp_dir / "data").read_text() == {"file": "file"}
|
||||
|
||||
self._test_checkout()
|
||||
remove(tmp_dir / "foo")
|
||||
remove("data")
|
||||
|
||||
assert main(["checkout", "--force", "foo.dvc"]) == 0
|
||||
assert main(["checkout", "--force", "data.dvc"]) == 0
|
||||
assert (tmp_dir / "foo").read_text() == "foo"
|
||||
assert (tmp_dir / "data").read_text() == {"file": "file"}
|
||||
|
||||
|
||||
class TestCheckoutCorruptedCacheFile(TestRepro):
|
||||
def test(self):
|
||||
cache = self.foo_stage.outs[0].cache_path
|
||||
def test_checkout_corrupted_cache_file(tmp_dir, dvc):
|
||||
(foo_stage,) = tmp_dir.dvc_gen("foo", "foo")
|
||||
cache = foo_stage.outs[0].cache_path
|
||||
|
||||
os.chmod(cache, 0o644)
|
||||
with open(cache, "a", encoding="utf-8") as fd:
|
||||
fd.write("1")
|
||||
os.chmod(cache, 0o644)
|
||||
with open(cache, "a", encoding="utf-8") as fd:
|
||||
fd.write("1")
|
||||
|
||||
with pytest.raises(CheckoutError):
|
||||
self.dvc.checkout(force=True)
|
||||
with pytest.raises(CheckoutError):
|
||||
dvc.checkout(force=True)
|
||||
|
||||
self.assertFalse(os.path.isfile(self.FOO))
|
||||
self.assertFalse(os.path.isfile(cache))
|
||||
assert not os.path.isfile("foo")
|
||||
assert not os.path.isfile(cache)
|
||||
|
||||
|
||||
class TestCheckoutCorruptedCacheDir(TestDvc):
|
||||
def test(self):
|
||||
from dvc_data.hashfile import load
|
||||
def test_checkout_corrupted_cache_dir(tmp_dir, dvc):
|
||||
from dvc_data.hashfile import load
|
||||
|
||||
# NOTE: using 'copy' so that cache and link don't have same inode
|
||||
ret = main(["config", "cache.type", "copy"])
|
||||
self.assertEqual(ret, 0)
|
||||
tmp_dir.gen("data", {"foo": "foo", "bar": "bar"})
|
||||
# NOTE: using 'copy' so that cache and link don't have same inode
|
||||
ret = main(["config", "cache.type", "copy"])
|
||||
assert ret == 0
|
||||
|
||||
self.dvc.config.load()
|
||||
dvc.config.load()
|
||||
|
||||
stages = self.dvc.add(self.DATA_DIR)
|
||||
self.assertEqual(len(stages), 1)
|
||||
self.assertEqual(len(stages[0].outs), 1)
|
||||
out = stages[0].outs[0]
|
||||
stages = dvc.add("data")
|
||||
assert len(stages) == 1
|
||||
assert len(stages[0].outs) == 1
|
||||
out = stages[0].outs[0]
|
||||
|
||||
# NOTE: modifying cache file for one of the files inside the directory
|
||||
# to check if dvc will detect that the cache is corrupted.
|
||||
obj = load(self.dvc.odb.local, out.hash_info)
|
||||
_, _, entry_oid = list(obj)[0]
|
||||
cache = self.dvc.odb.local.oid_to_path(entry_oid.value)
|
||||
# NOTE: modifying cache file for one of the files inside the directory
|
||||
# to check if dvc will detect that the cache is corrupted.
|
||||
obj = load(dvc.odb.local, out.hash_info)
|
||||
_, _, entry_oid = list(obj)[0]
|
||||
cache = dvc.odb.local.oid_to_path(entry_oid.value)
|
||||
|
||||
os.chmod(cache, 0o644)
|
||||
with open(cache, "w+", encoding="utf-8") as fobj:
|
||||
fobj.write("1")
|
||||
os.chmod(cache, 0o644)
|
||||
with open(cache, "w+", encoding="utf-8") as fobj:
|
||||
fobj.write("1")
|
||||
|
||||
with pytest.raises(CheckoutError):
|
||||
self.dvc.checkout(force=True)
|
||||
with pytest.raises(CheckoutError):
|
||||
dvc.checkout(force=True)
|
||||
|
||||
self.assertFalse(os.path.exists(cache))
|
||||
assert not os.path.exists(cache)
|
||||
|
||||
|
||||
class TestCmdCheckout(TestCheckout):
|
||||
def test(self):
|
||||
ret = main(["checkout", "--force"])
|
||||
self.assertEqual(ret, 0)
|
||||
self._test_checkout()
|
||||
def test_remove_files_when_checkout(tmp_dir, dvc, scm):
|
||||
# add the file into a separate branch
|
||||
scm.checkout("branch", True)
|
||||
ret = main(["checkout", "--force"])
|
||||
assert ret == 0
|
||||
tmp_dir.dvc_gen("file_in_a_branch", "random text", commit="add file")
|
||||
|
||||
# Checkout back in master
|
||||
scm.checkout("master")
|
||||
assert os.path.exists("file_in_a_branch")
|
||||
|
||||
# Make sure `dvc checkout` removes the file
|
||||
# self.dvc.checkout()
|
||||
ret = main(["checkout", "--force"])
|
||||
assert ret == 0
|
||||
assert not os.path.exists("file_in_a_branch")
|
||||
|
||||
|
||||
class CheckoutBase(TestDvcGit):
|
||||
GIT_IGNORE = ".gitignore"
|
||||
|
||||
def commit_data_file(self, fname, content="random text"):
|
||||
with open(fname, "w", encoding="utf-8") as fd:
|
||||
fd.write(content)
|
||||
stages = self.dvc.add(fname)
|
||||
self.assertEqual(len(stages), 1)
|
||||
self.assertTrue(stages[0] is not None)
|
||||
self.dvc.scm.add([fname + ".dvc", ".gitignore"])
|
||||
self.dvc.scm.commit("adding " + fname)
|
||||
|
||||
def read_ignored(self):
|
||||
with open(self.GIT_IGNORE, encoding="utf-8") as f:
|
||||
return [s.strip("\n") for s in f.readlines()]
|
||||
|
||||
def outs_info(self, stage):
|
||||
FileInfo = collections.namedtuple("FileInfo", "path inode")
|
||||
|
||||
paths = [
|
||||
path
|
||||
for output in stage["outs"]
|
||||
for path in self.dvc.fs.find(output["path"])
|
||||
]
|
||||
|
||||
return [
|
||||
FileInfo(path=path, inode=system.inode(path)) for path in paths
|
||||
]
|
||||
|
||||
|
||||
class TestRemoveFilesWhenCheckout(CheckoutBase):
|
||||
def test(self):
|
||||
fname = "file_in_a_branch"
|
||||
branch_master = "master"
|
||||
branch_1 = "b1"
|
||||
|
||||
self.dvc.scm.add(self.dvc.scm.untracked_files())
|
||||
self.dvc.scm.commit("add all files")
|
||||
|
||||
# add the file into a separate branch
|
||||
self.dvc.scm.checkout(branch_1, True)
|
||||
ret = main(["checkout", "--force"])
|
||||
self.assertEqual(ret, 0)
|
||||
self.commit_data_file(fname)
|
||||
|
||||
# Checkout back in master
|
||||
self.dvc.scm.checkout(branch_master)
|
||||
self.assertTrue(os.path.exists(fname))
|
||||
|
||||
# Make sure `dvc checkout` removes the file
|
||||
# self.dvc.checkout()
|
||||
ret = main(["checkout", "--force"])
|
||||
self.assertEqual(ret, 0)
|
||||
self.assertFalse(os.path.exists(fname))
|
||||
|
||||
|
||||
class TestCheckoutCleanWorkingDir(CheckoutBase):
|
||||
@patch("dvc.prompt.confirm", return_value=True)
|
||||
def test(self, mock_prompt):
|
||||
mock_prompt.return_value = True
|
||||
|
||||
stages = self.dvc.add(self.DATA_DIR)
|
||||
stage = stages[0]
|
||||
|
||||
working_dir_change = os.path.join(self.DATA_DIR, "not_cached.txt")
|
||||
with open(working_dir_change, "w", encoding="utf-8") as f:
|
||||
f.write("not_cached")
|
||||
|
||||
ret = main(["checkout", stage.relpath])
|
||||
self.assertEqual(ret, 0)
|
||||
self.assertFalse(os.path.exists(working_dir_change))
|
||||
|
||||
@patch("dvc.prompt.confirm", return_value=False)
|
||||
def test_force(self, mock_prompt):
|
||||
mock_prompt.return_value = False
|
||||
|
||||
stages = self.dvc.add(self.DATA_DIR)
|
||||
self.assertEqual(len(stages), 1)
|
||||
stage = stages[0]
|
||||
|
||||
working_dir_change = os.path.join(self.DATA_DIR, "not_cached.txt")
|
||||
with open(working_dir_change, "w", encoding="utf-8") as f:
|
||||
f.write("not_cached")
|
||||
|
||||
ret = main(["checkout", stage.relpath])
|
||||
self.assertNotEqual(ret, 0)
|
||||
class TestCheckoutCleanWorkingDir:
|
||||
def test(self, mocker, tmp_dir, dvc): # pylint: disable=unused-argument
|
||||
mock_prompt = mocker.patch("dvc.prompt.confirm", return_value=True)
|
||||
(stage,) = tmp_dir.dvc_gen("data", {"foo": "foo"})
|
||||
|
||||
# change working directory
|
||||
(tmp_dir / "data").gen("not_cached.txt", "not_cached")
|
||||
assert main(["checkout", stage.relpath]) == 0
|
||||
mock_prompt.assert_called()
|
||||
self.assertNotEqual(ret, 0)
|
||||
self.assertRaises(DvcException)
|
||||
assert not (tmp_dir / "data" / "not_cached.txt").exists()
|
||||
|
||||
def test_force(self, mocker, tmp_dir, dvc):
|
||||
mock_prompt = mocker.patch("dvc.prompt.confirm", return_value=False)
|
||||
(stage,) = tmp_dir.dvc_gen("data", {"foo": "foo"})
|
||||
|
||||
# change working directory
|
||||
(tmp_dir / "data").gen("not_cached.txt", "not_cached")
|
||||
assert main(["checkout", stage.relpath]) != 0
|
||||
mock_prompt.assert_called()
|
||||
assert (tmp_dir / "data" / "not_cached.txt").exists()
|
||||
|
||||
|
||||
def test_checkout_selective_remove(tmp_dir, dvc):
|
||||
# Use copy to test for changes in the inodes
|
||||
dvc.odb.local.cache_types = ["copy"]
|
||||
tmp_dir.dvc_gen({"data": {"foo": "foo", "bar": "bar"}})
|
||||
|
||||
foo_inode = system.inode(os.path.join("data", "foo"))
|
||||
bar_inode = system.inode(os.path.join("data", "bar"))
|
||||
# move instead of remove, to lock inode assigned to stage_files[0].path
|
||||
# if we were to use remove, we might end up with same inode assigned to
|
||||
# newly checked out file
|
||||
shutil.move(os.path.join("data", "foo"), "random_name")
|
||||
|
||||
assert main(["checkout", "--force", "data.dvc"]) == 0
|
||||
assert (tmp_dir / "data").read_text() == {"foo": "foo", "bar": "bar"}
|
||||
assert system.inode(os.path.join("data", "foo")) != foo_inode
|
||||
assert system.inode(os.path.join("data", "bar")) == bar_inode
|
||||
|
||||
|
||||
def test_gitignore_basic(tmp_dir, dvc, scm):
|
||||
tmp_dir.gen("foo", "foo")
|
||||
assert not os.path.exists(scm.GITIGNORE)
|
||||
|
||||
tmp_dir.dvc_gen("file1", "random text1", commit="add file1")
|
||||
tmp_dir.dvc_gen("file2", "random text2", commit="add file2")
|
||||
dvc.run(
|
||||
single_stage=True,
|
||||
cmd="cp foo file3",
|
||||
deps=["foo"],
|
||||
outs_no_cache=["file3"],
|
||||
)
|
||||
assert get_gitignore_content() == ["/file1", "/file2"]
|
||||
|
||||
|
||||
def test_gitignore_when_checkout(tmp_dir, dvc, scm):
|
||||
tmp_dir.dvc_gen("file_in_a_master", "master", commit="master")
|
||||
|
||||
class TestCheckoutSelectiveRemove(CheckoutBase):
|
||||
def test(self):
|
||||
# Use copy to test for changes in the inodes
|
||||
ret = main(["config", "cache.type", "copy"])
|
||||
self.assertEqual(ret, 0)
|
||||
scm.checkout("branch", True)
|
||||
ret = main(["checkout", "--force"])
|
||||
assert ret == 0
|
||||
tmp_dir.dvc_gen("file_in_a_branch", "branch", commit="branch")
|
||||
|
||||
scm.checkout("master")
|
||||
ret = main(["checkout", "--force"])
|
||||
assert ret == 0
|
||||
|
||||
ignored = get_gitignore_content()
|
||||
|
||||
assert len(ignored) == 1
|
||||
assert "/file_in_a_master" in ignored
|
||||
|
||||
ret = main(["add", self.DATA_DIR])
|
||||
self.assertEqual(0, ret)
|
||||
scm.checkout("branch")
|
||||
ret = main(["checkout", "--force"])
|
||||
assert ret == 0
|
||||
ignored = get_gitignore_content()
|
||||
assert "/file_in_a_branch" in ignored
|
||||
|
||||
|
||||
def test_checkout_missing_md5_in_stage_file(tmp_dir, dvc, copy_script):
|
||||
tmp_dir.dvc_gen({"foo": "foo", "data": {"file": "file"}})
|
||||
stage = dvc.run(
|
||||
fname="file1.dvc",
|
||||
outs=["file1"],
|
||||
deps=["foo", "copy.py"],
|
||||
cmd="python copy.py foo file1",
|
||||
single_stage=True,
|
||||
)
|
||||
d = load_yaml(stage.relpath)
|
||||
del d[Stage.PARAM_OUTS][0][LocalFileSystem.PARAM_CHECKSUM]
|
||||
del d[Stage.PARAM_DEPS][0][LocalFileSystem.PARAM_CHECKSUM]
|
||||
dump_yaml(stage.relpath, d)
|
||||
|
||||
with pytest.raises(CheckoutError):
|
||||
dvc.checkout(force=True)
|
||||
|
||||
|
||||
def test_checkout_empty_dir(tmp_dir, dvc):
|
||||
empty_dir = tmp_dir / "empty_dir"
|
||||
empty_dir.mkdir()
|
||||
(stage,) = dvc.add("empty_dir")
|
||||
|
||||
stage.outs[0].remove()
|
||||
assert not empty_dir.exists()
|
||||
|
||||
stats = dvc.checkout(force=True)
|
||||
assert stats["added"] == [os.path.join("empty_dir", "")]
|
||||
assert empty_dir.is_dir()
|
||||
assert not list(empty_dir.iterdir())
|
||||
|
||||
|
||||
def test_checkout_not_cached_file(tmp_dir, dvc):
|
||||
tmp_dir.dvc_gen("foo", "foo")
|
||||
dvc.run(
|
||||
cmd="cp foo bar",
|
||||
deps=["foo"],
|
||||
outs_no_cache=["bar"],
|
||||
single_stage=True,
|
||||
)
|
||||
stats = dvc.checkout(force=True)
|
||||
assert not any(stats.values())
|
||||
|
||||
|
||||
def test_checkout_with_deps_cli(tmp_dir, dvc, copy_script):
|
||||
tmp_dir.dvc_gen({"foo": "foo", "data": {"file": "file"}})
|
||||
dvc.run(
|
||||
fname="file1.dvc",
|
||||
outs=["file1"],
|
||||
deps=["foo", "copy.py"],
|
||||
cmd="python copy.py foo file1",
|
||||
single_stage=True,
|
||||
)
|
||||
remove("foo")
|
||||
remove("file1")
|
||||
|
||||
assert not os.path.exists("foo")
|
||||
assert not os.path.exists("file1")
|
||||
|
||||
stage_path = self.DATA_DIR + DVC_FILE_SUFFIX
|
||||
stage = load_yaml(stage_path)
|
||||
staged_files = self.outs_info(stage)
|
||||
ret = main(["checkout", "--force", "file1.dvc", "--with-deps"])
|
||||
assert ret == 0
|
||||
|
||||
assert os.path.exists("foo")
|
||||
assert os.path.exists("file1")
|
||||
|
||||
# move instead of remove, to lock inode assigned to stage_files[0].path
|
||||
# if we were to use remove, we might end up with same inode assigned to
|
||||
# newly checked out file
|
||||
shutil.move(staged_files[0].path, "random_name")
|
||||
|
||||
ret = main(["checkout", "--force", stage_path])
|
||||
self.assertEqual(ret, 0)
|
||||
def test_checkout_directory(tmp_dir, dvc):
|
||||
(stage,) = tmp_dir.dvc_gen({"data": {"foo": "foo", "bar": "bar"}})
|
||||
|
||||
remove("data")
|
||||
assert not os.path.exists("data")
|
||||
|
||||
checkedout_files = self.outs_info(stage)
|
||||
ret = main(["checkout", stage.path])
|
||||
assert ret == 0
|
||||
|
||||
self.assertEqual(len(staged_files), len(checkedout_files))
|
||||
self.assertEqual(staged_files[0].path, checkedout_files[0].path)
|
||||
self.assertNotEqual(staged_files[0].inode, checkedout_files[0].inode)
|
||||
self.assertEqual(staged_files[1].inode, checkedout_files[1].inode)
|
||||
assert os.path.exists("data")
|
||||
|
||||
|
||||
class TestGitIgnoreBasic(CheckoutBase):
|
||||
def test(self):
|
||||
fname1 = "file_1"
|
||||
fname2 = "file_2"
|
||||
fname3 = "file_3"
|
||||
def test_checkout_hook(mocker, tmp_dir, dvc):
|
||||
"""Test that dvc checkout handles EOFError gracefully, which is what
|
||||
it will experience when running in a git hook.
|
||||
"""
|
||||
tmp_dir.dvc_gen({"data": {"foo": "foo"}})
|
||||
mocker.patch("sys.stdout.isatty", return_value=True)
|
||||
mocker.patch("dvc.prompt.input", side_effect=EOFError)
|
||||
|
||||
self.dvc.scm.add(self.dvc.scm.untracked_files())
|
||||
self.dvc.scm.commit("add all files")
|
||||
(tmp_dir / "data").gen("test", "test")
|
||||
with pytest.raises(ConfirmRemoveError):
|
||||
dvc.checkout()
|
||||
|
||||
self.assertFalse(os.path.exists(self.GIT_IGNORE))
|
||||
|
||||
self.commit_data_file(fname1)
|
||||
self.commit_data_file(fname2)
|
||||
self.dvc.run(
|
||||
single_stage=True,
|
||||
cmd=f"python {self.CODE} {self.FOO} {fname3}",
|
||||
deps=[self.CODE, self.FOO],
|
||||
outs_no_cache=[fname3],
|
||||
)
|
||||
def test_checkout_suggest_git(tmp_dir, dvc, scm):
|
||||
# pylint: disable=no-member
|
||||
tmp_dir.dvc_gen("foo", "foo")
|
||||
try:
|
||||
dvc.checkout(targets="gitbranch")
|
||||
except DvcException as exc:
|
||||
assert isinstance(exc, CheckoutErrorSuggestGit)
|
||||
assert isinstance(exc.__cause__, NoOutputOrStageError)
|
||||
assert isinstance(exc.__cause__.__cause__, StageFileDoesNotExistError)
|
||||
|
||||
try:
|
||||
dvc.checkout(targets="foo")
|
||||
except DvcException as exc:
|
||||
assert isinstance(exc, CheckoutErrorSuggestGit)
|
||||
assert isinstance(exc.__cause__, NoOutputOrStageError)
|
||||
assert exc.__cause__.__cause__ is None
|
||||
|
||||
try:
|
||||
dvc.checkout(targets="looks-like-dvcfile.dvc")
|
||||
except DvcException as exc:
|
||||
assert isinstance(exc, CheckoutErrorSuggestGit)
|
||||
assert isinstance(exc.__cause__, StageFileDoesNotExistError)
|
||||
assert exc.__cause__.__cause__ is None
|
||||
|
||||
self.assertTrue(os.path.exists(self.GIT_IGNORE))
|
||||
|
||||
ignored = self.read_ignored()
|
||||
def test_checkout_target_recursive_should_not_remove_other_used_files(
|
||||
tmp_dir, dvc
|
||||
):
|
||||
tmp_dir.dvc_gen({"foo": "foo", "bar": "bar", "data": {"file": "file"}})
|
||||
assert main(["checkout", "-R", "data"]) == 0
|
||||
assert (tmp_dir / "foo").exists()
|
||||
assert (tmp_dir / "bar").exists()
|
||||
|
||||
self.assertEqual(len(ignored), 2)
|
||||
|
||||
self.assertIn("/" + fname1, ignored)
|
||||
self.assertIn("/" + fname2, ignored)
|
||||
def test_checkout_recursive_not_directory(tmp_dir, dvc):
|
||||
tmp_dir.gen("foo", "foo")
|
||||
ret = main(["add", "foo"])
|
||||
assert ret == 0
|
||||
|
||||
stats = dvc.checkout(targets=["foo" + ".dvc"], recursive=True)
|
||||
assert stats == {"added": [], "modified": [], "deleted": []}
|
||||
|
||||
class TestGitIgnoreWhenCheckout(CheckoutBase):
|
||||
def test(self):
|
||||
fname_master = "file_in_a_master"
|
||||
branch_master = "master"
|
||||
fname_branch = "file_in_a_branch"
|
||||
branch_1 = "b1"
|
||||
|
||||
self.dvc.scm.add(self.dvc.scm.untracked_files())
|
||||
self.dvc.scm.commit("add all files")
|
||||
self.commit_data_file(fname_master)
|
||||
def test_checkout_moved_cache_dir_with_symlinks(tmp_dir, dvc):
|
||||
tmp_dir.gen({"foo": "foo", "data": {"file": "file"}})
|
||||
ret = main(["config", "cache.type", "symlink"])
|
||||
assert ret == 0
|
||||
|
||||
self.dvc.scm.checkout(branch_1, True)
|
||||
ret = main(["checkout", "--force"])
|
||||
self.assertEqual(ret, 0)
|
||||
self.commit_data_file(fname_branch)
|
||||
ret = main(["add", "foo"])
|
||||
assert ret == 0
|
||||
|
||||
self.dvc.scm.checkout(branch_master)
|
||||
ret = main(["checkout", "--force"])
|
||||
self.assertEqual(ret, 0)
|
||||
ret = main(["add", "data"])
|
||||
assert ret == 0
|
||||
|
||||
ignored = self.read_ignored()
|
||||
assert system.is_symlink("foo")
|
||||
old_foo_link = os.path.realpath("foo")
|
||||
|
||||
self.assertEqual(len(ignored), 1)
|
||||
self.assertIn("/" + fname_master, ignored)
|
||||
assert system.is_symlink(os.path.join("data", "file"))
|
||||
old_data_link = os.path.realpath(os.path.join("data", "file"))
|
||||
|
||||
self.dvc.scm.checkout(branch_1)
|
||||
ret = main(["checkout", "--force"])
|
||||
self.assertEqual(ret, 0)
|
||||
ignored = self.read_ignored()
|
||||
self.assertIn("/" + fname_branch, ignored)
|
||||
old_cache_dir = dvc.odb.local.path
|
||||
new_cache_dir = old_cache_dir + "_new"
|
||||
os.rename(old_cache_dir, new_cache_dir)
|
||||
|
||||
ret = main(["cache", "dir", new_cache_dir])
|
||||
assert ret == 0
|
||||
|
||||
class TestCheckoutMissingMd5InStageFile(TestRepro):
|
||||
def test(self):
|
||||
d = load_yaml(self.file1_stage)
|
||||
del d[Stage.PARAM_OUTS][0][LocalFileSystem.PARAM_CHECKSUM]
|
||||
del d[Stage.PARAM_DEPS][0][LocalFileSystem.PARAM_CHECKSUM]
|
||||
dump_yaml(self.file1_stage, d)
|
||||
ret = main(["checkout", "-f"])
|
||||
assert ret == 0
|
||||
|
||||
with pytest.raises(CheckoutError):
|
||||
self.dvc.checkout(force=True)
|
||||
assert system.is_symlink("foo")
|
||||
new_foo_link = os.path.realpath("foo")
|
||||
|
||||
assert system.is_symlink(os.path.join("data", "file"))
|
||||
new_data_link = os.path.realpath(os.path.join("data", "file"))
|
||||
|
||||
class TestCheckoutEmptyDir(TestDvc):
|
||||
def test(self):
|
||||
dname = "empty_dir"
|
||||
os.mkdir(dname)
|
||||
assert relpath(old_foo_link, old_cache_dir) == relpath(
|
||||
new_foo_link, new_cache_dir
|
||||
)
|
||||
|
||||
stages = self.dvc.add(dname)
|
||||
self.assertEqual(len(stages), 1)
|
||||
stage = stages[0]
|
||||
self.assertTrue(stage is not None)
|
||||
self.assertEqual(len(stage.outs), 1)
|
||||
|
||||
stage.outs[0].remove()
|
||||
self.assertFalse(os.path.exists(dname))
|
||||
|
||||
stats = self.dvc.checkout(force=True)
|
||||
assert stats["added"] == [dname + os.sep]
|
||||
|
||||
self.assertTrue(os.path.isdir(dname))
|
||||
self.assertEqual(len(os.listdir(dname)), 0)
|
||||
|
||||
|
||||
class TestCheckoutNotCachedFile(TestDvc):
|
||||
def test(self):
|
||||
cmd = "python {} {} {}".format(self.CODE, self.FOO, "out")
|
||||
|
||||
self.dvc.add(self.FOO)
|
||||
stage = self.dvc.run(
|
||||
cmd=cmd,
|
||||
deps=[self.FOO, self.CODE],
|
||||
outs_no_cache=["out"],
|
||||
single_stage=True,
|
||||
)
|
||||
self.assertTrue(stage is not None)
|
||||
|
||||
stats = self.dvc.checkout(force=True)
|
||||
assert not any(stats.values())
|
||||
|
||||
|
||||
class TestCheckoutWithDeps(TestRepro):
|
||||
def test(self):
|
||||
os.unlink(self.FOO)
|
||||
os.unlink(self.file1)
|
||||
|
||||
self.assertFalse(os.path.exists(self.FOO))
|
||||
self.assertFalse(os.path.exists(self.file1))
|
||||
|
||||
ret = main(["checkout", "--force", self.file1_stage, "--with-deps"])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
self.assertTrue(os.path.exists(self.FOO))
|
||||
self.assertTrue(os.path.exists(self.file1))
|
||||
|
||||
|
||||
class TestCheckoutDirectory(TestRepro):
|
||||
def test(self):
|
||||
stage = self.dvc.add(self.DATA_DIR)[0]
|
||||
|
||||
shutil.rmtree(self.DATA_DIR)
|
||||
self.assertFalse(os.path.exists(self.DATA_DIR))
|
||||
|
||||
ret = main(["checkout", stage.path])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
self.assertTrue(os.path.exists(self.DATA_DIR))
|
||||
|
||||
|
||||
class TestCheckoutHook(TestDvc):
|
||||
@patch("sys.stdout.isatty", return_value=True)
|
||||
@patch("dvc.prompt.input", side_effect=EOFError)
|
||||
def test(self, _mock_input, _mock_isatty):
|
||||
"""Test that dvc checkout handles EOFError gracefully, which is what
|
||||
it will experience when running in a git hook.
|
||||
"""
|
||||
stages = self.dvc.add(self.DATA_DIR)
|
||||
self.assertEqual(len(stages), 1)
|
||||
stage = stages[0]
|
||||
self.assertNotEqual(stage, None)
|
||||
|
||||
self.create(os.path.join(self.DATA_DIR, "test"), "test")
|
||||
|
||||
with self.assertRaises(ConfirmRemoveError):
|
||||
self.dvc.checkout()
|
||||
|
||||
|
||||
class TestCheckoutSuggestGit(TestRepro):
|
||||
def test(self):
|
||||
# pylint: disable=no-member
|
||||
|
||||
try:
|
||||
self.dvc.checkout(targets="gitbranch")
|
||||
except DvcException as exc:
|
||||
self.assertIsInstance(exc, CheckoutErrorSuggestGit)
|
||||
self.assertIsInstance(exc.__cause__, NoOutputOrStageError)
|
||||
self.assertIsInstance(
|
||||
exc.__cause__.__cause__, StageFileDoesNotExistError
|
||||
)
|
||||
|
||||
try:
|
||||
self.dvc.checkout(targets=self.FOO)
|
||||
except DvcException as exc:
|
||||
self.assertIsInstance(exc, CheckoutErrorSuggestGit)
|
||||
self.assertIsInstance(exc.__cause__, NoOutputOrStageError)
|
||||
self.assertIsNone(exc.__cause__.__cause__)
|
||||
|
||||
try:
|
||||
self.dvc.checkout(targets="looks-like-dvcfile.dvc")
|
||||
except DvcException as exc:
|
||||
self.assertIsInstance(exc, CheckoutErrorSuggestGit)
|
||||
self.assertIsInstance(exc.__cause__, StageFileDoesNotExistError)
|
||||
self.assertIsNone(exc.__cause__.__cause__)
|
||||
|
||||
|
||||
class TestCheckoutTargetRecursiveShouldNotRemoveOtherUsedFiles(TestDvc):
|
||||
def test(self):
|
||||
ret = main(["add", self.DATA_DIR, self.FOO, self.BAR])
|
||||
self.assertEqual(0, ret)
|
||||
|
||||
ret = main(["checkout", "-R", self.DATA_DIR])
|
||||
self.assertEqual(0, ret)
|
||||
|
||||
self.assertTrue(os.path.exists(self.FOO))
|
||||
self.assertTrue(os.path.exists(self.BAR))
|
||||
|
||||
|
||||
class TestCheckoutRecursiveNotDirectory(TestDvc):
|
||||
def test(self):
|
||||
ret = main(["add", self.FOO])
|
||||
self.assertEqual(0, ret)
|
||||
|
||||
stats = self.dvc.checkout(targets=[self.FOO + ".dvc"], recursive=True)
|
||||
assert stats == {"added": [], "modified": [], "deleted": []}
|
||||
|
||||
|
||||
class TestCheckoutMovedCacheDirWithSymlinks(TestDvc):
|
||||
def test(self):
|
||||
ret = main(["config", "cache.type", "symlink"])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
ret = main(["add", self.FOO])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
ret = main(["add", self.DATA_DIR])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
self.assertTrue(system.is_symlink(self.FOO))
|
||||
old_foo_link = os.path.realpath(self.FOO)
|
||||
|
||||
self.assertTrue(system.is_symlink(self.DATA))
|
||||
old_data_link = os.path.realpath(self.DATA)
|
||||
|
||||
old_cache_dir = self.dvc.odb.local.path
|
||||
new_cache_dir = old_cache_dir + "_new"
|
||||
os.rename(old_cache_dir, new_cache_dir)
|
||||
|
||||
ret = main(["cache", "dir", new_cache_dir])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
ret = main(["checkout", "-f"])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
self.assertTrue(system.is_symlink(self.FOO))
|
||||
new_foo_link = os.path.realpath(self.FOO)
|
||||
|
||||
self.assertTrue(system.is_symlink(self.DATA))
|
||||
new_data_link = os.path.realpath(self.DATA)
|
||||
|
||||
self.assertEqual(
|
||||
relpath(old_foo_link, old_cache_dir),
|
||||
relpath(new_foo_link, new_cache_dir),
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
relpath(old_data_link, old_cache_dir),
|
||||
relpath(new_data_link, new_cache_dir),
|
||||
)
|
||||
assert relpath(old_data_link, old_cache_dir) == relpath(
|
||||
new_data_link, new_cache_dir
|
||||
)
|
||||
|
||||
|
||||
def test_checkout_no_checksum(tmp_dir, dvc):
|
||||
|
||||
+137
-147
@@ -1,5 +1,7 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from dvc.cli import DvcParserError, parse_args
|
||||
from dvc.cli.command import CmdBase
|
||||
from dvc.commands.add import CmdAdd
|
||||
@@ -12,200 +14,188 @@ from dvc.commands.repro import CmdRepro
|
||||
from dvc.commands.run import CmdRun
|
||||
from dvc.commands.status import CmdDataStatus
|
||||
from dvc.exceptions import DvcException
|
||||
from tests.basic_env import TestDvc
|
||||
|
||||
|
||||
class TestArgParse(TestDvc):
|
||||
def test(self):
|
||||
args = parse_args(["init"])
|
||||
self.assertIsInstance(args.func(args), CmdInit)
|
||||
def test_argparse(dvc):
|
||||
args = parse_args(["init"])
|
||||
assert isinstance(args.func(args), CmdInit)
|
||||
|
||||
|
||||
class TestRun(TestDvc):
|
||||
def test(self):
|
||||
dep1 = "dep1"
|
||||
dep2 = "dep2"
|
||||
def test_run(dvc):
|
||||
dep1 = "dep1"
|
||||
dep2 = "dep2"
|
||||
|
||||
out1 = "out1"
|
||||
out2 = "out2"
|
||||
out1 = "out1"
|
||||
out2 = "out2"
|
||||
|
||||
out_no_cache1 = "out_no_cache1"
|
||||
out_no_cache2 = "out_no_cache2"
|
||||
out_no_cache1 = "out_no_cache1"
|
||||
out_no_cache2 = "out_no_cache2"
|
||||
|
||||
fname = "dvc.dvc"
|
||||
cmd = "cmd"
|
||||
arg1 = "arg1"
|
||||
arg2 = "arg2"
|
||||
fname = "dvc.dvc"
|
||||
cmd = "cmd"
|
||||
arg1 = "arg1"
|
||||
arg2 = "arg2"
|
||||
|
||||
args = parse_args(
|
||||
[
|
||||
"run",
|
||||
"-d",
|
||||
dep1,
|
||||
"--deps",
|
||||
dep2,
|
||||
"-o",
|
||||
out1,
|
||||
"--outs",
|
||||
out2,
|
||||
"-O",
|
||||
out_no_cache1,
|
||||
"--outs-no-cache",
|
||||
out_no_cache2,
|
||||
"--file",
|
||||
fname,
|
||||
cmd,
|
||||
arg1,
|
||||
arg2,
|
||||
]
|
||||
)
|
||||
cmd_cls = args.func(args)
|
||||
self.assertIsInstance(cmd_cls, CmdRun)
|
||||
self.assertEqual(args.deps, [dep1, dep2])
|
||||
self.assertEqual(args.outs, [out1, out2])
|
||||
self.assertEqual(args.outs_no_cache, [out_no_cache1, out_no_cache2])
|
||||
self.assertEqual(args.file, fname)
|
||||
self.assertEqual(args.command, [cmd, arg1, arg2])
|
||||
args = parse_args(
|
||||
[
|
||||
"run",
|
||||
"-d",
|
||||
dep1,
|
||||
"--deps",
|
||||
dep2,
|
||||
"-o",
|
||||
out1,
|
||||
"--outs",
|
||||
out2,
|
||||
"-O",
|
||||
out_no_cache1,
|
||||
"--outs-no-cache",
|
||||
out_no_cache2,
|
||||
"--file",
|
||||
fname,
|
||||
cmd,
|
||||
arg1,
|
||||
arg2,
|
||||
]
|
||||
)
|
||||
cmd_cls = args.func(args)
|
||||
assert isinstance(cmd_cls, CmdRun)
|
||||
assert args.deps == [dep1, dep2]
|
||||
assert args.outs == [out1, out2]
|
||||
assert args.outs_no_cache == [out_no_cache1, out_no_cache2]
|
||||
assert args.file == fname
|
||||
assert args.command == [cmd, arg1, arg2]
|
||||
|
||||
cmd_cls.repo.close()
|
||||
cmd_cls.repo.close()
|
||||
|
||||
|
||||
class TestPull(TestDvc):
|
||||
def test(self):
|
||||
args = parse_args(["pull"])
|
||||
cmd = args.func(args)
|
||||
self.assertIsInstance(cmd, CmdDataPull)
|
||||
def test_pull(dvc):
|
||||
args = parse_args(["pull"])
|
||||
cmd = args.func(args)
|
||||
assert isinstance(cmd, CmdDataPull)
|
||||
|
||||
cmd.repo.close()
|
||||
cmd.repo.close()
|
||||
|
||||
|
||||
class TestPush(TestDvc):
|
||||
def test(self):
|
||||
args = parse_args(["push"])
|
||||
cmd = args.func(args)
|
||||
self.assertIsInstance(cmd, CmdDataPush)
|
||||
def test_push(dvc):
|
||||
args = parse_args(["push"])
|
||||
cmd = args.func(args)
|
||||
assert isinstance(cmd, CmdDataPush)
|
||||
|
||||
cmd.repo.close()
|
||||
cmd.repo.close()
|
||||
|
||||
|
||||
class TestStatus(TestDvc):
|
||||
def test(self):
|
||||
args = parse_args(["status"])
|
||||
cmd = args.func(args)
|
||||
self.assertIsInstance(cmd, CmdDataStatus)
|
||||
def test_status(dvc):
|
||||
args = parse_args(["status"])
|
||||
cmd = args.func(args)
|
||||
assert isinstance(cmd, CmdDataStatus)
|
||||
|
||||
cmd.repo.close()
|
||||
cmd.repo.close()
|
||||
|
||||
|
||||
class TestRepro(TestDvc):
|
||||
def test(self):
|
||||
target1 = "1"
|
||||
target2 = "2"
|
||||
def test_repro(dvc):
|
||||
target1 = "1"
|
||||
target2 = "2"
|
||||
|
||||
args = parse_args(
|
||||
["repro", target1, target2, "-f", "--force", "-s", "--single-item"]
|
||||
)
|
||||
args = parse_args(
|
||||
["repro", target1, target2, "-f", "--force", "-s", "--single-item"]
|
||||
)
|
||||
|
||||
cmd = args.func(args)
|
||||
self.assertIsInstance(cmd, CmdRepro)
|
||||
self.assertEqual(args.targets, [target1, target2])
|
||||
self.assertEqual(args.force, True)
|
||||
self.assertEqual(args.single_item, True)
|
||||
cmd = args.func(args)
|
||||
assert isinstance(cmd, CmdRepro)
|
||||
assert args.targets == [target1, target2]
|
||||
assert args.force
|
||||
assert args.single_item
|
||||
|
||||
cmd.repo.close()
|
||||
cmd.repo.close()
|
||||
|
||||
|
||||
class TestRemove(TestDvc):
|
||||
def test(self):
|
||||
target1 = "1"
|
||||
target2 = "2"
|
||||
def test_remove(dvc):
|
||||
target1 = "1"
|
||||
target2 = "2"
|
||||
|
||||
args = parse_args(["remove", target1, target2])
|
||||
args = parse_args(["remove", target1, target2])
|
||||
|
||||
cmd = args.func(args)
|
||||
self.assertIsInstance(cmd, CmdRemove)
|
||||
self.assertEqual(args.targets, [target1, target2])
|
||||
cmd = args.func(args)
|
||||
assert isinstance(cmd, CmdRemove)
|
||||
assert args.targets == [target1, target2]
|
||||
|
||||
cmd.repo.close()
|
||||
cmd.repo.close()
|
||||
|
||||
|
||||
class TestAdd(TestDvc):
|
||||
def test(self):
|
||||
target1 = "1"
|
||||
target2 = "2"
|
||||
def test_add(dvc):
|
||||
target1 = "1"
|
||||
target2 = "2"
|
||||
|
||||
args = parse_args(["add", target1, target2])
|
||||
args = parse_args(["add", target1, target2])
|
||||
|
||||
cmd = args.func(args)
|
||||
self.assertIsInstance(cmd, CmdAdd)
|
||||
self.assertEqual(args.targets, [target1, target2])
|
||||
cmd = args.func(args)
|
||||
assert isinstance(cmd, CmdAdd)
|
||||
assert args.targets == [target1, target2]
|
||||
|
||||
cmd.repo.close()
|
||||
cmd.repo.close()
|
||||
|
||||
|
||||
class TestConfig(TestDvc):
|
||||
def test(self):
|
||||
name = "section.option"
|
||||
value = "1"
|
||||
def test_config_unset(dvc):
|
||||
name = "section.option"
|
||||
value = "1"
|
||||
|
||||
args = parse_args(["config", "-u", "--unset", name, value])
|
||||
args = parse_args(["config", "-u", "--unset", name, value])
|
||||
|
||||
cmd = args.func(args)
|
||||
self.assertIsInstance(cmd, CmdConfig)
|
||||
self.assertEqual(args.unset, True)
|
||||
self.assertEqual(args.name, (False, "section", "option"))
|
||||
self.assertEqual(args.value, value)
|
||||
|
||||
def test_config_list(self):
|
||||
args = parse_args(["config", "--list"])
|
||||
|
||||
self.assertTrue(args.list)
|
||||
self.assertIsNone(args.name)
|
||||
self.assertIsNone(args.value)
|
||||
cmd = args.func(args)
|
||||
assert isinstance(cmd, CmdConfig)
|
||||
assert args.unset
|
||||
assert args.name == (False, "section", "option")
|
||||
assert args.value == value
|
||||
|
||||
|
||||
class TestCheckout(TestDvc):
|
||||
def test(self):
|
||||
args = parse_args(["checkout"])
|
||||
cmd = args.func(args)
|
||||
self.assertIsInstance(cmd, CmdCheckout)
|
||||
def test_config_list():
|
||||
args = parse_args(["config", "--list"])
|
||||
|
||||
cmd.repo.close()
|
||||
assert args.list
|
||||
assert args.name is None
|
||||
assert args.value is None
|
||||
|
||||
|
||||
class TestFindRoot(TestDvc):
|
||||
def test(self):
|
||||
class Cmd(CmdBase):
|
||||
def run(self):
|
||||
pass
|
||||
def test_checkout(dvc):
|
||||
args = parse_args(["checkout"])
|
||||
cmd = args.func(args)
|
||||
assert isinstance(cmd, CmdCheckout)
|
||||
|
||||
class A:
|
||||
quiet = False
|
||||
verbose = True
|
||||
cd = os.path.pardir
|
||||
|
||||
args = A()
|
||||
with self.assertRaises(DvcException):
|
||||
Cmd(args)
|
||||
cmd.repo.close()
|
||||
|
||||
|
||||
class TestCd(TestDvc):
|
||||
def test(self):
|
||||
class Cmd(CmdBase):
|
||||
def run(self):
|
||||
pass
|
||||
def test_find_root(dvc):
|
||||
class Cmd(CmdBase):
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
class A:
|
||||
quiet = False
|
||||
verbose = True
|
||||
cd = os.path.pardir
|
||||
class A:
|
||||
quiet = False
|
||||
verbose = True
|
||||
cd = os.path.pardir
|
||||
|
||||
parent_dir = os.path.realpath(os.path.pardir)
|
||||
args = A()
|
||||
with self.assertRaises(DvcException):
|
||||
Cmd(args)
|
||||
current_dir = os.path.realpath(os.path.curdir)
|
||||
self.assertEqual(parent_dir, current_dir)
|
||||
args = A()
|
||||
with pytest.raises(DvcException):
|
||||
Cmd(args)
|
||||
|
||||
|
||||
def test_cd(dvc):
|
||||
class Cmd(CmdBase):
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
class A:
|
||||
quiet = False
|
||||
verbose = True
|
||||
cd = os.path.pardir
|
||||
|
||||
parent_dir = os.path.realpath(os.path.pardir)
|
||||
args = A()
|
||||
with pytest.raises(DvcException):
|
||||
Cmd(args)
|
||||
current_dir = os.path.realpath(os.path.curdir)
|
||||
assert parent_dir == current_dir
|
||||
|
||||
|
||||
def test_unknown_command_help(capsys):
|
||||
|
||||
@@ -418,6 +418,7 @@ def test_dvcfile_load_dump_stage_with_desc_meta(tmp_dir, dvc):
|
||||
"plots": {
|
||||
"path/to/plot": {"x": "value", "y": "value"},
|
||||
"path/to/another/plot": {"x": "value", "y": "value"},
|
||||
"path/to/empty/plot": None,
|
||||
},
|
||||
"stages": STAGE_EXAMPLE,
|
||||
},
|
||||
@@ -425,6 +426,8 @@ def test_dvcfile_load_dump_stage_with_desc_meta(tmp_dir, dvc):
|
||||
"plots": [
|
||||
{"path/to/plot": {"x": "value", "y": "value"}},
|
||||
{"path/to/another/plot": {"x": "value", "y": "value"}},
|
||||
{"path/to/empty/plot": None},
|
||||
"path/to/plot/str",
|
||||
],
|
||||
"stages": STAGE_EXAMPLE,
|
||||
},
|
||||
|
||||
+56
-150
@@ -2,186 +2,92 @@ import logging
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import configobj
|
||||
import pytest
|
||||
from git import Repo
|
||||
|
||||
from dvc.cli import main
|
||||
from dvc.exceptions import CollectCacheError
|
||||
from dvc.fs import LocalFileSystem
|
||||
from dvc.repo import Repo as DvcRepo
|
||||
from dvc.utils.fs import remove
|
||||
from dvc_data.hashfile.db.local import LocalHashFileDB
|
||||
from tests.basic_env import TestDir, TestDvcGit
|
||||
|
||||
|
||||
class TestGC(TestDvcGit):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
@pytest.fixture
|
||||
def good_and_bad_cache(tmp_dir, dvc):
|
||||
tmp_dir.dvc_gen("foo", "foo")
|
||||
(stage,) = tmp_dir.dvc_gen(
|
||||
"data",
|
||||
{"sub": {"data_sub": "data_sub", "data": "data", "тест": "проверка"}},
|
||||
)
|
||||
raw_dir_hash = stage.outs[0].hash_info.as_raw().value
|
||||
odb = dvc.odb.local
|
||||
|
||||
self.dvc.add(self.FOO)
|
||||
stages = self.dvc.add(self.DATA_DIR)
|
||||
raw_dir_hash = stages[0].outs[0].hash_info.as_raw().value
|
||||
bad_cache = {raw_dir_hash}
|
||||
for i in ["123", "234", "345"]:
|
||||
odb.add_bytes(i, i.encode("utf8"))
|
||||
bad_cache.add(i)
|
||||
|
||||
self.good_cache = [
|
||||
self.dvc.odb.local.oid_to_path(md5)
|
||||
for md5 in self.dvc.odb.local.all()
|
||||
if md5 != raw_dir_hash
|
||||
]
|
||||
|
||||
self.bad_cache = [self.dvc.odb.local.oid_to_path(raw_dir_hash)]
|
||||
for i in ["123", "234", "345"]:
|
||||
path = os.path.join(self.dvc.odb.local.path, i[0:2], i[2:])
|
||||
self.create(path, i)
|
||||
self.bad_cache.append(path)
|
||||
|
||||
def test_api(self):
|
||||
self.dvc.gc(workspace=True)
|
||||
self._test_gc()
|
||||
|
||||
def test_cli(self):
|
||||
ret = main(["gc", "-wf"])
|
||||
self.assertEqual(ret, 0)
|
||||
self._test_gc()
|
||||
|
||||
def _test_gc(self):
|
||||
self.assertTrue(os.path.isdir(self.dvc.odb.local.path))
|
||||
for c in self.bad_cache:
|
||||
self.assertFalse(os.path.exists(c))
|
||||
|
||||
for c in self.good_cache:
|
||||
self.assertTrue(os.path.exists(c))
|
||||
good_cache = {md5 for md5 in odb.all() if md5 not in bad_cache}
|
||||
return good_cache, bad_cache
|
||||
|
||||
|
||||
class TestGCBranchesTags(TestDvcGit):
|
||||
def _check_cache(self, num):
|
||||
total = 0
|
||||
for _, _, files in os.walk(os.path.join(".dvc", "cache")):
|
||||
total += len(files)
|
||||
self.assertEqual(total, num)
|
||||
|
||||
def test(self):
|
||||
fname = "file"
|
||||
|
||||
with open(fname, "w+", encoding="utf-8") as fobj:
|
||||
fobj.write("v1.0")
|
||||
|
||||
stages = self.dvc.add(fname)
|
||||
self.assertEqual(len(stages), 1)
|
||||
self.dvc.scm.add([".gitignore", stages[0].relpath])
|
||||
self.dvc.scm.commit("v1.0")
|
||||
self.dvc.scm.tag("v1.0")
|
||||
|
||||
self.dvc.scm.checkout("test", create_new=True)
|
||||
self.dvc.remove(stages[0].relpath)
|
||||
with open(fname, "w+", encoding="utf-8") as fobj:
|
||||
fobj.write("test")
|
||||
stages = self.dvc.add(fname)
|
||||
self.assertEqual(len(stages), 1)
|
||||
self.dvc.scm.add([".gitignore", stages[0].relpath])
|
||||
self.dvc.scm.commit("test")
|
||||
|
||||
self.dvc.scm.checkout("master")
|
||||
self.dvc.remove(stages[0].relpath)
|
||||
with open(fname, "w+", encoding="utf-8") as fobj:
|
||||
fobj.write("trash")
|
||||
stages = self.dvc.add(fname)
|
||||
self.assertEqual(len(stages), 1)
|
||||
self.dvc.scm.add([".gitignore", stages[0].relpath])
|
||||
self.dvc.scm.commit("trash")
|
||||
|
||||
self.dvc.remove(stages[0].relpath)
|
||||
with open(fname, "w+", encoding="utf-8") as fobj:
|
||||
fobj.write("master")
|
||||
stages = self.dvc.add(fname)
|
||||
self.assertEqual(len(stages), 1)
|
||||
self.dvc.scm.add([".gitignore", stages[0].relpath])
|
||||
self.dvc.scm.commit("master")
|
||||
|
||||
self._check_cache(4)
|
||||
|
||||
self.dvc.gc(all_tags=True, all_branches=True)
|
||||
|
||||
self._check_cache(3)
|
||||
|
||||
self.dvc.gc(all_tags=False, all_branches=True)
|
||||
|
||||
self._check_cache(2)
|
||||
|
||||
self.dvc.gc(all_tags=True, all_branches=False)
|
||||
|
||||
self._check_cache(1)
|
||||
def test_gc_api(dvc, good_and_bad_cache):
|
||||
dvc.gc(workspace=True)
|
||||
odb = dvc.odb.local
|
||||
good_cache, bad_cache = good_and_bad_cache
|
||||
assert set(odb.oids_exist([*good_cache, *bad_cache])) == good_cache
|
||||
|
||||
|
||||
class TestGCMultipleDvcRepos(TestDvcGit):
|
||||
def _check_cache(self, num):
|
||||
total = 0
|
||||
for _, _, files in os.walk(os.path.join(".dvc", "cache")):
|
||||
total += len(files)
|
||||
self.assertEqual(total, num)
|
||||
def test_gc_cli(dvc, good_and_bad_cache):
|
||||
assert main(["gc", "-wf"]) == 0
|
||||
odb = dvc.odb.local
|
||||
good_cache, bad_cache = good_and_bad_cache
|
||||
assert set(odb.oids_exist([*good_cache, *bad_cache])) == good_cache
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.additional_path = TestDir.mkdtemp()
|
||||
self.additional_git = Repo.init(self.additional_path)
|
||||
self.additional_dvc = DvcRepo.init(self.additional_path)
|
||||
|
||||
cache_path = os.path.join(self._root_dir, ".dvc", "cache")
|
||||
config_path = os.path.join(
|
||||
self.additional_path, ".dvc", "config.local"
|
||||
)
|
||||
cfg = configobj.ConfigObj()
|
||||
cfg.filename = config_path
|
||||
cfg["cache"] = {"dir": cache_path}
|
||||
cfg.write()
|
||||
def test_gc_branches_tags(tmp_dir, dvc, scm):
|
||||
tmp_dir.dvc_gen("file", "v1.0", commit="v1.0")
|
||||
scm.tag("v1.0")
|
||||
|
||||
self.additional_dvc = DvcRepo(self.additional_path)
|
||||
with tmp_dir.branch("test", new=True):
|
||||
dvc.remove("file.dvc")
|
||||
tmp_dir.dvc_gen("file", "test", commit="test")
|
||||
|
||||
def test(self):
|
||||
dvc.remove("file.dvc")
|
||||
tmp_dir.dvc_gen("file", "trash", commit="trash")
|
||||
|
||||
# ADD FILE ONLY IN MAIN PROJECT
|
||||
fname = "only_in_first"
|
||||
with open(fname, "w+", encoding="utf-8") as fobj:
|
||||
fobj.write("only in main repo")
|
||||
dvc.remove("file.dvc")
|
||||
tmp_dir.dvc_gen("file", "master", commit="trash")
|
||||
|
||||
stages = self.dvc.add(fname)
|
||||
self.assertEqual(len(stages), 1)
|
||||
odb = dvc.odb.local
|
||||
assert len(list(odb.all())) == 4
|
||||
|
||||
# ADD FILE IN MAIN PROJECT THAT IS ALSO IN SECOND PROJECT
|
||||
fname = "in_both"
|
||||
with open(fname, "w+", encoding="utf-8") as fobj:
|
||||
fobj.write("in both repos")
|
||||
dvc.gc(all_tags=True, all_branches=True)
|
||||
assert len(list(odb.all())) == 3
|
||||
|
||||
stages = self.dvc.add(fname)
|
||||
self.assertEqual(len(stages), 1)
|
||||
dvc.gc(all_tags=False, all_branches=True)
|
||||
assert len(list(odb.all())) == 2
|
||||
|
||||
cwd = os.getcwd()
|
||||
os.chdir(self.additional_path)
|
||||
# ADD FILE ONLY IN SECOND PROJECT
|
||||
fname = "only_in_second"
|
||||
with open(fname, "w+", encoding="utf-8") as fobj:
|
||||
fobj.write("only in additional repo")
|
||||
dvc.gc(all_tags=True, all_branches=False)
|
||||
assert len(list(odb.all())) == 1
|
||||
|
||||
stages = self.additional_dvc.add(fname)
|
||||
self.assertEqual(len(stages), 1)
|
||||
|
||||
# ADD FILE IN SECOND PROJECT THAT IS ALSO IN MAIN PROJECT
|
||||
fname = "in_both"
|
||||
with open(fname, "w+", encoding="utf-8") as fobj:
|
||||
fobj.write("in both repos")
|
||||
def test_gc_multiple_dvc_repos(tmp_dir, scm, dvc, erepo_dir):
|
||||
tmp_dir.dvc_gen("only_in_first", "only in main repo")
|
||||
tmp_dir.dvc_gen("in_both", "in both repos")
|
||||
|
||||
stages = self.additional_dvc.add(fname)
|
||||
self.assertEqual(len(stages), 1)
|
||||
erepo_dir.dvc.odb.local.path = dvc.odb.local.path
|
||||
with erepo_dir.chdir():
|
||||
erepo_dir.dvc_gen("in_both", "in both repos")
|
||||
erepo_dir.dvc_gen("only_in_second", "only in additional repo")
|
||||
|
||||
os.chdir(cwd)
|
||||
odb = dvc.odb.local
|
||||
assert len(list(odb.all())) == 3
|
||||
|
||||
self._check_cache(3)
|
||||
dvc.gc(repos=[erepo_dir], workspace=True)
|
||||
assert len(list(odb.all())) == 3
|
||||
|
||||
self.dvc.gc(repos=[self.additional_path], workspace=True)
|
||||
self._check_cache(3)
|
||||
|
||||
self.dvc.gc(workspace=True)
|
||||
self._check_cache(2)
|
||||
dvc.gc(workspace=True)
|
||||
assert len(list(odb.all())) == 2
|
||||
|
||||
|
||||
def test_all_commits(tmp_dir, scm, dvc):
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import filecmp
|
||||
import os
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from funcy import first
|
||||
@@ -49,7 +48,7 @@ def test_import_git_file(tmp_dir, scm, dvc, git_dir, src_is_dvc):
|
||||
}
|
||||
|
||||
|
||||
def test_import_cached_file(erepo_dir, tmp_dir, dvc, scm, monkeypatch):
|
||||
def test_import_cached_file(mocker, erepo_dir, tmp_dir, dvc, scm, monkeypatch):
|
||||
src = "some_file"
|
||||
dst = "some_file_imported"
|
||||
|
||||
@@ -60,10 +59,10 @@ def test_import_cached_file(erepo_dir, tmp_dir, dvc, scm, monkeypatch):
|
||||
(tmp_dir / dst).unlink()
|
||||
|
||||
remote_exception = NoRemoteError("dvc import")
|
||||
with patch.object(
|
||||
mocker.patch.object(
|
||||
dvc.cloud, "get_remote_odb", side_effect=remote_exception
|
||||
):
|
||||
tmp_dir.dvc.imp(os.fspath(erepo_dir), src, dst)
|
||||
)
|
||||
tmp_dir.dvc.imp(os.fspath(erepo_dir), src, dst)
|
||||
|
||||
assert (tmp_dir / dst).is_file()
|
||||
assert filecmp.cmp(erepo_dir / src, tmp_dir / dst, shallow=False)
|
||||
@@ -357,7 +356,9 @@ def test_push_wildcard_from_bare_git_repo(
|
||||
dvc_repo.dvc.imp(os.fspath(tmp_dir), "dir123")
|
||||
|
||||
|
||||
def test_download_error_pulling_imported_stage(tmp_dir, dvc, erepo_dir):
|
||||
def test_download_error_pulling_imported_stage(
|
||||
mocker, tmp_dir, dvc, erepo_dir
|
||||
):
|
||||
with erepo_dir.chdir():
|
||||
erepo_dir.dvc_gen("foo", "foo content", commit="create foo")
|
||||
dvc.imp(os.fspath(erepo_dir), "foo", "foo_imported")
|
||||
@@ -368,9 +369,8 @@ def test_download_error_pulling_imported_stage(tmp_dir, dvc, erepo_dir):
|
||||
remove("foo_imported")
|
||||
remove(dst_cache)
|
||||
|
||||
with patch(
|
||||
"dvc_objects.fs.generic.transfer", side_effect=Exception
|
||||
), pytest.raises(DownloadError):
|
||||
mocker.patch("dvc_objects.fs.generic.transfer", side_effect=Exception)
|
||||
with pytest.raises(DownloadError):
|
||||
dvc.pull(["foo_imported.dvc"])
|
||||
|
||||
|
||||
|
||||
@@ -13,37 +13,32 @@ from dvc.exceptions import InvalidArgumentError
|
||||
from dvc.stage import Stage
|
||||
from dvc.testing.workspace_tests import TestImport as _TestImport
|
||||
from dvc.utils.fs import makedirs
|
||||
from tests.basic_env import TestDvc
|
||||
|
||||
|
||||
class TestCmdImport(TestDvc):
|
||||
def test(self):
|
||||
ret = main(["import-url", self.FOO, "import"])
|
||||
self.assertEqual(ret, 0)
|
||||
self.assertTrue(os.path.exists("import.dvc"))
|
||||
def test_cmd_import(tmp_dir, dvc):
|
||||
tmp_dir.gen("foo", "foo")
|
||||
ret = main(["import-url", "foo", "import"])
|
||||
assert ret == 0
|
||||
assert os.path.exists("import.dvc")
|
||||
|
||||
ret = main(["import-url", "non-existing-file", "import"])
|
||||
self.assertNotEqual(ret, 0)
|
||||
|
||||
def test_unsupported(self):
|
||||
ret = main(["import-url", "unsupported://path", "import_unsupported"])
|
||||
self.assertNotEqual(ret, 0)
|
||||
ret = main(["import-url", "non-existing-file", "import"])
|
||||
assert ret != 0
|
||||
|
||||
|
||||
class TestDefaultOutput(TestDvc):
|
||||
def test(self):
|
||||
tmpdir = self.mkdtemp()
|
||||
filename = str(uuid4())
|
||||
tmpfile = os.path.join(tmpdir, filename)
|
||||
def test_cmd_unsupported_scheme(dvc):
|
||||
ret = main(["import-url", "unsupported://path", "import_unsupported"])
|
||||
assert ret != 0
|
||||
|
||||
with open(tmpfile, "w", encoding="utf-8") as fd:
|
||||
fd.write("content")
|
||||
|
||||
ret = main(["import-url", tmpfile])
|
||||
self.assertEqual(ret, 0)
|
||||
self.assertTrue(os.path.exists(filename))
|
||||
with open(filename, encoding="utf-8") as fd:
|
||||
self.assertEqual(fd.read(), "content")
|
||||
def test_default_output(tmp_dir, dvc, cloud):
|
||||
filename = str(uuid4())
|
||||
tmpfile = cloud / filename
|
||||
tmpfile.write_bytes(b"content")
|
||||
cloud.gen(filename, "content")
|
||||
|
||||
ret = main(["import-url", tmpfile.fs_path])
|
||||
assert ret == 0
|
||||
assert (tmp_dir / filename).read_bytes() == b"content"
|
||||
|
||||
|
||||
def test_should_remove_outs_before_import(tmp_dir, dvc, mocker, erepo_dir):
|
||||
@@ -56,26 +51,22 @@ def test_should_remove_outs_before_import(tmp_dir, dvc, mocker, erepo_dir):
|
||||
assert remove_outs_call_counter.mock.call_count == 1
|
||||
|
||||
|
||||
class TestImportFilename(TestDvc):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
tmp_dir = self.mkdtemp()
|
||||
self.external_source = os.path.join(tmp_dir, "file")
|
||||
with open(self.external_source, "w", encoding="utf-8") as fobj:
|
||||
fobj.write("content")
|
||||
def test_import_filename(tmp_dir, dvc, cloud):
|
||||
external_source = cloud / "file"
|
||||
(cloud / "file").write_text("content", encoding="utf-8")
|
||||
ret = main(["import-url", "--file", "bar.dvc", external_source.fs_path])
|
||||
assert ret == 0
|
||||
assert (tmp_dir / "bar.dvc").exists()
|
||||
|
||||
def test(self):
|
||||
ret = main(["import-url", "--file", "bar.dvc", self.external_source])
|
||||
self.assertEqual(0, ret)
|
||||
self.assertTrue(os.path.exists("bar.dvc"))
|
||||
(tmp_dir / "bar.dvc").unlink()
|
||||
(tmp_dir / "sub").mkdir()
|
||||
|
||||
os.remove("bar.dvc")
|
||||
os.mkdir("sub")
|
||||
|
||||
path = os.path.join("sub", "bar.dvc")
|
||||
ret = main(["import-url", "--file", path, self.external_source, "out"])
|
||||
self.assertEqual(0, ret)
|
||||
self.assertTrue(os.path.exists(path))
|
||||
path = tmp_dir / "sub" / "bar.dvc"
|
||||
ret = main(
|
||||
["import-url", "--file", path.fs_path, external_source.fs_path, "out"]
|
||||
)
|
||||
assert ret == 0
|
||||
assert path.exists()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dname", [".", "dir", "dir/subdir"])
|
||||
|
||||
@@ -64,7 +64,7 @@ def test_init_quiet_should_not_display_welcome_screen(tmp_dir, scm, caplog):
|
||||
with caplog.at_level(logging.INFO, logger="dvc"):
|
||||
ret = main(["init", "--quiet"])
|
||||
|
||||
assert 0 == ret
|
||||
assert ret == 0
|
||||
assert "" == caplog.text
|
||||
|
||||
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
from copy import deepcopy
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
from funcy import first
|
||||
|
||||
from dvc import stage as stage_module
|
||||
|
||||
pytest.importorskip("dvclive", reason="no dvclive")
|
||||
|
||||
|
||||
LIVE_SCRIPT = dedent(
|
||||
"""
|
||||
from dvclive import Live
|
||||
from PIL import Image
|
||||
|
||||
import sys
|
||||
r = 2
|
||||
metrics_logger = Live()
|
||||
for i in range(r):
|
||||
metrics_logger.log("loss", 1-i/r)
|
||||
metrics_logger.log("accuracy", i/r)
|
||||
image = Image.new("RGB", (10,10), (i,i,i))
|
||||
metrics_logger.log_image("image.jpg", image)
|
||||
metrics_logger.next_step()"""
|
||||
)
|
||||
|
||||
LIVE_CHECKPOINT_SCRIPT = dedent(
|
||||
"""
|
||||
import os
|
||||
from dvclive import Live
|
||||
|
||||
def read(path):
|
||||
value=0
|
||||
if os.path.exists(path):
|
||||
with open(path, 'r') as fobj:
|
||||
try:
|
||||
value = int(fobj.read())
|
||||
except ValueError:
|
||||
pass
|
||||
return value
|
||||
|
||||
def dump(value, path):
|
||||
with open(path, "w") as fobj:
|
||||
fobj.write(str(value))
|
||||
|
||||
r = 3
|
||||
checkpoint_file = "checkpoint"
|
||||
metrics_logger = Live()
|
||||
|
||||
value = read(checkpoint_file)
|
||||
for i in range(1,r):
|
||||
m = i + value
|
||||
dump(m, checkpoint_file)
|
||||
|
||||
metrics_logger.log("metric1", m)
|
||||
metrics_logger.log("metric2", m * 2)
|
||||
metrics_logger.next_step()"""
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def live_stage(tmp_dir, scm, dvc, mocker):
|
||||
mocker.patch("dvc.stage.run.Monitor.AWAIT", 0.01)
|
||||
|
||||
def make(
|
||||
html=True,
|
||||
live=None,
|
||||
live_no_cache=None,
|
||||
code=LIVE_SCRIPT,
|
||||
):
|
||||
assert bool(live) != bool(live_no_cache)
|
||||
tmp_dir.gen("train.py", code)
|
||||
tmp_dir.gen("params.yaml", "foo: 1")
|
||||
stage = dvc.run(
|
||||
cmd="python train.py",
|
||||
params=["foo"],
|
||||
deps=["train.py"],
|
||||
name="live_stage",
|
||||
live=live,
|
||||
live_no_cache=live_no_cache,
|
||||
live_no_html=not html,
|
||||
)
|
||||
|
||||
scm.add(["dvc.yaml", "dvc.lock", "train.py", "params.yaml"])
|
||||
scm.commit("initial: live_stage")
|
||||
return stage
|
||||
|
||||
yield make
|
||||
|
||||
|
||||
@pytest.mark.parametrize("html", (True, False))
|
||||
def test_export_config(tmp_dir, dvc, mocker, live_stage, html):
|
||||
run_spy = mocker.spy(stage_module.run, "_run")
|
||||
live_stage(html=html, live="logs")
|
||||
|
||||
assert run_spy.call_count == 1
|
||||
_, kwargs = run_spy.call_args
|
||||
|
||||
assert "DVCLIVE_PATH" in kwargs["env"]
|
||||
assert kwargs["env"]["DVCLIVE_PATH"] == "logs"
|
||||
|
||||
assert "DVCLIVE_HTML" in kwargs["env"]
|
||||
assert kwargs["env"]["DVCLIVE_HTML"] == str(int(html))
|
||||
|
||||
|
||||
def test_live_provides_metrics(tmp_dir, dvc, live_stage):
|
||||
live_stage(live="logs")
|
||||
|
||||
assert (tmp_dir / "logs.json").is_file()
|
||||
assert dvc.metrics.show() == {
|
||||
"": {
|
||||
"data": {
|
||||
"logs.json": {
|
||||
"data": {"accuracy": 0.5, "loss": 0.5, "step": 1}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert (tmp_dir / "logs").is_dir()
|
||||
plots_data = dvc.plots.show()
|
||||
files = list(plots_data["workspace"]["sources"]["data"])
|
||||
assert "logs/scalars/accuracy.tsv" in files
|
||||
assert "logs/scalars/loss.tsv" in files
|
||||
assert "logs/images/0/image.jpg" in files
|
||||
assert "logs/images/1/image.jpg" in files
|
||||
|
||||
|
||||
@pytest.mark.parametrize("typ", ("live", "live_no_cache"))
|
||||
def test_experiments_track_summary(tmp_dir, scm, dvc, live_stage, typ):
|
||||
live_stage(**{typ: "logs"})
|
||||
baseline_rev = scm.get_rev()
|
||||
|
||||
experiments = dvc.experiments.run(targets=["live_stage"], params=["foo=2"])
|
||||
assert len(experiments) == 1
|
||||
((exp_rev, _),) = experiments.items()
|
||||
|
||||
res = dvc.experiments.show()
|
||||
assert "logs.json" in res[baseline_rev][exp_rev]["data"]["metrics"].keys()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("html", [True, False])
|
||||
def test_live_html(tmp_dir, dvc, live_stage, html):
|
||||
live_stage(live="logs", html=html)
|
||||
|
||||
assert (tmp_dir / "logs_dvc_plots" / "index.html").is_file() == html
|
||||
if html:
|
||||
html_text = (tmp_dir / "logs_dvc_plots" / "index.html").read_text()
|
||||
assert 'http-equiv="refresh"' in html_text
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def live_checkpoint_stage(tmp_dir, scm, dvc, mocker):
|
||||
mocker.patch("dvc.stage.run.Monitor.AWAIT", 0.01)
|
||||
|
||||
def make(live=None, live_no_cache=None):
|
||||
assert bool(live) != bool(live_no_cache)
|
||||
|
||||
tmp_dir.gen("train.py", LIVE_CHECKPOINT_SCRIPT)
|
||||
tmp_dir.gen("params.yaml", "foo: 1")
|
||||
stage = dvc.run(
|
||||
cmd="python train.py",
|
||||
params=["foo"],
|
||||
deps=["train.py"],
|
||||
name="live_stage",
|
||||
live=live,
|
||||
live_no_cache=live_no_cache,
|
||||
checkpoints=["checkpoint"],
|
||||
no_exec=True,
|
||||
)
|
||||
|
||||
scm.add(["dvc.yaml", "train.py", "params.yaml", ".gitignore"])
|
||||
scm.commit("initial: live_stage")
|
||||
return stage
|
||||
|
||||
yield make
|
||||
|
||||
|
||||
def checkpoints_metric(show_results, metric_file, metric_name):
|
||||
tmp = deepcopy(show_results)
|
||||
tmp.pop("workspace")
|
||||
tmp = first(tmp.values())
|
||||
tmp.pop("baseline")
|
||||
return [
|
||||
exp["data"]["metrics"][metric_file]["data"][metric_name]
|
||||
for exp in tmp.values()
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("typ", ("live", "live_no_cache"))
|
||||
def test_live_checkpoints_resume(
|
||||
tmp_dir, scm, dvc, live_checkpoint_stage, typ
|
||||
):
|
||||
stage = live_checkpoint_stage(**{typ: "logs"})
|
||||
results = dvc.experiments.run(
|
||||
stage.addressing, params=["foo=2"], tmp_dir=False
|
||||
)
|
||||
|
||||
checkpoint_resume = first(results)
|
||||
|
||||
dvc.experiments.run(
|
||||
stage.addressing, checkpoint_resume=checkpoint_resume, tmp_dir=False
|
||||
)
|
||||
|
||||
results = dvc.experiments.show()
|
||||
assert checkpoints_metric(results, "logs.json", "step") == [3, 2, 1, 0]
|
||||
assert checkpoints_metric(results, "logs.json", "metric1") == [4, 3, 2, 1]
|
||||
assert checkpoints_metric(results, "logs.json", "metric2") == [8, 6, 4, 2]
|
||||
|
||||
|
||||
def test_dvclive_stage_with_different_wdir(tmp_dir, scm, dvc):
|
||||
(tmp_dir / "subdir").gen("train.py", LIVE_SCRIPT)
|
||||
(tmp_dir / "subdir" / "params.yaml").dump({"foo": 1})
|
||||
dvc.stage.add(
|
||||
cmd="python train.py",
|
||||
params=["foo"],
|
||||
deps=["train.py"],
|
||||
name="live_stage",
|
||||
live="dvclive",
|
||||
live_no_html=True,
|
||||
wdir="subdir",
|
||||
)
|
||||
|
||||
dvc.reproduce()
|
||||
|
||||
assert (tmp_dir / "subdir" / "dvclive").is_dir()
|
||||
assert (tmp_dir / "subdir" / "dvclive.json").is_file()
|
||||
+87
-156
@@ -4,198 +4,129 @@ import textwrap
|
||||
import pytest
|
||||
|
||||
from dvc.cli import main
|
||||
from dvc.dvcfile import DVC_FILE_SUFFIX
|
||||
from dvc.exceptions import DvcException, MoveNotDataSourceError
|
||||
from dvc.utils.serialize import load_yaml
|
||||
from tests.basic_env import TestDvc, TestDvcGit
|
||||
from tests.func.test_repro import TestRepro
|
||||
from tests.utils import cd
|
||||
|
||||
|
||||
class TestMove(TestDvc):
|
||||
def test(self):
|
||||
dst = self.FOO + "1"
|
||||
self.dvc.add(self.FOO)
|
||||
self.dvc.move(self.FOO, dst)
|
||||
def test_move(tmp_dir, dvc):
|
||||
tmp_dir.dvc_gen("foo", "foo")
|
||||
dvc.move("foo", "foo1")
|
||||
|
||||
self.assertFalse(os.path.isfile(self.FOO))
|
||||
self.assertTrue(os.path.isfile(dst))
|
||||
assert not (tmp_dir / "foo").is_file()
|
||||
assert (tmp_dir / "foo1").is_file()
|
||||
|
||||
|
||||
class TestMoveNonExistentFile(TestDvc):
|
||||
def test(self):
|
||||
with self.assertRaises(DvcException):
|
||||
self.dvc.move("non_existent_file", "dst")
|
||||
def test_move_non_existent_file(dvc):
|
||||
with pytest.raises(DvcException):
|
||||
dvc.move("non_existent_file", "dst")
|
||||
|
||||
|
||||
class TestMoveDirectory(TestDvc):
|
||||
def test(self):
|
||||
dst = "dst"
|
||||
stages = self.dvc.add(self.DATA_DIR)
|
||||
self.assertEqual(len(stages), 1)
|
||||
self.assertTrue(stages[0] is not None)
|
||||
self.dvc.move(self.DATA_DIR, dst)
|
||||
self.assertFalse(os.path.exists(self.DATA_DIR))
|
||||
self.assertTrue(os.path.exists(dst))
|
||||
def test_move_directory(tmp_dir, dvc):
|
||||
tmp_dir.dvc_gen("data", {"foo": "foo", "bar": "bar"})
|
||||
dvc.move("data", "dst")
|
||||
assert not (tmp_dir / "data").is_dir()
|
||||
assert (tmp_dir / "dst").is_dir()
|
||||
|
||||
|
||||
class TestCmdMove(TestDvc):
|
||||
def test(self):
|
||||
stages = self.dvc.add(self.FOO)
|
||||
self.assertEqual(len(stages), 1)
|
||||
self.assertTrue(stages[0] is not None)
|
||||
|
||||
ret = main(["move", self.FOO, self.FOO + "1"])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
ret = main(["move", "non-existing-file", "dst"])
|
||||
self.assertNotEqual(ret, 0)
|
||||
def test_cmd_move(tmp_dir, dvc):
|
||||
tmp_dir.dvc_gen("foo", "foo")
|
||||
assert main(["move", "foo", "foo" + "1"]) == 0
|
||||
assert main(["move", "non-existing-file", "dst"]) != 0
|
||||
|
||||
|
||||
class TestMoveNotDataSource(TestRepro):
|
||||
def test(self):
|
||||
from dvc.repo import Repo as DvcRepo
|
||||
def test_move_not_data_source(tmp_dir, dvc):
|
||||
tmp_dir.dvc_gen("foo", "foo")
|
||||
dvc.run(
|
||||
cmd="cp foo file1",
|
||||
outs=["file1"],
|
||||
deps=["foo"],
|
||||
single_stage=True,
|
||||
)
|
||||
|
||||
self.dvc = DvcRepo(self._root_dir)
|
||||
with self.assertRaises(MoveNotDataSourceError):
|
||||
self.dvc.move(self.file1, "dst")
|
||||
with pytest.raises(MoveNotDataSourceError):
|
||||
dvc.move("file1", "dst")
|
||||
|
||||
ret = main(["move", self.file1, "dst"])
|
||||
self.assertNotEqual(ret, 0)
|
||||
assert main(["move", "file1", "dst"]) != 0
|
||||
assert (tmp_dir / "file1").exists()
|
||||
|
||||
|
||||
class TestMoveFileWithExtension(TestDvc):
|
||||
def test(self):
|
||||
with open(
|
||||
os.path.join(self.dvc.root_dir, "file.csv"), "w", encoding="utf-8"
|
||||
) as fd:
|
||||
fd.write("1,2,3\n")
|
||||
def test_move_file_with_extension(tmp_dir, dvc):
|
||||
tmp_dir.dvc_gen("file.csv", "1,2,3\n")
|
||||
|
||||
self.dvc.add("file.csv")
|
||||
|
||||
self.assertTrue(os.path.exists("file.csv"))
|
||||
self.assertTrue(os.path.exists("file.csv.dvc"))
|
||||
|
||||
ret = main(["move", "file.csv", "other_name.csv"])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
self.assertFalse(os.path.exists("file.csv"))
|
||||
self.assertFalse(os.path.exists("file.csv.dvc"))
|
||||
self.assertTrue(os.path.exists("other_name.csv"))
|
||||
self.assertTrue(os.path.exists("other_name.csv.dvc"))
|
||||
assert main(["move", "file.csv", "other_name.csv"]) == 0
|
||||
assert not (tmp_dir / "file.csv").exists()
|
||||
assert not (tmp_dir / "file.csv.dvc").exists()
|
||||
assert (tmp_dir / "other_name.csv").exists()
|
||||
assert (tmp_dir / "other_name.csv.dvc").exists()
|
||||
|
||||
|
||||
class TestMoveFileToDirectory(TestDvc):
|
||||
def test(self):
|
||||
foo_dvc_file = self.FOO + DVC_FILE_SUFFIX
|
||||
ret = main(["add", self.FOO])
|
||||
self.assertEqual(ret, 0)
|
||||
self.assertTrue(os.path.exists(foo_dvc_file))
|
||||
def test_move_file_to_directory(tmp_dir, dvc):
|
||||
tmp_dir.dvc_gen("foo", "foo")
|
||||
tmp_dir.gen({"data": {"bar": "bar"}})
|
||||
|
||||
new_foo_path = os.path.join(self.DATA_DIR, self.FOO)
|
||||
new_foo_dvc_path = new_foo_path + DVC_FILE_SUFFIX
|
||||
ret = main(["move", self.FOO, new_foo_path])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
self.assertFalse(os.path.exists(self.FOO))
|
||||
self.assertFalse(os.path.exists(foo_dvc_file))
|
||||
self.assertTrue(os.path.exists(new_foo_path))
|
||||
self.assertTrue(os.path.exists(new_foo_dvc_path))
|
||||
assert main(["move", "foo", os.path.join("data", "foo")]) == 0
|
||||
assert not (tmp_dir / "foo").exists()
|
||||
assert not (tmp_dir / "foo.dvc").exists()
|
||||
assert (tmp_dir / "data" / "foo").exists()
|
||||
assert (tmp_dir / "data" / "foo.dvc").exists()
|
||||
|
||||
|
||||
class TestMoveFileToDirectoryWithoutSpecifiedTargetName(TestDvc):
|
||||
def test(self):
|
||||
foo_stage_file_path = self.FOO + DVC_FILE_SUFFIX
|
||||
ret = main(["add", self.FOO])
|
||||
self.assertEqual(ret, 0)
|
||||
self.assertTrue(os.path.exists(foo_stage_file_path))
|
||||
def test_move_file_to_directory_without_specified_target_name(tmp_dir, dvc):
|
||||
tmp_dir.dvc_gen("foo", "foo")
|
||||
tmp_dir.gen({"data": {"bar": "bar"}})
|
||||
|
||||
target_foo_path = os.path.join(self.DATA_DIR, self.FOO)
|
||||
target_foo_stage_file_path = target_foo_path + DVC_FILE_SUFFIX
|
||||
assert main(["move", "foo", "data"]) == 0
|
||||
assert not (tmp_dir / "foo").exists()
|
||||
assert not (tmp_dir / "foo.dvc").exists()
|
||||
assert (tmp_dir / "data" / "foo").exists()
|
||||
assert (tmp_dir / "data" / "foo.dvc").exists()
|
||||
|
||||
ret = main(["move", self.FOO, self.DATA_DIR])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
self.assertFalse(os.path.exists(self.FOO))
|
||||
self.assertFalse(os.path.exists(foo_stage_file_path))
|
||||
|
||||
self.assertTrue(os.path.exists(target_foo_path))
|
||||
self.assertTrue(os.path.exists(target_foo_stage_file_path))
|
||||
|
||||
new_stage = load_yaml(target_foo_stage_file_path)
|
||||
self.assertEqual(self.FOO, new_stage["outs"][0]["path"])
|
||||
new_stage = (tmp_dir / "data" / "foo.dvc").load_yaml()
|
||||
assert new_stage["outs"][0]["path"] == "foo"
|
||||
|
||||
|
||||
class TestMoveDirectoryShouldNotOverwriteExisting(TestDvcGit):
|
||||
def test(self):
|
||||
dir_name = "dir"
|
||||
orig_listdir = set(os.listdir(self.DATA_DIR))
|
||||
def test_move_directory_should_not_overwrite_existing(tmp_dir, dvc, scm):
|
||||
tmp_dir.dvc_gen({"data": {"foo": "foo"}})
|
||||
new_dir = tmp_dir / "dir"
|
||||
new_dir.mkdir()
|
||||
|
||||
self.dvc.add(self.DATA_DIR)
|
||||
|
||||
os.mkdir(dir_name)
|
||||
new_dir_name = os.path.join(dir_name, self.DATA_DIR)
|
||||
|
||||
self.dvc.move(self.DATA_DIR, dir_name)
|
||||
|
||||
data_dir_stage = self.DATA_DIR + DVC_FILE_SUFFIX
|
||||
self.assertFalse(os.path.exists(self.DATA_DIR))
|
||||
self.assertFalse(os.path.exists(data_dir_stage))
|
||||
|
||||
self.assertTrue(os.path.exists(dir_name))
|
||||
self.assertEqual(
|
||||
set(os.listdir(dir_name)),
|
||||
{".gitignore", data_dir_stage, self.DATA_DIR},
|
||||
)
|
||||
|
||||
self.assertTrue(os.path.exists(new_dir_name))
|
||||
self.assertTrue(os.path.isfile(new_dir_name + DVC_FILE_SUFFIX))
|
||||
self.assertEqual(set(os.listdir(new_dir_name)), orig_listdir)
|
||||
dvc.move("data", "dir")
|
||||
assert not (tmp_dir / "data").exists()
|
||||
assert not (tmp_dir / "data.dvc").exists()
|
||||
assert set(new_dir.iterdir()) == {
|
||||
new_dir / ".gitignore",
|
||||
new_dir / "data.dvc",
|
||||
new_dir / "data",
|
||||
}
|
||||
assert set((new_dir / "data").iterdir()) == {new_dir / "data" / "foo"}
|
||||
|
||||
|
||||
class TestMoveFileBetweenDirectories(TestDvc):
|
||||
def test(self):
|
||||
data_stage_file = self.DATA + DVC_FILE_SUFFIX
|
||||
ret = main(["add", self.DATA])
|
||||
self.assertEqual(ret, 0)
|
||||
self.assertTrue(os.path.exists(data_stage_file))
|
||||
def test_move_file_between_directories(tmp_dir, dvc):
|
||||
tmp_dir.gen({"data": {"foo": "foo"}})
|
||||
dvc.add(os.path.join("data", "foo"))
|
||||
|
||||
new_data_dir = "data_dir2"
|
||||
os.makedirs(new_data_dir)
|
||||
(tmp_dir / "data2").mkdir()
|
||||
|
||||
ret = main(["move", self.DATA, new_data_dir])
|
||||
self.assertEqual(ret, 0)
|
||||
assert main(["move", os.path.join("data", "foo"), "data2"]) == 0
|
||||
assert not (tmp_dir / "data" / "foo").exists()
|
||||
assert not (tmp_dir / "data" / "foo.dvc").exists()
|
||||
assert (tmp_dir / "data2" / "foo").exists()
|
||||
assert (tmp_dir / "data2" / "foo.dvc").exists()
|
||||
|
||||
new_data_path = os.path.join(new_data_dir, os.path.basename(self.DATA))
|
||||
new_data_stage_file = new_data_path + DVC_FILE_SUFFIX
|
||||
|
||||
self.assertFalse(os.path.exists(self.DATA))
|
||||
self.assertFalse(os.path.exists(data_stage_file))
|
||||
|
||||
self.assertTrue(os.path.exists(new_data_path))
|
||||
self.assertTrue(os.path.exists(new_data_stage_file))
|
||||
|
||||
new_stage_file = load_yaml(new_data_stage_file)
|
||||
self.assertEqual(
|
||||
os.path.basename(self.DATA), new_stage_file["outs"][0]["path"]
|
||||
)
|
||||
d = (tmp_dir / "data2" / "foo.dvc").load_yaml()
|
||||
assert d["outs"][0]["path"] == "foo"
|
||||
|
||||
|
||||
class TestMoveFileInsideDirectory(TestDvc):
|
||||
def test(self):
|
||||
ret = main(["add", self.DATA])
|
||||
self.assertEqual(ret, 0)
|
||||
def test_move_file_inside_directory(tmp_dir, dvc):
|
||||
tmp_dir.gen({"data": {"foo": "foo"}})
|
||||
file = tmp_dir / "data" / "foo"
|
||||
dvc.add(file.fs_path)
|
||||
|
||||
with cd(self.DATA_DIR):
|
||||
ret = main(["move", os.path.basename(self.DATA), "data.txt"])
|
||||
self.assertEqual(ret, 0)
|
||||
with (tmp_dir / "data").chdir():
|
||||
assert main(["move", "foo", "data.txt"]) == 0
|
||||
|
||||
self.assertFalse(os.path.exists(self.DATA))
|
||||
|
||||
data_fullpath = os.path.join(self.DATA_DIR, "data.txt")
|
||||
dvc_fullpath = os.path.join(self.DATA_DIR, "data.txt.dvc")
|
||||
self.assertTrue(os.path.exists(data_fullpath))
|
||||
self.assertTrue(os.path.exists(dvc_fullpath))
|
||||
assert not file.exists()
|
||||
assert (tmp_dir / "data" / "data.txt").exists()
|
||||
assert (tmp_dir / "data" / "data.txt.dvc").exists()
|
||||
|
||||
|
||||
def test_move_should_save_stage_info(tmp_dir, dvc):
|
||||
|
||||
+56
-65
@@ -10,58 +10,51 @@ from dvc.cli import main
|
||||
from dvc.config import Config
|
||||
from dvc.exceptions import DownloadError, RemoteCacheRequiredError, UploadError
|
||||
from dvc.utils.fs import remove
|
||||
from tests.basic_env import TestDvc
|
||||
|
||||
|
||||
class TestRemote(TestDvc):
|
||||
def test(self):
|
||||
remotes = ["a", "b", "c"]
|
||||
def test_remote(dvc):
|
||||
remotes = ["a", "b", "c"]
|
||||
|
||||
self.assertEqual(main(["remote", "list"]), 0)
|
||||
self.assertNotEqual(main(["remote", "remove", remotes[0]]), 0)
|
||||
assert main(["remote", "list"]) == 0
|
||||
assert main(["remote", "remove", remotes[0]]) != 0
|
||||
|
||||
for r in remotes:
|
||||
self.assertEqual(
|
||||
main(["remote", "add", "--default", r, "s3://bucket/name"]), 0
|
||||
)
|
||||
for r in remotes:
|
||||
assert main(["remote", "add", "--default", r, "s3://bucket/name"]) == 0
|
||||
|
||||
self.assertEqual(main(["remote", "list"]), 0)
|
||||
assert main(["remote", "list"]) == 0
|
||||
|
||||
self.assertEqual(
|
||||
main(["remote", "modify", remotes[0], "checksum_jobs", "1"]), 0
|
||||
)
|
||||
self.assertEqual(main(["remote", "remove", remotes[0]]), 0)
|
||||
assert main(["remote", "modify", remotes[0], "checksum_jobs", "1"]) == 0
|
||||
assert main(["remote", "remove", remotes[0]]) == 0
|
||||
|
||||
self.assertEqual(main(["remote", "list"]), 0)
|
||||
assert main(["remote", "list"]) == 0
|
||||
|
||||
def test_relative_path(self):
|
||||
dname = os.path.join("..", "path", "to", "dir")
|
||||
ret = main(["remote", "add", "mylocal", dname])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
# NOTE: we are in the repo's root and config is in .dvc/, so
|
||||
# dir path written to config should be just one level above.
|
||||
rel = os.path.join("..", dname)
|
||||
config = configobj.ConfigObj(self.dvc.config.files["repo"])
|
||||
self.assertEqual(
|
||||
config['remote "mylocal"']["url"], rel.replace("\\", "/")
|
||||
)
|
||||
def test_remote_add_relative_path(dvc):
|
||||
dname = os.path.join("..", "path", "to", "dir")
|
||||
ret = main(["remote", "add", "mylocal", dname])
|
||||
assert ret == 0
|
||||
|
||||
def test_overwrite(self):
|
||||
remote_name = "a"
|
||||
remote_url = "s3://bucket/name"
|
||||
self.assertEqual(main(["remote", "add", remote_name, remote_url]), 0)
|
||||
self.assertEqual(main(["remote", "add", remote_name, remote_url]), 251)
|
||||
self.assertEqual(
|
||||
main(["remote", "add", "-f", remote_name, remote_url]), 0
|
||||
)
|
||||
# NOTE: we are in the repo's root and config is in .dvc/, so
|
||||
# dir path written to config should be just one level above.
|
||||
rel = os.path.join("..", dname)
|
||||
config = configobj.ConfigObj(dvc.config.files["repo"])
|
||||
assert config['remote "mylocal"']["url"] == rel.replace("\\", "/")
|
||||
|
||||
def test_referencing_other_remotes(self):
|
||||
assert main(["remote", "add", "foo", "ssh://localhost/"]) == 0
|
||||
assert main(["remote", "add", "bar", "remote://foo/dvc-storage"]) == 0
|
||||
|
||||
config = configobj.ConfigObj(self.dvc.config.files["repo"])
|
||||
assert config['remote "bar"']["url"] == "remote://foo/dvc-storage"
|
||||
def test_remote_overwrite(dvc):
|
||||
remote_name = "a"
|
||||
remote_url = "s3://bucket/name"
|
||||
assert main(["remote", "add", remote_name, remote_url]) == 0
|
||||
assert main(["remote", "add", remote_name, remote_url]) == 251
|
||||
assert main(["remote", "add", "-f", remote_name, remote_url]) == 0
|
||||
|
||||
|
||||
def test_referencing_other_remotes(dvc):
|
||||
assert main(["remote", "add", "foo", "ssh://localhost/"]) == 0
|
||||
assert main(["remote", "add", "bar", "remote://foo/dvc-storage"]) == 0
|
||||
|
||||
config = configobj.ConfigObj(dvc.config.files["repo"])
|
||||
assert config['remote "bar"']["url"] == "remote://foo/dvc-storage"
|
||||
|
||||
|
||||
def test_remove_default(tmp_dir, dvc):
|
||||
@@ -85,37 +78,35 @@ def test_remove_default(tmp_dir, dvc):
|
||||
assert local_config.get("core", {}).get("remote") is None
|
||||
|
||||
|
||||
class TestRemoteRemove(TestDvc):
|
||||
def test(self):
|
||||
ret = main(["config", "core.checksum_jobs", "1"])
|
||||
self.assertEqual(ret, 0)
|
||||
def test_remote_remove(dvc):
|
||||
ret = main(["config", "core.checksum_jobs", "1"])
|
||||
assert ret == 0
|
||||
|
||||
remote = "mys3"
|
||||
ret = main(["remote", "add", remote, "s3://bucket/name"])
|
||||
self.assertEqual(ret, 0)
|
||||
remote = "mys3"
|
||||
ret = main(["remote", "add", remote, "s3://bucket/name"])
|
||||
assert ret == 0
|
||||
|
||||
ret = main(["remote", "remove", remote])
|
||||
self.assertEqual(ret, 0)
|
||||
ret = main(["remote", "remove", remote])
|
||||
assert ret == 0
|
||||
|
||||
|
||||
class TestRemoteDefault(TestDvc):
|
||||
def test(self):
|
||||
remote = "mys3"
|
||||
ret = main(["remote", "add", "mys3", "s3://bucket/path"])
|
||||
self.assertEqual(ret, 0)
|
||||
def test_remote_default_cmd(dvc):
|
||||
remote = "mys3"
|
||||
ret = main(["remote", "add", "mys3", "s3://bucket/path"])
|
||||
assert ret == 0
|
||||
|
||||
ret = main(["remote", "default", "mys3"])
|
||||
self.assertEqual(ret, 0)
|
||||
config_file = os.path.join(self.dvc.dvc_dir, Config.CONFIG)
|
||||
config = configobj.ConfigObj(config_file)
|
||||
default = config["core"]["remote"]
|
||||
self.assertEqual(default, remote)
|
||||
ret = main(["remote", "default", "mys3"])
|
||||
assert ret == 0
|
||||
config_file = os.path.join(dvc.dvc_dir, Config.CONFIG)
|
||||
config = configobj.ConfigObj(config_file)
|
||||
default = config["core"]["remote"]
|
||||
assert default == remote
|
||||
|
||||
ret = main(["remote", "default", "--unset"])
|
||||
self.assertEqual(ret, 0)
|
||||
config = configobj.ConfigObj(config_file)
|
||||
default = config.get("core", {}).get("remote")
|
||||
self.assertEqual(default, None)
|
||||
ret = main(["remote", "default", "--unset"])
|
||||
assert ret == 0
|
||||
config = configobj.ConfigObj(config_file)
|
||||
default = config.get("core", {}).get("remote")
|
||||
assert default is None
|
||||
|
||||
|
||||
def test_show_default(dvc, capsys):
|
||||
|
||||
@@ -220,8 +220,8 @@ def test_unique_identifier(tmp_dir, dvc, scm, run_copy):
|
||||
# we get "workspace" as well from the brancher by default
|
||||
assert len(revs) + 1 == len(ids)
|
||||
possible_ids = {
|
||||
True: "2ba7c7c5b395d4211348d6274b869fc7",
|
||||
False: "8406970ad2fcafaa84d9310330a67576",
|
||||
True: "dd9cf3a3dc2caac5835e64f258ac2d7b",
|
||||
False: "b446f3cdc35539884dcc474d90ac6286",
|
||||
}
|
||||
assert set(ids) == {possible_ids[os.name == "posix"]}
|
||||
|
||||
|
||||
+731
-740
File diff suppressed because it is too large
Load Diff
@@ -9,149 +9,6 @@ from dvc.cli import main
|
||||
from dvc.dvcfile import PIPELINE_FILE, PIPELINE_LOCK
|
||||
from dvc.exceptions import CyclicGraphError, ReproductionError
|
||||
from dvc.stage import PipelineStage
|
||||
from tests.func import test_repro
|
||||
from tests.utils.scriptify import scriptify
|
||||
|
||||
|
||||
def copy_script(src, dest):
|
||||
import shutil
|
||||
|
||||
shutil.copyfile(src, dest)
|
||||
|
||||
|
||||
COPY_SCRIPT, _ = scriptify(copy_script)
|
||||
|
||||
|
||||
class MultiStageRun:
|
||||
def _run(self, **kwargs):
|
||||
assert kwargs.get("name")
|
||||
kwargs.pop("fname", None)
|
||||
# ignore fname for now
|
||||
return self.dvc.run(**kwargs) # noqa, pylint: disable=no-member
|
||||
|
||||
@staticmethod
|
||||
def _get_stage_target(stage):
|
||||
return stage.addressing
|
||||
|
||||
|
||||
class TestReproFailMultiStage(MultiStageRun, test_repro.TestReproFail):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproCyclicGraphMultiStage(
|
||||
MultiStageRun, test_repro.TestReproCyclicGraph
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproUnderDirMultiStage(
|
||||
MultiStageRun, test_repro.TestReproDepUnderDir
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproDepDirWithOutputsUnderItMultiStage(
|
||||
MultiStageRun, test_repro.TestReproDepDirWithOutputsUnderIt
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproForceMultiStage(MultiStageRun, test_repro.TestReproForce):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproChangedCodeMultiStage(
|
||||
MultiStageRun, test_repro.TestReproChangedCode
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproChangedDataMultiStage(
|
||||
MultiStageRun, test_repro.TestReproChangedData
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproDry(MultiStageRun, test_repro.TestReproDry):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproUpToDateMultiStage(MultiStageRun, test_repro.TestReproUpToDate):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproChangedDeepDataMultiStage(
|
||||
MultiStageRun, test_repro.TestReproChangedDeepData
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproPipelineMultiStage(MultiStageRun, test_repro.TestReproPipeline):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproPipelinesMultiStage(
|
||||
MultiStageRun, test_repro.TestReproPipelines
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproFrozenMultiStage(MultiStageRun, test_repro.TestReproFrozen):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproFrozenCallbackMultiStage(
|
||||
MultiStageRun, test_repro.TestReproFrozenCallback
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproFrozenUnchangedMultiStage(
|
||||
MultiStageRun, test_repro.TestReproFrozenUnchanged
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproPhonyMultiStage(MultiStageRun, test_repro.TestReproPhony):
|
||||
pass
|
||||
|
||||
|
||||
class TestCmdReproMultiStage(MultiStageRun, test_repro.TestCmdRepro):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproAllPipelinesMultiStage(
|
||||
MultiStageRun, test_repro.TestReproAllPipelines
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproNoCommit(MultiStageRun, test_repro.TestReproNoCommit):
|
||||
pass
|
||||
|
||||
|
||||
class TestNonExistingOutputMultiStage(
|
||||
MultiStageRun, test_repro.TestNonExistingOutput
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproAlreadyCachedMultiStage(
|
||||
MultiStageRun, test_repro.TestReproAlreadyCached
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproChangedDirMultiStage(
|
||||
MultiStageRun, test_repro.TestReproChangedDir
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class TestReproChangedDirDataMultiStage(
|
||||
MultiStageRun, test_repro.TestReproChangedDirData
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def test_non_existing_stage_name(tmp_dir, dvc, run_copy):
|
||||
@@ -300,10 +157,11 @@ def test_repro_when_cmd_changes(tmp_dir, dvc, run_copy, mocker):
|
||||
)
|
||||
|
||||
|
||||
def test_repro_when_new_deps_is_added_in_dvcfile(tmp_dir, dvc, run_copy):
|
||||
def test_repro_when_new_deps_is_added_in_dvcfile(
|
||||
tmp_dir, dvc, run_copy, copy_script
|
||||
):
|
||||
from dvc.dvcfile import Dvcfile
|
||||
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
tmp_dir.gen({"foo": "foo", "bar": "bar"})
|
||||
stage = dvc.run(
|
||||
cmd="python copy.py {} {}".format("foo", "foobar"),
|
||||
@@ -322,10 +180,9 @@ def test_repro_when_new_deps_is_added_in_dvcfile(tmp_dir, dvc, run_copy):
|
||||
assert dvc.reproduce(target)[0] == stage
|
||||
|
||||
|
||||
def test_repro_when_new_outs_is_added_in_dvcfile(tmp_dir, dvc):
|
||||
def test_repro_when_new_outs_is_added_in_dvcfile(tmp_dir, dvc, copy_script):
|
||||
from dvc.dvcfile import Dvcfile
|
||||
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
tmp_dir.gen({"foo": "foo", "bar": "bar"})
|
||||
stage = dvc.run(
|
||||
cmd="python copy.py {} {}".format("foo", "foobar"),
|
||||
@@ -344,10 +201,9 @@ def test_repro_when_new_outs_is_added_in_dvcfile(tmp_dir, dvc):
|
||||
assert dvc.reproduce(target)[0] == stage
|
||||
|
||||
|
||||
def test_repro_when_new_deps_is_moved(tmp_dir, dvc):
|
||||
def test_repro_when_new_deps_is_moved(tmp_dir, dvc, copy_script):
|
||||
from dvc.dvcfile import Dvcfile
|
||||
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
tmp_dir.gen({"foo": "foo", "bar": "foo"})
|
||||
stage = dvc.run(
|
||||
cmd="python copy.py {} {}".format("foo", "foobar"),
|
||||
@@ -360,7 +216,12 @@ def test_repro_when_new_deps_is_moved(tmp_dir, dvc):
|
||||
|
||||
# hardcode values in source code, ignore sys.argv
|
||||
tmp_dir.gen(
|
||||
"copy.py", COPY_SCRIPT.replace("func(*argv)", 'func("bar", "foobar")')
|
||||
"copy.py",
|
||||
"""
|
||||
import shutil
|
||||
|
||||
shutil.copyfile('bar', 'foobar')
|
||||
""",
|
||||
)
|
||||
from shutil import move
|
||||
|
||||
@@ -394,8 +255,7 @@ def test_repro_when_new_out_overlaps_others_stage_outs(tmp_dir, dvc):
|
||||
dvc.reproduce(":run-copy")
|
||||
|
||||
|
||||
def test_repro_when_new_deps_added_does_not_exist(tmp_dir, dvc):
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
def test_repro_when_new_deps_added_does_not_exist(tmp_dir, dvc, copy_script):
|
||||
tmp_dir.gen("foo", "foo")
|
||||
(tmp_dir / PIPELINE_FILE).dump(
|
||||
{
|
||||
@@ -412,8 +272,7 @@ def test_repro_when_new_deps_added_does_not_exist(tmp_dir, dvc):
|
||||
dvc.reproduce(":run-copy")
|
||||
|
||||
|
||||
def test_repro_when_new_outs_added_does_not_exist(tmp_dir, dvc):
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
def test_repro_when_new_outs_added_does_not_exist(tmp_dir, dvc, copy_script):
|
||||
tmp_dir.gen("foo", "foo")
|
||||
(tmp_dir / PIPELINE_FILE).dump(
|
||||
{
|
||||
@@ -430,8 +289,7 @@ def test_repro_when_new_outs_added_does_not_exist(tmp_dir, dvc):
|
||||
dvc.reproduce(":run-copy")
|
||||
|
||||
|
||||
def test_repro_when_lockfile_gets_deleted(tmp_dir, dvc):
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
def test_repro_when_lockfile_gets_deleted(tmp_dir, dvc, copy_script):
|
||||
tmp_dir.gen("foo", "foo")
|
||||
(tmp_dir / PIPELINE_FILE).dump(
|
||||
{
|
||||
|
||||
+498
-605
File diff suppressed because it is too large
Load Diff
+53
-72
@@ -1,10 +1,8 @@
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
|
||||
from dvc.annotations import Annotation
|
||||
from dvc.cli import main
|
||||
from dvc.dvcfile import SingleStageFile
|
||||
from dvc.fs import LocalFileSystem
|
||||
from dvc.output import Output
|
||||
@@ -15,7 +13,6 @@ from dvc.stage.utils import compute_md5
|
||||
from dvc.utils import dict_md5
|
||||
from dvc.utils.serialize import dump_yaml, load_yaml
|
||||
from dvc.utils.strictyaml import YAMLValidationError
|
||||
from tests.basic_env import TestDvc
|
||||
|
||||
|
||||
def test_cmd_obj():
|
||||
@@ -71,90 +68,74 @@ def test_list():
|
||||
SingleStageFile.validate(d)
|
||||
|
||||
|
||||
class TestReload(TestDvc):
|
||||
def test(self):
|
||||
stages = self.dvc.add(self.FOO)
|
||||
self.assertEqual(len(stages), 1)
|
||||
stage = stages[0]
|
||||
self.assertTrue(stage is not None)
|
||||
def test_reload(tmp_dir, dvc):
|
||||
(stage,) = tmp_dir.dvc_gen("foo", "foo")
|
||||
d = load_yaml(stage.relpath)
|
||||
|
||||
d = load_yaml(stage.relpath)
|
||||
# NOTE: checking that reloaded stage didn't change its checksum
|
||||
md5 = "11111111111111111111111111111111"
|
||||
d[stage.PARAM_MD5] = md5
|
||||
dump_yaml(stage.relpath, d)
|
||||
|
||||
# NOTE: checking that reloaded stage didn't change its checksum
|
||||
md5 = "11111111111111111111111111111111"
|
||||
d[stage.PARAM_MD5] = md5
|
||||
dump_yaml(stage.relpath, d)
|
||||
dvcfile = SingleStageFile(dvc, stage.relpath)
|
||||
stage = dvcfile.stage
|
||||
|
||||
dvcfile = SingleStageFile(self.dvc, stage.relpath)
|
||||
stage = dvcfile.stage
|
||||
assert stage is not None
|
||||
dvcfile.dump(stage)
|
||||
|
||||
self.assertTrue(stage is not None)
|
||||
dvcfile.dump(stage)
|
||||
|
||||
d = load_yaml(stage.relpath)
|
||||
self.assertEqual(d[stage.PARAM_MD5], md5)
|
||||
d = load_yaml(stage.relpath)
|
||||
assert d[stage.PARAM_MD5] == md5
|
||||
|
||||
|
||||
class TestDefaultWorkingDirectory(TestDvc):
|
||||
def test_ignored_in_checksum(self):
|
||||
stage = self.dvc.run(
|
||||
cmd=f"echo test > {self.FOO}",
|
||||
deps=[self.BAR],
|
||||
outs=[self.FOO],
|
||||
single_stage=True,
|
||||
)
|
||||
def test_default_wdir_ignored_in_checksum(tmp_dir, dvc):
|
||||
tmp_dir.gen("bar", "bar")
|
||||
stage = dvc.run(
|
||||
cmd="cp bar foo",
|
||||
deps=["bar"],
|
||||
outs=["foo"],
|
||||
single_stage=True,
|
||||
)
|
||||
|
||||
d = stage.dumpd()
|
||||
self.assertNotIn(Stage.PARAM_WDIR, d.keys())
|
||||
d = stage.dumpd()
|
||||
assert Stage.PARAM_WDIR not in d.keys()
|
||||
|
||||
d = load_yaml(stage.relpath)
|
||||
self.assertNotIn(Stage.PARAM_WDIR, d.keys())
|
||||
d = load_yaml(stage.relpath)
|
||||
assert Stage.PARAM_WDIR not in d.keys()
|
||||
|
||||
with self.dvc.lock:
|
||||
stage = SingleStageFile(self.dvc, stage.relpath).stage
|
||||
self.assertFalse(stage.changed())
|
||||
with dvc.lock:
|
||||
stage = SingleStageFile(dvc, stage.relpath).stage
|
||||
assert not stage.changed()
|
||||
|
||||
|
||||
class TestExternalRemoteResolution(TestDvc):
|
||||
def test_remote_output(self):
|
||||
tmp_path = tempfile.mkdtemp()
|
||||
storage = os.path.join(tmp_path, "storage")
|
||||
file_path = os.path.join(storage, "file")
|
||||
def test_external_remote_output_resolution(tmp_dir, dvc, make_remote):
|
||||
tmp_path = make_remote("tmp", default=False)
|
||||
tmp_dir.add_remote(
|
||||
url="remote://tmp/storage", name="storage", default=False
|
||||
)
|
||||
storage = tmp_path / "storage"
|
||||
storage.mkdir()
|
||||
file_path = storage / "file"
|
||||
|
||||
os.makedirs(storage)
|
||||
dvc.run(
|
||||
cmd=f"echo file > {file_path}",
|
||||
outs_no_cache=["remote://storage/file"],
|
||||
single_stage=True,
|
||||
)
|
||||
assert os.path.exists(file_path)
|
||||
|
||||
assert main(["remote", "add", "tmp", tmp_path]) == 0
|
||||
assert main(["remote", "add", "storage", "remote://tmp/storage"]) == 0
|
||||
assert (
|
||||
main(
|
||||
[
|
||||
"run",
|
||||
"--single-stage",
|
||||
"-O",
|
||||
"remote://storage/file",
|
||||
f"echo file > {file_path}",
|
||||
]
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
assert os.path.exists(file_path)
|
||||
def test_external_remote_dependency_resolution(tmp_dir, dvc, make_remote):
|
||||
tmp_path = make_remote("tmp", default=False)
|
||||
tmp_dir.add_remote(
|
||||
url="remote://tmp/storage", name="storage", default=False
|
||||
)
|
||||
storage = tmp_path / "storage"
|
||||
storage.mkdir()
|
||||
file_path = storage / "file"
|
||||
file_path.write_text("Isle of Dogs", encoding="utf-8")
|
||||
|
||||
def test_remote_dependency(self):
|
||||
tmp_path = tempfile.mkdtemp()
|
||||
storage = os.path.join(tmp_path, "storage")
|
||||
file_path = os.path.join(storage, "file")
|
||||
|
||||
os.makedirs(storage)
|
||||
|
||||
with open(file_path, "w", encoding="utf-8") as fobj:
|
||||
fobj.write("Isle of Dogs")
|
||||
|
||||
assert main(["remote", "add", "tmp", tmp_path]) == 0
|
||||
assert main(["remote", "add", "storage", "remote://tmp/storage"]) == 0
|
||||
assert main(["import-url", "remote://storage/file", "movie.txt"]) == 0
|
||||
|
||||
assert os.path.exists("movie.txt")
|
||||
dvc.imp_url("remote://storage/file", "movie.txt")
|
||||
assert (tmp_dir / "movie.txt").read_text() == "Isle of Dogs"
|
||||
|
||||
|
||||
def test_md5_ignores_comments(tmp_dir, dvc):
|
||||
|
||||
@@ -1,37 +1,27 @@
|
||||
import os
|
||||
|
||||
from dvc.cli import main
|
||||
from tests.basic_env import TestDvc
|
||||
|
||||
def test_unprotect(tmp_dir, dvc):
|
||||
tmp_dir.gen("foo", "foo")
|
||||
|
||||
class TestUnprotect(TestDvc):
|
||||
def test(self):
|
||||
ret = main(["config", "cache.type", "hardlink"])
|
||||
self.assertEqual(ret, 0)
|
||||
dvc.odb.local.cache_types = ["hardlink"]
|
||||
dvc.add("foo")
|
||||
cache = os.path.join(
|
||||
".dvc", "cache", "ac", "bd18db4cc2f85cedef654fccc4a4d8"
|
||||
)
|
||||
assert not os.access("foo", os.W_OK)
|
||||
assert not os.access(cache, os.W_OK)
|
||||
|
||||
ret = main(["add", self.FOO])
|
||||
self.assertEqual(ret, 0)
|
||||
dvc.unprotect("foo")
|
||||
assert os.access("foo", os.W_OK)
|
||||
|
||||
cache = os.path.join(
|
||||
".dvc", "cache", "ac", "bd18db4cc2f85cedef654fccc4a4d8"
|
||||
)
|
||||
if os.name == "nt":
|
||||
# NOTE: cache is now unprotected, because NTFS doesn't allow
|
||||
# deleting read-only files, so we have to try to set write perms
|
||||
# on files that we try to delete, which propagates to the cache
|
||||
# file. But it should be restored after the next cache check, hence
|
||||
# why we call `dvc status` here.
|
||||
assert os.access(cache, os.W_OK)
|
||||
dvc.status()
|
||||
|
||||
self.assertFalse(os.access(self.FOO, os.W_OK))
|
||||
self.assertFalse(os.access(cache, os.W_OK))
|
||||
|
||||
ret = main(["unprotect", self.FOO])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
self.assertTrue(os.access(self.FOO, os.W_OK))
|
||||
|
||||
if os.name == "nt":
|
||||
# NOTE: cache is now unprotected, because NTFS doesn't allow
|
||||
# deleting read-only files, so we have to try to set write perms
|
||||
# on files that we try to delete, which propagates to the cache
|
||||
# file. But it should be restored after the next cache check, hence
|
||||
# why we call `dvc status` here.
|
||||
self.assertTrue(os.access(cache, os.W_OK))
|
||||
ret = main(["status"])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
self.assertFalse(os.access(cache, os.W_OK))
|
||||
assert not os.access(cache, os.W_OK)
|
||||
|
||||
@@ -16,7 +16,7 @@ def test_(tmp_dir, dvc, scm, capsys):
|
||||
assert re.search(rf"DVC version: {DVC_VERSION_REGEX}", out)
|
||||
assert re.search(f"Platform: {PYTHON_VERSION_REGEX} on .*", out)
|
||||
for subproject in SUBPROJECTS:
|
||||
assert re.search(rf"{subproject} = {DVC_VERSION_REGEX}", out)
|
||||
assert re.search(rf"{subproject} = .*", out)
|
||||
|
||||
assert find_supported_remotes(out)
|
||||
assert re.search(r"Cache types: .*", out)
|
||||
|
||||
@@ -44,7 +44,7 @@ def extract_vega_specs(html_path, plots_ids):
|
||||
|
||||
result = {}
|
||||
|
||||
with open(html_path, "r", encoding="utf-8") as fd:
|
||||
with open(html_path, encoding="utf-8") as fd:
|
||||
content = fd.read()
|
||||
|
||||
reader = BeautifulSoup(content, features="html.parser")
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import pytest
|
||||
|
||||
COPY_SCRIPT = """
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
if os.path.isfile(sys.argv[1]):
|
||||
shutil.copyfile(sys.argv[1], sys.argv[2])
|
||||
else:
|
||||
shutil.copytree(sys.argv[1], sys.argv[2])
|
||||
"""
|
||||
|
||||
|
||||
def _add_script(tmp_dir, path, contents=""):
|
||||
script, *_ = tmp_dir.gen(path, contents)
|
||||
if hasattr(tmp_dir, "scm"):
|
||||
tmp_dir.scm_add(path, commit=f"add {path}")
|
||||
return script.fs_path
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def append_foo_script(tmp_dir):
|
||||
return _add_script(
|
||||
tmp_dir,
|
||||
"append_foo.py",
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
with Path(sys.argv[1]).open("a+", encoding="utf-8") as f:
|
||||
f.write("foo")
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def copy_script(
|
||||
tmp_dir,
|
||||
):
|
||||
return _add_script(tmp_dir, "copy.py", COPY_SCRIPT)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def head_script(tmp_dir):
|
||||
"""Output first line of each file to different file with '-1' appended.
|
||||
Useful for tracking multiple outputs/dependencies which are not a copy
|
||||
of each others.
|
||||
"""
|
||||
return _add_script(
|
||||
tmp_dir,
|
||||
"head.py",
|
||||
"""
|
||||
import sys
|
||||
for file in sys.argv[1:]:
|
||||
with open(file) as f, open(file +"-1","w+") as w:
|
||||
w.write(f.readline())
|
||||
""",
|
||||
)
|
||||
@@ -39,7 +39,7 @@ def test_default(mocker, capsys):
|
||||
}
|
||||
mocker.patch("dvc.repo.Repo.diff", return_value=diff)
|
||||
|
||||
assert 0 == cmd.run()
|
||||
assert cmd.run() == 0
|
||||
assert (
|
||||
"Added:\n"
|
||||
" file\n"
|
||||
@@ -77,7 +77,7 @@ def test_show_hash(mocker, capsys):
|
||||
"not in cache": [],
|
||||
}
|
||||
mocker.patch("dvc.repo.Repo.diff", return_value=diff)
|
||||
assert 0 == cmd.run()
|
||||
assert cmd.run() == 0
|
||||
|
||||
out, _ = capsys.readouterr()
|
||||
assert (
|
||||
@@ -115,7 +115,7 @@ def test_show_json(mocker, capsys):
|
||||
}
|
||||
mocker.patch("dvc.repo.Repo.diff", return_value=diff)
|
||||
|
||||
assert 0 == cmd.run()
|
||||
assert cmd.run() == 0
|
||||
out, _ = capsys.readouterr()
|
||||
assert '"added": [{"path": "file1"}, {"path": "file2"}]' in out
|
||||
assert '"deleted": []' in out
|
||||
@@ -145,7 +145,7 @@ def test_show_json_and_hash(mocker, capsys):
|
||||
}
|
||||
mocker.patch("dvc.repo.Repo.diff", return_value=diff)
|
||||
|
||||
assert 0 == cmd.run()
|
||||
assert cmd.run() == 0
|
||||
out, _ = capsys.readouterr()
|
||||
assert (
|
||||
'"added": [{"path": "file1", "hash": "11111111"}, '
|
||||
@@ -180,7 +180,7 @@ def test_show_json_hide_missing(mocker, capsys):
|
||||
}
|
||||
mocker.patch("dvc.repo.Repo.diff", return_value=diff)
|
||||
|
||||
assert 0 == cmd.run()
|
||||
assert cmd.run() == 0
|
||||
out, _ = capsys.readouterr()
|
||||
assert '"added": [{"path": "file1"}, {"path": "file2"}]' in out
|
||||
assert '"deleted": []' in out
|
||||
@@ -200,7 +200,7 @@ def test_diff_show_markdown_and_hash(mocker, show_hash):
|
||||
mock_show_markdown = mocker.patch("dvc.commands.diff._show_markdown")
|
||||
mocker.patch("dvc.repo.Repo.diff", return_value=diff.copy())
|
||||
|
||||
assert 0 == cmd.run()
|
||||
assert cmd.run() == 0
|
||||
mock_show_markdown.assert_called_once_with(diff, show_hash, False)
|
||||
|
||||
|
||||
@@ -226,7 +226,7 @@ def test_no_changes(mocker, capsys, opts, show, expected):
|
||||
cmd = args.func(args)
|
||||
mocker.patch("dvc.repo.Repo.diff", return_value={})
|
||||
|
||||
assert 0 == cmd.run()
|
||||
assert cmd.run() == 0
|
||||
out, _ = capsys.readouterr()
|
||||
assert expected == out.strip()
|
||||
|
||||
@@ -349,7 +349,7 @@ def test_hide_missing(mocker, capsys):
|
||||
}
|
||||
mocker.patch("dvc.repo.Repo.diff", return_value=diff)
|
||||
|
||||
assert 0 == cmd.run()
|
||||
assert cmd.run() == 0
|
||||
out, _ = capsys.readouterr()
|
||||
assert (
|
||||
"Added:\n"
|
||||
|
||||
@@ -23,12 +23,6 @@ def test_run(mocker, dvc):
|
||||
"plots",
|
||||
"--plots-no-cache",
|
||||
"plots-no-cache",
|
||||
"--live",
|
||||
"live",
|
||||
"--live-no-cache",
|
||||
"live-no-cache",
|
||||
"--live-no-summary",
|
||||
"--live-no-html",
|
||||
"--file",
|
||||
"file",
|
||||
"--wdir",
|
||||
@@ -74,10 +68,6 @@ def test_run(mocker, dvc):
|
||||
outs_persist_no_cache=["outs-persist-no-cache"],
|
||||
checkpoints=["checkpoints"],
|
||||
params=["file:param1,param2", "param3"],
|
||||
live="live",
|
||||
live_no_cache="live-no-cache",
|
||||
live_no_summary=True,
|
||||
live_no_html=True,
|
||||
fname="file",
|
||||
wdir="wdir",
|
||||
no_exec=True,
|
||||
@@ -107,10 +97,6 @@ def test_run_args_from_cli(mocker, dvc):
|
||||
metrics_no_cache=[],
|
||||
plots=[],
|
||||
plots_no_cache=[],
|
||||
live=None,
|
||||
live_no_cache=None,
|
||||
live_no_summary=False,
|
||||
live_no_html=False,
|
||||
outs_persist=[],
|
||||
outs_persist_no_cache=[],
|
||||
checkpoints=[],
|
||||
@@ -144,10 +130,6 @@ def test_run_args_with_spaces(mocker, dvc):
|
||||
metrics_no_cache=[],
|
||||
plots=[],
|
||||
plots_no_cache=[],
|
||||
live=None,
|
||||
live_no_cache=None,
|
||||
live_no_summary=False,
|
||||
live_no_html=False,
|
||||
outs_persist=[],
|
||||
outs_persist_no_cache=[],
|
||||
checkpoints=[],
|
||||
|
||||
@@ -35,10 +35,6 @@ def test_stage_add(mocker, dvc, command, parsed_command):
|
||||
"plots",
|
||||
"--plots-no-cache",
|
||||
"plots-no-cache",
|
||||
"--live",
|
||||
"live",
|
||||
"--live-no-summary",
|
||||
"--live-no-html",
|
||||
"--wdir",
|
||||
"wdir",
|
||||
"--force",
|
||||
@@ -80,9 +76,6 @@ def test_stage_add(mocker, dvc, command, parsed_command):
|
||||
metrics_no_cache=["metrics-no-cache"],
|
||||
plots=["plots"],
|
||||
plots_no_cache=["plots-no-cache"],
|
||||
live="live",
|
||||
live_no_summary=True,
|
||||
live_no_html=True,
|
||||
wdir="wdir",
|
||||
outs_persist=["outs-persist"],
|
||||
outs_persist_no_cache=["outs-persist-no-cache"],
|
||||
|
||||
@@ -7,6 +7,6 @@ from dvc.stage import Stage
|
||||
def test_save_missing(dvc, mocker):
|
||||
stage = Stage(dvc)
|
||||
dep = Dependency(stage, "path")
|
||||
with mocker.patch.object(dep.fs, "exists", return_value=False):
|
||||
with pytest.raises(dep.DoesNotExistError):
|
||||
dep.save()
|
||||
mocker.patch.object(dep.fs, "exists", return_value=False)
|
||||
with pytest.raises(dep.DoesNotExistError):
|
||||
dep.save()
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import os
|
||||
from unittest.mock import patch
|
||||
|
||||
from dvc.output import Output
|
||||
from dvc.stage import Stage
|
||||
from dvc.utils import relpath
|
||||
from dvc_data.hashfile.hash_info import HashInfo
|
||||
from dvc_data.hashfile.meta import Meta
|
||||
from tests.basic_env import TestDvc
|
||||
|
||||
|
||||
def test_str_workdir_outside_repo(tmp_dir, erepo_dir):
|
||||
@@ -52,24 +50,21 @@ def test_str_on_external_absolute_path(dvc):
|
||||
assert str(output) == abs_path
|
||||
|
||||
|
||||
class TestGetFilesNumber(TestDvc):
|
||||
def _get_output(self):
|
||||
stage = Stage(self.dvc)
|
||||
return Output(stage, "path")
|
||||
def test_return_0_on_no_cache(dvc):
|
||||
o = Output(Stage(dvc), "path")
|
||||
o.use_cache = False
|
||||
assert o.get_files_number() == 0
|
||||
|
||||
def test_return_0_on_no_cache(self):
|
||||
o = self._get_output()
|
||||
o.use_cache = False
|
||||
self.assertEqual(0, o.get_files_number())
|
||||
|
||||
def test_return_multiple_for_dir(self):
|
||||
o = self._get_output()
|
||||
o.hash_info = HashInfo("md5", "12345678.dir")
|
||||
o.meta = Meta(nfiles=2)
|
||||
self.assertEqual(2, o.get_files_number())
|
||||
def test_return_multiple_for_dir(dvc):
|
||||
o = Output(Stage(dvc), "path")
|
||||
o.hash_info = HashInfo("md5", "12345678.dir")
|
||||
o.meta = Meta(nfiles=2)
|
||||
assert o.get_files_number() == 2
|
||||
|
||||
@patch.object(Output, "is_dir_checksum", False)
|
||||
def test_return_1_on_single_file_cache(self):
|
||||
o = self._get_output()
|
||||
o.hash_info = HashInfo("md5", "12345678")
|
||||
self.assertEqual(1, o.get_files_number())
|
||||
|
||||
def test_return_1_on_single_file_cache(mocker, dvc):
|
||||
mocker.patch.object(Output, "is_dir_checksum", False)
|
||||
o = Output(Stage(dvc), "path")
|
||||
o.hash_info = HashInfo("md5", "12345678")
|
||||
assert o.get_files_number() == 1
|
||||
|
||||
@@ -15,9 +15,9 @@ from dvc.utils.fs import remove
|
||||
def test_save_missing(dvc, mocker):
|
||||
stage = Stage(dvc)
|
||||
out = Output(stage, "path", cache=False)
|
||||
with mocker.patch.object(out.fs, "exists", return_value=False):
|
||||
with pytest.raises(out.DoesNotExistError):
|
||||
out.save()
|
||||
mocker.patch.object(out.fs, "exists", return_value=False)
|
||||
with pytest.raises(out.DoesNotExistError):
|
||||
out.save()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -115,9 +115,9 @@ def test_remote_missing_dependency_on_dir_pull(tmp_dir, scm, dvc, mocker):
|
||||
remove("dir")
|
||||
remove(dvc.odb.local.path)
|
||||
|
||||
with mocker.patch(
|
||||
mocker.patch(
|
||||
"dvc.data_cloud.DataCloud.get_remote_odb",
|
||||
side_effect=RemoteMissingDepsError(dvc.fs, "azure", "azure://", []),
|
||||
):
|
||||
with pytest.raises(RemoteMissingDepsError):
|
||||
dvc.pull()
|
||||
)
|
||||
with pytest.raises(RemoteMissingDepsError):
|
||||
dvc.pull()
|
||||
|
||||
@@ -50,11 +50,11 @@ def test_wait(tmp_dir, mocker):
|
||||
|
||||
proc._proc.wait = mocker.Mock(return_value=None)
|
||||
proc._proc.returncode = 0
|
||||
assert 0 == proc.wait()
|
||||
assert proc.wait() == 0
|
||||
proc._proc.wait.assert_called_once()
|
||||
|
||||
# once subprocess return code is set, future ManagedProcess.wait() calls
|
||||
# should not block
|
||||
proc._proc.wait.reset_mock()
|
||||
assert 0 == proc.wait()
|
||||
assert proc.wait() == 0
|
||||
proc._proc.wait.assert_not_called()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import math
|
||||
import posixpath
|
||||
from unittest import mock
|
||||
|
||||
from dvc.fs import FileSystem
|
||||
from dvc_data.hashfile.db import ObjectDB
|
||||
@@ -16,59 +15,54 @@ class _CallableOrNone:
|
||||
CallableOrNone = _CallableOrNone()
|
||||
|
||||
|
||||
@mock.patch.object(ObjectDB, "_list_oids_traverse")
|
||||
@mock.patch.object(ObjectDB, "list_oids_exists")
|
||||
def test_oids_exist(object_exists, traverse, dvc):
|
||||
def test_oids_exist(mocker, dvc):
|
||||
traverse = mocker.patch.object(ObjectDB, "_list_oids_traverse")
|
||||
object_exists = mocker.patch.object(ObjectDB, "list_oids_exists")
|
||||
odb = ObjectDB(FileSystem(), None)
|
||||
|
||||
# remote does not support traverse
|
||||
odb.fs.CAN_TRAVERSE = False
|
||||
with mock.patch.object(odb, "_list_oids", return_value=list(range(256))):
|
||||
oids = set(range(1000))
|
||||
odb.oids_exist(oids)
|
||||
object_exists.assert_called_with(oids, None)
|
||||
traverse.assert_not_called()
|
||||
mocker.patch.object(odb, "_list_oids", return_value=list(range(256)))
|
||||
oids = set(range(1000))
|
||||
odb.oids_exist(oids)
|
||||
object_exists.assert_called_with(oids, None)
|
||||
traverse.assert_not_called()
|
||||
|
||||
odb.fs.CAN_TRAVERSE = True
|
||||
|
||||
# large remote, small local
|
||||
object_exists.reset_mock()
|
||||
traverse.reset_mock()
|
||||
with mock.patch.object(odb, "_list_oids", return_value=list(range(2048))):
|
||||
oids = list(range(1000))
|
||||
odb.oids_exist(oids)
|
||||
# verify that _odb_paths_with_max() short circuits
|
||||
# before returning all 2048 remote oids
|
||||
max_oids = math.ceil(
|
||||
odb._max_estimation_size(oids)
|
||||
/ pow(16, odb.fs.TRAVERSE_PREFIX_LEN)
|
||||
)
|
||||
assert max_oids < 2048
|
||||
object_exists.assert_called_with(
|
||||
frozenset(range(max_oids, 1000)), None
|
||||
)
|
||||
traverse.assert_not_called()
|
||||
|
||||
mocker.patch.object(odb, "_list_oids", return_value=list(range(2048)))
|
||||
oids = list(range(1000))
|
||||
odb.oids_exist(oids)
|
||||
# verify that _odb_paths_with_max() short circuits
|
||||
# before returning all 2048 remote oids
|
||||
max_oids = math.ceil(
|
||||
odb._max_estimation_size(oids) / pow(16, odb.fs.TRAVERSE_PREFIX_LEN)
|
||||
)
|
||||
assert max_oids < 2048
|
||||
object_exists.assert_called_with(frozenset(range(max_oids, 1000)), None)
|
||||
traverse.assert_not_called()
|
||||
|
||||
# large remote, large local
|
||||
object_exists.reset_mock()
|
||||
traverse.reset_mock()
|
||||
odb.fs._JOBS = 16
|
||||
with mock.patch.object(odb, "_list_oids", return_value=list(range(256))):
|
||||
oids = list(range(1000000))
|
||||
odb.oids_exist(oids)
|
||||
object_exists.assert_not_called()
|
||||
traverse.assert_called_with(
|
||||
256 * pow(16, odb.fs.TRAVERSE_PREFIX_LEN),
|
||||
set(range(256)),
|
||||
jobs=None,
|
||||
)
|
||||
mocker.patch.object(odb, "_list_oids", return_value=list(range(256)))
|
||||
oids = list(range(1000000))
|
||||
odb.oids_exist(oids)
|
||||
object_exists.assert_not_called()
|
||||
traverse.assert_called_with(
|
||||
256 * pow(16, odb.fs.TRAVERSE_PREFIX_LEN),
|
||||
set(range(256)),
|
||||
jobs=None,
|
||||
)
|
||||
|
||||
|
||||
@mock.patch.object(ObjectDB, "_list_oids", return_value=[])
|
||||
@mock.patch.object(ObjectDB, "path_to_oid", side_effect=lambda x: x)
|
||||
def test_list_oids_traverse(
|
||||
path_to_oid, list_oids, dvc # pylint: disable=unused-argument
|
||||
):
|
||||
def test_list_oids_traverse(mocker, dvc): # pylint: disable=unused-argument
|
||||
list_oids = mocker.patch.object(ObjectDB, "_list_oids", return_value=[])
|
||||
odb = ObjectDB(FileSystem(), None)
|
||||
odb.fs_path = "foo"
|
||||
|
||||
@@ -87,28 +81,24 @@ def test_list_oids_traverse(
|
||||
list_oids.assert_called_with(None)
|
||||
|
||||
|
||||
def test_list_oids(dvc):
|
||||
def test_list_oids(mocker, dvc):
|
||||
odb = ObjectDB(FileSystem(), None)
|
||||
odb.fs_path = "foo"
|
||||
|
||||
with mock.patch.object(
|
||||
odb, "_list_paths", return_value=["12/3456", "bar"]
|
||||
):
|
||||
oids = list(odb._list_oids())
|
||||
assert oids == ["123456"]
|
||||
mocker.patch.object(odb, "_list_paths", return_value=["12/3456", "bar"])
|
||||
oids = list(odb._list_oids())
|
||||
assert oids == ["123456"]
|
||||
|
||||
|
||||
def test_list_paths(dvc):
|
||||
def test_list_paths(mocker, dvc):
|
||||
path = "foo"
|
||||
odb = ObjectDB(FileSystem(), path)
|
||||
|
||||
with mock.patch.object(odb.fs, "find", return_value=[]) as walk_mock:
|
||||
for _ in odb._list_paths():
|
||||
pass
|
||||
walk_mock.assert_called_with(path, prefix=False)
|
||||
walk_mock = mocker.patch.object(odb.fs, "find", return_value=[])
|
||||
for _ in odb._list_paths():
|
||||
pass
|
||||
walk_mock.assert_called_with(path, prefix=False)
|
||||
|
||||
for _ in odb._list_paths(prefix="000"):
|
||||
pass
|
||||
walk_mock.assert_called_with(
|
||||
posixpath.join(path, "00", "0"), prefix=True
|
||||
)
|
||||
for _ in odb._list_paths(prefix="000"):
|
||||
pass
|
||||
walk_mock.assert_called_with(posixpath.join(path, "00", "0"), prefix=True)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import ssl
|
||||
from unittest.mock import patch
|
||||
|
||||
from dvc.fs import HTTPFileSystem
|
||||
|
||||
@@ -64,8 +63,8 @@ def test_ssl_verify_disable(dvc):
|
||||
assert not fs.fs_args["client_kwargs"]["connector"]._ssl
|
||||
|
||||
|
||||
@patch("ssl.SSLContext.load_verify_locations")
|
||||
def test_ssl_verify_custom_cert(dvc, mocker):
|
||||
mocker.patch("ssl.SSLContext.load_verify_locations")
|
||||
config = {
|
||||
"url": "http://example.com/",
|
||||
"path": "file.html",
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from dvc_webdav import WebDAVFileSystem, WebDAVSFileSystem
|
||||
|
||||
@@ -66,9 +64,10 @@ def test_token():
|
||||
)
|
||||
|
||||
|
||||
@patch("dvc_webdav.ask_password")
|
||||
def test_ask_password(ask_password_mocked):
|
||||
ask_password_mocked.return_value = "pass"
|
||||
def test_ask_password(mocker):
|
||||
ask_password_mocked = mocker.patch(
|
||||
"dvc_webdav.ask_password", return_value="pass"
|
||||
)
|
||||
host = "host"
|
||||
|
||||
# it should not ask for password as password is set
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from unittest.mock import Mock, create_autospec
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
from dvc_webhdfs import WebHDFSFileSystem
|
||||
@@ -42,9 +40,11 @@ def test_init(dvc, webhdfs_config):
|
||||
assert fs.fs_args["use_https"] == use_https
|
||||
|
||||
|
||||
def test_verify_ssl(dvc, webhdfs_config, monkeypatch):
|
||||
mock_session = create_autospec(requests.Session)
|
||||
monkeypatch.setattr(requests, "Session", Mock(return_value=mock_session))
|
||||
def test_verify_ssl(dvc, webhdfs_config, monkeypatch, mocker):
|
||||
mock_session = mocker.create_autospec(requests.Session)
|
||||
monkeypatch.setattr(
|
||||
requests, "Session", mocker.Mock(return_value=mock_session)
|
||||
)
|
||||
# can't have token at the same time as user or proxy_to
|
||||
del webhdfs_config["token"]
|
||||
fs = WebHDFSFileSystem(**webhdfs_config)
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
from contextlib import contextmanager
|
||||
from functools import partial
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
|
||||
from dvc.repo.experiments.queue.celery import LocalCeleryQueue
|
||||
from tests.func.test_repro_multistage import COPY_SCRIPT
|
||||
|
||||
DEFAULT_ITERATIONS = 2
|
||||
CHECKPOINT_SCRIPT_FORMAT = dedent(
|
||||
@@ -44,8 +43,7 @@ CHECKPOINT_SCRIPT = CHECKPOINT_SCRIPT_FORMAT.format(
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def exp_stage(tmp_dir, scm, dvc):
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
def exp_stage(tmp_dir, scm, dvc, copy_script):
|
||||
tmp_dir.gen("params.yaml", "foo: 1")
|
||||
stage = dvc.run(
|
||||
cmd="python copy.py params.yaml metrics.yaml",
|
||||
@@ -93,8 +91,7 @@ def checkpoint_stage(tmp_dir, scm, dvc, mocker):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def failed_exp_stage(tmp_dir, scm, dvc):
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
def failed_exp_stage(tmp_dir, scm, dvc, copy_script):
|
||||
tmp_dir.gen("params.yaml", "foo: 1")
|
||||
stage = dvc.stage.add(
|
||||
cmd="python -c 'import sys; sys.exit(1)'",
|
||||
@@ -117,15 +114,13 @@ def failed_exp_stage(tmp_dir, scm, dvc):
|
||||
return stage
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _thread_worker(app, **kwargs):
|
||||
# Based on pytest-celery's celery_worker fixture but using thread pool
|
||||
# instead of solo pool so that broadcast/control API is available
|
||||
from celery.contrib.testing import worker
|
||||
|
||||
app.loader.import_task_module("celery.contrib.testing.tasks")
|
||||
with worker.start_worker(app, pool="threads", **kwargs) as test_worker:
|
||||
yield test_worker
|
||||
return worker.start_worker(app, pool="threads", **kwargs)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -134,12 +129,26 @@ def test_queue(tmp_dir, dvc, scm, mocker) -> LocalCeleryQueue:
|
||||
|
||||
Test queue worker runs for the duration of the test in separate thread(s).
|
||||
"""
|
||||
celery_queue = dvc.experiments.celery_queue
|
||||
mocker.patch.object(celery_queue, "spawn_worker")
|
||||
with _thread_worker(
|
||||
celery_queue.celery,
|
||||
import celery
|
||||
|
||||
queue = dvc.experiments.celery_queue
|
||||
mocker.patch.object(queue, "spawn_worker")
|
||||
|
||||
f = partial(
|
||||
_thread_worker,
|
||||
queue.celery,
|
||||
concurrency=1,
|
||||
ping_task_timeout=30,
|
||||
) as worker:
|
||||
mocker.patch.object(celery_queue, "worker", return_value=worker)
|
||||
yield celery_queue
|
||||
ping_task_timeout=20,
|
||||
)
|
||||
exc = None
|
||||
for _ in range(3):
|
||||
try:
|
||||
with f() as worker:
|
||||
mocker.patch.object(queue, "worker", return_value=worker)
|
||||
yield queue
|
||||
return
|
||||
except celery.exceptions.TimeoutError as e:
|
||||
exc = e
|
||||
continue
|
||||
assert exc
|
||||
raise exc
|
||||
|
||||
@@ -180,14 +180,14 @@ def test_shared_stage_cache(tmp_dir, dvc, run_copy):
|
||||
|
||||
run_copy("foo", "bar", name="copy-foo-bar")
|
||||
|
||||
parent_cache_dir = os.path.join(dvc.stage_cache.cache_dir, "88")
|
||||
parent_cache_dir = os.path.join(dvc.stage_cache.cache_dir, "fd")
|
||||
cache_dir = os.path.join(
|
||||
parent_cache_dir,
|
||||
"883395068439203a9de3d1e1649a16e9027bfd1ab5dab4f438d321c4a928b328",
|
||||
"fdbe7847136ebe88f59a29ee5a568f893cab031f74f7d3ab050828b53fd0033a",
|
||||
)
|
||||
cache_file = os.path.join(
|
||||
cache_dir,
|
||||
"e42b7ebb9bc5ac4bccab769c8d1338914dad25d7ffecc8671dbd4581bad4aa15",
|
||||
"8716052b2074f5acf1a379529d47d6ef1c1f50c2281a7489b8d7ce251f234f86",
|
||||
)
|
||||
|
||||
# sanity check
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import json
|
||||
import platform
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from voluptuous import Any, Schema
|
||||
@@ -57,8 +56,9 @@ def test_runtime_info(tmp_global_dir):
|
||||
assert schema(analytics._runtime_info())
|
||||
|
||||
|
||||
@mock.patch("requests.post")
|
||||
def test_send(mock_post, tmp_path):
|
||||
def test_send(mocker, tmp_path):
|
||||
mock_post = mocker.patch("requests.post")
|
||||
|
||||
import requests
|
||||
|
||||
url = "https://analytics.dvc.org"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user