blob: 1480e2fc71aaac4b9c54b862c6cfb945f249ea08 [file] [log] [blame]
Antoine Pitrou31119e42013-11-22 17:38:12 +01001import fnmatch
2import functools
3import io
4import ntpath
5import os
6import posixpath
7import re
8import sys
Antoine Pitrou069a5e12013-12-03 09:41:35 +01009from collections import Sequence
Antoine Pitrou31119e42013-11-22 17:38:12 +010010from contextlib import contextmanager
Antoine Pitrou2b2852b2014-10-30 23:14:03 +010011from errno import EINVAL, ENOENT, ENOTDIR
Antoine Pitrou31119e42013-11-22 17:38:12 +010012from operator import attrgetter
13from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
Antoine Pitrou069a5e12013-12-03 09:41:35 +010014from urllib.parse import quote_from_bytes as urlquote_from_bytes
Antoine Pitrou31119e42013-11-22 17:38:12 +010015
16
17supports_symlinks = True
Antoine Pitroudb118f52014-11-19 00:32:08 +010018if os.name == 'nt':
Antoine Pitrou31119e42013-11-22 17:38:12 +010019 import nt
Antoine Pitrou31119e42013-11-22 17:38:12 +010020 if sys.getwindowsversion()[:2] >= (6, 0):
21 from nt import _getfinalpathname
22 else:
23 supports_symlinks = False
24 _getfinalpathname = None
Antoine Pitroudb118f52014-11-19 00:32:08 +010025else:
26 nt = None
Antoine Pitrou31119e42013-11-22 17:38:12 +010027
28
29__all__ = [
30 "PurePath", "PurePosixPath", "PureWindowsPath",
31 "Path", "PosixPath", "WindowsPath",
32 ]
33
34#
35# Internals
36#
37
38def _is_wildcard_pattern(pat):
39 # Whether this pattern needs actual matching using fnmatch, or can
40 # be looked up directly as a file.
41 return "*" in pat or "?" in pat or "[" in pat
42
43
44class _Flavour(object):
45 """A flavour implements a particular (platform-specific) set of path
46 semantics."""
47
48 def __init__(self):
49 self.join = self.sep.join
50
51 def parse_parts(self, parts):
52 parsed = []
53 sep = self.sep
54 altsep = self.altsep
55 drv = root = ''
56 it = reversed(parts)
57 for part in it:
58 if not part:
59 continue
60 if altsep:
61 part = part.replace(altsep, sep)
62 drv, root, rel = self.splitroot(part)
63 if sep in rel:
64 for x in reversed(rel.split(sep)):
65 if x and x != '.':
66 parsed.append(sys.intern(x))
67 else:
68 if rel and rel != '.':
69 parsed.append(sys.intern(rel))
70 if drv or root:
71 if not drv:
72 # If no drive is present, try to find one in the previous
73 # parts. This makes the result of parsing e.g.
74 # ("C:", "/", "a") reasonably intuitive.
75 for part in it:
Antoine Pitrou57fffd62015-02-15 18:03:59 +010076 if not part:
77 continue
78 if altsep:
79 part = part.replace(altsep, sep)
Antoine Pitrou31119e42013-11-22 17:38:12 +010080 drv = self.splitroot(part)[0]
81 if drv:
82 break
83 break
84 if drv or root:
85 parsed.append(drv + root)
86 parsed.reverse()
87 return drv, root, parsed
88
89 def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2):
90 """
91 Join the two paths represented by the respective
92 (drive, root, parts) tuples. Return a new (drive, root, parts) tuple.
93 """
94 if root2:
Serhiy Storchakaa9939022013-12-06 17:14:12 +020095 if not drv2 and drv:
96 return drv, root2, [drv + root2] + parts2[1:]
97 elif drv2:
98 if drv2 == drv or self.casefold(drv2) == self.casefold(drv):
99 # Same drive => second path is relative to the first
100 return drv, root, parts + parts2[1:]
Antoine Pitrou31119e42013-11-22 17:38:12 +0100101 else:
Serhiy Storchakaa9939022013-12-06 17:14:12 +0200102 # Second path is non-anchored (common case)
103 return drv, root, parts + parts2
104 return drv2, root2, parts2
Antoine Pitrou31119e42013-11-22 17:38:12 +0100105
106
107class _WindowsFlavour(_Flavour):
108 # Reference for Windows paths can be found at
109 # http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx
110
111 sep = '\\'
112 altsep = '/'
113 has_drv = True
114 pathmod = ntpath
115
Antoine Pitroudb118f52014-11-19 00:32:08 +0100116 is_supported = (os.name == 'nt')
Antoine Pitrou31119e42013-11-22 17:38:12 +0100117
118 drive_letters = (
119 set(chr(x) for x in range(ord('a'), ord('z') + 1)) |
120 set(chr(x) for x in range(ord('A'), ord('Z') + 1))
121 )
122 ext_namespace_prefix = '\\\\?\\'
123
124 reserved_names = (
125 {'CON', 'PRN', 'AUX', 'NUL'} |
126 {'COM%d' % i for i in range(1, 10)} |
127 {'LPT%d' % i for i in range(1, 10)}
128 )
129
130 # Interesting findings about extended paths:
131 # - '\\?\c:\a', '//?/c:\a' and '//?/c:/a' are all supported
132 # but '\\?\c:/a' is not
133 # - extended paths are always absolute; "relative" extended paths will
134 # fail.
135
136 def splitroot(self, part, sep=sep):
137 first = part[0:1]
138 second = part[1:2]
139 if (second == sep and first == sep):
140 # XXX extended paths should also disable the collapsing of "."
141 # components (according to MSDN docs).
142 prefix, part = self._split_extended_path(part)
143 first = part[0:1]
144 second = part[1:2]
145 else:
146 prefix = ''
147 third = part[2:3]
148 if (second == sep and first == sep and third != sep):
149 # is a UNC path:
150 # vvvvvvvvvvvvvvvvvvvvv root
151 # \\machine\mountpoint\directory\etc\...
152 # directory ^^^^^^^^^^^^^^
153 index = part.find(sep, 2)
154 if index != -1:
155 index2 = part.find(sep, index + 1)
156 # a UNC path can't have two slashes in a row
157 # (after the initial two)
158 if index2 != index + 1:
159 if index2 == -1:
160 index2 = len(part)
161 if prefix:
162 return prefix + part[1:index2], sep, part[index2+1:]
163 else:
164 return part[:index2], sep, part[index2+1:]
165 drv = root = ''
166 if second == ':' and first in self.drive_letters:
167 drv = part[:2]
168 part = part[2:]
169 first = third
170 if first == sep:
171 root = first
172 part = part.lstrip(sep)
173 return prefix + drv, root, part
174
175 def casefold(self, s):
176 return s.lower()
177
178 def casefold_parts(self, parts):
179 return [p.lower() for p in parts]
180
181 def resolve(self, path):
182 s = str(path)
183 if not s:
184 return os.getcwd()
185 if _getfinalpathname is not None:
186 return self._ext_to_normal(_getfinalpathname(s))
187 # Means fallback on absolute
188 return None
189
190 def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix):
191 prefix = ''
192 if s.startswith(ext_prefix):
193 prefix = s[:4]
194 s = s[4:]
195 if s.startswith('UNC\\'):
196 prefix += s[:3]
197 s = '\\' + s[3:]
198 return prefix, s
199
200 def _ext_to_normal(self, s):
201 # Turn back an extended path into a normal DOS-like path
202 return self._split_extended_path(s)[1]
203
204 def is_reserved(self, parts):
205 # NOTE: the rules for reserved names seem somewhat complicated
206 # (e.g. r"..\NUL" is reserved but not r"foo\NUL").
207 # We err on the side of caution and return True for paths which are
208 # not considered reserved by Windows.
209 if not parts:
210 return False
211 if parts[0].startswith('\\\\'):
212 # UNC paths are never reserved
213 return False
214 return parts[-1].partition('.')[0].upper() in self.reserved_names
215
216 def make_uri(self, path):
217 # Under Windows, file URIs use the UTF-8 encoding.
218 drive = path.drive
219 if len(drive) == 2 and drive[1] == ':':
220 # It's a path on a local drive => 'file:///c:/a/b'
221 rest = path.as_posix()[2:].lstrip('/')
222 return 'file:///%s/%s' % (
223 drive, urlquote_from_bytes(rest.encode('utf-8')))
224 else:
225 # It's a path on a network drive => 'file://host/share/a/b'
226 return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8'))
227
Antoine Pitrou8477ed62014-12-30 20:54:45 +0100228 def gethomedir(self, username):
229 if 'HOME' in os.environ:
230 userhome = os.environ['HOME']
231 elif 'USERPROFILE' in os.environ:
232 userhome = os.environ['USERPROFILE']
233 elif 'HOMEPATH' in os.environ:
Antoine Pitrou5d4e27e2014-12-30 22:09:42 +0100234 try:
235 drv = os.environ['HOMEDRIVE']
236 except KeyError:
237 drv = ''
238 userhome = drv + os.environ['HOMEPATH']
Antoine Pitrou8477ed62014-12-30 20:54:45 +0100239 else:
240 raise RuntimeError("Can't determine home directory")
241
242 if username:
243 # Try to guess user home directory. By default all users
244 # directories are located in the same place and are named by
245 # corresponding usernames. If current user home directory points
246 # to nonstandard place, this guess is likely wrong.
247 if os.environ['USERNAME'] != username:
248 drv, root, parts = self.parse_parts((userhome,))
249 if parts[-1] != os.environ['USERNAME']:
250 raise RuntimeError("Can't determine home directory "
251 "for %r" % username)
252 parts[-1] = username
253 if drv or root:
254 userhome = drv + root + self.join(parts[1:])
255 else:
256 userhome = self.join(parts)
257 return userhome
Antoine Pitrou31119e42013-11-22 17:38:12 +0100258
259class _PosixFlavour(_Flavour):
260 sep = '/'
261 altsep = ''
262 has_drv = False
263 pathmod = posixpath
264
265 is_supported = (os.name != 'nt')
266
267 def splitroot(self, part, sep=sep):
268 if part and part[0] == sep:
269 stripped_part = part.lstrip(sep)
270 # According to POSIX path resolution:
271 # http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11
272 # "A pathname that begins with two successive slashes may be
273 # interpreted in an implementation-defined manner, although more
274 # than two leading slashes shall be treated as a single slash".
275 if len(part) - len(stripped_part) == 2:
276 return '', sep * 2, stripped_part
277 else:
278 return '', sep, stripped_part
279 else:
280 return '', '', part
281
282 def casefold(self, s):
283 return s
284
285 def casefold_parts(self, parts):
286 return parts
287
288 def resolve(self, path):
289 sep = self.sep
Antoine Pitrou31119e42013-11-22 17:38:12 +0100290 accessor = path._accessor
Antoine Pitrouc274fd22013-12-16 19:57:41 +0100291 seen = {}
292 def _resolve(path, rest):
293 if rest.startswith(sep):
294 path = ''
295
296 for name in rest.split(sep):
297 if not name or name == '.':
298 # current dir
299 continue
300 if name == '..':
301 # parent dir
302 path, _, _ = path.rpartition(sep)
303 continue
304 newpath = path + sep + name
305 if newpath in seen:
306 # Already seen this path
307 path = seen[newpath]
308 if path is not None:
309 # use cached value
310 continue
311 # The symlink is not resolved, so we must have a symlink loop.
312 raise RuntimeError("Symlink loop from %r" % newpath)
313 # Resolve the symbolic link
314 try:
315 target = accessor.readlink(newpath)
316 except OSError as e:
317 if e.errno != EINVAL:
318 raise
319 # Not a symlink
320 path = newpath
321 else:
322 seen[newpath] = None # not resolved symlink
323 path = _resolve(path, target)
324 seen[newpath] = path # resolved symlink
325
326 return path
327 # NOTE: according to POSIX, getcwd() cannot contain path components
328 # which are symlinks.
329 base = '' if path.is_absolute() else os.getcwd()
330 return _resolve(base, str(path)) or sep
Antoine Pitrou31119e42013-11-22 17:38:12 +0100331
332 def is_reserved(self, parts):
333 return False
334
335 def make_uri(self, path):
336 # We represent the path using the local filesystem encoding,
337 # for portability to other applications.
338 bpath = bytes(path)
339 return 'file://' + urlquote_from_bytes(bpath)
340
Antoine Pitrou8477ed62014-12-30 20:54:45 +0100341 def gethomedir(self, username):
342 if not username:
343 try:
344 return os.environ['HOME']
345 except KeyError:
346 import pwd
347 return pwd.getpwuid(os.getuid()).pw_dir
348 else:
349 import pwd
350 try:
351 return pwd.getpwnam(username).pw_dir
352 except KeyError:
353 raise RuntimeError("Can't determine home directory "
354 "for %r" % username)
355
Antoine Pitrou31119e42013-11-22 17:38:12 +0100356
357_windows_flavour = _WindowsFlavour()
358_posix_flavour = _PosixFlavour()
359
360
361class _Accessor:
362 """An accessor implements a particular (system-specific or not) way of
363 accessing paths on the filesystem."""
364
365
366class _NormalAccessor(_Accessor):
367
368 def _wrap_strfunc(strfunc):
369 @functools.wraps(strfunc)
370 def wrapped(pathobj, *args):
371 return strfunc(str(pathobj), *args)
372 return staticmethod(wrapped)
373
374 def _wrap_binary_strfunc(strfunc):
375 @functools.wraps(strfunc)
376 def wrapped(pathobjA, pathobjB, *args):
377 return strfunc(str(pathobjA), str(pathobjB), *args)
378 return staticmethod(wrapped)
379
380 stat = _wrap_strfunc(os.stat)
381
382 lstat = _wrap_strfunc(os.lstat)
383
384 open = _wrap_strfunc(os.open)
385
386 listdir = _wrap_strfunc(os.listdir)
387
388 chmod = _wrap_strfunc(os.chmod)
389
390 if hasattr(os, "lchmod"):
391 lchmod = _wrap_strfunc(os.lchmod)
392 else:
393 def lchmod(self, pathobj, mode):
394 raise NotImplementedError("lchmod() not available on this system")
395
396 mkdir = _wrap_strfunc(os.mkdir)
397
398 unlink = _wrap_strfunc(os.unlink)
399
400 rmdir = _wrap_strfunc(os.rmdir)
401
402 rename = _wrap_binary_strfunc(os.rename)
403
404 replace = _wrap_binary_strfunc(os.replace)
405
406 if nt:
407 if supports_symlinks:
408 symlink = _wrap_binary_strfunc(os.symlink)
409 else:
410 def symlink(a, b, target_is_directory):
411 raise NotImplementedError("symlink() not available on this system")
412 else:
413 # Under POSIX, os.symlink() takes two args
414 @staticmethod
415 def symlink(a, b, target_is_directory):
416 return os.symlink(str(a), str(b))
417
418 utime = _wrap_strfunc(os.utime)
419
420 # Helper for resolve()
421 def readlink(self, path):
422 return os.readlink(path)
423
424
425_normal_accessor = _NormalAccessor()
426
427
428#
429# Globbing helpers
430#
431
432@contextmanager
433def _cached(func):
434 try:
435 func.__cached__
436 yield func
437 except AttributeError:
438 cache = {}
439 def wrapper(*args):
440 try:
441 return cache[args]
442 except KeyError:
443 value = cache[args] = func(*args)
444 return value
445 wrapper.__cached__ = True
446 try:
447 yield wrapper
448 finally:
449 cache.clear()
450
451def _make_selector(pattern_parts):
452 pat = pattern_parts[0]
453 child_parts = pattern_parts[1:]
454 if pat == '**':
455 cls = _RecursiveWildcardSelector
456 elif '**' in pat:
457 raise ValueError("Invalid pattern: '**' can only be an entire path component")
458 elif _is_wildcard_pattern(pat):
459 cls = _WildcardSelector
460 else:
461 cls = _PreciseSelector
462 return cls(pat, child_parts)
463
464if hasattr(functools, "lru_cache"):
465 _make_selector = functools.lru_cache()(_make_selector)
466
467
468class _Selector:
469 """A selector matches a specific glob pattern part against the children
470 of a given path."""
471
472 def __init__(self, child_parts):
473 self.child_parts = child_parts
474 if child_parts:
475 self.successor = _make_selector(child_parts)
476 else:
477 self.successor = _TerminatingSelector()
478
479 def select_from(self, parent_path):
480 """Iterate over all child paths of `parent_path` matched by this
481 selector. This can contain parent_path itself."""
482 path_cls = type(parent_path)
483 is_dir = path_cls.is_dir
484 exists = path_cls.exists
485 listdir = parent_path._accessor.listdir
486 return self._select_from(parent_path, is_dir, exists, listdir)
487
488
489class _TerminatingSelector:
490
491 def _select_from(self, parent_path, is_dir, exists, listdir):
492 yield parent_path
493
494
495class _PreciseSelector(_Selector):
496
497 def __init__(self, name, child_parts):
498 self.name = name
499 _Selector.__init__(self, child_parts)
500
501 def _select_from(self, parent_path, is_dir, exists, listdir):
Guido van Rossum6c2d33a2016-01-06 09:42:07 -0800502 try:
503 if not is_dir(parent_path):
504 return
505 path = parent_path._make_child_relpath(self.name)
506 if exists(path):
507 for p in self.successor._select_from(path, is_dir, exists, listdir):
508 yield p
509 except PermissionError:
Antoine Pitrou31119e42013-11-22 17:38:12 +0100510 return
Antoine Pitrou31119e42013-11-22 17:38:12 +0100511
512
513class _WildcardSelector(_Selector):
514
515 def __init__(self, pat, child_parts):
516 self.pat = re.compile(fnmatch.translate(pat))
517 _Selector.__init__(self, child_parts)
518
519 def _select_from(self, parent_path, is_dir, exists, listdir):
Guido van Rossum6c2d33a2016-01-06 09:42:07 -0800520 try:
521 if not is_dir(parent_path):
522 return
523 cf = parent_path._flavour.casefold
524 for name in listdir(parent_path):
525 casefolded = cf(name)
526 if self.pat.match(casefolded):
527 path = parent_path._make_child_relpath(name)
528 for p in self.successor._select_from(path, is_dir, exists, listdir):
529 yield p
530 except PermissionError:
Antoine Pitrou31119e42013-11-22 17:38:12 +0100531 return
Guido van Rossum6c2d33a2016-01-06 09:42:07 -0800532
Antoine Pitrou31119e42013-11-22 17:38:12 +0100533
534
535class _RecursiveWildcardSelector(_Selector):
536
537 def __init__(self, pat, child_parts):
538 _Selector.__init__(self, child_parts)
539
540 def _iterate_directories(self, parent_path, is_dir, listdir):
541 yield parent_path
Guido van Rossumbc9fdda2016-01-07 10:56:36 -0800542 try:
543 for name in listdir(parent_path):
544 path = parent_path._make_child_relpath(name)
545 if is_dir(path) and not path.is_symlink():
546 for p in self._iterate_directories(path, is_dir, listdir):
547 yield p
548 except PermissionError:
549 return
Antoine Pitrou31119e42013-11-22 17:38:12 +0100550
551 def _select_from(self, parent_path, is_dir, exists, listdir):
Guido van Rossum6c2d33a2016-01-06 09:42:07 -0800552 try:
553 if not is_dir(parent_path):
554 return
555 with _cached(listdir) as listdir:
556 yielded = set()
557 try:
558 successor_select = self.successor._select_from
559 for starting_point in self._iterate_directories(parent_path, is_dir, listdir):
560 for p in successor_select(starting_point, is_dir, exists, listdir):
561 if p not in yielded:
562 yield p
563 yielded.add(p)
564 finally:
565 yielded.clear()
566 except PermissionError:
Antoine Pitrou31119e42013-11-22 17:38:12 +0100567 return
Antoine Pitrou31119e42013-11-22 17:38:12 +0100568
569
570#
571# Public API
572#
573
574class _PathParents(Sequence):
575 """This object provides sequence-like access to the logical ancestors
576 of a path. Don't try to construct it yourself."""
577 __slots__ = ('_pathcls', '_drv', '_root', '_parts')
578
579 def __init__(self, path):
580 # We don't store the instance to avoid reference cycles
581 self._pathcls = type(path)
582 self._drv = path._drv
583 self._root = path._root
584 self._parts = path._parts
585
586 def __len__(self):
587 if self._drv or self._root:
588 return len(self._parts) - 1
589 else:
590 return len(self._parts)
591
592 def __getitem__(self, idx):
593 if idx < 0 or idx >= len(self):
594 raise IndexError(idx)
595 return self._pathcls._from_parsed_parts(self._drv, self._root,
596 self._parts[:-idx - 1])
597
598 def __repr__(self):
599 return "<{}.parents>".format(self._pathcls.__name__)
600
601
602class PurePath(object):
603 """PurePath represents a filesystem path and offers operations which
604 don't imply any actual filesystem I/O. Depending on your system,
605 instantiating a PurePath will return either a PurePosixPath or a
606 PureWindowsPath object. You can also instantiate either of these classes
607 directly, regardless of your system.
608 """
609 __slots__ = (
610 '_drv', '_root', '_parts',
611 '_str', '_hash', '_pparts', '_cached_cparts',
612 )
613
614 def __new__(cls, *args):
615 """Construct a PurePath from one or several strings and or existing
616 PurePath objects. The strings and path objects are combined so as
617 to yield a canonicalized path, which is incorporated into the
618 new PurePath object.
619 """
620 if cls is PurePath:
621 cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
622 return cls._from_parts(args)
623
624 def __reduce__(self):
625 # Using the parts tuple helps share interned path parts
626 # when pickling related paths.
627 return (self.__class__, tuple(self._parts))
628
629 @classmethod
630 def _parse_args(cls, args):
631 # This is useful when you don't want to create an instance, just
632 # canonicalize some constructor arguments.
633 parts = []
634 for a in args:
635 if isinstance(a, PurePath):
636 parts += a._parts
637 elif isinstance(a, str):
Antoine Pitroucb5ec772014-04-23 00:34:15 +0200638 # Force-cast str subclasses to str (issue #21127)
639 parts.append(str(a))
Antoine Pitrou31119e42013-11-22 17:38:12 +0100640 else:
641 raise TypeError(
642 "argument should be a path or str object, not %r"
643 % type(a))
644 return cls._flavour.parse_parts(parts)
645
646 @classmethod
647 def _from_parts(cls, args, init=True):
648 # We need to call _parse_args on the instance, so as to get the
649 # right flavour.
650 self = object.__new__(cls)
651 drv, root, parts = self._parse_args(args)
652 self._drv = drv
653 self._root = root
654 self._parts = parts
655 if init:
656 self._init()
657 return self
658
659 @classmethod
660 def _from_parsed_parts(cls, drv, root, parts, init=True):
661 self = object.__new__(cls)
662 self._drv = drv
663 self._root = root
664 self._parts = parts
665 if init:
666 self._init()
667 return self
668
669 @classmethod
670 def _format_parsed_parts(cls, drv, root, parts):
671 if drv or root:
672 return drv + root + cls._flavour.join(parts[1:])
673 else:
674 return cls._flavour.join(parts)
675
676 def _init(self):
Martin Pantere26da7c2016-06-02 10:07:09 +0000677 # Overridden in concrete Path
Antoine Pitrou31119e42013-11-22 17:38:12 +0100678 pass
679
680 def _make_child(self, args):
681 drv, root, parts = self._parse_args(args)
682 drv, root, parts = self._flavour.join_parsed_parts(
683 self._drv, self._root, self._parts, drv, root, parts)
684 return self._from_parsed_parts(drv, root, parts)
685
686 def __str__(self):
687 """Return the string representation of the path, suitable for
688 passing to system calls."""
689 try:
690 return self._str
691 except AttributeError:
692 self._str = self._format_parsed_parts(self._drv, self._root,
693 self._parts) or '.'
694 return self._str
695
696 def as_posix(self):
697 """Return the string representation of the path with forward (/)
698 slashes."""
699 f = self._flavour
700 return str(self).replace(f.sep, '/')
701
702 def __bytes__(self):
703 """Return the bytes representation of the path. This is only
704 recommended to use under Unix."""
705 return os.fsencode(str(self))
706
707 def __repr__(self):
708 return "{}({!r})".format(self.__class__.__name__, self.as_posix())
709
710 def as_uri(self):
711 """Return the path as a 'file' URI."""
712 if not self.is_absolute():
713 raise ValueError("relative path can't be expressed as a file URI")
714 return self._flavour.make_uri(self)
715
716 @property
717 def _cparts(self):
718 # Cached casefolded parts, for hashing and comparison
719 try:
720 return self._cached_cparts
721 except AttributeError:
722 self._cached_cparts = self._flavour.casefold_parts(self._parts)
723 return self._cached_cparts
724
725 def __eq__(self, other):
726 if not isinstance(other, PurePath):
727 return NotImplemented
728 return self._cparts == other._cparts and self._flavour is other._flavour
729
Antoine Pitrou31119e42013-11-22 17:38:12 +0100730 def __hash__(self):
731 try:
732 return self._hash
733 except AttributeError:
734 self._hash = hash(tuple(self._cparts))
735 return self._hash
736
737 def __lt__(self, other):
738 if not isinstance(other, PurePath) or self._flavour is not other._flavour:
739 return NotImplemented
740 return self._cparts < other._cparts
741
742 def __le__(self, other):
743 if not isinstance(other, PurePath) or self._flavour is not other._flavour:
744 return NotImplemented
745 return self._cparts <= other._cparts
746
747 def __gt__(self, other):
748 if not isinstance(other, PurePath) or self._flavour is not other._flavour:
749 return NotImplemented
750 return self._cparts > other._cparts
751
752 def __ge__(self, other):
753 if not isinstance(other, PurePath) or self._flavour is not other._flavour:
754 return NotImplemented
755 return self._cparts >= other._cparts
756
757 drive = property(attrgetter('_drv'),
758 doc="""The drive prefix (letter or UNC path), if any.""")
759
760 root = property(attrgetter('_root'),
761 doc="""The root of the path, if any.""")
762
763 @property
764 def anchor(self):
765 """The concatenation of the drive and root, or ''."""
766 anchor = self._drv + self._root
767 return anchor
768
769 @property
770 def name(self):
771 """The final path component, if any."""
772 parts = self._parts
773 if len(parts) == (1 if (self._drv or self._root) else 0):
774 return ''
775 return parts[-1]
776
777 @property
778 def suffix(self):
779 """The final component's last suffix, if any."""
780 name = self.name
781 i = name.rfind('.')
782 if 0 < i < len(name) - 1:
783 return name[i:]
784 else:
785 return ''
786
787 @property
788 def suffixes(self):
789 """A list of the final component's suffixes, if any."""
790 name = self.name
791 if name.endswith('.'):
792 return []
793 name = name.lstrip('.')
794 return ['.' + suffix for suffix in name.split('.')[1:]]
795
796 @property
797 def stem(self):
798 """The final path component, minus its last suffix."""
799 name = self.name
800 i = name.rfind('.')
801 if 0 < i < len(name) - 1:
802 return name[:i]
803 else:
804 return name
805
806 def with_name(self, name):
807 """Return a new path with the file name changed."""
808 if not self.name:
809 raise ValueError("%r has an empty name" % (self,))
Antoine Pitrou7084e732014-07-06 21:31:12 -0400810 drv, root, parts = self._flavour.parse_parts((name,))
811 if (not name or name[-1] in [self._flavour.sep, self._flavour.altsep]
812 or drv or root or len(parts) != 1):
813 raise ValueError("Invalid name %r" % (name))
Antoine Pitrou31119e42013-11-22 17:38:12 +0100814 return self._from_parsed_parts(self._drv, self._root,
815 self._parts[:-1] + [name])
816
817 def with_suffix(self, suffix):
818 """Return a new path with the file suffix changed (or added, if none)."""
819 # XXX if suffix is None, should the current suffix be removed?
Antoine Pitroue50dafc2014-07-06 21:37:15 -0400820 f = self._flavour
821 if f.sep in suffix or f.altsep and f.altsep in suffix:
Antoine Pitrou1b02da92014-01-03 00:07:17 +0100822 raise ValueError("Invalid suffix %r" % (suffix))
Antoine Pitroue50dafc2014-07-06 21:37:15 -0400823 if suffix and not suffix.startswith('.') or suffix == '.':
Antoine Pitrou1b02da92014-01-03 00:07:17 +0100824 raise ValueError("Invalid suffix %r" % (suffix))
Antoine Pitrou31119e42013-11-22 17:38:12 +0100825 name = self.name
826 if not name:
827 raise ValueError("%r has an empty name" % (self,))
828 old_suffix = self.suffix
829 if not old_suffix:
830 name = name + suffix
831 else:
832 name = name[:-len(old_suffix)] + suffix
833 return self._from_parsed_parts(self._drv, self._root,
834 self._parts[:-1] + [name])
835
836 def relative_to(self, *other):
837 """Return the relative path to another path identified by the passed
838 arguments. If the operation is not possible (because this is not
839 a subpath of the other path), raise ValueError.
840 """
841 # For the purpose of this method, drive and root are considered
842 # separate parts, i.e.:
843 # Path('c:/').relative_to('c:') gives Path('/')
844 # Path('c:/').relative_to('/') raise ValueError
845 if not other:
846 raise TypeError("need at least one argument")
847 parts = self._parts
848 drv = self._drv
849 root = self._root
Antoine Pitrou156b3612013-12-28 19:49:04 +0100850 if root:
851 abs_parts = [drv, root] + parts[1:]
Antoine Pitrou31119e42013-11-22 17:38:12 +0100852 else:
853 abs_parts = parts
854 to_drv, to_root, to_parts = self._parse_args(other)
Antoine Pitrou156b3612013-12-28 19:49:04 +0100855 if to_root:
856 to_abs_parts = [to_drv, to_root] + to_parts[1:]
Antoine Pitrou31119e42013-11-22 17:38:12 +0100857 else:
858 to_abs_parts = to_parts
859 n = len(to_abs_parts)
Antoine Pitrou156b3612013-12-28 19:49:04 +0100860 cf = self._flavour.casefold_parts
861 if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):
Antoine Pitrou31119e42013-11-22 17:38:12 +0100862 formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
863 raise ValueError("{!r} does not start with {!r}"
864 .format(str(self), str(formatted)))
Antoine Pitrou156b3612013-12-28 19:49:04 +0100865 return self._from_parsed_parts('', root if n == 1 else '',
866 abs_parts[n:])
Antoine Pitrou31119e42013-11-22 17:38:12 +0100867
868 @property
869 def parts(self):
870 """An object providing sequence-like access to the
871 components in the filesystem path."""
872 # We cache the tuple to avoid building a new one each time .parts
873 # is accessed. XXX is this necessary?
874 try:
875 return self._pparts
876 except AttributeError:
877 self._pparts = tuple(self._parts)
878 return self._pparts
879
880 def joinpath(self, *args):
881 """Combine this path with one or several arguments, and return a
882 new path representing either a subpath (if all arguments are relative
883 paths) or a totally different path (if one of the arguments is
884 anchored).
885 """
886 return self._make_child(args)
887
888 def __truediv__(self, key):
889 return self._make_child((key,))
890
891 def __rtruediv__(self, key):
892 return self._from_parts([key] + self._parts)
893
894 @property
895 def parent(self):
896 """The logical parent of the path."""
897 drv = self._drv
898 root = self._root
899 parts = self._parts
900 if len(parts) == 1 and (drv or root):
901 return self
902 return self._from_parsed_parts(drv, root, parts[:-1])
903
904 @property
905 def parents(self):
906 """A sequence of this path's logical parents."""
907 return _PathParents(self)
908
909 def is_absolute(self):
910 """True if the path is absolute (has both a root and, if applicable,
911 a drive)."""
912 if not self._root:
913 return False
914 return not self._flavour.has_drv or bool(self._drv)
915
916 def is_reserved(self):
917 """Return True if the path contains one of the special names reserved
918 by the system, if any."""
919 return self._flavour.is_reserved(self._parts)
920
921 def match(self, path_pattern):
922 """
923 Return True if this path matches the given pattern.
924 """
925 cf = self._flavour.casefold
926 path_pattern = cf(path_pattern)
927 drv, root, pat_parts = self._flavour.parse_parts((path_pattern,))
928 if not pat_parts:
929 raise ValueError("empty pattern")
930 if drv and drv != cf(self._drv):
931 return False
932 if root and root != cf(self._root):
933 return False
934 parts = self._cparts
935 if drv or root:
936 if len(pat_parts) != len(parts):
937 return False
938 pat_parts = pat_parts[1:]
939 elif len(pat_parts) > len(parts):
940 return False
941 for part, pat in zip(reversed(parts), reversed(pat_parts)):
942 if not fnmatch.fnmatchcase(part, pat):
943 return False
944 return True
945
946
947class PurePosixPath(PurePath):
948 _flavour = _posix_flavour
949 __slots__ = ()
950
951
952class PureWindowsPath(PurePath):
953 _flavour = _windows_flavour
954 __slots__ = ()
955
956
957# Filesystem-accessing classes
958
959
960class Path(PurePath):
961 __slots__ = (
962 '_accessor',
963 '_closed',
964 )
965
966 def __new__(cls, *args, **kwargs):
967 if cls is Path:
968 cls = WindowsPath if os.name == 'nt' else PosixPath
969 self = cls._from_parts(args, init=False)
970 if not self._flavour.is_supported:
971 raise NotImplementedError("cannot instantiate %r on your system"
972 % (cls.__name__,))
973 self._init()
974 return self
975
976 def _init(self,
977 # Private non-constructor arguments
978 template=None,
979 ):
980 self._closed = False
981 if template is not None:
982 self._accessor = template._accessor
983 else:
984 self._accessor = _normal_accessor
985
986 def _make_child_relpath(self, part):
987 # This is an optimization used for dir walking. `part` must be
988 # a single part relative to this path.
989 parts = self._parts + [part]
990 return self._from_parsed_parts(self._drv, self._root, parts)
991
992 def __enter__(self):
993 if self._closed:
994 self._raise_closed()
995 return self
996
997 def __exit__(self, t, v, tb):
998 self._closed = True
999
1000 def _raise_closed(self):
1001 raise ValueError("I/O operation on closed path")
1002
1003 def _opener(self, name, flags, mode=0o666):
1004 # A stub for the opener argument to built-in open()
1005 return self._accessor.open(self, flags, mode)
1006
Antoine Pitrou4a60d422013-12-02 21:25:18 +01001007 def _raw_open(self, flags, mode=0o777):
1008 """
1009 Open the file pointed by this path and return a file descriptor,
1010 as os.open() does.
1011 """
1012 if self._closed:
1013 self._raise_closed()
1014 return self._accessor.open(self, flags, mode)
1015
Antoine Pitrou31119e42013-11-22 17:38:12 +01001016 # Public API
1017
1018 @classmethod
1019 def cwd(cls):
1020 """Return a new path pointing to the current working directory
1021 (as returned by os.getcwd()).
1022 """
1023 return cls(os.getcwd())
1024
Antoine Pitrou17cba7d2015-01-12 21:03:41 +01001025 @classmethod
1026 def home(cls):
1027 """Return a new path pointing to the user's home directory (as
1028 returned by os.path.expanduser('~')).
1029 """
1030 return cls(cls()._flavour.gethomedir(None))
1031
Antoine Pitrou43e3d942014-05-13 10:50:15 +02001032 def samefile(self, other_path):
Berker Peksag05492b82015-10-22 03:34:16 +03001033 """Return whether other_path is the same or not as this file
Berker Peksag267597f2015-10-21 20:10:24 +03001034 (as returned by os.path.samefile()).
Antoine Pitrou43e3d942014-05-13 10:50:15 +02001035 """
1036 st = self.stat()
1037 try:
1038 other_st = other_path.stat()
1039 except AttributeError:
1040 other_st = os.stat(other_path)
1041 return os.path.samestat(st, other_st)
1042
Antoine Pitrou31119e42013-11-22 17:38:12 +01001043 def iterdir(self):
1044 """Iterate over the files in this directory. Does not yield any
1045 result for the special paths '.' and '..'.
1046 """
1047 if self._closed:
1048 self._raise_closed()
1049 for name in self._accessor.listdir(self):
1050 if name in {'.', '..'}:
1051 # Yielding a path object for these makes little sense
1052 continue
1053 yield self._make_child_relpath(name)
1054 if self._closed:
1055 self._raise_closed()
1056
1057 def glob(self, pattern):
1058 """Iterate over this subtree and yield all existing files (of any
1059 kind, including directories) matching the given pattern.
1060 """
Berker Peksag4a208e42016-01-30 17:50:48 +02001061 if not pattern:
1062 raise ValueError("Unacceptable pattern: {!r}".format(pattern))
Antoine Pitrou31119e42013-11-22 17:38:12 +01001063 pattern = self._flavour.casefold(pattern)
1064 drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
1065 if drv or root:
1066 raise NotImplementedError("Non-relative patterns are unsupported")
1067 selector = _make_selector(tuple(pattern_parts))
1068 for p in selector.select_from(self):
1069 yield p
1070
1071 def rglob(self, pattern):
1072 """Recursively yield all existing files (of any kind, including
1073 directories) matching the given pattern, anywhere in this subtree.
1074 """
1075 pattern = self._flavour.casefold(pattern)
1076 drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
1077 if drv or root:
1078 raise NotImplementedError("Non-relative patterns are unsupported")
1079 selector = _make_selector(("**",) + tuple(pattern_parts))
1080 for p in selector.select_from(self):
1081 yield p
1082
1083 def absolute(self):
1084 """Return an absolute version of this path. This function works
1085 even if the path doesn't point to anything.
1086
1087 No normalization is done, i.e. all '.' and '..' will be kept along.
1088 Use resolve() to get the canonical path to a file.
1089 """
1090 # XXX untested yet!
1091 if self._closed:
1092 self._raise_closed()
1093 if self.is_absolute():
1094 return self
1095 # FIXME this must defer to the specific flavour (and, under Windows,
1096 # use nt._getfullpathname())
1097 obj = self._from_parts([os.getcwd()] + self._parts, init=False)
1098 obj._init(template=self)
1099 return obj
1100
1101 def resolve(self):
1102 """
1103 Make the path absolute, resolving all symlinks on the way and also
1104 normalizing it (for example turning slashes into backslashes under
1105 Windows).
1106 """
1107 if self._closed:
1108 self._raise_closed()
1109 s = self._flavour.resolve(self)
1110 if s is None:
1111 # No symlink resolution => for consistency, raise an error if
1112 # the path doesn't exist or is forbidden
1113 self.stat()
1114 s = str(self.absolute())
1115 # Now we have no symlinks in the path, it's safe to normalize it.
1116 normed = self._flavour.pathmod.normpath(s)
1117 obj = self._from_parts((normed,), init=False)
1118 obj._init(template=self)
1119 return obj
1120
1121 def stat(self):
1122 """
1123 Return the result of the stat() system call on this path, like
1124 os.stat() does.
1125 """
1126 return self._accessor.stat(self)
1127
1128 def owner(self):
1129 """
1130 Return the login name of the file owner.
1131 """
1132 import pwd
1133 return pwd.getpwuid(self.stat().st_uid).pw_name
1134
1135 def group(self):
1136 """
1137 Return the group name of the file gid.
1138 """
1139 import grp
1140 return grp.getgrgid(self.stat().st_gid).gr_name
1141
Antoine Pitrou31119e42013-11-22 17:38:12 +01001142 def open(self, mode='r', buffering=-1, encoding=None,
1143 errors=None, newline=None):
1144 """
1145 Open the file pointed by this path and return a file object, as
1146 the built-in open() function does.
1147 """
1148 if self._closed:
1149 self._raise_closed()
1150 return io.open(str(self), mode, buffering, encoding, errors, newline,
1151 opener=self._opener)
1152
Georg Brandlea683982014-10-01 19:12:33 +02001153 def read_bytes(self):
1154 """
1155 Open the file in bytes mode, read it, and close the file.
1156 """
1157 with self.open(mode='rb') as f:
1158 return f.read()
1159
1160 def read_text(self, encoding=None, errors=None):
1161 """
1162 Open the file in text mode, read it, and close the file.
1163 """
1164 with self.open(mode='r', encoding=encoding, errors=errors) as f:
1165 return f.read()
1166
1167 def write_bytes(self, data):
1168 """
1169 Open the file in bytes mode, write to it, and close the file.
1170 """
1171 # type-check for the buffer interface before truncating the file
1172 view = memoryview(data)
1173 with self.open(mode='wb') as f:
1174 return f.write(view)
1175
1176 def write_text(self, data, encoding=None, errors=None):
1177 """
1178 Open the file in text mode, write to it, and close the file.
1179 """
1180 if not isinstance(data, str):
1181 raise TypeError('data must be str, not %s' %
1182 data.__class__.__name__)
1183 with self.open(mode='w', encoding=encoding, errors=errors) as f:
1184 return f.write(data)
1185
Antoine Pitrou31119e42013-11-22 17:38:12 +01001186 def touch(self, mode=0o666, exist_ok=True):
1187 """
1188 Create this file with the given access mode, if it doesn't exist.
1189 """
1190 if self._closed:
1191 self._raise_closed()
1192 if exist_ok:
1193 # First try to bump modification time
1194 # Implementation note: GNU touch uses the UTIME_NOW option of
1195 # the utimensat() / futimens() functions.
Antoine Pitrou31119e42013-11-22 17:38:12 +01001196 try:
Antoine Pitrou2cf39172013-11-23 15:25:59 +01001197 self._accessor.utime(self, None)
Antoine Pitrou31119e42013-11-22 17:38:12 +01001198 except OSError:
1199 # Avoid exception chaining
1200 pass
1201 else:
1202 return
1203 flags = os.O_CREAT | os.O_WRONLY
1204 if not exist_ok:
1205 flags |= os.O_EXCL
1206 fd = self._raw_open(flags, mode)
1207 os.close(fd)
1208
Barry Warsaw7c549c42014-08-05 11:28:12 -04001209 def mkdir(self, mode=0o777, parents=False, exist_ok=False):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001210 if self._closed:
1211 self._raise_closed()
1212 if not parents:
Barry Warsaw7c549c42014-08-05 11:28:12 -04001213 try:
1214 self._accessor.mkdir(self, mode)
1215 except FileExistsError:
1216 if not exist_ok or not self.is_dir():
1217 raise
Antoine Pitrou31119e42013-11-22 17:38:12 +01001218 else:
1219 try:
1220 self._accessor.mkdir(self, mode)
Barry Warsaw7c549c42014-08-05 11:28:12 -04001221 except FileExistsError:
1222 if not exist_ok or not self.is_dir():
1223 raise
Antoine Pitrou31119e42013-11-22 17:38:12 +01001224 except OSError as e:
1225 if e.errno != ENOENT:
1226 raise
Antoine Pitrou0048c982013-12-16 20:22:37 +01001227 self.parent.mkdir(parents=True)
Antoine Pitrou31119e42013-11-22 17:38:12 +01001228 self._accessor.mkdir(self, mode)
1229
1230 def chmod(self, mode):
1231 """
1232 Change the permissions of the path, like os.chmod().
1233 """
1234 if self._closed:
1235 self._raise_closed()
1236 self._accessor.chmod(self, mode)
1237
1238 def lchmod(self, mode):
1239 """
1240 Like chmod(), except if the path points to a symlink, the symlink's
1241 permissions are changed, rather than its target's.
1242 """
1243 if self._closed:
1244 self._raise_closed()
1245 self._accessor.lchmod(self, mode)
1246
1247 def unlink(self):
1248 """
1249 Remove this file or link.
1250 If the path is a directory, use rmdir() instead.
1251 """
1252 if self._closed:
1253 self._raise_closed()
1254 self._accessor.unlink(self)
1255
1256 def rmdir(self):
1257 """
1258 Remove this directory. The directory must be empty.
1259 """
1260 if self._closed:
1261 self._raise_closed()
1262 self._accessor.rmdir(self)
1263
1264 def lstat(self):
1265 """
1266 Like stat(), except if the path points to a symlink, the symlink's
1267 status information is returned, rather than its target's.
1268 """
1269 if self._closed:
1270 self._raise_closed()
1271 return self._accessor.lstat(self)
1272
1273 def rename(self, target):
1274 """
1275 Rename this path to the given path.
1276 """
1277 if self._closed:
1278 self._raise_closed()
1279 self._accessor.rename(self, target)
1280
1281 def replace(self, target):
1282 """
1283 Rename this path to the given path, clobbering the existing
1284 destination if it exists.
1285 """
1286 if self._closed:
1287 self._raise_closed()
1288 self._accessor.replace(self, target)
1289
1290 def symlink_to(self, target, target_is_directory=False):
1291 """
1292 Make this path a symlink pointing to the given path.
1293 Note the order of arguments (self, target) is the reverse of os.symlink's.
1294 """
1295 if self._closed:
1296 self._raise_closed()
1297 self._accessor.symlink(target, self, target_is_directory)
1298
1299 # Convenience functions for querying the stat results
1300
1301 def exists(self):
1302 """
1303 Whether this path exists.
1304 """
1305 try:
1306 self.stat()
1307 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001308 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001309 raise
1310 return False
1311 return True
1312
1313 def is_dir(self):
1314 """
1315 Whether this path is a directory.
1316 """
1317 try:
1318 return S_ISDIR(self.stat().st_mode)
1319 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001320 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001321 raise
1322 # Path doesn't exist or is a broken symlink
1323 # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1324 return False
1325
1326 def is_file(self):
1327 """
1328 Whether this path is a regular file (also True for symlinks pointing
1329 to regular files).
1330 """
1331 try:
1332 return S_ISREG(self.stat().st_mode)
1333 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001334 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001335 raise
1336 # Path doesn't exist or is a broken symlink
1337 # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1338 return False
1339
1340 def is_symlink(self):
1341 """
1342 Whether this path is a symbolic link.
1343 """
1344 try:
1345 return S_ISLNK(self.lstat().st_mode)
1346 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001347 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001348 raise
1349 # Path doesn't exist
1350 return False
1351
1352 def is_block_device(self):
1353 """
1354 Whether this path is a block device.
1355 """
1356 try:
1357 return S_ISBLK(self.stat().st_mode)
1358 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001359 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001360 raise
1361 # Path doesn't exist or is a broken symlink
1362 # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1363 return False
1364
1365 def is_char_device(self):
1366 """
1367 Whether this path is a character device.
1368 """
1369 try:
1370 return S_ISCHR(self.stat().st_mode)
1371 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001372 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001373 raise
1374 # Path doesn't exist or is a broken symlink
1375 # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1376 return False
1377
1378 def is_fifo(self):
1379 """
1380 Whether this path is a FIFO.
1381 """
1382 try:
1383 return S_ISFIFO(self.stat().st_mode)
1384 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001385 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001386 raise
1387 # Path doesn't exist or is a broken symlink
1388 # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1389 return False
1390
1391 def is_socket(self):
1392 """
1393 Whether this path is a socket.
1394 """
1395 try:
1396 return S_ISSOCK(self.stat().st_mode)
1397 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001398 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001399 raise
1400 # Path doesn't exist or is a broken symlink
1401 # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1402 return False
1403
Antoine Pitrou8477ed62014-12-30 20:54:45 +01001404 def expanduser(self):
1405 """ Return a new path with expanded ~ and ~user constructs
1406 (as returned by os.path.expanduser)
1407 """
1408 if (not (self._drv or self._root) and
1409 self._parts and self._parts[0][:1] == '~'):
1410 homedir = self._flavour.gethomedir(self._parts[0][1:])
1411 return self._from_parts([homedir] + self._parts[1:])
1412
1413 return self
1414
Antoine Pitrou31119e42013-11-22 17:38:12 +01001415
1416class PosixPath(Path, PurePosixPath):
1417 __slots__ = ()
1418
1419class WindowsPath(Path, PureWindowsPath):
1420 __slots__ = ()
Berker Peksag04d42292016-03-11 23:07:27 +02001421
1422 def owner(self):
1423 raise NotImplementedError("Path.owner() is unsupported on this system")
1424
1425 def group(self):
1426 raise NotImplementedError("Path.group() is unsupported on this system")