[Python-Dev] zipfile.py, file system abstractions

Fred L. Drake, Jr. fdrake@acm.org
Thu, 3 Feb 2000 10:42:42 -0500 (EST)


--rDGr2BijA3
Content-Type: text/plain; charset=us-ascii
Content-Description: message body text
Content-Transfer-Encoding: 7bit


  At one point we discussed file system abstractions on this list.  Do 
we want to pursue the idea?  I have an implementation that's been
tested on Unix:  a Filesystem instance refers to either the entire
"native" filesystem, or a subset of a filesystem (either a filesystem
object or the native filesystem), based on a "root" that's a directory 
within the parent filesystem.
  There'd need to be some work done to make sure it works properly on
Windows and the Mac, but I don't think that would be a lot of work.
  I think this should be available as an abstraction in Python.
Implementing this on top of a ZIP/JAR file is also useful.  If Jim
A.'s zipfile.py will be added to the standard library in 1.6, I'd like 
to add a Filesystem class that operates on a zipfile object.
  Any thoughts?
  If you want this, someone needs to check in and document
zipfile.py.  ;)  I'll add filesys.py & it's documentation.


  -Fred

--
Fred L. Drake, Jr.	  <fdrake at acm.org>
Corporation for National Research Initiatives


--rDGr2BijA3
Content-Type: text/x-python
Content-Description: Filesystem abstraction for Python
Content-Disposition: inline;
	filename="filesys.py"
Content-Transfer-Encoding: 7bit

"""FilesystemAPI.Filesystem implementations.

These aren't actually connected to ILU in any way.
"""
__version__ = '$Revision: 1.7 $'


# This probably doesn't work anywhere that doesn't use Unix-style
# pathnames.


class Filesystem:
    def __init__(self, fs=None, root=None):
        if fs:
            self._fs = fs
            self._path = fs.path
            self._open = fs.open
        else:
            import os, __builtin__
            self._fs = os
            self._path = os.path
            self._open = __builtin__.open
        self._cwd = self._fs.sep
        self.pardir = self._fs.pardir
        self.curdir = self._fs.curdir
        self.sep = self._fs.sep
        if root:
            self._root = root = self._path.abspath(root)
            self.path = SubsettingPath(self, self._path, root)
        else:
            self._root = ''
            self.path = SimplePath(self, self._path)

    def __getinitargs__(self):
        return (self._fs, self._root)

    def chdir(self, path):
        return self.__pathcall(self._fs.chdir, path)

    def fstat(self, file):
        try:
            return self._fs.fstat(file)
        except IOError, e:
            # make sure no filename information leaks out
            if hasattr(file, "name"):
                e.filename = file.name
            else:
                e.filename = None
            raise e

    def getcwd(self):
        return self._cwd

    def listdir(self, path):
        return self.__pathcall(self._fs.listdir, path)

    def lstat(self, path):
        return self.__pathcall(self._fs.lstat, path)

    def mkdir(self, path):
        self.__pathcall(self._fs.mkdir, path)

    def fopen(self, path, mode="r"):
        name = self.path._normpath(path)
        try:
            f = self._open(self._root + name, mode)
        except IOError, e:
            e.filename = path
            raise e
        if self._root:
            f = SubsettingFile(f, path, mode)
        return f

    def remove(self, path):
        return self.__pathcall(self._fs.remove, path)

    def rmdir(self, path):
        return self.__pathcall(self._fs.rmdir, path)

    def stat(self, path):
        return self.__pathcall(self._fs.stat, path)

    def unlink(self, path):
        return self.__pathcall(self._fs.unlink, path)

    def __pathcall(self, func, path):
        name = self.path._normpath(path)
        try:
            return func(self._root + name)
        except IOError, e:
            e.filename = path
            raise e


class SubsettingFile:
    """Class used to mask a real file when the origin filesystem is being
    'subsetted'.

    This avoids revealing the real file name without restricting the
    ability to use the file as a replacement sys.stdout (which needs
    softspace support).

    """
    def __init__(self, file, path, mode):
        self.__dict__['_file'] = file
        self.__dict__['_mode'] = mode
        self.__dict__['_path'] = path
        self.__dict__['name'] = path

    def __getattr__(self, name):
        v = getattr(self._file, name)
        if callable(v) and not self.__dict__.has_key(name):
            v = SubsettingMethod(v, self._path).call
            self.__dict__[name] = v
        return v

    def __setattr__(self, name, value):
        setattr(self._file, name, value)

    def __repr__(self):
        oc = self._file.closed and "closed" or "open"
        return "<%s file %s, mode %s at %x>" \
               % (oc, `self._path`, `self._mode`, id(self))


class SubsettingMethod:
    def __init__(self, method, path):
        self.method = method
        self.path = path

    def call(self, *args, **kw):
        try:
            return apply(self.method, args, kw)
        except IOError, e:
            e.filename = self.path
            raise e


class BasePath:
    def __init__(self, fs, realpath):
        self._fs = fs
        self._path = realpath
        self._prefix = ''
        try:
            fs.stat(fs.curdir)
            self._have_stat = 1
        except AttributeError:
            self._have_stat = 0

    def __getattr__(self, name):
        v = getattr(self._path, name)
        setattr(self, name, v)
        return v

    def abspath(self, path):
        if not self.isabs(path):
            path = self._path.join(self._fs.getcwd(), path)
        return self.normpath(path)

    def basename(self, path):
        return self._path.basename(path)

    def commonprefix(self, list):
        return self._path.commonprefix(list)

    def dirname(self, path):
        return self._path.dirname(path)

    def exists(self, path):
        name = self._normpath(path)
        try:
            return self._path.exists(self._prefix + name)
        except IOError, e:
            e.filename = path
            raise e

    def isabs(self, path):
        return self._path.isabs(path)

    def isdir(self, path):
        name = self._normpath(path)
        try:
            return self._path.isdir(self._prefix + name)
        except IOError, e:
            e.filename = name
            raise e

    def isfile(self, path):
        name = self._normpath(path)
        try:
            return self._path.isfile(self._prefix + name)
        except IOError, e:
            e.filename = path
            raise e

    def join(self, *args):
        return apply(self._path.join, args)

    def normcase(self, path):
        return self._path.normcase(path)

    def normpath(self, path):
        return self._path.normpath(path)

    def samefile(self, path1, path2):
        """Return true if both pathname arguments refer to the same
        file or directory.

        If the underlying filesystem supports stat(), the comparison
        is made using samestat(), otherwise a simple string comparison 
        is used.

        """
        if self._have_stat:
            return self.samestat(self._fs.stat(path1), self._fs.stat(path2))
        else:
            p1 = self._normpath(path1)
            p2 = self._normpath(path2)
            return p1 == p2

    def sameopenfile(self, file1, file2):
        stat1 = self._fs.fstat(file1)
        stat2 = self._fs.fstat(file2)
        return self.samestat(stat1, stat2)

    def split(self, path):
        return self._path.split(path)

    def splitdrive(self, path):
        return self._path.splitdrive(path)

    def splitext(self, path):
        return self._path.splitext(path)

    def walk(self, root, func, arg):
        queue = [root]
        while queue:
            dir = queue[0]
            del queue[0]
            if self.isdir(dir):
                files = self._fs.listdir(dir)
                func(arg, dir, files)
                for file in files:
                    path = self.join(dir, file)
                    if self.isdir(path):
                        queue.append(path)


class SimplePath(BasePath):
    def __getinitargs__(self):
        return (self._fs, self._path)

    def _normpath(self, path):
        return self.abspath(path)


class SubsettingPath(BasePath):
    """os.path equivalent that pretends some directory down in the
    tree is the root.

    This class works relative to a filesystem and path-math object that
    are passed to the constructor.

    """
    def __init__(self, fs, realpath, prefix):
        BasePath.__init__(self, fs, realpath)
        self._prefix = prefix
        self._rootref = "%s%s%s" \
                        % (self._fs.sep, self._fs.pardir, self._fs.sep)

    def __getinitargs__(self):
        return (self._fs, self._path, self._prefix)

    def _normpath(self, path):
        """Return the absolute pathname for path, relative to the
        filesystem object."""
        p = self.abspath(path)
        while p[:len(self._rootref)] == self._rootref:
            # on Unix, /.. refers to /, on Windows, \.. refers to \ (yech!)
            p = p[len(self._rootref) - len(self._fs.sep):]
        if not self.isabs(p):
            raise IOError(errno.ENOENT, "No such file or directory", path)
        return p

--rDGr2BijA3--