Skip to content

Commit a9f94ca

Browse files
committed
Change way to generate requirements
Generate requirements only from a sdist if the said package don't generate any requirements from wheels, while generating requirements from a sdist generate requirements from a requirements.txt file if nothing can't be generated from setup.cfg and setup.py and requirements.txt was used in either setup.py or setup.cfg Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com>
1 parent d29f034 commit a9f94ca

4 files changed

Lines changed: 146 additions & 969 deletions

File tree

src/python_inspector/resolution.py

Lines changed: 117 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727

2828
from _packagedcode.pypi import PipRequirementsFileHandler
2929
from _packagedcode.pypi import PypiWheelHandler
30-
from _packagedcode.pypi import PythonSdistPkgInfoFile
3130
from _packagedcode.pypi import PythonSetupPyHandler
3231
from _packagedcode.pypi import SetupCfgHandler
3332
from python_inspector import utils_pypi
@@ -53,6 +52,41 @@ def get_response(url):
5352
return None
5453

5554

55+
def get_requirements_from_distribution(handler, path, resolved_requirements):
56+
"""
57+
Return a list of requirements from a distribution.
58+
"""
59+
if not os.path.exists(path):
60+
return []
61+
deps = list(handler.parse(path))
62+
assert len(deps) == 1
63+
return list(
64+
get_requirements_from_dependencies(
65+
deps[0].dependencies, resolved_requirements=resolved_requirements
66+
)
67+
)
68+
69+
70+
def is_dep_resolved_and_in_resolved_requirements(dep, dep_purl_name, resolved_requirements):
71+
"""
72+
Return True if the given ``dep`` is resolved and is in the given ``resolved_requirements``.
73+
"""
74+
return dep.is_resolved and dep_purl_name in resolved_requirements
75+
76+
77+
def is_requirements_file_in_setup_files(setup_files):
78+
"""
79+
Return True if the given ``requirements.txt`` is in the given ``setup_files``.
80+
"""
81+
for setup_file in setup_files:
82+
if not os.path.exists(setup_file):
83+
continue
84+
with open(setup_file, encoding="utf-8") as f:
85+
if "requirements.txt" in f.read():
86+
return True
87+
return False
88+
89+
5690
def is_valid_version(parsed_version, requirements, identifier, bad_versions):
5791
"""
5892
Return True if the parsed_version is valid for the given identifier.
@@ -116,6 +150,37 @@ def fetch_and_extract_sdist(repos, candidate, python_version):
116150
return os.path.join(utils_pypi.CACHE_THIRDPARTY_DIR, "extracted_sdists", sdist_file, sdist_file)
117151

118152

153+
def get_requirements_from_dependencies(dependencies, resolved_requirements):
154+
"""
155+
Generate parsed requirements for the given ``dependencies``.
156+
"""
157+
for dep in dependencies:
158+
if not dep.purl:
159+
continue
160+
161+
if dep.scope != "install":
162+
continue
163+
164+
dep_purl = PackageURL.from_string(dep.purl)
165+
166+
dep_purl_name = packaging.utils.canonicalize_name(dep_purl.name)
167+
168+
if is_dep_resolved_and_in_resolved_requirements(
169+
dep=dep, dep_purl_name=dep_purl_name, resolved_requirements=resolved_requirements
170+
):
171+
yield packaging.requirements.Requirement(
172+
f"{str(dep_purl_name)}{str(resolved_requirements[str(dep_purl_name)])}"
173+
)
174+
continue
175+
176+
if dep.is_resolved:
177+
resolved_requirements[dep_purl_name] = f"=={dep_purl.version}"
178+
# skip the requirement starting with -- like
179+
# --editable, --requirement
180+
if not dep.extracted_requirement.startswith("-"):
181+
yield packaging.requirements.Requirement(str(dep.extracted_requirement))
182+
183+
119184
def remove_extras(identifier):
120185
"""
121186
Return the identifier without extras.
@@ -224,76 +289,64 @@ def get_requirements_for_package_from_pypi_simple(self, candidate):
224289
repos=self.repos,
225290
python_version=python_version,
226291
)
227-
for wheel in wheels:
228-
deps = list(
229-
PypiWheelHandler.parse(os.path.join(utils_pypi.CACHE_THIRDPARTY_DIR, wheel))
230-
)
231-
assert len(deps) == 1
232-
deps = deps[0].dependencies
233-
for dep in deps:
234-
if dep.scope == "install":
235-
yield packaging.requirements.Requirement(str(dep.extracted_requirement))
236-
237-
sdist_file = fetch_and_extract_sdist(
238-
repos=self.repos, candidate=candidate, python_version=python_version
239-
)
240292

241-
if sdist_file:
242-
setup_py_path = os.path.join(
243-
sdist_file,
244-
"setup.py",
245-
)
246-
setup_cfg_path = os.path.join(
247-
sdist_file,
248-
"setup.cfg",
293+
has_wheels = False
294+
295+
for wheel in wheels:
296+
wheel_path = os.path.join(utils_pypi.CACHE_THIRDPARTY_DIR, wheel)
297+
deps = get_requirements_from_distribution(
298+
handler=PypiWheelHandler,
299+
path=wheel_path,
300+
resolved_requirements=self.resolved_requirements,
249301
)
250-
pkg_info_path = os.path.dirname(sdist_file)
251-
requirement_path = os.path.join(
252-
sdist_file,
253-
"requirements.txt",
302+
if deps:
303+
has_wheels = True
304+
yield from deps
305+
print(has_wheels, candidate)
306+
if not has_wheels:
307+
sdist_file = fetch_and_extract_sdist(
308+
repos=self.repos, candidate=candidate, python_version=python_version
254309
)
255310

256-
path_by_sdist_parser = {
257-
PythonSdistPkgInfoFile: pkg_info_path,
258-
PythonSetupPyHandler: setup_py_path,
259-
SetupCfgHandler: setup_cfg_path,
260-
PipRequirementsFileHandler: requirement_path,
261-
}
262-
263-
for handler, path in path_by_sdist_parser.items():
264-
if not os.path.exists(path):
265-
continue
266-
267-
deps = list(handler.parse(path))
268-
assert len(deps) == 1
269-
270-
dependencies = deps[0].dependencies
271-
for dep in dependencies:
272-
if not dep.purl:
273-
continue
274-
275-
if dep.scope != "install":
276-
continue
277-
278-
dep_purl = PackageURL.from_string(dep.purl)
311+
if sdist_file:
312+
setup_py_path = os.path.join(
313+
sdist_file,
314+
"setup.py",
315+
)
316+
setup_cfg_path = os.path.join(
317+
sdist_file,
318+
"setup.cfg",
319+
)
279320

280-
dep_purl_name = packaging.utils.canonicalize_name(dep_purl.name)
321+
path_by_sdist_parser = {
322+
PythonSetupPyHandler: setup_py_path,
323+
SetupCfgHandler: setup_cfg_path,
324+
}
281325

282-
if self.is_dep_resolved_and_in_resolved_requirements(dep, dep_purl_name):
283-
yield packaging.requirements.Requirement(
284-
f"{str(dep_purl_name)}{str(self.resolved_requirements[str(dep_purl_name)])}"
285-
)
286-
continue
326+
deps_in_setup = False
287327

288-
if dep.is_resolved:
289-
self.resolved_requirements[dep_purl_name] = f"=={dep_purl.version}"
290-
# skip the requirement starting with -- like
291-
# --editable, --requirement
292-
if not dep.extracted_requirement.startswith("-"):
293-
yield packaging.requirements.Requirement(str(dep.extracted_requirement))
328+
for handler, path in path_by_sdist_parser.items():
329+
deps = get_requirements_from_distribution(
330+
handler=handler, path=path, resolved_requirements=self.resolved_requirements
331+
)
332+
if deps:
333+
deps_in_setup = True
334+
yield from deps
294335

295-
def is_dep_resolved_and_in_resolved_requirements(self, dep, dep_purl_name):
296-
return dep.is_resolved and dep_purl_name in self.resolved_requirements
336+
requirement_path = os.path.join(
337+
sdist_file,
338+
"requirements.txt",
339+
)
340+
if not deps_in_setup and is_requirements_file_in_setup_files(
341+
setup_files=[setup_py_path, setup_cfg_path]
342+
):
343+
deps = get_requirements_from_distribution(
344+
hanlder=PipRequirementsFileHandler,
345+
path=requirement_path,
346+
resolved_requirements=self.resolved_requirements,
347+
)
348+
if deps:
349+
yield from deps
297350

298351
def get_requirements_for_package_from_pypi_json_api(self, purl):
299352
"""
@@ -380,6 +433,7 @@ def _iter_dependencies(self, candidate):
380433
self.environment.python_version
381434
),
382435
"platform_system": self.environment.operating_system.capitalize(),
436+
"sys_platform": self.environment.operating_system,
383437
}
384438
):
385439
yield r

0 commit comments

Comments
 (0)