2727
2828from _packagedcode .pypi import PipRequirementsFileHandler
2929from _packagedcode .pypi import PypiWheelHandler
30- from _packagedcode .pypi import PythonSdistPkgInfoFile
3130from _packagedcode .pypi import PythonSetupPyHandler
3231from _packagedcode .pypi import SetupCfgHandler
3332from 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+
5690def 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+
119184def 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