Skip to content

Commit 2eefadd

Browse files
committed
fix poetry add --optional locking
1 parent 77cd259 commit 2eefadd

File tree

2 files changed

+56
-18
lines changed

2 files changed

+56
-18
lines changed

src/poetry/console/commands/add.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,13 @@ def handle(self) -> int:
335335
else:
336336
poetry_section[constraint_name] = poetry_constraint
337337

338+
if optional:
339+
extra_name = canonicalize_name(optional)
340+
# _in_extras must be set after converting the dependency to PEP 508
341+
# and adding it to the project section to avoid a redundant extra marker
342+
dependency._in_extras = [extra_name]
343+
self._add_dependency_to_extras(dependency, extra_name)
344+
338345
# Refresh the locker
339346
if project_section:
340347
assert group == MAIN_GROUP
@@ -409,3 +416,20 @@ def notify_about_existing_packages(self, existing_packages: list[str]) -> None:
409416
for name in existing_packages:
410417
self.line(f" - <c1>{name}</c1>")
411418
self.line(self._hint_update_packages)
419+
420+
def _add_dependency_to_extras(
421+
self, dependency: Dependency, extra_name: NormalizedName
422+
) -> None:
423+
extras = dict(self.poetry.package.extras)
424+
extra_deps = []
425+
replaced = False
426+
for dep in extras.get(extra_name, ()):
427+
if dep.name == dependency.name:
428+
extra_deps.append(dependency)
429+
replaced = True
430+
else:
431+
extra_deps.append(dep)
432+
if not replaced:
433+
extra_deps.append(dependency)
434+
extras[extra_name] = extra_deps
435+
self.poetry.package.extras = extras

tests/console/commands/test_add.py

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -822,13 +822,16 @@ def test_add_url_constraint_wheel_with_extras(
822822
[
823823
(None, {"my-extra": ["cachy (==0.2.0)"]}),
824824
(
825-
{"other": ["foo>2"]},
826-
{"other": ["foo>2"], "my-extra": ["cachy (==0.2.0)"]},
825+
{"other": ["tomlkit (<2)"]},
826+
{"other": ["tomlkit (<2)"], "my-extra": ["cachy (==0.2.0)"]},
827827
),
828-
({"my-extra": ["foo>2"]}, {"my-extra": ["foo>2", "cachy (==0.2.0)"]}),
829828
(
830-
{"my-extra": ["foo>2", "cachy (==0.1.0)", "bar>1"]},
831-
{"my-extra": ["foo>2", "cachy (==0.2.0)", "bar>1"]},
829+
{"my-extra": ["tomlkit (<2)"]},
830+
{"my-extra": ["tomlkit (<2)", "cachy (==0.2.0)"]},
831+
),
832+
(
833+
{"my-extra": ["tomlkit (<2)", "cachy (==0.1.0)", "pendulum (>1)"]},
834+
{"my-extra": ["tomlkit (<2)", "cachy (==0.2.0)", "pendulum (>1)"]},
832835
),
833836
],
834837
)
@@ -841,29 +844,22 @@ def test_add_constraint_with_optional(
841844
) -> None:
842845
pyproject: dict[str, Any] = app.poetry.file.read()
843846
if project_dependencies:
844-
pyproject["project"]["dependencies"] = ["foo>1"]
847+
pyproject["project"]["dependencies"] = ["tomlkit (<1)"]
845848
if existing_extras:
846849
pyproject["project"]["optional-dependencies"] = existing_extras
847850
else:
848-
pyproject["tool"]["poetry"]["dependencies"]["foo"] = "^1.0"
851+
pyproject["tool"]["poetry"]["dependencies"]["tomlkit"] = "<1"
849852
pyproject = cast("TOMLDocument", pyproject)
850853
app.poetry.file.write(pyproject)
854+
app.reset_poetry()
851855

852856
tester.execute("cachy=0.2.0 --optional my-extra")
853-
expected = """\
854857

855-
Updating dependencies
856-
Resolving dependencies...
857-
858-
No dependencies to install or update
859-
860-
Writing lock file
861-
"""
862-
863-
assert tester.io.fetch_output() == expected
858+
assert tester.io.fetch_output().endswith("Writing lock file\n")
864859
assert isinstance(tester.command, InstallerCommand)
865-
assert tester.command.installer.executor.installations_count == 0
860+
assert tester.command.installer.executor.installations_count > 0
866861

862+
# check pyproject content
867863
pyproject2: dict[str, Any] = app.poetry.file.read()
868864
project_content = pyproject2["project"]
869865
poetry_content = pyproject2["tool"]["poetry"]
@@ -887,6 +883,24 @@ def test_add_constraint_with_optional(
887883
in tester.io.fetch_error()
888884
)
889885

886+
# check lock content
887+
if project_dependencies:
888+
lock_data = app.poetry.locker.lock_data
889+
890+
extras = lock_data["extras"]
891+
assert list(extras) == sorted(expected_extras)
892+
assert extras["my-extra"] == sorted(
893+
e.split(" ")[0] for e in expected_extras["my-extra"]
894+
)
895+
896+
added_package: dict[str, Any] | None = None
897+
for package in lock_data["package"]:
898+
if package["name"] == "cachy":
899+
added_package = package
900+
break
901+
assert added_package is not None
902+
assert added_package.get("markers") == 'extra == "my-extra"'
903+
890904

891905
def test_add_constraint_with_optional_not_main_group(
892906
app: PoetryTestApplication, tester: CommandTester

0 commit comments

Comments
 (0)