Skip to content

Commit e2bf591

Browse files
committed
Fix resolution of setup files which partially have dynamic dependencies
Signed-off-by: Tushar Goel <[email protected]>
1 parent 271921d commit e2bf591

File tree

6 files changed

+207
-31
lines changed

6 files changed

+207
-31
lines changed

src/python_inspector/api.py

+22-13
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from python_inspector.resolution import get_environment_marker_from_environment
3838
from python_inspector.resolution import get_package_list
3939
from python_inspector.resolution import get_python_version_from_env_tag
40+
from python_inspector.resolution import get_reqs_insecurely
4041
from python_inspector.resolution import get_requirements_from_python_manifest
4142
from python_inspector.utils_pypi import PLATFORMS_BY_OS
4243
from python_inspector.utils_pypi import PYPI_SIMPLE_URL
@@ -175,22 +176,30 @@ def resolve_dependencies(
175176
f"is not compatible with setup.py {setup_py_file} "
176177
f"python_requires {python_requires}",
177178
)
178-
179-
setup_py_file_deps = package_data.dependencies
180-
for dep in package_data.dependencies:
181-
# TODO : we need to handle to all the scopes
182-
if dep.scope == "install":
183-
direct_dependencies.append(dep)
184-
185-
if not package_data.dependencies:
186-
reqs = get_requirements_from_python_manifest(
187-
sdist_location=os.path.dirname(setup_py_file),
188-
setup_py_location=setup_py_file,
189-
files=[setup_py_file],
190-
analyze_setup_py_insecurely=analyze_setup_py_insecurely,
179+
if analyze_setup_py_insecurely:
180+
reqs = list(
181+
get_reqs_insecurely(
182+
setup_py_location=setup_py_file,
183+
)
191184
)
192185
setup_py_file_deps = list(get_dependent_packages_from_reqs(reqs))
193186
direct_dependencies.extend(setup_py_file_deps)
187+
else:
188+
setup_py_file_deps = package_data.dependencies
189+
for dep in package_data.dependencies:
190+
# TODO : we need to handle to all the scopes
191+
if dep.scope == "install":
192+
direct_dependencies.append(dep)
193+
194+
if not package_data.dependencies:
195+
reqs = get_requirements_from_python_manifest(
196+
sdist_location=os.path.dirname(setup_py_file),
197+
setup_py_location=setup_py_file,
198+
files=[setup_py_file],
199+
analyze_setup_py_insecurely=analyze_setup_py_insecurely,
200+
)
201+
setup_py_file_deps = list(get_dependent_packages_from_reqs(reqs))
202+
direct_dependencies.extend(setup_py_file_deps)
194203

195204
package_data.dependencies = setup_py_file_deps
196205
file_package_data = [package_data.to_dict()]

src/python_inspector/resolution.py

+20-15
Original file line numberDiff line numberDiff line change
@@ -493,25 +493,30 @@ def get_requirements_for_package_from_pypi_simple(
493493
"setup.cfg",
494494
)
495495

496-
requirements = list(
497-
get_setup_requirements(
498-
sdist_location=sdist_location,
496+
if self.analyze_setup_py_insecurely:
497+
yield from get_reqs_insecurely(
499498
setup_py_location=setup_py_location,
500-
setup_cfg_location=setup_cfg_location,
501499
)
502-
)
503-
if requirements:
504-
yield from requirements
505500
else:
506-
# Look in requirements file if and only if thy are refered in setup.py or setup.cfg
507-
# And no deps have been yielded by requirements file
508-
509-
yield from get_requirements_from_python_manifest(
510-
sdist_location=sdist_location,
511-
setup_py_location=setup_py_location,
512-
files=[setup_cfg_location, setup_py_location],
513-
analyze_setup_py_insecurely=self.analyze_setup_py_insecurely,
501+
requirements = list(
502+
get_setup_requirements(
503+
sdist_location=sdist_location,
504+
setup_py_location=setup_py_location,
505+
setup_cfg_location=setup_cfg_location,
506+
)
514507
)
508+
if requirements:
509+
yield from requirements
510+
else:
511+
# Look in requirements file if and only if thy are refered in setup.py or setup.cfg
512+
# And no deps have been yielded by requirements file
513+
514+
yield from get_requirements_from_python_manifest(
515+
sdist_location=sdist_location,
516+
setup_py_location=setup_py_location,
517+
files=[setup_cfg_location, setup_py_location],
518+
analyze_setup_py_insecurely=self.analyze_setup_py_insecurely,
519+
)
515520

516521
def get_requirements_for_package_from_pypi_json_api(
517522
self, purl: PackageURL

tests/data/partial-setup.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from setuptools import setup
2+
3+
semver_version = "2.13.0"
4+
5+
setup(
6+
name="example",
7+
version="0.0.1",
8+
install_requires=[
9+
f"semver @ git+https://github.com/python-semver/python-semver.git@{semver_version}",
10+
],
11+
extras_require={"test": ["botocore==1.27.76"]},
12+
)

tests/data/setup/spdx-setup.py-expected.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
"purl": "pkg:pypi/ply",
7676
"extracted_requirement": "ply",
7777
"scope": "install",
78-
"is_runtime": true,
78+
"is_runtime": false,
7979
"is_optional": false,
8080
"is_resolved": false,
8181
"resolved_package": {},
@@ -85,7 +85,7 @@
8585
"purl": "pkg:pypi/rdflib",
8686
"extracted_requirement": "rdflib",
8787
"scope": "install",
88-
"is_runtime": true,
88+
"is_runtime": false,
8989
"is_optional": false,
9090
"is_resolved": false,
9191
"resolved_package": {},
@@ -95,7 +95,7 @@
9595
"purl": "pkg:pypi/six",
9696
"extracted_requirement": "six",
9797
"scope": "install",
98-
"is_runtime": true,
98+
"is_runtime": false,
9999
"is_optional": false,
100100
"is_resolved": false,
101101
"resolved_package": {},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
{
2+
"files": [
3+
{
4+
"type": "file",
5+
"path": "/home/tg1999/Desktop/python-inspector-1/tests/data/partial-setup.py",
6+
"package_data": [
7+
{
8+
"type": "pypi",
9+
"namespace": null,
10+
"name": "example",
11+
"version": "0.0.1",
12+
"qualifiers": {},
13+
"subpath": null,
14+
"primary_language": "Python",
15+
"description": "",
16+
"release_date": null,
17+
"parties": [],
18+
"keywords": [],
19+
"homepage_url": null,
20+
"download_url": null,
21+
"size": null,
22+
"sha1": null,
23+
"md5": null,
24+
"sha256": null,
25+
"sha512": null,
26+
"bug_tracking_url": null,
27+
"code_view_url": null,
28+
"vcs_url": null,
29+
"copyright": null,
30+
"license_expression": null,
31+
"declared_license": {},
32+
"notice_text": null,
33+
"source_packages": [],
34+
"file_references": [],
35+
"extra_data": {},
36+
"dependencies": [
37+
{
38+
"purl": "pkg:pypi/semver",
39+
"extracted_requirement": "semver",
40+
"scope": "install",
41+
"is_runtime": false,
42+
"is_optional": false,
43+
"is_resolved": false,
44+
"resolved_package": {},
45+
"extra_data": {}
46+
}
47+
],
48+
"repository_homepage_url": "https://pypi.org/project/example",
49+
"repository_download_url": "https://pypi.org/packages/source/e/example/example-0.0.1.tar.gz",
50+
"api_data_url": "https://pypi.org/pypi/example/0.0.1/json",
51+
"datasource_id": "pypi_setup_py",
52+
"purl": "pkg:pypi/[email protected]"
53+
}
54+
]
55+
}
56+
],
57+
"packages": [
58+
{
59+
"type": "pypi",
60+
"namespace": null,
61+
"name": "semver",
62+
"version": "2.13.0",
63+
"qualifiers": {},
64+
"subpath": null,
65+
"primary_language": "Python",
66+
"description": "Python helper for Semantic Versioning (http://semver.org/)\nQuickstart\n==========\n\n.. teaser-begin\n\nA Python module for `semantic versioning`_. Simplifies comparing versions.\n\n|build-status| |python-support| |downloads| |license| |docs| |black|\n\n.. teaser-end\n\n.. warning::\n\n As anything comes to an end, this project will focus on Python 3.x only.\n New features and bugfixes will be integrated into the 3.x.y branch only.\n\n Major version 3 of semver will contain some incompatible changes:\n\n * removes support for Python 2.7 and 3.3\n * removes deprecated functions.\n\n The last version of semver which supports Python 2.7 and 3.4 will be\n 2.10.x. However, keep in mind, version 2.10.x is frozen: no new\n features nor backports will be integrated.\n\n We recommend to upgrade your workflow to Python 3.x to gain support,\n bugfixes, and new features.\n\nThe module follows the ``MAJOR.MINOR.PATCH`` style:\n\n* ``MAJOR`` version when you make incompatible API changes,\n* ``MINOR`` version when you add functionality in a backwards compatible manner, and\n* ``PATCH`` version when you make backwards compatible bug fixes.\n\nAdditional labels for pre-release and build metadata are supported.\n\nTo import this library, use:\n\n.. code-block:: python\n\n >>> import semver\n\nWorking with the library is quite straightforward. To turn a version string into the\ndifferent parts, use the ``semver.VersionInfo.parse`` function:\n\n.. code-block:: python\n\n >>> ver = semver.VersionInfo.parse('1.2.3-pre.2+build.4')\n >>> ver.major\n 1\n >>> ver.minor\n 2\n >>> ver.patch\n 3\n >>> ver.prerelease\n 'pre.2'\n >>> ver.build\n 'build.4'\n\nTo raise parts of a version, there are a couple of functions available for\nyou. The function ``semver.VersionInfo.bump_major`` leaves the original object untouched, but\nreturns a new ``semver.VersionInfo`` instance with the raised major part:\n\n.. code-block:: python\n\n >>> ver = semver.VersionInfo.parse(\"3.4.5\")\n >>> ver.bump_major()\n VersionInfo(major=4, minor=0, patch=0, prerelease=None, build=None)\n\nIt is allowed to concatenate different \"bump functions\":\n\n.. code-block:: python\n\n >>> ver.bump_major().bump_minor()\n VersionInfo(major=4, minor=1, patch=0, prerelease=None, build=None)\n\nTo compare two versions, semver provides the ``semver.compare`` function.\nThe return value indicates the relationship between the first and second\nversion:\n\n.. code-block:: python\n\n >>> semver.compare(\"1.0.0\", \"2.0.0\")\n -1\n >>> semver.compare(\"2.0.0\", \"1.0.0\")\n 1\n >>> semver.compare(\"2.0.0\", \"2.0.0\")\n 0\n\n\nThere are other functions to discover. Read on!\n\n\n.. |latest-version| image:: https://img.shields.io/pypi/v/semver.svg\n :alt: Latest version on PyPI\n :target: https://pypi.org/project/semver\n.. |build-status| image:: https://travis-ci.com/python-semver/python-semver.svg?branch=master\n :alt: Build status\n :target: https://travis-ci.com/python-semver/python-semver\n.. |python-support| image:: https://img.shields.io/pypi/pyversions/semver.svg\n :target: https://pypi.org/project/semver\n :alt: Python versions\n.. |downloads| image:: https://img.shields.io/pypi/dm/semver.svg\n :alt: Monthly downloads from PyPI\n :target: https://pypi.org/project/semver\n.. |license| image:: https://img.shields.io/pypi/l/semver.svg\n :alt: Software license\n :target: https://github.com/python-semver/python-semver/blob/master/LICENSE.txt\n.. |docs| image:: https://readthedocs.org/projects/python-semver/badge/?version=latest\n :target: http://python-semver.readthedocs.io/en/latest/?badge=latest\n :alt: Documentation Status\n.. _semantic versioning: http://semver.org/\n.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg\n :target: https://github.com/psf/black\n :alt: Black Formatter",
67+
"release_date": "2020-10-20T20:16:54",
68+
"parties": [
69+
{
70+
"type": "person",
71+
"role": "author",
72+
"name": "Kostiantyn Rybnikov",
73+
"email": "[email protected]",
74+
"url": null
75+
}
76+
],
77+
"keywords": [
78+
"Environment :: Web Environment",
79+
"Intended Audience :: Developers",
80+
"Operating System :: OS Independent",
81+
"Programming Language :: Python",
82+
"Programming Language :: Python :: 2",
83+
"Programming Language :: Python :: 2.7",
84+
"Programming Language :: Python :: 3",
85+
"Programming Language :: Python :: 3.4",
86+
"Programming Language :: Python :: 3.5",
87+
"Programming Language :: Python :: 3.6",
88+
"Programming Language :: Python :: 3.7",
89+
"Topic :: Software Development :: Libraries :: Python Modules"
90+
],
91+
"homepage_url": "https://github.com/python-semver/python-semver",
92+
"download_url": "https://files.pythonhosted.org/packages/31/a9/b61190916030ee9af83de342e101f192bbb436c59be20a4cb0cdb7256ece/semver-2.13.0.tar.gz",
93+
"size": 45816,
94+
"sha1": null,
95+
"md5": "e98b5fb283ea84daa5195087de83ebf1",
96+
"sha256": "fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f",
97+
"sha512": null,
98+
"bug_tracking_url": "https://github.com/python-semver/python-semver/issues",
99+
"code_view_url": null,
100+
"vcs_url": null,
101+
"copyright": null,
102+
"license_expression": null,
103+
"declared_license": {
104+
"license": "BSD",
105+
"classifiers": [
106+
"License :: OSI Approved :: BSD License"
107+
]
108+
},
109+
"notice_text": null,
110+
"source_packages": [],
111+
"file_references": [],
112+
"extra_data": {},
113+
"dependencies": [],
114+
"repository_homepage_url": null,
115+
"repository_download_url": null,
116+
"api_data_url": "https://pypi.org/pypi/semver/2.13.0/json",
117+
"datasource_id": null,
118+
"purl": "pkg:pypi/[email protected]"
119+
}
120+
],
121+
"resolution": [
122+
{
123+
"package": "pkg:pypi/[email protected]",
124+
"dependencies": []
125+
}
126+
]
127+
}

tests/test_api.py

+23
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,26 @@ def test_api_with_python_311():
181181
expected_file=expected_file,
182182
clean=True,
183183
)
184+
185+
186+
def test_api_with_partial_setup_py():
187+
result_file = test_env.get_temp_file("json")
188+
setup_py_file = test_env.get_test_loc("partial-setup.py")
189+
expected_file = test_env.get_test_loc("test-api-with-partial-setup-py.json", must_exist=False)
190+
with open(result_file, "w") as result:
191+
result.write(
192+
json.dumps(
193+
resolver_api(
194+
python_version="3.11",
195+
operating_system="linux",
196+
setup_py_file=setup_py_file,
197+
prefer_source=True,
198+
analyze_setup_py_insecurely=True,
199+
).to_dict()
200+
)
201+
)
202+
check_json_results(
203+
result_file=result_file,
204+
expected_file=expected_file,
205+
clean=True,
206+
)

0 commit comments

Comments
 (0)