dvc-utils 0.3.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,74 @@
1
+ name: Verify README examples, release to PyPI
2
+ on:
3
+ push:
4
+ branches: [ "main" ]
5
+ tags: [ "v**" ]
6
+ pull_request:
7
+ branches: [ "main" ]
8
+ workflow_dispatch:
9
+ env:
10
+ AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
11
+ AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
12
+ jobs:
13
+ test:
14
+ name: Verify README examples
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ with:
19
+ fetch-depth: 0
20
+ submodules: true
21
+ - uses: astral-sh/setup-uv@v5
22
+ with:
23
+ enable-cache: true
24
+ - name: Set up Python 3.11
25
+ run: uv python install 3.11
26
+ - uses: dtolnay/rust-toolchain@stable
27
+ - uses: Swatinem/rust-cache@v2
28
+ - run: cargo install parquet2json
29
+ - name: Install dependencies
30
+ run: uv sync --extra ci --extra test
31
+ - name: Run pytest
32
+ run: |
33
+ source .venv/bin/activate
34
+ pytest
35
+ - name: '`dvc pull` test/data'
36
+ working-directory: test/data
37
+ run: |
38
+ source ../../.venv/bin/activate
39
+ dvc pull -r s3 -R -A
40
+ - name: Set up parquet-helpers
41
+ uses: actions/checkout@v4
42
+ with:
43
+ repository: ryan-williams/parquet-helpers
44
+ path: pqt
45
+ - name: Verify README examples
46
+ env:
47
+ # Evaluate README examples from within the `test/data` submodule
48
+ BMDF_WORKDIR: test/data
49
+ run: |
50
+ source .venv/bin/activate
51
+ export PATH="$PWD/pqt:$PATH"
52
+ . pqt/.pqt-rc
53
+ export SHELL
54
+ mdcmd
55
+ git diff --exit-code
56
+ release:
57
+ name: Release to PyPI
58
+ if: startsWith(github.ref, 'refs/tags/')
59
+ needs: test
60
+ runs-on: ubuntu-latest
61
+ steps:
62
+ - uses: actions/checkout@v4
63
+ - uses: astral-sh/setup-uv@v5
64
+ - name: Build package
65
+ run: uv build
66
+ - name: Publish to PyPI
67
+ run: uv publish --username __token__ --password ${{ secrets.PYPI_TOKEN }}
68
+ - name: Create GitHub Release
69
+ env:
70
+ GH_TOKEN: ${{ github.token }}
71
+ run: |
72
+ gh release create ${{ github.ref_name }} \
73
+ --title "${{ github.ref_name }}" \
74
+ --generate-notes
@@ -0,0 +1,3 @@
1
+ [submodule "test/data"]
2
+ path = test/data
3
+ url = https://github.com/ryan-williams/dvc-helpers
@@ -1,22 +1,23 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dvc-utils
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: CLI for diffing DVC-tracked files at two commits (or one commit vs. current worktree), optionally passing both through another command first
5
- Author-email: Ryan Williams <ryan@runsascoded.com>
6
- License: MIT
7
5
  Project-URL: Homepage, https://github.com/runsascoded/dvc-utils
8
6
  Project-URL: Author URL, https://github.com/ryan-williams
9
- Requires-Python: >=3.9
10
- Description-Content-Type: text/markdown
7
+ Author-email: Ryan Williams <ryan@runsascoded.com>
8
+ License: MIT
11
9
  License-File: LICENSE
10
+ Requires-Python: >=3.9
12
11
  Requires-Dist: click
13
- Requires-Dist: dffs>=0.0.5
12
+ Requires-Dist: dffs>=0.0.6
14
13
  Requires-Dist: pyyaml
15
14
  Requires-Dist: utz>=0.20.0
16
15
  Provides-Extra: ci
17
- Requires-Dist: bmdf==0.5.2; extra == "ci"
18
- Requires-Dist: dvc-s3; extra == "ci"
19
- Dynamic: license-file
16
+ Requires-Dist: bmdf==0.5.2; extra == 'ci'
17
+ Requires-Dist: dvc-s3; extra == 'ci'
18
+ Provides-Extra: test
19
+ Requires-Dist: pytest>=7.0.0; extra == 'test'
20
+ Description-Content-Type: text/markdown
20
21
 
21
22
  # dvc-utils
22
23
  Diff [DVC] files, optionally piping through other commands first.
@@ -1,10 +1,10 @@
1
1
  [build-system]
2
- requires = ["setuptools>=75"]
3
- build-backend = "setuptools.build_meta"
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "dvc-utils"
7
- version = "0.3.0"
7
+ version = "0.3.1"
8
8
  description = "CLI for diffing DVC-tracked files at two commits (or one commit vs. current worktree), optionally passing both through another command first"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -14,7 +14,7 @@ authors = [
14
14
  requires-python = ">=3.9"
15
15
  dependencies = [
16
16
  "click",
17
- "dffs>=0.0.5",
17
+ "dffs>=0.0.6",
18
18
  "pyyaml",
19
19
  "utz>=0.20.0",
20
20
  ]
@@ -24,6 +24,9 @@ ci = [
24
24
  "bmdf==0.5.2",
25
25
  "dvc-s3",
26
26
  ]
27
+ test = [
28
+ "pytest>=7.0.0",
29
+ ]
27
30
 
28
31
  [project.urls]
29
32
  Homepage = "https://github.com/runsascoded/dvc-utils"
@@ -33,8 +36,10 @@ Homepage = "https://github.com/runsascoded/dvc-utils"
33
36
  dvc-utils = "dvc_utils.main:main"
34
37
  dvc-diff = "dvc_utils.diff:dvc_diff"
35
38
 
36
- [tool.setuptools]
37
- package-dir = {"" = "src"}
39
+ [dependency-groups]
40
+ dev = [
41
+ "pytest>=7.0.0",
42
+ ]
38
43
 
39
- [tool.setuptools.packages.find]
40
- where = ["src"]
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["src/dvc_utils"]
@@ -0,0 +1,6 @@
1
+ [pytest]
2
+ testpaths = tests
3
+ python_files = test_*.py
4
+ python_classes = Test*
5
+ python_functions = test_*
6
+ addopts = -v --tb=short
@@ -111,7 +111,7 @@ def dvc_diff(
111
111
  cmds1 = [ shlex.split(cmd) for cmd in cmds1 ]
112
112
  cmds2 = [ shlex.split(cmd) for cmd in cmds2 ]
113
113
 
114
- join_pipelines(
114
+ returncode = join_pipelines(
115
115
  base_cmd=['diff', *diff_args],
116
116
  cmds1=cmds1,
117
117
  cmds2=cmds2,
@@ -119,6 +119,7 @@ def dvc_diff(
119
119
  shell=shell,
120
120
  executable=shell_executable,
121
121
  )
122
+ exit(returncode)
122
123
  else:
123
124
  res = process.run('diff', *diff_args, path1 or '/dev/null', path2 or '/dev/null', log=log, check=False)
124
125
  exit(res.returncode)
File without changes
@@ -0,0 +1,83 @@
1
+ """Tests for dvc-diff exit code handling."""
2
+ import pytest
3
+ import subprocess
4
+ from pathlib import Path
5
+ import tempfile
6
+
7
+
8
+ class TestDiffExitCodes:
9
+ """Test that dvc-diff properly propagates exit codes from pipeline commands."""
10
+
11
+ def test_successful_pipeline_returns_zero(self, tmp_path):
12
+ """Test that successful identical pipeline returns 0."""
13
+ # Create test files
14
+ file1 = tmp_path / "test1.txt"
15
+ file2 = tmp_path / "test2.txt"
16
+ file1.write_text("foo\nbar\n")
17
+ file2.write_text("foo\nbar\n")
18
+
19
+ # Run diff-x (not dvc-diff, but tests the same join_pipelines code)
20
+ result = subprocess.run(
21
+ ["diff-x", "cat", str(file1), str(file2)],
22
+ capture_output=True,
23
+ )
24
+ assert result.returncode == 0
25
+
26
+ def test_diff_found_returns_one(self, tmp_path):
27
+ """Test that differences found returns 1."""
28
+ file1 = tmp_path / "test1.txt"
29
+ file2 = tmp_path / "test2.txt"
30
+ file1.write_text("foo\n")
31
+ file2.write_text("bar\n")
32
+
33
+ result = subprocess.run(
34
+ ["diff-x", "cat", str(file1), str(file2)],
35
+ capture_output=True,
36
+ )
37
+ assert result.returncode == 1
38
+
39
+ def test_pipeline_error_propagates(self, tmp_path):
40
+ """Test that pipeline command errors propagate to exit code."""
41
+ file1 = tmp_path / "test1.txt"
42
+ file2 = tmp_path / "test2.txt"
43
+ file1.write_text("foo\n")
44
+ file2.write_text("bar\n")
45
+
46
+ # Use a command that will fail
47
+ result = subprocess.run(
48
+ ["diff-x", "cat /nonexistent/file/that/does/not/exist ||", str(file1), str(file2)],
49
+ capture_output=True,
50
+ shell=False,
51
+ )
52
+ # Should return non-zero due to cat failing
53
+ assert result.returncode != 0
54
+
55
+ def test_false_command_propagates_error(self, tmp_path):
56
+ """Test that 'false' command in pipeline propagates error."""
57
+ file1 = tmp_path / "test1.txt"
58
+ file2 = tmp_path / "test2.txt"
59
+ file1.write_text("foo\n")
60
+ file2.write_text("bar\n")
61
+
62
+ # Use 'false' which always returns 1
63
+ result = subprocess.run(
64
+ ["diff-x", "cat", "false", str(file1), str(file2)],
65
+ capture_output=True,
66
+ )
67
+ # Should return non-zero due to false in pipeline
68
+ assert result.returncode != 0
69
+
70
+ def test_multi_stage_pipeline_error(self, tmp_path):
71
+ """Test that errors in multi-stage pipelines are detected."""
72
+ file1 = tmp_path / "test1.txt"
73
+ file2 = tmp_path / "test2.txt"
74
+ file1.write_text("foo\nbar\n")
75
+ file2.write_text("bar\nfoo\n")
76
+
77
+ # Pipeline: sort (succeeds) | false (fails)
78
+ result = subprocess.run(
79
+ ["diff-x", "-x", "sort", "-x", "false", str(file1), str(file2)],
80
+ capture_output=True,
81
+ )
82
+ # Should return non-zero due to false in pipeline
83
+ assert result.returncode != 0