Skip to content

Commit

Permalink
Added Path.iterdir() and deprecated Path.listdir(). Ref #214
Browse files Browse the repository at this point in the history
  • Loading branch information
jaraco committed Dec 4, 2023
1 parent f1d623c commit 8565be1
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 38 deletions.
1 change: 1 addition & 0 deletions newsfragments/214.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added Path.iterdir() and deprecated Path.listdir(). Ref #214.
26 changes: 17 additions & 9 deletions path/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,8 +475,8 @@ def relpathto(self, dest):

# --- Listing, searching, walking, and matching

def listdir(self, match=None):
"""List of items in this directory.
def iterdir(self, match=None):
"""Yields items in this directory.
Use :meth:`files` or :meth:`dirs` instead if you want a listing
of just files or just subdirectories.
Expand All @@ -489,7 +489,15 @@ def listdir(self, match=None):
.. seealso:: :meth:`files`, :meth:`dirs`
"""
match = matchers.load(match)
return list(filter(match, (self / child for child in os.listdir(self))))
return filter(match, (self / child for child in os.listdir(self)))

def listdir(self, match=None):
warnings.warn(
".listdir is deprecated; use iterdir",
DeprecationWarning,
stacklevel=2,
)
return list(self.iterdir(match=match))

def dirs(self, *args, **kwargs):
"""List of this directory's subdirectories.
Expand All @@ -498,20 +506,20 @@ def dirs(self, *args, **kwargs):
This does not walk recursively into subdirectories
(but see :meth:`walkdirs`).
Accepts parameters to :meth:`listdir`.
Accepts parameters to :meth:`iterdir`.
"""
return [p for p in self.listdir(*args, **kwargs) if p.isdir()]
return [p for p in self.iterdir(*args, **kwargs) if p.isdir()]

def files(self, *args, **kwargs):
"""List of the files in self.
The elements of the list are Path objects.
This does not walk into subdirectories (see :meth:`walkfiles`).
Accepts parameters to :meth:`listdir`.
Accepts parameters to :meth:`iterdir`.
"""

return [p for p in self.listdir(*args, **kwargs) if p.isfile()]
return [p for p in self.iterdir(*args, **kwargs) if p.isfile()]

def walk(self, match=None, errors='strict'):
"""Iterator over files and subdirs, recursively.
Expand All @@ -534,7 +542,7 @@ def walk(self, match=None, errors='strict'):
match = matchers.load(match)

try:
childList = self.listdir()
childList = self.iterdir()
except Exception as exc:
errors(f"Unable to list directory '{self}': {exc}")
return
Expand Down Expand Up @@ -1336,7 +1344,7 @@ def merge_tree(
dst = self._next_class(dst)
dst.makedirs_p()

sources = self.listdir()
sources = list(self.iterdir())
_ignored = ignore(self, [item.name for item in sources])

def ignored(item):
Expand Down
2 changes: 1 addition & 1 deletion path/matchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def __call__(self, path):
class CaseInsensitive(Pattern):
"""
A Pattern with a ``'normcase'`` property, suitable for passing to
:meth:`listdir`, :meth:`dirs`, :meth:`files`, :meth:`walk`,
:meth:`iterdir`, :meth:`dirs`, :meth:`files`, :meth:`walk`,
:meth:`walkdirs`, or :meth:`walkfiles` to match case-insensitive.
For example, to get all files ending in .py, .Py, .pY, or .PY in the
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ testing =
appdirs
packaging
pywin32; platform_system == "Windows" and python_version < "3.12"
more_itertools

# required for checkdocs on README.rst
pygments
Expand Down
61 changes: 33 additions & 28 deletions test_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import stat

import pytest
from more_itertools import ilen

import path
from path import Path
Expand Down Expand Up @@ -512,7 +513,7 @@ def test_touch(self, tmpdir):

def test_listing(self, tmpdir):
d = Path(tmpdir)
assert d.listdir() == []
assert list(d.iterdir()) == []

f = 'testfile.txt'
af = d / f
Expand All @@ -521,7 +522,7 @@ def test_listing(self, tmpdir):
try:
assert af.exists()

assert d.listdir() == [af]
assert list(d.iterdir()) == [af]

# .glob()
assert d.glob('testfile.txt') == [af]
Expand All @@ -545,7 +546,7 @@ def test_listing(self, tmpdir):
with open(f, 'w', encoding='utf-8') as fobj:
fobj.write('some text\n')
try:
files2 = d.listdir()
files2 = list(d.iterdir())
files.sort()
files2.sort()
assert files == files2
Expand All @@ -565,17 +566,17 @@ def bytes_filename(self, tmpdir):
raise pytest.skip(f"Invalid encodings disallowed {exc}")
return name

def test_listdir_other_encoding(self, tmpdir, bytes_filename): # pragma: nocover
def test_iterdir_other_encoding(self, tmpdir, bytes_filename): # pragma: nocover
"""
Some filesystems allow non-character sequences in path names.
``.listdir`` should still function in this case.
``.iterdir`` should still function in this case.
See issue #61 for details.
"""
# first demonstrate that os.listdir works
assert os.listdir(str(tmpdir).encode('ascii'))

# now try with path
results = Path(tmpdir).listdir()
results = Path(tmpdir).iterdir()
(res,) = results
assert isinstance(res, Path)
assert len(res.basename()) == len(bytes_filename)
Expand Down Expand Up @@ -663,7 +664,7 @@ def test_shutil(self, tmpdir):
testA.copytree(testC)
assert testC.isdir()
self.assertSetsEqual(
testC.listdir(),
testC.iterdir(),
[testC / testCopy.name, testC / testFile.name, testCopyOfLink],
)
assert not testCopyOfLink.islink()
Expand All @@ -676,7 +677,7 @@ def test_shutil(self, tmpdir):
testA.copytree(testC, True)
assert testC.isdir()
self.assertSetsEqual(
testC.listdir(),
testC.iterdir(),
[testC / testCopy.name, testC / testFile.name, testCopyOfLink],
)
if hasattr(os, 'symlink'):
Expand All @@ -686,7 +687,7 @@ def test_shutil(self, tmpdir):
# Clean up.
testDir.rmtree()
assert not testDir.exists()
self.assertList(d.listdir(), [])
self.assertList(d.iterdir(), [])

def assertList(self, listing, expected):
assert sorted(listing) == sorted(expected)
Expand All @@ -702,7 +703,7 @@ def test_patterns(self, tmpdir):

for name in names:
(e / name).touch()
self.assertList(d.listdir('*.tmp'), [d / 'x.tmp', d / 'xdir.tmp'])
self.assertList(d.iterdir('*.tmp'), [d / 'x.tmp', d / 'xdir.tmp'])
self.assertList(d.files('*.tmp'), [d / 'x.tmp'])
self.assertList(d.dirs('*.tmp'), [d / 'xdir.tmp'])
self.assertList(
Expand Down Expand Up @@ -922,7 +923,7 @@ def test_with_nonexisting_dst_kwargs(self):
self.subdir_b / self.test_file.name,
self.subdir_b / self.test_link.name,
}
assert set(self.subdir_b.listdir()) == expected
assert set(self.subdir_b.iterdir()) == expected
self.check_link()

def test_with_nonexisting_dst_args(self):
Expand All @@ -932,7 +933,7 @@ def test_with_nonexisting_dst_args(self):
self.subdir_b / self.test_file.name,
self.subdir_b / self.test_link.name,
}
assert set(self.subdir_b.listdir()) == expected
assert set(self.subdir_b.iterdir()) == expected
self.check_link()

def test_with_existing_dst(self):
Expand All @@ -953,7 +954,7 @@ def test_with_existing_dst(self):
self.subdir_b / self.test_link.name,
self.subdir_b / test_new.name,
}
assert set(self.subdir_b.listdir()) == expected
assert set(self.subdir_b.iterdir()) == expected
self.check_link()
assert len(Path(self.subdir_b / self.test_file.name).bytes()) == 5000

Expand All @@ -965,7 +966,7 @@ def test_copytree_parameters(self):
self.subdir_a.merge_tree(self.subdir_b, ignore=ignore)

assert self.subdir_b.isdir()
assert self.subdir_b.listdir() == [self.subdir_b / self.test_file.name]
assert list(self.subdir_b.iterdir()) == [self.subdir_b / self.test_file.name]

def test_only_newer(self):
"""
Expand All @@ -985,6 +986,10 @@ def test_nested(self):
self.subdir_a.merge_tree(self.subdir_b)
assert self.subdir_b.joinpath('subsub').isdir()

def test_listdir(self):
with pytest.deprecated_call():
Path().listdir()


class TestChdir:
def test_chdir_or_cd(self, tmpdir):
Expand Down Expand Up @@ -1111,22 +1116,22 @@ def normcase(path):
assert p.fnmatch('foobar', normcase=normcase)
assert p.fnmatch('FOO[ABC]AR', normcase=normcase)

def test_listdir_simple(self):
def test_iterdir_simple(self):
p = Path('.')
assert len(p.listdir()) == len(os.listdir('.'))
assert ilen(p.iterdir()) == len(os.listdir('.'))

def test_listdir_empty_pattern(self):
def test_iterdir_empty_pattern(self):
p = Path('.')
assert p.listdir('') == []
assert list(p.iterdir('')) == []

def test_listdir_patterns(self, tmpdir):
def test_iterdir_patterns(self, tmpdir):
p = Path(tmpdir)
(p / 'sub').mkdir()
(p / 'File').touch()
assert p.listdir('s*') == [p / 'sub']
assert len(p.listdir('*')) == 2
assert list(p.iterdir('s*')) == [p / 'sub']
assert ilen(p.iterdir('*')) == 2

def test_listdir_custom_module(self, tmpdir):
def test_iterdir_custom_module(self, tmpdir):
"""
Listdir patterns should honor the case sensitivity of the path module
used by that Path class.
Expand All @@ -1135,23 +1140,23 @@ def test_listdir_custom_module(self, tmpdir):
p = always_unix(tmpdir)
(p / 'sub').mkdir()
(p / 'File').touch()
assert p.listdir('S*') == []
assert list(p.iterdir('S*')) == []

always_win = Path.using_module(ntpath)
p = always_win(tmpdir)
assert p.listdir('S*') == [p / 'sub']
assert p.listdir('f*') == [p / 'File']
assert list(p.iterdir('S*')) == [p / 'sub']
assert list(p.iterdir('f*')) == [p / 'File']

def test_listdir_case_insensitive(self, tmpdir):
def test_iterdir_case_insensitive(self, tmpdir):
"""
Listdir patterns should honor the case sensitivity of the path module
used by that Path class.
"""
p = Path(tmpdir)
(p / 'sub').mkdir()
(p / 'File').touch()
assert p.listdir(matchers.CaseInsensitive('S*')) == [p / 'sub']
assert p.listdir(matchers.CaseInsensitive('f*')) == [p / 'File']
assert list(p.iterdir(matchers.CaseInsensitive('S*'))) == [p / 'sub']
assert list(p.iterdir(matchers.CaseInsensitive('f*'))) == [p / 'File']
assert p.files(matchers.CaseInsensitive('S*')) == []
assert p.dirs(matchers.CaseInsensitive('f*')) == []

Expand Down

0 comments on commit 8565be1

Please sign in to comment.