From 8565be1a1faae95be4606a1b924b59a25bedb9c9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 4 Dec 2023 15:21:36 -0500 Subject: [PATCH] Added Path.iterdir() and deprecated Path.listdir(). Ref #214 --- newsfragments/214.feature.rst | 1 + path/__init__.py | 26 +++++++++------ path/matchers.py | 2 +- setup.cfg | 1 + test_path.py | 61 +++++++++++++++++++---------------- 5 files changed, 53 insertions(+), 38 deletions(-) create mode 100644 newsfragments/214.feature.rst diff --git a/newsfragments/214.feature.rst b/newsfragments/214.feature.rst new file mode 100644 index 00000000..904897f8 --- /dev/null +++ b/newsfragments/214.feature.rst @@ -0,0 +1 @@ +Added Path.iterdir() and deprecated Path.listdir(). Ref #214. \ No newline at end of file diff --git a/path/__init__.py b/path/__init__.py index 2283175c..250a7e20 100644 --- a/path/__init__.py +++ b/path/__init__.py @@ -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. @@ -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. @@ -498,9 +506,9 @@ 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. @@ -508,10 +516,10 @@ def files(self, *args, **kwargs): 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. @@ -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 @@ -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): diff --git a/path/matchers.py b/path/matchers.py index 63ca218a..79636d65 100644 --- a/path/matchers.py +++ b/path/matchers.py @@ -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 diff --git a/setup.cfg b/setup.cfg index 722ffe7d..c21a3772 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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 diff --git a/test_path.py b/test_path.py index 0bc0440d..34804de1 100644 --- a/test_path.py +++ b/test_path.py @@ -30,6 +30,7 @@ import stat import pytest +from more_itertools import ilen import path from path import Path @@ -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 @@ -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] @@ -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 @@ -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) @@ -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() @@ -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'): @@ -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) @@ -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( @@ -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): @@ -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): @@ -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 @@ -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): """ @@ -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): @@ -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. @@ -1135,14 +1140,14 @@ 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. @@ -1150,8 +1155,8 @@ def test_listdir_case_insensitive(self, tmpdir): 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*')) == []