From be81e4cc9b4175cb6751f702bd119d5a84dbffd3 Mon Sep 17 00:00:00 2001 From: aiolibsbot Date: Sun, 17 May 2026 16:16:51 +0000 Subject: [PATCH] fix: write wheel METADATA as UTF-8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Path.write_text() uses the locale-default encoding when no encoding is passed. On systems with a non-UTF-8 default (e.g. LC_ALL=C and PYTHONUTF8=0), a wheel whose METADATA contains non-ASCII characters (author names, descriptions) raises UnicodeEncodeError and aborts the indexer. Force encoding="utf-8" — METADATA is defined as UTF-8 per the wheel spec, and we already read it as text via dist_meta. Adds a regression test that round-trips a metadata payload with non-ASCII characters. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/index_503/wheel_file.py | 2 +- tests/test_wheelfile.py | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/index_503/wheel_file.py b/src/index_503/wheel_file.py index 4c1253e..31ab973 100644 --- a/src/index_503/wheel_file.py +++ b/src/index_503/wheel_file.py @@ -141,7 +141,7 @@ def from_wheel( metadata_string = extract_metadata_from_wheel_file(wheel_path) if not metadata_string: return None - metadata_path.write_text(metadata_string) + metadata_path.write_text(metadata_string, encoding="utf-8") wheel_metadata = metadata.loads(metadata_string) wheel_file_name = wheel_path.name metadata_name = wheel_metadata["Name"] diff --git a/tests/test_wheelfile.py b/tests/test_wheelfile.py index f687e55..900bbe9 100644 --- a/tests/test_wheelfile.py +++ b/tests/test_wheelfile.py @@ -1,5 +1,5 @@ from pathlib import Path -from unittest.mock import ANY +from unittest.mock import ANY, patch from index_503.util import get_mtime_and_size_from_path from index_503.wheel_file import WHEEL_FILE_VERSION, WheelFile @@ -41,3 +41,26 @@ def test_wheel_file_with_missing_metadata(tmp_path: Path) -> None: wheel_path = FIXTURES.joinpath("sphinxcontrib.applehelp-1.0.3-py3-none-any.whl") wheel_file_obj = WheelFile.from_wheel(wheel_path, metadata_path) assert wheel_file_obj is None + + +def test_wheel_file_metadata_written_as_utf8(tmp_path: Path) -> None: + """METADATA with non-ASCII content must be written as UTF-8 regardless of locale.""" + metadata_path = tmp_path.joinpath("metadata") + wheel_path = FIXTURES.joinpath("CO2Signal-0.4.2-py3-none-any.whl") + # Simulate METADATA carrying non-ASCII characters (author names, descriptions). + fake_metadata = ( + "Metadata-Version: 2.1\n" + "Name: CO2Signal\n" + "Version: 0.4.2\n" + "Author: Héllo Wörld\n" + "\n" + "Description with em dash — and curly quotes “like this”.\n" + ) + with patch( + "index_503.wheel_file.extract_metadata_from_wheel_file", + return_value=fake_metadata, + ): + wheel_file_obj = WheelFile.from_wheel(wheel_path, metadata_path) + assert wheel_file_obj is not None + # Reading the bytes back as UTF-8 must round-trip cleanly. + assert metadata_path.read_bytes() == fake_metadata.encode("utf-8")