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