pysentry-rs 0.3.11__tar.gz → 0.3.12__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.

Potentially problematic release.


This version of pysentry-rs might be problematic. Click here for more details.

Files changed (80) hide show
  1. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/.github/workflows/ci.yml +1 -1
  2. pysentry_rs-0.3.12/.github/workflows/dev-release-test.yml +213 -0
  3. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/.github/workflows/release.yml +20 -64
  4. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/Cargo.lock +1 -1
  5. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/Cargo.toml +1 -1
  6. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/PKG-INFO +16 -13
  7. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/README.md +15 -12
  8. pysentry_rs-0.3.12/benchmarks/results/0.3.11.md +141 -0
  9. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/config.rs +86 -0
  10. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/parsers/requirements.rs +258 -1
  11. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/python.rs +11 -5
  12. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/.github/FUNDING.yml +0 -0
  13. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/.github/dependabot.yml +0 -0
  14. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/.github/workflows/benchmark.yml +0 -0
  15. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/.gitignore +0 -0
  16. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/.pre-commit-config.yaml +0 -0
  17. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/.pre-commit-hooks.yaml +0 -0
  18. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/LICENSE +0 -0
  19. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/.gitignore +0 -0
  20. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/.python-version +0 -0
  21. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/README.md +0 -0
  22. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/main.py +0 -0
  23. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/pyproject.toml +0 -0
  24. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/results/0.2.3.md +0 -0
  25. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/results/0.3.1.md +0 -0
  26. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/results/0.3.10.md +0 -0
  27. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/results/0.3.2.md +0 -0
  28. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/results/0.3.3.md +0 -0
  29. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/results/0.3.4.md +0 -0
  30. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/results/0.3.5.md +0 -0
  31. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/results/0.3.6.md +0 -0
  32. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/results/0.3.7.md +0 -0
  33. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/results/latest.md +0 -0
  34. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/src/benchmark_runner.py +0 -0
  35. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/src/performance_monitor.py +0 -0
  36. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/src/report_generator.py +0 -0
  37. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/src/tool_wrapper.py +0 -0
  38. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/test_data/large_requirements.txt +0 -0
  39. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/test_data/small_requirements.txt +0 -0
  40. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/test_data/uv.lock +0 -0
  41. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/benchmarks/uv.lock +0 -0
  42. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/fixtures/pipfile-tests/Pipfile +0 -0
  43. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/fixtures/pipfile-tests/Pipfile.lock +0 -0
  44. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/fixtures/pipfile-vulnerable-tests/Pipfile +0 -0
  45. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/fixtures/requirements-tests/requirements-dev.txt +0 -0
  46. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/fixtures/requirements-tests/requirements.txt +0 -0
  47. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/fixtures/requirements-tests-vulnerable/requirements.txt +0 -0
  48. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/pyproject.toml +0 -0
  49. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/python/pysentry/__init__.py +0 -0
  50. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/cache/audit.rs +0 -0
  51. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/cache/mod.rs +0 -0
  52. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/cache/storage.rs +0 -0
  53. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/cli.rs +0 -0
  54. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/dependency/mod.rs +0 -0
  55. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/dependency/resolvers/mod.rs +0 -0
  56. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/dependency/resolvers/pip_tools.rs +0 -0
  57. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/dependency/resolvers/uv.rs +0 -0
  58. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/dependency/scanner.rs +0 -0
  59. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/error.rs +0 -0
  60. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/lib.rs +0 -0
  61. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/main.rs +0 -0
  62. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/output/mod.rs +0 -0
  63. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/output/report.rs +0 -0
  64. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/output/sarif.rs +0 -0
  65. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/parsers/lock.rs +0 -0
  66. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/parsers/mod.rs +0 -0
  67. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/parsers/pipfile.rs +0 -0
  68. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/parsers/pipfile_lock.rs +0 -0
  69. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/parsers/poetry_lock.rs +0 -0
  70. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/parsers/pylock.rs +0 -0
  71. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/parsers/pyproject.rs +0 -0
  72. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/providers/mod.rs +0 -0
  73. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/providers/osv.rs +0 -0
  74. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/providers/pypa.rs +0 -0
  75. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/providers/pypi.rs +0 -0
  76. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/providers/retry.rs +0 -0
  77. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/types.rs +0 -0
  78. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/vulnerability/database.rs +0 -0
  79. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/vulnerability/matcher.rs +0 -0
  80. {pysentry_rs-0.3.11 → pysentry_rs-0.3.12}/src/vulnerability/mod.rs +0 -0
@@ -249,7 +249,7 @@ jobs:
249
249
  strategy:
250
250
  matrix:
251
251
  os: [ubuntu-latest, macos-latest, windows-latest]
252
- python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
252
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
253
253
 
254
254
  steps:
255
255
  - uses: actions/checkout@v4
@@ -0,0 +1,213 @@
1
+ name: Dev Release Test
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ env:
7
+ CARGO_TERM_COLOR: always
8
+ RUST_BACKTRACE: 1
9
+
10
+ jobs:
11
+ test-build-binaries:
12
+ name: Test Build Binaries
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ include:
17
+ - target: x86_64-unknown-linux-gnu
18
+ os: ubuntu-latest
19
+ name: linux-x64
20
+ artifact_name: pysentry
21
+ - target: x86_64-unknown-linux-musl
22
+ os: ubuntu-latest
23
+ name: linux-x64-musl
24
+ artifact_name: pysentry
25
+ - target: aarch64-unknown-linux-gnu
26
+ os: ubuntu-latest
27
+ name: linux-arm64
28
+ artifact_name: pysentry
29
+ - target: x86_64-apple-darwin
30
+ os: macos-latest
31
+ name: macos-x64
32
+ artifact_name: pysentry
33
+ - target: aarch64-apple-darwin
34
+ os: macos-latest
35
+ name: macos-arm64
36
+ artifact_name: pysentry
37
+ - target: x86_64-pc-windows-msvc
38
+ os: windows-latest
39
+ name: windows-x64
40
+ artifact_name: pysentry.exe
41
+
42
+ runs-on: ${{ matrix.os }}
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+
46
+ - name: Install Rust
47
+ uses: dtolnay/rust-toolchain@stable
48
+ with:
49
+ targets: ${{ matrix.target }}
50
+
51
+ - name: Install cross
52
+ if: matrix.target != 'x86_64-unknown-linux-gnu'
53
+ run: cargo install cross
54
+
55
+ - name: Build binary (native)
56
+ if: matrix.target == 'x86_64-unknown-linux-gnu'
57
+ run: cargo build --release --target ${{ matrix.target }} --bin pysentry
58
+
59
+ - name: Build binary (cross-compile)
60
+ if: matrix.target != 'x86_64-unknown-linux-gnu'
61
+ run: cross build --release --target ${{ matrix.target }} --bin pysentry
62
+
63
+ - name: Package binary (Unix)
64
+ if: matrix.os != 'windows-latest'
65
+ run: |
66
+ name=pysentry-${{ matrix.name }}
67
+ mkdir $name
68
+ cp target/${{ matrix.target }}/release/${{ matrix.artifact_name }} $name/
69
+ cp README.md $name/
70
+ cp LICENSE $name/
71
+ tar -czf $name.tar.gz $name
72
+ echo "ASSET=$name.tar.gz" >> $GITHUB_ENV
73
+
74
+ - name: Package binary (Windows)
75
+ if: matrix.os == 'windows-latest'
76
+ run: |
77
+ $name = "pysentry-${{ matrix.name }}"
78
+ mkdir $name
79
+ cp target/${{ matrix.target }}/release/${{ matrix.artifact_name }} $name/
80
+ cp README.md $name/
81
+ cp LICENSE $name/
82
+ Compress-Archive -Path $name -DestinationPath "$name.zip"
83
+ echo "ASSET=$name.zip" | Out-File -FilePath $env:GITHUB_ENV -Append
84
+
85
+ - name: Upload binary artifact
86
+ uses: actions/upload-artifact@v4
87
+ with:
88
+ name: binary-${{ matrix.name }}
89
+ path: ${{ env.ASSET }}
90
+ retention-days: 7
91
+
92
+ test-build-python-wheels:
93
+ name: Test Build Python Wheels
94
+ runs-on: ${{ matrix.osarch.os }}
95
+ strategy:
96
+ fail-fast: false
97
+ matrix:
98
+ osarch:
99
+ - os: ubuntu-latest
100
+ target: x86_64-unknown-linux-gnu
101
+ - os: ubuntu-latest
102
+ target: aarch64-unknown-linux-gnu
103
+ - os: windows-latest
104
+ target: x86_64-pc-windows-msvc
105
+ - os: macos-latest
106
+ target: x86_64-apple-darwin
107
+ - os: macos-latest
108
+ target: aarch64-apple-darwin
109
+ python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
110
+ steps:
111
+ - uses: actions/checkout@v4
112
+
113
+ - name: Install system dependencies (Ubuntu)
114
+ if: startsWith(matrix.osarch.os, 'ubuntu')
115
+ run: sudo apt-get update && sudo apt-get install -y libssl-dev pkg-config
116
+
117
+ - name: Set up Python
118
+ uses: actions/setup-python@v5
119
+ with:
120
+ python-version: ${{ matrix.python }}
121
+ allow-prereleases: true
122
+
123
+ - name: Install Rust
124
+ uses: dtolnay/rust-toolchain@stable
125
+
126
+ - name: Set manylinux version
127
+ id: manylinux
128
+ if: startsWith(matrix.osarch.os, 'ubuntu')
129
+ run: |
130
+ if [[ "${{ matrix.osarch.target }}" == "aarch64-unknown-linux-gnu" ]]; then
131
+ echo "version=2_28" >> $GITHUB_OUTPUT
132
+ else
133
+ echo "version=auto" >> $GITHUB_OUTPUT
134
+ fi
135
+
136
+ - name: Build wheels
137
+ uses: PyO3/maturin-action@v1.49.4
138
+ with:
139
+ command: build
140
+ args: --release --features python --out dist -i ${{ matrix.python }}
141
+ sccache: "true"
142
+ manylinux: ${{ steps.manylinux.outputs.version || 'auto' }}
143
+ target: ${{ matrix.osarch.target }}
144
+
145
+ - name: Upload wheels
146
+ uses: actions/upload-artifact@v4
147
+ with:
148
+ name: wheels-${{ matrix.osarch.os }}-${{ matrix.osarch.target }}-${{ matrix.python }}
149
+ path: dist/*.whl
150
+ retention-days: 7
151
+
152
+ test-build-sdist:
153
+ name: Test Build Source Distribution
154
+ runs-on: ubuntu-latest
155
+ steps:
156
+ - uses: actions/checkout@v4
157
+
158
+ - name: Set up Python
159
+ uses: actions/setup-python@v5
160
+ with:
161
+ python-version: "3.12"
162
+
163
+ - name: Install Rust
164
+ uses: dtolnay/rust-toolchain@stable
165
+
166
+ - name: Build sdist
167
+ uses: PyO3/maturin-action@v1.49.3
168
+ with:
169
+ command: sdist
170
+ args: --out dist
171
+
172
+ - name: Upload sdist
173
+ uses: actions/upload-artifact@v4
174
+ with:
175
+ name: sdist
176
+ path: dist/*.tar.gz
177
+ retention-days: 7
178
+
179
+ test-release-summary:
180
+ name: Release Test Summary
181
+ runs-on: ubuntu-latest
182
+ needs: [test-build-binaries, test-build-python-wheels, test-build-sdist]
183
+ if: always()
184
+ steps:
185
+ - name: Check build results
186
+ run: |
187
+ echo "## Build Results" >> $GITHUB_STEP_SUMMARY
188
+ echo "" >> $GITHUB_STEP_SUMMARY
189
+
190
+ if [ "${{ needs.test-build-binaries.result }}" == "success" ]; then
191
+ echo "✅ Binary builds: SUCCESS" >> $GITHUB_STEP_SUMMARY
192
+ else
193
+ echo "❌ Binary builds: FAILED" >> $GITHUB_STEP_SUMMARY
194
+ fi
195
+
196
+ if [ "${{ needs.test-build-python-wheels.result }}" == "success" ]; then
197
+ echo "✅ Python wheel builds: SUCCESS" >> $GITHUB_STEP_SUMMARY
198
+ else
199
+ echo "❌ Python wheel builds: FAILED" >> $GITHUB_STEP_SUMMARY
200
+ fi
201
+
202
+ if [ "${{ needs.test-build-sdist.result }}" == "success" ]; then
203
+ echo "✅ Source distribution build: SUCCESS" >> $GITHUB_STEP_SUMMARY
204
+ else
205
+ echo "❌ Source distribution build: FAILED" >> $GITHUB_STEP_SUMMARY
206
+ fi
207
+
208
+ echo "" >> $GITHUB_STEP_SUMMARY
209
+ echo "All artifacts are available in the workflow run for 7 days." >> $GITHUB_STEP_SUMMARY
210
+
211
+ - name: Fail if any build failed
212
+ if: needs.test-build-binaries.result != 'success' || needs.test-build-python-wheels.result != 'success' || needs.test-build-sdist.result != 'success'
213
+ run: exit 1
@@ -136,80 +136,26 @@ jobs:
136
136
 
137
137
  build-python-wheels:
138
138
  name: Build Python Wheels
139
- runs-on: ${{ matrix.os }}
139
+ runs-on: ${{ matrix.osarch.os }}
140
140
  strategy:
141
- fail-fast: false
142
141
  matrix:
143
- include:
144
- # x86_64 Linux
142
+ osarch:
145
143
  - os: ubuntu-latest
146
144
  target: x86_64-unknown-linux-gnu
147
- python: "3.9"
148
145
  - os: ubuntu-latest
149
- target: x86_64-unknown-linux-gnu
150
- python: "3.10"
151
- - os: ubuntu-latest
152
- target: x86_64-unknown-linux-gnu
153
- python: "3.11"
154
- - os: ubuntu-latest
155
- target: x86_64-unknown-linux-gnu
156
- python: "3.12"
157
- - os: ubuntu-latest
158
- target: x86_64-unknown-linux-gnu
159
- python: "3.13"
160
- # macOS
161
- - os: macos-latest
162
- target: x86_64-apple-darwin
163
- python: "3.9"
164
- - os: macos-latest
165
- target: x86_64-apple-darwin
166
- python: "3.10"
167
- - os: macos-latest
168
- target: x86_64-apple-darwin
169
- python: "3.11"
170
- - os: macos-latest
171
- target: x86_64-apple-darwin
172
- python: "3.12"
146
+ target: aarch64-unknown-linux-gnu
147
+ - os: windows-latest
148
+ target: x86_64-pc-windows-msvc
173
149
  - os: macos-latest
174
150
  target: x86_64-apple-darwin
175
- python: "3.13"
176
151
  - os: macos-latest
177
152
  target: aarch64-apple-darwin
178
- python: "3.9"
179
- - os: macos-latest
180
- target: aarch64-apple-darwin
181
- python: "3.10"
182
- - os: macos-latest
183
- target: aarch64-apple-darwin
184
- python: "3.11"
185
- - os: macos-latest
186
- target: aarch64-apple-darwin
187
- python: "3.12"
188
- - os: macos-latest
189
- target: aarch64-apple-darwin
190
- python: "3.13"
191
- # Windows
192
- - os: windows-latest
193
- target: x86_64-pc-windows-msvc
194
- python: "3.9"
195
- - os: windows-latest
196
- target: x86_64-pc-windows-msvc
197
- python: "3.10"
198
- - os: windows-latest
199
- target: x86_64-pc-windows-msvc
200
- python: "3.11"
201
- - os: windows-latest
202
- target: x86_64-pc-windows-msvc
203
- python: "3.12"
204
- - os: windows-latest
205
- target: x86_64-pc-windows-msvc
206
- python: "3.13"
207
-
153
+ python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
208
154
  steps:
209
155
  - uses: actions/checkout@v4
210
156
 
211
157
  - name: Install system dependencies (Ubuntu)
212
- if: startsWith(matrix.os, 'ubuntu')
158
+ if: startsWith(matrix.osarch.os, 'ubuntu')
213
159
  run: sudo apt-get update && sudo apt-get install -y libssl-dev pkg-config
214
160
 
215
161
  - name: Set up Python
@@ -220,19 +166,29 @@ jobs:
220
166
  - name: Install Rust
221
167
  uses: dtolnay/rust-toolchain@stable
222
168
 
169
+ - name: Set manylinux version
170
+ id: manylinux
171
+ if: startsWith(matrix.osarch.os, 'ubuntu')
172
+ run: |
173
+ if [[ "${{ matrix.osarch.target }}" == "aarch64-unknown-linux-gnu" ]]; then
174
+ echo "version=2_28" >> $GITHUB_OUTPUT
175
+ else
176
+ echo "version=auto" >> $GITHUB_OUTPUT
177
+ fi
178
+
223
179
  - name: Build wheels
224
180
  uses: PyO3/maturin-action@v1.49.4
225
181
  with:
226
182
  command: build
227
183
  args: --release --features python --out dist -i ${{ matrix.python }}
228
184
  sccache: "true"
229
- manylinux: auto
230
- target: ${{ matrix.target }}
185
+ manylinux: ${{ steps.manylinux.outputs.version || 'auto' }}
186
+ target: ${{ matrix.osarch.target }}
231
187
 
232
188
  - name: Upload wheels
233
189
  uses: actions/upload-artifact@v4
234
190
  with:
235
- name: wheels-${{ matrix.os }}-${{ matrix.target }}-${{ matrix.python }}
191
+ name: wheels-${{ matrix.osarch.os }}-${{ matrix.osarch.target }}-${{ matrix.python }}
236
192
  path: dist/*.whl
237
193
 
238
194
  build-sdist:
@@ -1154,7 +1154,7 @@ dependencies = [
1154
1154
 
1155
1155
  [[package]]
1156
1156
  name = "pysentry"
1157
- version = "0.3.11"
1157
+ version = "0.3.12"
1158
1158
  dependencies = [
1159
1159
  "anyhow",
1160
1160
  "async-trait",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "pysentry"
3
- version = "0.3.11"
3
+ version = "0.3.12"
4
4
  edition = "2021"
5
5
  rust-version = "1.79"
6
6
  description = "Security vulnerability auditing for Python packages"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pysentry-rs
3
- Version: 0.3.11
3
+ Version: 0.3.12
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
@@ -78,7 +78,7 @@ This method:
78
78
 
79
79
  ### 📦 From PyPI (Python Package)
80
80
 
81
- For Python 3.9+ on Linux, macOS, and Windows:
81
+ For Python 3.9-3.14 on Linux, macOS, and Windows:
82
82
 
83
83
  ```bash
84
84
  pip install pysentry-rs
@@ -129,20 +129,23 @@ The binary will be available at `target/release/pysentry`.
129
129
 
130
130
  ### Requirements
131
131
 
132
- - **For uvx**: Python 3.9+ and [uv](https://docs.astral.sh/uv/) installed
132
+ - **For uvx**: Python 3.9-3.14 and [uv](https://docs.astral.sh/uv/) installed
133
133
  - **For binaries**: No additional dependencies
134
- - **For Python package**: Python 3.9+
134
+ - **For Python package**: Python 3.9-3.14
135
135
  - **For Rust package and source**: Rust 1.79+
136
136
 
137
137
  ### Platform Support
138
138
 
139
- | Installation Method | Linux | macOS | Windows |
140
- | ------------------- | ----- | ----- | ------- |
141
- | uvx | ✅ | ✅ | ✅ |
142
- | PyPI (pip) | ✅ | ✅ | ✅ |
143
- | Crates.io (cargo) | ✅ | ✅ | ✅ |
144
- | GitHub Releases | ✅ | ✅ | ✅ |
145
- | From Source | ✅ | ✅ | ✅ |
139
+ | Installation Method | Linux (x64) | Linux (ARM64) | macOS (x64) | macOS (ARM64) | Windows (x64) |
140
+ | ------------------- | ----------- | ------------- | ----------- | ------------- | ------------- |
141
+ | uvx | ✅ | ✅ | ✅ | ✅ | ✅ |
142
+ | PyPI (pip) | ✅ | ✅ | ✅ | ✅ | ✅ |
143
+ | Crates.io (cargo) | ✅ | ✅ | ✅ | ✅ | ✅ |
144
+ | GitHub Releases | ✅ | ✅ | ✅ | ✅ | ✅ |
145
+ | From Source | ✅ | ✅ | ✅ | ✅ | ✅ |
146
+
147
+ **Supported Python Versions**: 3.9, 3.10, 3.11, 3.12, 3.13, 3.14
148
+ **Supported Architectures**: x86_64 (x64), ARM64 (aarch64)
146
149
 
147
150
 
148
151
  ### CLI Command Names
@@ -302,7 +305,7 @@ Add PySentry to your `.pre-commit-config.yaml`:
302
305
  ```yaml
303
306
  repos:
304
307
  - repo: https://github.com/pysentry/pysentry-pre-commit
305
- rev: v0.3.7
308
+ rev: v0.3.11
306
309
  hooks:
307
310
  - id: pysentry # default pysentry settings
308
311
  ```
@@ -312,7 +315,7 @@ repos:
312
315
  ```yaml
313
316
  repos:
314
317
  - repo: https://github.com/pysentry/pysentry-pre-commit
315
- rev: v0.3.7
318
+ rev: v0.3.11
316
319
  hooks:
317
320
  - id: pysentry
318
321
  args: ["--sources", "pypa,osv", "--fail-on", "high"]
@@ -53,7 +53,7 @@ This method:
53
53
 
54
54
  ### 📦 From PyPI (Python Package)
55
55
 
56
- For Python 3.9+ on Linux, macOS, and Windows:
56
+ For Python 3.9-3.14 on Linux, macOS, and Windows:
57
57
 
58
58
  ```bash
59
59
  pip install pysentry-rs
@@ -104,20 +104,23 @@ The binary will be available at `target/release/pysentry`.
104
104
 
105
105
  ### Requirements
106
106
 
107
- - **For uvx**: Python 3.9+ and [uv](https://docs.astral.sh/uv/) installed
107
+ - **For uvx**: Python 3.9-3.14 and [uv](https://docs.astral.sh/uv/) installed
108
108
  - **For binaries**: No additional dependencies
109
- - **For Python package**: Python 3.9+
109
+ - **For Python package**: Python 3.9-3.14
110
110
  - **For Rust package and source**: Rust 1.79+
111
111
 
112
112
  ### Platform Support
113
113
 
114
- | Installation Method | Linux | macOS | Windows |
115
- | ------------------- | ----- | ----- | ------- |
116
- | uvx | ✅ | ✅ | ✅ |
117
- | PyPI (pip) | ✅ | ✅ | ✅ |
118
- | Crates.io (cargo) | ✅ | ✅ | ✅ |
119
- | GitHub Releases | ✅ | ✅ | ✅ |
120
- | From Source | ✅ | ✅ | ✅ |
114
+ | Installation Method | Linux (x64) | Linux (ARM64) | macOS (x64) | macOS (ARM64) | Windows (x64) |
115
+ | ------------------- | ----------- | ------------- | ----------- | ------------- | ------------- |
116
+ | uvx | ✅ | ✅ | ✅ | ✅ | ✅ |
117
+ | PyPI (pip) | ✅ | ✅ | ✅ | ✅ | ✅ |
118
+ | Crates.io (cargo) | ✅ | ✅ | ✅ | ✅ | ✅ |
119
+ | GitHub Releases | ✅ | ✅ | ✅ | ✅ | ✅ |
120
+ | From Source | ✅ | ✅ | ✅ | ✅ | ✅ |
121
+
122
+ **Supported Python Versions**: 3.9, 3.10, 3.11, 3.12, 3.13, 3.14
123
+ **Supported Architectures**: x86_64 (x64), ARM64 (aarch64)
121
124
 
122
125
 
123
126
  ### CLI Command Names
@@ -277,7 +280,7 @@ Add PySentry to your `.pre-commit-config.yaml`:
277
280
  ```yaml
278
281
  repos:
279
282
  - repo: https://github.com/pysentry/pysentry-pre-commit
280
- rev: v0.3.7
283
+ rev: v0.3.11
281
284
  hooks:
282
285
  - id: pysentry # default pysentry settings
283
286
  ```
@@ -287,7 +290,7 @@ repos:
287
290
  ```yaml
288
291
  repos:
289
292
  - repo: https://github.com/pysentry/pysentry-pre-commit
290
- rev: v0.3.7
293
+ rev: v0.3.11
291
294
  hooks:
292
295
  - id: pysentry
293
296
  args: ["--sources", "pypa,osv", "--fail-on", "high"]
@@ -0,0 +1,141 @@
1
+ # PySentry - pip-audit Benchmark Report
2
+
3
+ **Generated:** 2025-10-01 17:03:50
4
+ **Duration:** 1m 58.52s
5
+ **Total Tests:** 20
6
+
7
+ ## Executive Summary
8
+
9
+ **Overall Success Rate:** 100.0% (20/20 successful runs)
10
+
11
+ ### Small_Requirements Dataset - Cold Cache
12
+ - **Fastest:** pysentry-pypi (0.251s) - 35.82x faster than slowest
13
+ - **Memory Efficient:** pysentry-osv (9.73 MB) - 8.91x less memory than highest
14
+
15
+ ### Small_Requirements Dataset - Hot Cache
16
+ - **Fastest:** pysentry-pypi (0.225s) - 35.73x faster than slowest
17
+ - **Memory Efficient:** pysentry-osv (9.61 MB) - 9.75x less memory than highest
18
+
19
+ ### Large_Requirements Dataset - Cold Cache
20
+ - **Fastest:** pysentry-pypi (0.725s) - 27.37x faster than slowest
21
+ - **Memory Efficient:** pysentry-osv (10.55 MB) - 8.94x less memory than highest
22
+
23
+ ### Large_Requirements Dataset - Hot Cache
24
+ - **Fastest:** pysentry-pypi (0.710s) - 22.73x faster than slowest
25
+ - **Memory Efficient:** pysentry-osv (9.75 MB) - 8.22x less memory than highest
26
+
27
+ ## Test Environment
28
+
29
+ - **Platform:** Linux-6.11.0-1018-azure-x86_64-with-glibc2.39
30
+ - **Python Version:** 3.11.13
31
+ - **CPU Cores:** 4
32
+ - **Total Memory:** 15.62 GB
33
+ - **Available Memory:** 14.68 GB
34
+
35
+ ## Performance Comparison
36
+
37
+ ### Small_Requirements Dataset - Cold Cache
38
+
39
+ #### Execution Time Comparison
40
+
41
+ | Tool Configuration | Execution Time | Relative Performance |
42
+ |---------------------|---------------------|---------------------|
43
+ | 🥇 pysentry-pypi | 0.251s | 1.00x |
44
+ | 🥈 pysentry-osv | 0.800s | 3.18x |
45
+ | pysentry-all-sources | 1.105s | 4.40x |
46
+ | pysentry-pypa | 1.344s | 5.35x |
47
+ | pip-audit-default | 8.995s | 35.82x |
48
+
49
+ #### Memory Usage Comparison
50
+
51
+ | Tool Configuration | Peak Memory | Relative Performance |
52
+ |---------------------|---------------------|---------------------|
53
+ | 🥇 pysentry-osv | 9.73 MB | 1.00x |
54
+ | 🥈 pysentry-pypi | 10.30 MB | 1.06x |
55
+ | pip-audit-default | 45.71 MB | 4.70x |
56
+ | pysentry-pypa | 60.87 MB | 6.26x |
57
+ | pysentry-all-sources | 86.62 MB | 8.91x |
58
+
59
+ ### Small_Requirements Dataset - Hot Cache
60
+
61
+ #### Execution Time Comparison
62
+
63
+ | Tool Configuration | Execution Time | Relative Performance |
64
+ |---------------------|---------------------|---------------------|
65
+ | 🥇 pysentry-pypi | 0.225s | 1.00x |
66
+ | 🥈 pysentry-pypa | 0.693s | 3.08x |
67
+ | pysentry-osv | 0.781s | 3.47x |
68
+ | pysentry-all-sources | 0.819s | 3.64x |
69
+ | pip-audit-default | 8.033s | 35.73x |
70
+
71
+ #### Memory Usage Comparison
72
+
73
+ | Tool Configuration | Peak Memory | Relative Performance |
74
+ |---------------------|---------------------|---------------------|
75
+ | 🥇 pysentry-osv | 9.61 MB | 1.00x |
76
+ | 🥈 pysentry-pypi | 10.67 MB | 1.11x |
77
+ | pysentry-pypa | 43.20 MB | 4.49x |
78
+ | pip-audit-default | 44.82 MB | 4.66x |
79
+ | pysentry-all-sources | 93.77 MB | 9.75x |
80
+
81
+ ### Large_Requirements Dataset - Cold Cache
82
+
83
+ #### Execution Time Comparison
84
+
85
+ | Tool Configuration | Execution Time | Relative Performance |
86
+ |---------------------|---------------------|---------------------|
87
+ | 🥇 pysentry-pypi | 0.725s | 1.00x |
88
+ | 🥈 pysentry-pypa | 1.522s | 2.10x |
89
+ | pysentry-osv | 3.638s | 5.02x |
90
+ | pysentry-all-sources | 4.179s | 5.76x |
91
+ | pip-audit-default | 19.837s | 27.37x |
92
+
93
+ #### Memory Usage Comparison
94
+
95
+ | Tool Configuration | Peak Memory | Relative Performance |
96
+ |---------------------|---------------------|---------------------|
97
+ | 🥇 pysentry-osv | 10.55 MB | 1.00x |
98
+ | 🥈 pysentry-pypi | 12.95 MB | 1.23x |
99
+ | pip-audit-default | 53.12 MB | 5.03x |
100
+ | pysentry-pypa | 61.15 MB | 5.80x |
101
+ | pysentry-all-sources | 94.30 MB | 8.94x |
102
+
103
+ ### Large_Requirements Dataset - Hot Cache
104
+
105
+ #### Execution Time Comparison
106
+
107
+ | Tool Configuration | Execution Time | Relative Performance |
108
+ |---------------------|---------------------|---------------------|
109
+ | 🥇 pysentry-pypi | 0.710s | 1.00x |
110
+ | 🥈 pysentry-pypa | 1.130s | 1.59x |
111
+ | pysentry-osv | 3.954s | 5.57x |
112
+ | pysentry-all-sources | 4.199s | 5.91x |
113
+ | pip-audit-default | 16.134s | 22.73x |
114
+
115
+ #### Memory Usage Comparison
116
+
117
+ | Tool Configuration | Peak Memory | Relative Performance |
118
+ |---------------------|---------------------|---------------------|
119
+ | 🥇 pysentry-osv | 9.75 MB | 1.00x |
120
+ | 🥈 pysentry-pypi | 12.74 MB | 1.31x |
121
+ | pip-audit-default | 47.41 MB | 4.86x |
122
+ | pysentry-pypa | 60.06 MB | 6.16x |
123
+ | pysentry-all-sources | 80.21 MB | 8.22x |
124
+
125
+ ## Detailed Analysis
126
+
127
+ ### Pysentry Performance
128
+
129
+ - **Execution Time:** Avg: 1.630s, Min: 0.225s, Max: 4.199s
130
+
131
+ - **Memory Usage:** Avg: 41.65 MB, Min: 9.61 MB, Max: 94.30 MB
132
+
133
+ - **Success Rate:** 100.0% (16/16)
134
+
135
+ ### Pip-Audit Performance
136
+
137
+ - **Execution Time:** Avg: 13.250s, Min: 8.033s, Max: 19.837s
138
+
139
+ - **Memory Usage:** Avg: 47.77 MB, Min: 44.82 MB, Max: 53.12 MB
140
+
141
+ - **Success Rate:** 100.0% (4/4)
@@ -662,3 +662,89 @@ fn default_http_retry_max_backoff() -> u64 {
662
662
  fn default_http_show_progress() -> bool {
663
663
  true
664
664
  }
665
+
666
+ #[cfg(test)]
667
+ mod tests {
668
+ use super::*;
669
+ use std::fs;
670
+ use tempfile::TempDir;
671
+
672
+ #[test]
673
+ fn test_config_load_from_file() {
674
+ let temp_dir = TempDir::new().unwrap();
675
+ let config_path = temp_dir.path().join(".pysentry.toml");
676
+
677
+ let config_content = r#"
678
+ version = 1
679
+
680
+ [defaults]
681
+ format = "markdown"
682
+ severity = "medium"
683
+ fail_on = "low"
684
+
685
+ [sources]
686
+ enabled = ["pypa", "pypi", "osv"]
687
+
688
+ [cache]
689
+ enabled = false
690
+ "#;
691
+
692
+ fs::write(&config_path, config_content).unwrap();
693
+
694
+ let loader = ConfigLoader::load_from_file(&config_path).unwrap();
695
+
696
+ assert_eq!(loader.config.defaults.format, "markdown");
697
+ assert_eq!(loader.config.defaults.severity, "medium");
698
+ assert_eq!(loader.config.defaults.fail_on, "low");
699
+ assert_eq!(loader.config.sources.enabled, vec!["pypa", "pypi", "osv"]);
700
+ assert!(!loader.config.cache.enabled);
701
+ assert!(loader.config_path.is_some());
702
+ }
703
+
704
+ #[test]
705
+ fn test_config_default_values() {
706
+ let config = Config::default();
707
+
708
+ assert_eq!(config.version, 1);
709
+ assert_eq!(config.defaults.format, "human");
710
+ assert_eq!(config.defaults.severity, "low");
711
+ assert_eq!(config.defaults.fail_on, "medium");
712
+ assert_eq!(config.sources.enabled, vec!["pypa"]);
713
+ assert!(config.cache.enabled);
714
+ }
715
+
716
+ #[test]
717
+ fn test_config_validation() {
718
+ let mut config = Config::default();
719
+
720
+ // Valid config should pass
721
+ assert!(config.validate().is_ok());
722
+
723
+ // Invalid format
724
+ config.defaults.format = "invalid".to_string();
725
+ assert!(config.validate().is_err());
726
+ config.defaults.format = "human".to_string();
727
+
728
+ // Invalid severity
729
+ config.defaults.severity = "invalid".to_string();
730
+ assert!(config.validate().is_err());
731
+ config.defaults.severity = "low".to_string();
732
+
733
+ // Invalid source
734
+ config.sources.enabled = vec!["invalid".to_string()];
735
+ assert!(config.validate().is_err());
736
+ config.sources.enabled = vec!["pypa".to_string()];
737
+
738
+ // Empty sources
739
+ config.sources.enabled = vec![];
740
+ assert!(config.validate().is_err());
741
+ }
742
+
743
+ #[test]
744
+ fn test_config_loader_with_no_config() {
745
+ // Test that loader works even when no config file exists
746
+ let loader = ConfigLoader::load_with_options(true).unwrap();
747
+ assert!(loader.config_path.is_none());
748
+ assert_eq!(loader.config.defaults.format, "human");
749
+ }
750
+ }
@@ -314,6 +314,80 @@ impl RequirementsParser {
314
314
  Ok(filtered_dependencies)
315
315
  }
316
316
 
317
+ /// Parse include directive (-r or -c) from a requirements line
318
+ fn parse_include_directive(line: &str) -> Option<(&str, &str)> {
319
+ let trimmed = line.trim();
320
+
321
+ // Handle -r and --requirement (for requirements includes)
322
+ if let Some(path) = trimmed.strip_prefix("-r ") {
323
+ return Some(("r", path.trim()));
324
+ }
325
+ if let Some(path) = trimmed.strip_prefix("--requirement ") {
326
+ return Some(("r", path.trim()));
327
+ }
328
+ if let Some(path) = trimmed.strip_prefix("--requirement=") {
329
+ return Some(("r", path.trim()));
330
+ }
331
+
332
+ // Handle -c and --constraint (for constraint files - same issue)
333
+ if let Some(path) = trimmed.strip_prefix("-c ") {
334
+ return Some(("c", path.trim()));
335
+ }
336
+ if let Some(path) = trimmed.strip_prefix("--constraint ") {
337
+ return Some(("c", path.trim()));
338
+ }
339
+ if let Some(path) = trimmed.strip_prefix("--constraint=") {
340
+ return Some(("c", path.trim()));
341
+ }
342
+
343
+ None
344
+ }
345
+
346
+ /// Resolve relative -r/-c directive paths to absolute paths
347
+ fn resolve_requirements_includes(content: &str, source_file: &Path) -> Result<String> {
348
+ let parent_dir = source_file.parent().ok_or_else(|| {
349
+ AuditError::other(format!(
350
+ "Cannot get parent directory for: {}",
351
+ source_file.display()
352
+ ))
353
+ })?;
354
+
355
+ let mut resolved = String::new();
356
+
357
+ for line in content.lines() {
358
+ if let Some((directive_type, rel_path)) = Self::parse_include_directive(line) {
359
+ // Resolve relative path to absolute path
360
+ let include_path = parent_dir.join(rel_path);
361
+
362
+ match include_path.canonicalize() {
363
+ Ok(abs_path) => {
364
+ let flag = if directive_type == "r" { "-r" } else { "-c" };
365
+ debug!("Resolved {} {} -> {}", flag, rel_path, abs_path.display());
366
+ resolved.push_str(&format!("{} {}\n", flag, abs_path.display()));
367
+ }
368
+ Err(e) => {
369
+ // If file doesn't exist, keep original path and let UV handle the error
370
+ // This preserves existing error messages from UV
371
+ warn!(
372
+ "Could not resolve include path '{}' from {}: {}. Using original path.",
373
+ rel_path,
374
+ source_file.display(),
375
+ e
376
+ );
377
+ resolved.push_str(line);
378
+ resolved.push('\n');
379
+ }
380
+ }
381
+ } else {
382
+ // Not an include directive, keep line as-is
383
+ resolved.push_str(line);
384
+ resolved.push('\n');
385
+ }
386
+ }
387
+
388
+ Ok(resolved)
389
+ }
390
+
317
391
  /// Combine multiple explicit requirements files into single content
318
392
  async fn combine_explicit_files(&self, files: &[PathBuf]) -> Result<String> {
319
393
  let mut combined = String::new();
@@ -329,8 +403,11 @@ impl RequirementsParser {
329
403
  ))
330
404
  })?;
331
405
 
406
+ // Resolve relative -r/-c directives to absolute paths
407
+ let resolved_content = Self::resolve_requirements_includes(&content, file)?;
408
+
332
409
  combined.push_str(&format!("# From: {}\n", file.display()));
333
- combined.push_str(&content);
410
+ combined.push_str(&resolved_content);
334
411
  combined.push('\n');
335
412
  }
336
413
 
@@ -506,4 +583,184 @@ click>=8.0.0
506
583
  assert!(result.contains(&PackageName::new("flask")));
507
584
  assert!(result.contains(&PackageName::new("click")));
508
585
  }
586
+
587
+ #[test]
588
+ fn test_parse_include_directive() {
589
+ // Test -r directive
590
+ assert_eq!(
591
+ RequirementsParser::parse_include_directive("-r ../requirements.txt"),
592
+ Some(("r", "../requirements.txt"))
593
+ );
594
+
595
+ // Test --requirement directive
596
+ assert_eq!(
597
+ RequirementsParser::parse_include_directive("--requirement base.txt"),
598
+ Some(("r", "base.txt"))
599
+ );
600
+
601
+ // Test --requirement= directive
602
+ assert_eq!(
603
+ RequirementsParser::parse_include_directive("--requirement=../requirements.txt"),
604
+ Some(("r", "../requirements.txt"))
605
+ );
606
+
607
+ // Test -c constraint directive
608
+ assert_eq!(
609
+ RequirementsParser::parse_include_directive("-c constraints.txt"),
610
+ Some(("c", "constraints.txt"))
611
+ );
612
+
613
+ // Test --constraint directive
614
+ assert_eq!(
615
+ RequirementsParser::parse_include_directive("--constraint ../constraints.txt"),
616
+ Some(("c", "../constraints.txt"))
617
+ );
618
+
619
+ // Test --constraint= directive
620
+ assert_eq!(
621
+ RequirementsParser::parse_include_directive("--constraint=constraints.txt"),
622
+ Some(("c", "constraints.txt"))
623
+ );
624
+
625
+ // Test with extra whitespace
626
+ assert_eq!(
627
+ RequirementsParser::parse_include_directive(" -r ../requirements.txt "),
628
+ Some(("r", "../requirements.txt"))
629
+ );
630
+
631
+ // Test non-directive lines
632
+ assert_eq!(
633
+ RequirementsParser::parse_include_directive("requests>=2.25.0"),
634
+ None
635
+ );
636
+ assert_eq!(
637
+ RequirementsParser::parse_include_directive("# Comment"),
638
+ None
639
+ );
640
+ assert_eq!(RequirementsParser::parse_include_directive(""), None);
641
+ }
642
+
643
+ #[test]
644
+ fn test_resolve_requirements_includes() {
645
+ use std::fs;
646
+ use tempfile::TempDir;
647
+
648
+ // Create temporary directory structure
649
+ let temp_dir = TempDir::new().unwrap();
650
+ let project_path = temp_dir.path();
651
+
652
+ // Create requirements files
653
+ let base_req = project_path.join("requirements.txt");
654
+ fs::write(&base_req, "flask==2.0.0\n").unwrap();
655
+
656
+ // Create dev subdirectory
657
+ let dev_dir = project_path.join("dev");
658
+ fs::create_dir(&dev_dir).unwrap();
659
+ let dev_req = dev_dir.join("requirements.txt");
660
+
661
+ // Test content with relative path
662
+ let content_with_include = "-r ../requirements.txt\npytest==8.4.2\n";
663
+
664
+ let resolved =
665
+ RequirementsParser::resolve_requirements_includes(content_with_include, &dev_req)
666
+ .unwrap();
667
+
668
+ // Should contain absolute path to requirements.txt
669
+ assert!(resolved.contains("-r"));
670
+ assert!(resolved.contains("requirements.txt"));
671
+ assert!(resolved.contains("pytest==8.4.2"));
672
+
673
+ // Verify the path was made absolute
674
+ let expected_abs_path = base_req.canonicalize().unwrap();
675
+ assert!(resolved.contains(&expected_abs_path.display().to_string()));
676
+ }
677
+
678
+ #[test]
679
+ fn test_resolve_requirements_includes_constraint_files() {
680
+ use std::fs;
681
+ use tempfile::TempDir;
682
+
683
+ let temp_dir = TempDir::new().unwrap();
684
+ let project_path = temp_dir.path();
685
+
686
+ // Create constraint file
687
+ let constraints = project_path.join("constraints.txt");
688
+ fs::write(&constraints, "flask<3.0\n").unwrap();
689
+
690
+ // Create dev subdirectory
691
+ let dev_dir = project_path.join("dev");
692
+ fs::create_dir(&dev_dir).unwrap();
693
+ let dev_req = dev_dir.join("requirements.txt");
694
+
695
+ let content = "-c ../constraints.txt\nflask>=2.0\n";
696
+
697
+ let resolved =
698
+ RequirementsParser::resolve_requirements_includes(content, &dev_req).unwrap();
699
+
700
+ // Should contain absolute path to constraints.txt
701
+ assert!(resolved.contains("-c"));
702
+ assert!(resolved.contains("constraints.txt"));
703
+ assert!(resolved.contains("flask>=2.0"));
704
+
705
+ let expected_abs_path = constraints.canonicalize().unwrap();
706
+ assert!(resolved.contains(&expected_abs_path.display().to_string()));
707
+ }
708
+
709
+ #[test]
710
+ fn test_resolve_requirements_includes_preserves_regular_lines() {
711
+ use std::fs;
712
+ use tempfile::TempDir;
713
+
714
+ let temp_dir = TempDir::new().unwrap();
715
+ let test_file = temp_dir.path().join("requirements.txt");
716
+ fs::write(&test_file, "").unwrap();
717
+
718
+ let content = r#"
719
+ # This is a comment
720
+ requests>=2.25.0
721
+ flask[async]>=2.0,<3.0
722
+
723
+ click==8.0.0
724
+ "#;
725
+
726
+ let resolved =
727
+ RequirementsParser::resolve_requirements_includes(content, &test_file).unwrap();
728
+
729
+ // All lines should be preserved
730
+ assert!(resolved.contains("# This is a comment"));
731
+ assert!(resolved.contains("requests>=2.25.0"));
732
+ assert!(resolved.contains("flask[async]>=2.0,<3.0"));
733
+ assert!(resolved.contains("click==8.0.0"));
734
+ }
735
+
736
+ #[test]
737
+ fn test_resolve_requirements_includes_multiple_directives() {
738
+ use std::fs;
739
+ use tempfile::TempDir;
740
+
741
+ let temp_dir = TempDir::new().unwrap();
742
+ let project_path = temp_dir.path();
743
+
744
+ // Create referenced files
745
+ let base_req = project_path.join("base.txt");
746
+ fs::write(&base_req, "flask==2.0.0\n").unwrap();
747
+
748
+ let security_req = project_path.join("security.txt");
749
+ fs::write(&security_req, "cryptography>=3.0\n").unwrap();
750
+
751
+ let dev_req = project_path.join("dev.txt");
752
+
753
+ let content = "-r base.txt\n-r security.txt\npytest==8.4.2\n";
754
+
755
+ let resolved =
756
+ RequirementsParser::resolve_requirements_includes(content, &dev_req).unwrap();
757
+
758
+ // Both includes should be resolved to absolute paths
759
+ let base_abs = base_req.canonicalize().unwrap();
760
+ let security_abs = security_req.canonicalize().unwrap();
761
+
762
+ assert!(resolved.contains(&format!("-r {}", base_abs.display())));
763
+ assert!(resolved.contains(&format!("-r {}", security_abs.display())));
764
+ assert!(resolved.contains("pytest==8.4.2"));
765
+ }
509
766
  }
@@ -65,17 +65,23 @@ fn run_cli(args: Vec<String>) -> PyResult<i32> {
65
65
 
66
66
  match cli.command {
67
67
  None => {
68
- let audit_args = cli.audit_args;
68
+ let (merged_audit_args, config) = match cli.audit_args.load_and_merge_config() {
69
+ Ok(result) => result,
70
+ Err(e) => {
71
+ eprintln!("Configuration error: {e}");
72
+ return Ok(1);
73
+ }
74
+ };
69
75
 
70
- let cache_dir = audit_args.cache_dir.clone().unwrap_or_else(|| {
76
+ let http_config = config.as_ref().map(|c| c.http.clone()).unwrap_or_default();
77
+
78
+ let cache_dir = merged_audit_args.cache_dir.clone().unwrap_or_else(|| {
71
79
  dirs::cache_dir()
72
80
  .unwrap_or_else(std::env::temp_dir)
73
81
  .join("pysentry")
74
82
  });
75
83
 
76
- let http_config = crate::config::HttpConfig::default();
77
-
78
- match audit(&audit_args, &cache_dir, http_config).await {
84
+ match audit(&merged_audit_args, &cache_dir, http_config).await {
79
85
  Ok(exit_code) => Ok(exit_code),
80
86
  Err(e) => {
81
87
  eprintln!("Error: Audit failed: {e}");
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes