From f24e2e5464ba6498e7b8d73c3f9b417d59fd1b26 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Fri, 23 Apr 2021 21:48:52 +0100 Subject: [PATCH] bpo-39950: add `pathlib.Path.hardlink_to()` method that supersedes `link_to()` (GH-18909) The argument order of `link_to()` is reversed compared to what one may expect, so: a.link_to(b) Might be expected to create *a* as a link to *b*, in fact it creates *b* as a link to *a*, making it function more like a "link from". This doesn't match `symlink_to()` nor the documentation and doesn't seem to be the original author's intent. This PR deprecates `link_to()` and introduces `hardlink_to()`, which has the same argument order as `symlink_to()`. --- Doc/library/pathlib.rst | 21 ++++++++++++++++--- Doc/whatsnew/3.10.rst | 9 ++++++++ Lib/pathlib.py | 15 +++++++++++++ Lib/test/test_pathlib.py | 21 ++++++++++++++++++- .../2021-01-22-00-15-37.bpo-39950.NzLVaR.rst | 2 ++ 5 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2021-01-22-00-15-37.bpo-39950.NzLVaR.rst diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index 122642ad5a4..b6507eb4d6f 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -1140,6 +1140,15 @@ call fails (for example because the path doesn't exist). The order of arguments (link, target) is the reverse of :func:`os.symlink`'s. +.. method:: Path.hardlink_to(target) + + Make this path a hard link to the same file as *target*. + + .. note:: + The order of arguments (link, target) is the reverse + of :func:`os.link`'s. + + .. versionadded:: 3.10 .. method:: Path.link_to(target) @@ -1149,11 +1158,17 @@ call fails (for example because the path doesn't exist). This function does not make this path a hard link to *target*, despite the implication of the function and argument names. The argument order - (target, link) is the reverse of :func:`Path.symlink_to`, but matches - that of :func:`os.link`. + (target, link) is the reverse of :func:`Path.symlink_to` and + :func:`Path.hardlink_to`, but matches that of :func:`os.link`. .. versionadded:: 3.8 + .. deprecated:: 3.10 + + This method is deprecated in favor of :meth:`Path.hardlink_to`, as the + argument order of :meth:`Path.link_to` does not match that of + :meth:`Path.symlink_to`. + .. method:: Path.touch(mode=0o666, exist_ok=True) @@ -1246,7 +1261,7 @@ Below is a table mapping various :mod:`os` functions to their corresponding :func:`os.path.isdir` :meth:`Path.is_dir` :func:`os.path.isfile` :meth:`Path.is_file` :func:`os.path.islink` :meth:`Path.is_symlink` -:func:`os.link` :meth:`Path.link_to` +:func:`os.link` :meth:`Path.hardlink_to` :func:`os.symlink` :meth:`Path.symlink_to` :func:`os.readlink` :meth:`Path.readlink` :func:`os.path.relpath` :meth:`Path.relative_to` [#]_ diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 86cf11f796a..247749a48a5 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -1007,6 +1007,11 @@ Added negative indexing support to :attr:`PurePath.parents `. (Contributed by Yaroslav Pankovych in :issue:`21041`) +Added :meth:`Path.hardlink_to ` method that +supersedes :meth:`~pathlib.Path.link_to`. The new method has the same argument +order as :meth:`~pathlib.Path.symlink_to`. +(Contributed by Barney Gale in :issue:`39950`.) + platform -------- @@ -1363,6 +1368,10 @@ Deprecated (Contributed by Jelle Zijlstra in :issue:`21574`.) +* :meth:`pathlib.Path.link_to` is deprecated and slated for removal in + Python 3.12. Use :meth:`pathlib.Path.hardlink_to` instead. + (Contributed by Barney Gale in :issue:`39950`.) + Removed ======= diff --git a/Lib/pathlib.py b/Lib/pathlib.py index ebc2c02ecc0..37934c6038e 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -6,6 +6,7 @@ import os import posixpath import re import sys +import warnings from _collections_abc import Sequence from errno import EINVAL, ENOENT, ENOTDIR, EBADF, ELOOP from operator import attrgetter @@ -1306,6 +1307,14 @@ class Path(PurePath): """ self._accessor.symlink(target, self, target_is_directory) + def hardlink_to(self, target): + """ + Make this path a hard link pointing to the same file as *target*. + + Note the order of arguments (self, target) is the reverse of os.link's. + """ + self._accessor.link(target, self) + def link_to(self, target): """ Make the target path a hard link pointing to this path. @@ -1315,7 +1324,13 @@ class Path(PurePath): of arguments (target, link) is the reverse of Path.symlink_to, but matches that of os.link. + Deprecated since Python 3.10 and scheduled for removal in Python 3.12. + Use `hardlink_to()` instead. """ + warnings.warn("pathlib.Path.link_to() is deprecated and is scheduled " + "for removal in Python 3.12. " + "Use pathlib.Path.hardlink_to() instead.", + DeprecationWarning) self._accessor.link(self, target) # Convenience functions for querying the stat results diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 0c89b6ef141..6ed08f7e70c 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -1925,7 +1925,8 @@ class _BasePathTest(object): # linking to another path. q = P / 'dirA' / 'fileAA' try: - p.link_to(q) + with self.assertWarns(DeprecationWarning): + p.link_to(q) except PermissionError as e: self.skipTest('os.link(): %s' % e) self.assertEqual(q.stat().st_size, size) @@ -1937,6 +1938,24 @@ class _BasePathTest(object): self.assertEqual(os.stat(r).st_size, size) self.assertTrue(q.stat) + @unittest.skipUnless(hasattr(os, "link"), "os.link() is not present") + def test_hardlink_to(self): + P = self.cls(BASE) + target = P / 'fileA' + size = target.stat().st_size + # linking to another path. + link = P / 'dirA' / 'fileAA' + link.hardlink_to(target) + self.assertEqual(link.stat().st_size, size) + self.assertTrue(os.path.samefile(target, link)) + self.assertTrue(target.exists()) + # Linking to a str of a relative path. + link2 = P / 'dirA' / 'fileAAA' + target2 = rel_join('fileA') + link2.hardlink_to(target2) + self.assertEqual(os.stat(target2).st_size, size) + self.assertTrue(link2.exists()) + @unittest.skipIf(hasattr(os, "link"), "os.link() is present") def test_link_to_not_implemented(self): P = self.cls(BASE) diff --git a/Misc/NEWS.d/next/Library/2021-01-22-00-15-37.bpo-39950.NzLVaR.rst b/Misc/NEWS.d/next/Library/2021-01-22-00-15-37.bpo-39950.NzLVaR.rst new file mode 100644 index 00000000000..33b8acf9c18 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-01-22-00-15-37.bpo-39950.NzLVaR.rst @@ -0,0 +1,2 @@ +Add `pathlib.Path.hardlink_to()` method that supersedes `link_to()`. The new +method has the same argument order as `symlink_to()`.