如何在Python包元数据中指定要求

6

核心元数据规范记录了元数据字段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 元数据。
1个回答

0
“那么如何在setuptools/distutils中传递Requires-External的语法?”
默认情况下没有,因为既有的distutils也不支持该字段。此外,requires_external关键字参数也不受支持 - 它会被静默忽略,就像任何其他未知的关键字参数一样。
要添加对requires_external关键字参数的本地支持,您需要提供自己的分发实现。最小示例(需要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())。我认为最简单的方法是通过自定义的egg_info实现事后修改PKG-INFO
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实现:

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元数据。此外,它似乎有点侵入性(在猴子补丁的基础上再次进行猴子补丁!),您确定所有这些setuptools的部分都是公共API吗?特别是_DISTUTILS_UNSUPPORTED_METADATA具有下划线前缀。 - platypus
它在.tar.gz内部生成了两个不同的PKG-INFO文件 - 这是distutils的一部分,与答案中的代码无关。在空目录中运行 python -c 'from setuptools import setup; setup()' sdist 也会在源分发文件中编写两个 PKG-INFO 文件。 - hoefling
似乎有点侵入性(在猴子补丁的基础上再打一个猴子补丁!)- 我在代码中没有打任何猴子补丁。你在说什么? - hoefling
你确定所有这些setuptools的部分都是公共API吗?是的,我知道使用私有API的部分,这就是为什么我指出这是一个“最小示例”,因为你几乎不可能写得更短。如果你想要以显式和冗长的方式完成所有操作,你需要编写一个Distribution.__init__方法,从attrs中读取和验证自定义kwargs,并在元数据中重新分配它们。我只是为了演示目的而采取了捷径。 - hoefling
我明白了。我有点惊讶核心元数据规范中有这个字段,但工具中却没有任何支持,你觉得这有点奇怪吗?我会接受这个答案,但如果不麻烦的话,您能否更新代码,使其不会在dist内写入两个_不同的_ PKG-INFO文件。如果必须有两个文件,则它们应该至少具有相同的元数据,否则容易出错(根据您选择使用的PKG-INFO文件获取不同的元数据?)。 - platypus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接