• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import io
2import ntpath
3import operator
4import os
5import posixpath
6import sys
7import warnings
8from glob import _StringGlobber
9from itertools import chain
10from _collections_abc import Sequence
11
12try:
13    import pwd
14except ImportError:
15    pwd = None
16try:
17    import grp
18except ImportError:
19    grp = None
20
21from ._abc import UnsupportedOperation, PurePathBase, PathBase
22
23
24__all__ = [
25    "PurePath", "PurePosixPath", "PureWindowsPath",
26    "Path", "PosixPath", "WindowsPath",
27    ]
28
29
30class _PathParents(Sequence):
31    """This object provides sequence-like access to the logical ancestors
32    of a path.  Don't try to construct it yourself."""
33    __slots__ = ('_path', '_drv', '_root', '_tail')
34
35    def __init__(self, path):
36        self._path = path
37        self._drv = path.drive
38        self._root = path.root
39        self._tail = path._tail
40
41    def __len__(self):
42        return len(self._tail)
43
44    def __getitem__(self, idx):
45        if isinstance(idx, slice):
46            return tuple(self[i] for i in range(*idx.indices(len(self))))
47
48        if idx >= len(self) or idx < -len(self):
49            raise IndexError(idx)
50        if idx < 0:
51            idx += len(self)
52        return self._path._from_parsed_parts(self._drv, self._root,
53                                             self._tail[:-idx - 1])
54
55    def __repr__(self):
56        return "<{}.parents>".format(type(self._path).__name__)
57
58
59class PurePath(PurePathBase):
60    """Base class for manipulating paths without I/O.
61
62    PurePath represents a filesystem path and offers operations which
63    don't imply any actual filesystem I/O.  Depending on your system,
64    instantiating a PurePath will return either a PurePosixPath or a
65    PureWindowsPath object.  You can also instantiate either of these classes
66    directly, regardless of your system.
67    """
68
69    __slots__ = (
70        # The `_raw_paths` slot stores unnormalized string paths. This is set
71        # in the `__init__()` method.
72        '_raw_paths',
73
74        # The `_drv`, `_root` and `_tail_cached` slots store parsed and
75        # normalized parts of the path. They are set when any of the `drive`,
76        # `root` or `_tail` properties are accessed for the first time. The
77        # three-part division corresponds to the result of
78        # `os.path.splitroot()`, except that the tail is further split on path
79        # separators (i.e. it is a list of strings), and that the root and
80        # tail are normalized.
81        '_drv', '_root', '_tail_cached',
82
83        # The `_str` slot stores the string representation of the path,
84        # computed from the drive, root and tail when `__str__()` is called
85        # for the first time. It's used to implement `_str_normcase`
86        '_str',
87
88        # The `_str_normcase_cached` slot stores the string path with
89        # normalized case. It is set when the `_str_normcase` property is
90        # accessed for the first time. It's used to implement `__eq__()`
91        # `__hash__()`, and `_parts_normcase`
92        '_str_normcase_cached',
93
94        # The `_parts_normcase_cached` slot stores the case-normalized
95        # string path after splitting on path separators. It's set when the
96        # `_parts_normcase` property is accessed for the first time. It's used
97        # to implement comparison methods like `__lt__()`.
98        '_parts_normcase_cached',
99
100        # The `_hash` slot stores the hash of the case-normalized string
101        # path. It's set when `__hash__()` is called for the first time.
102        '_hash',
103    )
104    parser = os.path
105    _globber = _StringGlobber
106
107    def __new__(cls, *args, **kwargs):
108        """Construct a PurePath from one or several strings and or existing
109        PurePath objects.  The strings and path objects are combined so as
110        to yield a canonicalized path, which is incorporated into the
111        new PurePath object.
112        """
113        if cls is PurePath:
114            cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
115        return object.__new__(cls)
116
117    def __init__(self, *args):
118        paths = []
119        for arg in args:
120            if isinstance(arg, PurePath):
121                if arg.parser is not self.parser:
122                    # GH-103631: Convert separators for backwards compatibility.
123                    paths.append(arg.as_posix())
124                else:
125                    paths.extend(arg._raw_paths)
126            else:
127                try:
128                    path = os.fspath(arg)
129                except TypeError:
130                    path = arg
131                if not isinstance(path, str):
132                    raise TypeError(
133                        "argument should be a str or an os.PathLike "
134                        "object where __fspath__ returns a str, "
135                        f"not {type(path).__name__!r}")
136                paths.append(path)
137        # Avoid calling super().__init__, as an optimisation
138        self._raw_paths = paths
139
140    def joinpath(self, *pathsegments):
141        """Combine this path with one or several arguments, and return a
142        new path representing either a subpath (if all arguments are relative
143        paths) or a totally different path (if one of the arguments is
144        anchored).
145        """
146        return self.with_segments(self, *pathsegments)
147
148    def __truediv__(self, key):
149        try:
150            return self.with_segments(self, key)
151        except TypeError:
152            return NotImplemented
153
154    def __rtruediv__(self, key):
155        try:
156            return self.with_segments(key, self)
157        except TypeError:
158            return NotImplemented
159
160    def __reduce__(self):
161        return self.__class__, tuple(self._raw_paths)
162
163    def __repr__(self):
164        return "{}({!r})".format(self.__class__.__name__, self.as_posix())
165
166    def __fspath__(self):
167        return str(self)
168
169    def __bytes__(self):
170        """Return the bytes representation of the path.  This is only
171        recommended to use under Unix."""
172        return os.fsencode(self)
173
174    @property
175    def _str_normcase(self):
176        # String with normalized case, for hashing and equality checks
177        try:
178            return self._str_normcase_cached
179        except AttributeError:
180            if self.parser is posixpath:
181                self._str_normcase_cached = str(self)
182            else:
183                self._str_normcase_cached = str(self).lower()
184            return self._str_normcase_cached
185
186    def __hash__(self):
187        try:
188            return self._hash
189        except AttributeError:
190            self._hash = hash(self._str_normcase)
191            return self._hash
192
193    def __eq__(self, other):
194        if not isinstance(other, PurePath):
195            return NotImplemented
196        return self._str_normcase == other._str_normcase and self.parser is other.parser
197
198    @property
199    def _parts_normcase(self):
200        # Cached parts with normalized case, for comparisons.
201        try:
202            return self._parts_normcase_cached
203        except AttributeError:
204            self._parts_normcase_cached = self._str_normcase.split(self.parser.sep)
205            return self._parts_normcase_cached
206
207    def __lt__(self, other):
208        if not isinstance(other, PurePath) or self.parser is not other.parser:
209            return NotImplemented
210        return self._parts_normcase < other._parts_normcase
211
212    def __le__(self, other):
213        if not isinstance(other, PurePath) or self.parser is not other.parser:
214            return NotImplemented
215        return self._parts_normcase <= other._parts_normcase
216
217    def __gt__(self, other):
218        if not isinstance(other, PurePath) or self.parser is not other.parser:
219            return NotImplemented
220        return self._parts_normcase > other._parts_normcase
221
222    def __ge__(self, other):
223        if not isinstance(other, PurePath) or self.parser is not other.parser:
224            return NotImplemented
225        return self._parts_normcase >= other._parts_normcase
226
227    def __str__(self):
228        """Return the string representation of the path, suitable for
229        passing to system calls."""
230        try:
231            return self._str
232        except AttributeError:
233            self._str = self._format_parsed_parts(self.drive, self.root,
234                                                  self._tail) or '.'
235            return self._str
236
237    @classmethod
238    def _format_parsed_parts(cls, drv, root, tail):
239        if drv or root:
240            return drv + root + cls.parser.sep.join(tail)
241        elif tail and cls.parser.splitdrive(tail[0])[0]:
242            tail = ['.'] + tail
243        return cls.parser.sep.join(tail)
244
245    def _from_parsed_parts(self, drv, root, tail):
246        path = self._from_parsed_string(self._format_parsed_parts(drv, root, tail))
247        path._drv = drv
248        path._root = root
249        path._tail_cached = tail
250        return path
251
252    def _from_parsed_string(self, path_str):
253        path = self.with_segments(path_str)
254        path._str = path_str or '.'
255        return path
256
257    @classmethod
258    def _parse_path(cls, path):
259        if not path:
260            return '', '', []
261        sep = cls.parser.sep
262        altsep = cls.parser.altsep
263        if altsep:
264            path = path.replace(altsep, sep)
265        drv, root, rel = cls.parser.splitroot(path)
266        if not root and drv.startswith(sep) and not drv.endswith(sep):
267            drv_parts = drv.split(sep)
268            if len(drv_parts) == 4 and drv_parts[2] not in '?.':
269                # e.g. //server/share
270                root = sep
271            elif len(drv_parts) == 6:
272                # e.g. //?/unc/server/share
273                root = sep
274        parsed = [sys.intern(str(x)) for x in rel.split(sep) if x and x != '.']
275        return drv, root, parsed
276
277    @property
278    def _raw_path(self):
279        """The joined but unnormalized path."""
280        paths = self._raw_paths
281        if len(paths) == 0:
282            path = ''
283        elif len(paths) == 1:
284            path = paths[0]
285        else:
286            path = self.parser.join(*paths)
287        return path
288
289    @property
290    def drive(self):
291        """The drive prefix (letter or UNC path), if any."""
292        try:
293            return self._drv
294        except AttributeError:
295            self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)
296            return self._drv
297
298    @property
299    def root(self):
300        """The root of the path, if any."""
301        try:
302            return self._root
303        except AttributeError:
304            self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)
305            return self._root
306
307    @property
308    def _tail(self):
309        try:
310            return self._tail_cached
311        except AttributeError:
312            self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)
313            return self._tail_cached
314
315    @property
316    def anchor(self):
317        """The concatenation of the drive and root, or ''."""
318        return self.drive + self.root
319
320    @property
321    def parts(self):
322        """An object providing sequence-like access to the
323        components in the filesystem path."""
324        if self.drive or self.root:
325            return (self.drive + self.root,) + tuple(self._tail)
326        else:
327            return tuple(self._tail)
328
329    @property
330    def parent(self):
331        """The logical parent of the path."""
332        drv = self.drive
333        root = self.root
334        tail = self._tail
335        if not tail:
336            return self
337        return self._from_parsed_parts(drv, root, tail[:-1])
338
339    @property
340    def parents(self):
341        """A sequence of this path's logical parents."""
342        # The value of this property should not be cached on the path object,
343        # as doing so would introduce a reference cycle.
344        return _PathParents(self)
345
346    @property
347    def name(self):
348        """The final path component, if any."""
349        tail = self._tail
350        if not tail:
351            return ''
352        return tail[-1]
353
354    def with_name(self, name):
355        """Return a new path with the file name changed."""
356        p = self.parser
357        if not name or p.sep in name or (p.altsep and p.altsep in name) or name == '.':
358            raise ValueError(f"Invalid name {name!r}")
359        tail = self._tail.copy()
360        if not tail:
361            raise ValueError(f"{self!r} has an empty name")
362        tail[-1] = name
363        return self._from_parsed_parts(self.drive, self.root, tail)
364
365    def relative_to(self, other, /, *_deprecated, walk_up=False):
366        """Return the relative path to another path identified by the passed
367        arguments.  If the operation is not possible (because this is not
368        related to the other path), raise ValueError.
369
370        The *walk_up* parameter controls whether `..` may be used to resolve
371        the path.
372        """
373        if _deprecated:
374            msg = ("support for supplying more than one positional argument "
375                   "to pathlib.PurePath.relative_to() is deprecated and "
376                   "scheduled for removal in Python 3.14")
377            warnings.warn(msg, DeprecationWarning, stacklevel=2)
378            other = self.with_segments(other, *_deprecated)
379        elif not isinstance(other, PurePath):
380            other = self.with_segments(other)
381        for step, path in enumerate(chain([other], other.parents)):
382            if path == self or path in self.parents:
383                break
384            elif not walk_up:
385                raise ValueError(f"{str(self)!r} is not in the subpath of {str(other)!r}")
386            elif path.name == '..':
387                raise ValueError(f"'..' segment in {str(other)!r} cannot be walked")
388        else:
389            raise ValueError(f"{str(self)!r} and {str(other)!r} have different anchors")
390        parts = ['..'] * step + self._tail[len(path._tail):]
391        return self._from_parsed_parts('', '', parts)
392
393    def is_relative_to(self, other, /, *_deprecated):
394        """Return True if the path is relative to another path or False.
395        """
396        if _deprecated:
397            msg = ("support for supplying more than one argument to "
398                   "pathlib.PurePath.is_relative_to() is deprecated and "
399                   "scheduled for removal in Python 3.14")
400            warnings.warn(msg, DeprecationWarning, stacklevel=2)
401            other = self.with_segments(other, *_deprecated)
402        elif not isinstance(other, PurePath):
403            other = self.with_segments(other)
404        return other == self or other in self.parents
405
406    def is_absolute(self):
407        """True if the path is absolute (has both a root and, if applicable,
408        a drive)."""
409        if self.parser is posixpath:
410            # Optimization: work with raw paths on POSIX.
411            for path in self._raw_paths:
412                if path.startswith('/'):
413                    return True
414            return False
415        return self.parser.isabs(self)
416
417    def is_reserved(self):
418        """Return True if the path contains one of the special names reserved
419        by the system, if any."""
420        msg = ("pathlib.PurePath.is_reserved() is deprecated and scheduled "
421               "for removal in Python 3.15. Use os.path.isreserved() to "
422               "detect reserved paths on Windows.")
423        warnings.warn(msg, DeprecationWarning, stacklevel=2)
424        if self.parser is ntpath:
425            return self.parser.isreserved(self)
426        return False
427
428    def as_uri(self):
429        """Return the path as a URI."""
430        if not self.is_absolute():
431            raise ValueError("relative path can't be expressed as a file URI")
432
433        drive = self.drive
434        if len(drive) == 2 and drive[1] == ':':
435            # It's a path on a local drive => 'file:///c:/a/b'
436            prefix = 'file:///' + drive
437            path = self.as_posix()[2:]
438        elif drive:
439            # It's a path on a network drive => 'file://host/share/a/b'
440            prefix = 'file:'
441            path = self.as_posix()
442        else:
443            # It's a posix path => 'file:///etc/hosts'
444            prefix = 'file://'
445            path = str(self)
446        from urllib.parse import quote_from_bytes
447        return prefix + quote_from_bytes(os.fsencode(path))
448
449    @property
450    def _pattern_str(self):
451        """The path expressed as a string, for use in pattern-matching."""
452        # The string representation of an empty path is a single dot ('.'). Empty
453        # paths shouldn't match wildcards, so we change it to the empty string.
454        path_str = str(self)
455        return '' if path_str == '.' else path_str
456
457# Subclassing os.PathLike makes isinstance() checks slower,
458# which in turn makes Path construction slower. Register instead!
459os.PathLike.register(PurePath)
460
461
462class PurePosixPath(PurePath):
463    """PurePath subclass for non-Windows systems.
464
465    On a POSIX system, instantiating a PurePath should return this object.
466    However, you can also instantiate it directly on any system.
467    """
468    parser = posixpath
469    __slots__ = ()
470
471
472class PureWindowsPath(PurePath):
473    """PurePath subclass for Windows systems.
474
475    On a Windows system, instantiating a PurePath should return this object.
476    However, you can also instantiate it directly on any system.
477    """
478    parser = ntpath
479    __slots__ = ()
480
481
482class Path(PathBase, PurePath):
483    """PurePath subclass that can make system calls.
484
485    Path represents a filesystem path but unlike PurePath, also offers
486    methods to do system calls on path objects. Depending on your system,
487    instantiating a Path will return either a PosixPath or a WindowsPath
488    object. You can also instantiate a PosixPath or WindowsPath directly,
489    but cannot instantiate a WindowsPath on a POSIX system or vice versa.
490    """
491    __slots__ = ()
492    as_uri = PurePath.as_uri
493
494    @classmethod
495    def _unsupported_msg(cls, attribute):
496        return f"{cls.__name__}.{attribute} is unsupported on this system"
497
498    def __init__(self, *args, **kwargs):
499        if kwargs:
500            msg = ("support for supplying keyword arguments to pathlib.PurePath "
501                   "is deprecated and scheduled for removal in Python {remove}")
502            warnings._deprecated("pathlib.PurePath(**kwargs)", msg, remove=(3, 14))
503        super().__init__(*args)
504
505    def __new__(cls, *args, **kwargs):
506        if cls is Path:
507            cls = WindowsPath if os.name == 'nt' else PosixPath
508        return object.__new__(cls)
509
510    def stat(self, *, follow_symlinks=True):
511        """
512        Return the result of the stat() system call on this path, like
513        os.stat() does.
514        """
515        return os.stat(self, follow_symlinks=follow_symlinks)
516
517    def is_mount(self):
518        """
519        Check if this path is a mount point
520        """
521        return os.path.ismount(self)
522
523    def is_junction(self):
524        """
525        Whether this path is a junction.
526        """
527        return os.path.isjunction(self)
528
529    def open(self, mode='r', buffering=-1, encoding=None,
530             errors=None, newline=None):
531        """
532        Open the file pointed to by this path and return a file object, as
533        the built-in open() function does.
534        """
535        if "b" not in mode:
536            encoding = io.text_encoding(encoding)
537        return io.open(self, mode, buffering, encoding, errors, newline)
538
539    def read_text(self, encoding=None, errors=None, newline=None):
540        """
541        Open the file in text mode, read it, and close the file.
542        """
543        # Call io.text_encoding() here to ensure any warning is raised at an
544        # appropriate stack level.
545        encoding = io.text_encoding(encoding)
546        return PathBase.read_text(self, encoding, errors, newline)
547
548    def write_text(self, data, encoding=None, errors=None, newline=None):
549        """
550        Open the file in text mode, write to it, and close the file.
551        """
552        # Call io.text_encoding() here to ensure any warning is raised at an
553        # appropriate stack level.
554        encoding = io.text_encoding(encoding)
555        return PathBase.write_text(self, data, encoding, errors, newline)
556
557    _remove_leading_dot = operator.itemgetter(slice(2, None))
558    _remove_trailing_slash = operator.itemgetter(slice(-1))
559
560    def _filter_trailing_slash(self, paths):
561        sep = self.parser.sep
562        anchor_len = len(self.anchor)
563        for path_str in paths:
564            if len(path_str) > anchor_len and path_str[-1] == sep:
565                path_str = path_str[:-1]
566            yield path_str
567
568    def iterdir(self):
569        """Yield path objects of the directory contents.
570
571        The children are yielded in arbitrary order, and the
572        special entries '.' and '..' are not included.
573        """
574        root_dir = str(self)
575        with os.scandir(root_dir) as scandir_it:
576            paths = [entry.path for entry in scandir_it]
577        if root_dir == '.':
578            paths = map(self._remove_leading_dot, paths)
579        return map(self._from_parsed_string, paths)
580
581    def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=False):
582        """Iterate over this subtree and yield all existing files (of any
583        kind, including directories) matching the given relative pattern.
584        """
585        sys.audit("pathlib.Path.glob", self, pattern)
586        if not isinstance(pattern, PurePath):
587            pattern = self.with_segments(pattern)
588        if pattern.anchor:
589            raise NotImplementedError("Non-relative patterns are unsupported")
590        parts = pattern._tail.copy()
591        if not parts:
592            raise ValueError("Unacceptable pattern: {!r}".format(pattern))
593        raw = pattern._raw_path
594        if raw[-1] in (self.parser.sep, self.parser.altsep):
595            # GH-65238: pathlib doesn't preserve trailing slash. Add it back.
596            parts.append('')
597        select = self._glob_selector(parts[::-1], case_sensitive, recurse_symlinks)
598        root = str(self)
599        paths = select(root)
600
601        # Normalize results
602        if root == '.':
603            paths = map(self._remove_leading_dot, paths)
604        if parts[-1] == '':
605            paths = map(self._remove_trailing_slash, paths)
606        elif parts[-1] == '**':
607            paths = self._filter_trailing_slash(paths)
608        paths = map(self._from_parsed_string, paths)
609        return paths
610
611    def rglob(self, pattern, *, case_sensitive=None, recurse_symlinks=False):
612        """Recursively yield all existing files (of any kind, including
613        directories) matching the given relative pattern, anywhere in
614        this subtree.
615        """
616        sys.audit("pathlib.Path.rglob", self, pattern)
617        if not isinstance(pattern, PurePath):
618            pattern = self.with_segments(pattern)
619        pattern = '**' / pattern
620        return self.glob(pattern, case_sensitive=case_sensitive, recurse_symlinks=recurse_symlinks)
621
622    def walk(self, top_down=True, on_error=None, follow_symlinks=False):
623        """Walk the directory tree from this directory, similar to os.walk()."""
624        sys.audit("pathlib.Path.walk", self, on_error, follow_symlinks)
625        root_dir = str(self)
626        if not follow_symlinks:
627            follow_symlinks = os._walk_symlinks_as_files
628        results = os.walk(root_dir, top_down, on_error, follow_symlinks)
629        for path_str, dirnames, filenames in results:
630            if root_dir == '.':
631                path_str = path_str[2:]
632            yield self._from_parsed_string(path_str), dirnames, filenames
633
634    def absolute(self):
635        """Return an absolute version of this path
636        No normalization or symlink resolution is performed.
637
638        Use resolve() to resolve symlinks and remove '..' segments.
639        """
640        if self.is_absolute():
641            return self
642        if self.root:
643            drive = os.path.splitroot(os.getcwd())[0]
644            return self._from_parsed_parts(drive, self.root, self._tail)
645        if self.drive:
646            # There is a CWD on each drive-letter drive.
647            cwd = os.path.abspath(self.drive)
648        else:
649            cwd = os.getcwd()
650        if not self._tail:
651            # Fast path for "empty" paths, e.g. Path("."), Path("") or Path().
652            # We pass only one argument to with_segments() to avoid the cost
653            # of joining, and we exploit the fact that getcwd() returns a
654            # fully-normalized string by storing it in _str. This is used to
655            # implement Path.cwd().
656            return self._from_parsed_string(cwd)
657        drive, root, rel = os.path.splitroot(cwd)
658        if not rel:
659            return self._from_parsed_parts(drive, root, self._tail)
660        tail = rel.split(self.parser.sep)
661        tail.extend(self._tail)
662        return self._from_parsed_parts(drive, root, tail)
663
664    def resolve(self, strict=False):
665        """
666        Make the path absolute, resolving all symlinks on the way and also
667        normalizing it.
668        """
669
670        return self.with_segments(os.path.realpath(self, strict=strict))
671
672    if pwd:
673        def owner(self, *, follow_symlinks=True):
674            """
675            Return the login name of the file owner.
676            """
677            uid = self.stat(follow_symlinks=follow_symlinks).st_uid
678            return pwd.getpwuid(uid).pw_name
679
680    if grp:
681        def group(self, *, follow_symlinks=True):
682            """
683            Return the group name of the file gid.
684            """
685            gid = self.stat(follow_symlinks=follow_symlinks).st_gid
686            return grp.getgrgid(gid).gr_name
687
688    if hasattr(os, "readlink"):
689        def readlink(self):
690            """
691            Return the path to which the symbolic link points.
692            """
693            return self.with_segments(os.readlink(self))
694
695    def touch(self, mode=0o666, exist_ok=True):
696        """
697        Create this file with the given access mode, if it doesn't exist.
698        """
699
700        if exist_ok:
701            # First try to bump modification time
702            # Implementation note: GNU touch uses the UTIME_NOW option of
703            # the utimensat() / futimens() functions.
704            try:
705                os.utime(self, None)
706            except OSError:
707                # Avoid exception chaining
708                pass
709            else:
710                return
711        flags = os.O_CREAT | os.O_WRONLY
712        if not exist_ok:
713            flags |= os.O_EXCL
714        fd = os.open(self, flags, mode)
715        os.close(fd)
716
717    def mkdir(self, mode=0o777, parents=False, exist_ok=False):
718        """
719        Create a new directory at this given path.
720        """
721        try:
722            os.mkdir(self, mode)
723        except FileNotFoundError:
724            if not parents or self.parent == self:
725                raise
726            self.parent.mkdir(parents=True, exist_ok=True)
727            self.mkdir(mode, parents=False, exist_ok=exist_ok)
728        except OSError:
729            # Cannot rely on checking for EEXIST, since the operating system
730            # could give priority to other errors like EACCES or EROFS
731            if not exist_ok or not self.is_dir():
732                raise
733
734    def chmod(self, mode, *, follow_symlinks=True):
735        """
736        Change the permissions of the path, like os.chmod().
737        """
738        os.chmod(self, mode, follow_symlinks=follow_symlinks)
739
740    def unlink(self, missing_ok=False):
741        """
742        Remove this file or link.
743        If the path is a directory, use rmdir() instead.
744        """
745        try:
746            os.unlink(self)
747        except FileNotFoundError:
748            if not missing_ok:
749                raise
750
751    def rmdir(self):
752        """
753        Remove this directory.  The directory must be empty.
754        """
755        os.rmdir(self)
756
757    def rename(self, target):
758        """
759        Rename this path to the target path.
760
761        The target path may be absolute or relative. Relative paths are
762        interpreted relative to the current working directory, *not* the
763        directory of the Path object.
764
765        Returns the new Path instance pointing to the target path.
766        """
767        os.rename(self, target)
768        return self.with_segments(target)
769
770    def replace(self, target):
771        """
772        Rename this path to the target path, overwriting if that path exists.
773
774        The target path may be absolute or relative. Relative paths are
775        interpreted relative to the current working directory, *not* the
776        directory of the Path object.
777
778        Returns the new Path instance pointing to the target path.
779        """
780        os.replace(self, target)
781        return self.with_segments(target)
782
783    if hasattr(os, "symlink"):
784        def symlink_to(self, target, target_is_directory=False):
785            """
786            Make this path a symlink pointing to the target path.
787            Note the order of arguments (link, target) is the reverse of os.symlink.
788            """
789            os.symlink(target, self, target_is_directory)
790
791    if hasattr(os, "link"):
792        def hardlink_to(self, target):
793            """
794            Make this path a hard link pointing to the same file as *target*.
795
796            Note the order of arguments (self, target) is the reverse of os.link's.
797            """
798            os.link(target, self)
799
800    def expanduser(self):
801        """ Return a new path with expanded ~ and ~user constructs
802        (as returned by os.path.expanduser)
803        """
804        if (not (self.drive or self.root) and
805            self._tail and self._tail[0][:1] == '~'):
806            homedir = os.path.expanduser(self._tail[0])
807            if homedir[:1] == "~":
808                raise RuntimeError("Could not determine home directory.")
809            drv, root, tail = self._parse_path(homedir)
810            return self._from_parsed_parts(drv, root, tail + self._tail[1:])
811
812        return self
813
814    @classmethod
815    def from_uri(cls, uri):
816        """Return a new path from the given 'file' URI."""
817        if not uri.startswith('file:'):
818            raise ValueError(f"URI does not start with 'file:': {uri!r}")
819        path = uri[5:]
820        if path[:3] == '///':
821            # Remove empty authority
822            path = path[2:]
823        elif path[:12] == '//localhost/':
824            # Remove 'localhost' authority
825            path = path[11:]
826        if path[:3] == '///' or (path[:1] == '/' and path[2:3] in ':|'):
827            # Remove slash before DOS device/UNC path
828            path = path[1:]
829        if path[1:2] == '|':
830            # Replace bar with colon in DOS drive
831            path = path[:1] + ':' + path[2:]
832        from urllib.parse import unquote_to_bytes
833        path = cls(os.fsdecode(unquote_to_bytes(path)))
834        if not path.is_absolute():
835            raise ValueError(f"URI is not absolute: {uri!r}")
836        return path
837
838
839class PosixPath(Path, PurePosixPath):
840    """Path subclass for non-Windows systems.
841
842    On a POSIX system, instantiating a Path should return this object.
843    """
844    __slots__ = ()
845
846    if os.name == 'nt':
847        def __new__(cls, *args, **kwargs):
848            raise UnsupportedOperation(
849                f"cannot instantiate {cls.__name__!r} on your system")
850
851class WindowsPath(Path, PureWindowsPath):
852    """Path subclass for Windows systems.
853
854    On a Windows system, instantiating a Path should return this object.
855    """
856    __slots__ = ()
857
858    if os.name != 'nt':
859        def __new__(cls, *args, **kwargs):
860            raise UnsupportedOperation(
861                f"cannot instantiate {cls.__name__!r} on your system")
862