From fff771e1682bb98af2ad74da712701d7fdb0d739 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sat, 5 Feb 2022 13:56:42 +0000 Subject: [PATCH] group packages correctly when exporting --- src/poetry/packages/locker.py | 2 +- src/poetry/utils/exporter.py | 24 +++++--- tests/utils/test_exporter.py | 108 ++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 9 deletions(-) diff --git a/src/poetry/packages/locker.py b/src/poetry/packages/locker.py index 4cf56b9ae93..0c2fe9d17a2 100644 --- a/src/poetry/packages/locker.py +++ b/src/poetry/packages/locker.py @@ -342,7 +342,7 @@ def get_project_dependencies( requirement.marker ) - return sorted(nested_dependencies.values(), key=lambda x: x.name.lower()) + return nested_dependencies.values() def get_project_dependency_packages( self, diff --git a/src/poetry/utils/exporter.py b/src/poetry/utils/exporter.py index 98070d19b23..1e4a544faa9 100644 --- a/src/poetry/utils/exporter.py +++ b/src/poetry/utils/exporter.py @@ -1,7 +1,9 @@ -import itertools import urllib.parse +from collections import defaultdict from typing import TYPE_CHECKING +from typing import Dict +from typing import List from typing import Optional from typing import Sequence from typing import Union @@ -15,6 +17,8 @@ from pathlib import Path from cleo.io.io import IO + from poetry.core.packages.dependency_package import DependencyPackage + from poetry.core.packages.package import Package from poetry.poetry import Poetry @@ -70,14 +74,18 @@ def _export_requirements_txt( content = "" dependency_lines = set() - for package, groups in itertools.groupby( - self._poetry.locker.get_project_dependency_packages( - project_requires=self._poetry.package.all_requires, - dev=dev, - extras=extras, - ), - lambda dependency_package: dependency_package.package, + # Group by package. + dependency_packages: Dict["Package", List["DependencyPackage"]] = defaultdict( + list + ) + for dependency_package in self._poetry.locker.get_project_dependency_packages( + project_requires=self._poetry.package.all_requires, + dev=dev, + extras=extras, ): + dependency_packages[dependency_package.package].append(dependency_package) + + for package, groups in dependency_packages.items(): line = "" dependency_packages = list(groups) dependency = dependency_packages[0].dependency diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py index 53dfd0a2282..73c438db9b4 100644 --- a/tests/utils/test_exporter.py +++ b/tests/utils/test_exporter.py @@ -1775,3 +1775,111 @@ def test_exporter_exports_requirements_txt_to_standard_output( """ 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.6,", + "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": [], + }, + }, + } + ) + set_package_requires(poetry) + + 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"\n' + 'celery==5.2.3 ; python_version >= "3.7"\n' + 'click-didyoumean==0.0.3\n' + 'click-didyoumean==0.3.0 ; python_full_version >= "3.6.2" and python_full_version < "4.0.0"\n' # noqa: E501 + 'click-plugins==1.1.1\n' + 'click==7.1.2 ; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" or python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7" or python_full_version >= "3.6.2" and python_full_version < "4.0.0" or python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"\n' # noqa: E501 + 'click==8.0.3 ; python_version >= "3.6"\n' + ) + + assert out == expected