【问题标题】:How to specify requirements in Python packages metadata如何在 Python 包元数据中指定要求
【发布时间】:2021-12-17 02:31:15
【问题描述】:

Core metadata specification 记录了元数据字段Requires-External,它似乎用于指定系统(非python)依赖项。

您实际上是如何指定此字段的?这是我尝试过的:

.
├── mypackage
│   └── __init__.py
└── setup.py

setup.py的内容

from setuptools import setup

setup(
    name="mypackage",
    description="blah blah",
    url='https://example.org',
    version="0.1",
    packages=["mypackage"],
    requires_external=[
        "C",
        "libpng (>=1.5)",
        'make; sys_platform != "win32"',
    ],
)

当我构建这个包时,元数据并未包含在内

Metadata-Version: 2.1
Name: mypackage
Version: 0.1
Summary: blah blah
Home-page: https://example.org
License: UNKNOWN
Platform: UNKNOWN

UNKNOWN

那么将 Requires-External 传递给 setuptools/distutils 的语法是什么?注意:这个问题不是询问 Requires-Dist 元数据。

【问题讨论】:

    标签: python metadata setuptools pypi distutils


    【解决方案1】:

    那么将 Requires-External 传递给 setuptools/distutils 的语法是什么?

    默认情况下没有,因为distutilssetuptools 都不支持该字段。此外,requires_external 关键字 arg 也不受支持 - 它被默默忽略,就像任何其他未知关键字 arg 一样。

    要添加对requires_external kwarg 的本地支持,您需要提供自己的分发实现。最小示例(需要 Python 3.9):

    from setuptools.dist import Distribution as DistributionOrig
    
    class Distribution(DistributionOrig):
        _DISTUTILS_UNSUPPORTED_METADATA = DistributionOrig._DISTUTILS_UNSUPPORTED_METADATA | {'requires_external': dict}
    

    当与distclass 配对时,您现在可以将requires_external 传递给setup()

    setup(
        ...,
        requires_external=[
            'C',
            'libpng (>=1.5)',
            'make; sys_platform != "win32"',
        ],
        distclass=Distribution,
    )
    

    下一个目标是将requires_external的内容实际写入PKG-INFO。通过分发元数据本身来做到这一点非常棘手,因为setuptools 用自己的整体实现修补了相关方法(DistributionMetadata.read_pkg_file()DistributionMetadata.write_pkg_file())。这个 IMO 最简单的方法是通过自定义 egg_info impl 修改 PKG-INFO post factum:

    import email
    from pathlib import Path
    from setuptools.command.egg_info import egg_info as egg_info_orig
    
    
    class egg_info(egg_info_orig):
        def run(self):
            super().run()
            # PKG-INFO is now guaranteed to exist
            pkg_info_file = Path(self.egg_info, 'PKG-INFO')
            pkg_info = email.message_from_bytes(pkg_info_file.read_bytes())
            for req in self.distribution.metadata.requires_external:
                pkg_info.add_header('Requires-External', req)
            pkg_info_file.write_bytes(pkg_info.as_bytes())
    

    setup() 中通过cmdclass 参数传递您自己的egg_info impl:

    setup(
        ...,
        requires_external=[
            'C',
            'libpng (>=1.5)',
            'make; sys_platform != "win32"',
        ],
        distclass=Distribution,
        cmdclass={'egg_info': egg_info},
    )
    

    【讨论】:

    • 感谢您的建议。这实际上适用于 bdist_wheel,但不适用于 sdist。它在 .tar.gz 中创建了两个不同的 PKG-INFO 文件,一个位于根目录,一个位于 .egg-info 子目录中,其中只有一个文件具有 Requires-External 元数据。此外,它似乎有点侵入性(monkeypatch 在monkeypatch 之上!),您确定 setuptools 的所有这些部分都是公共 API 吗? _DISTUTILS_UNSUPPORTED_METADATA 特别是有一个下划线前缀。
    • 它在 .tar.gz 中创建了两个不同的 PKG-INFO 文件 - 这是distutils 的一部分,与答案中的代码无关。在空目录中运行 python -c 'from setuptools import setup; setup()' sdist 也会在源 dist 中写入两个 PKG-INFO 文件。
    • 而且它似乎有点侵入性(monkeypatch 位于monkeypatch 之上!) - 我不会在代码中的任何地方进行monkeypatch。你在说什么?
    • 你确定 setuptools 的所有这些部分都是公共 API 吗? 是的,我知道使用部分私有 API,这就是为什么我指出这是一个 最小的例子,因为你很难把它写得更短。如果您想明确地完成这一切,您可以编写一个Distribution.__init__ 方法,从attrs 读取并验证自定义kwargs,然后在元数据中重新分配它们。我只是出于演示的目的使用了快捷方式。
    • 我明白了。我有点惊讶核心元数据规范中有这个字段,但在工具中对它的支持为零,你不觉得这有点奇怪吗?我会接受这个答案,但我认为如果不是太麻烦的话,你可以更新代码,这样它就不会在 dist.xml 中写入两个 不同 PKG-INFO 文件。如果必须有两个,它们至少应该具有相同的元数据,否则容易出错(根据您选择使用的 PKG-INFO 文件获得不同的元数据?)。
    猜你喜欢
    • 1970-01-01
    • 2021-12-28
    • 1970-01-01
    • 2022-01-14
    • 2014-01-10
    • 1970-01-01
    • 2021-09-01
    • 2021-08-30
    • 1970-01-01
    相关资源
    最近更新 更多