blob: ed31ddb8dd8c2dc19152d78a7b26a018f7d89fe7 [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):
677 # Overriden in concrete Path
678 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
Guido van Rossume4282312016-01-06 11:01:42 -0800696 @property
697 def path(self):
698 try:
699 return self._str
700 except AttributeError:
701 return str(self)
702
Antoine Pitrou31119e42013-11-22 17:38:12 +0100703 def as_posix(self):
704 """Return the string representation of the path with forward (/)
705 slashes."""
706 f = self._flavour
707 return str(self).replace(f.sep, '/')
708
709 def __bytes__(self):
710 """Return the bytes representation of the path. This is only
711 recommended to use under Unix."""
712 return os.fsencode(str(self))
713
714 def __repr__(self):
715 return "{}({!r})".format(self.__class__.__name__, self.as_posix())
716
717 def as_uri(self):
718 """Return the path as a 'file' URI."""
719 if not self.is_absolute():
720 raise ValueError("relative path can't be expressed as a file URI")
721 return self._flavour.make_uri(self)
722
723 @property
724 def _cparts(self):
725 # Cached casefolded parts, for hashing and comparison
726 try:
727 return self._cached_cparts
728 except AttributeError:
729 self._cached_cparts = self._flavour.casefold_parts(self._parts)
730 return self._cached_cparts
731
732 def __eq__(self, other):
733 if not isinstance(other, PurePath):
734 return NotImplemented
735 return self._cparts == other._cparts and self._flavour is other._flavour
736
Antoine Pitrou31119e42013-11-22 17:38:12 +0100737 def __hash__(self):
738 try:
739 return self._hash
740 except AttributeError:
741 self._hash = hash(tuple(self._cparts))
742 return self._hash
743
744 def __lt__(self, other):
745 if not isinstance(other, PurePath) or self._flavour is not other._flavour:
746 return NotImplemented
747 return self._cparts < other._cparts
748
749 def __le__(self, other):
750 if not isinstance(other, PurePath) or self._flavour is not other._flavour:
751 return NotImplemented
752 return self._cparts <= other._cparts
753
754 def __gt__(self, other):
755 if not isinstance(other, PurePath) or self._flavour is not other._flavour:
756 return NotImplemented
757 return self._cparts > other._cparts
758
759 def __ge__(self, other):
760 if not isinstance(other, PurePath) or self._flavour is not other._flavour:
761 return NotImplemented
762 return self._cparts >= other._cparts
763
764 drive = property(attrgetter('_drv'),
765 doc="""The drive prefix (letter or UNC path), if any.""")
766
767 root = property(attrgetter('_root'),
768 doc="""The root of the path, if any.""")
769
770 @property
771 def anchor(self):
772 """The concatenation of the drive and root, or ''."""
773 anchor = self._drv + self._root
774 return anchor
775
776 @property
777 def name(self):
778 """The final path component, if any."""
779 parts = self._parts
780 if len(parts) == (1 if (self._drv or self._root) else 0):
781 return ''
782 return parts[-1]
783
784 @property
785 def suffix(self):
786 """The final component's last suffix, if any."""
787 name = self.name
788 i = name.rfind('.')
789 if 0 < i < len(name) - 1:
790 return name[i:]
791 else:
792 return ''
793
794 @property
795 def suffixes(self):
796 """A list of the final component's suffixes, if any."""
797 name = self.name
798 if name.endswith('.'):
799 return []
800 name = name.lstrip('.')
801 return ['.' + suffix for suffix in name.split('.')[1:]]
802
803 @property
804 def stem(self):
805 """The final path component, minus its last suffix."""
806 name = self.name
807 i = name.rfind('.')
808 if 0 < i < len(name) - 1:
809 return name[:i]
810 else:
811 return name
812
813 def with_name(self, name):
814 """Return a new path with the file name changed."""
815 if not self.name:
816 raise ValueError("%r has an empty name" % (self,))
Antoine Pitrou7084e732014-07-06 21:31:12 -0400817 drv, root, parts = self._flavour.parse_parts((name,))
818 if (not name or name[-1] in [self._flavour.sep, self._flavour.altsep]
819 or drv or root or len(parts) != 1):
820 raise ValueError("Invalid name %r" % (name))
Antoine Pitrou31119e42013-11-22 17:38:12 +0100821 return self._from_parsed_parts(self._drv, self._root,
822 self._parts[:-1] + [name])
823
824 def with_suffix(self, suffix):
825 """Return a new path with the file suffix changed (or added, if none)."""
826 # XXX if suffix is None, should the current suffix be removed?
Antoine Pitroue50dafc2014-07-06 21:37:15 -0400827 f = self._flavour
828 if f.sep in suffix or f.altsep and f.altsep in suffix:
Antoine Pitrou1b02da92014-01-03 00:07:17 +0100829 raise ValueError("Invalid suffix %r" % (suffix))
Antoine Pitroue50dafc2014-07-06 21:37:15 -0400830 if suffix and not suffix.startswith('.') or suffix == '.':
Antoine Pitrou1b02da92014-01-03 00:07:17 +0100831 raise ValueError("Invalid suffix %r" % (suffix))
Antoine Pitrou31119e42013-11-22 17:38:12 +0100832 name = self.name
833 if not name:
834 raise ValueError("%r has an empty name" % (self,))
835 old_suffix = self.suffix
836 if not old_suffix:
837 name = name + suffix
838 else:
839 name = name[:-len(old_suffix)] + suffix
840 return self._from_parsed_parts(self._drv, self._root,
841 self._parts[:-1] + [name])
842
843 def relative_to(self, *other):
844 """Return the relative path to another path identified by the passed
845 arguments. If the operation is not possible (because this is not
846 a subpath of the other path), raise ValueError.
847 """
848 # For the purpose of this method, drive and root are considered
849 # separate parts, i.e.:
850 # Path('c:/').relative_to('c:') gives Path('/')
851 # Path('c:/').relative_to('/') raise ValueError
852 if not other:
853 raise TypeError("need at least one argument")
854 parts = self._parts
855 drv = self._drv
856 root = self._root
Antoine Pitrou156b3612013-12-28 19:49:04 +0100857 if root:
858 abs_parts = [drv, root] + parts[1:]
Antoine Pitrou31119e42013-11-22 17:38:12 +0100859 else:
860 abs_parts = parts
861 to_drv, to_root, to_parts = self._parse_args(other)
Antoine Pitrou156b3612013-12-28 19:49:04 +0100862 if to_root:
863 to_abs_parts = [to_drv, to_root] + to_parts[1:]
Antoine Pitrou31119e42013-11-22 17:38:12 +0100864 else:
865 to_abs_parts = to_parts
866 n = len(to_abs_parts)
Antoine Pitrou156b3612013-12-28 19:49:04 +0100867 cf = self._flavour.casefold_parts
868 if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):
Antoine Pitrou31119e42013-11-22 17:38:12 +0100869 formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
870 raise ValueError("{!r} does not start with {!r}"
871 .format(str(self), str(formatted)))
Antoine Pitrou156b3612013-12-28 19:49:04 +0100872 return self._from_parsed_parts('', root if n == 1 else '',
873 abs_parts[n:])
Antoine Pitrou31119e42013-11-22 17:38:12 +0100874
875 @property
876 def parts(self):
877 """An object providing sequence-like access to the
878 components in the filesystem path."""
879 # We cache the tuple to avoid building a new one each time .parts
880 # is accessed. XXX is this necessary?
881 try:
882 return self._pparts
883 except AttributeError:
884 self._pparts = tuple(self._parts)
885 return self._pparts
886
887 def joinpath(self, *args):
888 """Combine this path with one or several arguments, and return a
889 new path representing either a subpath (if all arguments are relative
890 paths) or a totally different path (if one of the arguments is
891 anchored).
892 """
893 return self._make_child(args)
894
895 def __truediv__(self, key):
896 return self._make_child((key,))
897
898 def __rtruediv__(self, key):
899 return self._from_parts([key] + self._parts)
900
901 @property
902 def parent(self):
903 """The logical parent of the path."""
904 drv = self._drv
905 root = self._root
906 parts = self._parts
907 if len(parts) == 1 and (drv or root):
908 return self
909 return self._from_parsed_parts(drv, root, parts[:-1])
910
911 @property
912 def parents(self):
913 """A sequence of this path's logical parents."""
914 return _PathParents(self)
915
916 def is_absolute(self):
917 """True if the path is absolute (has both a root and, if applicable,
918 a drive)."""
919 if not self._root:
920 return False
921 return not self._flavour.has_drv or bool(self._drv)
922
923 def is_reserved(self):
924 """Return True if the path contains one of the special names reserved
925 by the system, if any."""
926 return self._flavour.is_reserved(self._parts)
927
928 def match(self, path_pattern):
929 """
930 Return True if this path matches the given pattern.
931 """
932 cf = self._flavour.casefold
933 path_pattern = cf(path_pattern)
934 drv, root, pat_parts = self._flavour.parse_parts((path_pattern,))
935 if not pat_parts:
936 raise ValueError("empty pattern")
937 if drv and drv != cf(self._drv):
938 return False
939 if root and root != cf(self._root):
940 return False
941 parts = self._cparts
942 if drv or root:
943 if len(pat_parts) != len(parts):
944 return False
945 pat_parts = pat_parts[1:]
946 elif len(pat_parts) > len(parts):
947 return False
948 for part, pat in zip(reversed(parts), reversed(pat_parts)):
949 if not fnmatch.fnmatchcase(part, pat):
950 return False
951 return True
952
953
954class PurePosixPath(PurePath):
955 _flavour = _posix_flavour
956 __slots__ = ()
957
958
959class PureWindowsPath(PurePath):
960 _flavour = _windows_flavour
961 __slots__ = ()
962
963
964# Filesystem-accessing classes
965
966
967class Path(PurePath):
968 __slots__ = (
969 '_accessor',
970 '_closed',
971 )
972
973 def __new__(cls, *args, **kwargs):
974 if cls is Path:
975 cls = WindowsPath if os.name == 'nt' else PosixPath
976 self = cls._from_parts(args, init=False)
977 if not self._flavour.is_supported:
978 raise NotImplementedError("cannot instantiate %r on your system"
979 % (cls.__name__,))
980 self._init()
981 return self
982
983 def _init(self,
984 # Private non-constructor arguments
985 template=None,
986 ):
987 self._closed = False
988 if template is not None:
989 self._accessor = template._accessor
990 else:
991 self._accessor = _normal_accessor
992
993 def _make_child_relpath(self, part):
994 # This is an optimization used for dir walking. `part` must be
995 # a single part relative to this path.
996 parts = self._parts + [part]
997 return self._from_parsed_parts(self._drv, self._root, parts)
998
999 def __enter__(self):
1000 if self._closed:
1001 self._raise_closed()
1002 return self
1003
1004 def __exit__(self, t, v, tb):
1005 self._closed = True
1006
1007 def _raise_closed(self):
1008 raise ValueError("I/O operation on closed path")
1009
1010 def _opener(self, name, flags, mode=0o666):
1011 # A stub for the opener argument to built-in open()
1012 return self._accessor.open(self, flags, mode)
1013
Antoine Pitrou4a60d422013-12-02 21:25:18 +01001014 def _raw_open(self, flags, mode=0o777):
1015 """
1016 Open the file pointed by this path and return a file descriptor,
1017 as os.open() does.
1018 """
1019 if self._closed:
1020 self._raise_closed()
1021 return self._accessor.open(self, flags, mode)
1022
Antoine Pitrou31119e42013-11-22 17:38:12 +01001023 # Public API
1024
1025 @classmethod
1026 def cwd(cls):
1027 """Return a new path pointing to the current working directory
1028 (as returned by os.getcwd()).
1029 """
1030 return cls(os.getcwd())
1031
Antoine Pitrou17cba7d2015-01-12 21:03:41 +01001032 @classmethod
1033 def home(cls):
1034 """Return a new path pointing to the user's home directory (as
1035 returned by os.path.expanduser('~')).
1036 """
1037 return cls(cls()._flavour.gethomedir(None))
1038
Antoine Pitrou43e3d942014-05-13 10:50:15 +02001039 def samefile(self, other_path):
Berker Peksag05492b82015-10-22 03:34:16 +03001040 """Return whether other_path is the same or not as this file
Berker Peksag267597f2015-10-21 20:10:24 +03001041 (as returned by os.path.samefile()).
Antoine Pitrou43e3d942014-05-13 10:50:15 +02001042 """
1043 st = self.stat()
1044 try:
1045 other_st = other_path.stat()
1046 except AttributeError:
1047 other_st = os.stat(other_path)
1048 return os.path.samestat(st, other_st)
1049
Antoine Pitrou31119e42013-11-22 17:38:12 +01001050 def iterdir(self):
1051 """Iterate over the files in this directory. Does not yield any
1052 result for the special paths '.' and '..'.
1053 """
1054 if self._closed:
1055 self._raise_closed()
1056 for name in self._accessor.listdir(self):
1057 if name in {'.', '..'}:
1058 # Yielding a path object for these makes little sense
1059 continue
1060 yield self._make_child_relpath(name)
1061 if self._closed:
1062 self._raise_closed()
1063
1064 def glob(self, pattern):
1065 """Iterate over this subtree and yield all existing files (of any
1066 kind, including directories) matching the given pattern.
1067 """
Berker Peksag4a208e42016-01-30 17:50:48 +02001068 if not pattern:
1069 raise ValueError("Unacceptable pattern: {!r}".format(pattern))
Antoine Pitrou31119e42013-11-22 17:38:12 +01001070 pattern = self._flavour.casefold(pattern)
1071 drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
1072 if drv or root:
1073 raise NotImplementedError("Non-relative patterns are unsupported")
1074 selector = _make_selector(tuple(pattern_parts))
1075 for p in selector.select_from(self):
1076 yield p
1077
1078 def rglob(self, pattern):
1079 """Recursively yield all existing files (of any kind, including
1080 directories) matching the given pattern, anywhere in this subtree.
1081 """
1082 pattern = self._flavour.casefold(pattern)
1083 drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
1084 if drv or root:
1085 raise NotImplementedError("Non-relative patterns are unsupported")
1086 selector = _make_selector(("**",) + tuple(pattern_parts))
1087 for p in selector.select_from(self):
1088 yield p
1089
1090 def absolute(self):
1091 """Return an absolute version of this path. This function works
1092 even if the path doesn't point to anything.
1093
1094 No normalization is done, i.e. all '.' and '..' will be kept along.
1095 Use resolve() to get the canonical path to a file.
1096 """
1097 # XXX untested yet!
1098 if self._closed:
1099 self._raise_closed()
1100 if self.is_absolute():
1101 return self
1102 # FIXME this must defer to the specific flavour (and, under Windows,
1103 # use nt._getfullpathname())
1104 obj = self._from_parts([os.getcwd()] + self._parts, init=False)
1105 obj._init(template=self)
1106 return obj
1107
1108 def resolve(self):
1109 """
1110 Make the path absolute, resolving all symlinks on the way and also
1111 normalizing it (for example turning slashes into backslashes under
1112 Windows).
1113 """
1114 if self._closed:
1115 self._raise_closed()
1116 s = self._flavour.resolve(self)
1117 if s is None:
1118 # No symlink resolution => for consistency, raise an error if
1119 # the path doesn't exist or is forbidden
1120 self.stat()
1121 s = str(self.absolute())
1122 # Now we have no symlinks in the path, it's safe to normalize it.
1123 normed = self._flavour.pathmod.normpath(s)
1124 obj = self._from_parts((normed,), init=False)
1125 obj._init(template=self)
1126 return obj
1127
1128 def stat(self):
1129 """
1130 Return the result of the stat() system call on this path, like
1131 os.stat() does.
1132 """
1133 return self._accessor.stat(self)
1134
1135 def owner(self):
1136 """
1137 Return the login name of the file owner.
1138 """
1139 import pwd
1140 return pwd.getpwuid(self.stat().st_uid).pw_name
1141
1142 def group(self):
1143 """
1144 Return the group name of the file gid.
1145 """
1146 import grp
1147 return grp.getgrgid(self.stat().st_gid).gr_name
1148
Antoine Pitrou31119e42013-11-22 17:38:12 +01001149 def open(self, mode='r', buffering=-1, encoding=None,
1150 errors=None, newline=None):
1151 """
1152 Open the file pointed by this path and return a file object, as
1153 the built-in open() function does.
1154 """
1155 if self._closed:
1156 self._raise_closed()
1157 return io.open(str(self), mode, buffering, encoding, errors, newline,
1158 opener=self._opener)
1159
Georg Brandlea683982014-10-01 19:12:33 +02001160 def read_bytes(self):
1161 """
1162 Open the file in bytes mode, read it, and close the file.
1163 """
1164 with self.open(mode='rb') as f:
1165 return f.read()
1166
1167 def read_text(self, encoding=None, errors=None):
1168 """
1169 Open the file in text mode, read it, and close the file.
1170 """
1171 with self.open(mode='r', encoding=encoding, errors=errors) as f:
1172 return f.read()
1173
1174 def write_bytes(self, data):
1175 """
1176 Open the file in bytes mode, write to it, and close the file.
1177 """
1178 # type-check for the buffer interface before truncating the file
1179 view = memoryview(data)
1180 with self.open(mode='wb') as f:
1181 return f.write(view)
1182
1183 def write_text(self, data, encoding=None, errors=None):
1184 """
1185 Open the file in text mode, write to it, and close the file.
1186 """
1187 if not isinstance(data, str):
1188 raise TypeError('data must be str, not %s' %
1189 data.__class__.__name__)
1190 with self.open(mode='w', encoding=encoding, errors=errors) as f:
1191 return f.write(data)
1192
Antoine Pitrou31119e42013-11-22 17:38:12 +01001193 def touch(self, mode=0o666, exist_ok=True):
1194 """
1195 Create this file with the given access mode, if it doesn't exist.
1196 """
1197 if self._closed:
1198 self._raise_closed()
1199 if exist_ok:
1200 # First try to bump modification time
1201 # Implementation note: GNU touch uses the UTIME_NOW option of
1202 # the utimensat() / futimens() functions.
Antoine Pitrou31119e42013-11-22 17:38:12 +01001203 try:
Antoine Pitrou2cf39172013-11-23 15:25:59 +01001204 self._accessor.utime(self, None)
Antoine Pitrou31119e42013-11-22 17:38:12 +01001205 except OSError:
1206 # Avoid exception chaining
1207 pass
1208 else:
1209 return
1210 flags = os.O_CREAT | os.O_WRONLY
1211 if not exist_ok:
1212 flags |= os.O_EXCL
1213 fd = self._raw_open(flags, mode)
1214 os.close(fd)
1215
Barry Warsaw7c549c42014-08-05 11:28:12 -04001216 def mkdir(self, mode=0o777, parents=False, exist_ok=False):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001217 if self._closed:
1218 self._raise_closed()
1219 if not parents:
Barry Warsaw7c549c42014-08-05 11:28:12 -04001220 try:
1221 self._accessor.mkdir(self, mode)
1222 except FileExistsError:
1223 if not exist_ok or not self.is_dir():
1224 raise
Antoine Pitrou31119e42013-11-22 17:38:12 +01001225 else:
1226 try:
1227 self._accessor.mkdir(self, mode)
Barry Warsaw7c549c42014-08-05 11:28:12 -04001228 except FileExistsError:
1229 if not exist_ok or not self.is_dir():
1230 raise
Antoine Pitrou31119e42013-11-22 17:38:12 +01001231 except OSError as e:
1232 if e.errno != ENOENT:
1233 raise
Antoine Pitrou0048c982013-12-16 20:22:37 +01001234 self.parent.mkdir(parents=True)
Antoine Pitrou31119e42013-11-22 17:38:12 +01001235 self._accessor.mkdir(self, mode)
1236
1237 def chmod(self, mode):
1238 """
1239 Change the permissions of the path, like os.chmod().
1240 """
1241 if self._closed:
1242 self._raise_closed()
1243 self._accessor.chmod(self, mode)
1244
1245 def lchmod(self, mode):
1246 """
1247 Like chmod(), except if the path points to a symlink, the symlink's
1248 permissions are changed, rather than its target's.
1249 """
1250 if self._closed:
1251 self._raise_closed()
1252 self._accessor.lchmod(self, mode)
1253
1254 def unlink(self):
1255 """
1256 Remove this file or link.
1257 If the path is a directory, use rmdir() instead.
1258 """
1259 if self._closed:
1260 self._raise_closed()
1261 self._accessor.unlink(self)
1262
1263 def rmdir(self):
1264 """
1265 Remove this directory. The directory must be empty.
1266 """
1267 if self._closed:
1268 self._raise_closed()
1269 self._accessor.rmdir(self)
1270
1271 def lstat(self):
1272 """
1273 Like stat(), except if the path points to a symlink, the symlink's
1274 status information is returned, rather than its target's.
1275 """
1276 if self._closed:
1277 self._raise_closed()
1278 return self._accessor.lstat(self)
1279
1280 def rename(self, target):
1281 """
1282 Rename this path to the given path.
1283 """
1284 if self._closed:
1285 self._raise_closed()
1286 self._accessor.rename(self, target)
1287
1288 def replace(self, target):
1289 """
1290 Rename this path to the given path, clobbering the existing
1291 destination if it exists.
1292 """
1293 if self._closed:
1294 self._raise_closed()
1295 self._accessor.replace(self, target)
1296
1297 def symlink_to(self, target, target_is_directory=False):
1298 """
1299 Make this path a symlink pointing to the given path.
1300 Note the order of arguments (self, target) is the reverse of os.symlink's.
1301 """
1302 if self._closed:
1303 self._raise_closed()
1304 self._accessor.symlink(target, self, target_is_directory)
1305
1306 # Convenience functions for querying the stat results
1307
1308 def exists(self):
1309 """
1310 Whether this path exists.
1311 """
1312 try:
1313 self.stat()
1314 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001315 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001316 raise
1317 return False
1318 return True
1319
1320 def is_dir(self):
1321 """
1322 Whether this path is a directory.
1323 """
1324 try:
1325 return S_ISDIR(self.stat().st_mode)
1326 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001327 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001328 raise
1329 # Path doesn't exist or is a broken symlink
1330 # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1331 return False
1332
1333 def is_file(self):
1334 """
1335 Whether this path is a regular file (also True for symlinks pointing
1336 to regular files).
1337 """
1338 try:
1339 return S_ISREG(self.stat().st_mode)
1340 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001341 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001342 raise
1343 # Path doesn't exist or is a broken symlink
1344 # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1345 return False
1346
1347 def is_symlink(self):
1348 """
1349 Whether this path is a symbolic link.
1350 """
1351 try:
1352 return S_ISLNK(self.lstat().st_mode)
1353 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001354 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001355 raise
1356 # Path doesn't exist
1357 return False
1358
1359 def is_block_device(self):
1360 """
1361 Whether this path is a block device.
1362 """
1363 try:
1364 return S_ISBLK(self.stat().st_mode)
1365 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001366 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001367 raise
1368 # Path doesn't exist or is a broken symlink
1369 # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1370 return False
1371
1372 def is_char_device(self):
1373 """
1374 Whether this path is a character device.
1375 """
1376 try:
1377 return S_ISCHR(self.stat().st_mode)
1378 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001379 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001380 raise
1381 # Path doesn't exist or is a broken symlink
1382 # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1383 return False
1384
1385 def is_fifo(self):
1386 """
1387 Whether this path is a FIFO.
1388 """
1389 try:
1390 return S_ISFIFO(self.stat().st_mode)
1391 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001392 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001393 raise
1394 # Path doesn't exist or is a broken symlink
1395 # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1396 return False
1397
1398 def is_socket(self):
1399 """
1400 Whether this path is a socket.
1401 """
1402 try:
1403 return S_ISSOCK(self.stat().st_mode)
1404 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001405 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001406 raise
1407 # Path doesn't exist or is a broken symlink
1408 # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1409 return False
1410
Antoine Pitrou8477ed62014-12-30 20:54:45 +01001411 def expanduser(self):
1412 """ Return a new path with expanded ~ and ~user constructs
1413 (as returned by os.path.expanduser)
1414 """
1415 if (not (self._drv or self._root) and
1416 self._parts and self._parts[0][:1] == '~'):
1417 homedir = self._flavour.gethomedir(self._parts[0][1:])
1418 return self._from_parts([homedir] + self._parts[1:])
1419
1420 return self
1421
Antoine Pitrou31119e42013-11-22 17:38:12 +01001422
1423class PosixPath(Path, PurePosixPath):
1424 __slots__ = ()
1425
1426class WindowsPath(Path, PureWindowsPath):
1427 __slots__ = ()
Berker Peksag04d42292016-03-11 23:07:27 +02001428
1429 def owner(self):
1430 raise NotImplementedError("Path.owner() is unsupported on this system")
1431
1432 def group(self):
1433 raise NotImplementedError("Path.group() is unsupported on this system")