Skip to content

Commit 9eac0a9

Browse files
authored
Validate package names in poetry update command (#10721)
1 parent d4e9399 commit 9eac0a9

2 files changed

Lines changed: 148 additions & 0 deletions

File tree

src/poetry/console/commands/update.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
from __future__ import annotations
22

3+
import re
4+
35
from typing import TYPE_CHECKING
46
from typing import ClassVar
57

68
from cleo.helpers import argument
79
from cleo.helpers import option
10+
from packaging.utils import canonicalize_name
811

912
from poetry.console.commands.installer_command import InstallerCommand
1013

@@ -13,6 +16,8 @@
1316
from cleo.io.inputs.argument import Argument
1417
from cleo.io.inputs.option import Option
1518

19+
_VERSION_SPECIFIER_RE = re.compile(r"[><=!~]")
20+
1621

1722
class UpdateCommand(InstallerCommand):
1823
name = "update"
@@ -45,6 +50,37 @@ class UpdateCommand(InstallerCommand):
4550
def handle(self) -> int:
4651
packages = self.argument("packages")
4752
if packages:
53+
# Detect version specifiers in package arguments — poetry update
54+
# only accepts bare package names, not requirement strings.
55+
packages_with_specifiers = [
56+
p for p in packages if _VERSION_SPECIFIER_RE.search(p)
57+
]
58+
if packages_with_specifiers:
59+
self.line_error(
60+
"<error>Version specifiers are not allowed in"
61+
" <c1>poetry update</c1>.</error>"
62+
)
63+
for pkg in packages_with_specifiers:
64+
self.line_error(f" - {pkg}")
65+
self.line_error(
66+
"Use <c1>poetry add</c1> to change version constraints."
67+
)
68+
return 1
69+
70+
# Validate that all specified packages are declared dependencies
71+
all_dependencies = {dep.name for dep in self.poetry.package.all_requires}
72+
73+
invalid_packages = [
74+
p for p in packages if canonicalize_name(p) not in all_dependencies
75+
]
76+
77+
if invalid_packages:
78+
self.line_error(
79+
"<error>The following packages are not dependencies"
80+
f" of this project: {', '.join(invalid_packages)}</error>"
81+
)
82+
return 1
83+
4884
self.installer.whitelist(dict.fromkeys(packages, "*"))
4985

5086
self.installer.only_groups(self.activated_groups)

tests/console/commands/test_update.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,115 @@ def test_update_sync_option_is_passed_to_the_installer(
100100
tester.execute("--sync")
101101

102102
assert tester.command.installer._requires_synchronization
103+
104+
105+
def test_update_with_valid_package_name(
106+
poetry_with_outdated_lockfile: Poetry,
107+
repo: TestRepository,
108+
command_tester_factory: CommandTesterFactory,
109+
mocker: MockerFixture,
110+
) -> None:
111+
"""
112+
Specifying a valid dependency should not raise an error.
113+
"""
114+
tester = command_tester_factory("update", poetry=poetry_with_outdated_lockfile)
115+
assert isinstance(tester.command, UpdateCommand)
116+
mocker.patch.object(tester.command.installer, "run", return_value=0)
117+
118+
repo.add_package(get_package("docker", "4.3.1"))
119+
120+
status = tester.execute("docker")
121+
122+
assert status == 0
123+
assert tester.io.fetch_error() == ""
124+
125+
126+
def test_update_with_non_normalized_package_name(
127+
poetry_with_outdated_lockfile: Poetry,
128+
repo: TestRepository,
129+
command_tester_factory: CommandTesterFactory,
130+
mocker: MockerFixture,
131+
) -> None:
132+
"""
133+
Package names that differ only in normalization (e.g. 'Docker' vs 'docker')
134+
should be accepted.
135+
"""
136+
tester = command_tester_factory("update", poetry=poetry_with_outdated_lockfile)
137+
assert isinstance(tester.command, UpdateCommand)
138+
mocker.patch.object(tester.command.installer, "run", return_value=0)
139+
140+
repo.add_package(get_package("docker", "4.3.1"))
141+
142+
status = tester.execute("Docker")
143+
144+
assert status == 0
145+
assert tester.io.fetch_error() == ""
146+
147+
148+
def test_update_with_invalid_package_name_shows_error(
149+
poetry_with_outdated_lockfile: Poetry,
150+
command_tester_factory: CommandTesterFactory,
151+
) -> None:
152+
"""
153+
Providing non-existent package names should raise an error.
154+
"""
155+
tester = command_tester_factory("update", poetry=poetry_with_outdated_lockfile)
156+
157+
status = tester.execute("nonexistent-package")
158+
159+
assert status == 1
160+
assert (
161+
"The following packages are not dependencies of this project: nonexistent-package"
162+
in tester.io.fetch_error()
163+
)
164+
165+
166+
def test_update_with_multiple_invalid_package_names_shows_error(
167+
poetry_with_outdated_lockfile: Poetry,
168+
command_tester_factory: CommandTesterFactory,
169+
) -> None:
170+
"""
171+
Providing multiple non-existent package names should list all of them in the error.
172+
"""
173+
tester = command_tester_factory("update", poetry=poetry_with_outdated_lockfile)
174+
175+
status = tester.execute("fake1 fake2 fake3")
176+
177+
assert status == 1
178+
error = tester.io.fetch_error()
179+
assert "The following packages are not dependencies of this project" in error
180+
assert "fake1" in error
181+
assert "fake2" in error
182+
assert "fake3" in error
183+
184+
185+
@pytest.mark.parametrize(
186+
"package_spec",
187+
[
188+
"docker==1.2.3",
189+
"docker>=1.0,<2.0",
190+
"docker!=1.0",
191+
"docker[extra]>=1.0",
192+
"nonexistent==1.2.3",
193+
"nonexistent>=1.0",
194+
],
195+
)
196+
def test_update_with_version_specifier_raises_error(
197+
package_spec: str,
198+
poetry_with_outdated_lockfile: Poetry,
199+
command_tester_factory: CommandTesterFactory,
200+
) -> None:
201+
"""
202+
The update command only accepts bare package names. Passing requirement
203+
strings with version specifiers should raise a clear error pointing
204+
to poetry add, regardless of whether the package is a dependency.
205+
"""
206+
tester = command_tester_factory("update", poetry=poetry_with_outdated_lockfile)
207+
208+
status = tester.execute(package_spec)
209+
210+
assert status == 1
211+
error = tester.io.fetch_error()
212+
assert "Version specifiers are not allowed" in error
213+
assert "poetry update" in error
214+
assert "poetry add" in error

0 commit comments

Comments
 (0)