view paint/package.py @ 14:6d27c2136163

a decent structure; need to reuse more
author Jeff Hammel <jhammel@mozilla.com>
date Fri, 24 Feb 2012 15:50:31 -0800
parents 0dd1f8f83be2
children 8c8b7482772f
line wrap: on
line source

"""
package model for python PAckage INTrospection
"""

import os
import shutil
import tarfile
import tempfile
import urllib2
import utils

__all__ = ['Package']

class Package(object):
    # XXX much of this is generic resource stuff and should be split off

    def __init__(self, src):
        self.src = src

        # ephemeral data
        self._tmppath = None
        self._egg_info = None

    def path(self):
        """filesystem path to package"""

        # return cached copy if it exists
        if self._tmppath:
            return self._tmppath

        # fetch from the web if a URL
        tmpfile = None
        src = self.src
        if utils.isURL(self.src):
            tmpfile = src = self.fetch()

        # unpack if an archive
        if self.is_archive(src):
            try:
                self.unpack(src)
            finally:
                if tmpfile:
                    os.remove(tmpfile)
            return self._tmppath

        return self.src

    def fetch(self):
        """fetch from remote source to a temporary file"""
        fd, filename = tempfile.mkstemp()
        resource = urllib2.urlopen(self.src)
        os.write(fd, resource.read())
        os.close(fd)
        return filename

    def unpack(self, archive):
        """unpack the archive to a temporary destination"""
        # TODO: should handle zipfile additionally at least
        # Ideally, this would be pluggable, etc
        assert tarfile.is_tarfile(archive), "%s is not an archive" % self.src
        tf = tarfile.TarFile.open(archive)
        self._tmppath = tempfile.mkdtemp()
        tf.extractall(path=self._tmppath)

    def is_archive(self, path):
        """returns if the filesystem path is an archive"""
        # TODO: should handle zipfile additionally at least
        # Ideally, this would be pluggable, etc
        return tarfile.is_tarfile(path)

    def cleanup(self):
        if self._tmppath:
            shutil.rmtree(self._tmppath)
        self._tmppath = None

    __del__ = cleanup

    ### python-package-specific functionality

    def egg_info(self):
        """build the egg_info directory"""

        if self._egg_info:
            # return cached copy
            return self._egg_info

        directory = self.path()
        assert os.path.exists(os.path.join(path, 'setup.py'))

        # setup the egg info
        call([sys.executable, 'setup.py', 'egg_info'], cwd=directory, stdout=PIPE)

        # get the .egg-info directory
        egg_info = [i for i in os.listdir(directory)
                    if i.endswith('.egg-info')]
        assert len(egg_info) == 1, 'Expected one .egg-info directory in %s, got: %s' % (directory, egg_info)
        egg_info = os.path.join(directory, egg_info[0])
        assert os.path.isdir(egg_info), "%s is not a directory" % egg_info

        # cache it
        self._egg_info = egg_info
        return self._egg_info

    def info(self):
        """return info dictionary for package"""
        # could use pkginfo

        egg_info = self.egg_info()

        # read the package information
        pkg_info = os.path.join(egg_info, 'PKG-INFO')
        info_dict = {}
        for line in file(pkg_info).readlines():
            if not line or line[0].isspace():
                continue # XXX neglects description
            assert ':' in line
            key, value = [i.strip() for i in line.split(':', 1)]
            info_dict[key] = value

        # return the information
        return info_dict['Name'], dependencies

    def dependencies(self):
        """return the dependencies"""

        egg_info = self.egg_info()

        # read the dependencies
        requires = os.path.join(egg_info, 'requires.txt')
        if os.path.exists(requires):
            dependencies = [i.strip() for i in file(requires).readlines() if i.strip()]
        else:
            dependencies = []