diff --git a/src/python_inspector/resolve_cli.py b/src/python_inspector/resolve_cli.py index cdd5abc..38891a2 100644 --- a/src/python_inspector/resolve_cli.py +++ b/src/python_inspector/resolve_cli.py @@ -15,7 +15,7 @@ from python_inspector import utils_pypi from python_inspector.cli_utils import FileOptionType -from python_inspector.utils import write_output_in_file +from python_inspector.utils import write_output_in_file, write_resolved_packages TRACE = False @@ -115,6 +115,15 @@ def print_version(ctx, param, value): help="Write output as pretty-printed JSON to FILE as a tree in the style of pipdeptree. " "Use the special '-' file name to print results on screen/stdout.", ) +@click.option( + "--resolved-output", + "resolved_output", + type=FileOptionType(mode="w", encoding="utf-8", lazy=True), + required=False, + metavar="FILE", + help="Write the packages that are resolved after inspecting, into a TEXT file." + "The packages are written in a typical requirements file format that can be used by pip.", +) @click.option( "-n", "--netrc", @@ -194,6 +203,7 @@ def resolve_dependencies( index_urls, json_output, pdt_output, + resolved_output, netrc_file, max_rounds, use_cached_index=False, @@ -285,6 +295,8 @@ def resolve_dependencies( output=output, location=json_output or pdt_output, ) + if resolved_output: + write_resolved_packages(files, resolved_output) except Exception: import traceback diff --git a/src/python_inspector/utils.py b/src/python_inspector/utils.py index 9bdf622..785abac 100644 --- a/src/python_inspector/utils.py +++ b/src/python_inspector/utils.py @@ -54,6 +54,20 @@ def write_output_in_file(output, location): return output +def write_resolved_packages(package_list, requirements_file): + """ + Write the resolved package names and versions into ``requirements_file_path`` + """ + dependencies = package_list[0]['package_data'][0]['dependencies'] + resolved_packages = [] + for dependency in dependencies: + if dependency['is_resolved']: + package = dependency['extracted_requirement'] + resolved_packages.append(package) + for package in resolved_packages: + requirements_file.write(package + '\n') + + class Candidate(NamedTuple): """ A candidate is a package that can be installed. diff --git a/tests/data/resolved-pinned-requirements-expected.txt b/tests/data/resolved-pinned-requirements-expected.txt new file mode 100644 index 0000000..fd9569c --- /dev/null +++ b/tests/data/resolved-pinned-requirements-expected.txt @@ -0,0 +1,28 @@ +aboutcode-toolkit==7.0.2 +attrs==21.4.0 +beautifulsoup4==4.11.1 +certifi==2022.5.18.1 +charset-normalizer==2.0.12 +click==8.0.4 +colorama==0.4.4 +commoncode==30.2.0 +dparse2==0.6.1 +idna==3.3 +importlib-metadata==4.8.3 +intbitset==3.0.1 +packageurl-python==0.9.9 +packaging==21.3 +pip-requirements-parser==31.2.0 +pkginfo2==30.0.0 +pyparsing==3.0.9 +PyYAML==6.0 +requests==2.27.1 +resolvelib==0.8.1 +saneyaml==0.5.2 +soupsieve==2.3.2.post1 +text-unidecode==1.3 +toml==0.10.2 +typing==3.6.6 +typing_extensions==4.1.1 +urllib3==1.26.9 +zipp==3.6.0 diff --git a/tests/test_cli.py b/tests/test_cli.py index 323e4bb..470be4e 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -361,10 +361,10 @@ def test_cli_with_setup_py_no_direct_dependencies(): def check_specs_resolution( - specifier, - expected_file, - extra_options=tuple(), - regen=REGEN_TEST_FIXTURES, + specifier, + expected_file, + extra_options=tuple(), + regen=REGEN_TEST_FIXTURES, ): result_file = test_env.get_temp_file("json") options = ["--specifier", specifier, "--json", result_file] @@ -444,12 +444,51 @@ def test_passing_of_unsupported_os(): assert message in result.output +@pytest.mark.online +def test_resolved_cli(): + """ + Tests the '--resolved-output' option of resolve_dependencies() function. + The '--resolved-output' option takes in a filename and writes. + The resolved package dependencies into a .txt file with the given filename. + """ + requirements_file = test_env.get_test_loc("pinned-requirements.txt") + expected_file = test_env.get_test_loc( + "resolved-pinned-requirements-expected.txt", must_exist=False + ) + result_txt_file = test_env.get_temp_file('txt') + result_json_file = test_env.get_temp_file('json') + options = ["--requirement", requirements_file, "--resolved-output", result_txt_file, "--json", result_json_file] + run_cli(options=options) + check_resolved_packages_results(result_txt_file, expected_file) + + +def check_resolved_packages_results(result_file, expected_file, regen=REGEN_TEST_FIXTURES): + """ + Check the ``result_file`` data against the ``expected_file`` expected results. + + If ``regen`` is True the expected_file WILL BE overwritten with the new + results from ``results_file``. This is convenient for updating tests + expectations. + """ + with open(result_file, "r") as res_file: + results = res_file.readlines() + + if regen: + with open(expected_file, "w") as exo: + exo.writelines(results) + expected = results + else: + with open(expected_file) as reso: + expected = reso.readlines() + assert results == expected + + def check_requirements_resolution( - requirements_file, - expected_file, - extra_options=tuple(), - regen=REGEN_TEST_FIXTURES, - pdt_output=False, + requirements_file, + expected_file, + extra_options=tuple(), + regen=REGEN_TEST_FIXTURES, + pdt_output=False, ): result_file = test_env.get_temp_file("json") if pdt_output: @@ -462,13 +501,13 @@ def check_requirements_resolution( def check_setup_py_resolution( - setup_py, - expected_file, - extra_options=tuple(), - regen=REGEN_TEST_FIXTURES, - pdt_output=False, - expected_rc=0, - message="", + setup_py, + expected_file, + extra_options=tuple(), + regen=REGEN_TEST_FIXTURES, + pdt_output=False, + expected_rc=0, + message="", ): result_file = setup_test_env.get_temp_file(file_name="json") if pdt_output: