8
8
# See https://aboutcode-orgnexB/python-inspector for support or download.
9
9
# See https://aboutcode.org for more information about nexB OSS projects.
10
10
#
11
-
11
+ import asyncio
12
12
import os
13
13
from netrc import netrc
14
14
from typing import Dict
15
15
from typing import List
16
16
from typing import NamedTuple
17
17
from typing import Sequence
18
+ from typing import Tuple
18
19
19
20
from packageurl import PackageURL
20
21
from packvers .requirements import Requirement
26
27
from _packagedcode .pypi import PipRequirementsFileHandler
27
28
from _packagedcode .pypi import PythonSetupPyHandler
28
29
from _packagedcode .pypi import can_process_dependent_package
30
+ from _packagedcode .pypi import get_resolved_purl
29
31
from python_inspector import dependencies
30
32
from python_inspector import pyinspector_settings as settings
31
33
from python_inspector import utils
39
41
from python_inspector .resolution import get_python_version_from_env_tag
40
42
from python_inspector .resolution import get_reqs_insecurely
41
43
from python_inspector .resolution import get_requirements_from_python_manifest
44
+ from python_inspector .utils import Candidate
42
45
from python_inspector .utils_pypi import PLATFORMS_BY_OS
43
46
from python_inspector .utils_pypi import Environment
44
47
from python_inspector .utils_pypi import PypiSimpleRepository
@@ -54,7 +57,7 @@ class Resolution(NamedTuple):
54
57
``files`` is a parsed list of input file data.
55
58
"""
56
59
57
- resolution : Dict
60
+ resolution : List [ Dict ]
58
61
packages : List [PackageData ]
59
62
files : List [Dict ]
60
63
@@ -286,21 +289,27 @@ def resolve_dependencies(
286
289
pdt_output = pdt_output ,
287
290
analyze_setup_py_insecurely = analyze_setup_py_insecurely ,
288
291
ignore_errors = ignore_errors ,
292
+ verbose = verbose ,
293
+ printer = printer ,
289
294
)
290
295
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
+ )
292
301
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 ]
304
313
305
314
if verbose :
306
315
printer ("done!" )
@@ -316,14 +325,16 @@ def resolve_dependencies(
316
325
317
326
318
327
def 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 ,
327
338
):
328
339
"""
329
340
Resolve dependencies given a ``direct_dependencies`` list of
@@ -350,6 +361,8 @@ def resolve(
350
361
pdt_output = pdt_output ,
351
362
analyze_setup_py_insecurely = analyze_setup_py_insecurely ,
352
363
ignore_errors = ignore_errors ,
364
+ verbose = verbose ,
365
+ printer = printer ,
353
366
)
354
367
355
368
return resolved_dependencies , packages
@@ -364,32 +377,77 @@ def get_resolved_dependencies(
364
377
pdt_output : bool = False ,
365
378
analyze_setup_py_insecurely : bool = False ,
366
379
ignore_errors : bool = False ,
367
- ):
380
+ verbose : bool = False ,
381
+ printer = print ,
382
+ ) -> Tuple [List [Dict ], List [str ]]:
368
383
"""
369
384
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
371
386
parent/children or a nested tree if ``as_tree`` is True.
372
387
373
388
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.
375
390
"""
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
+
376
442
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 ,
383
444
reporter = BaseReporter (),
384
445
)
385
446
resolver_results = resolver .resolve (requirements = requirements , max_rounds = max_rounds )
386
447
package_list = get_package_list (results = resolver_results )
387
448
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
393
451
394
452
395
453
def get_requirements_from_direct_dependencies (
0 commit comments