https://github.com/python/cpython/commit/f141e8ec2a2e8d21fc08c1f56ef40104c7a7fad2
commit: f141e8ec2a2e8d21fc08c1f56ef40104c7a7fad2
branch: main
author: Barney Gale <barney.g...@gmail.com>
committer: barneygale <barney.g...@gmail.com>
date: 2025-03-20T00:54:36Z
summary:

GH-123599: Deprecate duplicate `pathname2url()` implementation (#127380)

Call `urllib.request.pathname2url()` from `pathlib.Path.as_uri()`, and
deprecate the duplicate implementation in `PurePath`.

Co-authored-by: Adam Turner <9087854+aa-tur...@users.noreply.github.com>

files:
A Misc/NEWS.d/next/Library/2024-11-29-00-53-28.gh-issue-123599.vyUh2S.rst
M Doc/library/pathlib.rst
M Doc/whatsnew/3.14.rst
M Lib/pathlib/__init__.py
M Lib/test/test_pathlib/test_pathlib.py

diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst
index a2955d28d2dd9e..708a16e6bc8c94 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.19
+
+      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 04b6eb1099b4fd..767cf9a1f08dc2 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -1159,6 +1159,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.19. Use :meth:`pathlib.Path.as_uri` instead.
+  (Contributed by Barney Gale in :gh:`123599`.)
+
 * :mod:`pdb`:
   The undocumented ``pdb.Pdb.curframe_locals`` attribute is now a deprecated
   read-only property. The low overhead dynamic frame locals access added in
diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py
index f44283a53e5704..a8111cc4f305fa 100644
--- a/Lib/pathlib/__init__.py
+++ b/Lib/pathlib/__init__.py
@@ -525,13 +525,17 @@ def is_reserved(self):
         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.")
-        warnings.warn(msg, DeprecationWarning, stacklevel=2)
+        warnings._deprecated("pathlib.PurePath.is_reserved", msg, remove=(3, 
15))
         if self.parser is ntpath:
             return self.parser.isreserved(self)
         return False
 
     def as_uri(self):
         """Return the path as a URI."""
+        import warnings
+        msg = ("pathlib.PurePath.as_uri() is deprecated and scheduled "
+               "for removal in Python 3.19. Use pathlib.Path.as_uri().")
+        warnings._deprecated("pathlib.PurePath.as_uri", msg, remove=(3, 19))
         if not self.is_absolute():
             raise ValueError("relative path can't be expressed as a file URI")
 
@@ -1266,6 +1270,13 @@ def home(cls):
             raise RuntimeError("Could not determine home directory.")
         return cls(homedir)
 
+    def as_uri(self):
+        """Return the path as a URI."""
+        if not self.is_absolute():
+            raise ValueError("relative paths can't be expressed as file URIs")
+        from urllib.request import pathname2url
+        return f'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 bda94f51968cb2..b1fcc5f6f0538e 100644
--- a/Lib/test/test_pathlib/test_pathlib.py
+++ b/Lib/test/test_pathlib/test_pathlib.py
@@ -412,12 +412,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'):
@@ -645,9 +651,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):
@@ -657,7 +663,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
@@ -751,17 +757,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.

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to