From b74b06174fc0598273cbf536422278ec2d1693be Mon Sep 17 00:00:00 2001 From: barneygale Date: Tue, 4 Jun 2024 04:47:44 +0100 Subject: [PATCH 1/2] GH-116380: Move pathlib-specific code from `glob` to `pathlib._abc`. In `glob._Globber`, move pathlib-specific methods to `pathlib._abc.Globber` and replace them with abstract methods. Rename `glob._Globber` to `glob._GlobberBase`, and `glob._StringGlobber` to `glob._Globber`. As a result, the `glob` module is no longer befouled by code that can only ever apply to pathlib. No change of behaviour. --- Lib/glob.py | 47 +++++++++++++++++++++++-------------------- Lib/pathlib/_abc.py | 31 ++++++++++++++++++++++++++-- Lib/pathlib/_local.py | 4 ++-- 3 files changed, 56 insertions(+), 26 deletions(-) diff --git a/Lib/glob.py b/Lib/glob.py index fbb1d35aab71fa..b33da28460f565 100644 --- a/Lib/glob.py +++ b/Lib/glob.py @@ -328,8 +328,8 @@ def _compile_pattern(pat, sep, case_sensitive, recursive=True): return re.compile(regex, flags=flags).match -class _Globber: - """Class providing shell-style pattern matching and globbing. +class _GlobberBase: + """Abstract class providing shell-style pattern matching and globbing. """ def __init__(self, sep, case_sensitive, case_pedantic=False, recursive=False): @@ -338,29 +338,32 @@ def __init__(self, sep, case_sensitive, case_pedantic=False, recursive=False): self.case_pedantic = case_pedantic self.recursive = recursive - # Low-level methods + # Abstract methods - lexists = operator.methodcaller('exists', follow_symlinks=False) - add_slash = operator.methodcaller('joinpath', '') + def lexists(self, path): + """Implements os.path.lexists(). + """ + raise NotImplementedError + + def scandir(self, path): + """Implements os.scandir(). + """ + raise NotImplementedError - @staticmethod - def scandir(path): - """Emulates os.scandir(), which returns an object that can be used as - a context manager. This method is called by walk() and glob(). + def add_slash(self, path): + """Returns a path with a trailing slash added. """ - return contextlib.nullcontext(path.iterdir()) + raise NotImplementedError - @staticmethod - def concat_path(path, text): - """Appends text to the given path. + def concat_path(self, path, text): + """Implements path concatenation. """ - return path.with_segments(path._raw_path + text) + raise NotImplementedError - @staticmethod - def parse_entry(entry): + def parse_entry(self, entry): """Returns the path of an entry yielded from scandir(). """ - return entry + raise NotImplementedError # High-level methods @@ -520,22 +523,22 @@ def select_exists(self, path, exists=False): yield path -class _StringGlobber(_Globber): +class _Globber(_GlobberBase): + """Provides shell-style pattern matching and globbing for string paths. + """ lexists = staticmethod(os.path.lexists) scandir = staticmethod(os.scandir) parse_entry = operator.attrgetter('path') concat_path = operator.add if os.name == 'nt': - @staticmethod - def add_slash(pathname): + def add_slash(self, pathname): tail = os.path.splitroot(pathname)[2] if not tail or tail[-1] in '\\/': return pathname return f'{pathname}\\' else: - @staticmethod - def add_slash(pathname): + def add_slash(self, pathname): if not pathname or pathname[-1] == '/': return pathname return f'{pathname}/' diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index d7471b6927331d..e0a9186d06a244 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -12,7 +12,8 @@ """ import functools -from glob import _Globber, _no_recurse_symlinks +import operator +from glob import _GlobberBase, _no_recurse_symlinks from errno import ENOTDIR, ELOOP from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO @@ -84,6 +85,32 @@ def isabs(self, path): raise UnsupportedOperation(self._unsupported_msg('isabs()')) +class Globber(_GlobberBase): + """ + Class providing shell-style globbing for path objects. + """ + + lexists = operator.methodcaller('exists', follow_symlinks=False) + add_slash = operator.methodcaller('joinpath', '') + + def scandir(self, path): + """Emulates os.scandir(), which returns an object that can be used as + a context manager. This method is called by walk() and glob(). + """ + import contextlib + return contextlib.nullcontext(path.iterdir()) + + def concat_path(self, path, text): + """Appends text to the given path. + """ + return path.with_segments(path._raw_path + text) + + def parse_entry(self, entry): + """Returns the path of an entry yielded from scandir(). + """ + return entry + + class PurePathBase: """Base class for pure path objects. @@ -104,7 +131,7 @@ class PurePathBase: '_resolving', ) parser = ParserBase() - _globber = _Globber + _globber = Globber def __init__(self, path, *paths): self._raw_path = self.parser.join(path, *paths) if paths else path diff --git a/Lib/pathlib/_local.py b/Lib/pathlib/_local.py index 473fd525768b50..f4e8134a597c11 100644 --- a/Lib/pathlib/_local.py +++ b/Lib/pathlib/_local.py @@ -4,7 +4,7 @@ import os import posixpath import sys -from glob import _StringGlobber +from glob import _Globber from itertools import chain from _collections_abc import Sequence @@ -101,7 +101,7 @@ class PurePath(PurePathBase): '_hash', ) parser = os.path - _globber = _StringGlobber + _globber = _Globber def __new__(cls, *args, **kwargs): """Construct a PurePath from one or several strings and or existing From 5ed1c09d2023e67d4fe684f6cfcb9879825eea70 Mon Sep 17 00:00:00 2001 From: barneygale Date: Tue, 4 Jun 2024 16:38:28 +0100 Subject: [PATCH 2/2] Simplify diff --- Lib/glob.py | 23 +++++++++++++++-------- Lib/pathlib/_abc.py | 19 ++++++++++--------- Lib/pathlib/_local.py | 4 ++-- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/Lib/glob.py b/Lib/glob.py index b33da28460f565..574e5ad51b601d 100644 --- a/Lib/glob.py +++ b/Lib/glob.py @@ -340,27 +340,32 @@ def __init__(self, sep, case_sensitive, case_pedantic=False, recursive=False): # Abstract methods - def lexists(self, path): + @staticmethod + def lexists(path): """Implements os.path.lexists(). """ raise NotImplementedError - def scandir(self, path): + @staticmethod + def scandir(path): """Implements os.scandir(). """ raise NotImplementedError - def add_slash(self, path): + @staticmethod + def add_slash(path): """Returns a path with a trailing slash added. """ raise NotImplementedError - def concat_path(self, path, text): + @staticmethod + def concat_path(path, text): """Implements path concatenation. """ raise NotImplementedError - def parse_entry(self, entry): + @staticmethod + def parse_entry(entry): """Returns the path of an entry yielded from scandir(). """ raise NotImplementedError @@ -523,7 +528,7 @@ def select_exists(self, path, exists=False): yield path -class _Globber(_GlobberBase): +class _StringGlobber(_GlobberBase): """Provides shell-style pattern matching and globbing for string paths. """ lexists = staticmethod(os.path.lexists) @@ -532,13 +537,15 @@ class _Globber(_GlobberBase): concat_path = operator.add if os.name == 'nt': - def add_slash(self, pathname): + @staticmethod + def add_slash(pathname): tail = os.path.splitroot(pathname)[2] if not tail or tail[-1] in '\\/': return pathname return f'{pathname}\\' else: - def add_slash(self, pathname): + @staticmethod + def add_slash(pathname): if not pathname or pathname[-1] == '/': return pathname return f'{pathname}/' diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index e0a9186d06a244..18c31060b58dcc 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -85,7 +85,7 @@ def isabs(self, path): raise UnsupportedOperation(self._unsupported_msg('isabs()')) -class Globber(_GlobberBase): +class PathGlobber(_GlobberBase): """ Class providing shell-style globbing for path objects. """ @@ -93,21 +93,22 @@ class Globber(_GlobberBase): lexists = operator.methodcaller('exists', follow_symlinks=False) add_slash = operator.methodcaller('joinpath', '') - def scandir(self, path): + @staticmethod + def scandir(path): """Emulates os.scandir(), which returns an object that can be used as a context manager. This method is called by walk() and glob(). """ import contextlib return contextlib.nullcontext(path.iterdir()) - def concat_path(self, path, text): - """Appends text to the given path. - """ + @staticmethod + def concat_path(path, text): + """Appends text to the given path.""" return path.with_segments(path._raw_path + text) - def parse_entry(self, entry): - """Returns the path of an entry yielded from scandir(). - """ + @staticmethod + def parse_entry(entry): + """Returns the path of an entry yielded from scandir().""" return entry @@ -131,7 +132,7 @@ class PurePathBase: '_resolving', ) parser = ParserBase() - _globber = Globber + _globber = PathGlobber def __init__(self, path, *paths): self._raw_path = self.parser.join(path, *paths) if paths else path diff --git a/Lib/pathlib/_local.py b/Lib/pathlib/_local.py index f4e8134a597c11..473fd525768b50 100644 --- a/Lib/pathlib/_local.py +++ b/Lib/pathlib/_local.py @@ -4,7 +4,7 @@ import os import posixpath import sys -from glob import _Globber +from glob import _StringGlobber from itertools import chain from _collections_abc import Sequence @@ -101,7 +101,7 @@ class PurePath(PurePathBase): '_hash', ) parser = os.path - _globber = _Globber + _globber = _StringGlobber def __new__(cls, *args, **kwargs): """Construct a PurePath from one or several strings and or existing