Skip to content

Commit 8704d33

Browse files
committed
Deprecate follow_symlinks=None
1 parent b2766f8 commit 8704d33

File tree

3 files changed

+96
-67
lines changed

3 files changed

+96
-67
lines changed

Doc/library/pathlib.rst

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -873,9 +873,9 @@ call fails (for example because the path doesn't exist).
873873
PosixPath('setup.py'),
874874
PosixPath('test_pathlib.py')]
875875

876-
By default, :meth:`Path.glob` follows symlinks except when expanding
877-
"``**``" wildcards. Set *follow_symlinks* to true to always follow
878-
symlinks, or false to treat all symlinks as files.
876+
By default, :meth:`Path.glob` emits a deprecation warning and follows
877+
symlinks except when expanding "``**``" wildcards. Set *follow_symlinks*
878+
to true to always follow symlinks, or false to treat all symlinks as files.
879879

880880
.. note::
881881
Using the "``**``" pattern in large directory trees may consume
@@ -890,6 +890,10 @@ call fails (for example because the path doesn't exist).
890890
.. versionchanged:: 3.12
891891
The *follow_symlinks* parameter was added.
892892

893+
.. deprecated-removed:: 3.12 3.14
894+
895+
Setting *follow_symlinks* to ``None`` (e.g. by omitting it) is deprecated.
896+
893897
.. method:: Path.group()
894898

895899
Return the name of the group owning the file. :exc:`KeyError` is raised
@@ -1288,9 +1292,9 @@ call fails (for example because the path doesn't exist).
12881292
PosixPath('setup.py'),
12891293
PosixPath('test_pathlib.py')]
12901294

1291-
By default, :meth:`Path.rglob` follows symlinks except when expanding
1292-
"``**``" wildcards. Set *follow_symlinks* to true to always follow
1293-
symlinks, or false to treat all symlinks as files.
1295+
By default, :meth:`Path.rglob` emits a deprecation warning and follows
1296+
symlinks except when expanding "``**``" wildcards. Set *follow_symlinks*
1297+
to true to always follow symlinks, or false to treat all symlinks as files.
12941298

12951299
.. audit-event:: pathlib.Path.rglob self,pattern pathlib.Path.rglob
12961300

@@ -1301,6 +1305,10 @@ call fails (for example because the path doesn't exist).
13011305
.. versionchanged:: 3.12
13021306
The *follow_symlinks* parameter was added.
13031307

1308+
.. deprecated-removed:: 3.12 3.14
1309+
1310+
Setting *follow_symlinks* to ``None`` (e.g. by omitting it) is deprecated.
1311+
13041312
.. method:: Path.rmdir()
13051313

13061314
Remove this directory. The directory must be empty.

Lib/pathlib.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,13 @@ def glob(self, pattern, *, follow_symlinks=None):
755755
kind, including directories) matching the given relative pattern.
756756
"""
757757
sys.audit("pathlib.Path.glob", self, pattern)
758+
if follow_symlinks is None:
759+
msg = ("pathlib.Path.glob(pattern, follow_symlinks=None) is "
760+
"deprecated and scheduled for removal in Python {remove}. "
761+
"The follow_symlinks keyword-only argument should be set "
762+
"to either True or False.")
763+
warnings._deprecated("pathlib.Path.glob(pattern, follow_symlinks=None)",
764+
msg, remove=(3, 14))
758765
if not pattern:
759766
raise ValueError("Unacceptable pattern: {!r}".format(pattern))
760767
drv, root, pattern_parts = self._parse_parts((pattern,))
@@ -772,6 +779,13 @@ def rglob(self, pattern, *, follow_symlinks=None):
772779
this subtree.
773780
"""
774781
sys.audit("pathlib.Path.rglob", self, pattern)
782+
if follow_symlinks is None:
783+
msg = ("pathlib.Path.rglob(pattern, follow_symlinks=None) is "
784+
"deprecated and scheduled for removal in Python {remove}. "
785+
"The follow_symlinks keyword-only argument should be set "
786+
"to either True or False.")
787+
warnings._deprecated("pathlib.Path.rglob(pattern, follow_symlinks=None)", msg,
788+
remove=(3, 14))
775789
drv, root, pattern_parts = self._parse_parts((pattern,))
776790
if drv or root:
777791
raise NotImplementedError("Non-relative patterns are unsupported")

Lib/test/test_pathlib.py

Lines changed: 68 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import tempfile
1212
import unittest
1313
from unittest import mock
14+
import warnings
1415

1516
from test.support import import_helper
1617
from test.support import is_emscripten, is_wasi
@@ -1749,39 +1750,42 @@ def test_iterdir_nodir(self):
17491750
errno.ENOENT, errno.EINVAL))
17501751

17511752
def test_glob_common(self):
1752-
def _check(glob, expected):
1753-
self.assertEqual(set(glob), { P(BASE, q) for q in expected })
1753+
def _check(path, glob, expected):
1754+
with self.assertWarns(DeprecationWarning):
1755+
actual = {q for q in path.glob(glob)}
1756+
self.assertEqual(actual, { P(BASE, q) for q in expected })
17541757
P = self.cls
17551758
p = P(BASE)
1756-
it = p.glob("fileA")
1757-
self.assertIsInstance(it, collections.abc.Iterator)
1758-
_check(it, ["fileA"])
1759-
_check(p.glob("fileB"), [])
1760-
_check(p.glob("dir*/file*"), ["dirB/fileB", "dirC/fileC"])
1759+
with self.assertWarns(DeprecationWarning):
1760+
it = p.glob("fileA")
1761+
self.assertIsInstance(it, collections.abc.Iterator)
1762+
self.assertEqual(set(it), { P(BASE, "fileA") })
1763+
_check(p, "fileB", [])
1764+
_check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"])
17611765
if not os_helper.can_symlink():
1762-
_check(p.glob("*A"), ['dirA', 'fileA'])
1766+
_check(p, "*A", ['dirA', 'fileA'])
17631767
else:
1764-
_check(p.glob("*A"), ['dirA', 'fileA', 'linkA'])
1768+
_check(p, "*A", ['dirA', 'fileA', 'linkA'])
17651769
if not os_helper.can_symlink():
1766-
_check(p.glob("*B/*"), ['dirB/fileB'])
1770+
_check(p, "*B/*", ['dirB/fileB'])
17671771
else:
1768-
_check(p.glob("*B/*"), ['dirB/fileB', 'dirB/linkD',
1772+
_check(p, "*B/*", ['dirB/fileB', 'dirB/linkD',
17691773
'linkB/fileB', 'linkB/linkD'])
17701774
if not os_helper.can_symlink():
1771-
_check(p.glob("*/fileB"), ['dirB/fileB'])
1775+
_check(p, "*/fileB", ['dirB/fileB'])
17721776
else:
1773-
_check(p.glob("*/fileB"), ['dirB/fileB', 'linkB/fileB'])
1777+
_check(p, "*/fileB", ['dirB/fileB', 'linkB/fileB'])
17741778

17751779
if not os_helper.can_symlink():
1776-
_check(p.glob("*/"), ["dirA", "dirB", "dirC", "dirE"])
1780+
_check(p, "*/", ["dirA", "dirB", "dirC", "dirE"])
17771781
else:
1778-
_check(p.glob("*/"), ["dirA", "dirB", "dirC", "dirE", "linkB"])
1782+
_check(p, "*/", ["dirA", "dirB", "dirC", "dirE", "linkB"])
17791783

17801784
@os_helper.skip_unless_symlink
17811785
def test_glob_follow_symlinks_common(self):
17821786
def _check(path, glob, expected):
1783-
actual = {path for path in path.glob(glob, follow_symlinks=True)
1784-
if "linkD" not in path.parent.parts} # exclude symlink loop.
1787+
actual = {q for q in path.glob(glob, follow_symlinks=True)
1788+
if "linkD" not in q.parent.parts} # exclude symlink loop.
17851789
self.assertEqual(actual, { P(BASE, q) for q in expected })
17861790
P = self.cls
17871791
p = P(BASE)
@@ -1795,7 +1799,7 @@ def _check(path, glob, expected):
17951799
@os_helper.skip_unless_symlink
17961800
def test_glob_no_follow_symlinks_common(self):
17971801
def _check(path, glob, expected):
1798-
actual = {path for path in path.glob(glob, follow_symlinks=False)}
1802+
actual = {q for q in path.glob(glob, follow_symlinks=False)}
17991803
self.assertEqual(actual, { P(BASE, q) for q in expected })
18001804
P = self.cls
18011805
p = P(BASE)
@@ -1807,43 +1811,46 @@ def _check(path, glob, expected):
18071811
_check(p, "*/", ["dirA", "dirB", "dirC", "dirE"])
18081812

18091813
def test_rglob_common(self):
1810-
def _check(glob, expected):
1811-
self.assertEqual(set(glob), { P(BASE, q) for q in expected })
1814+
def _check(path, glob, expected):
1815+
with self.assertWarns(DeprecationWarning):
1816+
actual = {q for q in path.rglob(glob)}
1817+
self.assertEqual(actual, { P(BASE, q) for q in expected })
18121818
P = self.cls
18131819
p = P(BASE)
1814-
it = p.rglob("fileA")
1815-
self.assertIsInstance(it, collections.abc.Iterator)
1816-
_check(it, ["fileA"])
1817-
_check(p.rglob("fileB"), ["dirB/fileB"])
1818-
_check(p.rglob("*/fileA"), [])
1820+
with self.assertWarns(DeprecationWarning):
1821+
it = p.rglob("fileA")
1822+
self.assertIsInstance(it, collections.abc.Iterator)
1823+
self.assertEqual(set(it), { P(BASE, "fileA") })
1824+
_check(p, "fileB", ["dirB/fileB"])
1825+
_check(p, "*/fileA", [])
18191826
if not os_helper.can_symlink():
1820-
_check(p.rglob("*/fileB"), ["dirB/fileB"])
1827+
_check(p, "*/fileB", ["dirB/fileB"])
18211828
else:
1822-
_check(p.rglob("*/fileB"), ["dirB/fileB", "dirB/linkD/fileB",
1829+
_check(p, "*/fileB", ["dirB/fileB", "dirB/linkD/fileB",
18231830
"linkB/fileB", "dirA/linkC/fileB"])
1824-
_check(p.rglob("file*"), ["fileA", "dirB/fileB",
1831+
_check(p, "file*", ["fileA", "dirB/fileB",
18251832
"dirC/fileC", "dirC/dirD/fileD"])
18261833
if not os_helper.can_symlink():
1827-
_check(p.rglob("*/"), [
1834+
_check(p, "*/", [
18281835
"dirA", "dirB", "dirC", "dirC/dirD", "dirE",
18291836
])
18301837
else:
1831-
_check(p.rglob("*/"), [
1838+
_check(p, "*/", [
18321839
"dirA", "dirA/linkC", "dirB", "dirB/linkD", "dirC",
18331840
"dirC/dirD", "dirE", "linkB",
18341841
])
1835-
_check(p.rglob(""), ["", "dirA", "dirB", "dirC", "dirE", "dirC/dirD"])
1842+
_check(p, "", ["", "dirA", "dirB", "dirC", "dirE", "dirC/dirD"])
18361843

18371844
p = P(BASE, "dirC")
1838-
_check(p.rglob("*"), ["dirC/fileC", "dirC/novel.txt",
1845+
_check(p, "*", ["dirC/fileC", "dirC/novel.txt",
18391846
"dirC/dirD", "dirC/dirD/fileD"])
1840-
_check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"])
1841-
_check(p.rglob("*/*"), ["dirC/dirD/fileD"])
1842-
_check(p.rglob("*/"), ["dirC/dirD"])
1843-
_check(p.rglob(""), ["dirC", "dirC/dirD"])
1847+
_check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"])
1848+
_check(p, "*/*", ["dirC/dirD/fileD"])
1849+
_check(p, "*/", ["dirC/dirD"])
1850+
_check(p, "", ["dirC", "dirC/dirD"])
18441851
# gh-91616, a re module regression
1845-
_check(p.rglob("*.txt"), ["dirC/novel.txt"])
1846-
_check(p.rglob("*.*"), ["dirC/novel.txt"])
1852+
_check(p, "*.txt", ["dirC/novel.txt"])
1853+
_check(p, "*.*", ["dirC/novel.txt"])
18471854

18481855
@os_helper.skip_unless_symlink
18491856
def test_rglob_follow_symlinks_common(self):
@@ -1904,7 +1911,7 @@ def test_rglob_symlink_loop(self):
19041911
# Don't get fooled by symlink loops (Issue #26012).
19051912
P = self.cls
19061913
p = P(BASE)
1907-
given = set(p.rglob('*'))
1914+
given = set(p.rglob('*', follow_symlinks=False))
19081915
expect = {'brokenLink',
19091916
'dirA', 'dirA/linkC',
19101917
'dirB', 'dirB/fileB', 'dirB/linkD',
@@ -1925,10 +1932,10 @@ def test_glob_many_open_files(self):
19251932
p = P(base, *(['d']*depth))
19261933
p.mkdir(parents=True)
19271934
pattern = '/'.join(['*'] * depth)
1928-
iters = [base.glob(pattern) for j in range(100)]
1935+
iters = [base.glob(pattern, follow_symlinks=True) for j in range(100)]
19291936
for it in iters:
19301937
self.assertEqual(next(it), p)
1931-
iters = [base.rglob('d') for j in range(100)]
1938+
iters = [base.rglob('d', follow_symlinks=True) for j in range(100)]
19321939
p = base
19331940
for i in range(depth):
19341941
p = p / 'd'
@@ -1939,9 +1946,9 @@ def test_glob_dotdot(self):
19391946
# ".." is not special in globs.
19401947
P = self.cls
19411948
p = P(BASE)
1942-
self.assertEqual(set(p.glob("..")), { P(BASE, "..") })
1943-
self.assertEqual(set(p.glob("dirA/../file*")), { P(BASE, "dirA/../fileA") })
1944-
self.assertEqual(set(p.glob("../xyzzy")), set())
1949+
self.assertEqual(set(p.glob("..", follow_symlinks=True)), { P(BASE, "..") })
1950+
self.assertEqual(set(p.glob("dirA/../file*", follow_symlinks=True)), { P(BASE, "dirA/../fileA") })
1951+
self.assertEqual(set(p.glob("../xyzzy", follow_symlinks=True)), set())
19451952

19461953
@os_helper.skip_unless_symlink
19471954
def test_glob_permissions(self):
@@ -1971,11 +1978,11 @@ def my_scandir(path):
19711978
return contextlib.nullcontext(entries)
19721979

19731980
with mock.patch("os.scandir", my_scandir):
1974-
self.assertEqual(len(set(base.glob("*"))), 3)
1981+
self.assertEqual(len(set(base.glob("*", follow_symlinks=True))), 3)
19751982
subdir.mkdir()
1976-
self.assertEqual(len(set(base.glob("*"))), 4)
1983+
self.assertEqual(len(set(base.glob("*", follow_symlinks=True))), 4)
19771984
subdir.chmod(000)
1978-
self.assertEqual(len(set(base.glob("*"))), 4)
1985+
self.assertEqual(len(set(base.glob("*", follow_symlinks=True))), 4)
19791986

19801987
def _check_resolve(self, p, expected, strict=True):
19811988
q = p.resolve(strict)
@@ -2894,7 +2901,7 @@ def test_unsupported_flavour(self):
28942901
def test_glob_empty_pattern(self):
28952902
p = self.cls()
28962903
with self.assertRaisesRegex(ValueError, 'Unacceptable pattern'):
2897-
list(p.glob(''))
2904+
list(p.glob('', follow_symlinks=True))
28982905

28992906

29002907
@only_posix
@@ -2987,18 +2994,18 @@ def test_resolve_loop(self):
29872994
def test_glob(self):
29882995
P = self.cls
29892996
p = P(BASE)
2990-
given = set(p.glob("FILEa"))
2997+
given = set(p.glob("FILEa", follow_symlinks=True))
29912998
expect = set() if not os_helper.fs_is_case_insensitive(BASE) else given
29922999
self.assertEqual(given, expect)
2993-
self.assertEqual(set(p.glob("FILEa*")), set())
3000+
self.assertEqual(set(p.glob("FILEa*", follow_symlinks=True)), set())
29943001

29953002
def test_rglob(self):
29963003
P = self.cls
29973004
p = P(BASE, "dirC")
2998-
given = set(p.rglob("FILEd"))
3005+
given = set(p.rglob("FILEd", follow_symlinks=True))
29993006
expect = set() if not os_helper.fs_is_case_insensitive(BASE) else given
30003007
self.assertEqual(given, expect)
3001-
self.assertEqual(set(p.rglob("FILEd*")), set())
3008+
self.assertEqual(set(p.rglob("FILEd*", follow_symlinks=True)), set())
30023009

30033010
@unittest.skipUnless(hasattr(pwd, 'getpwall'),
30043011
'pwd module does not expose getpwall()')
@@ -3061,7 +3068,7 @@ def test_expanduser(self):
30613068
"Bad file descriptor in /dev/fd affects only macOS")
30623069
def test_handling_bad_descriptor(self):
30633070
try:
3064-
file_descriptors = list(pathlib.Path('/dev/fd').rglob("*"))[3:]
3071+
file_descriptors = list(pathlib.Path('/dev/fd').rglob("*", follow_symlinks=True))[3:]
30653072
if not file_descriptors:
30663073
self.skipTest("no file descriptors - issue was not reproduced")
30673074
# Checking all file descriptors because there is no guarantee
@@ -3133,18 +3140,18 @@ def test_absolute(self):
31333140
def test_glob(self):
31343141
P = self.cls
31353142
p = P(BASE)
3136-
self.assertEqual(set(p.glob("FILEa")), { P(BASE, "fileA") })
3137-
self.assertEqual(set(p.glob("*a\\")), { P(BASE, "dirA") })
3138-
self.assertEqual(set(p.glob("F*a")), { P(BASE, "fileA") })
3139-
self.assertEqual(set(map(str, p.glob("FILEa"))), {f"{p}\\fileA"})
3140-
self.assertEqual(set(map(str, p.glob("F*a"))), {f"{p}\\fileA"})
3143+
self.assertEqual(set(p.glob("FILEa", follow_symlinks=True)), { P(BASE, "fileA") })
3144+
self.assertEqual(set(p.glob("*a\\", follow_symlinks=True)), { P(BASE, "dirA") })
3145+
self.assertEqual(set(p.glob("F*a", follow_symlinks=True)), { P(BASE, "fileA") })
3146+
self.assertEqual(set(map(str, p.glob("FILEa", follow_symlinks=True))), {f"{p}\\fileA"})
3147+
self.assertEqual(set(map(str, p.glob("F*a", follow_symlinks=True))), {f"{p}\\fileA"})
31413148

31423149
def test_rglob(self):
31433150
P = self.cls
31443151
p = P(BASE, "dirC")
3145-
self.assertEqual(set(p.rglob("FILEd")), { P(BASE, "dirC/dirD/fileD") })
3146-
self.assertEqual(set(p.rglob("*\\")), { P(BASE, "dirC/dirD") })
3147-
self.assertEqual(set(map(str, p.rglob("FILEd"))), {f"{p}\\dirD\\fileD"})
3152+
self.assertEqual(set(p.rglob("FILEd", follow_symlinks=True)), { P(BASE, "dirC/dirD/fileD") })
3153+
self.assertEqual(set(p.rglob("*\\", follow_symlinks=True)), { P(BASE, "dirC/dirD") })
3154+
self.assertEqual(set(map(str, p.rglob("FILEd", follow_symlinks=True))), {f"{p}\\dirD\\fileD"})
31483155

31493156
def test_expanduser(self):
31503157
P = self.cls

0 commit comments

Comments
 (0)