From a694aadb16e5624d2fcd9758efedad464ac1a20a Mon Sep 17 00:00:00 2001 From: DustinMoriarty Date: Mon, 15 Nov 2021 01:04:40 -0600 Subject: [PATCH] feat: export without urls (#4763) Export requirements.txt without index URLs Co-authored-by: DBM012 --- docs/cli.md | 1 + src/poetry/console/commands/export.py | 6 +++ src/poetry/utils/exporter.py | 5 ++- tests/console/commands/test_export.py | 24 +++++++++++ tests/utils/test_exporter.py | 58 +++++++++++++++++++++++++++ 5 files changed, 93 insertions(+), 1 deletion(-) diff --git a/docs/cli.md b/docs/cli.md index 1ccf04cacae..c5c64ef8149 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -602,6 +602,7 @@ Only the `requirements.txt` format is currently supported. * `--dev`: Include development dependencies. * `--extras (-E)`: Extra sets of dependencies to include. * `--without-hashes`: Exclude hashes from the exported file. +* `--without-urls`: Exclude source repository urls from the exported file. * `--with-credentials`: Include credentials for extra indices. ## env diff --git a/src/poetry/console/commands/export.py b/src/poetry/console/commands/export.py index accdc199460..9d42a1fba05 100644 --- a/src/poetry/console/commands/export.py +++ b/src/poetry/console/commands/export.py @@ -20,6 +20,11 @@ class ExportCommand(Command): ), option("output", "o", "The name of the output file.", flag=False), option("without-hashes", None, "Exclude hashes from the exported file."), + option( + "without-urls", + None, + "Exclude source repository urls from the exported file.", + ), option("dev", None, "Include development dependencies."), option( "extras", @@ -71,4 +76,5 @@ def handle(self) -> None: dev=self.option("dev"), extras=self.option("extras"), with_credentials=self.option("with-credentials"), + with_urls=not self.option("without-urls"), ) diff --git a/src/poetry/utils/exporter.py b/src/poetry/utils/exporter.py index 2268ecb222c..3a9cf1d6e84 100644 --- a/src/poetry/utils/exporter.py +++ b/src/poetry/utils/exporter.py @@ -34,6 +34,7 @@ def export( dev: bool = False, extras: Optional[Union[bool, Sequence[str]]] = None, with_credentials: bool = False, + with_urls: bool = True, ) -> None: if fmt not in self.ACCEPTED_FORMATS: raise ValueError(f"Invalid export format: {fmt}") @@ -45,6 +46,7 @@ def export( dev=dev, extras=extras, with_credentials=with_credentials, + with_urls=with_urls, ) def _export_requirements_txt( @@ -55,6 +57,7 @@ def _export_requirements_txt( dev: bool = False, extras: Optional[Union[bool, Sequence[str]]] = None, with_credentials: bool = False, + with_urls: bool = True, ) -> None: indexes = set() content = "" @@ -122,7 +125,7 @@ def _export_requirements_txt( content += "\n".join(sorted(dependency_lines)) content += "\n" - if indexes: + if indexes and with_urls: # If we have extra indexes, we add them to the beginning of the output indexes_header = "" for index in sorted(indexes): diff --git a/tests/console/commands/test_export.py b/tests/console/commands/test_export.py index 80b2f14f732..7d04b211607 100644 --- a/tests/console/commands/test_export.py +++ b/tests/console/commands/test_export.py @@ -1,5 +1,9 @@ +from unittest.mock import ANY +from unittest.mock import Mock + import pytest +from poetry.console.commands.export import Exporter from tests.helpers import get_package @@ -110,3 +114,23 @@ def test_export_includes_extras_by_flag(tester, do_lock): foo==1.0.0 """ assert expected == tester.io.fetch_output() + + +def test_export_with_urls(monkeypatch, tester, poetry): + """ + We are just validating that the option gets passed. The option itself is tested in + the Exporter test. + """ + mock_export = Mock() + monkeypatch.setattr(Exporter, "export", mock_export) + tester.execute("--without-urls") + mock_export.assert_called_once_with( + ANY, + ANY, + ANY, + dev=False, + extras=[], + with_credentials=False, + with_hashes=True, + with_urls=False, + ) diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py index 5f6f80556a6..a1bfbb1e13f 100644 --- a/tests/utils/test_exporter.py +++ b/tests/utils/test_exporter.py @@ -1367,6 +1367,64 @@ def test_exporter_exports_requirements_txt_with_legacy_packages(tmp_dir, poetry) assert expected == content +def test_exporter_exports_requirements_txt_with_url_false(tmp_dir, poetry): + poetry.pool.add_repository( + LegacyRepository( + "custom", + "https://example.com/simple", + ) + ) + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + }, + { + "name": "bar", + "version": "4.5.6", + "category": "dev", + "optional": False, + "python-versions": "*", + "source": { + "type": "legacy", + "url": "https://example.com/simple", + "reference": "", + }, + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": ["12345"], "bar": ["67890"]}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export( + "requirements.txt", Path(tmp_dir), "requirements.txt", dev=True, with_urls=False + ) + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +bar==4.5.6 \\ + --hash=sha256:67890 +foo==1.2.3 \\ + --hash=sha256:12345 +""" + + assert expected == content + + def test_exporter_exports_requirements_txt_with_legacy_packages_trusted_host( tmp_dir, poetry ):