如何在不安装的情况下阅读Python包元数据?

时间:2015-05-12 10:27:16

标签: python python-3.x pip packaging distutils

我有一个Python程序,它是pip的一个包装器,用于帮助开发Python包。基本上我面临的问题是如何读取包的 Name Version 等元数据(通常是' .tar.gz'和' ; .whl'档案)没有安装。 distutils或其他工具可以执行此操作吗?

只需几个注释......代码是为Python 3编写的,但我正在使用各种Python包,例如 sdist bdist_wheel ,用于Py2和Py3。另外,我只关心我有路径的本地包,而不是PyPi上可用的理论包。

我现在所做的工作很好,但看起来很乱,我想知道是否有更好的工具可以抽象出来。现在我正在阅读存档中的元数据文本文件并手动解析出我需要的字段。如果失败了,我将从包的文件名中删除名称和版本(非常糟糕)。有一个更好的方法吗?以下是我用来解析包 Name Version 的两个函数。


更新

Simeon,感谢您建议使用 wheel 档案中包含的 metadata.json 文件。我不熟悉档案中包含的所有文件,但我希望有一种很好的方法可以解析其中的一些文件。 metadata.json 当然符合轮子的标准。在接受之前,我要打开问题一段时间,看看是否还有其他建议。

无论如何,如果将来有人遇到此问题,我已附上我的更新代码。它可以说是一个更干净的班级,但这就是我现在所拥有的。对于边缘情况来说,它不具有超强的耐用性,所以买家要注意这一切。

import tarfile, zipfile

def getmetapath(afo):
    """
    Return path to the metadata file within a tarfile or zipfile object.

    tarfile: PKG-INFO
    zipfile: metadata.json
    """

    if isinstance(afo, tarfile.TarFile):
        pkgname = afo.fileobj.name
        for path in afo.getnames():
            if path.endswith('/PKG-INFO'):
                return path
    elif isinstance(afo, zipfile.ZipFile):
        pkgname = afo.filename
        for path in afo.namelist():
            if path.endswith('.dist-info/metadata.json'):
                return path

    try:
        raise AttributeError("Unable to identify metadata file for '{0}'".format(pkgname))
    except NameError:
        raise AttributeError("Unable to identify archive's metadata file")


def getmetafield(pkgpath, field):
    """
    Return the value of a field from package metadata file.
    Whenever possible, version fields are returned as a version object.

    i.e. getmetafield('/path/to/archive-0.3.tar.gz', 'name') ==> 'archive'
    """

    wrapper = str

    if field.casefold() == 'version':
        try:
            # attempt to use version object (able to perform comparisons)
            from distutils.version import LooseVersion as wrapper
        except ImportError:
            pass

    # package is a tar archive
    if pkgpath.endswith('.tar.gz'):

        with tarfile.open(pkgpath) as tfo:
            with tfo.extractfile(getmetapath(tfo)) as mfo:
                metalines = mfo.read().decode().splitlines()

        for line in metalines:
            if line.startswith(field.capitalize() + ': '):
                return wrapper(line.split(': ')[-1])

    # package is a wheel (zip) archive
    elif pkgpath.endswith('.whl'):

        import json

        with zipfile.ZipFile(pkgpath) as zfo:
            metadata = json.loads(zfo.read(getmetapath(zfo)).decode())
            try:
                return wrapper(metadata[field.lower()])
            except KeyError:
                pass

    raise Exception("Unable to extract field '{0}' from package '{1}'". \
                    format(field, pkgpath))

2 个答案:

答案 0 :(得分:1)

这种情况并不是很好,这就是创建轮文件的原因。如果您只需要支持wheel文件,那么您可以清理代码,但只要您必须支持<package>-<version>.dist-info源包,您的方法就会有点混乱。

轮子的文件格式在3中指定,因此您既可以解析某些信息的文件名,也可以读取里面metadata.json目录的内容。特别是METADATAmetadata.json非常有用。事实上,阅读metadata.json就足够了,这将导致干净的代码无需安装即可访问该信息。

我会重构代码以使用PKG-INFO并为tar.gz源包实现尽力而为的方法。长期计划是将所有PKG-INFO源包转换为轮子并删除当时过时的static解析代码。

答案 1 :(得分:0)

pkginfo软件包使这一过程变得更加容易。

from pkginfo import Wheel, SDist

def getmeta(pkgpath):
    if pkgpath.endswith('.tar.gz'):
        dist = SDist
    elif pkgpath.endswith('.whl'): 
        dist = Wheel
    pkg = dist(pkgpath)       
    print(pkg.name, pkg.license)