88# See https://aboutcode-orgnexB/python-inspector for support or download.
99# See https://aboutcode.org for more information about nexB OSS projects.
1010#
11-
11+ import asyncio
1212import os
1313from netrc import netrc
1414from typing import Dict
1515from typing import List
1616from typing import NamedTuple
1717from typing import Sequence
18+ from typing import Tuple
1819
1920from packageurl import PackageURL
2021from packvers .requirements import Requirement
2627from _packagedcode .pypi import PipRequirementsFileHandler
2728from _packagedcode .pypi import PythonSetupPyHandler
2829from _packagedcode .pypi import can_process_dependent_package
30+ from _packagedcode .pypi import get_resolved_purl
2931from python_inspector import dependencies
3032from python_inspector import pyinspector_settings as settings
3133from python_inspector import utils
3941from python_inspector .resolution import get_python_version_from_env_tag
4042from python_inspector .resolution import get_reqs_insecurely
4143from python_inspector .resolution import get_requirements_from_python_manifest
44+ from python_inspector .utils import Candidate
4245from python_inspector .utils_pypi import PLATFORMS_BY_OS
4346from python_inspector .utils_pypi import Environment
4447from python_inspector .utils_pypi import PypiSimpleRepository
@@ -54,7 +57,7 @@ class Resolution(NamedTuple):
5457 ``files`` is a parsed list of input file data.
5558 """
5659
57- resolution : Dict
60+ resolution : List [ Dict ]
5861 packages : List [PackageData ]
5962 files : List [Dict ]
6063
@@ -286,21 +289,27 @@ def resolve_dependencies(
286289 pdt_output = pdt_output ,
287290 analyze_setup_py_insecurely = analyze_setup_py_insecurely ,
288291 ignore_errors = ignore_errors ,
292+ verbose = verbose ,
293+ printer = printer ,
289294 )
290295
291- packages = []
296+ async def gather_pypi_data ():
297+ async def get_pypi_data (package ):
298+ data = await get_pypi_data_from_purl (
299+ package , repos = repos , environment = environment , prefer_source = prefer_source
300+ )
292301
293- for package in purls :
294- packages . extend (
295- [
296- pkg . to_dict ()
297- for pkg in list (
298- get_pypi_data_from_purl (
299- package , repos = repos , environment = environment , prefer_source = prefer_source
300- )
301- )
302- ],
303- )
302+ if verbose :
303+ printer ( f" retrieved package ' { package } '" )
304+
305+ return data
306+
307+ if verbose :
308+ printer ( f"retrieve package data from pypi:" )
309+
310+ return await asyncio . gather ( * [ get_pypi_data ( package ) for package in purls ] )
311+
312+ packages = [ pkg . to_dict () for pkg in asyncio . run ( gather_pypi_data ()) if pkg is not None ]
304313
305314 if verbose :
306315 printer ("done!" )
@@ -316,14 +325,16 @@ def resolve_dependencies(
316325
317326
318327def resolve (
319- direct_dependencies ,
320- environment ,
321- repos = tuple (),
322- as_tree = False ,
323- max_rounds = 200000 ,
324- pdt_output = False ,
325- analyze_setup_py_insecurely = False ,
326- ignore_errors = False ,
328+ direct_dependencies : List [DependentPackage ],
329+ environment : Environment ,
330+ repos : Sequence [utils_pypi .PypiSimpleRepository ] = tuple (),
331+ as_tree : bool = False ,
332+ max_rounds : int = 200000 ,
333+ pdt_output : bool = False ,
334+ analyze_setup_py_insecurely : bool = False ,
335+ ignore_errors : bool = False ,
336+ verbose : bool = False ,
337+ printer = print ,
327338):
328339 """
329340 Resolve dependencies given a ``direct_dependencies`` list of
@@ -350,6 +361,8 @@ def resolve(
350361 pdt_output = pdt_output ,
351362 analyze_setup_py_insecurely = analyze_setup_py_insecurely ,
352363 ignore_errors = ignore_errors ,
364+ verbose = verbose ,
365+ printer = printer ,
353366 )
354367
355368 return resolved_dependencies , packages
@@ -364,32 +377,77 @@ def get_resolved_dependencies(
364377 pdt_output : bool = False ,
365378 analyze_setup_py_insecurely : bool = False ,
366379 ignore_errors : bool = False ,
367- ):
380+ verbose : bool = False ,
381+ printer = print ,
382+ ) -> Tuple [List [Dict ], List [str ]]:
368383 """
369384 Return resolved dependencies of a ``requirements`` list of Requirement for
370- an ``enviroment `` Environment. The resolved dependencies are formatted as
385+ an ``environment `` Environment. The resolved dependencies are formatted as
371386 parent/children or a nested tree if ``as_tree`` is True.
372387
373388 Used the provided ``repos`` list of PypiSimpleRepository.
374- If empty, use instead the PyPI.org JSON API exclusively instead
389+ If empty, use instead the PyPI.org JSON API exclusively instead.
375390 """
391+ provider = PythonInputProvider (
392+ environment = environment ,
393+ repos = repos ,
394+ analyze_setup_py_insecurely = analyze_setup_py_insecurely ,
395+ ignore_errors = ignore_errors ,
396+ )
397+
398+ # gather version data for all requirements concurrently in advance.
399+
400+ async def gather_version_data ():
401+ async def get_version_data (name : str ):
402+ versions = await provider .fill_versions_for_package (name )
403+
404+ if verbose :
405+ printer (f" retrieved versions for package '{ name } '" )
406+
407+ return versions
408+
409+ if verbose :
410+ printer (f"versions:" )
411+
412+ return await asyncio .gather (
413+ * [get_version_data (requirement .name ) for requirement in requirements ]
414+ )
415+
416+ asyncio .run (gather_version_data ())
417+
418+ # gather dependencies for all pinned requirements concurrently in advance.
419+
420+ async def gather_dependencies ():
421+ async def get_dependencies (requirement : Requirement ):
422+ purl = PackageURL (type = "pypi" , name = requirement .name )
423+ resolved_purl = get_resolved_purl (purl = purl , specifiers = requirement .specifier )
424+
425+ if resolved_purl :
426+ purl = resolved_purl .purl
427+ candidate = Candidate (requirement .name , purl .version , requirement .extras )
428+ await provider .fill_requirements_for_package (purl , candidate )
429+
430+ if verbose :
431+ printer (f" retrieved dependencies for requirement '{ str (purl )} '" )
432+
433+ if verbose :
434+ printer (f"dependencies:" )
435+
436+ return await asyncio .gather (
437+ * [get_dependencies (requirement ) for requirement in requirements ]
438+ )
439+
440+ asyncio .run (gather_dependencies ())
441+
376442 resolver = Resolver (
377- provider = PythonInputProvider (
378- environment = environment ,
379- repos = repos ,
380- analyze_setup_py_insecurely = analyze_setup_py_insecurely ,
381- ignore_errors = ignore_errors ,
382- ),
443+ provider = provider ,
383444 reporter = BaseReporter (),
384445 )
385446 resolver_results = resolver .resolve (requirements = requirements , max_rounds = max_rounds )
386447 package_list = get_package_list (results = resolver_results )
387448 if pdt_output :
388- return (format_pdt_tree (resolver_results ), package_list )
389- return (
390- format_resolution (resolver_results , as_tree = as_tree ),
391- package_list ,
392- )
449+ return format_pdt_tree (resolver_results ), package_list
450+ return format_resolution (resolver_results , as_tree = as_tree ), package_list
393451
394452
395453def get_requirements_from_direct_dependencies (
0 commit comments