Skip to content

Commit f24e2e5

Browse files
authored
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()`.
1 parent e047239 commit f24e2e5

File tree

5 files changed

+64
-4
lines changed

5 files changed

+64
-4
lines changed

Doc/library/pathlib.rst

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,15 @@ call fails (for example because the path doesn't exist).
11401140
The order of arguments (link, target) is the reverse
11411141
of :func:`os.symlink`'s.
11421142

1143+
.. method:: Path.hardlink_to(target)
1144+
1145+
Make this path a hard link to the same file as *target*.
1146+
1147+
.. note::
1148+
The order of arguments (link, target) is the reverse
1149+
of :func:`os.link`'s.
1150+
1151+
.. versionadded:: 3.10
11431152

11441153
.. method:: Path.link_to(target)
11451154

@@ -1149,11 +1158,17 @@ call fails (for example because the path doesn't exist).
11491158

11501159
This function does not make this path a hard link to *target*, despite
11511160
the implication of the function and argument names. The argument order
1152-
(target, link) is the reverse of :func:`Path.symlink_to`, but matches
1153-
that of :func:`os.link`.
1161+
(target, link) is the reverse of :func:`Path.symlink_to` and
1162+
:func:`Path.hardlink_to`, but matches that of :func:`os.link`.
11541163

11551164
.. versionadded:: 3.8
11561165

1166+
.. deprecated:: 3.10
1167+
1168+
This method is deprecated in favor of :meth:`Path.hardlink_to`, as the
1169+
argument order of :meth:`Path.link_to` does not match that of
1170+
:meth:`Path.symlink_to`.
1171+
11571172

11581173
.. method:: Path.touch(mode=0o666, exist_ok=True)
11591174

@@ -1246,7 +1261,7 @@ Below is a table mapping various :mod:`os` functions to their corresponding
12461261
:func:`os.path.isdir` :meth:`Path.is_dir`
12471262
:func:`os.path.isfile` :meth:`Path.is_file`
12481263
:func:`os.path.islink` :meth:`Path.is_symlink`
1249-
:func:`os.link` :meth:`Path.link_to`
1264+
:func:`os.link` :meth:`Path.hardlink_to`
12501265
:func:`os.symlink` :meth:`Path.symlink_to`
12511266
:func:`os.readlink` :meth:`Path.readlink`
12521267
:func:`os.path.relpath` :meth:`Path.relative_to` [#]_

Doc/whatsnew/3.10.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,11 @@ Added negative indexing support to :attr:`PurePath.parents
10071007
<pathlib.PurePath.parents>`.
10081008
(Contributed by Yaroslav Pankovych in :issue:`21041`)
10091009
1010+
Added :meth:`Path.hardlink_to <pathlib.Path.hardlink_to>` method that
1011+
supersedes :meth:`~pathlib.Path.link_to`. The new method has the same argument
1012+
order as :meth:`~pathlib.Path.symlink_to`.
1013+
(Contributed by Barney Gale in :issue:`39950`.)
1014+
10101015
platform
10111016
--------
10121017
@@ -1363,6 +1368,10 @@ Deprecated
13631368
13641369
(Contributed by Jelle Zijlstra in :issue:`21574`.)
13651370
1371+
* :meth:`pathlib.Path.link_to` is deprecated and slated for removal in
1372+
Python 3.12. Use :meth:`pathlib.Path.hardlink_to` instead.
1373+
(Contributed by Barney Gale in :issue:`39950`.)
1374+
13661375
13671376
Removed
13681377
=======

Lib/pathlib.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import posixpath
77
import re
88
import sys
9+
import warnings
910
from _collections_abc import Sequence
1011
from errno import EINVAL, ENOENT, ENOTDIR, EBADF, ELOOP
1112
from operator import attrgetter
@@ -1306,6 +1307,14 @@ def symlink_to(self, target, target_is_directory=False):
13061307
"""
13071308
self._accessor.symlink(target, self, target_is_directory)
13081309

1310+
def hardlink_to(self, target):
1311+
"""
1312+
Make this path a hard link pointing to the same file as *target*.
1313+
1314+
Note the order of arguments (self, target) is the reverse of os.link's.
1315+
"""
1316+
self._accessor.link(target, self)
1317+
13091318
def link_to(self, target):
13101319
"""
13111320
Make the target path a hard link pointing to this path.
@@ -1315,7 +1324,13 @@ def link_to(self, target):
13151324
of arguments (target, link) is the reverse of Path.symlink_to, but
13161325
matches that of os.link.
13171326
1327+
Deprecated since Python 3.10 and scheduled for removal in Python 3.12.
1328+
Use `hardlink_to()` instead.
13181329
"""
1330+
warnings.warn("pathlib.Path.link_to() is deprecated and is scheduled "
1331+
"for removal in Python 3.12. "
1332+
"Use pathlib.Path.hardlink_to() instead.",
1333+
DeprecationWarning)
13191334
self._accessor.link(self, target)
13201335

13211336
# Convenience functions for querying the stat results

Lib/test/test_pathlib.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1925,7 +1925,8 @@ def test_link_to(self):
19251925
# linking to another path.
19261926
q = P / 'dirA' / 'fileAA'
19271927
try:
1928-
p.link_to(q)
1928+
with self.assertWarns(DeprecationWarning):
1929+
p.link_to(q)
19291930
except PermissionError as e:
19301931
self.skipTest('os.link(): %s' % e)
19311932
self.assertEqual(q.stat().st_size, size)
@@ -1937,6 +1938,24 @@ def test_link_to(self):
19371938
self.assertEqual(os.stat(r).st_size, size)
19381939
self.assertTrue(q.stat)
19391940

1941+
@unittest.skipUnless(hasattr(os, "link"), "os.link() is not present")
1942+
def test_hardlink_to(self):
1943+
P = self.cls(BASE)
1944+
target = P / 'fileA'
1945+
size = target.stat().st_size
1946+
# linking to another path.
1947+
link = P / 'dirA' / 'fileAA'
1948+
link.hardlink_to(target)
1949+
self.assertEqual(link.stat().st_size, size)
1950+
self.assertTrue(os.path.samefile(target, link))
1951+
self.assertTrue(target.exists())
1952+
# Linking to a str of a relative path.
1953+
link2 = P / 'dirA' / 'fileAAA'
1954+
target2 = rel_join('fileA')
1955+
link2.hardlink_to(target2)
1956+
self.assertEqual(os.stat(target2).st_size, size)
1957+
self.assertTrue(link2.exists())
1958+
19401959
@unittest.skipIf(hasattr(os, "link"), "os.link() is present")
19411960
def test_link_to_not_implemented(self):
19421961
P = self.cls(BASE)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add `pathlib.Path.hardlink_to()` method that supersedes `link_to()`. The new
2+
method has the same argument order as `symlink_to()`.

0 commit comments

Comments
 (0)