Skip to content

Commit 2dbaf86

Browse files
authored
Merge branch 'main' into main
2 parents 43dd00d + 8a1b011 commit 2dbaf86

File tree

25 files changed

+711
-77
lines changed

25 files changed

+711
-77
lines changed

docs/cli.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -711,9 +711,15 @@ poetry publish
711711
It can also build the package if you pass it the `--build` option.
712712

713713
{{% note %}}
714-
See [Publishable Repositories]({{< relref "repositories/#publishable-repositories" >}}) for more information on how to configure and use publishable repositories.
714+
See [Publishable Repositories]({{< relref "repositories/#publishable-repositories" >}}) for more information
715+
on how to configure and use publishable repositories.
715716
{{% /note %}}
716717

718+
{{% warning %}}
719+
Only artifacts of the latest version of your package in the dist directory will be uploaded.
720+
Older versions from previous builds as well as artifacts of other packages are ignored.
721+
{{% /warning %}}
722+
717723
#### Options
718724

719725
* `--repository (-r)`: The repository to register the package to (default: `pypi`).

poetry.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/poetry/console/commands/publish.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,9 @@ def handle(self) -> int:
8282

8383
self.call("build", args=f"--output {dist_dir}")
8484

85-
files = publisher.files
86-
if not files:
85+
publisher = Publisher(self.poetry, self.io, Path(dist_dir))
86+
87+
if not publisher.files:
8788
self.line_error(
8889
"<error>No files to publish. "
8990
"Run poetry build first or use the --build option.</error>"

src/poetry/console/commands/remove.py

Lines changed: 79 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,20 @@ def handle(self) -> int:
102102
group_name=group_name,
103103
)
104104
if group_name != MAIN_GROUP:
105-
if not poetry_section and group_name in poetry_groups_content:
105+
if (
106+
not poetry_section
107+
and "dependencies" in poetry_groups_content.get(group_name, {})
108+
):
106109
del poetry_content["group"][group_name]["dependencies"]
110+
if not poetry_content["group"][group_name]:
111+
del poetry_content["group"][group_name]
107112
if not standard_section and group_name in groups_content:
108113
del groups_content[group_name]
114+
if (
115+
group_name not in groups_content
116+
and group_name not in poetry_groups_content
117+
):
118+
self._remove_references_to_group(group_name, content)
109119

110120
elif group == "dev" and "dev-dependencies" in poetry_content:
111121
# We need to account for the old `dev-dependencies` section
@@ -117,21 +127,20 @@ def handle(self) -> int:
117127
del poetry_content["dev-dependencies"]
118128
else:
119129
removed = set()
120-
if "group" in poetry_content:
121-
if group in poetry_content["group"]:
122-
removed.update(
123-
self._remove_packages(
124-
packages=packages,
125-
standard_section=[],
126-
poetry_section=poetry_content["group"][group].get(
127-
"dependencies", {}
128-
),
129-
group_name=group,
130-
)
130+
if group_content := poetry_groups_content.get(group):
131+
poetry_section = group_content.get("dependencies", {})
132+
removed.update(
133+
self._remove_packages(
134+
packages=packages,
135+
standard_section=[],
136+
poetry_section=poetry_section,
137+
group_name=group,
131138
)
132-
133-
if not poetry_content["group"][group]:
134-
del poetry_content["group"][group]
139+
)
140+
if not poetry_section and "dependencies" in group_content:
141+
del group_content["dependencies"]
142+
if not group_content:
143+
del poetry_content["group"][group]
135144
if group in groups_content:
136145
removed.update(
137146
self._remove_packages(
@@ -143,6 +152,8 @@ def handle(self) -> int:
143152
)
144153
if not groups_content[group]:
145154
del groups_content[group]
155+
if group not in groups_content and group not in poetry_groups_content:
156+
self._remove_references_to_group(group, content)
146157

147158
if "group" in poetry_content and not poetry_content["group"]:
148159
del poetry_content["group"]
@@ -200,3 +211,56 @@ def _remove_packages(
200211
group.remove_dependency(package)
201212

202213
return removed
214+
215+
def _remove_references_to_group(
216+
self, group_name: str, content: dict[str, Any]
217+
) -> None:
218+
"""
219+
Removes references to the given group from other groups.
220+
"""
221+
# 1. PEP 735: [dependency-groups]
222+
if "dependency-groups" in content:
223+
groups_to_remove = []
224+
for group_key, group_content in content["dependency-groups"].items():
225+
if not isinstance(group_content, list):
226+
continue
227+
228+
to_remove = []
229+
for item in group_content:
230+
if (
231+
isinstance(item, dict)
232+
and item.get("include-group") == group_name
233+
):
234+
to_remove.append(item)
235+
236+
for item in to_remove:
237+
group_content.remove(item)
238+
239+
# Clean up now-empty lists (normalize with legacy behavior)
240+
# Only remove groups that became empty due to include-group cleanup,
241+
# not the target group itself (which is handled by the caller)
242+
if not group_content and group_key != group_name:
243+
groups_to_remove.append(group_key)
244+
245+
for group_key in groups_to_remove:
246+
del content["dependency-groups"][group_key]
247+
248+
# 2. Legacy: [tool.poetry.group.<name>] include-groups = [...]
249+
poetry_content = content.get("tool", {}).get("poetry", {})
250+
if "group" in poetry_content:
251+
groups_to_remove = []
252+
for group_key, group_content in poetry_content["group"].items():
253+
if "include-groups" not in group_content:
254+
continue
255+
256+
if group_name in group_content["include-groups"]:
257+
group_content["include-groups"].remove(group_name)
258+
259+
if not group_content["include-groups"]:
260+
del group_content["include-groups"]
261+
262+
if not group_content:
263+
groups_to_remove.append(group_key)
264+
265+
for group_key in groups_to_remove:
266+
del poetry_content["group"][group_key]

src/poetry/inspection/info.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import tempfile
88

99
from pathlib import Path
10+
from tempfile import TemporaryDirectory
1011
from typing import TYPE_CHECKING
1112
from typing import Any
1213

@@ -18,7 +19,6 @@
1819
from poetry.core.packages.package import Package
1920
from poetry.core.pyproject.toml import PyProjectTOML
2021
from poetry.core.utils.helpers import parse_requires
21-
from poetry.core.utils.helpers import temporary_directory
2222
from poetry.core.version.markers import InvalidMarkerError
2323
from poetry.core.version.requirements import InvalidRequirementError
2424

@@ -317,7 +317,7 @@ def _from_sdist_file(cls, path: Path) -> PackageInfo:
317317
elif not zip:
318318
suffix = ".tar.gz"
319319

320-
with temporary_directory() as tmp_str:
320+
with TemporaryDirectory(ignore_cleanup_errors=True) as tmp_str:
321321
tmp = Path(tmp_str)
322322
extractall(source=path, dest=tmp, zip=zip)
323323

@@ -533,7 +533,7 @@ def get_pep517_metadata(path: Path) -> PackageInfo:
533533
"""
534534
info = None
535535

536-
with tempfile.TemporaryDirectory() as dist:
536+
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as dist:
537537
try:
538538
dest = Path(dist)
539539

src/poetry/installation/chef.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33
import tempfile
44

55
from pathlib import Path
6+
from tempfile import TemporaryDirectory
67
from typing import TYPE_CHECKING
78

8-
from poetry.core.utils.helpers import temporary_directory
9-
109
from poetry.utils.helpers import extractall
1110
from poetry.utils.isolated_build import isolated_builder
1211

@@ -100,7 +99,7 @@ def _prepare_sdist(
10099
suffix = archive.suffix
101100
zip = suffix == ".zip"
102101

103-
with temporary_directory() as tmp_dir:
102+
with TemporaryDirectory(ignore_cleanup_errors=True) as tmp_dir:
104103
archive_dir = Path(tmp_dir)
105104
extractall(source=archive, dest=archive_dir, zip=zip)
106105

src/poetry/packages/locker.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
from poetry.__version__ import __version__
3131
from poetry.packages.transitive_package_info import TransitivePackageInfo
32+
from poetry.packages.transitive_package_info import group_sort_key
3233
from poetry.toml.file import TOMLFile
3334
from poetry.utils._compat import tomllib
3435

@@ -583,7 +584,7 @@ def _dump_package(
583584
"description": package.description or "",
584585
"optional": package.optional,
585586
"python-versions": package.python_versions,
586-
"groups": sorted(transitive_info.groups, key=lambda x: (x != "main", x)),
587+
"groups": sorted(transitive_info.groups, key=group_sort_key),
587588
}
588589
if transitive_info.markers:
589590
if len(markers := set(transitive_info.markers.values())) == 1:
@@ -593,7 +594,7 @@ def _dump_package(
593594
data["markers"] = inline_table()
594595
for group, marker in sorted(
595596
transitive_info.markers.items(),
596-
key=lambda x: (x[0] != "main", x[0]),
597+
key=lambda x: group_sort_key(x[0]),
597598
):
598599
if not marker.is_any():
599600
data["markers"][group] = str(marker)

src/poetry/packages/transitive_package_info.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from dataclasses import dataclass
44
from typing import TYPE_CHECKING
55

6+
from poetry.core.packages.dependency_group import MAIN_GROUP
67
from poetry.core.version.markers import BaseMarker
78
from poetry.core.version.markers import EmptyMarker
89

@@ -13,6 +14,10 @@
1314
from packaging.utils import NormalizedName
1415

1516

17+
def group_sort_key(group: NormalizedName) -> tuple[bool, NormalizedName]:
18+
return group != MAIN_GROUP, group
19+
20+
1621
@dataclass
1722
class TransitivePackageInfo:
1823
depth: int # max depth in the dependency tree
@@ -21,7 +26,7 @@ class TransitivePackageInfo:
2126

2227
def get_marker(self, groups: Iterable[NormalizedName]) -> BaseMarker:
2328
marker: BaseMarker = EmptyMarker()
24-
for group in groups:
29+
for group in sorted(groups, key=group_sort_key):
2530
if group_marker := self.markers.get(group):
2631
marker = marker.union(group_marker)
2732
return marker

src/poetry/publishing/publisher.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def publish(
7979
repository_name = "PyPI"
8080
self._io.write_line(
8181
f"Publishing <c1>{self._package.pretty_name}</c1>"
82-
f" (<c2>{self._package.pretty_version}</c2>) to"
82+
f" (<c2>{self._uploader.version}</c2>) to"
8383
f" <info>{repository_name}</info>"
8484
)
8585

src/poetry/publishing/uploader.py

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
from __future__ import annotations
22

3+
import itertools
34
import tarfile
45
import zipfile
56

7+
from collections import defaultdict
8+
from functools import cached_property
69
from pathlib import Path
710
from typing import TYPE_CHECKING
811
from typing import Any
@@ -12,6 +15,7 @@
1215

1316
from packaging.metadata import RawMetadata
1417
from packaging.metadata import parse_email
18+
from poetry.core.constraints.version import Version
1519
from poetry.core.masonry.utils.helpers import distribution_name
1620
from requests_toolbelt import user_agent
1721
from requests_toolbelt.multipart import MultipartEncoder
@@ -36,7 +40,7 @@ class UploadError(Exception):
3640
class Uploader:
3741
def __init__(self, poetry: Poetry, io: IO, dist_dir: Path | None = None) -> None:
3842
self._poetry = poetry
39-
self._package = poetry.package
43+
self._dist_name = distribution_name(poetry.package.name)
4044
self._io = io
4145
self._dist_dir = dist_dir or self.default_dist_dir
4246
self._username: str | None = None
@@ -60,14 +64,39 @@ def dist_dir(self) -> Path:
6064

6165
@property
6266
def files(self) -> list[Path]:
63-
dist = self.dist_dir
64-
version = self._package.version.to_string()
65-
escaped_name = distribution_name(self._package.name)
67+
return self._files_and_version[0]
6668

67-
wheels = list(dist.glob(f"{escaped_name}-{version}-*.whl"))
68-
tars = list(dist.glob(f"{escaped_name}-{version}.tar.gz"))
69+
@property
70+
def version(self) -> str:
71+
return self._files_and_version[1]
6972

70-
return sorted(wheels + tars)
73+
@cached_property
74+
def _files_and_version(self) -> tuple[list[Path], str]:
75+
dist = self.dist_dir
76+
77+
wheels = dist.glob(f"{self._dist_name}-*-*.whl")
78+
tars = dist.glob(f"{self._dist_name}-*.tar.gz")
79+
artifacts_by_version = defaultdict(list)
80+
for artifact in itertools.chain(wheels, tars):
81+
version = (
82+
artifact.stem.removesuffix(".tar")
83+
.removeprefix(f"{self._dist_name}-")
84+
.split("-", maxsplit=1)[0]
85+
)
86+
artifacts_by_version[version].append(artifact)
87+
match len(artifacts_by_version):
88+
case 0:
89+
return [], ""
90+
case 1:
91+
latest_version = next(iter(artifacts_by_version))
92+
artifacts = artifacts_by_version[latest_version]
93+
case _:
94+
latest_version = max(
95+
artifacts_by_version, key=lambda v: Version.parse(v)
96+
)
97+
artifacts = artifacts_by_version[latest_version]
98+
99+
return sorted(artifacts, key=lambda a: (a.suffix == ".whl", a)), latest_version
71100

72101
def auth(self, username: str | None, password: str | None) -> None:
73102
self._username = username
@@ -258,14 +287,7 @@ def _register(self, session: requests.Session, url: str) -> requests.Response:
258287
"""
259288
Register a package to a repository.
260289
"""
261-
dist = self.dist_dir
262-
escaped_name = distribution_name(self._package.name)
263-
file = dist / f"{escaped_name}-{self._package.version.to_string()}.tar.gz"
264-
265-
if not file.exists():
266-
raise RuntimeError(f'"{file.name}" does not exist.')
267-
268-
data = self.post_data(file)
290+
data = self.post_data(self.files[0])
269291
data.update({":action": "submit", "protocol_version": "1"})
270292

271293
data_to_send = self._prepare_data(data)

0 commit comments

Comments
 (0)