Skip to content

Commit d56d3bd

Browse files
committed
Refactor code to get requirements from sdist
Signed-off-by: Tushar Goel <[email protected]>
1 parent f633626 commit d56d3bd

11 files changed

+10625
-8495
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ v0.9.0
99
- Add support for setuptools.setup in live evaluation.
1010
- Do not fail if no direct dependencies are provided.
1111

12+
1213
v0.8.5
1314
------
1415

src/python_inspector/api.py

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from typing import NamedTuple
1616
from typing import Sequence
1717

18+
from packageurl import PackageURL
1819
from packaging.requirements import Requirement
1920
from resolvelib import BaseReporter
2021
from resolvelib import Resolver
@@ -33,12 +34,10 @@
3334
from python_inspector.resolution import PythonInputProvider
3435
from python_inspector.resolution import format_pdt_tree
3536
from python_inspector.resolution import format_resolution
36-
from python_inspector.resolution import get_deps_from_distribution
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 parse_deps_from_setup_py_insecurely
41-
from python_inspector.utils import contain_string
40+
from python_inspector.resolution import get_requirements_from_python_manifest
4241
from python_inspector.utils_pypi import PYPI_SIMPLE_URL
4342
from python_inspector.utils_pypi import Environment
4443

@@ -64,7 +63,7 @@ def to_dict(self):
6463
}
6564

6665

67-
def resolver_api(
66+
def resolve_dependencies(
6867
requirement_files=tuple(),
6968
setup_py_file=None,
7069
specifiers=tuple(),
@@ -165,34 +164,14 @@ def resolver_api(
165164
direct_dependencies.append(dep)
166165

167166
if not package_data.dependencies:
168-
has_deps = False
169-
if contain_string(string="requirements.txt", files=[setup_py_file]):
170-
# Look in requirements file if and only if thy are refered in setup.py or setup.cfg
171-
# And no deps have been yielded by requirements file.
172-
173-
location = os.path.dirname(setup_py_file)
174-
requirement_location = os.path.join(
175-
location,
176-
"requirements.txt",
177-
)
178-
deps = get_deps_from_distribution(
179-
handler=PipRequirementsFileHandler,
180-
location=requirement_location,
181-
)
182-
if deps:
183-
setup_py_file_deps = list(deps)
184-
has_deps = True
185-
direct_dependencies.extend(deps)
186-
187-
if not has_deps and contain_string(string="_require", files=[setup_py_file]):
188-
if analyze_setup_py_insecurely:
189-
insecure_setup_py_deps = list(
190-
parse_deps_from_setup_py_insecurely(setup_py=setup_py_file)
191-
)
192-
setup_py_file_deps = insecure_setup_py_deps
193-
direct_dependencies.extend(insecure_setup_py_deps)
194-
else:
195-
printer("Unable to collect setup.py dependencies securely")
167+
reqs = get_requirements_from_python_manifest(
168+
sdist_location=os.path.dirname(setup_py_file),
169+
setup_py_location=setup_py_file,
170+
files=[setup_py_file],
171+
analyze_setup_py_insecurely=analyze_setup_py_insecurely,
172+
)
173+
setup_py_file_deps = list(get_dependent_packages_from_reqs(reqs))
174+
direct_dependencies.extend(setup_py_file_deps)
196175

197176
package_data.dependencies = setup_py_file_deps
198177
file_package_data = [package_data.to_dict()]
@@ -267,7 +246,12 @@ def resolver_api(
267246

268247
for package in purls:
269248
packages.extend(
270-
list(get_pypi_data_from_purl(package, repos=repos, environment=environment)),
249+
[
250+
pkg.to_dict()
251+
for pkg in list(
252+
get_pypi_data_from_purl(package, repos=repos, environment=environment)
253+
)
254+
],
271255
)
272256

273257
if verbose:
@@ -371,3 +355,18 @@ def get_requirements_from_direct_dependencies(
371355
else:
372356
if req.marker.evaluate(environment_marker):
373357
yield req
358+
359+
360+
def get_dependent_packages_from_reqs(requirements: List[Requirement]):
361+
for req in requirements:
362+
yield DependentPackage(
363+
purl=str(
364+
PackageURL(
365+
type="pypi",
366+
name=req.name,
367+
)
368+
),
369+
extracted_requirement=str(req),
370+
scope="install",
371+
is_runtime=False,
372+
)

src/python_inspector/resolution.py

Lines changed: 88 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,59 @@ def remove_extras(identifier: str) -> str:
253253
return name
254254

255255

256+
def get_reqs_from_requirements_file_in_sdist(sdist_location: str, files: str) -> List[Requirement]:
257+
"""
258+
Return a list of parsed requirements from the ``sdist_location`` sdist location
259+
"""
260+
if contain_string(string="requirements.txt", files=files):
261+
requirement_location = os.path.join(
262+
sdist_location,
263+
"requirements.txt",
264+
)
265+
yield from get_requirements_from_distribution(
266+
handler=PipRequirementsFileHandler,
267+
location=requirement_location,
268+
)
269+
270+
271+
def get_reqs_insecurely(setup_py_location):
272+
"""
273+
Return a list of Requirement(s) from ``setup_py_location`` setup.py file location
274+
"""
275+
yield from parse_reqs_from_setup_py_insecurely(setup_py=setup_py_location)
276+
277+
278+
def get_requirements_from_python_manifest(
279+
sdist_location: str, setup_py_location: str, files: List, analyze_setup_py_insecurely: bool
280+
) -> List[Requirement]:
281+
"""
282+
Return a list of parsed requirements from the ``sdist_location`` sdist location
283+
"""
284+
# Look in requirements file if and only if thy are refered in setup.py or setup.cfg
285+
# And no deps have been yielded by requirements file.
286+
requirements = list(
287+
get_reqs_from_requirements_file_in_sdist(
288+
files=files,
289+
sdist_location=sdist_location,
290+
)
291+
)
292+
if requirements:
293+
yield from requirements
294+
295+
elif contain_string(string="_require", files=[setup_py_location]):
296+
if analyze_setup_py_insecurely:
297+
yield from get_reqs_insecurely(
298+
setup_py_location=setup_py_location,
299+
)
300+
301+
else:
302+
# We should not raise exception here as we may have a setup.py that does not
303+
# have any dependencies. We should not fail in this case.
304+
raise Exception(
305+
f"Unable to collect setup.py dependencies securely: {setup_py_location}"
306+
)
307+
308+
256309
DEFAULT_ENVIRONMENT = utils_pypi.Environment.from_pyver_and_os(
257310
python_version="38", operating_system="linux"
258311
)
@@ -376,12 +429,12 @@ def get_requirements_for_package_from_pypi_simple(
376429
if wheels:
377430
for wheel in wheels:
378431
wheel_location = os.path.join(utils_pypi.CACHE_THIRDPARTY_DIR, wheel)
379-
deps = get_requirements_from_distribution(
432+
requirements = get_requirements_from_distribution(
380433
handler=PypiWheelHandler,
381434
location=wheel_location,
382435
)
383-
if deps:
384-
yield from deps
436+
if requirements:
437+
yield from requirements
385438
# We are only looking at the first wheel and not other wheels
386439
break
387440

@@ -391,11 +444,36 @@ def get_requirements_for_package_from_pypi_simple(
391444
)
392445
if not sdist_location:
393446
return
394-
yield from get_setup_dependencies(
395-
location=sdist_location,
396-
analyze_setup_py_insecurely=self.analyze_setup_py_insecurely,
447+
448+
setup_py_location = os.path.join(
449+
sdist_location,
450+
"setup.py",
451+
)
452+
setup_cfg_location = os.path.join(
453+
sdist_location,
454+
"setup.cfg",
397455
)
398456

457+
requirements = list(
458+
get_setup_requirements(
459+
sdist_location=sdist_location,
460+
setup_py_location=setup_py_location,
461+
setup_cfg_location=setup_cfg_location,
462+
)
463+
)
464+
if requirements:
465+
yield from requirements
466+
else:
467+
# Look in requirements file if and only if thy are refered in setup.py or setup.cfg
468+
# And no deps have been yielded by requirements file
469+
470+
yield from get_requirements_from_python_manifest(
471+
sdist_location=sdist_location,
472+
setup_py_location=setup_py_location,
473+
files=[setup_cfg_location, setup_py_location],
474+
analyze_setup_py_insecurely=self.analyze_setup_py_insecurely,
475+
)
476+
399477
def get_requirements_for_package_from_pypi_json_api(
400478
self, purl: PackageURL
401479
) -> List[Requirement]:
@@ -654,7 +732,7 @@ def get_package_list(results):
654732
return list(sorted(packages))
655733

656734

657-
def get_setup_dependencies(location, analyze_setup_py_insecurely=False, use_requirements=True):
735+
def get_setup_requirements(sdist_location: str, setup_py_location: str, setup_cfg_location: str):
658736
"""
659737
Yield Requirement(s) from Pypi in the ``location`` directory that contains
660738
a setup.py and/or a setup.cfg and optionally a requirements.txt file if
@@ -663,17 +741,8 @@ def get_setup_dependencies(location, analyze_setup_py_insecurely=False, use_requ
663741
``analyze_setup_py_insecurely`` is True.
664742
"""
665743

666-
setup_py_location = os.path.join(
667-
location,
668-
"setup.py",
669-
)
670-
setup_cfg_location = os.path.join(
671-
location,
672-
"setup.cfg",
673-
)
674-
675744
if not os.path.exists(setup_py_location) and not os.path.exists(setup_cfg_location):
676-
raise Exception(f"No setup.py or setup.cfg found in pypi sdist {location}")
745+
raise Exception(f"No setup.py or setup.cfg found in pypi sdist {sdist_location}")
677746

678747
# Some commonon packages like flask may have some dependencies in setup.cfg
679748
# and some dependencies in setup.py. We are going to check both.
@@ -682,43 +751,9 @@ def get_setup_dependencies(location, analyze_setup_py_insecurely=False, use_requ
682751
SetupCfgHandler: setup_cfg_location,
683752
}
684753

685-
# Set to True if we found any dependencies in setup.py or setup.cfg
686-
has_deps = False
687-
688754
for handler, location in location_by_sdist_parser.items():
689-
deps = get_requirements_from_distribution(
755+
reqs = get_requirements_from_distribution(
690756
handler=handler,
691757
location=location,
692758
)
693-
if deps:
694-
has_deps = True
695-
yield from deps
696-
697-
if (
698-
use_requirements
699-
and not has_deps
700-
and contain_string(string="requirements.txt", files=[setup_py_location, setup_cfg_location])
701-
):
702-
# Look in requirements file if and only if thy are refered in setup.py or setup.cfg
703-
# And no deps have been yielded by requirements file.
704-
requirement_location = os.path.join(
705-
location,
706-
"requirements.txt",
707-
)
708-
deps = get_requirements_from_distribution(
709-
handler=PipRequirementsFileHandler,
710-
location=requirement_location,
711-
)
712-
if deps:
713-
has_deps = True
714-
yield from deps
715-
716-
if not has_deps and contain_string(
717-
string="_require", files=[setup_py_location, setup_cfg_location]
718-
):
719-
if analyze_setup_py_insecurely:
720-
yield from parse_reqs_from_setup_py_insecurely(setup_py=setup_py_location)
721-
else:
722-
# We should not raise exception here as we may have a setup.py that does not
723-
# have any dependencies. We should not fail in this case.
724-
print(f"Unable to collect setup.py dependencies securely: {setup_py_location}")
759+
yield from reqs

src/python_inspector/resolve_cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import click
1515

1616
from python_inspector import utils_pypi
17-
from python_inspector.api import resolver_api
17+
from python_inspector.api import resolve_dependencies as resolver_api
1818
from python_inspector.cli_utils import FileOptionType
1919
from python_inspector.utils import write_output_in_file
2020

0 commit comments

Comments
 (0)