Skip to content

Commit f5eb28e

Browse files
robd518pre-commit-ci[bot]dmadisetti
authored
improvement: poetry package list support for v2 (#7144)
Fixes #7090 ## 📝 Summary Poetry 1.x and 2.x handle the `poetry show` command differently. The current `PoetryPackageManager` implementation is fine but with the release of Poetry 2.0 in January the current implementation breaks. This PR will fix issue #7090 - and as a side benefit - improve the `PoetryPackageManager` class. ## 🔍 Description of Changes ### Poetry version detection: The manager now detects the installed Poetry version (1.x vs 2.x) at runtime. ### Adaptive command generation: - For Poetry 1.x, use `poetry show --no-dev`. - If no dev dependencies exist, this command still succeeds. - For Poetry 2.x, attempt `poetry show --without dev`. - If the command succeeds, we use its output. - If it fails with `Group(s) not found`, this indicates that no dev group is defined — in that case, fall back to `poetry show` to capture all installed packages. ### Fallback behavior: If version detection or command inference fails for any reason, default gracefully to poetry show. This ensures PoetryPackageManager works consistently across Poetry versions 1.x → 2.x and avoids breaking when no dependency groups are defined. ## 📋 Checklist - [x] I have read the [contributor guidelines](https://github.com/marimo-team/marimo/blob/main/CONTRIBUTING.md). - [x] For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on [Discord](https://marimo.io/discord?ref=pr), or the community [discussions](https://github.com/marimo-team/marimo/discussions) (Please provide a link if applicable). - [x] I have added tests for the changes made. - [x] I have run the code and verified that it works as expected. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Dylan Madisetti <[email protected]>
1 parent 801c5af commit f5eb28e

File tree

3 files changed

+142
-6
lines changed

3 files changed

+142
-6
lines changed

marimo/_runtime/packages/pypi_package_manager.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,16 @@ class PoetryPackageManager(PypiPackageManager):
539539
name = "poetry"
540540
docs_url = "https://python-poetry.org/docs/"
541541

542+
def _get_poetry_version(self) -> int:
543+
proc = subprocess.run(
544+
["poetry", "--version"], capture_output=True, text=True
545+
)
546+
if proc.returncode != 0:
547+
return -1 # and raise on the impl side
548+
version_str = proc.stdout.split()[-1].strip("()")
549+
major, *_ = map(int, version_str.split("."))
550+
return major
551+
542552
def install_command(self, package: str, *, upgrade: bool) -> list[str]:
543553
return [
544554
"poetry",
@@ -558,6 +568,7 @@ def _list_packages_from_cmd(
558568
) -> list[PackageDescription]:
559569
if not self.is_manager_installed():
560570
return []
571+
561572
proc = subprocess.run(
562573
cmd, capture_output=True, text=True, encoding="utf-8"
563574
)
@@ -580,6 +591,43 @@ def _list_packages_from_cmd(
580591
)
581592
return packages
582593

594+
def _generate_list_packages_cmd(self, version: int) -> list[str]:
595+
"""Poetry 1.x and 2.x handle the "show" command differently
596+
In poetry 1.x, "poetry show --no-dev" works perfectly fine but is deprecated. This
597+
shouldn't matter if 1.8.x is still installed.
598+
In poetry 2.x the preferred command is "poetry show --without dev" but will throw
599+
an error if there are no dev packages installed. We will capture that error and
600+
adjust the cmd accordingly.
601+
"""
602+
if version == 1:
603+
return ["poetry", "show", "--no-dev"]
604+
605+
elif version != 2:
606+
LOGGER.warning(
607+
f"Unknown poetry version {version}, attempting fallback"
608+
)
609+
610+
try:
611+
cmd = ["poetry", "show", "--without", "dev"]
612+
result = subprocess.run(
613+
cmd, capture_output=True, text=True, check=False
614+
)
615+
616+
# If Poetry 2.x throws "Group(s) not found"
617+
if "Group(s) not found" in result.stderr:
618+
return ["poetry", "show"]
619+
620+
# Otherwise, if the command succeeded
621+
if result.returncode == 0:
622+
return cmd
623+
624+
except FileNotFoundError:
625+
return []
626+
627+
# Default fallback
628+
return ["poetry", "show"]
629+
583630
def list_packages(self) -> list[PackageDescription]:
584-
cmd = ["poetry", "show", "--no-dev"]
631+
version = self._get_poetry_version()
632+
cmd = self._generate_list_packages_cmd(version)
585633
return self._list_packages_from_cmd(cmd)

tests/_runtime/packages/test_package_managers.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -668,11 +668,14 @@ def test_poetry_list_packages_uses_utf8_encoding(mock_run: MagicMock):
668668

669669
packages = mgr.list_packages()
670670

671-
# Verify encoding='utf-8' is passed
672-
mock_run.assert_called_once()
673-
call_kwargs = mock_run.call_args[1]
674-
assert call_kwargs.get("encoding") == "utf-8"
675-
assert call_kwargs.get("text") is True
671+
# Verify encoding='utf-8' is passed (PoetryPackageManager does multiple subprocess calls)
672+
encoding_calls = [
673+
kwargs
674+
for _, kwargs in mock_run.call_args_list
675+
if kwargs.get("encoding") == "utf-8"
676+
]
677+
assert encoding_calls, "Expected at least one call with encoding='utf-8'"
678+
assert all(call.get("text") is True for call in encoding_calls)
676679

677680

678681
@pytest.mark.skipif(

tests/_runtime/packages/test_pypi_package_manager.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from marimo._runtime.packages.pypi_package_manager import (
1111
PackageDescription,
1212
PipPackageManager,
13+
PoetryPackageManager,
1314
UvPackageManager,
1415
)
1516

@@ -120,6 +121,90 @@ def test_list_packages_failure(mock_run: MagicMock):
120121
assert len(packages) == 0
121122

122123

124+
# Poetry Package Manager Tests
125+
126+
127+
def test_poetry_generate_cmd_version_one():
128+
mgr = PoetryPackageManager()
129+
assert mgr._generate_list_packages_cmd(1) == [
130+
"poetry",
131+
"show",
132+
"--no-dev",
133+
]
134+
135+
136+
@patch("subprocess.run")
137+
def test_poetry_generate_cmd_version_two_prefers_without_dev(
138+
mock_run: MagicMock,
139+
):
140+
mock_run.return_value = MagicMock(returncode=0, stderr="")
141+
mgr = PoetryPackageManager()
142+
143+
cmd = mgr._generate_list_packages_cmd(2)
144+
145+
assert cmd == ["poetry", "show", "--without", "dev"]
146+
mock_run.assert_called_once_with(
147+
["poetry", "show", "--without", "dev"],
148+
capture_output=True,
149+
text=True,
150+
check=False,
151+
)
152+
153+
154+
@patch("subprocess.run")
155+
def test_poetry_generate_cmd_version_two_falls_back_when_missing_group(
156+
mock_run: MagicMock,
157+
):
158+
mock_run.return_value = MagicMock(
159+
returncode=1, stderr="Group(s) not found"
160+
)
161+
mgr = PoetryPackageManager()
162+
163+
cmd = mgr._generate_list_packages_cmd(2)
164+
165+
assert cmd == ["poetry", "show"]
166+
167+
168+
@patch("subprocess.run")
169+
def test_poetry_generate_cmd_default_for_other_versions(
170+
mock_run: MagicMock,
171+
):
172+
mock_run.return_value = MagicMock(returncode=1, stderr="")
173+
mgr = PoetryPackageManager()
174+
175+
cmd = mgr._generate_list_packages_cmd(3)
176+
177+
assert cmd == ["poetry", "show"]
178+
179+
180+
@patch("subprocess.run")
181+
def test_poetry_list_packages_parses_output(mock_run: MagicMock):
182+
mock_run.side_effect = [
183+
MagicMock(returncode=0, stdout="Poetry (1.8.2)"),
184+
MagicMock(
185+
returncode=0,
186+
stdout="package-1 1.0.0\npackage-two 2.0.0\n",
187+
),
188+
]
189+
mgr = PoetryPackageManager()
190+
191+
with patch.object(
192+
PoetryPackageManager, "is_manager_installed", return_value=True
193+
):
194+
packages = mgr.list_packages()
195+
196+
assert packages == [
197+
PackageDescription(name="package-1", version="1.0.0"),
198+
PackageDescription(name="package-two", version="2.0.0"),
199+
]
200+
201+
# Last subprocess call should be the list invocation with UTF-8 encoding
202+
cmd_args, cmd_kwargs = mock_run.call_args_list[-1]
203+
assert cmd_args[0] == ["poetry", "show", "--no-dev"]
204+
assert cmd_kwargs.get("encoding") == "utf-8"
205+
assert cmd_kwargs.get("text") is True
206+
207+
123208
# UV Package Manager Tests
124209

125210

0 commit comments

Comments
 (0)