diff --git a/src/poetry/console/application.py b/src/poetry/console/application.py index 97a8a2d4197..44a0efd7b0d 100644 --- a/src/poetry/console/application.py +++ b/src/poetry/console/application.py @@ -53,7 +53,6 @@ def _load() -> type[Command]: "build", "check", "config", - "export", "init", "install", "lock", diff --git a/src/poetry/console/commands/export.py b/src/poetry/console/commands/export.py deleted file mode 100644 index c4cdb0f01ac..00000000000 --- a/src/poetry/console/commands/export.py +++ /dev/null @@ -1,80 +0,0 @@ -from __future__ import annotations - -from cleo.helpers import option - -from poetry.console.commands.command import Command -from poetry.utils.exporter import Exporter - - -class ExportCommand(Command): - - name = "export" - description = "Exports the lock file to alternative formats." - - options = [ - option( - "format", - "f", - "Format to export to. Currently, only requirements.txt is supported.", - flag=False, - default=Exporter.FORMAT_REQUIREMENTS_TXT, - ), - 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", - "E", - "Extra sets of dependencies to include.", - flag=False, - multiple=True, - ), - option("with-credentials", None, "Include credentials for extra indices."), - ] - - def handle(self) -> None: - fmt = self.option("format") - - if fmt not in Exporter.ACCEPTED_FORMATS: - raise ValueError(f"Invalid export format: {fmt}") - - output = self.option("output") - - locker = self.poetry.locker - if not locker.is_locked(): - self.line_error("The lock file does not exist. Locking.") - options = [] - if self.io.is_debug(): - options.append("-vvv") - elif self.io.is_very_verbose(): - options.append("-vv") - elif self.io.is_verbose(): - options.append("-v") - - self.call("lock", " ".join(options)) - - if not locker.is_fresh(): - self.line_error( - "" - "Warning: poetry.lock is not consistent with pyproject.toml. " - "You may be getting improper dependencies. " - "Run `poetry lock [--no-update]` to fix it." - "" - ) - - exporter = Exporter(self.poetry) - exporter.export( - fmt, - self.poetry.file.parent, - output or self.io, - with_hashes=not self.option("without-hashes"), - 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 deleted file mode 100644 index c219a8b8522..00000000000 --- a/src/poetry/utils/exporter.py +++ /dev/null @@ -1,182 +0,0 @@ -from __future__ import annotations - -import urllib.parse - -from typing import TYPE_CHECKING -from typing import Sequence - -from poetry.core.packages.utils.utils import path_to_url - -from poetry.utils._compat import decode - - -if TYPE_CHECKING: - from pathlib import Path - - from cleo.io.io import IO - - from poetry.poetry import Poetry - - -class Exporter: - """ - Exporter class to export a lock file to alternative formats. - """ - - FORMAT_REQUIREMENTS_TXT = "requirements.txt" - #: The names of the supported export formats. - ACCEPTED_FORMATS = (FORMAT_REQUIREMENTS_TXT,) - ALLOWED_HASH_ALGORITHMS = ("sha256", "sha384", "sha512") - - def __init__(self, poetry: Poetry) -> None: - self._poetry = poetry - - def export( - self, - fmt: str, - cwd: Path, - output: IO | str, - with_hashes: bool = True, - dev: bool = False, - extras: bool | Sequence[str] | None = None, - with_credentials: bool = False, - with_urls: bool = True, - ) -> None: - if fmt not in self.ACCEPTED_FORMATS: - raise ValueError(f"Invalid export format: {fmt}") - - getattr(self, "_export_" + fmt.replace(".", "_"))( - cwd, - output, - with_hashes=with_hashes, - dev=dev, - extras=extras, - with_credentials=with_credentials, - with_urls=with_urls, - ) - - def _export_requirements_txt( - self, - cwd: Path, - output: IO | str, - with_hashes: bool = True, - dev: bool = False, - extras: bool | Sequence[str] | None = None, - with_credentials: bool = False, - with_urls: bool = True, - ) -> None: - indexes = set() - content = "" - dependency_lines = set() - - # Get project dependencies. - root_package = ( - self._poetry.package.clone() - if dev - else self._poetry.package.with_dependency_groups(["default"], only=True) - ) - - for dependency_package in self._poetry.locker.get_project_dependency_packages( - project_requires=root_package.all_requires, - project_python_marker=root_package.python_marker, - dev=dev, - extras=extras, - ): - line = "" - dependency = dependency_package.dependency - package = dependency_package.package - - if package.develop: - line += "-e " - - requirement = dependency.to_pep_508(with_extras=False) - is_direct_local_reference = ( - dependency.is_file() or dependency.is_directory() - ) - is_direct_remote_reference = dependency.is_vcs() or dependency.is_url() - - if is_direct_remote_reference: - line = requirement - elif is_direct_local_reference: - dependency_uri = path_to_url(dependency.source_url) - line = f"{dependency.name} @ {dependency_uri}" - else: - line = f"{package.name}=={package.version}" - - if not is_direct_remote_reference and ";" in requirement: - markers = requirement.split(";", 1)[1].strip() - if markers: - line += f" ; {markers}" - - if ( - not is_direct_remote_reference - and not is_direct_local_reference - and package.source_url - ): - indexes.add(package.source_url) - - if package.files and with_hashes: - hashes = [] - for f in package.files: - h = f["hash"] - algorithm = "sha256" - if ":" in h: - algorithm, h = h.split(":") - - if algorithm not in self.ALLOWED_HASH_ALGORITHMS: - continue - - hashes.append(f"{algorithm}:{h}") - - if hashes: - sep = " \\\n" - line += sep + sep.join(f" --hash={h}" for h in hashes) - dependency_lines.add(line) - - content += "\n".join(sorted(dependency_lines)) - content += "\n" - - 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): - repositories = [ - r - for r in self._poetry.pool.repositories - if r.url == index.rstrip("/") - ] - if not repositories: - continue - repository = repositories[0] - if ( - self._poetry.pool.has_default() - and repository is self._poetry.pool.repositories[0] - ): - url = ( - repository.authenticated_url - if with_credentials - else repository.url - ) - indexes_header = f"--index-url {url}\n" - continue - - url = ( - repository.authenticated_url if with_credentials else repository.url - ) - parsed_url = urllib.parse.urlsplit(url) - if parsed_url.scheme == "http": - indexes_header += f"--trusted-host {parsed_url.netloc}\n" - indexes_header += f"--extra-index-url {url}\n" - - content = indexes_header + "\n" + content - - self._output(content, cwd, output) - - def _output(self, content: str, cwd: Path, output: IO | str) -> None: - decoded = decode(content) - try: - output.write(decoded) - except AttributeError: - filepath = cwd / output - with filepath.open("w", encoding="utf-8") as f: - f.write(decoded) diff --git a/tests/console/commands/test_export.py b/tests/console/commands/test_export.py deleted file mode 100644 index d34d899aa11..00000000000 --- a/tests/console/commands/test_export.py +++ /dev/null @@ -1,160 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING -from unittest.mock import Mock - -import pytest - -from poetry_plugin_export.exporter import Exporter - -from tests.helpers import get_package - - -if TYPE_CHECKING: - from _pytest.monkeypatch import MonkeyPatch - from cleo.testers.command_tester import CommandTester - - from poetry.poetry import Poetry - from tests.helpers import TestRepository - from tests.types import CommandTesterFactory - from tests.types import ProjectFactory - - -PYPROJECT_CONTENT = """\ -[tool.poetry] -name = "simple-project" -version = "1.2.3" -description = "Some description." -authors = [ - "Sébastien Eustace " -] -license = "MIT" - -readme = "README.rst" - -homepage = "https://python-poetry.org" -repository = "https://github.com/python-poetry/poetry" -documentation = "https://python-poetry.org/docs" - -keywords = ["packaging", "dependency", "poetry"] - -classifiers = [ - "Topic :: Software Development :: Build Tools", - "Topic :: Software Development :: Libraries :: Python Modules" -] - -# Requirements -[tool.poetry.dependencies] -python = "~2.7 || ^3.4" -foo = "^1.0" -bar = { version = "^1.1", optional = true } - -[tool.poetry.extras] -feature_bar = ["bar"] -""" - - -@pytest.fixture(autouse=True) -def setup(repo: TestRepository) -> None: - repo.add_package(get_package("foo", "1.0.0")) - repo.add_package(get_package("bar", "1.1.0")) - - -@pytest.fixture -def poetry(project_factory: ProjectFactory) -> Poetry: - return project_factory(name="export", pyproject_content=PYPROJECT_CONTENT) - - -@pytest.fixture -def tester( - command_tester_factory: CommandTesterFactory, poetry: Poetry -) -> CommandTester: - return command_tester_factory("export", poetry=poetry) - - -def _export_requirements(tester: CommandTester, poetry: Poetry) -> None: - tester.execute("--format requirements.txt --output requirements.txt") - - requirements = poetry.file.parent / "requirements.txt" - assert requirements.exists() - - with requirements.open(encoding="utf-8") as f: - content = f.read() - - assert poetry.locker.lock.exists() - - expected = """\ -foo==1.0.0 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.4" and python_version < "4.0" -""" - - assert content == expected - - -def test_export_exports_requirements_txt_file_locks_if_no_lock_file( - tester: CommandTester, poetry: Poetry -): - assert not poetry.locker.lock.exists() - _export_requirements(tester, poetry) - assert "The lock file does not exist. Locking." in tester.io.fetch_error() - - -def test_export_exports_requirements_txt_uses_lock_file( - tester: CommandTester, poetry: Poetry, do_lock: None -): - _export_requirements(tester, poetry) - assert "The lock file does not exist. Locking." not in tester.io.fetch_error() - - -def test_export_fails_on_invalid_format(tester: CommandTester, do_lock: None): - with pytest.raises(ValueError): - tester.execute("--format invalid") - - -def test_export_prints_to_stdout_by_default(tester: CommandTester, do_lock: None): - tester.execute("--format requirements.txt") - expected = """\ -foo==1.0.0 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.4" and python_version < "4.0" -""" - assert tester.io.fetch_output() == expected - - -def test_export_uses_requirements_txt_format_by_default( - tester: CommandTester, do_lock: None -): - tester.execute() - expected = """\ -foo==1.0.0 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.4" and python_version < "4.0" -""" - assert tester.io.fetch_output() == expected - - -def test_export_includes_extras_by_flag(tester: CommandTester, do_lock: None): - tester.execute("--format requirements.txt --extras feature_bar") - expected = """\ -bar==1.1.0 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.4" and python_version < "4.0" -foo==1.0.0 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.4" and python_version < "4.0" -""" - assert tester.io.fetch_output() == expected - - -def test_export_with_urls( - monkeypatch: MonkeyPatch, tester: CommandTester, poetry: 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, "with_urls", mock_export) - tester.execute("--without-urls") - mock_export.assert_called_once_with(False) diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py deleted file mode 100644 index 1c1cd3f97d2..00000000000 --- a/tests/utils/test_exporter.py +++ /dev/null @@ -1,2168 +0,0 @@ -from __future__ import annotations - -import sys - -from pathlib import Path -from typing import TYPE_CHECKING -from typing import Any -from typing import Iterator - -import pytest - -from poetry.core.packages.dependency import Dependency -from poetry.core.toml.file import TOMLFile - -from poetry.factory import Factory -from poetry.packages import Locker as BaseLocker -from poetry.repositories.legacy_repository import LegacyRepository -from poetry.utils.exporter import Exporter - - -if TYPE_CHECKING: - from _pytest.capture import CaptureFixture - from pytest_mock import MockerFixture - - from poetry.poetry import Poetry - from tests.conftest import Config - from tests.types import FixtureDirGetter - - -class Locker(BaseLocker): - def __init__(self) -> None: - self._lock = TOMLFile(Path.cwd().joinpath("poetry.lock")) - self._locked = True - self._content_hash = self._get_content_hash() - - def locked(self, is_locked: bool = True) -> Locker: - self._locked = is_locked - - return self - - def mock_lock_data(self, data: dict[str, Any]): - self._lock_data = data - - def is_locked(self) -> bool: - return self._locked - - def is_fresh(self) -> bool: - return True - - def _get_content_hash(self) -> str: - return "123456789" - - -@pytest.fixture -def working_directory() -> Path: - return Path(__file__).parent.parent.parent - - -@pytest.fixture(autouse=True) -def mock_path_cwd( - mocker: MockerFixture, working_directory: Path -) -> Iterator[MockerFixture]: - yield mocker.patch("pathlib.Path.cwd", return_value=working_directory) - - -@pytest.fixture() -def locker() -> Locker: - return Locker() - - -@pytest.fixture -def poetry(fixture_dir: FixtureDirGetter, locker: Locker) -> Poetry: - p = Factory().create_poetry(fixture_dir("sample_project")) - p._locker = locker - - return p - - -def set_package_requires(poetry: Poetry, skip: set[str] | None = None) -> None: - skip = skip or set() - packages = poetry.locker.locked_repository(with_dev_reqs=True).packages - package = poetry.package.with_dependency_groups([], only=True) - for pkg in packages: - if pkg.name not in skip: - package.add_dependency(pkg.to_dependency()) - - poetry._package = package - - -def test_exporter_can_export_requirements_txt_with_standard_packages( - tmp_dir: str, poetry: Poetry -): - 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": "main", - "optional": False, - "python-versions": "*", - }, - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"foo": [], "bar": []}, - }, - } - ) - set_package_requires(poetry) - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = """\ -bar==4.5.6 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" -foo==1.2.3 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" -""" - - assert content == expected - - -def test_exporter_can_export_requirements_txt_with_standard_packages_and_markers( - tmp_dir: str, poetry: Poetry -): - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "foo", - "version": "1.2.3", - "category": "main", - "optional": False, - "python-versions": "*", - "marker": "python_version < '3.7'", - }, - { - "name": "bar", - "version": "4.5.6", - "category": "main", - "optional": False, - "python-versions": "*", - "marker": "extra =='foo'", - }, - { - "name": "baz", - "version": "7.8.9", - "category": "main", - "optional": False, - "python-versions": "*", - "marker": "sys_platform == 'win32'", - }, - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"foo": [], "bar": [], "baz": []}, - }, - } - ) - set_package_requires(poetry) - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = """\ -bar==4.5.6 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" -baz==7.8.9 ;\ - python_version >= "2.7" and python_version < "2.8" and sys_platform == "win32" or\ - python_version >= "3.6" and python_version < "4.0" and sys_platform == "win32" -foo==1.2.3 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "3.7" -""" - - assert content == expected - - -def test_exporter_can_export_requirements_txt_poetry(tmp_dir: str, poetry: Poetry): - """Regression test for #3254""" - - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "poetry", - "version": "1.1.4", - "category": "main", - "optional": False, - "python-versions": "*", - "dependencies": {"keyring": "*"}, - }, - { - "name": "junit-xml", - "version": "1.9", - "category": "main", - "optional": False, - "python-versions": "*", - "dependencies": {"six": "*"}, - }, - { - "name": "keyring", - "version": "21.8.0", - "category": "main", - "optional": False, - "python-versions": "*", - "dependencies": { - "SecretStorage": { - "version": "*", - "markers": "sys_platform == 'linux'", - } - }, - }, - { - "name": "secretstorage", - "version": "3.3.0", - "category": "main", - "optional": False, - "python-versions": "*", - "dependencies": {"cryptography": "*"}, - }, - { - "name": "cryptography", - "version": "3.2", - "category": "main", - "optional": False, - "python-versions": "*", - "dependencies": {"six": "*"}, - }, - { - "name": "six", - "version": "1.15.0", - "category": "main", - "optional": False, - "python-versions": "*", - }, - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": { - "poetry": [], - "keyring": [], - "secretstorage": [], - "cryptography": [], - "six": [], - "junit-xml": [], - }, - }, - } - ) - set_package_requires( - poetry, skip={"keyring", "secretstorage", "cryptography", "six"} - ) - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - # The dependency graph: - # junit-xml 1.9 Creates JUnit XML test result documents that can be read by tools - # └── six * such as Jenkins - # poetry 1.1.4 Python dependency management and packaging made easy. - # ├── keyring >=21.2.0,<22.0.0 - # │ ├── importlib-metadata >=1 - # │ │ └── zipp >=0.5 - # │ ├── jeepney >=0.4.2 - # │ ├── pywin32-ctypes <0.1.0 || >0.1.0,<0.1.1 || >0.1.1 - # │ └── secretstorage >=3.2 -- On linux only - # │ ├── cryptography >=2.0 - # │ │ └── six >=1.4.1 - # │ └── jeepney >=0.6 (circular dependency aborted here) - python27 = 'python_version >= "2.7" and python_version < "2.8"' - python36 = 'python_version >= "3.6" and python_version < "4.0"' - linux = 'sys_platform=="linux"' - expected = { - "poetry": Dependency.create_from_pep_508( - f"poetry==1.1.4; {python27} or {python36}" - ), - "junit-xml": Dependency.create_from_pep_508( - f"junit-xml==1.9 ; {python27} or {python36}" - ), - "keyring": Dependency.create_from_pep_508( - f"keyring==21.8.0 ; {python27} or {python36}" - ), - "secretstorage": Dependency.create_from_pep_508( - f"secretstorage==3.3.0 ; {python27} and {linux} or {python36} and {linux}" - ), - "cryptography": Dependency.create_from_pep_508( - f"cryptography==3.2 ; {python27} and {linux} or {python36} and {linux}" - ), - "six": Dependency.create_from_pep_508( - f"six==1.15.0 ; {python27} or {python36} or {python27} and {linux} or" - f" {python36} and {linux}" - ), - } - - for line in content.strip().split("\n"): - dependency = Dependency.create_from_pep_508(line) - assert dependency.name in expected - expected_dependency = expected.pop(dependency.name) - assert dependency == expected_dependency - assert dependency.marker == expected_dependency.marker - - -def test_exporter_can_export_requirements_txt_pyinstaller(tmp_dir: str, poetry: Poetry): - """Regression test for #3254""" - - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "pyinstaller", - "version": "4.0", - "category": "main", - "optional": False, - "python-versions": "*", - "dependencies": { - "altgraph": "*", - "macholib": { - "version": "*", - "markers": "sys_platform == 'darwin'", - }, - }, - }, - { - "name": "altgraph", - "version": "0.17", - "category": "main", - "optional": False, - "python-versions": "*", - }, - { - "name": "macholib", - "version": "1.8", - "category": "main", - "optional": False, - "python-versions": "*", - "dependencies": {"altgraph": ">=0.15"}, - }, - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"pyinstaller": [], "altgraph": [], "macholib": []}, - }, - } - ) - set_package_requires(poetry, skip={"altgraph", "macholib"}) - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - # Rationale for the results: - # * PyInstaller has an explicit dependency on altgraph, so it must always be - # installed. - # * PyInstaller requires macholib on Darwin, which in turn requires altgraph. - # The dependency graph: - # pyinstaller 4.0 PyInstaller bundles a Python application and all its - # ├── altgraph * dependencies into a single package. - # ├── macholib >=1.8 -- only on Darwin - # │ └── altgraph >=0.15 - python27 = 'python_version >= "2.7" and python_version < "2.8"' - python36 = 'python_version >= "3.6" and python_version < "4.0"' - darwin = 'sys_platform=="darwin"' - expected = { - "pyinstaller": Dependency.create_from_pep_508( - f"pyinstaller==4.0 ; {python27} or {python36}" - ), - "altgraph": Dependency.create_from_pep_508( - f"altgraph==0.17 ; {python27} or {python36} or {python27} and {darwin} or" - f" {python36} and {darwin}" - ), - "macholib": Dependency.create_from_pep_508( - f"macholib==1.8 ; {python27} and {darwin} or {python36} and {darwin}" - ), - } - - for line in content.strip().split("\n"): - dependency = Dependency.create_from_pep_508(line) - assert dependency.name in expected - expected_dependency = expected.pop(dependency.name) - assert dependency == expected_dependency - assert dependency.marker == expected_dependency.marker - - -def test_exporter_can_export_requirements_txt_with_nested_packages_and_markers( - tmp_dir: str, poetry: Poetry -): - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "a", - "version": "1.2.3", - "category": "main", - "optional": False, - "python-versions": "*", - "marker": "python_version < '3.7'", - "dependencies": {"b": ">=0.0.0", "c": ">=0.0.0"}, - }, - { - "name": "b", - "version": "4.5.6", - "category": "main", - "optional": False, - "python-versions": "*", - "marker": "platform_system == 'Windows'", - "dependencies": {"d": ">=0.0.0"}, - }, - { - "name": "c", - "version": "7.8.9", - "category": "main", - "optional": False, - "python-versions": "*", - "marker": "sys_platform == 'win32'", - "dependencies": {"d": ">=0.0.0"}, - }, - { - "name": "d", - "version": "0.0.1", - "category": "main", - "optional": False, - "python-versions": "*", - }, - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"a": [], "b": [], "c": [], "d": []}, - }, - } - ) - set_package_requires(poetry, skip={"b", "c", "d"}) - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - python27 = 'python_version >= "2.7" and python_version < "2.8"' - python36 = 'python_version >= "3.6" and python_version < "3.7"' - windows = 'platform_system == "Windows"' - win32 = 'sys_platform == "win32"' - expected = { - "a": Dependency.create_from_pep_508(f"a==1.2.3 ; {python27} or {python36}"), - "b": Dependency.create_from_pep_508( - f"b==4.5.6 ; {python27} and {windows} or {python36} and {windows}" - ), - "c": Dependency.create_from_pep_508( - f"c==7.8.9 ; {python27} and {win32} or {python36} and {win32}" - ), - "d": Dependency.create_from_pep_508( - f"d==0.0.1 ; {python27} and {windows} or {python36} and {windows} or" - f" {python27} and {win32} or {python36} and {win32}" - ), - } - - for line in content.strip().split("\n"): - dependency = Dependency.create_from_pep_508(line) - assert dependency.name in expected - expected_dependency = expected.pop(dependency.name) - assert dependency == expected_dependency - assert dependency.marker == expected_dependency.marker - - assert expected == {} - - -@pytest.mark.parametrize( - ["dev", "lines"], - [ - ( - False, - [ - 'a==1.2.3 ; python_version >= "2.7" and python_version < "2.8" or' - ' python_version >= "3.6" and python_version < "3.8"' - ], - ), - ( - True, - [ - 'a==1.2.3 ; python_version >= "2.7" and python_version < "2.8" or' - ' python_version >= "3.6" and python_version < "3.8" or python_version' - ' >= "3.6" and python_version < "4.0"', - 'b==4.5.6 ; python_version >= "2.7" and python_version < "2.8" or' - ' python_version >= "3.6" and python_version < "4.0"', - ], - ), - ], -) -def test_exporter_can_export_requirements_txt_with_nested_packages_and_markers_any( - tmp_dir: str, poetry: Poetry, dev: bool, lines: list[str] -): - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "a", - "version": "1.2.3", - "category": "main", - "optional": False, - "python-versions": "*", - }, - { - "name": "b", - "version": "4.5.6", - "category": "dev", - "optional": False, - "python-versions": "*", - "dependencies": {"a": ">=1.2.3"}, - }, - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"a": [], "b": []}, - }, - } - ) - - root = poetry.package.with_dependency_groups([], only=True) - root.add_dependency( - Factory.create_dependency( - name="a", constraint={"version": "^1.2.3", "python": "<3.8"} - ) - ) - root.add_dependency( - Factory.create_dependency( - name="b", constraint={"version": "^4.5.6"}, groups=["dev"] - ) - ) - poetry._package = root - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=dev) - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - assert content.strip() == "\n".join(lines) - - -def test_exporter_can_export_requirements_txt_with_standard_packages_and_hashes( - tmp_dir: str, poetry: Poetry -): - 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": "main", - "optional": False, - "python-versions": "*", - }, - ], - "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") - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = """\ -bar==4.5.6 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" \\ - --hash=sha256:67890 -foo==1.2.3 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" \\ - --hash=sha256:12345 -""" - - assert content == expected - - -def test_exporter_can_export_requirements_txt_with_standard_packages_and_hashes_disabled( # noqa: E501 - tmp_dir: str, poetry: Poetry -): - 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": "main", - "optional": False, - "python-versions": "*", - }, - ], - "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", with_hashes=False - ) - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = """\ -bar==4.5.6 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" -foo==1.2.3 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" -""" - - assert content == expected - - -def test_exporter_exports_requirements_txt_without_dev_packages_by_default( - tmp_dir: str, poetry: Poetry -): - 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": "*", - }, - ], - "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") - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = """\ -foo==1.2.3 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" \\ - --hash=sha256:12345 -""" - - assert content == expected - - -def test_exporter_exports_requirements_txt_with_dev_packages_if_opted_in( - tmp_dir: str, poetry: Poetry -): - 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": "*", - }, - ], - "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 (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = """\ -bar==4.5.6 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" \\ - --hash=sha256:67890 -foo==1.2.3 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" \\ - --hash=sha256:12345 -""" - - assert content == expected - - -def test_exporter_exports_requirements_txt_without_optional_packages( - tmp_dir: str, poetry: Poetry -): - 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": True, - "python-versions": "*", - }, - ], - "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 (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = """\ -foo==1.2.3 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" \\ - --hash=sha256:12345 -""" - - assert content == expected - - -@pytest.mark.parametrize( - ["extras", "lines"], - [ - ( - None, - [ - 'foo==1.2.3 ; python_version >= "2.7" and python_version < "2.8" or' - ' python_version >= "3.6" and python_version < "4.0"' - ], - ), - ( - False, - [ - 'foo==1.2.3 ; python_version >= "2.7" and python_version < "2.8" or' - ' python_version >= "3.6" and python_version < "4.0"' - ], - ), - ( - True, - [ - 'bar==4.5.6 ; python_version >= "2.7" and python_version < "2.8" or' - ' python_version >= "3.6" and python_version < "4.0"', - 'foo==1.2.3 ; python_version >= "2.7" and python_version < "2.8" or' - ' python_version >= "3.6" and python_version < "4.0"', - 'spam==0.1.0 ; python_version >= "2.7" and python_version < "2.8" or' - ' python_version >= "3.6" and python_version < "4.0"', - ], - ), - ( - ["feature_bar"], - [ - 'bar==4.5.6 ; python_version >= "2.7" and python_version < "2.8" or' - ' python_version >= "3.6" and python_version < "4.0"', - 'foo==1.2.3 ; python_version >= "2.7" and python_version < "2.8" or' - ' python_version >= "3.6" and python_version < "4.0"', - 'spam==0.1.0 ; python_version >= "2.7" and python_version < "2.8" or' - ' python_version >= "3.6" and python_version < "4.0"', - ], - ), - ], -) -def test_exporter_exports_requirements_txt_with_optional_packages( - tmp_dir: str, - poetry: Poetry, - extras: bool | list[str] | None, - lines: list[str], -): - 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": "main", - "optional": True, - "python-versions": "*", - "dependencies": {"spam": ">=0.1"}, - }, - { - "name": "spam", - "version": "0.1.0", - "category": "main", - "optional": True, - "python-versions": "*", - }, - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"foo": ["12345"], "bar": ["67890"], "spam": ["abcde"]}, - }, - "extras": {"feature_bar": ["bar"]}, - } - ) - set_package_requires(poetry) - - exporter = Exporter(poetry) - - exporter.export( - "requirements.txt", - Path(tmp_dir), - "requirements.txt", - dev=True, - with_hashes=False, - extras=extras, - ) - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = "\n".join(lines) - - assert content.strip() == expected - - -def test_exporter_can_export_requirements_txt_with_git_packages( - tmp_dir: str, poetry: Poetry -): - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "foo", - "version": "1.2.3", - "category": "main", - "optional": False, - "python-versions": "*", - "source": { - "type": "git", - "url": "https://github.com/foo/foo.git", - "reference": "123456", - }, - } - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"foo": []}, - }, - } - ) - set_package_requires(poetry) - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = """\ -foo @ git+https://github.com/foo/foo.git@123456 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" -""" - - assert content == expected - - -def test_exporter_can_export_requirements_txt_with_nested_packages( - tmp_dir: str, poetry: Poetry -): - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "foo", - "version": "1.2.3", - "category": "main", - "optional": False, - "python-versions": "*", - "source": { - "type": "git", - "url": "https://github.com/foo/foo.git", - "reference": "123456", - }, - }, - { - "name": "bar", - "version": "4.5.6", - "category": "main", - "optional": False, - "python-versions": "*", - "dependencies": {"foo": "rev 123456"}, - }, - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"foo": [], "bar": []}, - }, - } - ) - set_package_requires(poetry, skip={"foo"}) - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = """\ -bar==4.5.6 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" -foo @ git+https://github.com/foo/foo.git@123456 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" -""" - - assert content == expected - - -def test_exporter_can_export_requirements_txt_with_nested_packages_cyclic( - tmp_dir: str, poetry: Poetry -): - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "foo", - "version": "1.2.3", - "category": "main", - "optional": False, - "python-versions": "*", - "dependencies": {"bar": {"version": "4.5.6"}}, - }, - { - "name": "bar", - "version": "4.5.6", - "category": "main", - "optional": False, - "python-versions": "*", - "dependencies": {"baz": {"version": "7.8.9"}}, - }, - { - "name": "baz", - "version": "7.8.9", - "category": "main", - "optional": False, - "python-versions": "*", - "dependencies": {"foo": {"version": "1.2.3"}}, - }, - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"foo": [], "bar": [], "baz": []}, - }, - } - ) - set_package_requires(poetry, skip={"bar", "baz"}) - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = """\ -bar==4.5.6 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" -baz==7.8.9 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" -foo==1.2.3 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" -""" - - assert content == expected - - -def test_exporter_can_export_requirements_txt_with_nested_packages_and_multiple_markers( - tmp_dir: str, poetry: Poetry -): - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "foo", - "version": "1.2.3", - "category": "main", - "optional": False, - "python-versions": "*", - "dependencies": { - "bar": [ - { - "version": ">=1.2.3,<7.8.10", - "markers": 'platform_system != "Windows"', - }, - { - "version": ">=4.5.6,<7.8.10", - "markers": 'platform_system == "Windows"', - }, - ] - }, - }, - { - "name": "bar", - "version": "7.8.9", - "category": "main", - "optional": True, - "python-versions": "*", - "dependencies": { - "baz": { - "version": "!=10.11.12", - "markers": 'platform_system == "Windows"', - } - }, - }, - { - "name": "baz", - "version": "10.11.13", - "category": "main", - "optional": True, - "python-versions": "*", - }, - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"foo": [], "bar": [], "baz": []}, - }, - } - ) - set_package_requires(poetry) - - exporter = Exporter(poetry) - - exporter.export( - "requirements.txt", Path(tmp_dir), "requirements.txt", with_hashes=False - ) - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = """\ -bar==7.8.9 ;\ - python_version >= "2.7" and python_version < "2.8" and platform_system != "Windows" or\ - python_version >= "3.6" and python_version < "4.0" and platform_system != "Windows" or\ - python_version >= "2.7" and python_version < "2.8" and platform_system == "Windows" or\ - python_version >= "3.6" and python_version < "4.0" and platform_system == "Windows" -baz==10.11.13 ;\ - python_version >= "2.7" and python_version < "2.8" and platform_system == "Windows" or\ - python_version >= "3.6" and python_version < "4.0" and platform_system == "Windows" -foo==1.2.3 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" -""" - - assert content == expected - - -def test_exporter_can_export_requirements_txt_with_git_packages_and_markers( - tmp_dir: str, poetry: Poetry -): - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "foo", - "version": "1.2.3", - "category": "main", - "optional": False, - "python-versions": "*", - "marker": "python_version < '3.7'", - "source": { - "type": "git", - "url": "https://github.com/foo/foo.git", - "reference": "123456", - }, - } - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"foo": []}, - }, - } - ) - set_package_requires(poetry) - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = """\ -foo @ git+https://github.com/foo/foo.git@123456 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "3.7" -""" - - assert content == expected - - -def test_exporter_can_export_requirements_txt_with_directory_packages( - tmp_dir: str, poetry: Poetry, working_directory: Path -): - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "foo", - "version": "1.2.3", - "category": "main", - "optional": False, - "python-versions": "*", - "source": { - "type": "directory", - "url": "tests/fixtures/sample_project", - "reference": "", - }, - } - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"foo": []}, - }, - } - ) - set_package_requires(poetry) - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = f"""\ -foo @ {working_directory.as_uri()}/tests/fixtures/sample_project ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" -""" - - assert content == expected - - -def test_exporter_can_export_requirements_txt_with_nested_directory_packages( - tmp_dir: str, poetry: Poetry, working_directory: Path -): - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "foo", - "version": "1.2.3", - "category": "main", - "optional": False, - "python-versions": "*", - "source": { - "type": "directory", - "url": "tests/fixtures/sample_project", - "reference": "", - }, - }, - { - "name": "bar", - "version": "4.5.6", - "category": "main", - "optional": False, - "python-versions": "*", - "source": { - "type": "directory", - "url": "tests/fixtures/sample_project/../project_with_nested_local/bar", # noqa: E501 - "reference": "", - }, - }, - { - "name": "baz", - "version": "7.8.9", - "category": "main", - "optional": False, - "python-versions": "*", - "source": { - "type": "directory", - "url": "tests/fixtures/sample_project/../project_with_nested_local/bar/..", # noqa: E501 - "reference": "", - }, - }, - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"foo": [], "bar": [], "baz": []}, - }, - } - ) - set_package_requires(poetry) - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = f"""\ -bar @ {working_directory.as_uri()}/tests/fixtures/project_with_nested_local/bar ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" -baz @ {working_directory.as_uri()}/tests/fixtures/project_with_nested_local ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" -foo @ {working_directory.as_uri()}/tests/fixtures/sample_project ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" -""" - - assert content == expected - - -def test_exporter_can_export_requirements_txt_with_directory_packages_and_markers( - tmp_dir: str, poetry: Poetry, working_directory: Path -): - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "foo", - "version": "1.2.3", - "category": "main", - "optional": False, - "python-versions": "*", - "marker": "python_version < '3.7'", - "source": { - "type": "directory", - "url": "tests/fixtures/sample_project", - "reference": "", - }, - } - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"foo": []}, - }, - } - ) - set_package_requires(poetry) - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = f"""\ -foo @ {working_directory.as_uri()}/tests/fixtures/sample_project ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "3.7" -""" - - assert content == expected - - -def test_exporter_can_export_requirements_txt_with_file_packages( - tmp_dir: str, poetry: Poetry, working_directory: Path -): - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "foo", - "version": "1.2.3", - "category": "main", - "optional": False, - "python-versions": "*", - "source": { - "type": "file", - "url": "tests/fixtures/distributions/demo-0.1.0.tar.gz", - "reference": "", - }, - } - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"foo": []}, - }, - } - ) - set_package_requires(poetry) - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = f"""\ -foo @ {working_directory.as_uri()}/tests/fixtures/distributions/demo-0.1.0.tar.gz ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" -""" - - assert content == expected - - -def test_exporter_can_export_requirements_txt_with_file_packages_and_markers( - tmp_dir: str, poetry: Poetry, working_directory: Path -): - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "foo", - "version": "1.2.3", - "category": "main", - "optional": False, - "python-versions": "*", - "marker": "python_version < '3.7'", - "source": { - "type": "file", - "url": "tests/fixtures/distributions/demo-0.1.0.tar.gz", - "reference": "", - }, - } - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"foo": []}, - }, - } - ) - set_package_requires(poetry) - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = f"""\ -foo @ {working_directory.as_uri()}/tests/fixtures/distributions/demo-0.1.0.tar.gz ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "3.7" -""" - - assert content == expected - - -def test_exporter_exports_requirements_txt_with_legacy_packages( - tmp_dir: str, poetry: 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 (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = """\ ---extra-index-url https://example.com/simple - -bar==4.5.6 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" \\ - --hash=sha256:67890 -foo==1.2.3 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" \\ - --hash=sha256:12345 -""" - - assert content == expected - - -def test_exporter_exports_requirements_txt_with_url_false(tmp_dir: str, poetry: 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 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" \\ - --hash=sha256:67890 -foo==1.2.3 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" \\ - --hash=sha256:12345 -""" - - assert content == expected - - -def test_exporter_exports_requirements_txt_with_legacy_packages_trusted_host( - tmp_dir: str, poetry: Poetry -): - poetry.pool.add_repository( - LegacyRepository( - "custom", - "http://example.com/simple", - ) - ) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "bar", - "version": "4.5.6", - "category": "dev", - "optional": False, - "python-versions": "*", - "source": { - "type": "legacy", - "url": "http://example.com/simple", - "reference": "", - }, - }, - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"bar": ["67890"]}, - }, - } - ) - set_package_requires(poetry) - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True) - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = """\ ---trusted-host example.com ---extra-index-url http://example.com/simple - -bar==4.5.6 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" \\ - --hash=sha256:67890 -""" - - assert content == expected - - -@pytest.mark.parametrize( - ["dev", "expected"], - [ - ( - True, - [ - 'bar==1.2.2 ; python_version >= "2.7" and python_version < "2.8" or' - ' python_version >= "3.6" and python_version < "4.0"', - 'baz==1.2.3 ; python_version >= "2.7" and python_version < "2.8" or' - ' python_version >= "3.6" and python_version < "4.0"', - 'foo==1.2.1 ; python_version >= "2.7" and python_version < "2.8" or' - ' python_version >= "3.6" and python_version < "4.0"', - ], - ), - ( - False, - [ - 'bar==1.2.2 ; python_version >= "2.7" and python_version < "2.8" or' - ' python_version >= "3.6" and python_version < "4.0"', - 'foo==1.2.1 ; python_version >= "2.7" and python_version < "2.8" or' - ' python_version >= "3.6" and python_version < "4.0"', - ], - ), - ], -) -def test_exporter_exports_requirements_txt_with_dev_extras( - tmp_dir: str, poetry: Poetry, dev: bool, expected: list[str] -): - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "foo", - "version": "1.2.1", - "category": "main", - "optional": False, - "python-versions": "*", - }, - { - "name": "bar", - "version": "1.2.2", - "category": "main", - "optional": False, - "python-versions": "*", - "dependencies": { - "baz": { - "version": ">=0.1.0", - "optional": True, - "markers": "extra == 'baz'", - } - }, - "extras": {"baz": ["baz (>=0.1.0)"]}, - }, - { - "name": "baz", - "version": "1.2.3", - "category": "dev", - "optional": False, - "python-versions": "*", - }, - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"foo": [], "bar": [], "baz": []}, - }, - } - ) - set_package_requires(poetry) - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=dev) - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - assert content == "\n".join(expected) + "\n" - - -def test_exporter_exports_requirements_txt_with_legacy_packages_and_duplicate_sources( - tmp_dir: str, poetry: Poetry -): - poetry.pool.add_repository( - LegacyRepository( - "custom", - "https://example.com/simple", - ) - ) - poetry.pool.add_repository( - LegacyRepository( - "custom", - "https://foobaz.com/simple", - ) - ) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "foo", - "version": "1.2.3", - "category": "main", - "optional": False, - "python-versions": "*", - "source": { - "type": "legacy", - "url": "https://example.com/simple", - "reference": "", - }, - }, - { - "name": "bar", - "version": "4.5.6", - "category": "dev", - "optional": False, - "python-versions": "*", - "source": { - "type": "legacy", - "url": "https://example.com/simple", - "reference": "", - }, - }, - { - "name": "baz", - "version": "7.8.9", - "category": "dev", - "optional": False, - "python-versions": "*", - "source": { - "type": "legacy", - "url": "https://foobaz.com/simple", - "reference": "", - }, - }, - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"foo": ["12345"], "bar": ["67890"], "baz": ["24680"]}, - }, - } - ) - set_package_requires(poetry) - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True) - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = """\ ---extra-index-url https://example.com/simple ---extra-index-url https://foobaz.com/simple - -bar==4.5.6 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" \\ - --hash=sha256:67890 -baz==7.8.9 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" \\ - --hash=sha256:24680 -foo==1.2.3 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" \\ - --hash=sha256:12345 -""" - - assert content == expected - - -def test_exporter_exports_requirements_txt_with_legacy_packages_and_credentials( - tmp_dir: str, poetry: Poetry, config: Config -): - poetry.config.merge( - { - "repositories": {"custom": {"url": "https://example.com/simple"}}, - "http-basic": {"custom": {"username": "foo", "password": "bar"}}, - } - ) - poetry.pool.add_repository( - LegacyRepository("custom", "https://example.com/simple", config=poetry.config) - ) - 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_credentials=True, - ) - - with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: - content = f.read() - - expected = """\ ---extra-index-url https://foo:bar@example.com/simple - -bar==4.5.6 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" \\ - --hash=sha256:67890 -foo==1.2.3 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" \\ - --hash=sha256:12345 -""" - - assert content == expected - - -def test_exporter_exports_requirements_txt_to_standard_output( - tmp_dir: str, poetry: Poetry, capsys: CaptureFixture -): - 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": "main", - "optional": False, - "python-versions": "*", - }, - ], - "metadata": { - "python-versions": "*", - "content-hash": "123456789", - "hashes": {"foo": [], "bar": []}, - }, - } - ) - set_package_requires(poetry) - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), sys.stdout) - - out, err = capsys.readouterr() - expected = """\ -bar==4.5.6 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" -foo==1.2.3 ;\ - python_version >= "2.7" and python_version < "2.8" or\ - python_version >= "3.6" and python_version < "4.0" -""" - - assert out == expected - - -def test_exporter_doesnt_confuse_repeated_packages( - tmp_dir: str, poetry: Poetry, capsys: CaptureFixture -): - # Testcase derived from . - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "celery", - "version": "5.1.2", - "category": "main", - "optional": False, - "python-versions": "<3.7", - "dependencies": { - "click": ">=7.0,<8.0", - "click-didyoumean": ">=0.0.3", - "click-plugins": ">=1.1.1", - }, - }, - { - "name": "celery", - "version": "5.2.3", - "category": "main", - "optional": False, - "python-versions": ">=3.7", - "dependencies": { - "click": ">=8.0.3,<9.0", - "click-didyoumean": ">=0.0.3", - "click-plugins": ">=1.1.1", - }, - }, - { - "name": "click", - "version": "7.1.2", - "category": "main", - "optional": False, - "python-versions": ( - ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - ), - }, - { - "name": "click", - "version": "8.0.3", - "category": "main", - "optional": False, - "python-versions": ">=3.6", - "dependencies": {}, - }, - { - "name": "click-didyoumean", - "version": "0.0.3", - "category": "main", - "optional": False, - "python-versions": "*", - "dependencies": {"click": "*"}, - }, - { - "name": "click-didyoumean", - "version": "0.3.0", - "category": "main", - "optional": False, - "python-versions": ">=3.6.2,<4.0.0", - "dependencies": {"click": ">=7"}, - }, - { - "name": "click-plugins", - "version": "1.1.1", - "category": "main", - "optional": False, - "python-versions": "*", - "dependencies": {"click": ">=4.0"}, - }, - ], - "metadata": { - "lock-version": "1.1", - "python-versions": "^3.6", - "content-hash": ( - "832b13a88e5020c27cbcd95faa577bf0dbf054a65c023b45dc9442b640d414e6" - ), - "hashes": { - "celery": [], - "click-didyoumean": [], - "click-plugins": [], - "click": [], - }, - }, - } - ) - root = poetry.package.with_dependency_groups([], only=True) - root.python_versions = "^3.6" - root.add_dependency( - Factory.create_dependency( - name="celery", constraint={"version": "5.1.2", "python": "<3.7"} - ) - ) - root.add_dependency( - Factory.create_dependency( - name="celery", constraint={"version": "5.2.3", "python": ">=3.7"} - ) - ) - poetry._package = root - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), sys.stdout) - - out, err = capsys.readouterr() - expected = """\ -celery==5.1.2 ; python_version >= "3.6" and python_version < "3.7" -celery==5.2.3 ; python_version >= "3.7" and python_version < "4.0" -click-didyoumean==0.0.3 ; python_version >= "3.6" and python_version < "3.7" -click-didyoumean==0.3.0 ; python_version >= "3.7" and python_full_version < "4.0.0" -click-plugins==1.1.1 ;\ - python_version >= "3.6" and python_version < "3.7" or\ - python_version >= "3.7" and python_version < "4.0" -click==7.1.2 ; python_version >= "3.6" and python_version < "3.7" -click==8.0.3 ;\ - python_version >= "3.7" and python_version < "4.0" or\ - python_version >= "3.7" and python_full_version < "4.0.0" -""" - - assert out == expected - - -def test_exporter_handles_extras_next_to_non_extras( - tmp_dir: str, poetry: Poetry, capsys: CaptureFixture -): - # Testcase similar to the solver testcase added at #5305. - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "localstack", - "python-versions": "*", - "version": "1.0.0", - "category": "main", - "optional": False, - "dependencies": { - "localstack-ext": [ - {"version": ">=1.0.0"}, - { - "version": ">=1.0.0", - "extras": ["bar"], - "markers": 'extra == "foo"', - }, - ] - }, - "extras": {"foo": ["localstack-ext (>=1.0.0)"]}, - }, - { - "name": "localstack-ext", - "python-versions": "*", - "version": "1.0.0", - "category": "main", - "optional": False, - "dependencies": { - "something": "*", - "something-else": { - "version": ">=1.0.0", - "markers": 'extra == "bar"', - }, - "another-thing": { - "version": ">=1.0.0", - "markers": 'extra == "baz"', - }, - }, - "extras": { - "bar": ["something-else (>=1.0.0)"], - "baz": ["another-thing (>=1.0.0)"], - }, - }, - { - "name": "something", - "python-versions": "*", - "version": "1.0.0", - "category": "main", - "optional": False, - "dependencies": {}, - }, - { - "name": "something-else", - "python-versions": "*", - "version": "1.0.0", - "category": "main", - "optional": False, - "dependencies": {}, - }, - { - "name": "another-thing", - "python-versions": "*", - "version": "1.0.0", - "category": "main", - "optional": False, - "dependencies": {}, - }, - ], - "metadata": { - "lock-version": "1.1", - "python-versions": "^3.6", - "content-hash": ( - "832b13a88e5020c27cbcd95faa577bf0dbf054a65c023b45dc9442b640d414e6" - ), - "hashes": { - "localstack": [], - "localstack-ext": [], - "something": [], - "something-else": [], - "another-thing": [], - }, - }, - } - ) - root = poetry.package.with_dependency_groups([], only=True) - root.python_versions = "^3.6" - root.add_dependency( - Factory.create_dependency( - name="localstack", constraint={"version": "^1.0.0", "extras": ["foo"]} - ) - ) - poetry._package = root - - exporter = Exporter(poetry) - - exporter.export("requirements.txt", Path(tmp_dir), sys.stdout) - - out, err = capsys.readouterr() - expected = """\ -localstack-ext==1.0.0 ; python_version >= "3.6" and python_version < "4.0" -localstack==1.0.0 ; python_version >= "3.6" and python_version < "4.0" -something-else==1.0.0 ; python_version >= "3.6" and python_version < "4.0" -something==1.0.0 ; python_version >= "3.6" and python_version < "4.0" -""" - - assert out == expected