From e17fc75225e172507bb21eb9420400f27a067509 Mon Sep 17 00:00:00 2001 From: barneygale Date: Fri, 29 Nov 2024 00:52:39 +0000 Subject: [PATCH] GH-123599: Deprecate duplicate `pathname2url()` implementation Call `urllib.request.pathname2url()` from `pathlib.Path.as_uri()`, and deprecate the duplicate implementation in `PurePath`. --- Doc/library/pathlib.rst | 8 +++-- Doc/whatsnew/3.14.rst | 5 +++ Lib/pathlib/_local.py | 13 +++++-- Lib/test/test_pathlib/test_pathlib.py | 36 +++++++++++-------- ...-11-29-00-53-28.gh-issue-123599.vyUh2S.rst | 2 ++ 5 files changed, 44 insertions(+), 20 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-11-29-00-53-28.gh-issue-123599.vyUh2S.rst diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index a42ac1f8bcdf71..27be18a015e091 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -886,9 +886,11 @@ conforming to :rfc:`8089`. >>> p.as_uri() 'file:///c:/Windows' - For historical reasons, this method is also available from - :class:`PurePath` objects. However, its use of :func:`os.fsencode` makes - it strictly impure. + .. deprecated-removed:: 3.14 3.16 + + Calling this method from :class:`PurePath` rather than :class:`Path` is + possible but deprecated. The method's use of :func:`os.fsencode` makes + it strictly impure. Expanding and resolving paths diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 869a47c1261293..14e96e0dc25ec6 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -652,6 +652,11 @@ Deprecated write new code. The :mod:`subprocess` module is recommended instead. (Contributed by Victor Stinner in :gh:`120743`.) +* :mod:`pathlib`: + :meth:`!pathlib.PurePath.as_uri` is deprecated and will be removed in Python + 3.16. Use :meth:`pathlib.Path.as_uri` instead. + (Contributed by Barney Gale in :gh:`123599`.) + * :mod:`symtable`: Deprecate :meth:`symtable.Class.get_methods` due to the lack of interest. (Contributed by Bénédikt Tran in :gh:`119698`.) diff --git a/Lib/pathlib/_local.py b/Lib/pathlib/_local.py index b27f456d375225..96cf32af3d340e 100644 --- a/Lib/pathlib/_local.py +++ b/Lib/pathlib/_local.py @@ -4,6 +4,7 @@ import os import posixpath import sys +import warnings from glob import _StringGlobber from itertools import chain from _collections_abc import Sequence @@ -451,7 +452,6 @@ def is_absolute(self): def is_reserved(self): """Return True if the path contains one of the special names reserved by the system, if any.""" - import warnings msg = ("pathlib.PurePath.is_reserved() is deprecated and scheduled " "for removal in Python 3.15. Use os.path.isreserved() to " "detect reserved paths on Windows.") @@ -462,6 +462,9 @@ def is_reserved(self): def as_uri(self): """Return the path as a URI.""" + msg = ("pathlib.PurePath.as_uri() is deprecated and scheduled " + "for removal in Python 3.16. Use pathlib.Path.as_uri().") + warnings.warn(msg, DeprecationWarning, stacklevel=2) if not self.is_absolute(): raise ValueError("relative path can't be expressed as a file URI") @@ -524,7 +527,6 @@ class Path(PathBase, PurePath): but cannot instantiate a WindowsPath on a POSIX system or vice versa. """ __slots__ = () - as_uri = PurePath.as_uri @classmethod def _unsupported_msg(cls, attribute): @@ -900,6 +902,13 @@ def expanduser(self): return self + def as_uri(self): + """Return the path as a URI.""" + if not self.is_absolute(): + raise ValueError("relative path can't be expressed as a file URI") + from urllib.request import pathname2url + return 'file:' + pathname2url(str(self)) + @classmethod def from_uri(cls, uri): """Return a new path from the given 'file' URI.""" diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index 6a994f890da616..cc104f6a7e8078 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -290,12 +290,18 @@ def assertLess(a, b): with self.assertRaises(TypeError): P() < {} + def make_uri(self, path): + if isinstance(path, pathlib.Path): + return path.as_uri() + with self.assertWarns(DeprecationWarning): + return path.as_uri() + def test_as_uri_common(self): P = self.cls with self.assertRaises(ValueError): - P('a').as_uri() + self.make_uri(P('a')) with self.assertRaises(ValueError): - P().as_uri() + self.make_uri(P()) def test_repr_roundtrips(self): for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): @@ -369,9 +375,9 @@ def test_eq_posix(self): @needs_posix def test_as_uri_posix(self): P = self.cls - self.assertEqual(P('/').as_uri(), 'file:///') - self.assertEqual(P('/a/b.c').as_uri(), 'file:///a/b.c') - self.assertEqual(P('/a/b%#c').as_uri(), 'file:///a/b%25%23c') + self.assertEqual(self.make_uri(P('/')), 'file:///') + self.assertEqual(self.make_uri(P('/a/b.c')), 'file:///a/b.c') + self.assertEqual(self.make_uri(P('/a/b%#c')), 'file:///a/b%25%23c') @needs_posix def test_as_uri_non_ascii(self): @@ -381,7 +387,7 @@ def test_as_uri_non_ascii(self): os.fsencode('\xe9') except UnicodeEncodeError: self.skipTest("\\xe9 cannot be encoded to the filesystem encoding") - self.assertEqual(P('/a/b\xe9').as_uri(), + self.assertEqual(self.make_uri(P('/a/b\xe9')), 'file:///a/b' + quote_from_bytes(os.fsencode('\xe9'))) @needs_posix @@ -475,17 +481,17 @@ def test_eq_windows(self): def test_as_uri_windows(self): P = self.cls with self.assertRaises(ValueError): - P('/a/b').as_uri() + self.make_uri(P('/a/b')) with self.assertRaises(ValueError): - P('c:a/b').as_uri() - self.assertEqual(P('c:/').as_uri(), 'file:///c:/') - self.assertEqual(P('c:/a/b.c').as_uri(), 'file:///c:/a/b.c') - self.assertEqual(P('c:/a/b%#c').as_uri(), 'file:///c:/a/b%25%23c') - self.assertEqual(P('c:/a/b\xe9').as_uri(), 'file:///c:/a/b%C3%A9') - self.assertEqual(P('//some/share/').as_uri(), 'file://some/share/') - self.assertEqual(P('//some/share/a/b.c').as_uri(), + self.make_uri(P('c:a/b')) + self.assertEqual(self.make_uri(P('c:/')), 'file:///c:/') + self.assertEqual(self.make_uri(P('c:/a/b.c')), 'file:///c:/a/b.c') + self.assertEqual(self.make_uri(P('c:/a/b%#c')), 'file:///c:/a/b%25%23c') + self.assertEqual(self.make_uri(P('c:/a/b\xe9')), 'file:///c:/a/b%C3%A9') + self.assertEqual(self.make_uri(P('//some/share/')), 'file://some/share/') + self.assertEqual(self.make_uri(P('//some/share/a/b.c')), 'file://some/share/a/b.c') - self.assertEqual(P('//some/share/a/b%#c\xe9').as_uri(), + self.assertEqual(self.make_uri(P('//some/share/a/b%#c\xe9')), 'file://some/share/a/b%25%23c%C3%A9') @needs_windows diff --git a/Misc/NEWS.d/next/Library/2024-11-29-00-53-28.gh-issue-123599.vyUh2S.rst b/Misc/NEWS.d/next/Library/2024-11-29-00-53-28.gh-issue-123599.vyUh2S.rst new file mode 100644 index 00000000000000..68b63bc085aafb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-29-00-53-28.gh-issue-123599.vyUh2S.rst @@ -0,0 +1,2 @@ +Deprecate :meth:`!pathlib.PurePath.as_uri`; use :meth:`pathlib.Path.as_uri` +instead.