This wiki is in the process of being archived due to lack of usage and the resources necessary to serve it — predominately to bots, crawlers, and LLM companies. Edits are discouraged.
Pages are preserved as they were at the time of archival. For current information, please visit python.org.
If a change to this archive is absolutely needed, requests can be made via the infrastructure@python.org mailing list.
   1 """ path.py - An object representing a path to a file or a directory.
   2 
   3 Based on the path module by Jason Orendorff
   4 (http://www.jorendorff.com/articles/python/path)
   5 
   6 Written by Noam Raphael to show the idea of using a tuple instead of
   7 a string, and to reduce the number of methods.
   8 
   9 Currently only implements posix and nt paths - more can be added.
  10 
  11 """
  12 
  13 import os
  14 import stat
  15 import itertools
  16 import fnmatch
  17 import re
  18 import string
  19 
  20 class StatWrapper(object):
  21     """ A wrapper around stat_result objects which gives additional properties.
  22     
  23     This object is a wrapper around a stat_result object. It allows access
  24     to all the original object's attributes, and adds a few convinient
  25     properties, by using the stat module.
  26     
  27     This object should have been a subclass posix.stat_result - it simply
  28     isn't possible currently. This functionality may also be integrated into
  29     the original type.
  30     """
  31     
  32     __slots__ = ['_stat']
  33     
  34     def __init__(self, stat):
  35         self._stat = stat
  36         
  37     def __getattribute__(self, attr, *default):
  38         try:
  39             return object.__getattribute__(self, attr, *default)
  40         except AttributeError:
  41             return getattr(self._stat, attr, *default)
  42 
  43     # Mode properties
  44     
  45     @property
  46     def isdir(self):
  47         return stat.S_ISDIR(self.st_mode)
  48     @property
  49     def isfile(self):
  50         return stat.S_ISREG(self.st_mode)
  51     @property
  52     def islink(self):
  53         return stat.S_ISLNK(self.st_mode)
  54     
  55     # Easier names properties
  56 
  57     @property
  58     def size(self):
  59         return self.st_size
  60     @property
  61     def mtime(self):
  62         return self.st_mtime
  63     @property
  64     def atime(self):
  65         return self.st_atime
  66     @property
  67     def ctime(self):
  68         return self.st_ctime
  69 
  70 
  71 class BasePath(tuple):
  72     """ The base, abstract, path type.
  73     
  74     The OS-specific path types inherit from it.
  75     """
  76 
  77     # ----------------------------------------------------------------
  78     # We start with methods which don't use system calls - they just
  79     # manipulate paths.
  80 
  81     class _BaseRoot(object):
  82         """ Represents a start location for a path.
  83         
  84         A Root is an object which may be the first element of a path tuple,
  85         and represents from where to start the path.
  86         
  87         On posix, there's only one: ROOT (singleton).
  88         On nt, there are a few:
  89           CURROOT - the root of the current drive (singleton)
  90           Drive(letter) - the root of a specific drive
  91           UnrootedDrive(letter) - the current working directory on a specific
  92                                   drive
  93           UNCRoot(host, mountpoint) - a UNC mount point
  94 
  95         The class for each OS has its own root classes, which should inherit
  96         from _OSBaseRoot.
  97 
  98         str(root) should return the string name of the root. The string should
  99         identify the root: two root elements with the same string should have
 100         the same meaning. To allow meaningful sorting of path objects, root
 101         objects can be compared to strings and other root objects. They are
 102         smaller than all strings, and are compared with other root objects
 103         according to their string name.
 104 
 105         Every Root object should contain the "isabs" attribute, which is True
 106         if changes in the current working directory won't change the meaning
 107         of the root and False otherwise. (CURROOT and UnrootedDrive aren't
 108         absolute)
 109         If isabs is True, it should also implement the abspath() method, which
 110         should return an absolute path object, equivalent to the root when the
 111         call was made.
 112         """
 113         isabs = None
 114 
 115         def abspath(self):
 116             if self.abspath:
 117                 raise NotImplementedError, 'This root is already absolute'
 118             else:
 119                 raise NotImplementedError, 'abspath is abstract'
 120 
 121         def __str__(self):
 122             raise NotImplementedError, '__str__ is abstract'
 123 
 124         def __cmp__(self, other):
 125             if isinstance(other, str):
 126                 return -1
 127             elif isinstance(other, BasePath._BaseRoot):
 128                 return cmp(str(self), str(other))
 129             else:
 130                 raise TypeError, 'Comparison not defined'
 131 
 132         def __hash__(self):
 133             # This allows path objects to be hashable
 134             return hash(str(self))
 135 
 136     # _OSBaseRoot should be the base of the OS-specific root classes, which
 137     # should inherit from _BaseRoot
 138     _OSBaseRoot = None
 139 
 140     # These string constants should be filled by subclasses - they are real
 141     # directory names
 142     curdir = None
 143     pardir = None
 144 
 145     # These string constants are used by default implementations of methods,
 146     # but are not part of the interface - the whole idea is for the interface
 147     # to hide those details.
 148     _sep = None
 149     _altsep = None
 150 
 151     @staticmethod
 152     def _parse_str(pathstr):
 153         # Concrete path classes should implement _parse_str to get a path
 154         # string and return an iterable over path elements.
 155         raise NotImplementedError, '_parse_str is abstract'
 156 
 157     @staticmethod
 158     def normcasestr(string):
 159         """ Normalize the case of one path element.
 160         
 161         This default implementation returns string unchanged. On
 162         case-insensitive platforms, it returns the normalized string.
 163         """
 164         return string
 165 
 166     # We make this method a property, to show that it doesn't use any
 167     # system calls.
 168     # Case-sensitive subclasses can redefine it to return self.
 169     @property
 170     def normcase(self):
 171         """ Return an equivalent path with case-normalized elements. """
 172         if self.isrel:
 173             return self.__class__(self.normcasestr(element)
 174                                   for element in self)
 175         else:
 176             def gen():
 177                 it = iter(self)
 178                 yield it.next()
 179                 for element in it:
 180                     yield self.normcasestr(element)
 181             return self.__class__(gen())
 182 
 183     @classmethod
 184     def _normalize_elements(cls, elements):
 185         # This method gets an iterable over path elements.
 186         # It should return an iterator over normalized path elements -
 187         # that is, curdir elements should be ignored.
 188         
 189         for i, element in enumerate(elements):
 190             if isinstance(element, str):
 191                 if element != cls.curdir:
 192                     if (not element or
 193                         cls._sep in element or
 194                         (cls._altsep and cls._altsep in element)):
 195                         # Those elements will cause path(str(x)) != x
 196                         raise ValueError, "Element %r is invalid" % element
 197                     yield element
 198             elif i == 0 and isinstance(element, cls._OSBaseRoot):
 199                 yield element
 200             else:
 201                 raise TypeError, "Element %r is of a wrong type" % element
 202 
 203     def __new__(cls, arg=None):
 204         """ Create a new path object.
 205         
 206         If arg isn't given, an empty path, which represents the current
 207         working directory, is returned.
 208         If arg is a string, it is parsed into a logical path.
 209         If arg is an iterable over path elements, a new path is created from
 210         them.
 211         """
 212         if arg is None:
 213             return tuple.__new__(cls)
 214         elif type(arg) is cls:
 215             return arg
 216         elif isinstance(arg, str):
 217             return tuple.__new__(cls, cls._parse_str(arg))
 218         else:
 219             return tuple.__new__(cls, cls._normalize_elements(arg))
 220 
 221     def __init__(self, arg=None):
 222         # Since paths are immutable, we can cache the string representation
 223         self._cached_str = None
 224 
 225     def _build_str(self):
 226         # Return a string representation of self.
 227         # 
 228         # This is a default implementation, which may be overriden by
 229         # subclasses (form example, MacPath)
 230         if not self:
 231             return self.curdir
 232         elif isinstance(self[0], self._OSBaseRoot):
 233             return str(self[0]) + self._sep.join(self[1:])
 234         else:
 235             return self._sep.join(self)
 236 
 237     def __str__(self):
 238         """ Return a string representation of self. """
 239         if self._cached_str is None:
 240             self._cached_str = self._build_str()
 241         return self._cached_str
 242 
 243     def __repr__(self):
 244         # We want path, not the real class name.
 245         return 'path(%r)' % str(self)
 246 
 247     @property
 248     def isabs(self):
 249         """ Return whether this path represent an absolute path.
 250 
 251         An absolute path is a path whose meaning doesn't change when the
 252         the current working directory changes.
 253         
 254         (Note that this is not the same as "not self.isrelative")
 255         """
 256         return len(self) > 0 and \
 257                isinstance(self[0], self._OSBaseRoot) and \
 258                self[0].isabs
 259 
 260     @property
 261     def isrel(self):
 262         """ Return whether this path represents a relative path.
 263 
 264         A relative path is a path without a root element, so it can be
 265         concatenated to other paths.
 266 
 267         (Note that this is not the same as "not self.isabs")
 268         """
 269         return len(self) == 0 or \
 270                not isinstance(self[0], self._OSBaseRoot)
 271 
 272     # Wrap a few tuple methods to return path objects
 273 
 274     def __add__(self, other):
 275         other = self.__class__(other)
 276         if not other.isrel:
 277             raise ValueError, "Right operand should be a relative path"
 278         return self.__class__(itertools.chain(self, other))
 279 
 280     def __radd__(self, other):
 281         if not self.isrel:
 282             raise ValueError, "Right operand should be a relative path"
 283         other = self.__class__(other)
 284         return self.__class__(itertools.chain(other, self))
 285 
 286     def __getslice__(self, *args):
 287         return self.__class__(tuple.__getslice__(self, *args))
 288 
 289     def __mul__(self, *args):
 290         if not self.isrel:
 291             raise ValueError, "Only relative paths can be multiplied"
 292         return self.__class__(tuple.__mul__(self, *args))
 293 
 294     def __rmul__(self, *args):
 295         if not self.isrel:
 296             raise ValueError, "Only relative paths can be multiplied"
 297         return self.__class__(tuple.__rmul__(self, *args))
 298 
 299     def __eq__(self, other):
 300         return tuple.__eq__(self, self.__class__(other))
 301     def __ge__(self, other):
 302         return tuple.__ge__(self, self.__class__(other))
 303     def __gt__(self, other):
 304         return tuple.__gt__(self, self.__class__(other))
 305     def __le__(self, other):
 306         return tuple.__le__(self, self.__class__(other))
 307     def __lt__(self, other):
 308         return tuple.__lt__(self, self.__class__(other))
 309     def __ne__(self, other):
 310         return tuple.__ne__(self, self.__class__(other))
 311         
 312 
 313     # ----------------------------------------------------------------
 314     # Now come the methods which use system calls.
 315 
 316     # --- Path transformation which use system calls
 317 
 318     @classmethod
 319     def cwd(cls):
 320         return cls(os.getcwd())
 321 
 322     def chdir(self):
 323         return os.chdir(str(self))
 324 
 325     def abspath(self):
 326         if not self:
 327             return self.cwd()
 328         if isinstance(self[0], self._OSBaseRoot):
 329             if self[0].isabs:
 330                 return self
 331             else:
 332                 return self[0].abspath() + self[1:]
 333         else:
 334             return self.cwd() + self
 335 
 336     def realpath(self):
 337         return self.__class__(os.path.realpath(str(self)))
 338 
 339     def relpathto(self, dst):
 340         """ Return a relative path from self to dest.
 341 
 342         This method examines self.realpath() and dest.realpath(). If
 343         they have the same root element, a path in the form
 344         path([path.pardir, path.pardir, ..., dir1, dir2, ...])
 345         is returned. If they have different root elements,
 346         dest.realpath() is returned.
 347         """
 348         src = self.realpath()
 349         dst = self.__class__(dst).realpath()
 350 
 351         if src[0] == dst[0]:
 352             # They have the same root
 353             
 354             # find the length of the equal prefix
 355             i = 1
 356             while i < len(src) and i < len(dst) and \
 357                   self.normcasestr(src[i]) == self.normcasestr(dst[i]):
 358                 i += 1
 359 
 360             return [self.pardir] * (len(src) - i) + dst[i:]
 361 
 362         else:
 363             # They don't have the same root
 364             return dst
 365             
 366 
 367     
 368 
 369     # --- Expand
 370 
 371     def expanduser(self):
 372         return path(os.path.expanduser(str(self)))
 373 
 374     def expandvars(self):
 375         return path(os.path.expandvars(str(self)))
 376     
 377 
 378     # --- Info about the path
 379 
 380     def stat(self):
 381         return StatWrapper(os.stat(str(self)))
 382