Skip to content

Commit 5860a63

Browse files
committed
refactor poetry export
Introduce a new root_package section in poetry.lock, and miscellaneous fixes
1 parent 58d2355 commit 5860a63

37 files changed

+699
-226
lines changed

src/poetry/packages/locker.py

Lines changed: 171 additions & 179 deletions
Large diffs are not rendered by default.

src/poetry/repositories/base_repository.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
class BaseRepository:
1212
def __init__(self) -> None:
1313
self._packages: List["Package"] = []
14+
self.root_package: Optional["Package"] = None
1415

1516
@property
1617
def packages(self) -> List["Package"]:

src/poetry/utils/exporter.py

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import urllib.parse
22

3-
from collections import defaultdict
3+
from copy import deepcopy
44
from typing import TYPE_CHECKING
5-
from typing import Dict
6-
from typing import List
75
from typing import Optional
86
from typing import Sequence
97
from typing import Union
@@ -17,8 +15,6 @@
1715
from pathlib import Path
1816

1917
from cleo.io.io import IO
20-
from poetry.core.packages.dependency_package import DependencyPackage
21-
from poetry.core.packages.package import Package
2218

2319
from poetry.poetry import Poetry
2420

@@ -74,25 +70,37 @@ def _export_requirements_txt(
7470
content = ""
7571
dependency_lines = set()
7672

77-
# Group by package.
78-
dependency_packages: Dict["Package", List["DependencyPackage"]] = defaultdict(
79-
list
73+
# If we have a root package then use that to tell us what the top-level
74+
# dependencies are. If we don't (older version of poetry.lock) then try to
75+
# cover all dependencies.
76+
repository = self._poetry.locker.locked_repository(with_dev_reqs=dev)
77+
root_package = repository.root_package
78+
project_requires = (
79+
self._poetry.package.all_requires
80+
if root_package is None
81+
else root_package.requires
8082
)
83+
84+
# If we have a root package then we can also update the marker on each
85+
# dependency to take account of the project-level python version.
86+
if root_package is not None:
87+
restricted_project_requires = []
88+
for project_require in project_requires:
89+
project_require = deepcopy(project_require)
90+
project_require.marker = project_require.marker.intersect(
91+
root_package.python_marker
92+
)
93+
restricted_project_requires.append(project_require)
94+
project_requires = restricted_project_requires
95+
8196
for dependency_package in self._poetry.locker.get_project_dependency_packages(
82-
project_requires=self._poetry.package.all_requires,
97+
project_requires=project_requires,
8398
dev=dev,
8499
extras=extras,
85100
):
86-
dependency_packages[dependency_package.package].append(dependency_package)
87-
88-
for package, groups in dependency_packages.items():
89101
line = ""
90-
dependency_packages = list(groups)
91-
dependency = dependency_packages[0].dependency
92-
marker = dependency.marker
93-
for dep_package in dependency_packages[1:]:
94-
marker = marker.union(dep_package.dependency.marker)
95-
dependency.marker = marker
102+
dependency = dependency_package.dependency
103+
package = dependency_package.package
96104

97105
if package.develop:
98106
line += "-e "

tests/console/commands/test_export.py

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,7 @@ def _export_requirements(tester: "CommandTester", poetry: "Poetry") -> None:
8181

8282
assert poetry.locker.lock.exists()
8383

84-
expected = """\
85-
foo==1.0.0
86-
"""
84+
expected = 'foo==1.0.0 ; python_version >= "2.7" and python_version < "2.8" or python_version >= "3.4" and python_version < "4.0"\n' # noqa: E501
8785

8886
assert content == expected
8987

@@ -110,28 +108,24 @@ def test_export_fails_on_invalid_format(tester: "CommandTester", do_lock: None):
110108

111109
def test_export_prints_to_stdout_by_default(tester: "CommandTester", do_lock: None):
112110
tester.execute("--format requirements.txt")
113-
expected = """\
114-
foo==1.0.0
115-
"""
111+
expected = 'foo==1.0.0 ; python_version >= "2.7" and python_version < "2.8" or python_version >= "3.4" and python_version < "4.0"\n' # noqa: E501
116112
assert tester.io.fetch_output() == expected
117113

118114

119115
def test_export_uses_requirements_txt_format_by_default(
120116
tester: "CommandTester", do_lock: None
121117
):
122118
tester.execute()
123-
expected = """\
124-
foo==1.0.0
125-
"""
119+
expected = 'foo==1.0.0 ; python_version >= "2.7" and python_version < "2.8" or python_version >= "3.4" and python_version < "4.0"\n' # noqa: E501
126120
assert tester.io.fetch_output() == expected
127121

128122

129123
def test_export_includes_extras_by_flag(tester: "CommandTester", do_lock: None):
130124
tester.execute("--format requirements.txt --extras feature_bar")
131-
expected = """\
132-
bar==1.1.0
133-
foo==1.0.0
134-
"""
125+
expected = (
126+
'bar==1.1.0 ; python_version >= "2.7" and python_version < "2.8" or python_version >= "3.4" and python_version < "4.0"\n' # noqa: E501
127+
'foo==1.0.0 ; python_version >= "2.7" and python_version < "2.8" or python_version >= "3.4" and python_version < "4.0"\n' # noqa: E501
128+
)
135129
assert tester.io.fetch_output() == expected
136130

137131

tests/installation/fixtures/extras-with-dependencies.test

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
[root_package]
2+
name = "root"
3+
version = "1.0"
4+
description = ""
5+
category = "dev"
6+
optional = true
7+
python-versions = "*"
8+
files = []
9+
extras = { foo = ['c'] }
10+
11+
[root_package.dependencies]
12+
A = "^1.0"
13+
B = "^1.0"
14+
C = { optional = true, version = "^1.0" }
15+
116
[[package]]
217
name = "A"
318
version = "1.0"

tests/installation/fixtures/extras.test

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
[root_package]
2+
name = "root"
3+
version = "1.0"
4+
description = ""
5+
category = "dev"
6+
optional = true
7+
python-versions = "*"
8+
extras = { foo = ['d'] }
9+
files = []
10+
11+
[root_package.dependencies]
12+
A = "^1.0"
13+
B = "^1.0"
14+
C = "^1.0"
15+
D = { version = "^1.0", optional = true }
16+
117
[[package]]
218
name = "A"
319
version = "1.0"

tests/installation/fixtures/install-no-dev.test

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
[root_package]
2+
name = "root"
3+
version = "1.0"
4+
description = ""
5+
category = "dev"
6+
optional = true
7+
python-versions = "*"
8+
files = []
9+
110
[[package]]
211
name = "A"
312
version = "1.0"

tests/installation/fixtures/no-dependencies.test

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
package = []
22

3+
[root_package]
4+
name = "root"
5+
version = "1.0"
6+
description = ""
7+
category = "dev"
8+
optional = true
9+
python-versions = "*"
10+
files = []
11+
312
[metadata]
413
python-versions = "*"
514
lock-version = "1.1"

tests/installation/fixtures/old-lock.test

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
[root_package]
2+
name = "root"
3+
version = "1.0"
4+
description = ""
5+
category = "dev"
6+
optional = true
7+
python-versions = "*"
8+
files = []
9+
110
[[package]]
211
name = "attrs"
312
version = "17.4.0"

tests/installation/fixtures/remove.test

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
[root_package]
2+
name = "root"
3+
version = "1.0"
4+
description = ""
5+
category = "dev"
6+
optional = true
7+
python-versions = "*"
8+
files = []
9+
10+
[root_package.dependencies]
11+
A = "~1.0"
12+
113
[[package]]
214
name = "A"
315
version = "1.0"

0 commit comments

Comments
 (0)