##// END OF EJS Templates
Update bundled path.py to 4.3
Thomas Kluyver -
Show More
This diff has been collapsed as it changes many lines, (774 lines changed) Show them Hide them
@@ -1,56 +1,149 b''
1 #
2 # Copyright (c) 2010 Mikhail Gusarov
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining a copy
5 # of this software and associated documentation files (the "Software"), to deal
6 # in the Software without restriction, including without limitation the rights
7 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 # copies of the Software, and to permit persons to whom the Software is
9 # furnished to do so, subject to the following conditions:
10 #
11 # The above copyright notice and this permission notice shall be included in
12 # all copies or substantial portions of the Software.
13 #
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 # SOFTWARE.
21 #
22
1 23 """ path.py - An object representing a path to a file or directory.
2 24
3 Example:
25 Original author:
26 Jason Orendorff <jason.orendorff\x40gmail\x2ecom>
27
28 Current maintainer:
29 Jason R. Coombs <jaraco@jaraco.com>
4 30
5 from IPython.external.path import path
6 d = path('/home/guido/bin')
7 for f in d.files('*.py'):
8 f.chmod(0755)
31 Contributors:
32 Mikhail Gusarov <dottedmag@dottedmag.net>
33 Marc Abramowitz <marc@marc-abramowitz.com>
34 Jason R. Coombs <jaraco@jaraco.com>
35 Jason Chu <jchu@xentac.net>
36 Vojislav Stojkovic <vstojkovic@syntertainment.com>
9 37
10 This module requires Python 2.5 or later.
38 Example::
11 39
40 from path import path
41 d = path('/home/guido/bin')
42 for f in d.files('*.py'):
43 f.chmod(0755)
12 44
13 URL: http://pypi.python.org/pypi/path.py
14 Author: Jason Orendorff <jason.orendorff\x40gmail\x2ecom> (and others - see the url!)
15 Date: 9 Mar 2007
45 path.py requires Python 2.5 or later.
16 46 """
17 47
48 from __future__ import with_statement
49
50 import sys
51 import warnings
52 import os
53 import fnmatch
54 import glob
55 import shutil
56 import codecs
57 import hashlib
58 import errno
59 import tempfile
60 import functools
61 import operator
62 import re
63
64 try:
65 import win32security
66 except ImportError:
67 pass
18 68
19 # TODO
20 # - Tree-walking functions don't avoid symlink loops. Matt Harrison
21 # sent me a patch for this.
22 # - Bug in write_text(). It doesn't support Universal newline mode.
23 # - Better error message in listdir() when self isn't a
24 # directory. (On Windows, the error message really sucks.)
25 # - Make sure everything has a good docstring.
26 # - Add methods for regex find and replace.
27 # - guess_content_type() method?
28 # - Perhaps support arguments to touch().
69 try:
70 import pwd
71 except ImportError:
72 pass
29 73
30 from __future__ import generators
74 ################################
75 # Monkey patchy python 3 support
76 try:
77 basestring
78 except NameError:
79 basestring = str
80
81 try:
82 unicode
83 except NameError:
84 unicode = str
85
86 try:
87 os.getcwdu
88 except AttributeError:
89 os.getcwdu = os.getcwd
90
91 if sys.version < '3':
92 def u(x):
93 return codecs.unicode_escape_decode(x)[0]
94 else:
95 def u(x):
96 return x
31 97
32 import sys, warnings, os, fnmatch, glob, shutil, codecs
33 from hashlib import md5
98 o777 = 511
99 o766 = 502
100 o666 = 438
101 o554 = 364
102 ################################
34 103
35 __version__ = '2.2'
104 __version__ = '4.3'
36 105 __all__ = ['path']
37 106
38 # Platform-specific support for path.owner
39 if os.name == 'nt':
40 try:
41 import win32security
42 except ImportError:
43 win32security = None
44 else:
45 try:
46 import pwd
47 except ImportError:
48 pwd = None
49
50 107
51 108 class TreeWalkWarning(Warning):
52 109 pass
53 110
111
112 def simple_cache(func):
113 """
114 Save results for the 'using_module' classmethod.
115 When Python 3.2 is available, use functools.lru_cache instead.
116 """
117 saved_results = {}
118
119 def wrapper(cls, module):
120 if module in saved_results:
121 return saved_results[module]
122 saved_results[module] = func(cls, module)
123 return saved_results[module]
124 return wrapper
125
126
127 class ClassProperty(property):
128 def __get__(self, cls, owner):
129 return self.fget.__get__(None, owner)()
130
131
132 class multimethod(object):
133 """
134 Acts like a classmethod when invoked from the class and like an
135 instancemethod when invoked from the instance.
136 """
137 def __init__(self, func):
138 self.func = func
139
140 def __get__(self, instance, owner):
141 return (
142 functools.partial(self.func, owner) if instance is None
143 else functools.partial(self.func, owner, instance)
144 )
145
146
54 147 class path(unicode):
55 148 """ Represents a filesystem path.
56 149
@@ -58,26 +151,45 b' class path(unicode):'
58 151 counterparts in os.path.
59 152 """
60 153
154 module = os.path
155 "The path module to use for path operations."
156
157 def __init__(self, other=''):
158 if other is None:
159 raise TypeError("Invalid initial value for path: None")
160
161 @classmethod
162 @simple_cache
163 def using_module(cls, module):
164 subclass_name = cls.__name__ + '_' + module.__name__
165 bases = (cls,)
166 ns = {'module': module}
167 return type(subclass_name, bases, ns)
168
169 @ClassProperty
170 @classmethod
171 def _next_class(cls):
172 """
173 What class should be used to construct new instances from this class
174 """
175 return cls
176
61 177 # --- Special Python methods.
62 178
63 179 def __repr__(self):
64 return 'path(%s)' % unicode.__repr__(self)
180 return '%s(%s)' % (type(self).__name__, super(path, self).__repr__())
65 181
66 182 # Adding a path and a string yields a path.
67 183 def __add__(self, more):
68 184 try:
69 resultStr = unicode.__add__(self, more)
70 except TypeError: #Python bug
71 resultStr = NotImplemented
72 if resultStr is NotImplemented:
73 return resultStr
74 return self.__class__(resultStr)
185 return self._next_class(super(path, self).__add__(more))
186 except TypeError: # Python bug
187 return NotImplemented
75 188
76 189 def __radd__(self, other):
77 if isinstance(other, basestring):
78 return self.__class__(other.__add__(self))
79 else:
190 if not isinstance(other, basestring):
80 191 return NotImplemented
192 return self._next_class(other.__add__(self))
81 193
82 194 # The / operator joins paths.
83 195 def __div__(self, rel):
@@ -86,28 +198,50 b' class path(unicode):'
86 198 Join two path components, adding a separator character if
87 199 needed.
88 200 """
89 return self.__class__(os.path.join(self, rel))
201 return self._next_class(self.module.join(self, rel))
90 202
91 203 # Make the / operator work even when true division is enabled.
92 204 __truediv__ = __div__
93 205
206 def __enter__(self):
207 self._old_dir = self.getcwd()
208 os.chdir(self)
209 return self
210
211 def __exit__(self, *_):
212 os.chdir(self._old_dir)
213
214 @classmethod
94 215 def getcwd(cls):
95 216 """ Return the current working directory as a path object. """
96 217 return cls(os.getcwdu())
97 getcwd = classmethod(getcwd)
98
99 218
219 #
100 220 # --- Operations on path strings.
101 221
102 def isabs(s): return os.path.isabs(s)
103 def abspath(self): return self.__class__(os.path.abspath(self))
104 def normcase(self): return self.__class__(os.path.normcase(self))
105 def normpath(self): return self.__class__(os.path.normpath(self))
106 def realpath(self): return self.__class__(os.path.realpath(self))
107 def expanduser(self): return self.__class__(os.path.expanduser(self))
108 def expandvars(self): return self.__class__(os.path.expandvars(self))
109 def dirname(self): return self.__class__(os.path.dirname(self))
110 def basename(s): return os.path.basename(s)
222 def abspath(self):
223 return self._next_class(self.module.abspath(self))
224
225 def normcase(self):
226 return self._next_class(self.module.normcase(self))
227
228 def normpath(self):
229 return self._next_class(self.module.normpath(self))
230
231 def realpath(self):
232 return self._next_class(self.module.realpath(self))
233
234 def expanduser(self):
235 return self._next_class(self.module.expanduser(self))
236
237 def expandvars(self):
238 return self._next_class(self.module.expandvars(self))
239
240 def dirname(self):
241 return self._next_class(self.module.dirname(self))
242
243 def basename(self):
244 return self._next_class(self.module.basename(self))
111 245
112 246 def expand(self):
113 247 """ Clean up a filename by calling expandvars(),
@@ -118,23 +252,36 b' class path(unicode):'
118 252 """
119 253 return self.expandvars().expanduser().normpath()
120 254
121 def _get_namebase(self):
122 base, ext = os.path.splitext(self.name)
255 @property
256 def namebase(self):
257 """ The same as path.name, but with one file extension stripped off.
258
259 For example, path('/home/guido/python.tar.gz').name == 'python.tar.gz',
260 but path('/home/guido/python.tar.gz').namebase == 'python.tar'
261 """
262 base, ext = self.module.splitext(self.name)
123 263 return base
124 264
125 def _get_ext(self):
126 f, ext = os.path.splitext(unicode(self))
265 @property
266 def ext(self):
267 """ The file extension, for example '.py'. """
268 f, ext = self.module.splitext(self)
127 269 return ext
128 270
129 def _get_drive(self):
130 drive, r = os.path.splitdrive(self)
131 return self.__class__(drive)
271 @property
272 def drive(self):
273 """ The drive specifier, for example 'C:'.
274 This is always empty on systems that don't use drive specifiers.
275 """
276 drive, r = self.module.splitdrive(self)
277 return self._next_class(drive)
132 278
133 279 parent = property(
134 280 dirname, None, None,
135 281 """ This path's parent directory, as a new path object.
136 282
137 For example, path('/usr/local/lib/libpython.so').parent == path('/usr/local/lib')
283 For example,
284 path('/usr/local/lib/libpython.so').parent == path('/usr/local/lib')
138 285 """)
139 286
140 287 name = property(
@@ -144,28 +291,10 b' class path(unicode):'
144 291 For example, path('/usr/local/lib/libpython.so').name == 'libpython.so'
145 292 """)
146 293
147 namebase = property(
148 _get_namebase, None, None,
149 """ The same as path.name, but with one file extension stripped off.
150
151 For example, path('/home/guido/python.tar.gz').name == 'python.tar.gz',
152 but path('/home/guido/python.tar.gz').namebase == 'python.tar'
153 """)
154
155 ext = property(
156 _get_ext, None, None,
157 """ The file extension, for example '.py'. """)
158
159 drive = property(
160 _get_drive, None, None,
161 """ The drive specifier, for example 'C:'.
162 This is always empty on systems that don't use drive specifiers.
163 """)
164
165 294 def splitpath(self):
166 295 """ p.splitpath() -> Return (p.parent, p.name). """
167 parent, child = os.path.split(self)
168 return self.__class__(parent), child
296 parent, child = self.module.split(self)
297 return self._next_class(parent), child
169 298
170 299 def splitdrive(self):
171 300 """ p.splitdrive() -> Return (p.drive, <the rest of p>).
@@ -174,8 +303,8 b' class path(unicode):'
174 303 no drive specifier, p.drive is empty, so the return value
175 304 is simply (path(''), p). This is always the case on Unix.
176 305 """
177 drive, rel = os.path.splitdrive(self)
178 return self.__class__(drive), rel
306 drive, rel = self.module.splitdrive(self)
307 return self._next_class(drive), rel
179 308
180 309 def splitext(self):
181 310 """ p.splitext() -> Return (p.stripext(), p.ext).
@@ -187,8 +316,8 b' class path(unicode):'
187 316 last path segment. This has the property that if
188 317 (a, b) == p.splitext(), then a + b == p.
189 318 """
190 filename, ext = os.path.splitext(self)
191 return self.__class__(filename), ext
319 filename, ext = self.module.splitext(self)
320 return self._next_class(filename), ext
192 321
193 322 def stripext(self):
194 323 """ p.stripext() -> Remove one file extension from the path.
@@ -198,36 +327,39 b' class path(unicode):'
198 327 """
199 328 return self.splitext()[0]
200 329
201 if hasattr(os.path, 'splitunc'):
202 def splitunc(self):
203 unc, rest = os.path.splitunc(self)
204 return self.__class__(unc), rest
205
206 def _get_uncshare(self):
207 unc, r = os.path.splitunc(self)
208 return self.__class__(unc)
330 def splitunc(self):
331 unc, rest = self.module.splitunc(self)
332 return self._next_class(unc), rest
209 333
210 uncshare = property(
211 _get_uncshare, None, None,
212 """ The UNC mount point for this path.
213 This is empty for paths on local drives. """)
334 @property
335 def uncshare(self):
336 """
337 The UNC mount point for this path.
338 This is empty for paths on local drives.
339 """
340 unc, r = self.module.splitunc(self)
341 return self._next_class(unc)
214 342
215 def joinpath(self, *args):
216 """ Join two or more path components, adding a separator
217 character (os.sep) if needed. Returns a new path
218 object.
343 @multimethod
344 def joinpath(cls, first, *others):
345 """
346 Join first to zero or more path components, adding a separator
347 character (first.module.sep) if needed. Returns a new instance of
348 first._next_class.
219 349 """
220 return self.__class__(os.path.join(self, *args))
350 if not isinstance(first, cls):
351 first = cls(first)
352 return first._next_class(first.module.join(first, *others))
221 353
222 354 def splitall(self):
223 355 r""" Return a list of the path components in this path.
224 356
225 357 The first item in the list will be a path. Its value will be
226 358 either os.curdir, os.pardir, empty, or the root directory of
227 this path (for example, '/' or 'C:\\'). The other items in
359 this path (for example, ``'/'`` or ``'C:\\'``). The other items in
228 360 the list will be strings.
229 361
230 path.path.joinpath(*result) will yield the original path.
362 ``path.path.joinpath(*result)`` will yield the original path.
231 363 """
232 364 parts = []
233 365 loc = self
@@ -241,11 +373,11 b' class path(unicode):'
241 373 parts.reverse()
242 374 return parts
243 375
244 def relpath(self):
376 def relpath(self, start='.'):
245 377 """ Return this path as a relative path,
246 based from the current working directory.
378 based from start, which defaults to the current working directory.
247 379 """
248 cwd = self.__class__(os.getcwdu())
380 cwd = self._next_class(start)
249 381 return cwd.relpathto(self)
250 382
251 383 def relpathto(self, dest):
@@ -256,20 +388,20 b' class path(unicode):'
256 388 dest.abspath().
257 389 """
258 390 origin = self.abspath()
259 dest = self.__class__(dest).abspath()
391 dest = self._next_class(dest).abspath()
260 392
261 393 orig_list = origin.normcase().splitall()
262 394 # Don't normcase dest! We want to preserve the case.
263 395 dest_list = dest.splitall()
264 396
265 if orig_list[0] != os.path.normcase(dest_list[0]):
397 if orig_list[0] != self.module.normcase(dest_list[0]):
266 398 # Can't get here from there.
267 399 return dest
268 400
269 401 # Find the location where the two paths start to differ.
270 402 i = 0
271 403 for start_seg, dest_seg in zip(orig_list, dest_list):
272 if start_seg != os.path.normcase(dest_seg):
404 if start_seg != self.module.normcase(dest_seg):
273 405 break
274 406 i += 1
275 407
@@ -283,8 +415,8 b' class path(unicode):'
283 415 # If they happen to be identical, use os.curdir.
284 416 relpath = os.curdir
285 417 else:
286 relpath = os.path.join(*segments)
287 return self.__class__(relpath)
418 relpath = self.module.join(*segments)
419 return self._next_class(relpath)
288 420
289 421 # --- Listing, searching, walking, and matching
290 422
@@ -313,7 +445,7 b' class path(unicode):'
313 445
314 446 With the optional 'pattern' argument, this only lists
315 447 directories whose names match the given pattern. For
316 example, d.dirs('build-*').
448 example, ``d.dirs('build-*')``.
317 449 """
318 450 return [p for p in self.listdir(pattern) if p.isdir()]
319 451
@@ -325,9 +457,9 b' class path(unicode):'
325 457
326 458 With the optional 'pattern' argument, this only lists files
327 459 whose names match the given pattern. For example,
328 d.files('*.pyc').
460 ``d.files('*.pyc')``.
329 461 """
330
462
331 463 return [p for p in self.listdir(pattern) if p.isfile()]
332 464
333 465 def walk(self, pattern=None, errors='strict'):
@@ -388,7 +520,7 b' class path(unicode):'
388 520
389 521 With the optional 'pattern' argument, this yields only
390 522 directories whose names match the given pattern. For
391 example, mydir.walkdirs('*test') yields only directories
523 example, ``mydir.walkdirs('*test')`` yields only directories
392 524 with names ending in 'test'.
393 525
394 526 The errors= keyword argument controls behavior when an
@@ -424,7 +556,7 b' class path(unicode):'
424 556
425 557 The optional argument, pattern, limits the results to files
426 558 with names that match the pattern. For example,
427 mydir.walkfiles('*.tmp') yields only files with the .tmp
559 ``mydir.walkfiles('*.tmp')`` yields only files with the .tmp
428 560 extension.
429 561 """
430 562 if errors not in ('strict', 'warn', 'ignore'):
@@ -471,7 +603,7 b' class path(unicode):'
471 603 """ Return True if self.name matches the given pattern.
472 604
473 605 pattern - A filename pattern with wildcards,
474 for example '*.py'.
606 for example ``'*.py'``.
475 607 """
476 608 return fnmatch.fnmatch(self.name, pattern)
477 609
@@ -483,23 +615,40 b' class path(unicode):'
483 615 For example, path('/users').glob('*/bin/*') returns a list
484 616 of all the files users have in their bin directories.
485 617 """
486 cls = self.__class__
487 return [cls(s) for s in glob.glob(unicode(self / pattern))]
488
618 cls = self._next_class
619 return [cls(s) for s in glob.glob(self / pattern)]
489 620
621 #
490 622 # --- Reading or writing an entire file at once.
491 623
492 def open(self, mode='r'):
624 def open(self, *args, **kwargs):
493 625 """ Open this file. Return a file object. """
494 return open(self, mode)
626 return open(self, *args, **kwargs)
495 627
496 628 def bytes(self):
497 629 """ Open this file, read all bytes, return them as a string. """
498 f = self.open('rb')
499 try:
630 with self.open('rb') as f:
500 631 return f.read()
501 finally:
502 f.close()
632
633 def chunks(self, size, *args, **kwargs):
634 """ Returns a generator yielding chunks of the file, so it can
635 be read piece by piece with a simple for loop.
636
637 Any argument you pass after `size` will be passed to `open()`.
638
639 :example:
640
641 >>> for chunk in path("file.txt").chunk(8192):
642 ... print(chunk)
643
644 This will read the file by chunks of 8192 bytes.
645 """
646 with open(self, *args, **kwargs) as f:
647 while True:
648 d = f.read(size)
649 if not d:
650 break
651 yield d
503 652
504 653 def write_bytes(self, bytes, append=False):
505 654 """ Open this file and write the given bytes to it.
@@ -511,17 +660,14 b' class path(unicode):'
511 660 mode = 'ab'
512 661 else:
513 662 mode = 'wb'
514 f = self.open(mode)
515 try:
663 with self.open(mode) as f:
516 664 f.write(bytes)
517 finally:
518 f.close()
519 665
520 666 def text(self, encoding=None, errors='strict'):
521 667 r""" Open this file, read it in, return the content as a string.
522 668
523 This uses 'U' mode in Python 2.3 and later, so '\r\n' and '\r'
524 are automatically translated to '\n'.
669 This method uses 'U' mode, so '\r\n' and '\r' are automatically
670 translated to '\n'.
525 671
526 672 Optional arguments:
527 673
@@ -534,27 +680,22 b' class path(unicode):'
534 680 """
535 681 if encoding is None:
536 682 # 8-bit
537 f = self.open('U')
538 try:
683 with self.open('U') as f:
539 684 return f.read()
540 finally:
541 f.close()
542 685 else:
543 686 # Unicode
544 f = codecs.open(self, 'r', encoding, errors)
545 # (Note - Can't use 'U' mode here, since codecs.open
546 # doesn't support 'U' mode, even in Python 2.3.)
547 try:
687 with codecs.open(self, 'r', encoding, errors) as f:
688 # (Note - Can't use 'U' mode here, since codecs.open
689 # doesn't support 'U' mode.)
548 690 t = f.read()
549 finally:
550 f.close()
551 return (t.replace(u'\r\n', u'\n')
552 .replace(u'\r\x85', u'\n')
553 .replace(u'\r', u'\n')
554 .replace(u'\x85', u'\n')
555 .replace(u'\u2028', u'\n'))
556
557 def write_text(self, text, encoding=None, errors='strict', linesep=os.linesep, append=False):
691 return (t.replace(u('\r\n'), u('\n'))
692 .replace(u('\r\x85'), u('\n'))
693 .replace(u('\r'), u('\n'))
694 .replace(u('\x85'), u('\n'))
695 .replace(u('\u2028'), u('\n')))
696
697 def write_text(self, text, encoding=None, errors='strict',
698 linesep=os.linesep, append=False):
558 699 r""" Write the given text to this file.
559 700
560 701 The default behavior is to overwrite any existing file;
@@ -622,12 +763,12 b' class path(unicode):'
622 763 if linesep is not None:
623 764 # Convert all standard end-of-line sequences to
624 765 # ordinary newline characters.
625 text = (text.replace(u'\r\n', u'\n')
626 .replace(u'\r\x85', u'\n')
627 .replace(u'\r', u'\n')
628 .replace(u'\x85', u'\n')
629 .replace(u'\u2028', u'\n'))
630 text = text.replace(u'\n', linesep)
766 text = (text.replace(u('\r\n'), u('\n'))
767 .replace(u('\r\x85'), u('\n'))
768 .replace(u('\r'), u('\n'))
769 .replace(u('\x85'), u('\n'))
770 .replace(u('\u2028'), u('\n')))
771 text = text.replace(u('\n'), linesep)
631 772 if encoding is None:
632 773 encoding = sys.getdefaultencoding()
633 774 bytes = text.encode(encoding, errors)
@@ -658,14 +799,11 b' class path(unicode):'
658 799 translated to '\n'. If false, newline characters are
659 800 stripped off. Default is True.
660 801
661 This uses 'U' mode in Python 2.3 and later.
802 This uses 'U' mode.
662 803 """
663 804 if encoding is None and retain:
664 f = self.open('U')
665 try:
805 with self.open('U') as f:
666 806 return f.readlines()
667 finally:
668 f.close()
669 807 else:
670 808 return self.text(encoding, errors).splitlines(retain)
671 809
@@ -707,18 +845,17 b' class path(unicode):'
707 845 mode = 'ab'
708 846 else:
709 847 mode = 'wb'
710 f = self.open(mode)
711 try:
848 with self.open(mode) as f:
712 849 for line in lines:
713 850 isUnicode = isinstance(line, unicode)
714 851 if linesep is not None:
715 852 # Strip off any existing line-end and add the
716 853 # specified linesep string.
717 854 if isUnicode:
718 if line[-2:] in (u'\r\n', u'\x0d\x85'):
855 if line[-2:] in (u('\r\n'), u('\x0d\x85')):
719 856 line = line[:-2]
720 elif line[-1:] in (u'\r', u'\n',
721 u'\x85', u'\u2028'):
857 elif line[-1:] in (u('\r'), u('\n'),
858 u('\x85'), u('\u2028')):
722 859 line = line[:-1]
723 860 else:
724 861 if line[-2:] == '\r\n':
@@ -731,57 +868,91 b' class path(unicode):'
731 868 encoding = sys.getdefaultencoding()
732 869 line = line.encode(encoding, errors)
733 870 f.write(line)
734 finally:
735 f.close()
736 871
737 872 def read_md5(self):
738 873 """ Calculate the md5 hash for this file.
739 874
740 875 This reads through the entire file.
741 876 """
742 f = self.open('rb')
743 try:
744 m = md5()
745 while True:
746 d = f.read(8192)
747 if not d:
748 break
749 m.update(d)
750 finally:
751 f.close()
752 return m.digest()
877 return self.read_hash('md5')
878
879 def _hash(self, hash_name):
880 """ Returns a hash object for the file at the current path.
881
882 `hash_name` should be a hash algo name such as 'md5' or 'sha1'
883 that's available in the `hashlib` module.
884 """
885 m = hashlib.new(hash_name)
886 for chunk in self.chunks(8192):
887 m.update(chunk)
888 return m
889
890 def read_hash(self, hash_name):
891 """ Calculate given hash for this file.
892
893 List of supported hashes can be obtained from hashlib package. This
894 reads the entire file.
895 """
896 return self._hash(hash_name).digest()
897
898 def read_hexhash(self, hash_name):
899 """ Calculate given hash for this file, returning hexdigest.
900
901 List of supported hashes can be obtained from hashlib package. This
902 reads the entire file.
903 """
904 return self._hash(hash_name).hexdigest()
753 905
754 906 # --- Methods for querying the filesystem.
755 # N.B. We can't assign the functions directly, because they may on some
756 # platforms be implemented in C, and compiled functions don't get bound.
757 # See gh-737 for discussion of this.
907 # N.B. On some platforms, the os.path functions may be implemented in C
908 # (e.g. isdir on Windows, Python 3.2.2), and compiled functions don't get
909 # bound. Playing it safe and wrapping them all in method calls.
758 910
759 def exists(s): return os.path.exists(s)
760 def isdir(s): return os.path.isdir(s)
761 def isfile(s): return os.path.isfile(s)
762 def islink(s): return os.path.islink(s)
763 def ismount(s): return os.path.ismount(s)
911 def isabs(self):
912 return self.module.isabs(self)
764 913
765 if hasattr(os.path, 'samefile'):
766 def samefile(s, o): return os.path.samefile(s, o)
914 def exists(self):
915 return self.module.exists(self)
916
917 def isdir(self):
918 return self.module.isdir(self)
919
920 def isfile(self):
921 return self.module.isfile(self)
922
923 def islink(self):
924 return self.module.islink(self)
925
926 def ismount(self):
927 return self.module.ismount(self)
928
929 def samefile(self, other):
930 return self.module.samefile(self, other)
931
932 def getatime(self):
933 return self.module.getatime(self)
767 934
768 def getatime(s): return os.path.getatime(s)
769 935 atime = property(
770 936 getatime, None, None,
771 937 """ Last access time of the file. """)
772 938
773 def getmtime(s): return os.path.getmtime(s)
939 def getmtime(self):
940 return self.module.getmtime(self)
941
774 942 mtime = property(
775 943 getmtime, None, None,
776 944 """ Last-modified time of the file. """)
777 945
778 if hasattr(os.path, 'getctime'):
779 def getctime(s): return os.path.getctime(s)
780 ctime = property(
781 getctime, None, None,
782 """ Creation time of the file. """)
946 def getctime(self):
947 return self.module.getctime(self)
948
949 ctime = property(
950 getctime, None, None,
951 """ Creation time of the file. """)
952
953 def getsize(self):
954 return self.module.getsize(self)
783 955
784 def getsize(s): return os.path.getsize(s)
785 956 size = property(
786 957 getsize, None, None,
787 958 """ Size of the file, in bytes. """)
@@ -802,27 +973,36 b' class path(unicode):'
802 973 """ Like path.stat(), but do not follow symbolic links. """
803 974 return os.lstat(self)
804 975
805 def get_owner(self):
806 r""" Return the name of the owner of this file or directory.
976 def __get_owner_windows(self):
977 r"""
978 Return the name of the owner of this file or directory. Follow
979 symbolic links.
807 980
808 This follows symbolic links.
981 Return a name of the form ur'DOMAIN\User Name'; may be a group.
982 """
983 desc = win32security.GetFileSecurity(
984 self, win32security.OWNER_SECURITY_INFORMATION)
985 sid = desc.GetSecurityDescriptorOwner()
986 account, domain, typecode = win32security.LookupAccountSid(None, sid)
987 return domain + u('\\') + account
809 988
810 On Windows, this returns a name of the form ur'DOMAIN\User Name'.
811 On Windows, a group can own a file or directory.
989 def __get_owner_unix(self):
812 990 """
813 if os.name == 'nt':
814 if win32security is None:
815 raise Exception("path.owner requires win32all to be installed")
816 desc = win32security.GetFileSecurity(
817 self, win32security.OWNER_SECURITY_INFORMATION)
818 sid = desc.GetSecurityDescriptorOwner()
819 account, domain, typecode = win32security.LookupAccountSid(None, sid)
820 return domain + u'\\' + account
821 else:
822 if pwd is None:
823 raise NotImplementedError("path.owner is not implemented on this platform.")
824 st = self.stat()
825 return pwd.getpwuid(st.st_uid).pw_name
991 Return the name of the owner of this file or directory. Follow
992 symbolic links.
993 """
994 st = self.stat()
995 return pwd.getpwuid(st.st_uid).pw_name
996
997 def __get_owner_not_implemented(self):
998 raise NotImplementedError("Ownership not available on this platform.")
999
1000 if 'win32security' in globals():
1001 get_owner = __get_owner_windows
1002 elif 'pwd' in globals():
1003 get_owner = __get_owner_unix
1004 else:
1005 get_owner = __get_owner_not_implemented
826 1006
827 1007 owner = property(
828 1008 get_owner, None, None,
@@ -837,41 +1017,85 b' class path(unicode):'
837 1017 def pathconf(self, name):
838 1018 return os.pathconf(self, name)
839 1019
840
1020 #
841 1021 # --- Modifying operations on files and directories
842 1022
843 1023 def utime(self, times):
844 1024 """ Set the access and modified times of this file. """
845 1025 os.utime(self, times)
1026 return self
846 1027
847 1028 def chmod(self, mode):
848 1029 os.chmod(self, mode)
1030 return self
849 1031
850 1032 if hasattr(os, 'chown'):
851 def chown(self, uid, gid):
1033 def chown(self, uid=-1, gid=-1):
852 1034 os.chown(self, uid, gid)
1035 return self
853 1036
854 1037 def rename(self, new):
855 1038 os.rename(self, new)
1039 return self._next_class(new)
856 1040
857 1041 def renames(self, new):
858 1042 os.renames(self, new)
1043 return self._next_class(new)
859 1044
860
1045 #
861 1046 # --- Create/delete operations on directories
862 1047
863 def mkdir(self, mode=0o777):
1048 def mkdir(self, mode=o777):
864 1049 os.mkdir(self, mode)
1050 return self
1051
1052 def mkdir_p(self, mode=o777):
1053 try:
1054 self.mkdir(mode)
1055 except OSError:
1056 _, e, _ = sys.exc_info()
1057 if e.errno != errno.EEXIST:
1058 raise
1059 return self
865 1060
866 def makedirs(self, mode=0o777):
1061 def makedirs(self, mode=o777):
867 1062 os.makedirs(self, mode)
1063 return self
1064
1065 def makedirs_p(self, mode=o777):
1066 try:
1067 self.makedirs(mode)
1068 except OSError:
1069 _, e, _ = sys.exc_info()
1070 if e.errno != errno.EEXIST:
1071 raise
1072 return self
868 1073
869 1074 def rmdir(self):
870 1075 os.rmdir(self)
1076 return self
1077
1078 def rmdir_p(self):
1079 try:
1080 self.rmdir()
1081 except OSError:
1082 _, e, _ = sys.exc_info()
1083 if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
1084 raise
1085 return self
871 1086
872 1087 def removedirs(self):
873 1088 os.removedirs(self)
1089 return self
874 1090
1091 def removedirs_p(self):
1092 try:
1093 self.removedirs()
1094 except OSError:
1095 _, e, _ = sys.exc_info()
1096 if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
1097 raise
1098 return self
875 1099
876 1100 # --- Modifying operations on files
877 1101
@@ -879,16 +1103,31 b' class path(unicode):'
879 1103 """ Set the access/modified times of this file to the current time.
880 1104 Create the file if it does not exist.
881 1105 """
882 fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0o666)
1106 fd = os.open(self, os.O_WRONLY | os.O_CREAT, o666)
883 1107 os.close(fd)
884 1108 os.utime(self, None)
1109 return self
885 1110
886 1111 def remove(self):
887 1112 os.remove(self)
1113 return self
1114
1115 def remove_p(self):
1116 try:
1117 self.unlink()
1118 except OSError:
1119 _, e, _ = sys.exc_info()
1120 if e.errno != errno.ENOENT:
1121 raise
1122 return self
888 1123
889 1124 def unlink(self):
890 1125 os.unlink(self)
1126 return self
891 1127
1128 def unlink_p(self):
1129 self.remove_p()
1130 return self
892 1131
893 1132 # --- Links
894 1133
@@ -896,11 +1135,13 b' class path(unicode):'
896 1135 def link(self, newpath):
897 1136 """ Create a hard link at 'newpath', pointing to this file. """
898 1137 os.link(self, newpath)
1138 return self._next_class(newpath)
899 1139
900 1140 if hasattr(os, 'symlink'):
901 1141 def symlink(self, newlink):
902 1142 """ Create a symbolic link at 'newlink', pointing here. """
903 1143 os.symlink(self, newlink)
1144 return self._next_class(newlink)
904 1145
905 1146 if hasattr(os, 'readlink'):
906 1147 def readlink(self):
@@ -908,7 +1149,7 b' class path(unicode):'
908 1149
909 1150 The result may be an absolute or a relative path.
910 1151 """
911 return self.__class__(os.readlink(self))
1152 return self._next_class(os.readlink(self))
912 1153
913 1154 def readlinkabs(self):
914 1155 """ Return the path to which this symbolic link points.
@@ -921,7 +1162,7 b' class path(unicode):'
921 1162 else:
922 1163 return (self.parent / p).abspath()
923 1164
924
1165 #
925 1166 # --- High-level functions from shutil
926 1167
927 1168 copyfile = shutil.copyfile
@@ -934,7 +1175,21 b' class path(unicode):'
934 1175 move = shutil.move
935 1176 rmtree = shutil.rmtree
936 1177
1178 def rmtree_p(self):
1179 try:
1180 self.rmtree()
1181 except OSError:
1182 _, e, _ = sys.exc_info()
1183 if e.errno != errno.ENOENT:
1184 raise
1185 return self
1186
1187 def chdir(self):
1188 os.chdir(self)
1189
1190 cd = chdir
937 1191
1192 #
938 1193 # --- Special stuff from os
939 1194
940 1195 if hasattr(os, 'chroot'):
@@ -944,4 +1199,69 b' class path(unicode):'
944 1199 if hasattr(os, 'startfile'):
945 1200 def startfile(self):
946 1201 os.startfile(self)
1202 return self
947 1203
1204
1205 class tempdir(path):
1206 """
1207 A temporary directory via tempfile.mkdtemp, and constructed with the
1208 same parameters that you can use as a context manager.
1209
1210 Example:
1211
1212 with tempdir() as d:
1213 # do stuff with the path object "d"
1214
1215 # here the directory is deleted automatically
1216 """
1217
1218 @ClassProperty
1219 @classmethod
1220 def _next_class(cls):
1221 return path
1222
1223 def __new__(cls, *args, **kwargs):
1224 dirname = tempfile.mkdtemp(*args, **kwargs)
1225 return super(tempdir, cls).__new__(cls, dirname)
1226
1227 def __init__(self, *args, **kwargs):
1228 pass
1229
1230 def __enter__(self):
1231 return self
1232
1233 def __exit__(self, exc_type, exc_value, traceback):
1234 if not exc_value:
1235 self.rmtree()
1236
1237
1238 def _permission_mask(mode):
1239 """
1240 Convert a Unix chmod symbolic mode like 'ugo+rwx' to a function
1241 suitable for applying to a mask to affect that change.
1242
1243 >>> mask = _permission_mask('ugo+rwx')
1244 >>> oct(mask(o554))
1245 'o777'
1246
1247 >>> oct(_permission_mask('gw-x')(o777))
1248 'o766'
1249 """
1250 parsed = re.match('(?P<who>[ugo]+)(?P<op>[-+])(?P<what>[rwx]+)$', mode)
1251 if not parsed:
1252 raise ValueError("Unrecognized symbolic mode", mode)
1253 spec_map = dict(r=4, w=2, x=1)
1254 spec = reduce(operator.or_, [spec_map[perm]
1255 for perm in parsed.group('what')])
1256 # now apply spec to each in who
1257 shift_map = dict(u=6, g=3, o=0)
1258 mask = reduce(operator.or_, [spec << shift_map[subj]
1259 for subj in parsed.group('who')])
1260
1261 op = parsed.group('op')
1262 # if op is -, invert the mask
1263 if op == '-':
1264 mask ^= o777
1265
1266 op_map = {'+': operator.or_, '-': operator.and_}
1267 return functools.partial(op_map[op], mask)
General Comments 0
You need to be logged in to leave comments. Login now