diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index 79b8b4917f6cc4b..6a94886040f95d6 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -9,6 +9,8 @@ import ntpath import os import posixpath +import sys +import warnings try: import pwd @@ -230,7 +232,6 @@ def _unsupported(cls, method_name): def __init__(self, *args, **kwargs): if kwargs: - import warnings msg = ("support for supplying keyword arguments to pathlib.PurePath " "is deprecated and scheduled for removal in Python {remove}") warnings._deprecated("pathlib.PurePath(**kwargs)", msg, remove=(3, 14)) @@ -309,6 +310,46 @@ def _make_child_entry(self, entry): path._tail_cached = self._tail + [entry.name] return path + def glob(self, pattern, *, case_sensitive=None, follow_symlinks=None): + """Iterate over this subtree and yield all existing files (of any + kind, including directories) matching the given relative pattern. + """ + sys.audit("pathlib.Path.glob", self, pattern) + if pattern.endswith('**'): + # GH-70303: '**' only matches directories. Add trailing slash. + warnings.warn( + "Pattern ending '**' will match files and directories in a " + "future Python release. Add a trailing slash to match only " + "directories and remove this warning.", + FutureWarning, 2) + pattern = f'{pattern}/' + return _abc.PathBase.glob( + self, pattern, case_sensitive=case_sensitive, follow_symlinks=follow_symlinks) + + def rglob(self, pattern, *, case_sensitive=None, follow_symlinks=None): + """Recursively yield all existing files (of any kind, including + directories) matching the given relative pattern, anywhere in + this subtree. + """ + sys.audit("pathlib.Path.rglob", self, pattern) + if pattern.endswith('**'): + # GH-70303: '**' only matches directories. Add trailing slash. + warnings.warn( + "Pattern ending '**' will match files and directories in a " + "future Python release. Add a trailing slash to match only " + "directories and remove this warning.", + FutureWarning, 2) + pattern = f'{pattern}/' + pattern = f'**/{pattern}' + return _abc.PathBase.glob( + self, pattern, case_sensitive=case_sensitive, follow_symlinks=follow_symlinks) + + def walk(self, top_down=True, on_error=None, follow_symlinks=False): + """Walk the directory tree from this directory, similar to os.walk().""" + sys.audit("pathlib.Path.walk", self, on_error, follow_symlinks) + return _abc.PathBase.walk( + self, top_down=top_down, on_error=on_error, follow_symlinks=follow_symlinks) + def absolute(self): """Return an absolute version of this path No normalization or symlink resolution is performed. diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index f75b20a1d5f1e5c..da8d67f624107a0 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -811,18 +811,6 @@ def glob(self, pattern, *, case_sensitive=None, follow_symlinks=None): """Iterate over this subtree and yield all existing files (of any kind, including directories) matching the given relative pattern. """ - sys.audit("pathlib.Path.glob", self, pattern) - return self._glob(pattern, case_sensitive, follow_symlinks) - - def rglob(self, pattern, *, case_sensitive=None, follow_symlinks=None): - """Recursively yield all existing files (of any kind, including - directories) matching the given relative pattern, anywhere in - this subtree. - """ - sys.audit("pathlib.Path.rglob", self, pattern) - return self._glob(f'**/{pattern}', case_sensitive, follow_symlinks) - - def _glob(self, pattern, case_sensitive, follow_symlinks): path_pattern = self.with_segments(pattern) if path_pattern.drive or path_pattern.root: raise NotImplementedError("Non-relative patterns are unsupported") @@ -833,14 +821,6 @@ def _glob(self, pattern, case_sensitive, follow_symlinks): if pattern[-1] in (self.pathmod.sep, self.pathmod.altsep): # GH-65238: pathlib doesn't preserve trailing slash. Add it back. pattern_parts.append('') - if pattern_parts[-1] == '**': - # GH-70303: '**' only matches directories. Add trailing slash. - warnings.warn( - "Pattern ending '**' will match files and directories in a " - "future Python release. Add a trailing slash to match only " - "directories and remove this warning.", - FutureWarning, 3) - pattern_parts.append('') if case_sensitive is None: # TODO: evaluate case-sensitivity of each directory in _select_children(). @@ -895,9 +875,16 @@ def _glob(self, pattern, case_sensitive, follow_symlinks): paths = _select_children(paths, dir_only, follow_symlinks, match) return paths + def rglob(self, pattern, *, case_sensitive=None, follow_symlinks=None): + """Recursively yield all existing files (of any kind, including + directories) matching the given relative pattern, anywhere in + this subtree. + """ + return self.glob( + f'**/{pattern}', case_sensitive=case_sensitive, follow_symlinks=follow_symlinks) + def walk(self, top_down=True, on_error=None, follow_symlinks=False): """Walk the directory tree from this directory, similar to os.walk().""" - sys.audit("pathlib.Path.walk", self, on_error, follow_symlinks) paths = [self] while paths: diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index 8f95c804f80e69b..b64e6b59da5d9af 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -1703,6 +1703,18 @@ def test_glob_above_recursion_limit(self): with set_recursion_limit(recursion_limit): list(base.glob('**/')) + def test_glob_recursive_no_trailing_slash(self): + P = self.cls + p = P(self.base) + with self.assertWarns(FutureWarning): + p.glob('**') + with self.assertWarns(FutureWarning): + p.glob('*/**') + with self.assertWarns(FutureWarning): + p.rglob('**') + with self.assertWarns(FutureWarning): + p.rglob('*/**') + @only_posix class PosixPathTest(PathTest, PurePosixPathTest): diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index e4a4e81e547cd19..a67235b4da3dd39 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -1266,19 +1266,6 @@ def test_glob_long_symlink(self): bad_link.symlink_to("bad" * 200) self.assertEqual(sorted(base.glob('**/*')), [bad_link]) - def test_glob_recursive_no_trailing_slash(self): - P = self.cls - p = P(self.base) - with self.assertWarns(FutureWarning): - p.glob('**') - with self.assertWarns(FutureWarning): - p.glob('*/**') - with self.assertWarns(FutureWarning): - p.rglob('**') - with self.assertWarns(FutureWarning): - p.rglob('*/**') - - def test_readlink(self): if not self.can_symlink: self.skipTest("symlinks required") diff --git a/Misc/NEWS.d/next/Library/2023-12-29-17-30-49.gh-issue-113568.UpWNAI.rst b/Misc/NEWS.d/next/Library/2023-12-29-17-30-49.gh-issue-113568.UpWNAI.rst new file mode 100644 index 000000000000000..aaca52501841228 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-29-17-30-49.gh-issue-113568.UpWNAI.rst @@ -0,0 +1,2 @@ +Raise audit events from :class:`pathlib.Path` and not its private base class +``PathBase``.