【问题标题】:How to read Python package metadata without installation?如何在不安装的情况下读取 Python 包元数据?
【发布时间】:2015-05-12 10:27:15
【问题描述】:

我有一个 Python 程序,它有点像 pip 的包装器,我用它来协助开发 Python 包。基本上我面临的问题是如何在不安装的情况下读取包的元数据,例如 NameVersion(通常是 '.tar.gz' 和 '.whl' 档案) . distutils 或其他工具可以做到这一点吗?

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

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



更新

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))

【问题讨论】:

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


    【解决方案1】:

    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)
    

    【讨论】:

      【解决方案2】:

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

      wheel 的文件格式在PEP 427 中指定,因此您既可以解析文件名以获取某些信息,也可以读取其中<package>-<version>.dist-info 目录的内容。特别是metadata.jsonMETADATA 非常有用。事实上,阅读metadata.json 就足够了,这将导致无需安装即可访问该信息的干净代码。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-06-03
        • 2018-08-19
        • 2021-10-05
        • 2020-06-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多