1111
1212import json
1313import os
14+ from functools import partial
1415from typing import Dict
1516from typing import List
17+ from typing import Sequence
1618
1719import click
1820from packaging .requirements import Requirement
21+ from resolvelib import Resolver
22+ from resolvelib .reporters import BaseReporter
1923from tinynetrc import Netrc
2024
2125from _packagedcode .models import DependentPackage
2731from python_inspector import utils_pypi
2832from python_inspector .cli_utils import FileOptionType
2933from python_inspector .package_data import get_pypi_data_from_purl
34+ from python_inspector .resolution import PythonInputProvider
3035from python_inspector .resolution import contain_string
36+ from python_inspector .resolution import format_pdt_tree
37+ from python_inspector .resolution import format_resolution
3138from python_inspector .resolution import get_deps_from_distribution
3239from python_inspector .resolution import get_environment_marker_from_environment
40+ from python_inspector .resolution import get_package_list
3341from python_inspector .resolution import get_python_version_from_env_tag
34- from python_inspector .resolution import get_resolved_dependencies
3542from python_inspector .resolution import parse_deps_from_setup_py_insecurely
43+ from python_inspector .utils_pypi import Environment
3644
3745TRACE = False
3846
@@ -227,6 +235,13 @@ def resolve_dependencies(
227235 click .secho ("Only one of --json or --json-pdt can be used." , err = True )
228236 ctx .exit (1 )
229237
238+ options = [f"--requirement { rf } " for rf in requirement_files ]
239+ options += [f"--specifier { sp } " for sp in specifiers ]
240+ options += [f"--index-url { iu } " for iu in index_urls ]
241+ options += [f"--python-version { python_version } " ]
242+ options += [f"--operating-system { operating_system } " ]
243+ options += ["--json <file>" ]
244+
230245 resolver_api (
231246 requirement_files = requirement_files ,
232247 setup_py_file = setup_py_file ,
@@ -242,25 +257,23 @@ def resolve_dependencies(
242257 use_pypi_json_api = use_pypi_json_api ,
243258 verbose = verbose ,
244259 analyze_setup_py_insecurely = analyze_setup_py_insecurely ,
245- ctx = ctx ,
260+ printer = click .secho ,
261+ raise_error = partial (raise_exception_from_cli , ctx ),
262+ options = options ,
263+ write_output = True ,
246264 )
247265
248266
249- def raise_error (message , ctx ):
250- """Raise error."""
251- if ctx :
252- click .secho (message , err = True )
253- ctx .exit (1 )
254- else :
255- raise ValueError (message )
267+ def raise_exception_from_cli (ctx , message ):
268+ click .secho (message , err = True )
269+ ctx .exit (1 )
256270
257271
258- def print_message (message , ctx ):
259- """Print message."""
260- if ctx :
261- click .secho (message )
262- else :
263- print (message )
272+ def raise_exception (message ):
273+ """
274+ Raise error.
275+ """
276+ raise Exception (message )
264277
265278
266279def resolver_api (
@@ -278,7 +291,10 @@ def resolver_api(
278291 use_pypi_json_api = False ,
279292 verbose = False ,
280293 analyze_setup_py_insecurely = False ,
281- ctx = None ,
294+ printer = print ,
295+ raise_error = raise_exception ,
296+ options = [],
297+ write_output = False ,
282298):
283299 """
284300 Resolve the dependencies for the package requirements listed in one or
@@ -287,7 +303,7 @@ def resolver_api(
287303 """
288304
289305 if verbose :
290- print_message ("Resolving dependencies..." , ctx = ctx )
306+ printer ("Resolving dependencies..." )
291307
292308 if netrc_file :
293309 if not os .path .exists (netrc_file ):
@@ -302,7 +318,7 @@ def resolver_api(
302318
303319 if netrc_file :
304320 if verbose :
305- print_message (f"Using netrc file { netrc_file } " , ctx = ctx )
321+ printer (f"Using netrc file { netrc_file } " )
306322 netrc = Netrc (file = netrc_file )
307323 else :
308324 netrc = None
@@ -349,7 +365,6 @@ def resolver_api(
349365 f"Python version { get_python_version_from_env_tag (python_version )} "
350366 f"is not compatible with setup.py { setup_py_file } "
351367 f"python_requires { python_requires } " ,
352- ctx = ctx ,
353368 )
354369
355370 setup_py_file_deps = package_data .dependencies
@@ -399,20 +414,20 @@ def resolver_api(
399414 )
400415
401416 if not direct_dependencies :
402- raise_error ("Error: no requirements requested." , ctx = ctx )
417+ raise_error ("Error: no requirements requested." )
403418
404419 if verbose :
405- print_message ("direct_dependencies:" , ctx = ctx )
420+ printer ("direct_dependencies:" )
406421 for dep in direct_dependencies :
407- print_message (f" { dep } " , ctx = ctx )
422+ printer (f" { dep } " )
408423
409424 # create a resolution environments
410425 environment = utils_pypi .Environment .from_pyver_and_os (
411426 python_version = python_version , operating_system = operating_system
412427 )
413428
414429 if verbose :
415- print_message (f"environment: { environment } " , ctx = ctx )
430+ printer (f"environment: { environment } " )
416431
417432 repos = []
418433 if not use_pypi_json_api :
@@ -438,9 +453,9 @@ def resolver_api(
438453 repos .append (repo )
439454
440455 if verbose :
441- print_message ("repos:" , ctx = ctx )
456+ printer ("repos:" )
442457 for repo in repos :
443- print_message (f" { repo } " , ctx = ctx )
458+ printer (f" { repo } " )
444459
445460 # resolve dependencies proper
446461 resolved_dependencies , purls = resolve (
@@ -449,20 +464,12 @@ def resolver_api(
449464 repos = repos ,
450465 as_tree = False ,
451466 max_rounds = max_rounds ,
452- verbose = verbose ,
453467 pdt_output = pdt_output ,
454468 analyze_setup_py_insecurely = analyze_setup_py_insecurely ,
455- ctx = ctx ,
469+ raise_error = raise_error ,
456470 )
457471
458- if ctx :
459- options = [f"--requirement { rf } " for rf in requirement_files ]
460- options += [f"--specifier { sp } " for sp in specifiers ]
461- options += [f"--index-url { iu } " for iu in index_urls ]
462- options += [f"--python-version { python_version } " ]
463- options += [f"--operating-system { operating_system } " ]
464- options += ["--json <file>" ]
465- else :
472+ if not options :
466473 options = [f"requirement_files- { rf } " for rf in requirement_files ]
467474 options += [f"specifiers- { sp } " for sp in specifiers ]
468475 options += [f"index_urls- { iu } " for iu in index_urls ]
@@ -500,16 +507,16 @@ def resolver_api(
500507 packages = packages ,
501508 )
502509
503- if ctx :
504- write_output (
510+ if write_output :
511+ write_output_in_file (
505512 json_output = json_output or pdt_output ,
506513 output = output ,
507514 )
508515 else :
509516 return output
510517
511518 if verbose :
512- print_message ("done!" , ctx = ctx )
519+ printer ("done!" )
513520
514521
515522def resolve (
@@ -518,10 +525,9 @@ def resolve(
518525 repos = tuple (),
519526 as_tree = False ,
520527 max_rounds = 200000 ,
521- verbose = False ,
522528 pdt_output = False ,
523529 analyze_setup_py_insecurely = False ,
524- ctx = None ,
530+ raise_error = raise_exception ,
525531):
526532 """
527533 Resolve dependencies given a ``direct_dependencies`` list of
@@ -545,15 +551,53 @@ def resolve(
545551 repos = repos ,
546552 as_tree = as_tree ,
547553 max_rounds = max_rounds ,
548- verbose = verbose ,
549554 pdt_output = pdt_output ,
550555 analyze_setup_py_insecurely = analyze_setup_py_insecurely ,
551- ctx = ctx ,
556+ raise_error = raise_error ,
552557 )
553558
554559 return resolved_dependencies , packages
555560
556561
562+ def get_resolved_dependencies (
563+ requirements : List [Requirement ],
564+ environment : Environment = None ,
565+ repos : Sequence [utils_pypi .PypiSimpleRepository ] = tuple (),
566+ as_tree : bool = False ,
567+ max_rounds : int = 200000 ,
568+ pdt_output : bool = False ,
569+ analyze_setup_py_insecurely : bool = False ,
570+ raise_error = raise_exception ,
571+ ):
572+ """
573+ Return resolved dependencies of a ``requirements`` list of Requirement for
574+ an ``enviroment`` Environment. The resolved dependencies are formatted as
575+ parent/children or a nested tree if ``as_tree`` is True.
576+
577+ Used the provided ``repos`` list of PypiSimpleRepository.
578+ If empty, use instead the PyPI.org JSON API exclusively instead
579+ """
580+ try :
581+ resolver = Resolver (
582+ provider = PythonInputProvider (
583+ environment = environment ,
584+ repos = repos ,
585+ analyze_setup_py_insecurely = analyze_setup_py_insecurely ,
586+ ),
587+ reporter = BaseReporter (),
588+ )
589+ resolver_results = resolver .resolve (requirements = requirements , max_rounds = max_rounds )
590+ package_list = get_package_list (results = resolver_results )
591+ if pdt_output :
592+ return (format_pdt_tree (resolver_results ), package_list )
593+ return (
594+ format_resolution (resolver_results , as_tree = as_tree ),
595+ package_list ,
596+ )
597+ except Exception as e :
598+ raise_error (f"{ e !r} " )
599+
600+
557601def get_requirements_from_direct_dependencies (
558602 direct_dependencies : List [DependentPackage ], environment_marker : Dict
559603) -> List [Requirement ]:
@@ -571,7 +615,7 @@ def get_requirements_from_direct_dependencies(
571615 yield req
572616
573617
574- def write_output (output , json_output ):
618+ def write_output_in_file (output , json_output ):
575619 """
576620 Write headers, requirements and resolved_dependencies as JSON to ``json_output``.
577621 Return the output data.
0 commit comments