diff options
Diffstat (limited to 'Lib/pathlib.py')
| -rw-r--r-- | Lib/pathlib.py | 147 | 
1 files changed, 80 insertions, 67 deletions
| diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 1480e2f..0484dac 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -178,12 +178,28 @@ class _WindowsFlavour(_Flavour):      def casefold_parts(self, parts):          return [p.lower() for p in parts] -    def resolve(self, path): +    def resolve(self, path, strict=False):          s = str(path)          if not s:              return os.getcwd() +        previous_s = None          if _getfinalpathname is not None: -            return self._ext_to_normal(_getfinalpathname(s)) +            if strict: +                return self._ext_to_normal(_getfinalpathname(s)) +            else: +                while True: +                    try: +                        s = self._ext_to_normal(_getfinalpathname(s)) +                    except FileNotFoundError: +                        previous_s = s +                        s = os.path.dirname(s) +                        if previous_s == s: +                            return path +                    else: +                        if previous_s is None: +                            return s +                        else: +                            return s + os.path.sep + os.path.basename(previous_s)          # Means fallback on absolute          return None @@ -285,7 +301,7 @@ class _PosixFlavour(_Flavour):      def casefold_parts(self, parts):          return parts -    def resolve(self, path): +    def resolve(self, path, strict=False):          sep = self.sep          accessor = path._accessor          seen = {} @@ -315,7 +331,10 @@ class _PosixFlavour(_Flavour):                      target = accessor.readlink(newpath)                  except OSError as e:                      if e.errno != EINVAL: -                        raise +                        if strict: +                            raise +                        else: +                            return newpath                      # Not a symlink                      path = newpath                  else: @@ -385,6 +404,8 @@ class _NormalAccessor(_Accessor):      listdir = _wrap_strfunc(os.listdir) +    scandir = _wrap_strfunc(os.scandir) +      chmod = _wrap_strfunc(os.chmod)      if hasattr(os, "lchmod"): @@ -429,25 +450,6 @@ _normal_accessor = _NormalAccessor()  # Globbing helpers  # -@contextmanager -def _cached(func): -    try: -        func.__cached__ -        yield func -    except AttributeError: -        cache = {} -        def wrapper(*args): -            try: -                return cache[args] -            except KeyError: -                value = cache[args] = func(*args) -                return value -        wrapper.__cached__ = True -        try: -            yield wrapper -        finally: -            cache.clear() -  def _make_selector(pattern_parts):      pat = pattern_parts[0]      child_parts = pattern_parts[1:] @@ -473,8 +475,10 @@ class _Selector:          self.child_parts = child_parts          if child_parts:              self.successor = _make_selector(child_parts) +            self.dironly = True          else:              self.successor = _TerminatingSelector() +            self.dironly = False      def select_from(self, parent_path):          """Iterate over all child paths of `parent_path` matched by this @@ -482,13 +486,15 @@ class _Selector:          path_cls = type(parent_path)          is_dir = path_cls.is_dir          exists = path_cls.exists -        listdir = parent_path._accessor.listdir -        return self._select_from(parent_path, is_dir, exists, listdir) +        scandir = parent_path._accessor.scandir +        if not is_dir(parent_path): +            return iter([]) +        return self._select_from(parent_path, is_dir, exists, scandir)  class _TerminatingSelector: -    def _select_from(self, parent_path, is_dir, exists, listdir): +    def _select_from(self, parent_path, is_dir, exists, scandir):          yield parent_path @@ -498,13 +504,11 @@ class _PreciseSelector(_Selector):          self.name = name          _Selector.__init__(self, child_parts) -    def _select_from(self, parent_path, is_dir, exists, listdir): +    def _select_from(self, parent_path, is_dir, exists, scandir):          try: -            if not is_dir(parent_path): -                return              path = parent_path._make_child_relpath(self.name) -            if exists(path): -                for p in self.successor._select_from(path, is_dir, exists, listdir): +            if (is_dir if self.dironly else exists)(path): +                for p in self.successor._select_from(path, is_dir, exists, scandir):                      yield p          except PermissionError:              return @@ -516,17 +520,18 @@ class _WildcardSelector(_Selector):          self.pat = re.compile(fnmatch.translate(pat))          _Selector.__init__(self, child_parts) -    def _select_from(self, parent_path, is_dir, exists, listdir): +    def _select_from(self, parent_path, is_dir, exists, scandir):          try: -            if not is_dir(parent_path): -                return              cf = parent_path._flavour.casefold -            for name in listdir(parent_path): -                casefolded = cf(name) -                if self.pat.match(casefolded): -                    path = parent_path._make_child_relpath(name) -                    for p in self.successor._select_from(path, is_dir, exists, listdir): -                        yield p +            entries = list(scandir(parent_path)) +            for entry in entries: +                if not self.dironly or entry.is_dir(): +                    name = entry.name +                    casefolded = cf(name) +                    if self.pat.match(casefolded): +                        path = parent_path._make_child_relpath(name) +                        for p in self.successor._select_from(path, is_dir, exists, scandir): +                            yield p          except PermissionError:              return @@ -537,32 +542,30 @@ class _RecursiveWildcardSelector(_Selector):      def __init__(self, pat, child_parts):          _Selector.__init__(self, child_parts) -    def _iterate_directories(self, parent_path, is_dir, listdir): +    def _iterate_directories(self, parent_path, is_dir, scandir):          yield parent_path          try: -            for name in listdir(parent_path): -                path = parent_path._make_child_relpath(name) -                if is_dir(path) and not path.is_symlink(): -                    for p in self._iterate_directories(path, is_dir, listdir): +            entries = list(scandir(parent_path)) +            for entry in entries: +                if entry.is_dir() and not entry.is_symlink(): +                    path = parent_path._make_child_relpath(entry.name) +                    for p in self._iterate_directories(path, is_dir, scandir):                          yield p          except PermissionError:              return -    def _select_from(self, parent_path, is_dir, exists, listdir): +    def _select_from(self, parent_path, is_dir, exists, scandir):          try: -            if not is_dir(parent_path): -                return -            with _cached(listdir) as listdir: -                yielded = set() -                try: -                    successor_select = self.successor._select_from -                    for starting_point in self._iterate_directories(parent_path, is_dir, listdir): -                        for p in successor_select(starting_point, is_dir, exists, listdir): -                            if p not in yielded: -                                yield p -                                yielded.add(p) -                finally: -                    yielded.clear() +            yielded = set() +            try: +                successor_select = self.successor._select_from +                for starting_point in self._iterate_directories(parent_path, is_dir, scandir): +                    for p in successor_select(starting_point, is_dir, exists, scandir): +                        if p not in yielded: +                            yield p +                            yielded.add(p) +            finally: +                yielded.clear()          except PermissionError:              return @@ -634,13 +637,16 @@ class PurePath(object):          for a in args:              if isinstance(a, PurePath):                  parts += a._parts -            elif isinstance(a, str): -                # Force-cast str subclasses to str (issue #21127) -                parts.append(str(a))              else: -                raise TypeError( -                    "argument should be a path or str object, not %r" -                    % type(a)) +                a = os.fspath(a) +                if isinstance(a, str): +                    # Force-cast str subclasses to str (issue #21127) +                    parts.append(str(a)) +                else: +                    raise TypeError( +                        "argument should be a str object or an os.PathLike " +                        "object returning str, not %r" +                        % type(a))          return cls._flavour.parse_parts(parts)      @classmethod @@ -693,6 +699,9 @@ class PurePath(object):                                                    self._parts) or '.'              return self._str +    def __fspath__(self): +        return str(self) +      def as_posix(self):          """Return the string representation of the path with forward (/)          slashes.""" @@ -943,6 +952,10 @@ class PurePath(object):                  return False          return True +# Can't subclass os.PathLike from PurePath and keep the constructor +# optimizations in PurePath._parse_args(). +os.PathLike.register(PurePath) +  class PurePosixPath(PurePath):      _flavour = _posix_flavour @@ -1098,7 +1111,7 @@ class Path(PurePath):          obj._init(template=self)          return obj -    def resolve(self): +    def resolve(self, strict=False):          """          Make the path absolute, resolving all symlinks on the way and also          normalizing it (for example turning slashes into backslashes under @@ -1106,7 +1119,7 @@ class Path(PurePath):          """          if self._closed:              self._raise_closed() -        s = self._flavour.resolve(self) +        s = self._flavour.resolve(self, strict=strict)          if s is None:              # No symlink resolution => for consistency, raise an error if              # the path doesn't exist or is forbidden | 
