Issue #15177: Added dir_fd parameter to os.fwalk().
This commit is contained in:
parent
2a193a818a
commit
c48fe98a7c
|
@ -2211,18 +2211,21 @@ features:
|
|||
os.rmdir(os.path.join(root, name))
|
||||
|
||||
|
||||
.. function:: fwalk(top, topdown=True, onerror=None, followlinks=False)
|
||||
.. function:: fwalk(top='.', topdown=True, onerror=None, followlinks=False, *, dir_fd=None)
|
||||
|
||||
.. index::
|
||||
single: directory; walking
|
||||
single: directory; traversal
|
||||
|
||||
This behaves exactly like :func:`walk`, except that it yields a 4-tuple
|
||||
``(dirpath, dirnames, filenames, dirfd)``.
|
||||
``(dirpath, dirnames, filenames, dirfd)``, and it supports ``dir_fd``.
|
||||
|
||||
*dirpath*, *dirnames* and *filenames* are identical to :func:`walk` output,
|
||||
and *dirfd* is a file descriptor referring to the directory *dirpath*.
|
||||
|
||||
This function always supports :ref:`paths relative to directory descriptors
|
||||
<dir_fd>`.
|
||||
|
||||
.. note::
|
||||
|
||||
Since :func:`fwalk` yields file descriptors, those are only valid until
|
||||
|
|
18
Lib/os.py
18
Lib/os.py
|
@ -422,9 +422,9 @@ def walk(top, topdown=True, onerror=None, followlinks=False):
|
|||
|
||||
__all__.append("walk")
|
||||
|
||||
if open in supports_dir_fd:
|
||||
if {open, stat} <= supports_dir_fd and {listdir, stat} <= supports_fd:
|
||||
|
||||
def fwalk(top, topdown=True, onerror=None, followlinks=False):
|
||||
def fwalk(top=".", topdown=True, onerror=None, followlinks=False, *, dir_fd=None):
|
||||
"""Directory tree generator.
|
||||
|
||||
This behaves exactly like walk(), except that it yields a 4-tuple
|
||||
|
@ -434,9 +434,13 @@ if open in supports_dir_fd:
|
|||
`dirpath`, `dirnames` and `filenames` are identical to walk() output,
|
||||
and `dirfd` is a file descriptor referring to the directory `dirpath`.
|
||||
|
||||
The advantage of walkfd() over walk() is that it's safe against symlink
|
||||
The advantage of fwalk() over walk() is that it's safe against symlink
|
||||
races (when followlinks is False).
|
||||
|
||||
If dir_fd is not None, it should be a file descriptor open to a directory,
|
||||
and top should be relative; top will then be relative to that directory.
|
||||
(dir_fd is always supported for fwalk.)
|
||||
|
||||
Caution:
|
||||
Since fwalk() yields file descriptors, those are only valid until the
|
||||
next iteration step, so you should dup() them if you want to keep them
|
||||
|
@ -455,11 +459,11 @@ if open in supports_dir_fd:
|
|||
"""
|
||||
# Note: To guard against symlink races, we use the standard
|
||||
# lstat()/open()/fstat() trick.
|
||||
orig_st = lstat(top)
|
||||
topfd = open(top, O_RDONLY)
|
||||
orig_st = stat(top, follow_symlinks=False, dir_fd=dir_fd)
|
||||
topfd = open(top, O_RDONLY, dir_fd=dir_fd)
|
||||
try:
|
||||
if (followlinks or (st.S_ISDIR(orig_st.st_mode) and
|
||||
path.samestat(orig_st, fstat(topfd)))):
|
||||
path.samestat(orig_st, stat(topfd)))):
|
||||
yield from _fwalk(topfd, top, topdown, onerror, followlinks)
|
||||
finally:
|
||||
close(topfd)
|
||||
|
@ -502,7 +506,7 @@ if open in supports_dir_fd:
|
|||
onerror(err)
|
||||
return
|
||||
try:
|
||||
if followlinks or path.samestat(orig_st, fstat(dirfd)):
|
||||
if followlinks or path.samestat(orig_st, stat(dirfd)):
|
||||
dirpath = path.join(toppath, name)
|
||||
yield from _fwalk(dirfd, dirpath, topdown, onerror, followlinks)
|
||||
finally:
|
||||
|
|
|
@ -741,19 +741,38 @@ class WalkTests(unittest.TestCase):
|
|||
class FwalkTests(WalkTests):
|
||||
"""Tests for os.fwalk()."""
|
||||
|
||||
def test_compare_to_walk(self):
|
||||
# compare with walk() results
|
||||
def _compare_to_walk(self, walk_kwargs, fwalk_kwargs):
|
||||
"""
|
||||
compare with walk() results.
|
||||
"""
|
||||
for topdown, followlinks in itertools.product((True, False), repeat=2):
|
||||
args = support.TESTFN, topdown, None, followlinks
|
||||
d = {'topdown': topdown, 'followlinks': followlinks}
|
||||
walk_kwargs.update(d)
|
||||
fwalk_kwargs.update(d)
|
||||
|
||||
expected = {}
|
||||
for root, dirs, files in os.walk(*args):
|
||||
for root, dirs, files in os.walk(**walk_kwargs):
|
||||
expected[root] = (set(dirs), set(files))
|
||||
|
||||
for root, dirs, files, rootfd in os.fwalk(*args):
|
||||
for root, dirs, files, rootfd in os.fwalk(**fwalk_kwargs):
|
||||
self.assertIn(root, expected)
|
||||
self.assertEqual(expected[root], (set(dirs), set(files)))
|
||||
|
||||
def test_compare_to_walk(self):
|
||||
kwargs = {'top': support.TESTFN}
|
||||
self._compare_to_walk(kwargs, kwargs)
|
||||
|
||||
def test_dir_fd(self):
|
||||
try:
|
||||
fd = os.open(".", os.O_RDONLY)
|
||||
walk_kwargs = {'top': support.TESTFN}
|
||||
fwalk_kwargs = walk_kwargs.copy()
|
||||
fwalk_kwargs['dir_fd'] = fd
|
||||
self._compare_to_walk(walk_kwargs, fwalk_kwargs)
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
def test_yields_correct_dir_fd(self):
|
||||
# check returned file descriptors
|
||||
for topdown, followlinks in itertools.product((True, False), repeat=2):
|
||||
args = support.TESTFN, topdown, None, followlinks
|
||||
|
|
Loading…
Reference in New Issue