From 2f5535974533fb2a3a4b32c9d13e1bb1219d808b Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Wed, 21 Oct 2026 14:06:04 -0400 Subject: [PATCH] =?UTF-8?q?Fix=20=5Furl=5Ftools.url=5Fquote=20for=20Python?= =?UTF-8?q?=20=E2=89=A5=203.14?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Starting in Python 3.14 (https://github.com/python/cpython/pull/127194), urllib.request.pathname2url returns URLs with a leading '//' for absolute input paths on POSIX. For compatibility, restore the traditional behavior. All callers prepend their own scheme including the '//'. This fixes these tests by resolving string-equality AssertionErrors: - tests/test_osfs.py::TestOSFS::test_complex_geturl - tests/test_subfs.py::TestOSFS::test_complex_geturl - tests/test_subfs.py::TestSubFS::test_complex_geturl - tests/test_tarfs.py::TestReadTarFS::test_geturl_for_fs - tests/test_tarfs.py::TestReadTarFSMem::test_geturl_for_fs - tests/test_tempfs.py::TestOSFS::test_complex_geturl - tests/test_tempfs.py::TestTempFS::test_complex_geturl - tests/test_zipfs.py::TestReadZipFS::test_geturl_for_fs - tests/test_zipfs.py::TestReadZipFSMem::test_geturl_for_fs --- fs/_url_tools.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/fs/_url_tools.py b/fs/_url_tools.py index cfd76a7a..3410745a 100644 --- a/fs/_url_tools.py +++ b/fs/_url_tools.py @@ -1,8 +1,10 @@ import typing +import os.path import platform import re import six +import sys if typing.TYPE_CHECKING: from typing import Text @@ -27,12 +29,21 @@ def url_quote(path_snippet): if six.PY2: path = path.encode("utf-8") path = six.moves.urllib.request.pathname2url(path) - path_snippet = "{}:{}".format(drive_letter, path) + path = "{}:{}".format(drive_letter, path) else: if six.PY2: path_snippet = path_snippet.encode("utf-8") - path_snippet = six.moves.urllib.request.pathname2url(path_snippet) - return path_snippet + path = six.moves.urllib.request.pathname2url(path_snippet) + if sys.version_info >= (3, 14): + # For compatibility, restore the traditional behavior from before + # https://github.com/python/cpython/pull/127194. All callers prepend + # their own scheme including the '//'. + _, root, _ = os.path.splitroot(path_snippet) + if root: + assert path.startswith("//") + path = path[2:] + + return path def _has_drive_letter(path_snippet):