From f9a51a0950551bef98c62bc5a4b324a41fc1207d Mon Sep 17 00:00:00 2001 From: SanHsien <34234698+SanHsien@users.noreply.github.com> Date: Sun, 6 Sep 2026 10:18:35 +0800 Subject: [PATCH 1/3] fix(scripts): resolve editable file:// URLs with url2pathname A Windows file URL's path is "/C:/Users/...". Path() reads the leading slash as a root, so unquote() produced "C:\C:\Users\..." and the following resolve(strict=True) raised WinError 123. url2pathname is the stdlib conversion for this and is identical to the old behavior on POSIX, where the path has no drive letter to double. test_runtime_probe_hashes_installed_and_editable_dependency_bytes already covers this; it just never runs on a Windows host in CI. On Windows 11 / Python 3.13 it fails before this change and passes after. Fixes #485 Co-Authored-By: Claude Opus 5 Signed-off-by: SanHsien <34234698+SanHsien@users.noreply.github.com> --- scripts/compare_scan_accuracy.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/compare_scan_accuracy.py b/scripts/compare_scan_accuracy.py index 50e1b569..8f4441ca 100644 --- a/scripts/compare_scan_accuracy.py +++ b/scripts/compare_scan_accuracy.py @@ -61,6 +61,7 @@ import platform import sys import urllib.parse +import urllib.request from pathlib import Path MAX_DEPENDENCY_FILES = 200_000 @@ -178,7 +179,9 @@ def hash_file(digest, label, path): parsed = urllib.parse.urlsplit(raw_url) if parsed.scheme != "file" or parsed.netloc not in {"", "localhost"}: raise RuntimeError(f"editable dependency is not a local file target: {normalized_name}") - editable_root = Path(urllib.parse.unquote(parsed.path)).resolve(strict=True) + # url2pathname, not unquote: a Windows file URL's path is "/C:/..." + # and Path() would read the leading slash as a root, producing "C:\C:\...". + editable_root = Path(urllib.request.url2pathname(parsed.path)).resolve(strict=True) if not editable_root.is_dir(): raise RuntimeError(f"editable dependency target is not a directory: {normalized_name}") for editable_path in sorted(editable_root.rglob("*")): From 7f0cba54eaee5cf687c19768e1c51161826f814b Mon Sep 17 00:00:00 2001 From: Mohit Gupta Date: Sun, 6 Sep 2026 17:53:33 +0530 Subject: [PATCH 2/3] fix(scripts): preserve file URL authority semantics Keep empty-authority delimiters intact for POSIX double-slash paths while retaining correct Windows drive conversion. Prepared by Codex on behalf of Mohit Gupta. Signed-off-by: Mohit Gupta --- scripts/compare_scan_accuracy.py | 8 ++-- tests/unit/test_compare_scan_accuracy.py | 58 ++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/scripts/compare_scan_accuracy.py b/scripts/compare_scan_accuracy.py index 8f4441ca..ed4f98fe 100644 --- a/scripts/compare_scan_accuracy.py +++ b/scripts/compare_scan_accuracy.py @@ -179,9 +179,11 @@ def hash_file(digest, label, path): parsed = urllib.parse.urlsplit(raw_url) if parsed.scheme != "file" or parsed.netloc not in {"", "localhost"}: raise RuntimeError(f"editable dependency is not a local file target: {normalized_name}") - # url2pathname, not unquote: a Windows file URL's path is "/C:/..." - # and Path() would read the leading slash as a root, producing "C:\C:\...". - editable_root = Path(urllib.request.url2pathname(parsed.path)).resolve(strict=True) + # Preserve the scheme-less URL rather than passing only parsed.path: + # url2pathname needs the empty-authority delimiter for paths beginning + # with "//", while still removing a Windows drive path's leading slash. + schemeless_url = parsed._replace(scheme="", query="", fragment="").geturl() + editable_root = Path(urllib.request.url2pathname(schemeless_url)).resolve(strict=True) if not editable_root.is_dir(): raise RuntimeError(f"editable dependency target is not a directory: {normalized_name}") for editable_path in sorted(editable_root.rglob("*")): diff --git a/tests/unit/test_compare_scan_accuracy.py b/tests/unit/test_compare_scan_accuracy.py index 81ee3d50..544dfc36 100644 --- a/tests/unit/test_compare_scan_accuracy.py +++ b/tests/unit/test_compare_scan_accuracy.py @@ -9,6 +9,7 @@ import subprocess import sys import tarfile +import urllib.request from collections.abc import Iterator from contextlib import contextmanager, redirect_stdout from pathlib import Path @@ -1116,6 +1117,63 @@ def probe() -> dict[str, object]: assert editable_changed["dependencies"] != original["dependencies"] +@pytest.mark.parametrize( + ("editable_url", "expected_converter_input"), + [ + ("file:///C:/PortableRegressionProbe", "/C:/PortableRegressionProbe"), + ("file:////portable-regression-probe", "////portable-regression-probe"), + ], + ids=["windows_drive", "empty_authority"], +) +def test_runtime_probe_preserves_file_url_structure_for_path_conversion( + tmp_path: Path, + monkeypatch, + editable_url: str, + expected_converter_input: str, +) -> None: + installed_root = tmp_path / "site-packages" + installed_root.mkdir() + (installed_root / "dependency.py").write_text("VALUE = 1\n", encoding="utf-8") + editable_root = tmp_path / "editable-dependency" + editable_root.mkdir() + (editable_root / "source.py").write_text("VALUE = 2\n", encoding="utf-8") + + class FakeDistribution: + metadata = {"Name": "example-dependency"} + version = "1.0" + files = ["dependency.py"] + + def read_text(self, name: str) -> str | None: + if name == "RECORD": + return "dependency.py,,\n" + if name == "METADATA": + return "Name: example-dependency\nVersion: 1.0\n" + if name == "direct_url.json": + return json.dumps({"url": editable_url, "dir_info": {"editable": True}}) + return None + + def locate_file(self, package_path: object) -> Path: + return installed_root / str(package_path) + + monkeypatch.setattr(importlib.metadata, "distributions", lambda: [FakeDistribution()]) + converter_inputs: list[str] = [] + + def fake_url2pathname(value: str) -> str: + converter_inputs.append(value) + return str(editable_root) + + monkeypatch.setattr(urllib.request, "url2pathname", fake_url2pathname) + rendered = io.StringIO() + with redirect_stdout(rendered): + exec(compare_scan_accuracy._RUNTIME_IDENTITY_PROBE, {}) + + payload = json.loads(rendered.getvalue()) + dependency = payload["dependencies"][0] + assert converter_inputs == [expected_converter_input] + assert dependency["editable"] is True + assert dependency["editable_file_count"] == 1 + + @pytest.mark.parametrize( ("mutation", "message"), [ From 1c0a099eb504a59810232db233a067590c4bc269 Mon Sep 17 00:00:00 2001 From: Mohit Gupta Date: Sun, 6 Sep 2026 18:06:03 +0530 Subject: [PATCH 3/3] fix(scripts): avoid file URL normalization drift Construct the empty-authority delimiter explicitly so Python patch releases feed the same path shape to url2pathname. Prepared by Codex on behalf of Mohit Gupta. Signed-off-by: Mohit Gupta --- scripts/compare_scan_accuracy.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/compare_scan_accuracy.py b/scripts/compare_scan_accuracy.py index ed4f98fe..ce3b984d 100644 --- a/scripts/compare_scan_accuracy.py +++ b/scripts/compare_scan_accuracy.py @@ -179,11 +179,13 @@ def hash_file(digest, label, path): parsed = urllib.parse.urlsplit(raw_url) if parsed.scheme != "file" or parsed.netloc not in {"", "localhost"}: raise RuntimeError(f"editable dependency is not a local file target: {normalized_name}") - # Preserve the scheme-less URL rather than passing only parsed.path: # url2pathname needs the empty-authority delimiter for paths beginning - # with "//", while still removing a Windows drive path's leading slash. - schemeless_url = parsed._replace(scheme="", query="", fragment="").geturl() - editable_root = Path(urllib.request.url2pathname(schemeless_url)).resolve(strict=True) + # with "//". Build it explicitly because urlunsplit() normalizes this + # form differently across Python patch releases. + converter_input = parsed.path + if not parsed.netloc and converter_input.startswith("//"): + converter_input = f"//{converter_input}" + editable_root = Path(urllib.request.url2pathname(converter_input)).resolve(strict=True) if not editable_root.is_dir(): raise RuntimeError(f"editable dependency target is not a directory: {normalized_name}") for editable_path in sorted(editable_root.rglob("*")):