Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/py/_path/local.py : 14%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2local path implementation.
3"""
4from __future__ import with_statement
6from contextlib import contextmanager
7import sys, os, atexit, io, uuid
8import py
9from py._path import common
10from py._path.common import iswin32, fspath
11from stat import S_ISLNK, S_ISDIR, S_ISREG
13from os.path import abspath, normpath, isabs, exists, isdir, isfile, islink, dirname
15if sys.version_info > (3,0):
16 def map_as_list(func, iter):
17 return list(map(func, iter))
18else:
19 map_as_list = map
21ALLOW_IMPORTLIB_MODE = sys.version_info > (3,5)
22if ALLOW_IMPORTLIB_MODE:
23 import importlib
26class Stat(object):
27 def __getattr__(self, name):
28 return getattr(self._osstatresult, "st_" + name)
30 def __init__(self, path, osstatresult):
31 self.path = path
32 self._osstatresult = osstatresult
34 @property
35 def owner(self):
36 if iswin32:
37 raise NotImplementedError("XXX win32")
38 import pwd
39 entry = py.error.checked_call(pwd.getpwuid, self.uid)
40 return entry[0]
42 @property
43 def group(self):
44 """ return group name of file. """
45 if iswin32:
46 raise NotImplementedError("XXX win32")
47 import grp
48 entry = py.error.checked_call(grp.getgrgid, self.gid)
49 return entry[0]
51 def isdir(self):
52 return S_ISDIR(self._osstatresult.st_mode)
54 def isfile(self):
55 return S_ISREG(self._osstatresult.st_mode)
57 def islink(self):
58 st = self.path.lstat()
59 return S_ISLNK(self._osstatresult.st_mode)
61class PosixPath(common.PathBase):
62 def chown(self, user, group, rec=0):
63 """ change ownership to the given user and group.
64 user and group may be specified by a number or
65 by a name. if rec is True change ownership
66 recursively.
67 """
68 uid = getuserid(user)
69 gid = getgroupid(group)
70 if rec:
71 for x in self.visit(rec=lambda x: x.check(link=0)):
72 if x.check(link=0):
73 py.error.checked_call(os.chown, str(x), uid, gid)
74 py.error.checked_call(os.chown, str(self), uid, gid)
76 def readlink(self):
77 """ return value of a symbolic link. """
78 return py.error.checked_call(os.readlink, self.strpath)
80 def mklinkto(self, oldname):
81 """ posix style hard link to another name. """
82 py.error.checked_call(os.link, str(oldname), str(self))
84 def mksymlinkto(self, value, absolute=1):
85 """ create a symbolic link with the given value (pointing to another name). """
86 if absolute:
87 py.error.checked_call(os.symlink, str(value), self.strpath)
88 else:
89 base = self.common(value)
90 # with posix local paths '/' is always a common base
91 relsource = self.__class__(value).relto(base)
92 reldest = self.relto(base)
93 n = reldest.count(self.sep)
94 target = self.sep.join(('..', )*n + (relsource, ))
95 py.error.checked_call(os.symlink, target, self.strpath)
97def getuserid(user):
98 import pwd
99 if not isinstance(user, int):
100 user = pwd.getpwnam(user)[2]
101 return user
103def getgroupid(group):
104 import grp
105 if not isinstance(group, int):
106 group = grp.getgrnam(group)[2]
107 return group
109FSBase = not iswin32 and PosixPath or common.PathBase
111class LocalPath(FSBase):
112 """ object oriented interface to os.path and other local filesystem
113 related information.
114 """
115 class ImportMismatchError(ImportError):
116 """ raised on pyimport() if there is a mismatch of __file__'s"""
118 sep = os.sep
119 class Checkers(common.Checkers):
120 def _stat(self):
121 try:
122 return self._statcache
123 except AttributeError:
124 try:
125 self._statcache = self.path.stat()
126 except py.error.ELOOP:
127 self._statcache = self.path.lstat()
128 return self._statcache
130 def dir(self):
131 return S_ISDIR(self._stat().mode)
133 def file(self):
134 return S_ISREG(self._stat().mode)
136 def exists(self):
137 return self._stat()
139 def link(self):
140 st = self.path.lstat()
141 return S_ISLNK(st.mode)
143 def __init__(self, path=None, expanduser=False):
144 """ Initialize and return a local Path instance.
146 Path can be relative to the current directory.
147 If path is None it defaults to the current working directory.
148 If expanduser is True, tilde-expansion is performed.
149 Note that Path instances always carry an absolute path.
150 Note also that passing in a local path object will simply return
151 the exact same path object. Use new() to get a new copy.
152 """
153 if path is None:
154 self.strpath = py.error.checked_call(os.getcwd)
155 else:
156 try:
157 path = fspath(path)
158 except TypeError:
159 raise ValueError("can only pass None, Path instances "
160 "or non-empty strings to LocalPath")
161 if expanduser:
162 path = os.path.expanduser(path)
163 self.strpath = abspath(path)
165 def __hash__(self):
166 s = self.strpath
167 if iswin32:
168 s = s.lower()
169 return hash(s)
171 def __eq__(self, other):
172 s1 = fspath(self)
173 try:
174 s2 = fspath(other)
175 except TypeError:
176 return False
177 if iswin32:
178 s1 = s1.lower()
179 try:
180 s2 = s2.lower()
181 except AttributeError:
182 return False
183 return s1 == s2
185 def __ne__(self, other):
186 return not (self == other)
188 def __lt__(self, other):
189 return fspath(self) < fspath(other)
191 def __gt__(self, other):
192 return fspath(self) > fspath(other)
194 def samefile(self, other):
195 """ return True if 'other' references the same file as 'self'.
196 """
197 other = fspath(other)
198 if not isabs(other):
199 other = abspath(other)
200 if self == other:
201 return True
202 if not hasattr(os.path, "samefile"):
203 return False
204 return py.error.checked_call(
205 os.path.samefile, self.strpath, other)
207 def remove(self, rec=1, ignore_errors=False):
208 """ remove a file or directory (or a directory tree if rec=1).
209 if ignore_errors is True, errors while removing directories will
210 be ignored.
211 """
212 if self.check(dir=1, link=0):
213 if rec:
214 # force remove of readonly files on windows
215 if iswin32:
216 self.chmod(0o700, rec=1)
217 import shutil
218 py.error.checked_call(
219 shutil.rmtree, self.strpath,
220 ignore_errors=ignore_errors)
221 else:
222 py.error.checked_call(os.rmdir, self.strpath)
223 else:
224 if iswin32:
225 self.chmod(0o700)
226 py.error.checked_call(os.remove, self.strpath)
228 def computehash(self, hashtype="md5", chunksize=524288):
229 """ return hexdigest of hashvalue for this file. """
230 try:
231 try:
232 import hashlib as mod
233 except ImportError:
234 if hashtype == "sha1":
235 hashtype = "sha"
236 mod = __import__(hashtype)
237 hash = getattr(mod, hashtype)()
238 except (AttributeError, ImportError):
239 raise ValueError("Don't know how to compute %r hash" %(hashtype,))
240 f = self.open('rb')
241 try:
242 while 1:
243 buf = f.read(chunksize)
244 if not buf:
245 return hash.hexdigest()
246 hash.update(buf)
247 finally:
248 f.close()
250 def new(self, **kw):
251 """ create a modified version of this path.
252 the following keyword arguments modify various path parts::
254 a:/some/path/to/a/file.ext
255 xx drive
256 xxxxxxxxxxxxxxxxx dirname
257 xxxxxxxx basename
258 xxxx purebasename
259 xxx ext
260 """
261 obj = object.__new__(self.__class__)
262 if not kw:
263 obj.strpath = self.strpath
264 return obj
265 drive, dirname, basename, purebasename,ext = self._getbyspec(
266 "drive,dirname,basename,purebasename,ext")
267 if 'basename' in kw:
268 if 'purebasename' in kw or 'ext' in kw:
269 raise ValueError("invalid specification %r" % kw)
270 else:
271 pb = kw.setdefault('purebasename', purebasename)
272 try:
273 ext = kw['ext']
274 except KeyError:
275 pass
276 else:
277 if ext and not ext.startswith('.'):
278 ext = '.' + ext
279 kw['basename'] = pb + ext
281 if ('dirname' in kw and not kw['dirname']):
282 kw['dirname'] = drive
283 else:
284 kw.setdefault('dirname', dirname)
285 kw.setdefault('sep', self.sep)
286 obj.strpath = normpath(
287 "%(dirname)s%(sep)s%(basename)s" % kw)
288 return obj
290 def _getbyspec(self, spec):
291 """ see new for what 'spec' can be. """
292 res = []
293 parts = self.strpath.split(self.sep)
295 args = filter(None, spec.split(',') )
296 append = res.append
297 for name in args:
298 if name == 'drive':
299 append(parts[0])
300 elif name == 'dirname':
301 append(self.sep.join(parts[:-1]))
302 else:
303 basename = parts[-1]
304 if name == 'basename':
305 append(basename)
306 else:
307 i = basename.rfind('.')
308 if i == -1:
309 purebasename, ext = basename, ''
310 else:
311 purebasename, ext = basename[:i], basename[i:]
312 if name == 'purebasename':
313 append(purebasename)
314 elif name == 'ext':
315 append(ext)
316 else:
317 raise ValueError("invalid part specification %r" % name)
318 return res
320 def dirpath(self, *args, **kwargs):
321 """ return the directory path joined with any given path arguments. """
322 if not kwargs:
323 path = object.__new__(self.__class__)
324 path.strpath = dirname(self.strpath)
325 if args:
326 path = path.join(*args)
327 return path
328 return super(LocalPath, self).dirpath(*args, **kwargs)
330 def join(self, *args, **kwargs):
331 """ return a new path by appending all 'args' as path
332 components. if abs=1 is used restart from root if any
333 of the args is an absolute path.
334 """
335 sep = self.sep
336 strargs = [fspath(arg) for arg in args]
337 strpath = self.strpath
338 if kwargs.get('abs'):
339 newargs = []
340 for arg in reversed(strargs):
341 if isabs(arg):
342 strpath = arg
343 strargs = newargs
344 break
345 newargs.insert(0, arg)
346 # special case for when we have e.g. strpath == "/"
347 actual_sep = "" if strpath.endswith(sep) else sep
348 for arg in strargs:
349 arg = arg.strip(sep)
350 if iswin32:
351 # allow unix style paths even on windows.
352 arg = arg.strip('/')
353 arg = arg.replace('/', sep)
354 strpath = strpath + actual_sep + arg
355 actual_sep = sep
356 obj = object.__new__(self.__class__)
357 obj.strpath = normpath(strpath)
358 return obj
360 def open(self, mode='r', ensure=False, encoding=None):
361 """ return an opened file with the given mode.
363 If ensure is True, create parent directories if needed.
364 """
365 if ensure:
366 self.dirpath().ensure(dir=1)
367 if encoding:
368 return py.error.checked_call(io.open, self.strpath, mode, encoding=encoding)
369 return py.error.checked_call(open, self.strpath, mode)
371 def _fastjoin(self, name):
372 child = object.__new__(self.__class__)
373 child.strpath = self.strpath + self.sep + name
374 return child
376 def islink(self):
377 return islink(self.strpath)
379 def check(self, **kw):
380 if not kw:
381 return exists(self.strpath)
382 if len(kw) == 1:
383 if "dir" in kw:
384 return not kw["dir"] ^ isdir(self.strpath)
385 if "file" in kw:
386 return not kw["file"] ^ isfile(self.strpath)
387 return super(LocalPath, self).check(**kw)
389 _patternchars = set("*?[" + os.path.sep)
390 def listdir(self, fil=None, sort=None):
391 """ list directory contents, possibly filter by the given fil func
392 and possibly sorted.
393 """
394 if fil is None and sort is None:
395 names = py.error.checked_call(os.listdir, self.strpath)
396 return map_as_list(self._fastjoin, names)
397 if isinstance(fil, py.builtin._basestring):
398 if not self._patternchars.intersection(fil):
399 child = self._fastjoin(fil)
400 if exists(child.strpath):
401 return [child]
402 return []
403 fil = common.FNMatcher(fil)
404 names = py.error.checked_call(os.listdir, self.strpath)
405 res = []
406 for name in names:
407 child = self._fastjoin(name)
408 if fil is None or fil(child):
409 res.append(child)
410 self._sortlist(res, sort)
411 return res
413 def size(self):
414 """ return size of the underlying file object """
415 return self.stat().size
417 def mtime(self):
418 """ return last modification time of the path. """
419 return self.stat().mtime
421 def copy(self, target, mode=False, stat=False):
422 """ copy path to target.
424 If mode is True, will copy copy permission from path to target.
425 If stat is True, copy permission, last modification
426 time, last access time, and flags from path to target.
427 """
428 if self.check(file=1):
429 if target.check(dir=1):
430 target = target.join(self.basename)
431 assert self!=target
432 copychunked(self, target)
433 if mode:
434 copymode(self.strpath, target.strpath)
435 if stat:
436 copystat(self, target)
437 else:
438 def rec(p):
439 return p.check(link=0)
440 for x in self.visit(rec=rec):
441 relpath = x.relto(self)
442 newx = target.join(relpath)
443 newx.dirpath().ensure(dir=1)
444 if x.check(link=1):
445 newx.mksymlinkto(x.readlink())
446 continue
447 elif x.check(file=1):
448 copychunked(x, newx)
449 elif x.check(dir=1):
450 newx.ensure(dir=1)
451 if mode:
452 copymode(x.strpath, newx.strpath)
453 if stat:
454 copystat(x, newx)
456 def rename(self, target):
457 """ rename this path to target. """
458 target = fspath(target)
459 return py.error.checked_call(os.rename, self.strpath, target)
461 def dump(self, obj, bin=1):
462 """ pickle object into path location"""
463 f = self.open('wb')
464 import pickle
465 try:
466 py.error.checked_call(pickle.dump, obj, f, bin)
467 finally:
468 f.close()
470 def mkdir(self, *args):
471 """ create & return the directory joined with args. """
472 p = self.join(*args)
473 py.error.checked_call(os.mkdir, fspath(p))
474 return p
476 def write_binary(self, data, ensure=False):
477 """ write binary data into path. If ensure is True create
478 missing parent directories.
479 """
480 if ensure:
481 self.dirpath().ensure(dir=1)
482 with self.open('wb') as f:
483 f.write(data)
485 def write_text(self, data, encoding, ensure=False):
486 """ write text data into path using the specified encoding.
487 If ensure is True create missing parent directories.
488 """
489 if ensure:
490 self.dirpath().ensure(dir=1)
491 with self.open('w', encoding=encoding) as f:
492 f.write(data)
494 def write(self, data, mode='w', ensure=False):
495 """ write data into path. If ensure is True create
496 missing parent directories.
497 """
498 if ensure:
499 self.dirpath().ensure(dir=1)
500 if 'b' in mode:
501 if not py.builtin._isbytes(data):
502 raise ValueError("can only process bytes")
503 else:
504 if not py.builtin._istext(data):
505 if not py.builtin._isbytes(data):
506 data = str(data)
507 else:
508 data = py.builtin._totext(data, sys.getdefaultencoding())
509 f = self.open(mode)
510 try:
511 f.write(data)
512 finally:
513 f.close()
515 def _ensuredirs(self):
516 parent = self.dirpath()
517 if parent == self:
518 return self
519 if parent.check(dir=0):
520 parent._ensuredirs()
521 if self.check(dir=0):
522 try:
523 self.mkdir()
524 except py.error.EEXIST:
525 # race condition: file/dir created by another thread/process.
526 # complain if it is not a dir
527 if self.check(dir=0):
528 raise
529 return self
531 def ensure(self, *args, **kwargs):
532 """ ensure that an args-joined path exists (by default as
533 a file). if you specify a keyword argument 'dir=True'
534 then the path is forced to be a directory path.
535 """
536 p = self.join(*args)
537 if kwargs.get('dir', 0):
538 return p._ensuredirs()
539 else:
540 p.dirpath()._ensuredirs()
541 if not p.check(file=1):
542 p.open('w').close()
543 return p
545 def stat(self, raising=True):
546 """ Return an os.stat() tuple. """
547 if raising == True:
548 return Stat(self, py.error.checked_call(os.stat, self.strpath))
549 try:
550 return Stat(self, os.stat(self.strpath))
551 except KeyboardInterrupt:
552 raise
553 except Exception:
554 return None
556 def lstat(self):
557 """ Return an os.lstat() tuple. """
558 return Stat(self, py.error.checked_call(os.lstat, self.strpath))
560 def setmtime(self, mtime=None):
561 """ set modification time for the given path. if 'mtime' is None
562 (the default) then the file's mtime is set to current time.
564 Note that the resolution for 'mtime' is platform dependent.
565 """
566 if mtime is None:
567 return py.error.checked_call(os.utime, self.strpath, mtime)
568 try:
569 return py.error.checked_call(os.utime, self.strpath, (-1, mtime))
570 except py.error.EINVAL:
571 return py.error.checked_call(os.utime, self.strpath, (self.atime(), mtime))
573 def chdir(self):
574 """ change directory to self and return old current directory """
575 try:
576 old = self.__class__()
577 except py.error.ENOENT:
578 old = None
579 py.error.checked_call(os.chdir, self.strpath)
580 return old
583 @contextmanager
584 def as_cwd(self):
585 """
586 Return a context manager, which changes to the path's dir during the
587 managed "with" context.
588 On __enter__ it returns the old dir, which might be ``None``.
589 """
590 old = self.chdir()
591 try:
592 yield old
593 finally:
594 if old is not None:
595 old.chdir()
597 def realpath(self):
598 """ return a new path which contains no symbolic links."""
599 return self.__class__(os.path.realpath(self.strpath))
601 def atime(self):
602 """ return last access time of the path. """
603 return self.stat().atime
605 def __repr__(self):
606 return 'local(%r)' % self.strpath
608 def __str__(self):
609 """ return string representation of the Path. """
610 return self.strpath
612 def chmod(self, mode, rec=0):
613 """ change permissions to the given mode. If mode is an
614 integer it directly encodes the os-specific modes.
615 if rec is True perform recursively.
616 """
617 if not isinstance(mode, int):
618 raise TypeError("mode %r must be an integer" % (mode,))
619 if rec:
620 for x in self.visit(rec=rec):
621 py.error.checked_call(os.chmod, str(x), mode)
622 py.error.checked_call(os.chmod, self.strpath, mode)
624 def pypkgpath(self):
625 """ return the Python package path by looking for the last
626 directory upwards which still contains an __init__.py.
627 Return None if a pkgpath can not be determined.
628 """
629 pkgpath = None
630 for parent in self.parts(reverse=True):
631 if parent.isdir():
632 if not parent.join('__init__.py').exists():
633 break
634 if not isimportable(parent.basename):
635 break
636 pkgpath = parent
637 return pkgpath
639 def _ensuresyspath(self, ensuremode, path):
640 if ensuremode:
641 s = str(path)
642 if ensuremode == "append":
643 if s not in sys.path:
644 sys.path.append(s)
645 else:
646 if s != sys.path[0]:
647 sys.path.insert(0, s)
649 def pyimport(self, modname=None, ensuresyspath=True):
650 """ return path as an imported python module.
652 If modname is None, look for the containing package
653 and construct an according module name.
654 The module will be put/looked up in sys.modules.
655 if ensuresyspath is True then the root dir for importing
656 the file (taking __init__.py files into account) will
657 be prepended to sys.path if it isn't there already.
658 If ensuresyspath=="append" the root dir will be appended
659 if it isn't already contained in sys.path.
660 if ensuresyspath is False no modification of syspath happens.
662 Special value of ensuresyspath=="importlib" is intended
663 purely for using in pytest, it is capable only of importing
664 separate .py files outside packages, e.g. for test suite
665 without any __init__.py file. It effectively allows having
666 same-named test modules in different places and offers
667 mild opt-in via this option. Note that it works only in
668 recent versions of python.
669 """
670 if not self.check():
671 raise py.error.ENOENT(self)
673 if ensuresyspath == 'importlib':
674 if modname is None:
675 modname = self.purebasename
676 if not ALLOW_IMPORTLIB_MODE:
677 raise ImportError(
678 "Can't use importlib due to old version of Python")
679 spec = importlib.util.spec_from_file_location(
680 modname, str(self))
681 if spec is None:
682 raise ImportError(
683 "Can't find module %s at location %s" %
684 (modname, str(self))
685 )
686 mod = importlib.util.module_from_spec(spec)
687 spec.loader.exec_module(mod)
688 return mod
690 pkgpath = None
691 if modname is None:
692 pkgpath = self.pypkgpath()
693 if pkgpath is not None:
694 pkgroot = pkgpath.dirpath()
695 names = self.new(ext="").relto(pkgroot).split(self.sep)
696 if names[-1] == "__init__":
697 names.pop()
698 modname = ".".join(names)
699 else:
700 pkgroot = self.dirpath()
701 modname = self.purebasename
703 self._ensuresyspath(ensuresyspath, pkgroot)
704 __import__(modname)
705 mod = sys.modules[modname]
706 if self.basename == "__init__.py":
707 return mod # we don't check anything as we might
708 # be in a namespace package ... too icky to check
709 modfile = mod.__file__
710 if modfile[-4:] in ('.pyc', '.pyo'):
711 modfile = modfile[:-1]
712 elif modfile.endswith('$py.class'):
713 modfile = modfile[:-9] + '.py'
714 if modfile.endswith(os.path.sep + "__init__.py"):
715 if self.basename != "__init__.py":
716 modfile = modfile[:-12]
717 try:
718 issame = self.samefile(modfile)
719 except py.error.ENOENT:
720 issame = False
721 if not issame:
722 ignore = os.getenv('PY_IGNORE_IMPORTMISMATCH')
723 if ignore != '1':
724 raise self.ImportMismatchError(modname, modfile, self)
725 return mod
726 else:
727 try:
728 return sys.modules[modname]
729 except KeyError:
730 # we have a custom modname, do a pseudo-import
731 import types
732 mod = types.ModuleType(modname)
733 mod.__file__ = str(self)
734 sys.modules[modname] = mod
735 try:
736 py.builtin.execfile(str(self), mod.__dict__)
737 except:
738 del sys.modules[modname]
739 raise
740 return mod
742 def sysexec(self, *argv, **popen_opts):
743 """ return stdout text from executing a system child process,
744 where the 'self' path points to executable.
745 The process is directly invoked and not through a system shell.
746 """
747 from subprocess import Popen, PIPE
748 argv = map_as_list(str, argv)
749 popen_opts['stdout'] = popen_opts['stderr'] = PIPE
750 proc = Popen([str(self)] + argv, **popen_opts)
751 stdout, stderr = proc.communicate()
752 ret = proc.wait()
753 if py.builtin._isbytes(stdout):
754 stdout = py.builtin._totext(stdout, sys.getdefaultencoding())
755 if ret != 0:
756 if py.builtin._isbytes(stderr):
757 stderr = py.builtin._totext(stderr, sys.getdefaultencoding())
758 raise py.process.cmdexec.Error(ret, ret, str(self),
759 stdout, stderr,)
760 return stdout
762 def sysfind(cls, name, checker=None, paths=None):
763 """ return a path object found by looking at the systems
764 underlying PATH specification. If the checker is not None
765 it will be invoked to filter matching paths. If a binary
766 cannot be found, None is returned
767 Note: This is probably not working on plain win32 systems
768 but may work on cygwin.
769 """
770 if isabs(name):
771 p = py.path.local(name)
772 if p.check(file=1):
773 return p
774 else:
775 if paths is None:
776 if iswin32:
777 paths = os.environ['Path'].split(';')
778 if '' not in paths and '.' not in paths:
779 paths.append('.')
780 try:
781 systemroot = os.environ['SYSTEMROOT']
782 except KeyError:
783 pass
784 else:
785 paths = [path.replace('%SystemRoot%', systemroot)
786 for path in paths]
787 else:
788 paths = os.environ['PATH'].split(':')
789 tryadd = []
790 if iswin32:
791 tryadd += os.environ['PATHEXT'].split(os.pathsep)
792 tryadd.append("")
794 for x in paths:
795 for addext in tryadd:
796 p = py.path.local(x).join(name, abs=True) + addext
797 try:
798 if p.check(file=1):
799 if checker:
800 if not checker(p):
801 continue
802 return p
803 except py.error.EACCES:
804 pass
805 return None
806 sysfind = classmethod(sysfind)
808 def _gethomedir(cls):
809 try:
810 x = os.environ['HOME']
811 except KeyError:
812 try:
813 x = os.environ["HOMEDRIVE"] + os.environ['HOMEPATH']
814 except KeyError:
815 return None
816 return cls(x)
817 _gethomedir = classmethod(_gethomedir)
819 # """
820 # special class constructors for local filesystem paths
821 # """
822 @classmethod
823 def get_temproot(cls):
824 """ return the system's temporary directory
825 (where tempfiles are usually created in)
826 """
827 import tempfile
828 return py.path.local(tempfile.gettempdir())
830 @classmethod
831 def mkdtemp(cls, rootdir=None):
832 """ return a Path object pointing to a fresh new temporary directory
833 (which we created ourself).
834 """
835 import tempfile
836 if rootdir is None:
837 rootdir = cls.get_temproot()
838 return cls(py.error.checked_call(tempfile.mkdtemp, dir=str(rootdir)))
840 def make_numbered_dir(cls, prefix='session-', rootdir=None, keep=3,
841 lock_timeout=172800): # two days
842 """ return unique directory with a number greater than the current
843 maximum one. The number is assumed to start directly after prefix.
844 if keep is true directories with a number less than (maxnum-keep)
845 will be removed. If .lock files are used (lock_timeout non-zero),
846 algorithm is multi-process safe.
847 """
848 if rootdir is None:
849 rootdir = cls.get_temproot()
851 nprefix = prefix.lower()
852 def parse_num(path):
853 """ parse the number out of a path (if it matches the prefix) """
854 nbasename = path.basename.lower()
855 if nbasename.startswith(nprefix):
856 try:
857 return int(nbasename[len(nprefix):])
858 except ValueError:
859 pass
861 def create_lockfile(path):
862 """ exclusively create lockfile. Throws when failed """
863 mypid = os.getpid()
864 lockfile = path.join('.lock')
865 if hasattr(lockfile, 'mksymlinkto'):
866 lockfile.mksymlinkto(str(mypid))
867 else:
868 fd = py.error.checked_call(os.open, str(lockfile), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644)
869 with os.fdopen(fd, 'w') as f:
870 f.write(str(mypid))
871 return lockfile
873 def atexit_remove_lockfile(lockfile):
874 """ ensure lockfile is removed at process exit """
875 mypid = os.getpid()
876 def try_remove_lockfile():
877 # in a fork() situation, only the last process should
878 # remove the .lock, otherwise the other processes run the
879 # risk of seeing their temporary dir disappear. For now
880 # we remove the .lock in the parent only (i.e. we assume
881 # that the children finish before the parent).
882 if os.getpid() != mypid:
883 return
884 try:
885 lockfile.remove()
886 except py.error.Error:
887 pass
888 atexit.register(try_remove_lockfile)
890 # compute the maximum number currently in use with the prefix
891 lastmax = None
892 while True:
893 maxnum = -1
894 for path in rootdir.listdir():
895 num = parse_num(path)
896 if num is not None:
897 maxnum = max(maxnum, num)
899 # make the new directory
900 try:
901 udir = rootdir.mkdir(prefix + str(maxnum+1))
902 if lock_timeout:
903 lockfile = create_lockfile(udir)
904 atexit_remove_lockfile(lockfile)
905 except (py.error.EEXIST, py.error.ENOENT, py.error.EBUSY):
906 # race condition (1): another thread/process created the dir
907 # in the meantime - try again
908 # race condition (2): another thread/process spuriously acquired
909 # lock treating empty directory as candidate
910 # for removal - try again
911 # race condition (3): another thread/process tried to create the lock at
912 # the same time (happened in Python 3.3 on Windows)
913 # https://ci.appveyor.com/project/pytestbot/py/build/1.0.21/job/ffi85j4c0lqwsfwa
914 if lastmax == maxnum:
915 raise
916 lastmax = maxnum
917 continue
918 break
920 def get_mtime(path):
921 """ read file modification time """
922 try:
923 return path.lstat().mtime
924 except py.error.Error:
925 pass
927 garbage_prefix = prefix + 'garbage-'
929 def is_garbage(path):
930 """ check if path denotes directory scheduled for removal """
931 bn = path.basename
932 return bn.startswith(garbage_prefix)
934 # prune old directories
935 udir_time = get_mtime(udir)
936 if keep and udir_time:
937 for path in rootdir.listdir():
938 num = parse_num(path)
939 if num is not None and num <= (maxnum - keep):
940 try:
941 # try acquiring lock to remove directory as exclusive user
942 if lock_timeout:
943 create_lockfile(path)
944 except (py.error.EEXIST, py.error.ENOENT, py.error.EBUSY):
945 path_time = get_mtime(path)
946 if not path_time:
947 # assume directory doesn't exist now
948 continue
949 if abs(udir_time - path_time) < lock_timeout:
950 # assume directory with lockfile exists
951 # and lock timeout hasn't expired yet
952 continue
954 # path dir locked for exclusive use
955 # and scheduled for removal to avoid another thread/process
956 # treating it as a new directory or removal candidate
957 garbage_path = rootdir.join(garbage_prefix + str(uuid.uuid4()))
958 try:
959 path.rename(garbage_path)
960 garbage_path.remove(rec=1)
961 except KeyboardInterrupt:
962 raise
963 except: # this might be py.error.Error, WindowsError ...
964 pass
965 if is_garbage(path):
966 try:
967 path.remove(rec=1)
968 except KeyboardInterrupt:
969 raise
970 except: # this might be py.error.Error, WindowsError ...
971 pass
973 # make link...
974 try:
975 username = os.environ['USER'] #linux, et al
976 except KeyError:
977 try:
978 username = os.environ['USERNAME'] #windows
979 except KeyError:
980 username = 'current'
982 src = str(udir)
983 dest = src[:src.rfind('-')] + '-' + username
984 try:
985 os.unlink(dest)
986 except OSError:
987 pass
988 try:
989 os.symlink(src, dest)
990 except (OSError, AttributeError, NotImplementedError):
991 pass
993 return udir
994 make_numbered_dir = classmethod(make_numbered_dir)
997def copymode(src, dest):
998 """ copy permission from src to dst. """
999 import shutil
1000 shutil.copymode(src, dest)
1003def copystat(src, dest):
1004 """ copy permission, last modification time,
1005 last access time, and flags from src to dst."""
1006 import shutil
1007 shutil.copystat(str(src), str(dest))
1010def copychunked(src, dest):
1011 chunksize = 524288 # half a meg of bytes
1012 fsrc = src.open('rb')
1013 try:
1014 fdest = dest.open('wb')
1015 try:
1016 while 1:
1017 buf = fsrc.read(chunksize)
1018 if not buf:
1019 break
1020 fdest.write(buf)
1021 finally:
1022 fdest.close()
1023 finally:
1024 fsrc.close()
1027def isimportable(name):
1028 if name and (name[0].isalpha() or name[0] == '_'):
1029 name = name.replace("_", '')
1030 return not name or name.isalnum()