Skip to content

Commit 96cd35d

Browse files
authored
fix: Parse .whl files without file existence check (#291)
1 parent f1e9e67 commit 96cd35d

3 files changed

Lines changed: 94 additions & 4 deletions

File tree

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
"prepare": "npm run build",
1717
"test": "npm run test:jest",
1818
"test:jest": "jest",
19-
"test:pysrc2": "cd pysrc; python -m unittest test_pip_resolve_py2 test_pipfile",
20-
"test:pysrc3": "cd pysrc; python -m unittest test_pip_resolve_py3 test_pipfile",
21-
"test:pysrc3_12": "cd pysrc; python -m unittest test_pip_resolve_py3_12 test_pipfile",
19+
"test:pysrc2": "cd pysrc; python -m unittest test_pip_resolve_py2 test_pipfile test_requirements",
20+
"test:pysrc3": "cd pysrc; python -m unittest test_pip_resolve_py3 test_pipfile test_requirements",
21+
"test:pysrc3_12": "cd pysrc; python -m unittest test_pip_resolve_py3_12 test_pipfile test_requirements",
2222
"lint": "npm run build-tests && npm run format:check && eslint --cache '{lib,test}/**/*.{js,ts}'"
2323
},
2424
"author": "snyk.io",

pysrc/requirements/requirement.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,11 +202,15 @@ def parse_line(cls, line):
202202
req.hash_name, req.hash = get_hash_info(fragment)
203203
req.subdirectory = fragment.get('subdirectory')
204204
req.path = groups['path']
205-
elif os.path.isfile(line) and line.lower().endswith(".whl"):
205+
elif line.lower().endswith(".whl"):
206+
# Recognize .whl files by extension without checking file existence.
207+
# File existence check would fail when working directory differs (e.g., with --all-projects).
208+
# The parser's job is to extract package names; file validation happens at pip install time.
206209
match = re.match(WHL_FILE_REGEX, os.path.basename(line))
207210
if match:
208211
req.name = match.group("name")
209212
req.local_file = True
213+
req.path = line
210214
# Attempts to determine if the line refers to a local directory that could be
211215
# an installable Python package
212216
elif os.path.isdir(line) and os.path.isfile(os.path.join(line, "setup.py")):

pysrc/test_requirements.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# run with:
2+
# cd pysrc; python3 test_requirements.py; cd ..
3+
4+
import unittest
5+
import os
6+
from requirements.requirement import Requirement
7+
8+
class TestWheelFileRequirements(unittest.TestCase):
9+
"""Test that .whl files are parsed correctly regardless of file existence"""
10+
11+
def test_whl_file_with_relative_path(self):
12+
"""Test parsing .whl file with relative path (no ./ prefix)"""
13+
line = "offline_packages/anyio-4.5.2-py3-none-any.whl"
14+
req = Requirement.parse(line)
15+
16+
self.assertEqual(req.name, "anyio")
17+
self.assertTrue(req.local_file)
18+
self.assertEqual(req.path, line)
19+
20+
def test_whl_file_with_dot_slash_prefix(self):
21+
"""Test parsing .whl file with ./ prefix"""
22+
line = "./offline_packages/anyio-4.5.2-py3-none-any.whl"
23+
req = Requirement.parse(line)
24+
25+
self.assertEqual(req.name, "anyio")
26+
self.assertTrue(req.local_file)
27+
self.assertEqual(req.path, line)
28+
29+
def test_whl_file_without_file_existence_check(self):
30+
"""Test that .whl files are recognized even when file doesn't exist
31+
32+
This is the key test for the bug fix - previously the parser would
33+
only recognize .whl files if os.path.isfile(line) returned True.
34+
With --all-projects, the working directory context differs, causing
35+
the file check to fail and the parser to throw an error.
36+
37+
The fix checks for .whl extension first, before checking file existence.
38+
"""
39+
# Use a path that definitely doesn't exist
40+
line = "nonexistent/path/to/package-1.0.0-py3-none-any.whl"
41+
req = Requirement.parse(line)
42+
43+
self.assertEqual(req.name, "package")
44+
self.assertTrue(req.local_file)
45+
self.assertEqual(req.path, line)
46+
47+
def test_whl_file_with_complex_name(self):
48+
"""Test parsing .whl file with complex package name"""
49+
line = "./lib/my_complex_package-2.1.0-cp39-cp39-linux_x86_64.whl"
50+
req = Requirement.parse(line)
51+
52+
self.assertEqual(req.name, "my_complex_package")
53+
self.assertTrue(req.local_file)
54+
self.assertEqual(req.path, line)
55+
56+
def test_whl_file_uppercase_extension(self):
57+
"""Test that .WHL extension (uppercase) is recognized as local file
58+
59+
Note: The name extraction may not work due to case-sensitive regex,
60+
but the file is still recognized as a local .whl file.
61+
"""
62+
line = "packages/SomePackage-1.0.0-py3-none-any.WHL"
63+
req = Requirement.parse(line)
64+
65+
# Name extraction may fail due to case-sensitive regex, but that's ok
66+
# The important part is that it's recognized as a local_file
67+
self.assertTrue(req.local_file)
68+
self.assertEqual(req.path, line)
69+
70+
def test_whl_file_mixed_case_extension(self):
71+
"""Test that .Whl extension (mixed case) is recognized as local file
72+
73+
Note: The name extraction may not work due to case-sensitive regex,
74+
but the file is still recognized as a local .whl file.
75+
"""
76+
line = "packages/AnotherPackage-2.0.0-py3-none-any.Whl"
77+
req = Requirement.parse(line)
78+
79+
# Name extraction may fail due to case-sensitive regex, but that's ok
80+
# The important part is that it's recognized as a local_file
81+
self.assertTrue(req.local_file)
82+
self.assertEqual(req.path, line)
83+
84+
85+
if __name__ == '__main__':
86+
unittest.main()

0 commit comments

Comments
 (0)