From 10a925c86db4cbcb9324c7269f69f813d3e7ed79 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sun, 27 Jul 2025 12:44:41 +0100 Subject: [PATCH 1/3] GH-137059: `url2pathname()`: fix support for drive letter in netloc (#137060) Support file URLs like `file://c:/foo` in `urllib.request.url2pathname()` on Windows. This restores behaviour from 3.13. --- Lib/test/test_urllib.py | 4 ++++ Lib/urllib/request.py | 5 ++++- .../Library/2025-07-24-00-38-07.gh-issue-137059.fr64oW.rst | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2025-07-24-00-38-07.gh-issue-137059.fr64oW.rst diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py index c30fb5e27eea8a..ae524c5ffba6b1 100644 --- a/Lib/test/test_urllib.py +++ b/Lib/test/test_urllib.py @@ -1590,6 +1590,10 @@ def test_url2pathname_resolve_host(self): def test_url2pathname_win(self): fn = urllib.request.url2pathname self.assertEqual(fn('/C:/'), 'C:\\') + self.assertEqual(fn('//C:'), 'C:') + self.assertEqual(fn('//C:/'), 'C:\\') + self.assertEqual(fn('//C:\\'), 'C:\\') + self.assertEqual(fn('//C:80/'), 'C:80\\') self.assertEqual(fn("///C|"), 'C:') self.assertEqual(fn("///C:"), 'C:') self.assertEqual(fn('///C:/'), 'C:\\') diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py index c1c373d08815c1..af93d4cd75dbef 100644 --- a/Lib/urllib/request.py +++ b/Lib/urllib/request.py @@ -1660,7 +1660,10 @@ def url2pathname(url, *, require_scheme=False, resolve_host=False): if scheme != 'file': raise URLError("URL is missing a 'file:' scheme") if os.name == 'nt': - if not _is_local_authority(authority, resolve_host): + if authority[1:2] == ':': + # e.g. file://c:/file.txt + url = authority + url + elif not _is_local_authority(authority, resolve_host): # e.g. file://server/share/file.txt url = '//' + authority + url elif url[:3] == '///': diff --git a/Misc/NEWS.d/next/Library/2025-07-24-00-38-07.gh-issue-137059.fr64oW.rst b/Misc/NEWS.d/next/Library/2025-07-24-00-38-07.gh-issue-137059.fr64oW.rst new file mode 100644 index 00000000000000..8c63c1f1af8bf5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-24-00-38-07.gh-issue-137059.fr64oW.rst @@ -0,0 +1,3 @@ +Fix handling of file URLs with a Windows drive letter in the URL authority +by :func:`urllib.request.url2pathname`. This fixes a regression in earlier +pre-releases of Python 3.14. From 2bd4ff07001038fada17b6dbcb5ab5aabd27c26e Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sun, 27 Jul 2025 12:47:15 +0100 Subject: [PATCH 2/3] GH-128520: pathlib ABCs: tweak protocol for virtual path strings (#134104) Adjust `pathlib._os.vfspath()` so that it doesn't try `os.fsdecode()`. I don't know that supporting `os.PathLike` arguments is a good idea, so it's best to leave it out for now. --- Lib/pathlib/__init__.py | 6 +++--- Lib/pathlib/_os.py | 20 +++++++------------- Lib/test/test_pathlib/support/zip_path.py | 2 +- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index fd073445e89b62..cea1a9fe57eedf 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -188,9 +188,6 @@ def __reduce__(self): def __repr__(self): return "{}({!r})".format(self.__class__.__name__, self.as_posix()) - def __fspath__(self): - return str(self) - def __bytes__(self): """Return the bytes representation of the path. This is only recommended to use under Unix.""" @@ -259,6 +256,9 @@ def __str__(self): self._tail) or '.' return self._str + __fspath__ = __str__ + __vfspath__ = __str__ + @classmethod def _format_parsed_parts(cls, drv, root, tail): if drv or root: diff --git a/Lib/pathlib/_os.py b/Lib/pathlib/_os.py index 62a4adb555ea89..fbcbfb979d1278 100644 --- a/Lib/pathlib/_os.py +++ b/Lib/pathlib/_os.py @@ -210,24 +210,18 @@ def magic_open(path, mode='r', buffering=-1, encoding=None, errors=None, raise TypeError(f"{cls.__name__} can't be opened with mode {mode!r}") -def vfspath(path): +def vfspath(obj): """ Return the string representation of a virtual path object. """ + cls = type(obj) try: - return os.fsdecode(path) - except TypeError: - pass - - path_type = type(path) - try: - return path_type.__vfspath__(path) + vfspath_method = cls.__vfspath__ except AttributeError: - if hasattr(path_type, '__vfspath__'): - raise - - raise TypeError("expected str, bytes, os.PathLike or JoinablePath " - "object, not " + path_type.__name__) + cls_name = cls.__name__ + raise TypeError(f"expected JoinablePath object, not {cls_name}") from None + else: + return vfspath_method(obj) def ensure_distinct_paths(source, target): diff --git a/Lib/test/test_pathlib/support/zip_path.py b/Lib/test/test_pathlib/support/zip_path.py index 21e1d07423aff5..2bfe89b36595b0 100644 --- a/Lib/test/test_pathlib/support/zip_path.py +++ b/Lib/test/test_pathlib/support/zip_path.py @@ -334,4 +334,4 @@ def symlink_to(self, target, target_is_directory=False): zinfo.external_attr = stat.S_IFLNK << 16 if target_is_directory: zinfo.external_attr |= 0x10 - self.zip_file.writestr(zinfo, vfspath(target)) + self.zip_file.writestr(zinfo, target) From 6784ef7da7cbf1a944fd0685630ced54e4a0066c Mon Sep 17 00:00:00 2001 From: Toshaksha <147024929+Toshaksha@users.noreply.github.com> Date: Sun, 27 Jul 2025 22:07:52 +0530 Subject: [PATCH 3/3] gh-136278: Document codecs.escape_encode() and codecs.escape_decode() (#136314) Closes #136278 --- Doc/library/codecs.rst | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Doc/library/codecs.rst b/Doc/library/codecs.rst index 1cb0e225bca043..f96f2f8281f450 100644 --- a/Doc/library/codecs.rst +++ b/Doc/library/codecs.rst @@ -1483,6 +1483,36 @@ to :class:`bytes` mappings. They are not supported by :meth:`bytes.decode` Restoration of the aliases for the binary transforms. +.. _standalone-codec-functions: + +Standalone Codec Functions +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The following functions provide encoding and decoding functionality similar to codecs, +but are not available as named codecs through :func:`codecs.encode` or :func:`codecs.decode`. +They are used internally (for example, by :mod:`pickle`) and behave similarly to the +``string_escape`` codec that was removed in Python 3. + +.. function:: codecs.escape_encode(input, errors=None) + + Encode *input* using escape sequences. Similar to how :func:`repr` on bytes + produces escaped byte values. + + *input* must be a :class:`bytes` object. + + Returns a tuple ``(output, length)`` where *output* is a :class:`bytes` + object and *length* is the number of bytes consumed. + +.. function:: codecs.escape_decode(input, errors=None) + + Decode *input* from escape sequences back to the original bytes. + + *input* must be a :term:`bytes-like object`. + + Returns a tuple ``(output, length)`` where *output* is a :class:`bytes` + object and *length* is the number of bytes consumed. + + .. _text-transforms: Text Transforms