batchvault 0.1.0__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.
Files changed (95) hide show
  1. batchvault-0.1.0/.dockerignore +20 -0
  2. batchvault-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +38 -0
  3. batchvault-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  4. batchvault-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +14 -0
  5. batchvault-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +10 -0
  6. batchvault-0.1.0/.github/dependabot.yml +33 -0
  7. batchvault-0.1.0/.github/workflows/docker.yml +77 -0
  8. batchvault-0.1.0/.github/workflows/e2e.yml +338 -0
  9. batchvault-0.1.0/.github/workflows/lint.yml +27 -0
  10. batchvault-0.1.0/.github/workflows/release.yml +79 -0
  11. batchvault-0.1.0/.github/workflows/tests.yml +45 -0
  12. batchvault-0.1.0/.gitignore +223 -0
  13. batchvault-0.1.0/.pre-commit-config.yaml +17 -0
  14. batchvault-0.1.0/.python-version +1 -0
  15. batchvault-0.1.0/CHANGELOG.md +26 -0
  16. batchvault-0.1.0/CODE_OF_CONDUCT.md +15 -0
  17. batchvault-0.1.0/CONTRIBUTING.md +34 -0
  18. batchvault-0.1.0/Dockerfile +35 -0
  19. batchvault-0.1.0/LICENSE +21 -0
  20. batchvault-0.1.0/PKG-INFO +306 -0
  21. batchvault-0.1.0/README.md +273 -0
  22. batchvault-0.1.0/SECURITY.md +51 -0
  23. batchvault-0.1.0/action.yml +255 -0
  24. batchvault-0.1.0/docs/architecture.md +164 -0
  25. batchvault-0.1.0/docs/cli.md +361 -0
  26. batchvault-0.1.0/docs/compression.md +199 -0
  27. batchvault-0.1.0/docs/configuration.md +75 -0
  28. batchvault-0.1.0/docs/development.md +30 -0
  29. batchvault-0.1.0/docs/docker.md +84 -0
  30. batchvault-0.1.0/docs/github-action.md +138 -0
  31. batchvault-0.1.0/docs/python.md +173 -0
  32. batchvault-0.1.0/docs/testing.md +99 -0
  33. batchvault-0.1.0/pyproject.toml +53 -0
  34. batchvault-0.1.0/src/batchvault/__init__.py +61 -0
  35. batchvault-0.1.0/src/batchvault/__main__.py +3 -0
  36. batchvault-0.1.0/src/batchvault/api.py +884 -0
  37. batchvault-0.1.0/src/batchvault/blocks.py +176 -0
  38. batchvault-0.1.0/src/batchvault/cli.py +806 -0
  39. batchvault-0.1.0/src/batchvault/config.py +169 -0
  40. batchvault-0.1.0/src/batchvault/crypto.py +193 -0
  41. batchvault-0.1.0/src/batchvault/doctor.py +67 -0
  42. batchvault-0.1.0/src/batchvault/engines/__init__.py +0 -0
  43. batchvault-0.1.0/src/batchvault/engines/base.py +57 -0
  44. batchvault-0.1.0/src/batchvault/engines/native.py +237 -0
  45. batchvault-0.1.0/src/batchvault/engines/registry.py +61 -0
  46. batchvault-0.1.0/src/batchvault/engines/sevenzip.py +303 -0
  47. batchvault-0.1.0/src/batchvault/errors.py +38 -0
  48. batchvault-0.1.0/src/batchvault/eta.py +39 -0
  49. batchvault-0.1.0/src/batchvault/filters.py +51 -0
  50. batchvault-0.1.0/src/batchvault/inputs.py +226 -0
  51. batchvault-0.1.0/src/batchvault/levels.py +169 -0
  52. batchvault-0.1.0/src/batchvault/locate.py +140 -0
  53. batchvault-0.1.0/src/batchvault/logs.py +86 -0
  54. batchvault-0.1.0/src/batchvault/manifest.py +210 -0
  55. batchvault-0.1.0/src/batchvault/models.py +164 -0
  56. batchvault-0.1.0/src/batchvault/names.py +36 -0
  57. batchvault-0.1.0/src/batchvault/notify.py +73 -0
  58. batchvault-0.1.0/src/batchvault/options.py +71 -0
  59. batchvault-0.1.0/src/batchvault/py.typed +0 -0
  60. batchvault-0.1.0/src/batchvault/security.py +40 -0
  61. batchvault-0.1.0/src/batchvault/signing.py +152 -0
  62. batchvault-0.1.0/src/batchvault/ui.py +298 -0
  63. batchvault-0.1.0/src/batchvault/volumes.py +201 -0
  64. batchvault-0.1.0/tests/conftest.py +19 -0
  65. batchvault-0.1.0/tests/e2e/conftest.py +38 -0
  66. batchvault-0.1.0/tests/e2e/scripts/compare.py +48 -0
  67. batchvault-0.1.0/tests/e2e/scripts/make_data.py +37 -0
  68. batchvault-0.1.0/tests/e2e/test_cli_e2e.py +335 -0
  69. batchvault-0.1.0/tests/e2e/test_docker_e2e.py +199 -0
  70. batchvault-0.1.0/tests/e2e/test_package_e2e.py +240 -0
  71. batchvault-0.1.0/tests/integration/test_sevenzip_api.py +154 -0
  72. batchvault-0.1.0/tests/integration/test_sevenzip_roundtrip.py +70 -0
  73. batchvault-0.1.0/tests/test_api.py +249 -0
  74. batchvault-0.1.0/tests/test_cli.py +170 -0
  75. batchvault-0.1.0/tests/test_cli_features.py +177 -0
  76. batchvault-0.1.0/tests/test_config.py +163 -0
  77. batchvault-0.1.0/tests/test_crypto.py +145 -0
  78. batchvault-0.1.0/tests/test_eta.py +30 -0
  79. batchvault-0.1.0/tests/test_features.py +265 -0
  80. batchvault-0.1.0/tests/test_filters.py +31 -0
  81. batchvault-0.1.0/tests/test_folders.py +318 -0
  82. batchvault-0.1.0/tests/test_hardening.py +320 -0
  83. batchvault-0.1.0/tests/test_inputs.py +135 -0
  84. batchvault-0.1.0/tests/test_levels.py +266 -0
  85. batchvault-0.1.0/tests/test_logs.py +30 -0
  86. batchvault-0.1.0/tests/test_manifest.py +87 -0
  87. batchvault-0.1.0/tests/test_native.py +209 -0
  88. batchvault-0.1.0/tests/test_notify.py +71 -0
  89. batchvault-0.1.0/tests/test_options.py +37 -0
  90. batchvault-0.1.0/tests/test_sevenzip.py +101 -0
  91. batchvault-0.1.0/tests/test_signing.py +148 -0
  92. batchvault-0.1.0/tests/test_summary.py +198 -0
  93. batchvault-0.1.0/tests/test_volume_io.py +46 -0
  94. batchvault-0.1.0/tests/test_volumes.py +56 -0
  95. batchvault-0.1.0/uv.lock +523 -0
@@ -0,0 +1,20 @@
1
+ .git
2
+ .github
3
+ .venv
4
+ .temp
5
+ .pytest_cache
6
+ .ruff_cache
7
+ **/__pycache__
8
+ dist
9
+ tests
10
+ docs
11
+ .env
12
+ .env.*
13
+ *.key
14
+ *.pem
15
+ *.pfx
16
+ *.p12
17
+ *.sig
18
+ manifest.json
19
+ archives
20
+ restored
@@ -0,0 +1,38 @@
1
+ name: Bug report
2
+ description: Something does not work as expected
3
+ labels: [bug]
4
+ body:
5
+ - type: textarea
6
+ id: what
7
+ attributes:
8
+ label: What happened?
9
+ description: What did you run, what did you expect, and what happened instead?
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: command
14
+ attributes:
15
+ label: Command or code
16
+ render: shell
17
+ - type: input
18
+ id: version
19
+ attributes:
20
+ label: BatchVault version
21
+ description: Output of `batchvault --version`
22
+ validations:
23
+ required: true
24
+ - type: dropdown
25
+ id: engine
26
+ attributes:
27
+ label: Engine
28
+ options: [auto, native, 7zip]
29
+ - type: input
30
+ id: platform
31
+ attributes:
32
+ label: OS and Python version
33
+ - type: textarea
34
+ id: log
35
+ attributes:
36
+ label: Log or error output
37
+ description: Run `batchvault doctor` and attach the relevant lines of the log file. Remove anything private.
38
+ render: text
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Security vulnerability
4
+ url: https://github.com/coldsofttech/BatchVault/security/advisories/new
5
+ about: Please report security problems privately.
@@ -0,0 +1,14 @@
1
+ name: Feature request
2
+ description: Suggest an improvement
3
+ labels: [enhancement]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: What are you trying to do?
9
+ validations:
10
+ required: true
11
+ - type: textarea
12
+ id: idea
13
+ attributes:
14
+ label: What would you like BatchVault to do?
@@ -0,0 +1,10 @@
1
+ ## What changed
2
+
3
+ <!-- What does this do, and why? -->
4
+
5
+ ## Checklist
6
+
7
+ - [ ] Tests added or updated
8
+ - [ ] `uv run ruff check .` and `uv run pytest -m "not integration and not e2e"` pass
9
+ - [ ] `CHANGELOG.md` has a short `[Unreleased]` entry
10
+ - [ ] README or `docs/` updated if behaviour changed
@@ -0,0 +1,33 @@
1
+ version: 2
2
+
3
+ updates:
4
+ # Python dependencies and the uv.lock file
5
+ - package-ecosystem: "uv"
6
+ directory: "/"
7
+ schedule:
8
+ interval: "weekly"
9
+ commit-message:
10
+ prefix: "chore(deps)"
11
+ groups:
12
+ python-dependencies:
13
+ patterns: ["*"]
14
+ update-types: ["minor", "patch"]
15
+
16
+ # Actions used in .github/workflows
17
+ - package-ecosystem: "github-actions"
18
+ directory: "/"
19
+ schedule:
20
+ interval: "weekly"
21
+ commit-message:
22
+ prefix: "chore(ci)"
23
+ groups:
24
+ github-actions:
25
+ patterns: ["*"]
26
+
27
+ # Hook versions in .pre-commit-config.yaml
28
+ - package-ecosystem: "pre-commit"
29
+ directory: "/"
30
+ schedule:
31
+ interval: "weekly"
32
+ commit-message:
33
+ prefix: "chore(deps)"
@@ -0,0 +1,77 @@
1
+ name: Docker
2
+
3
+ # Builds the image on every pull request and publishes both variants to GHCR
4
+ # when a version tag such as v0.2.0 is pushed.
5
+ on:
6
+ push:
7
+ tags: ["v*.*.*"]
8
+ pull_request:
9
+ paths:
10
+ - Dockerfile
11
+ - .dockerignore
12
+ - pyproject.toml
13
+ - src/**
14
+ - .github/workflows/docker.yml
15
+
16
+ concurrency:
17
+ group: docker-${{ github.ref }}
18
+ cancel-in-progress: true
19
+
20
+ permissions:
21
+ contents: read
22
+
23
+ jobs:
24
+ image:
25
+ name: Image (${{ matrix.variant }})
26
+ runs-on: ubuntu-latest
27
+ permissions:
28
+ contents: read
29
+ packages: write
30
+ strategy:
31
+ fail-fast: false
32
+ matrix:
33
+ include:
34
+ - { variant: native, with_7zip: "false", suffix: "" }
35
+ - { variant: 7zip, with_7zip: "true", suffix: "-7zip" }
36
+
37
+ steps:
38
+ - uses: actions/checkout@v7
39
+
40
+ - uses: docker/setup-qemu-action@v3
41
+
42
+ - uses: docker/setup-buildx-action@v3
43
+
44
+ - name: Log in to GHCR
45
+ if: startsWith(github.ref, 'refs/tags/')
46
+ uses: docker/login-action@v3
47
+ with:
48
+ registry: ghcr.io
49
+ username: ${{ github.actor }}
50
+ password: ${{ secrets.GITHUB_TOKEN }}
51
+
52
+ - name: Tags
53
+ id: meta
54
+ uses: docker/metadata-action@v5
55
+ with:
56
+ images: ghcr.io/${{ github.repository_owner }}/batchvault
57
+ flavor: |
58
+ latest=false
59
+ suffix=${{ matrix.suffix }}
60
+ tags: |
61
+ type=semver,pattern={{version}}
62
+ type=semver,pattern={{major}}.{{minor}}
63
+ type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') }}
64
+
65
+ - name: Build${{ startsWith(github.ref, 'refs/tags/') && ' and push' || '' }}
66
+ uses: docker/build-push-action@v6
67
+ with:
68
+ context: .
69
+ platforms: linux/amd64,linux/arm64
70
+ build-args: WITH_7ZIP=${{ matrix.with_7zip }}
71
+ push: ${{ startsWith(github.ref, 'refs/tags/') }}
72
+ provenance: mode=max
73
+ sbom: true
74
+ tags: ${{ steps.meta.outputs.tags }}
75
+ labels: ${{ steps.meta.outputs.labels }}
76
+ cache-from: type=gha,scope=${{ matrix.variant }}
77
+ cache-to: type=gha,mode=max,scope=${{ matrix.variant }}
@@ -0,0 +1,338 @@
1
+ name: End-to-end
2
+
3
+ # Runs the real program the way users do: the command line and the built package
4
+ # (tests/e2e), the GitHub Action (this repository's action.yml, used as "./"),
5
+ # and the Docker images. Also run before every release (see release.yml).
6
+ on:
7
+ workflow_call:
8
+ workflow_dispatch:
9
+ push:
10
+ branches: [main]
11
+ pull_request:
12
+ paths:
13
+ - src/**
14
+ - tests/e2e/**
15
+ - action.yml
16
+ - Dockerfile
17
+ - .dockerignore
18
+ - pyproject.toml
19
+ - uv.lock
20
+ - .github/workflows/e2e.yml
21
+ schedule:
22
+ - cron: "0 4 * * 1" # Mondays; catches changes in runners, pip and Docker
23
+
24
+ concurrency:
25
+ group: e2e-${{ github.ref }}
26
+ cancel-in-progress: true
27
+
28
+ permissions:
29
+ contents: read
30
+
31
+ jobs:
32
+ cli-and-package:
33
+ name: CLI and package / ${{ matrix.os }} / Python ${{ matrix.python }}
34
+ runs-on: ${{ matrix.os }}
35
+ # Same matrix as tests.yml. Python 3.15 may still be a pre-release; don't
36
+ # let it block the build.
37
+ continue-on-error: ${{ matrix.python == '3.15' }}
38
+ strategy:
39
+ fail-fast: false
40
+ matrix:
41
+ include:
42
+ - { os: ubuntu-latest, python: "3.11" }
43
+ - { os: ubuntu-latest, python: "3.12" }
44
+ - { os: ubuntu-latest, python: "3.13" }
45
+ - { os: ubuntu-latest, python: "3.14" }
46
+ - { os: ubuntu-latest, python: "3.15" }
47
+ - { os: windows-latest, python: "3.11" }
48
+ - { os: macos-latest, python: "3.11" }
49
+ steps:
50
+ - uses: actions/checkout@v7
51
+
52
+ - uses: astral-sh/setup-uv@v7
53
+
54
+ - name: Install
55
+ run: uv sync --python ${{ matrix.python }}
56
+
57
+ # No -n: the package tests build a wheel and virtual environments once.
58
+ - name: CLI and package tests
59
+ run: uv run pytest -m "e2e and not docker" -v
60
+
61
+ docker:
62
+ name: Docker images
63
+ runs-on: ubuntu-latest
64
+ steps:
65
+ - uses: actions/checkout@v7
66
+
67
+ - uses: astral-sh/setup-uv@v7
68
+
69
+ - name: Install
70
+ run: uv sync --python 3.11
71
+
72
+ - name: Docker tests (builds both images)
73
+ run: uv run pytest -m docker -v
74
+
75
+ action:
76
+ name: GitHub Action / ${{ matrix.os }}
77
+ runs-on: ${{ matrix.os }}
78
+ strategy:
79
+ fail-fast: false
80
+ matrix:
81
+ os: [ubuntu-latest, windows-latest, macos-latest]
82
+ defaults:
83
+ run:
84
+ shell: bash
85
+ env:
86
+ PYTHONUTF8: "1"
87
+ steps:
88
+ - uses: actions/checkout@v7
89
+
90
+ - uses: actions/setup-python@v5
91
+ with:
92
+ python-version: "3.12"
93
+
94
+ # Only used by the steps below that create keys or edit files; the action
95
+ # installs its own copy.
96
+ - name: Install BatchVault for helper steps
97
+ run: python -m pip install --quiet ".[crypto]"
98
+
99
+ - name: Create test data
100
+ run: python tests/e2e/scripts/make_data.py data
101
+
102
+ # --- pack, verify, unpack -------------------------------------------
103
+ - name: Pack
104
+ id: pack
105
+ uses: ./
106
+ with:
107
+ files: |
108
+ data/report.bin
109
+ data/notes.txt
110
+ data/ünï cödé.txt
111
+ data/empty.dat
112
+ data/sub dir/nested file.csv
113
+ output: archives
114
+ engine: native
115
+ volume-size: 256k
116
+ verify: "true"
117
+
118
+ - name: Pack reported success and wrote a manifest
119
+ env:
120
+ STATUS: ${{ steps.pack.outputs.status }}
121
+ OUTPUT: ${{ steps.pack.outputs.output }}
122
+ RESULT: ${{ steps.pack.outputs.result-json }}
123
+ run: |
124
+ test "$STATUS" = success
125
+ test "$OUTPUT" = archives
126
+ test -f archives/manifest.json
127
+ test -s "$RESULT"
128
+ python -c "import json,sys; d=json.load(open(sys.argv[1], encoding='utf-8')); assert len(d['entries'])==5" "$RESULT"
129
+
130
+ - name: Verify against the originals
131
+ id: verify
132
+ uses: ./
133
+ with:
134
+ command: verify
135
+ source: archives
136
+ against: data
137
+
138
+ - name: Unpack
139
+ id: unpack
140
+ uses: ./
141
+ with:
142
+ command: unpack
143
+ source: archives
144
+ output: restored
145
+
146
+ - name: Restored files are identical
147
+ env:
148
+ VERIFY: ${{ steps.verify.outputs.status }}
149
+ UNPACK: ${{ steps.unpack.outputs.status }}
150
+ run: |
151
+ test "$VERIFY" = success
152
+ test "$UNPACK" = success
153
+ python tests/e2e/scripts/compare.py data restored
154
+
155
+ # --- incremental mode -----------------------------------------------
156
+ - name: Pack again with update (nothing changed)
157
+ id: update
158
+ uses: ./
159
+ with:
160
+ files: |
161
+ data/report.bin
162
+ data/notes.txt
163
+ output: archives
164
+ engine: native
165
+ volume-size: 256k
166
+ update: "true"
167
+ fail-on-error: "false"
168
+
169
+ - name: Nothing was re-archived
170
+ env:
171
+ STATUS: ${{ steps.update.outputs.status }}
172
+ run: test "$STATUS" = skipped
173
+
174
+ # --- artifact upload ------------------------------------------------
175
+ - name: Pack and upload as an artifact
176
+ uses: ./
177
+ with:
178
+ files: data/notes.txt
179
+ output: uploaded
180
+ engine: native
181
+ upload-artifact: "true"
182
+ artifact-name: e2e-archives-${{ matrix.os }}
183
+
184
+ - name: Download the artifact
185
+ uses: actions/download-artifact@v4
186
+ with:
187
+ name: e2e-archives-${{ matrix.os }}
188
+ path: downloaded
189
+
190
+ - name: Artifact holds the archive
191
+ run: |
192
+ test -f downloaded/manifest.json
193
+ ls downloaded/notes.txt
194
+
195
+ # --- encryption -----------------------------------------------------
196
+ - name: Pack encrypted
197
+ uses: ./
198
+ with:
199
+ files: data/notes.txt
200
+ output: vault
201
+ engine: native
202
+ encrypt: "true"
203
+ password: e2e-throwaway-password
204
+
205
+ - name: Unpack with the wrong password
206
+ id: wrong
207
+ uses: ./
208
+ with:
209
+ command: unpack
210
+ source: vault
211
+ output: wrong-restore
212
+ password: not-the-password
213
+ fail-on-error: "false"
214
+
215
+ - name: Unpack with the right password
216
+ id: right
217
+ uses: ./
218
+ with:
219
+ command: unpack
220
+ source: vault
221
+ output: right-restore
222
+ password: e2e-throwaway-password
223
+
224
+ - name: Only the right password restores
225
+ env:
226
+ WRONG: ${{ steps.wrong.outputs.status }}
227
+ RIGHT: ${{ steps.right.outputs.status }}
228
+ run: |
229
+ test "$WRONG" = failed
230
+ test "$RIGHT" = success
231
+ test ! -e wrong-restore/notes.txt
232
+ python tests/e2e/scripts/compare.py data right-restore notes.txt
233
+
234
+ # --- signing --------------------------------------------------------
235
+ - name: Create keys
236
+ run: batchvault keygen --private signing.key --public signing.pub -q
237
+
238
+ - name: Pack with a signed manifest
239
+ uses: ./
240
+ with:
241
+ files: data/notes.txt
242
+ output: signed
243
+ engine: native
244
+ sign-key: signing.key
245
+
246
+ - name: Verify the signature
247
+ id: signed
248
+ uses: ./
249
+ with:
250
+ command: verify
251
+ source: signed
252
+ public-key: signing.pub
253
+
254
+ - name: Tamper with the manifest
255
+ run: |
256
+ python - <<'PY'
257
+ from pathlib import Path
258
+ path = Path("signed/manifest.json")
259
+ path.write_text(path.read_text(encoding="utf-8").replace("notes.txt", "other.txt", 1), encoding="utf-8")
260
+ PY
261
+
262
+ - name: Verify the tampered manifest
263
+ id: tampered
264
+ uses: ./
265
+ with:
266
+ command: verify
267
+ source: signed
268
+ public-key: signing.pub
269
+ fail-on-error: "false"
270
+
271
+ - name: The signature check caught the change
272
+ env:
273
+ GOOD: ${{ steps.signed.outputs.status }}
274
+ BAD: ${{ steps.tampered.outputs.status }}
275
+ run: |
276
+ test "$GOOD" = success
277
+ test "$BAD" = failed
278
+
279
+ # --- failures -------------------------------------------------------
280
+ - name: Pack a file that does not exist
281
+ id: missing
282
+ continue-on-error: true
283
+ uses: ./
284
+ with:
285
+ files: data/does-not-exist.txt
286
+ output: nope
287
+
288
+ - name: Pack a missing file without failing the step
289
+ id: missing-quiet
290
+ uses: ./
291
+ with:
292
+ files: data/does-not-exist.txt
293
+ output: nope
294
+ fail-on-error: "false"
295
+
296
+ - name: Failures are reported
297
+ env:
298
+ OUTCOME: ${{ steps.missing.outcome }}
299
+ STATUS: ${{ steps.missing-quiet.outputs.status }}
300
+ run: |
301
+ test "$OUTCOME" = failure
302
+ test "$STATUS" = failed
303
+ test ! -e nope
304
+
305
+ # --- 7-Zip (only where the runner has it) ---------------------------
306
+ - name: Detect 7-Zip
307
+ id: sevenzip
308
+ run: |
309
+ if command -v 7zz >/dev/null || command -v 7z >/dev/null || [ -f "/c/Program Files/7-Zip/7z.exe" ]; then
310
+ echo "found=true" >> "$GITHUB_OUTPUT"
311
+ else
312
+ echo "found=false" >> "$GITHUB_OUTPUT"
313
+ fi
314
+
315
+ - name: Pack with 7-Zip
316
+ if: steps.sevenzip.outputs.found == 'true'
317
+ id: sz-pack
318
+ uses: ./
319
+ with:
320
+ files: |
321
+ data/report.bin
322
+ data/notes.txt
323
+ output: sz-archives
324
+ engine: 7zip
325
+ volume-size: 256k
326
+ verify: "true"
327
+
328
+ - name: Unpack the 7-Zip archives
329
+ if: steps.sevenzip.outputs.found == 'true'
330
+ uses: ./
331
+ with:
332
+ command: unpack
333
+ source: sz-archives
334
+ output: sz-restored
335
+
336
+ - name: 7-Zip round trip is identical
337
+ if: steps.sevenzip.outputs.found == 'true'
338
+ run: python tests/e2e/scripts/compare.py data sz-restored report.bin notes.txt
@@ -0,0 +1,27 @@
1
+ name: Lint
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ concurrency:
9
+ group: lint-${{ github.ref }}
10
+ cancel-in-progress: true
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ ruff:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v7
20
+
21
+ - uses: astral-sh/setup-uv@v7
22
+
23
+ - name: Install
24
+ run: uv sync --python 3.11
25
+
26
+ - name: Ruff
27
+ run: uv run ruff check .
@@ -0,0 +1,79 @@
1
+ name: Release
2
+
3
+ # Publishes to PyPI when a version tag such as v0.2.0 is pushed.
4
+ # Uses PyPI trusted publishing: register this repository and the "pypi"
5
+ # environment as a trusted publisher in the PyPI project settings.
6
+ on:
7
+ push:
8
+ tags: ["v*.*.*"]
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ check:
15
+ name: Check version and tests
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v7
19
+
20
+ - uses: astral-sh/setup-uv@v7
21
+
22
+ - name: Tag must match the package version
23
+ shell: bash
24
+ run: |
25
+ tag="${GITHUB_REF_NAME#v}"
26
+ project=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
27
+ module=$(python3 -c "import re; print(re.search(r'__version__ = \"(.+?)\"', open('src/batchvault/__init__.py').read()).group(1))")
28
+ echo "tag=$tag pyproject=$project __init__=$module"
29
+ if [ "$tag" != "$project" ] || [ "$tag" != "$module" ]; then
30
+ echo "::error::Tag v$tag must match the version in pyproject.toml ($project) and src/batchvault/__init__.py ($module)."
31
+ exit 1
32
+ fi
33
+
34
+ - name: Install
35
+ run: uv sync --python 3.11
36
+
37
+ - name: Lint
38
+ run: uv run ruff check .
39
+
40
+ - name: Unit tests
41
+ run: uv run pytest -m "not integration and not e2e"
42
+
43
+ - name: Integration tests
44
+ run: uv run pytest -m integration
45
+
46
+ e2e:
47
+ name: End-to-end tests
48
+ uses: ./.github/workflows/e2e.yml
49
+
50
+ publish:
51
+ name: Build and publish
52
+ needs: [check, e2e]
53
+ runs-on: ubuntu-latest
54
+ environment: pypi
55
+ permissions:
56
+ contents: write
57
+ id-token: write
58
+ attestations: write
59
+ steps:
60
+ - uses: actions/checkout@v7
61
+
62
+ - uses: astral-sh/setup-uv@v7
63
+
64
+ - name: Build
65
+ run: uv build
66
+
67
+ - name: Attest build provenance
68
+ uses: actions/attest-build-provenance@v2
69
+ with:
70
+ subject-path: dist/*
71
+
72
+ - name: Publish to PyPI
73
+ run: uv publish --trusted-publishing always
74
+
75
+ - name: GitHub release
76
+ uses: softprops/action-gh-release@v3
77
+ with:
78
+ generate_release_notes: true
79
+ files: dist/*
@@ -0,0 +1,45 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ concurrency:
9
+ group: tests-${{ github.ref }}
10
+ cancel-in-progress: true
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ test:
17
+ name: ${{ matrix.os }} / Python ${{ matrix.python }}
18
+ runs-on: ${{ matrix.os }}
19
+ # Python 3.15 may still be a pre-release; don't let it block the build.
20
+ continue-on-error: ${{ matrix.python == '3.15' }}
21
+ strategy:
22
+ fail-fast: false
23
+ matrix:
24
+ include:
25
+ - { os: ubuntu-latest, python: "3.11" }
26
+ - { os: ubuntu-latest, python: "3.12" }
27
+ - { os: ubuntu-latest, python: "3.13" }
28
+ - { os: ubuntu-latest, python: "3.14" }
29
+ - { os: ubuntu-latest, python: "3.15" }
30
+ - { os: windows-latest, python: "3.11" }
31
+ - { os: macos-latest, python: "3.11" }
32
+
33
+ steps:
34
+ - uses: actions/checkout@v7
35
+
36
+ - uses: astral-sh/setup-uv@v7
37
+
38
+ - name: Install
39
+ run: uv sync --python ${{ matrix.python }}
40
+
41
+ - name: Unit tests
42
+ run: uv run pytest -m "not integration and not e2e" -n auto
43
+
44
+ - name: Integration tests
45
+ run: uv run pytest -m integration -n auto