Skip to content

Commit cf8456f

Browse files
committed
Merge calizarr/fix-180-env-vars
Signed-off-by: Philippe Ombredanne <[email protected]>
2 parents eb15ffa + 056a825 commit cf8456f

12 files changed

+929
-233
lines changed

resolve_cli.python

Lines changed: 403 additions & 0 deletions
Large diffs are not rendered by default.

src/python_inspector/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# ScanCode is a trademark of nexB Inc.
66
# SPDX-License-Identifier: Apache-2.0
77
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
8-
# See https://aboutcode-orgnexB/python-inspector for support or download.
8+
# See https://aboutcode-org/python-inspector for support or download.
99
# See https://aboutcode.org for more information about nexB OSS projects.
1010
#
1111
import asyncio

src/python_inspector/resolve_cli.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def print_version(ctx, param, value):
9191
@click.option(
9292
"--index-url",
9393
"index_urls",
94+
envvar="PYINSP_INDEX_URL",
9495
type=str,
9596
metavar="INDEX",
9697
show_default=True,
@@ -122,6 +123,7 @@ def print_version(ctx, param, value):
122123
"--netrc",
123124
"netrc_file",
124125
type=click.Path(exists=True, readable=True, path_type=str, dir_okay=False),
126+
envvar="PYINSP_NETRC_FILE",
125127
metavar="NETRC-FILE",
126128
hidden=True,
127129
required=False,
@@ -163,6 +165,7 @@ def print_version(ctx, param, value):
163165
)
164166
@click.option(
165167
"--verbose",
168+
envvar="PYINSP_VERBOSE",
166169
is_flag=True,
167170
help="Enable verbose debug output.",
168171
)

src/python_inspector/utils_pypi.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ async def download_wheel(
252252
)
253253
continue
254254
for wheel in supported_and_valid_wheels:
255+
wheel.credentials = repo.credentials
255256
fetched_wheel_filename = await wheel.download(
256257
dest_dir=dest_dir,
257258
verbose=verbose,

test_resolution2.py.foo

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
#
4+
# Copyright (c) nexB Inc. and others. All rights reserved.
5+
# ScanCode is a trademark of nexB Inc.
6+
# SPDX-License-Identifier: Apache-2.0
7+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
8+
# See https://github.com/nexB/python-inspector for support or download.
9+
# See https://aboutcode.org for more information about nexB OSS projects.
10+
#
11+
import os
12+
from unittest.mock import patch
13+
14+
import packvers
15+
import pytest
16+
from commoncode.system import on_mac
17+
from commoncode.testcase import FileDrivenTesting
18+
from packvers.requirements import Requirement
19+
from test_cli import check_data_results
20+
21+
from _packagedcode import models
22+
from python_inspector.api import get_resolved_dependencies
23+
from python_inspector.error import NoVersionsFound
24+
from python_inspector.resolution import PythonInputProvider
25+
from python_inspector.resolution import get_requirements_from_dependencies
26+
from python_inspector.resolution import get_requirements_from_python_manifest
27+
from python_inspector.resolution import is_valid_version
28+
from python_inspector.resolution import parse_reqs_from_setup_py_insecurely
29+
from python_inspector.utils_pypi import Environment
30+
from python_inspector.utils_pypi import PypiSimpleRepository
31+
from python_inspector.utils_pypi import get_current_indexes
32+
33+
# Used for tests to regenerate fixtures with regen=True
34+
REGEN_TEST_FIXTURES = os.getenv("PYINSP_REGEN_TEST_FIXTURES", False)
35+
36+
setup_test_env = FileDrivenTesting()
37+
setup_test_env.test_data_dir = os.path.join(os.path.dirname(__file__), "data")
38+
39+
40+
def check_get_resolved_dependencies(
41+
requirement: Requirement,
42+
expected_file,
43+
python_version,
44+
operating_system,
45+
repos=None,
46+
as_tree=False,
47+
regen=REGEN_TEST_FIXTURES,
48+
):
49+
env = Environment(python_version=python_version, operating_system=operating_system)
50+
51+
results = list(
52+
get_resolved_dependencies(
53+
requirements=[requirement],
54+
environment=env,
55+
repos=repos or get_current_indexes(),
56+
as_tree=as_tree,
57+
)
58+
)
59+
check_data_results(results=results, expected_file=expected_file, regen=regen)
60+
61+
62+
@pytest.mark.online
63+
def test_get_resolved_dependencies_with_flask_and_python_310():
64+
req = Requirement("flask==2.1.2")
65+
req.is_requirement_resolved = True
66+
67+
expected_file = setup_test_env.get_test_loc(
68+
"resolved_deps/flask-310-expected.json", must_exist=False
69+
)
70+
71+
check_get_resolved_dependencies(
72+
req,
73+
expected_file=expected_file,
74+
python_version="310",
75+
operating_system="linux",
76+
as_tree=False,
77+
)
78+
79+
80+
@pytest.mark.online
81+
def test_get_resolved_dependencies_with_flask_and_python_310_windows():
82+
req = Requirement("flask==2.1.2")
83+
req.is_requirement_resolved = True
84+
85+
expected_file = setup_test_env.get_test_loc(
86+
"resolved_deps/flask-310-win-expected.json", must_exist=False
87+
)
88+
89+
check_get_resolved_dependencies(
90+
req,
91+
expected_file=expected_file,
92+
python_version="310",
93+
operating_system="windows",
94+
as_tree=False,
95+
)
96+
97+
98+
@pytest.mark.online
99+
def test_get_resolved_dependencies_with_flask_and_python_36():
100+
req = Requirement("flask")
101+
req.is_requirement_resolved = False
102+
103+
expected_file = setup_test_env.get_test_loc(
104+
"resolved_deps/flask-36-expected.json", must_exist=False
105+
)
106+
107+
check_get_resolved_dependencies(
108+
req,
109+
expected_file=expected_file,
110+
python_version="36",
111+
operating_system="linux",
112+
as_tree=False,
113+
)
114+
115+
116+
@pytest.mark.online
117+
def test_get_resolved_dependencies_with_tilde_requirement_using_json_api():
118+
req = Requirement("flask~=2.1.2")
119+
req.is_requirement_resolved = False
120+
121+
expected_file = setup_test_env.get_test_loc(
122+
"resolved_deps/flask-39-expected.json", must_exist=False
123+
)
124+
125+
check_get_resolved_dependencies(
126+
req,
127+
expected_file=expected_file,
128+
python_version="39",
129+
operating_system="linux",
130+
as_tree=False,
131+
)
132+
133+
134+
@pytest.mark.online
135+
@pytest.mark.skipif(on_mac, reason="torch is only available for linux and windows.")
136+
def test_get_resolved_dependencies_for_version_containing_local_version_identifier():
137+
req = Requirement("torchcodec==0.2.0+cu124")
138+
req.is_requirement_resolved = True
139+
140+
repos = [PypiSimpleRepository(index_url="https://download.pytorch.org/whl")]
141+
expected_file = setup_test_env.get_test_loc(
142+
"resolved_deps/torch-312-expected.json", must_exist=False
143+
)
144+
145+
check_get_resolved_dependencies(
146+
req,
147+
expected_file=expected_file,
148+
python_version="312",
149+
operating_system="linux",
150+
repos=repos,
151+
as_tree=False,
152+
)
153+
154+
155+
@pytest.mark.online
156+
def test_without_supported_wheels():
157+
req = Requirement("autobahn==22.3.2")
158+
req.is_requirement_resolved = True
159+
expected_file = setup_test_env.get_test_loc(
160+
"resolved_deps/autobahn-310-expected.json", must_exist=False
161+
)
162+
163+
check_get_resolved_dependencies(
164+
req,
165+
expected_file=expected_file,
166+
python_version="39",
167+
operating_system="linux",
168+
as_tree=False,
169+
)
170+
171+
172+
def test_is_valid_version():
173+
parsed_version = packvers.version.parse("2.1.2")
174+
requirements = {"flask": [Requirement("flask>2.0.0")]}
175+
bad_versions = []
176+
identifier = "flask"
177+
assert is_valid_version(parsed_version, requirements, identifier, bad_versions)
178+
179+
180+
def test_is_valid_version_with_no_specifier():
181+
parsed_version = packvers.version.parse("2.1.2")
182+
requirements = {"flask": [Requirement("flask")]}
183+
bad_versions = []
184+
identifier = "flask"
185+
assert is_valid_version(parsed_version, requirements, identifier, bad_versions)
186+
187+
188+
def test_is_valid_version_with_no_specifier_and_pre_release():
189+
parsed_version = packvers.version.parse("1.0.0b4")
190+
requirements = {"flask": [Requirement("flask")]}
191+
bad_versions = []
192+
identifier = "flask"
193+
assert is_valid_version(parsed_version, requirements, identifier, bad_versions)
194+
195+
196+
def test_get_requirements_from_dependencies():
197+
dependencies = [
198+
models.DependentPackage(
199+
purl="pkg:pypi/django",
200+
scope="install",
201+
is_runtime=True,
202+
is_optional=False,
203+
is_resolved=False,
204+
extracted_requirement="django>=1.11.11",
205+
extra_data=dict(
206+
is_editable=False,
207+
link=None,
208+
hash_options=[],
209+
is_constraint=False,
210+
is_archive=False,
211+
is_wheel=False,
212+
is_url=False,
213+
is_vcs_url=False,
214+
is_name_at_url=False,
215+
is_local_path=False,
216+
),
217+
)
218+
]
219+
220+
requirements = [str(r) for r in get_requirements_from_dependencies(dependencies)]
221+
222+
assert requirements == ["django>=1.11.11"]
223+
224+
225+
def test_get_requirements_from_dependencies_with_empty_list():
226+
assert list(get_requirements_from_dependencies(dependencies=[])) == []
227+
228+
229+
def test_get_requirements_from_dependencies_with_editable_requirements():
230+
dependencies = [
231+
models.DependentPackage(
232+
purl="pkg:pypi/django",
233+
scope="install",
234+
is_runtime=True,
235+
is_optional=False,
236+
is_resolved=False,
237+
extracted_requirement="django>=1.11.11",
238+
extra_data=dict(
239+
is_editable=True,
240+
link=None,
241+
hash_options=[],
242+
is_constraint=False,
243+
is_archive=False,
244+
is_wheel=False,
245+
is_url=False,
246+
is_vcs_url=False,
247+
is_name_at_url=False,
248+
is_local_path=False,
249+
),
250+
)
251+
]
252+
253+
requirements = [str(r) for r in get_requirements_from_dependencies(dependencies)]
254+
255+
assert requirements == []
256+
257+
258+
def test_get_requirements_from_python_manifest_securely():
259+
sdist_location = "tests/data/secure-setup"
260+
setup_py_emptyrequires = "setup-emptyrequires.py"
261+
setup_py_norequires = "setup-norequires.py"
262+
setup_py_requires = "setup-requires.py"
263+
analyze_setup_py_insecurely = False
264+
try:
265+
ret = list(
266+
get_requirements_from_python_manifest(
267+
sdist_location,
268+
sdist_location + "/" + setup_py_norequires,
269+
[sdist_location + "/" + setup_py_norequires],
270+
analyze_setup_py_insecurely,
271+
)
272+
)
273+
assert ret == []
274+
except Exception:
275+
pytest.fail("Failure parsing setup.py where requirements are not provided.")
276+
try:
277+
ret = list(
278+
get_requirements_from_python_manifest(
279+
sdist_location,
280+
sdist_location + "/" + setup_py_emptyrequires,
281+
[sdist_location + "/" + setup_py_emptyrequires],
282+
analyze_setup_py_insecurely,
283+
)
284+
)
285+
assert ret == []
286+
except Exception:
287+
pytest.fail("Failure getting empty requirements securely from setup.py.")
288+
with pytest.raises(Exception):
289+
ret = list(
290+
get_requirements_from_python_manifest(
291+
sdist_location,
292+
sdist_location + "/" + setup_py_requires,
293+
[sdist_location + "/" + setup_py_requires],
294+
analyze_setup_py_insecurely,
295+
).next()
296+
)
297+
298+
299+
def test_setup_py_parsing_insecure():
300+
setup_py_file = setup_test_env.get_test_loc("insecure-setup/setup.py")
301+
reqs = [str(req) for req in list(parse_reqs_from_setup_py_insecurely(setup_py=setup_py_file))]
302+
assert reqs == ["isodate", "pyparsing", "six"]
303+
304+
305+
def test_setup_py_parsing_insecure_testpkh():
306+
setup_py_file = setup_test_env.get_test_loc("insecure-setup-2/setup.py")
307+
reqs = [str(req) for req in list(parse_reqs_from_setup_py_insecurely(setup_py=setup_py_file))]
308+
assert reqs == [
309+
"CairoSVG<2.0.0,>=1.0.20",
310+
"click>=5.0.0",
311+
"invenio[auth,base,metadata]>=3.0.0",
312+
"invenio-records==1.0.*,>=1.0.0",
313+
"mock>=1.3.0",
314+
]
315+
316+
317+
@patch("python_inspector.resolution.PythonInputProvider.get_versions_for_package")
318+
def test_iter_matches(mock_versions):
319+
repos = get_current_indexes()
320+
mock_versions.return_value = []
321+
provider = PythonInputProvider(repos=repos)
322+
with pytest.raises(NoVersionsFound):
323+
list(provider._iter_matches("foo-bar", {"foo-bar": []}, {"foo-bar": []}))

0 commit comments

Comments
 (0)