From 6abd966aa41006de0ab4de42c154eee817236c25 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 4 Apr 2024 06:13:04 -0400 Subject: [PATCH] Added .is_dir and .is_file for parity with pathlib. Ref #214 --- newsfragments/214.feature.rst | 1 + path/__init__.py | 38 ++++++++++++++++++++--------- path/__init__.pyi | 2 ++ test_path.py | 46 +++++++++++++++++------------------ 4 files changed, 52 insertions(+), 35 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 0000000..a0bde55 --- /dev/null +++ b/newsfragments/214.feature.rst @@ -0,0 +1 @@ +Added .is_dir and .is_file for parity with pathlib. Deprecates .isdir and .isfile. diff --git a/path/__init__.py b/path/__init__.py index 40da95a..30c1494 100644 --- a/path/__init__.py +++ b/path/__init__.py @@ -125,7 +125,7 @@ class Traversal: Directories beginning with `.` will appear in the results, but their children will not. - >>> dot_dir = next(item for item in items if item.isdir() and item.startswith('.')) + >>> dot_dir = next(item for item in items if item.is_dir() and item.startswith('.')) >>> any(item.parent == dot_dir for item in items) False """ @@ -566,7 +566,7 @@ def dirs(self, *args, **kwargs): Accepts parameters to :meth:`iterdir`. """ - return [p for p in self.iterdir(*args, **kwargs) if p.isdir()] + return [p for p in self.iterdir(*args, **kwargs) if p.is_dir()] def files(self, *args, **kwargs): """List of the files in self. @@ -577,14 +577,14 @@ def files(self, *args, **kwargs): Accepts parameters to :meth:`iterdir`. """ - return [p for p in self.iterdir(*args, **kwargs) if p.isfile()] + return [p for p in self.iterdir(*args, **kwargs) if p.is_file()] def walk(self, match=None, errors='strict'): """Iterator over files and subdirs, recursively. The iterator yields Path objects naming each child item of this directory and its descendants. This requires that - ``D.isdir()``. + ``D.is_dir()``. This performs a depth-first traversal of the directory tree. Each directory is returned just before all its children. @@ -609,7 +609,7 @@ def walk(self, match=None, errors='strict'): traverse = None if match(child): traverse = yield child - traverse = traverse or child.isdir + traverse = traverse or child.is_dir try: do_traverse = traverse() except Exception as exc: @@ -621,11 +621,11 @@ def walk(self, match=None, errors='strict'): def walkdirs(self, *args, **kwargs): """Iterator over subdirs, recursively.""" - return (item for item in self.walk(*args, **kwargs) if item.isdir()) + return (item for item in self.walk(*args, **kwargs) if item.is_dir()) def walkfiles(self, *args, **kwargs): """Iterator over files, recursively.""" - return (item for item in self.walk(*args, **kwargs) if item.isfile()) + return (item for item in self.walk(*args, **kwargs) if item.is_file()) def fnmatch(self, pattern, normcase=None): """Return ``True`` if `self.name` matches the given `pattern`. @@ -1097,10 +1097,24 @@ def exists(self): return self.module.exists(self) def isdir(self): + warnings.warn( + "isdir is deprecated; use is_dir", + DeprecationWarning, + stacklevel=2, + ) + + def is_dir(self): """.. seealso:: :func:`os.path.isdir`""" return self.module.isdir(self) def isfile(self): + warnings.warn( + "isfile is deprecated; use is_file", + DeprecationWarning, + stacklevel=2, + ) + + def is_file(self): """.. seealso:: :func:`os.path.isfile`""" return self.module.isfile(self) @@ -1556,7 +1570,7 @@ def ignored(item): if symlinks and source.islink(): target = source.readlink() target.symlink(dest) - elif source.isdir(): + elif source.is_dir(): source.merge_tree( dest, symlinks=symlinks, @@ -1613,7 +1627,7 @@ def in_place( For example, to add line numbers to a file:: p = Path(filename) - assert p.isfile() + assert p.is_file() with p.in_place() as (reader, writer): for number, line in enumerate(reader, 1): writer.write('{0:3}: '.format(number))) @@ -1747,7 +1761,7 @@ class ExtantFile(Path): """ def _validate(self): - if not self.isfile(): + if not self.is_file(): raise FileNotFoundError(f"{self} does not exist as a file.") @@ -1818,12 +1832,12 @@ class TempDir(Path): For example: >>> with TempDir() as d: - ... d.isdir() and isinstance(d, Path) + ... d.is_dir() and isinstance(d, Path) True The directory is deleted automatically. - >>> d.isdir() + >>> d.is_dir() False .. seealso:: :func:`tempfile.mkdtemp` diff --git a/path/__init__.pyi b/path/__init__.pyi index 8a779d5..2e8a926 100644 --- a/path/__init__.pyi +++ b/path/__init__.pyi @@ -154,7 +154,9 @@ class Path(str): def isabs(self) -> bool: ... def exists(self) -> bool: ... def isdir(self) -> bool: ... + def is_dir(self) -> bool: ... def isfile(self) -> bool: ... + def is_file(self) -> bool: ... def islink(self) -> bool: ... def ismount(self) -> bool: ... def samefile(self, other: str) -> bool: ... diff --git a/test_path.py b/test_path.py index 6dbe143..6b6f316 100644 --- a/test_path.py +++ b/test_path.py @@ -300,10 +300,10 @@ def test_removedirs_p(self, tmpdir): (dir / 'file').touch() (dir / 'sub').mkdir() dir.removedirs_p() - assert dir.isdir() - assert (dir / 'file').isfile() + assert dir.is_dir() + assert (dir / 'file').is_file() # TODO: shouldn't sub get removed? - # assert not (dir / 'sub').isdir() + # assert not (dir / 'sub').is_dir() class TestReadWriteText: @@ -360,7 +360,7 @@ def test_symlink_none(self, tmpdir): with root: file = (Path('dir').mkdir() / 'file').touch() file.symlink() - assert Path('file').isfile() + assert Path('file').is_file() def test_readlinkabs_passthrough(self, tmpdir): link = Path(tmpdir) / 'link' @@ -383,7 +383,7 @@ def test_skip_symlinks(self, tmpdir): assert len(list(root.walk())) == 4 skip_links = path.Traversal( - lambda item: item.isdir() and not item.islink(), + lambda item: item.is_dir() and not item.islink(), ) assert len(list(skip_links(root.walk()))) == 3 @@ -474,7 +474,7 @@ def test_touch(self, tmpdir): t1 = time.time() + threshold assert f.exists() - assert f.isfile() + assert f.is_file() assert f.size == 0 assert t0 <= f.mtime <= t1 if hasattr(os.path, 'getctime'): @@ -493,7 +493,7 @@ def test_touch(self, tmpdir): assert t0 <= t1 < t2 <= t3 # sanity check assert f.exists() - assert f.isfile() + assert f.is_file() assert f.size == 10 assert t2 <= f.mtime <= t3 if hasattr(os.path, 'getctime'): @@ -593,7 +593,7 @@ def test_makedirs(self, tmpdir): boz = foo / 'bar' / 'baz' / 'boz' boz.makedirs() try: - assert boz.isdir() + assert boz.is_dir() finally: boz.removedirs() assert not foo.exists() @@ -602,7 +602,7 @@ def test_makedirs(self, tmpdir): foo.mkdir(0o750) boz.makedirs(0o700) try: - assert boz.isdir() + assert boz.is_dir() finally: boz.removedirs() assert not foo.exists() @@ -648,13 +648,13 @@ def test_shutil(self, tmpdir): # Test simple file copying. testFile.copyfile(testCopy) - assert testCopy.isfile() + assert testCopy.is_file() assert testFile.bytes() == testCopy.bytes() # Test copying into a directory. testCopy2 = testA / testFile.name testFile.copy(testA) - assert testCopy2.isfile() + assert testCopy2.is_file() assert testFile.bytes() == testCopy2.bytes() # Make a link for the next test to use. @@ -662,7 +662,7 @@ def test_shutil(self, tmpdir): # Test copying directory tree. testA.copytree(testC) - assert testC.isdir() + assert testC.is_dir() self.assertSetsEqual( testC.iterdir(), [testC / testCopy.name, testC / testFile.name, testCopyOfLink], @@ -675,7 +675,7 @@ def test_shutil(self, tmpdir): # Copy again, preserving symlinks. testA.copytree(testC, True) - assert testC.isdir() + assert testC.is_dir() self.assertSetsEqual( testC.iterdir(), [testC / testCopy.name, testC / testFile.name, testCopyOfLink], @@ -698,7 +698,7 @@ def test_patterns(self, tmpdir): dirs = [d, d / 'xdir', d / 'xdir.tmp', d / 'xdir.tmp' / 'xsubdir'] for e in dirs: - if not e.isdir(): + if not e.is_dir(): e.makedirs() for name in names: @@ -913,12 +913,12 @@ def testing_structure(self, tmpdir): def check_link(self): target = Path(self.subdir_b / self.test_link.name) - check = target.islink if hasattr(os, 'symlink') else target.isfile + check = target.islink if hasattr(os, 'symlink') else target.is_file assert check() def test_with_nonexisting_dst_kwargs(self): self.subdir_a.merge_tree(self.subdir_b, symlinks=True) - assert self.subdir_b.isdir() + assert self.subdir_b.is_dir() expected = { self.subdir_b / self.test_file.name, self.subdir_b / self.test_link.name, @@ -928,7 +928,7 @@ def test_with_nonexisting_dst_kwargs(self): def test_with_nonexisting_dst_args(self): self.subdir_a.merge_tree(self.subdir_b, True) - assert self.subdir_b.isdir() + assert self.subdir_b.is_dir() expected = { self.subdir_b / self.test_file.name, self.subdir_b / self.test_link.name, @@ -948,7 +948,7 @@ def test_with_existing_dst(self): self.subdir_a.merge_tree(self.subdir_b, True) - assert self.subdir_b.isdir() + assert self.subdir_b.is_dir() expected = { self.subdir_b / self.test_file.name, self.subdir_b / self.test_link.name, @@ -965,7 +965,7 @@ def test_copytree_parameters(self): ignore = shutil.ignore_patterns('testlink*') self.subdir_a.merge_tree(self.subdir_b, ignore=ignore) - assert self.subdir_b.isdir() + assert self.subdir_b.is_dir() assert list(self.subdir_b.iterdir()) == [self.subdir_b / self.test_file.name] def test_only_newer(self): @@ -984,7 +984,7 @@ def test_only_newer(self): def test_nested(self): self.subdir_a.joinpath('subsub').mkdir() self.subdir_a.merge_tree(self.subdir_b) - assert self.subdir_b.joinpath('subsub').isdir() + assert self.subdir_b.joinpath('subsub').is_dir() def test_listdir(self): with pytest.deprecated_call(): @@ -1040,7 +1040,7 @@ def test_constructor(self): d = TempDir() assert isinstance(d, path.Path) assert d.exists() - assert d.isdir() + assert d.is_dir() d.rmdir() assert not d.exists() @@ -1074,8 +1074,8 @@ def test_context_manager_using_with(self): """ with TempDir() as d: - assert d.isdir() - assert not d.isdir() + assert d.is_dir() + assert not d.is_dir() def test_cleaned_up_on_interrupt(self): with contextlib.suppress(KeyboardInterrupt):