Skip to content

Commit 2c5fef9

Browse files
committed
fix: utf8 for package management commands
1 parent 046c64d commit 2c5fef9

File tree

4 files changed

+128
-5
lines changed

4 files changed

+128
-5
lines changed

marimo/_runtime/packages/conda_package_manager.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def list_packages(self) -> list[PackageDescription]:
4646
["pixi", "list", "--json"],
4747
capture_output=True,
4848
text=True,
49+
encoding="utf-8",
4950
check=True,
5051
)
5152
packages = json.loads(proc.stdout)

marimo/_runtime/packages/pypi_package_manager.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ def _list_packages_from_cmd(
3939
) -> list[PackageDescription]:
4040
if not self.is_manager_installed():
4141
return []
42-
proc = subprocess.run(cmd, capture_output=True, text=True)
42+
proc = subprocess.run(
43+
cmd, capture_output=True, text=True, encoding="utf-8"
44+
)
4345
if proc.returncode != 0:
4446
return []
4547
try:
@@ -459,6 +461,7 @@ def dependency_tree(
459461
tree_cmd,
460462
capture_output=True,
461463
text=True,
464+
encoding="utf-8",
462465
check=True,
463466
)
464467
tree = parse_uv_tree(result.stdout)
@@ -519,7 +522,9 @@ def _list_packages_from_cmd(
519522
) -> list[PackageDescription]:
520523
if not self.is_manager_installed():
521524
return []
522-
proc = subprocess.run(cmd, capture_output=True, text=True)
525+
proc = subprocess.run(
526+
cmd, capture_output=True, text=True, encoding="utf-8"
527+
)
523528
if proc.returncode != 0:
524529
return []
525530

tests/_runtime/packages/test_package_managers.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,3 +531,53 @@ def test_package_manager_run_manager_not_installed() -> None:
531531
# Should also return False with log callback
532532
result = pm.run(["test", "command"], log_callback=lambda _: None)
533533
assert result is False
534+
535+
536+
# Encoding tests for Windows compatibility
537+
538+
539+
@patch("subprocess.run")
540+
def test_poetry_list_packages_uses_utf8_encoding(mock_run: MagicMock):
541+
"""Test that poetry list uses UTF-8 encoding to handle non-ASCII characters"""
542+
from marimo._runtime.packages.pypi_package_manager import (
543+
PoetryPackageManager,
544+
)
545+
546+
mock_output = "package-中文 1.0.0\npакет 2.0.0\n"
547+
mock_run.return_value = MagicMock(returncode=0, stdout=mock_output)
548+
mgr = PoetryPackageManager()
549+
550+
packages = mgr.list_packages()
551+
552+
# Verify encoding='utf-8' is passed
553+
mock_run.assert_called_once()
554+
call_kwargs = mock_run.call_args[1]
555+
assert call_kwargs.get("encoding") == "utf-8"
556+
assert call_kwargs.get("text") is True
557+
558+
559+
@patch("subprocess.run")
560+
def test_pixi_list_packages_uses_utf8_encoding(mock_run: MagicMock):
561+
"""Test that pixi list uses UTF-8 encoding to handle non-ASCII characters"""
562+
import json
563+
564+
from marimo._runtime.packages.conda_package_manager import (
565+
PixiPackageManager,
566+
)
567+
568+
mock_output = json.dumps(
569+
[
570+
{"name": "package-中文", "version": "1.0.0"},
571+
{"name": "пакет", "version": "2.0.0"},
572+
]
573+
)
574+
mock_run.return_value = MagicMock(returncode=0, stdout=mock_output)
575+
mgr = PixiPackageManager()
576+
577+
packages = mgr.list_packages()
578+
579+
# Verify encoding='utf-8' is passed
580+
mock_run.assert_called_once()
581+
call_kwargs = mock_run.call_args[1]
582+
assert call_kwargs.get("encoding") == "utf-8"
583+
assert call_kwargs.get("text") is True

tests/_runtime/packages/test_pypi_package_manager.py

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ async def test_failed_install_returns_false() -> None:
4444
async def test_install(mock_run: MagicMock):
4545
mock_run.return_value = MagicMock(returncode=0)
4646

47-
result = await manager._install("package1 package2", upgrade=False)
47+
with patch.object(manager, "is_manager_installed", return_value=True):
48+
result = await manager._install("package1 package2", upgrade=False)
4849

4950
mock_run.assert_called_once_with(
5051
["pip", "--python", PY_EXE, "install", "package1", "package2"],
@@ -65,7 +66,8 @@ async def test_install_failure(mock_run: MagicMock):
6566
async def test_uninstall(mock_run: MagicMock):
6667
mock_run.return_value = MagicMock(returncode=0)
6768

68-
result = await manager.uninstall("package1 package2")
69+
with patch.object(manager, "is_manager_installed", return_value=True):
70+
result = await manager.uninstall("package1 package2")
6971

7072
mock_run.assert_called_once_with(
7173
[
@@ -91,12 +93,14 @@ def test_list_packages(mock_run: MagicMock):
9193
)
9294
mock_run.return_value = MagicMock(returncode=0, stdout=mock_output)
9395

94-
packages = manager.list_packages()
96+
with patch.object(manager, "is_manager_installed", return_value=True):
97+
packages = manager.list_packages()
9598

9699
mock_run.assert_called_once_with(
97100
["pip", "--python", PY_EXE, "list", "--format=json"],
98101
capture_output=True,
99102
text=True,
103+
encoding="utf-8",
100104
)
101105
assert len(packages) == 2
102106
assert packages[0] == PackageDescription(name="package1", version="1.0.0")
@@ -310,6 +314,7 @@ def test_uv_list_packages(mock_run: MagicMock):
310314
["uv", "pip", "list", "--format=json", "-p", PY_EXE],
311315
capture_output=True,
312316
text=True,
317+
encoding="utf-8",
313318
)
314319
assert len(packages) == 2
315320
assert packages[0] == PackageDescription(name="package1", version="1.0.0")
@@ -402,6 +407,7 @@ def test_uv_list_packages_tree_fallback_to_pip_list(
402407
["uv", "pip", "list", "--format=json", "-p", PY_EXE],
403408
capture_output=True,
404409
text=True,
410+
encoding="utf-8",
405411
)
406412

407413
# Should return packages from fallback method
@@ -458,3 +464,64 @@ def test_uv_is_in_uv_project_uv_project_environment_mismatch():
458464
"""Test is_in_uv_project returns False when UV_PROJECT_ENVIRONMENT doesn't match VIRTUAL_ENV"""
459465
mgr = UvPackageManager()
460466
assert mgr.is_in_uv_project is False
467+
468+
469+
# Encoding tests for Windows compatibility
470+
471+
472+
@patch("subprocess.run")
473+
def test_pip_list_packages_uses_utf8_encoding(mock_run: MagicMock):
474+
"""Test that pip list uses UTF-8 encoding to handle non-ASCII characters"""
475+
mock_output = json.dumps(
476+
[
477+
{"name": "package-中文", "version": "1.0.0"},
478+
{"name": "пакет", "version": "2.0.0"},
479+
]
480+
)
481+
mock_run.return_value = MagicMock(returncode=0, stdout=mock_output)
482+
mgr = PipPackageManager()
483+
484+
with patch.object(mgr, "is_manager_installed", return_value=True):
485+
packages = mgr.list_packages()
486+
487+
# Verify encoding='utf-8' is passed
488+
mock_run.assert_called_once()
489+
call_kwargs = mock_run.call_args[1]
490+
assert call_kwargs.get("encoding") == "utf-8"
491+
assert call_kwargs.get("text") is True
492+
493+
494+
@patch("subprocess.run")
495+
def test_uv_dependency_tree_uses_utf8_encoding(mock_run: MagicMock):
496+
"""Test that uv tree uses UTF-8 encoding"""
497+
mock_output = "test-package v1.0.0\n"
498+
mock_run.return_value = MagicMock(
499+
returncode=0, stdout=mock_output, stderr=""
500+
)
501+
mgr = UvPackageManager()
502+
503+
mgr.dependency_tree(filename="test.py")
504+
505+
# Verify encoding='utf-8' is passed
506+
mock_run.assert_called_once()
507+
call_kwargs = mock_run.call_args[1]
508+
assert call_kwargs.get("encoding") == "utf-8"
509+
assert call_kwargs.get("text") is True
510+
511+
512+
@patch("subprocess.run")
513+
def test_uv_pip_list_uses_utf8_encoding(mock_run: MagicMock):
514+
"""Test that uv pip list uses UTF-8 encoding"""
515+
mock_output = json.dumps([{"name": "test-pkg", "version": "1.0.0"}])
516+
mock_run.return_value = MagicMock(returncode=0, stdout=mock_output)
517+
mgr = UvPackageManager()
518+
519+
# Mock dependency_tree to return None so it falls back to pip list
520+
with patch.object(mgr, "dependency_tree", return_value=None):
521+
mgr.list_packages()
522+
523+
# Verify encoding='utf-8' is passed
524+
mock_run.assert_called_once()
525+
call_kwargs = mock_run.call_args[1]
526+
assert call_kwargs.get("encoding") == "utf-8"
527+
assert call_kwargs.get("text") is True

0 commit comments

Comments
 (0)