|
1 """ |
|
2 local path implementation. |
|
3 """ |
|
4 import sys, os, stat, re, atexit |
|
5 import py |
|
6 from py._path import common |
|
7 |
|
8 iswin32 = sys.platform == "win32" or (getattr(os, '_name', False) == 'nt') |
|
9 |
|
10 class Stat(object): |
|
11 def __getattr__(self, name): |
|
12 return getattr(self._osstatresult, "st_" + name) |
|
13 |
|
14 def __init__(self, path, osstatresult): |
|
15 self.path = path |
|
16 self._osstatresult = osstatresult |
|
17 |
|
18 def owner(self): |
|
19 if iswin32: |
|
20 raise NotImplementedError("XXX win32") |
|
21 import pwd |
|
22 entry = py.error.checked_call(pwd.getpwuid, self.uid) |
|
23 return entry[0] |
|
24 owner = property(owner, None, None, "owner of path") |
|
25 |
|
26 def group(self): |
|
27 """ return group name of file. """ |
|
28 if iswin32: |
|
29 raise NotImplementedError("XXX win32") |
|
30 import grp |
|
31 entry = py.error.checked_call(grp.getgrgid, self.gid) |
|
32 return entry[0] |
|
33 group = property(group) |
|
34 |
|
35 class PosixPath(common.PathBase): |
|
36 def chown(self, user, group, rec=0): |
|
37 """ change ownership to the given user and group. |
|
38 user and group may be specified by a number or |
|
39 by a name. if rec is True change ownership |
|
40 recursively. |
|
41 """ |
|
42 uid = getuserid(user) |
|
43 gid = getgroupid(group) |
|
44 if rec: |
|
45 for x in self.visit(rec=lambda x: x.check(link=0)): |
|
46 if x.check(link=0): |
|
47 py.error.checked_call(os.chown, str(x), uid, gid) |
|
48 py.error.checked_call(os.chown, str(self), uid, gid) |
|
49 |
|
50 def readlink(self): |
|
51 """ return value of a symbolic link. """ |
|
52 return py.error.checked_call(os.readlink, self.strpath) |
|
53 |
|
54 def mklinkto(self, oldname): |
|
55 """ posix style hard link to another name. """ |
|
56 py.error.checked_call(os.link, str(oldname), str(self)) |
|
57 |
|
58 def mksymlinkto(self, value, absolute=1): |
|
59 """ create a symbolic link with the given value (pointing to another name). """ |
|
60 if absolute: |
|
61 py.error.checked_call(os.symlink, str(value), self.strpath) |
|
62 else: |
|
63 base = self.common(value) |
|
64 # with posix local paths '/' is always a common base |
|
65 relsource = self.__class__(value).relto(base) |
|
66 reldest = self.relto(base) |
|
67 n = reldest.count(self.sep) |
|
68 target = self.sep.join(('..', )*n + (relsource, )) |
|
69 py.error.checked_call(os.symlink, target, self.strpath) |
|
70 |
|
71 def getuserid(user): |
|
72 import pwd |
|
73 if not isinstance(user, int): |
|
74 user = pwd.getpwnam(user)[2] |
|
75 return user |
|
76 |
|
77 def getgroupid(group): |
|
78 import grp |
|
79 if not isinstance(group, int): |
|
80 group = grp.getgrnam(group)[2] |
|
81 return group |
|
82 |
|
83 FSBase = not iswin32 and PosixPath or common.PathBase |
|
84 |
|
85 class LocalPath(FSBase): |
|
86 """ object oriented interface to os.path and other local filesystem |
|
87 related information. |
|
88 """ |
|
89 class ImportMismatchError(ImportError): |
|
90 """ raised on pyimport() if there is a mismatch of __file__'s""" |
|
91 |
|
92 sep = os.sep |
|
93 class Checkers(common.Checkers): |
|
94 def _stat(self): |
|
95 try: |
|
96 return self._statcache |
|
97 except AttributeError: |
|
98 try: |
|
99 self._statcache = self.path.stat() |
|
100 except py.error.ELOOP: |
|
101 self._statcache = self.path.lstat() |
|
102 return self._statcache |
|
103 |
|
104 def dir(self): |
|
105 return stat.S_ISDIR(self._stat().mode) |
|
106 |
|
107 def file(self): |
|
108 return stat.S_ISREG(self._stat().mode) |
|
109 |
|
110 def exists(self): |
|
111 return self._stat() |
|
112 |
|
113 def link(self): |
|
114 st = self.path.lstat() |
|
115 return stat.S_ISLNK(st.mode) |
|
116 |
|
117 def __new__(cls, path=None): |
|
118 """ Initialize and return a local Path instance. |
|
119 |
|
120 Path can be relative to the current directory. |
|
121 If it is None then the current working directory is taken. |
|
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 isinstance(path, common.PathBase): |
|
127 if path.__class__ == cls: |
|
128 return path |
|
129 path = path.strpath |
|
130 # initialize the path |
|
131 self = object.__new__(cls) |
|
132 if not path: |
|
133 self.strpath = os.getcwd() |
|
134 elif isinstance(path, py.builtin._basestring): |
|
135 self.strpath = os.path.abspath(os.path.normpath(str(path))) |
|
136 else: |
|
137 raise ValueError("can only pass None, Path instances " |
|
138 "or non-empty strings to LocalPath") |
|
139 assert isinstance(self.strpath, str) |
|
140 return self |
|
141 |
|
142 def __hash__(self): |
|
143 return hash(self.strpath) |
|
144 |
|
145 def __eq__(self, other): |
|
146 s1 = str(self) |
|
147 s2 = str(other) |
|
148 if iswin32: |
|
149 s1 = s1.lower() |
|
150 s2 = s2.lower() |
|
151 return s1 == s2 |
|
152 |
|
153 def __ne__(self, other): |
|
154 return not (self == other) |
|
155 |
|
156 def __lt__(self, other): |
|
157 return str(self) < str(other) |
|
158 |
|
159 def samefile(self, other): |
|
160 """ return True if 'other' references the same file as 'self'. """ |
|
161 if self == other: |
|
162 return True |
|
163 if not iswin32: |
|
164 return py.error.checked_call(os.path.samefile, str(self), str(other)) |
|
165 return False |
|
166 |
|
167 def remove(self, rec=1, ignore_errors=False): |
|
168 """ remove a file or directory (or a directory tree if rec=1). |
|
169 if ignore_errors is True, errors while removing directories will |
|
170 be ignored. |
|
171 """ |
|
172 if self.check(dir=1, link=0): |
|
173 if rec: |
|
174 # force remove of readonly files on windows |
|
175 if iswin32: |
|
176 self.chmod(448, rec=1) # octcal 0700 |
|
177 py.error.checked_call(py.std.shutil.rmtree, self.strpath, |
|
178 ignore_errors=ignore_errors) |
|
179 else: |
|
180 py.error.checked_call(os.rmdir, self.strpath) |
|
181 else: |
|
182 if iswin32: |
|
183 self.chmod(448) # octcal 0700 |
|
184 py.error.checked_call(os.remove, self.strpath) |
|
185 |
|
186 def computehash(self, hashtype="md5", chunksize=524288): |
|
187 """ return hexdigest of hashvalue for this file. """ |
|
188 try: |
|
189 try: |
|
190 import hashlib as mod |
|
191 except ImportError: |
|
192 if hashtype == "sha1": |
|
193 hashtype = "sha" |
|
194 mod = __import__(hashtype) |
|
195 hash = getattr(mod, hashtype)() |
|
196 except (AttributeError, ImportError): |
|
197 raise ValueError("Don't know how to compute %r hash" %(hashtype,)) |
|
198 f = self.open('rb') |
|
199 try: |
|
200 while 1: |
|
201 buf = f.read(chunksize) |
|
202 if not buf: |
|
203 return hash.hexdigest() |
|
204 hash.update(buf) |
|
205 finally: |
|
206 f.close() |
|
207 |
|
208 def new(self, **kw): |
|
209 """ create a modified version of this path. |
|
210 the following keyword arguments modify various path parts:: |
|
211 |
|
212 a:/some/path/to/a/file.ext |
|
213 xx drive |
|
214 xxxxxxxxxxxxxxxxx dirname |
|
215 xxxxxxxx basename |
|
216 xxxx purebasename |
|
217 xxx ext |
|
218 """ |
|
219 obj = object.__new__(self.__class__) |
|
220 drive, dirname, basename, purebasename,ext = self._getbyspec( |
|
221 "drive,dirname,basename,purebasename,ext") |
|
222 if 'basename' in kw: |
|
223 if 'purebasename' in kw or 'ext' in kw: |
|
224 raise ValueError("invalid specification %r" % kw) |
|
225 else: |
|
226 pb = kw.setdefault('purebasename', purebasename) |
|
227 try: |
|
228 ext = kw['ext'] |
|
229 except KeyError: |
|
230 pass |
|
231 else: |
|
232 if ext and not ext.startswith('.'): |
|
233 ext = '.' + ext |
|
234 kw['basename'] = pb + ext |
|
235 |
|
236 if ('dirname' in kw and not kw['dirname']): |
|
237 kw['dirname'] = drive |
|
238 else: |
|
239 kw.setdefault('dirname', dirname) |
|
240 kw.setdefault('sep', self.sep) |
|
241 obj.strpath = os.path.normpath( |
|
242 "%(dirname)s%(sep)s%(basename)s" % kw) |
|
243 return obj |
|
244 |
|
245 def _getbyspec(self, spec): |
|
246 """ see new for what 'spec' can be. """ |
|
247 res = [] |
|
248 parts = self.strpath.split(self.sep) |
|
249 |
|
250 args = filter(None, spec.split(',') ) |
|
251 append = res.append |
|
252 for name in args: |
|
253 if name == 'drive': |
|
254 append(parts[0]) |
|
255 elif name == 'dirname': |
|
256 append(self.sep.join(parts[:-1])) |
|
257 else: |
|
258 basename = parts[-1] |
|
259 if name == 'basename': |
|
260 append(basename) |
|
261 else: |
|
262 i = basename.rfind('.') |
|
263 if i == -1: |
|
264 purebasename, ext = basename, '' |
|
265 else: |
|
266 purebasename, ext = basename[:i], basename[i:] |
|
267 if name == 'purebasename': |
|
268 append(purebasename) |
|
269 elif name == 'ext': |
|
270 append(ext) |
|
271 else: |
|
272 raise ValueError("invalid part specification %r" % name) |
|
273 return res |
|
274 |
|
275 def join(self, *args, **kwargs): |
|
276 """ return a new path by appending all 'args' as path |
|
277 components. if abs=1 is used restart from root if any |
|
278 of the args is an absolute path. |
|
279 """ |
|
280 if not args: |
|
281 return self |
|
282 strpath = self.strpath |
|
283 sep = self.sep |
|
284 strargs = [str(x) for x in args] |
|
285 if kwargs.get('abs', 0): |
|
286 for i in range(len(strargs)-1, -1, -1): |
|
287 if os.path.isabs(strargs[i]): |
|
288 strpath = strargs[i] |
|
289 strargs = strargs[i+1:] |
|
290 break |
|
291 for arg in strargs: |
|
292 arg = arg.strip(sep) |
|
293 if iswin32: |
|
294 # allow unix style paths even on windows. |
|
295 arg = arg.strip('/') |
|
296 arg = arg.replace('/', sep) |
|
297 if arg: |
|
298 if not strpath.endswith(sep): |
|
299 strpath += sep |
|
300 strpath += arg |
|
301 obj = self.new() |
|
302 obj.strpath = os.path.normpath(strpath) |
|
303 return obj |
|
304 |
|
305 def open(self, mode='r'): |
|
306 """ return an opened file with the given mode. """ |
|
307 return py.error.checked_call(open, self.strpath, mode) |
|
308 |
|
309 def listdir(self, fil=None, sort=None): |
|
310 """ list directory contents, possibly filter by the given fil func |
|
311 and possibly sorted. |
|
312 """ |
|
313 if isinstance(fil, str): |
|
314 fil = common.FNMatcher(fil) |
|
315 res = [] |
|
316 for name in py.error.checked_call(os.listdir, self.strpath): |
|
317 childurl = self.join(name) |
|
318 if fil is None or fil(childurl): |
|
319 res.append(childurl) |
|
320 self._sortlist(res, sort) |
|
321 return res |
|
322 |
|
323 def size(self): |
|
324 """ return size of the underlying file object """ |
|
325 return self.stat().size |
|
326 |
|
327 def mtime(self): |
|
328 """ return last modification time of the path. """ |
|
329 return self.stat().mtime |
|
330 |
|
331 def copy(self, target, archive=False): |
|
332 """ copy path to target.""" |
|
333 assert not archive, "XXX archive-mode not supported" |
|
334 if self.check(file=1): |
|
335 if target.check(dir=1): |
|
336 target = target.join(self.basename) |
|
337 assert self!=target |
|
338 copychunked(self, target) |
|
339 else: |
|
340 def rec(p): |
|
341 return p.check(link=0) |
|
342 for x in self.visit(rec=rec): |
|
343 relpath = x.relto(self) |
|
344 newx = target.join(relpath) |
|
345 newx.dirpath().ensure(dir=1) |
|
346 if x.check(link=1): |
|
347 newx.mksymlinkto(x.readlink()) |
|
348 elif x.check(file=1): |
|
349 copychunked(x, newx) |
|
350 elif x.check(dir=1): |
|
351 newx.ensure(dir=1) |
|
352 |
|
353 def rename(self, target): |
|
354 """ rename this path to target. """ |
|
355 return py.error.checked_call(os.rename, str(self), str(target)) |
|
356 |
|
357 def dump(self, obj, bin=1): |
|
358 """ pickle object into path location""" |
|
359 f = self.open('wb') |
|
360 try: |
|
361 py.error.checked_call(py.std.pickle.dump, obj, f, bin) |
|
362 finally: |
|
363 f.close() |
|
364 |
|
365 def mkdir(self, *args): |
|
366 """ create & return the directory joined with args. """ |
|
367 p = self.join(*args) |
|
368 py.error.checked_call(os.mkdir, str(p)) |
|
369 return p |
|
370 |
|
371 def write(self, data, mode='w'): |
|
372 """ write data into path. """ |
|
373 if 'b' in mode: |
|
374 if not py.builtin._isbytes(data): |
|
375 raise ValueError("can only process bytes") |
|
376 else: |
|
377 if not py.builtin._istext(data): |
|
378 if not py.builtin._isbytes(data): |
|
379 data = str(data) |
|
380 else: |
|
381 data = py.builtin._totext(data, sys.getdefaultencoding()) |
|
382 f = self.open(mode) |
|
383 try: |
|
384 f.write(data) |
|
385 finally: |
|
386 f.close() |
|
387 |
|
388 def _ensuredirs(self): |
|
389 parent = self.dirpath() |
|
390 if parent == self: |
|
391 return self |
|
392 if parent.check(dir=0): |
|
393 parent._ensuredirs() |
|
394 if self.check(dir=0): |
|
395 try: |
|
396 self.mkdir() |
|
397 except py.error.EEXIST: |
|
398 # race condition: file/dir created by another thread/process. |
|
399 # complain if it is not a dir |
|
400 if self.check(dir=0): |
|
401 raise |
|
402 return self |
|
403 |
|
404 def ensure(self, *args, **kwargs): |
|
405 """ ensure that an args-joined path exists (by default as |
|
406 a file). if you specify a keyword argument 'dir=True' |
|
407 then the path is forced to be a directory path. |
|
408 """ |
|
409 p = self.join(*args) |
|
410 if kwargs.get('dir', 0): |
|
411 return p._ensuredirs() |
|
412 else: |
|
413 p.dirpath()._ensuredirs() |
|
414 if not p.check(file=1): |
|
415 p.open('w').close() |
|
416 return p |
|
417 |
|
418 def stat(self): |
|
419 """ Return an os.stat() tuple. """ |
|
420 return Stat(self, py.error.checked_call(os.stat, self.strpath)) |
|
421 |
|
422 def lstat(self): |
|
423 """ Return an os.lstat() tuple. """ |
|
424 return Stat(self, py.error.checked_call(os.lstat, self.strpath)) |
|
425 |
|
426 def setmtime(self, mtime=None): |
|
427 """ set modification time for the given path. if 'mtime' is None |
|
428 (the default) then the file's mtime is set to current time. |
|
429 |
|
430 Note that the resolution for 'mtime' is platform dependent. |
|
431 """ |
|
432 if mtime is None: |
|
433 return py.error.checked_call(os.utime, self.strpath, mtime) |
|
434 try: |
|
435 return py.error.checked_call(os.utime, self.strpath, (-1, mtime)) |
|
436 except py.error.EINVAL: |
|
437 return py.error.checked_call(os.utime, self.strpath, (self.atime(), mtime)) |
|
438 |
|
439 def chdir(self): |
|
440 """ change directory to self and return old current directory """ |
|
441 old = self.__class__() |
|
442 py.error.checked_call(os.chdir, self.strpath) |
|
443 return old |
|
444 |
|
445 def realpath(self): |
|
446 """ return a new path which contains no symbolic links.""" |
|
447 return self.__class__(os.path.realpath(self.strpath)) |
|
448 |
|
449 def atime(self): |
|
450 """ return last access time of the path. """ |
|
451 return self.stat().atime |
|
452 |
|
453 def __repr__(self): |
|
454 return 'local(%r)' % self.strpath |
|
455 |
|
456 def __str__(self): |
|
457 """ return string representation of the Path. """ |
|
458 return self.strpath |
|
459 |
|
460 def pypkgpath(self, pkgname=None): |
|
461 """ return the Python package path by looking for a |
|
462 pkgname. If pkgname is None look for the last |
|
463 directory upwards which still contains an __init__.py |
|
464 and whose basename is python-importable. |
|
465 Return None if a pkgpath can not be determined. |
|
466 """ |
|
467 pkgpath = None |
|
468 for parent in self.parts(reverse=True): |
|
469 if pkgname is None: |
|
470 if parent.check(file=1): |
|
471 continue |
|
472 if not isimportable(parent.basename): |
|
473 break |
|
474 if parent.join('__init__.py').check(): |
|
475 pkgpath = parent |
|
476 continue |
|
477 return pkgpath |
|
478 else: |
|
479 if parent.basename == pkgname: |
|
480 return parent |
|
481 return pkgpath |
|
482 |
|
483 def _prependsyspath(self, path): |
|
484 s = str(path) |
|
485 if s != sys.path[0]: |
|
486 #print "prepending to sys.path", s |
|
487 sys.path.insert(0, s) |
|
488 |
|
489 def chmod(self, mode, rec=0): |
|
490 """ change permissions to the given mode. If mode is an |
|
491 integer it directly encodes the os-specific modes. |
|
492 if rec is True perform recursively. |
|
493 """ |
|
494 if not isinstance(mode, int): |
|
495 raise TypeError("mode %r must be an integer" % (mode,)) |
|
496 if rec: |
|
497 for x in self.visit(rec=rec): |
|
498 py.error.checked_call(os.chmod, str(x), mode) |
|
499 py.error.checked_call(os.chmod, str(self), mode) |
|
500 |
|
501 def pyimport(self, modname=None, ensuresyspath=True): |
|
502 """ return path as an imported python module. |
|
503 if modname is None, look for the containing package |
|
504 and construct an according module name. |
|
505 The module will be put/looked up in sys.modules. |
|
506 """ |
|
507 if not self.check(): |
|
508 raise py.error.ENOENT(self) |
|
509 #print "trying to import", self |
|
510 pkgpath = None |
|
511 if modname is None: |
|
512 pkgpath = self.pypkgpath() |
|
513 if pkgpath is not None: |
|
514 if ensuresyspath: |
|
515 self._prependsyspath(pkgpath.dirpath()) |
|
516 pkg = __import__(pkgpath.basename, None, None, []) |
|
517 names = self.new(ext='').relto(pkgpath.dirpath()) |
|
518 names = names.split(self.sep) |
|
519 if names and names[-1] == "__init__": |
|
520 names.pop() |
|
521 modname = ".".join(names) |
|
522 else: |
|
523 # no package scope, still make it possible |
|
524 if ensuresyspath: |
|
525 self._prependsyspath(self.dirpath()) |
|
526 modname = self.purebasename |
|
527 mod = __import__(modname, None, None, ['__doc__']) |
|
528 if self.basename == "__init__.py": |
|
529 return mod # we don't check anything as we might |
|
530 # we in a namespace package ... too icky to check |
|
531 modfile = mod.__file__ |
|
532 if modfile[-4:] in ('.pyc', '.pyo'): |
|
533 modfile = modfile[:-1] |
|
534 elif modfile.endswith('$py.class'): |
|
535 modfile = modfile[:-9] + '.py' |
|
536 if modfile.endswith(os.path.sep + "__init__.py"): |
|
537 if self.basename != "__init__.py": |
|
538 modfile = modfile[:-12] |
|
539 |
|
540 if not self.samefile(modfile): |
|
541 raise self.ImportMismatchError(modname, modfile, self) |
|
542 return mod |
|
543 else: |
|
544 try: |
|
545 return sys.modules[modname] |
|
546 except KeyError: |
|
547 # we have a custom modname, do a pseudo-import |
|
548 mod = py.std.types.ModuleType(modname) |
|
549 mod.__file__ = str(self) |
|
550 sys.modules[modname] = mod |
|
551 try: |
|
552 py.builtin.execfile(str(self), mod.__dict__) |
|
553 except: |
|
554 del sys.modules[modname] |
|
555 raise |
|
556 return mod |
|
557 |
|
558 def sysexec(self, *argv, **popen_opts): |
|
559 """ return stdout text from executing a system child process, |
|
560 where the 'self' path points to executable. |
|
561 The process is directly invoked and not through a system shell. |
|
562 """ |
|
563 from subprocess import Popen, PIPE |
|
564 argv = map(str, argv) |
|
565 popen_opts['stdout'] = popen_opts['stderr'] = PIPE |
|
566 proc = Popen([str(self)] + list(argv), **popen_opts) |
|
567 stdout, stderr = proc.communicate() |
|
568 ret = proc.wait() |
|
569 if py.builtin._isbytes(stdout): |
|
570 stdout = py.builtin._totext(stdout, sys.getdefaultencoding()) |
|
571 if ret != 0: |
|
572 if py.builtin._isbytes(stderr): |
|
573 stderr = py.builtin._totext(stderr, sys.getdefaultencoding()) |
|
574 raise py.process.cmdexec.Error(ret, ret, str(self), |
|
575 stdout, stderr,) |
|
576 return stdout |
|
577 |
|
578 def sysfind(cls, name, checker=None): |
|
579 """ return a path object found by looking at the systems |
|
580 underlying PATH specification. If the checker is not None |
|
581 it will be invoked to filter matching paths. If a binary |
|
582 cannot be found, None is returned |
|
583 Note: This is probably not working on plain win32 systems |
|
584 but may work on cygwin. |
|
585 """ |
|
586 if os.path.isabs(name): |
|
587 p = py.path.local(name) |
|
588 if p.check(file=1): |
|
589 return p |
|
590 else: |
|
591 if iswin32: |
|
592 paths = py.std.os.environ['Path'].split(';') |
|
593 if '' not in paths and '.' not in paths: |
|
594 paths.append('.') |
|
595 try: |
|
596 systemroot = os.environ['SYSTEMROOT'] |
|
597 except KeyError: |
|
598 pass |
|
599 else: |
|
600 paths = [re.sub('%SystemRoot%', systemroot, path) |
|
601 for path in paths] |
|
602 tryadd = '', '.exe', '.com', '.bat' # XXX add more? |
|
603 else: |
|
604 paths = py.std.os.environ['PATH'].split(':') |
|
605 tryadd = ('',) |
|
606 |
|
607 for x in paths: |
|
608 for addext in tryadd: |
|
609 p = py.path.local(x).join(name, abs=True) + addext |
|
610 try: |
|
611 if p.check(file=1): |
|
612 if checker: |
|
613 if not checker(p): |
|
614 continue |
|
615 return p |
|
616 except py.error.EACCES: |
|
617 pass |
|
618 return None |
|
619 sysfind = classmethod(sysfind) |
|
620 |
|
621 def _gethomedir(cls): |
|
622 try: |
|
623 x = os.environ['HOME'] |
|
624 except KeyError: |
|
625 x = os.environ["HOMEDRIVE"] + os.environ['HOMEPATH'] |
|
626 return cls(x) |
|
627 _gethomedir = classmethod(_gethomedir) |
|
628 |
|
629 #""" |
|
630 #special class constructors for local filesystem paths |
|
631 #""" |
|
632 def get_temproot(cls): |
|
633 """ return the system's temporary directory |
|
634 (where tempfiles are usually created in) |
|
635 """ |
|
636 return py.path.local(py.std.tempfile.gettempdir()) |
|
637 get_temproot = classmethod(get_temproot) |
|
638 |
|
639 def mkdtemp(cls, rootdir=None): |
|
640 """ return a Path object pointing to a fresh new temporary directory |
|
641 (which we created ourself). |
|
642 """ |
|
643 import tempfile |
|
644 if rootdir is None: |
|
645 rootdir = cls.get_temproot() |
|
646 return cls(py.error.checked_call(tempfile.mkdtemp, dir=str(rootdir))) |
|
647 mkdtemp = classmethod(mkdtemp) |
|
648 |
|
649 def make_numbered_dir(cls, prefix='session-', rootdir=None, keep=3, |
|
650 lock_timeout = 172800): # two days |
|
651 """ return unique directory with a number greater than the current |
|
652 maximum one. The number is assumed to start directly after prefix. |
|
653 if keep is true directories with a number less than (maxnum-keep) |
|
654 will be removed. |
|
655 """ |
|
656 if rootdir is None: |
|
657 rootdir = cls.get_temproot() |
|
658 |
|
659 def parse_num(path): |
|
660 """ parse the number out of a path (if it matches the prefix) """ |
|
661 bn = path.basename |
|
662 if bn.startswith(prefix): |
|
663 try: |
|
664 return int(bn[len(prefix):]) |
|
665 except ValueError: |
|
666 pass |
|
667 |
|
668 # compute the maximum number currently in use with the |
|
669 # prefix |
|
670 lastmax = None |
|
671 while True: |
|
672 maxnum = -1 |
|
673 for path in rootdir.listdir(): |
|
674 num = parse_num(path) |
|
675 if num is not None: |
|
676 maxnum = max(maxnum, num) |
|
677 |
|
678 # make the new directory |
|
679 try: |
|
680 udir = rootdir.mkdir(prefix + str(maxnum+1)) |
|
681 except py.error.EEXIST: |
|
682 # race condition: another thread/process created the dir |
|
683 # in the meantime. Try counting again |
|
684 if lastmax == maxnum: |
|
685 raise |
|
686 lastmax = maxnum |
|
687 continue |
|
688 break |
|
689 |
|
690 # put a .lock file in the new directory that will be removed at |
|
691 # process exit |
|
692 if lock_timeout: |
|
693 lockfile = udir.join('.lock') |
|
694 mypid = os.getpid() |
|
695 if hasattr(lockfile, 'mksymlinkto'): |
|
696 lockfile.mksymlinkto(str(mypid)) |
|
697 else: |
|
698 lockfile.write(str(mypid)) |
|
699 def try_remove_lockfile(): |
|
700 # in a fork() situation, only the last process should |
|
701 # remove the .lock, otherwise the other processes run the |
|
702 # risk of seeing their temporary dir disappear. For now |
|
703 # we remove the .lock in the parent only (i.e. we assume |
|
704 # that the children finish before the parent). |
|
705 if os.getpid() != mypid: |
|
706 return |
|
707 try: |
|
708 lockfile.remove() |
|
709 except py.error.Error: |
|
710 pass |
|
711 atexit.register(try_remove_lockfile) |
|
712 |
|
713 # prune old directories |
|
714 if keep: |
|
715 for path in rootdir.listdir(): |
|
716 num = parse_num(path) |
|
717 if num is not None and num <= (maxnum - keep): |
|
718 lf = path.join('.lock') |
|
719 try: |
|
720 t1 = lf.lstat().mtime |
|
721 t2 = lockfile.lstat().mtime |
|
722 if not lock_timeout or abs(t2-t1) < lock_timeout: |
|
723 continue # skip directories still locked |
|
724 except py.error.Error: |
|
725 pass # assume that it means that there is no 'lf' |
|
726 try: |
|
727 path.remove(rec=1) |
|
728 except KeyboardInterrupt: |
|
729 raise |
|
730 except: # this might be py.error.Error, WindowsError ... |
|
731 pass |
|
732 |
|
733 # make link... |
|
734 try: |
|
735 username = os.environ['USER'] #linux, et al |
|
736 except KeyError: |
|
737 try: |
|
738 username = os.environ['USERNAME'] #windows |
|
739 except KeyError: |
|
740 username = 'current' |
|
741 |
|
742 src = str(udir) |
|
743 dest = src[:src.rfind('-')] + '-' + username |
|
744 try: |
|
745 os.unlink(dest) |
|
746 except OSError: |
|
747 pass |
|
748 try: |
|
749 os.symlink(src, dest) |
|
750 except (OSError, AttributeError): # AttributeError on win32 |
|
751 pass |
|
752 |
|
753 return udir |
|
754 make_numbered_dir = classmethod(make_numbered_dir) |
|
755 |
|
756 def copychunked(src, dest): |
|
757 chunksize = 524288 # half a meg of bytes |
|
758 fsrc = src.open('rb') |
|
759 try: |
|
760 fdest = dest.open('wb') |
|
761 try: |
|
762 while 1: |
|
763 buf = fsrc.read(chunksize) |
|
764 if not buf: |
|
765 break |
|
766 fdest.write(buf) |
|
767 finally: |
|
768 fdest.close() |
|
769 finally: |
|
770 fsrc.close() |
|
771 |
|
772 def isimportable(name): |
|
773 if name: |
|
774 if not (name[0].isalpha() or name[0] == '_'): |
|
775 return False |
|
776 name= name.replace("_", '') |
|
777 return not name or name.isalnum() |