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
29- from python_inspector import DEFAULT_PYTHON_VERSION
30+ from _packagedcode . pypi import get_resolved_purl
3031from python_inspector import dependencies
3132from python_inspector import utils
3233from python_inspector import utils_pypi
3940from python_inspector .resolution import get_python_version_from_env_tag
4041from python_inspector .resolution import get_reqs_insecurely
4142from python_inspector .resolution import get_requirements_from_python_manifest
43+ from python_inspector .utils import Candidate
4244from python_inspector .utils_pypi import PLATFORMS_BY_OS
4345from python_inspector .utils_pypi import PYPI_SIMPLE_URL
4446from python_inspector .utils_pypi import Environment
@@ -54,7 +56,7 @@ class Resolution(NamedTuple):
5456 ``files`` is a parsed list of input file data.
5557 """
5658
57- resolution : Dict
59+ resolution : List [ Dict ]
5860 packages : List [PackageData ]
5961 files : List [Dict ]
6062
@@ -286,21 +288,27 @@ def resolve_dependencies(
286288 pdt_output = pdt_output ,
287289 analyze_setup_py_insecurely = analyze_setup_py_insecurely ,
288290 ignore_errors = ignore_errors ,
291+ verbose = verbose ,
292+ printer = printer ,
289293 )
290294
291- packages = []
295+ async def gather_pypi_data ():
296+ async def get_pypi_data (package ):
297+ data = await get_pypi_data_from_purl (
298+ package , repos = repos , environment = environment , prefer_source = prefer_source
299+ )
292300
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- )
301+ if verbose :
302+ printer ( f" retrieved package ' { package } '" )
303+
304+ return data
305+
306+ if verbose :
307+ printer ( f"retrieve package data from pypi:" )
308+
309+ return await asyncio . gather ( * [ get_pypi_data ( package ) for package in purls ] )
310+
311+ packages = [ pkg . to_dict () for pkg in asyncio . run ( gather_pypi_data ()) if pkg is not None ]
304312
305313 if verbose :
306314 printer ("done!" )
@@ -316,14 +324,16 @@ def resolve_dependencies(
316324
317325
318326def 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 ,
327+ direct_dependencies : List [DependentPackage ],
328+ environment : Environment ,
329+ repos : Sequence [utils_pypi .PypiSimpleRepository ] = tuple (),
330+ as_tree : bool = False ,
331+ max_rounds : int = 200000 ,
332+ pdt_output : bool = False ,
333+ analyze_setup_py_insecurely : bool = False ,
334+ ignore_errors : bool = False ,
335+ verbose : bool = False ,
336+ printer = print ,
327337):
328338 """
329339 Resolve dependencies given a ``direct_dependencies`` list of
@@ -350,6 +360,8 @@ def resolve(
350360 pdt_output = pdt_output ,
351361 analyze_setup_py_insecurely = analyze_setup_py_insecurely ,
352362 ignore_errors = ignore_errors ,
363+ verbose = verbose ,
364+ printer = printer ,
353365 )
354366
355367 return resolved_dependencies , packages
@@ -364,32 +376,77 @@ def get_resolved_dependencies(
364376 pdt_output : bool = False ,
365377 analyze_setup_py_insecurely : bool = False ,
366378 ignore_errors : bool = False ,
367- ):
379+ verbose : bool = False ,
380+ printer = print ,
381+ ) -> Tuple [List [Dict ], List [str ]]:
368382 """
369383 Return resolved dependencies of a ``requirements`` list of Requirement for
370- an ``enviroment `` Environment. The resolved dependencies are formatted as
384+ an ``environment `` Environment. The resolved dependencies are formatted as
371385 parent/children or a nested tree if ``as_tree`` is True.
372386
373387 Used the provided ``repos`` list of PypiSimpleRepository.
374- If empty, use instead the PyPI.org JSON API exclusively instead
388+ If empty, use instead the PyPI.org JSON API exclusively instead.
375389 """
390+ provider = PythonInputProvider (
391+ environment = environment ,
392+ repos = repos ,
393+ analyze_setup_py_insecurely = analyze_setup_py_insecurely ,
394+ ignore_errors = ignore_errors ,
395+ )
396+
397+ # gather version data for all requirements concurrently in advance.
398+
399+ async def gather_version_data ():
400+ async def get_version_data (name : str ):
401+ versions = await provider .fill_versions_for_package (name )
402+
403+ if verbose :
404+ printer (f" retrieved versions for package '{ name } '" )
405+
406+ return versions
407+
408+ if verbose :
409+ printer (f"versions:" )
410+
411+ return await asyncio .gather (
412+ * [get_version_data (requirement .name ) for requirement in requirements ]
413+ )
414+
415+ asyncio .run (gather_version_data ())
416+
417+ # gather dependencies for all pinned requirements concurrently in advance.
418+
419+ async def gather_dependencies ():
420+ async def get_dependencies (requirement : Requirement ):
421+ purl = PackageURL (type = "pypi" , name = requirement .name )
422+ resolved_purl = get_resolved_purl (purl = purl , specifiers = requirement .specifier )
423+
424+ if resolved_purl :
425+ purl = resolved_purl .purl
426+ candidate = Candidate (requirement .name , purl .version , requirement .extras )
427+ await provider .fill_requirements_for_package (purl , candidate )
428+
429+ if verbose :
430+ printer (f" retrieved dependencies for requirement '{ str (purl )} '" )
431+
432+ if verbose :
433+ printer (f"dependencies:" )
434+
435+ return await asyncio .gather (
436+ * [get_dependencies (requirement ) for requirement in requirements ]
437+ )
438+
439+ asyncio .run (gather_dependencies ())
440+
376441 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- ),
442+ provider = provider ,
383443 reporter = BaseReporter (),
384444 )
385445 resolver_results = resolver .resolve (requirements = requirements , max_rounds = max_rounds )
386446 package_list = get_package_list (results = resolver_results )
387447 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- )
448+ return format_pdt_tree (resolver_results ), package_list
449+ return format_resolution (resolver_results , as_tree = as_tree ), package_list
393450
394451
395452def get_requirements_from_direct_dependencies (
0 commit comments