| """Object-oriented filesystem paths. |
| |
| This module provides classes to represent abstract paths and concrete |
| paths with operations that have semantics appropriate for different |
| operating systems. |
| """ |
| |
| import io |
| import ntpath |
| import os |
| import posixpath |
| |
| try: |
| import pwd |
| except ImportError: |
| pwd = None |
| try: |
| import grp |
| except ImportError: |
| grp = None |
| |
| from . import _abc |
| |
| |
| __all__ = [ |
| "UnsupportedOperation", |
| "PurePath", "PurePosixPath", "PureWindowsPath", |
| "Path", "PosixPath", "WindowsPath", |
| ] |
| |
| |
| UnsupportedOperation = _abc.UnsupportedOperation |
| |
| |
| class PurePath(_abc.PurePathBase): |
| """Base class for manipulating paths without I/O. |
| |
| PurePath represents a filesystem path and offers operations which |
| don't imply any actual filesystem I/O. Depending on your system, |
| instantiating a PurePath will return either a PurePosixPath or a |
| PureWindowsPath object. You can also instantiate either of these classes |
| directly, regardless of your system. |
| """ |
| |
| __slots__ = ( |
| # The `_str_normcase_cached` slot stores the string path with |
| # normalized case. It is set when the `_str_normcase` property is |
| # accessed for the first time. It's used to implement `__eq__()` |
| # `__hash__()`, and `_parts_normcase` |
| '_str_normcase_cached', |
| |
| # The `_parts_normcase_cached` slot stores the case-normalized |
| # string path after splitting on path separators. It's set when the |
| # `_parts_normcase` property is accessed for the first time. It's used |
| # to implement comparison methods like `__lt__()`. |
| '_parts_normcase_cached', |
| |
| # The `_hash` slot stores the hash of the case-normalized string |
| # path. It's set when `__hash__()` is called for the first time. |
| '_hash', |
| ) |
| pathmod = os.path |
| |
| def __new__(cls, *args, **kwargs): |
| """Construct a PurePath from one or several strings and or existing |
| PurePath objects. The strings and path objects are combined so as |
| to yield a canonicalized path, which is incorporated into the |
| new PurePath object. |
| """ |
| if cls is PurePath: |
| cls = PureWindowsPath if os.name == 'nt' else PurePosixPath |
| return object.__new__(cls) |
| |
| def __init__(self, *args): |
| paths = [] |
| for arg in args: |
| if isinstance(arg, PurePath): |
| if arg.pathmod is ntpath and self.pathmod is posixpath: |
| # GH-103631: Convert separators for backwards compatibility. |
| paths.extend(path.replace('\\', '/') for path in arg._raw_paths) |
| else: |
| paths.extend(arg._raw_paths) |
| else: |
| try: |
| path = os.fspath(arg) |
| except TypeError: |
| path = arg |
| if not isinstance(path, str): |
| raise TypeError( |
| "argument should be a str or an os.PathLike " |
| "object where __fspath__ returns a str, " |
| f"not {type(path).__name__!r}") |
| paths.append(path) |
| # Avoid calling super().__init__, as an optimisation |
| self._raw_paths = paths |
| self._resolving = False |
| |
| def __reduce__(self): |
| # Using the parts tuple helps share interned path parts |
| # when pickling related paths. |
| return (self.__class__, self.parts) |
| |
| def __repr__(self): |
| return "{}({!r})".format(self.__class__.__name__, self.as_posix()) |
| |
| def __fspath__(self): |
| return str(self) |
| |
| def __bytes__(self): |
| """Return the bytes representation of the path. This is only |
| recommended to use under Unix.""" |
| return os.fsencode(self) |
| |
| @property |
| def _str_normcase(self): |
| # String with normalized case, for hashing and equality checks |
| try: |
| return self._str_normcase_cached |
| except AttributeError: |
| if _abc._is_case_sensitive(self.pathmod): |
| self._str_normcase_cached = str(self) |
| else: |
| self._str_normcase_cached = str(self).lower() |
| return self._str_normcase_cached |
| |
| def __hash__(self): |
| try: |
| return self._hash |
| except AttributeError: |
| self._hash = hash(self._str_normcase) |
| return self._hash |
| |
| def __eq__(self, other): |
| if not isinstance(other, PurePath): |
| return NotImplemented |
| return self._str_normcase == other._str_normcase and self.pathmod is other.pathmod |
| |
| @property |
| def _parts_normcase(self): |
| # Cached parts with normalized case, for comparisons. |
| try: |
| return self._parts_normcase_cached |
| except AttributeError: |
| self._parts_normcase_cached = self._str_normcase.split(self.pathmod.sep) |
| return self._parts_normcase_cached |
| |
| def __lt__(self, other): |
| if not isinstance(other, PurePath) or self.pathmod is not other.pathmod: |
| return NotImplemented |
| return self._parts_normcase < other._parts_normcase |
| |
| def __le__(self, other): |
| if not isinstance(other, PurePath) or self.pathmod is not other.pathmod: |
| return NotImplemented |
| return self._parts_normcase <= other._parts_normcase |
| |
| def __gt__(self, other): |
| if not isinstance(other, PurePath) or self.pathmod is not other.pathmod: |
| return NotImplemented |
| return self._parts_normcase > other._parts_normcase |
| |
| def __ge__(self, other): |
| if not isinstance(other, PurePath) or self.pathmod is not other.pathmod: |
| return NotImplemented |
| return self._parts_normcase >= other._parts_normcase |
| |
| def as_uri(self): |
| """Return the path as a URI.""" |
| if not self.is_absolute(): |
| raise ValueError("relative path can't be expressed as a file URI") |
| |
| drive = self.drive |
| if len(drive) == 2 and drive[1] == ':': |
| # It's a path on a local drive => 'file:///c:/a/b' |
| prefix = 'file:///' + drive |
| path = self.as_posix()[2:] |
| elif drive: |
| # It's a path on a network drive => 'file://host/share/a/b' |
| prefix = 'file:' |
| path = self.as_posix() |
| else: |
| # It's a posix path => 'file:///etc/hosts' |
| prefix = 'file://' |
| path = str(self) |
| from urllib.parse import quote_from_bytes |
| return prefix + quote_from_bytes(os.fsencode(path)) |
| |
| |
| # Subclassing os.PathLike makes isinstance() checks slower, |
| # which in turn makes Path construction slower. Register instead! |
| os.PathLike.register(PurePath) |
| |
| |
| class PurePosixPath(PurePath): |
| """PurePath subclass for non-Windows systems. |
| |
| On a POSIX system, instantiating a PurePath should return this object. |
| However, you can also instantiate it directly on any system. |
| """ |
| pathmod = posixpath |
| __slots__ = () |
| |
| |
| class PureWindowsPath(PurePath): |
| """PurePath subclass for Windows systems. |
| |
| On a Windows system, instantiating a PurePath should return this object. |
| However, you can also instantiate it directly on any system. |
| """ |
| pathmod = ntpath |
| __slots__ = () |
| |
| |
| class Path(_abc.PathBase, PurePath): |
| """PurePath subclass that can make system calls. |
| |
| Path represents a filesystem path but unlike PurePath, also offers |
| methods to do system calls on path objects. Depending on your system, |
| instantiating a Path will return either a PosixPath or a WindowsPath |
| object. You can also instantiate a PosixPath or WindowsPath directly, |
| but cannot instantiate a WindowsPath on a POSIX system or vice versa. |
| """ |
| __slots__ = () |
| as_uri = PurePath.as_uri |
| |
| @classmethod |
| def _unsupported(cls, method_name): |
| msg = f"{cls.__name__}.{method_name}() is unsupported on this system" |
| raise UnsupportedOperation(msg) |
| |
| 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)) |
| super().__init__(*args) |
| |
| def __new__(cls, *args, **kwargs): |
| if cls is Path: |
| cls = WindowsPath if os.name == 'nt' else PosixPath |
| return object.__new__(cls) |
| |
| def stat(self, *, follow_symlinks=True): |
| """ |
| Return the result of the stat() system call on this path, like |
| os.stat() does. |
| """ |
| return os.stat(self, follow_symlinks=follow_symlinks) |
| |
| def is_mount(self): |
| """ |
| Check if this path is a mount point |
| """ |
| return os.path.ismount(self) |
| |
| def is_junction(self): |
| """ |
| Whether this path is a junction. |
| """ |
| return os.path.isjunction(self) |
| |
| def open(self, mode='r', buffering=-1, encoding=None, |
| errors=None, newline=None): |
| """ |
| Open the file pointed by this path and return a file object, as |
| the built-in open() function does. |
| """ |
| if "b" not in mode: |
| encoding = io.text_encoding(encoding) |
| return io.open(self, mode, buffering, encoding, errors, newline) |
| |
| def iterdir(self): |
| """Yield path objects of the directory contents. |
| |
| The children are yielded in arbitrary order, and the |
| special entries '.' and '..' are not included. |
| """ |
| return (self._make_child_relpath(name) for name in os.listdir(self)) |
| |
| def _scandir(self): |
| return os.scandir(self) |
| |
| def absolute(self): |
| """Return an absolute version of this path |
| No normalization or symlink resolution is performed. |
| |
| Use resolve() to resolve symlinks and remove '..' segments. |
| """ |
| if self.is_absolute(): |
| return self |
| if self.root: |
| drive = os.path.splitroot(os.getcwd())[0] |
| return self._from_parsed_parts(drive, self.root, self._tail) |
| if self.drive: |
| # There is a CWD on each drive-letter drive. |
| cwd = os.path.abspath(self.drive) |
| else: |
| cwd = os.getcwd() |
| if not self._tail: |
| # Fast path for "empty" paths, e.g. Path("."), Path("") or Path(). |
| # We pass only one argument to with_segments() to avoid the cost |
| # of joining, and we exploit the fact that getcwd() returns a |
| # fully-normalized string by storing it in _str. This is used to |
| # implement Path.cwd(). |
| result = self.with_segments(cwd) |
| result._str = cwd |
| return result |
| drive, root, rel = os.path.splitroot(cwd) |
| if not rel: |
| return self._from_parsed_parts(drive, root, self._tail) |
| tail = rel.split(self.pathmod.sep) |
| tail.extend(self._tail) |
| return self._from_parsed_parts(drive, root, tail) |
| |
| def resolve(self, strict=False): |
| """ |
| Make the path absolute, resolving all symlinks on the way and also |
| normalizing it. |
| """ |
| |
| return self.with_segments(os.path.realpath(self, strict=strict)) |
| |
| if pwd: |
| def owner(self, *, follow_symlinks=True): |
| """ |
| Return the login name of the file owner. |
| """ |
| uid = self.stat(follow_symlinks=follow_symlinks).st_uid |
| return pwd.getpwuid(uid).pw_name |
| |
| if grp: |
| def group(self, *, follow_symlinks=True): |
| """ |
| Return the group name of the file gid. |
| """ |
| gid = self.stat(follow_symlinks=follow_symlinks).st_gid |
| return grp.getgrgid(gid).gr_name |
| |
| if hasattr(os, "readlink"): |
| def readlink(self): |
| """ |
| Return the path to which the symbolic link points. |
| """ |
| return self.with_segments(os.readlink(self)) |
| |
| def touch(self, mode=0o666, exist_ok=True): |
| """ |
| Create this file with the given access mode, if it doesn't exist. |
| """ |
| |
| if exist_ok: |
| # First try to bump modification time |
| # Implementation note: GNU touch uses the UTIME_NOW option of |
| # the utimensat() / futimens() functions. |
| try: |
| os.utime(self, None) |
| except OSError: |
| # Avoid exception chaining |
| pass |
| else: |
| return |
| flags = os.O_CREAT | os.O_WRONLY |
| if not exist_ok: |
| flags |= os.O_EXCL |
| fd = os.open(self, flags, mode) |
| os.close(fd) |
| |
| def mkdir(self, mode=0o777, parents=False, exist_ok=False): |
| """ |
| Create a new directory at this given path. |
| """ |
| try: |
| os.mkdir(self, mode) |
| except FileNotFoundError: |
| if not parents or self.parent == self: |
| raise |
| self.parent.mkdir(parents=True, exist_ok=True) |
| self.mkdir(mode, parents=False, exist_ok=exist_ok) |
| except OSError: |
| # Cannot rely on checking for EEXIST, since the operating system |
| # could give priority to other errors like EACCES or EROFS |
| if not exist_ok or not self.is_dir(): |
| raise |
| |
| def chmod(self, mode, *, follow_symlinks=True): |
| """ |
| Change the permissions of the path, like os.chmod(). |
| """ |
| os.chmod(self, mode, follow_symlinks=follow_symlinks) |
| |
| def unlink(self, missing_ok=False): |
| """ |
| Remove this file or link. |
| If the path is a directory, use rmdir() instead. |
| """ |
| try: |
| os.unlink(self) |
| except FileNotFoundError: |
| if not missing_ok: |
| raise |
| |
| def rmdir(self): |
| """ |
| Remove this directory. The directory must be empty. |
| """ |
| os.rmdir(self) |
| |
| def rename(self, target): |
| """ |
| Rename this path to the target path. |
| |
| The target path may be absolute or relative. Relative paths are |
| interpreted relative to the current working directory, *not* the |
| directory of the Path object. |
| |
| Returns the new Path instance pointing to the target path. |
| """ |
| os.rename(self, target) |
| return self.with_segments(target) |
| |
| def replace(self, target): |
| """ |
| Rename this path to the target path, overwriting if that path exists. |
| |
| The target path may be absolute or relative. Relative paths are |
| interpreted relative to the current working directory, *not* the |
| directory of the Path object. |
| |
| Returns the new Path instance pointing to the target path. |
| """ |
| os.replace(self, target) |
| return self.with_segments(target) |
| |
| if hasattr(os, "symlink"): |
| def symlink_to(self, target, target_is_directory=False): |
| """ |
| Make this path a symlink pointing to the target path. |
| Note the order of arguments (link, target) is the reverse of os.symlink. |
| """ |
| os.symlink(target, self, target_is_directory) |
| |
| if hasattr(os, "link"): |
| def hardlink_to(self, target): |
| """ |
| Make this path a hard link pointing to the same file as *target*. |
| |
| Note the order of arguments (self, target) is the reverse of os.link's. |
| """ |
| os.link(target, self) |
| |
| def expanduser(self): |
| """ Return a new path with expanded ~ and ~user constructs |
| (as returned by os.path.expanduser) |
| """ |
| if (not (self.drive or self.root) and |
| self._tail and self._tail[0][:1] == '~'): |
| homedir = os.path.expanduser(self._tail[0]) |
| if homedir[:1] == "~": |
| raise RuntimeError("Could not determine home directory.") |
| drv, root, tail = self._parse_path(homedir) |
| return self._from_parsed_parts(drv, root, tail + self._tail[1:]) |
| |
| return self |
| |
| @classmethod |
| def from_uri(cls, uri): |
| """Return a new path from the given 'file' URI.""" |
| if not uri.startswith('file:'): |
| raise ValueError(f"URI does not start with 'file:': {uri!r}") |
| path = uri[5:] |
| if path[:3] == '///': |
| # Remove empty authority |
| path = path[2:] |
| elif path[:12] == '//localhost/': |
| # Remove 'localhost' authority |
| path = path[11:] |
| if path[:3] == '///' or (path[:1] == '/' and path[2:3] in ':|'): |
| # Remove slash before DOS device/UNC path |
| path = path[1:] |
| if path[1:2] == '|': |
| # Replace bar with colon in DOS drive |
| path = path[:1] + ':' + path[2:] |
| from urllib.parse import unquote_to_bytes |
| path = cls(os.fsdecode(unquote_to_bytes(path))) |
| if not path.is_absolute(): |
| raise ValueError(f"URI is not absolute: {uri!r}") |
| return path |
| |
| |
| class PosixPath(Path, PurePosixPath): |
| """Path subclass for non-Windows systems. |
| |
| On a POSIX system, instantiating a Path should return this object. |
| """ |
| __slots__ = () |
| |
| if os.name == 'nt': |
| def __new__(cls, *args, **kwargs): |
| raise UnsupportedOperation( |
| f"cannot instantiate {cls.__name__!r} on your system") |
| |
| class WindowsPath(Path, PureWindowsPath): |
| """Path subclass for Windows systems. |
| |
| On a Windows system, instantiating a Path should return this object. |
| """ |
| __slots__ = () |
| |
| if os.name != 'nt': |
| def __new__(cls, *args, **kwargs): |
| raise UnsupportedOperation( |
| f"cannot instantiate {cls.__name__!r} on your system") |