You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1031 lines
36KB

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